[Sailplane Flying Game] [http://www.psnw.com/~alcald/screensh.jpg] ---- This is a project I started while trying to teach myself Tcl/Tk. It is a moving map using an aviation sectional chart. You can fly sailplanes over the map after selecting a set of goals, usually a triangular course. You can launch multiple sailplanes. Give each one a unique "contest ID". You must "stop flying" in order to launch a new sailplane. You can control the sailplane who's ID is in the box with the arrow keys. Left and Right arrow steer left and right. Up arrow speeds up the sailplane. Down arrow slows it down. Your sailplane starts with 5000 ft. of altitude. There is a polar performance curve similar to a real sailplane, so if you try to fly faster to get ahead, you will lose altitude faster and risk "landing out" or running out of altitude. You can regain lost altitude by slowing down and lingering in randomly generated thermals of varying strength that appear on the map as brown smoke stack like images. Flying faster does not make you win. The pilot that makes the best judgement of how fast or slow to fly, given the lift conditions, will win. In general, if the lift conditions are strong and lots of thermals can be reached, it will pay to fly fast. If the lift conditions are weak, it will take much longer to regain lost altitude, so it will pay you to fly slower between thermals and conserve your altitude. If you run out of altitude, you "land out" and the game is over for you. There is one consolation not available in a real sailplane race, if you can maneuver your glider behind an opponent, and line up your course in his or her direction, pressing the space bar will launch a cruise missle in his or her direction. If it hits within the bounding box of the opponents glider, he or she will disintegrate and be removed from competition. '''Alex Caldwell M.D.''' ---- '''Problems''' * The steering is wierd. A real sailplane circles in thermals. These can only slow down and make kind of "S" turns. The steering input is not linear, as you add input, the glider starts flying off course in a sort of exponentially faster rate, so you have to keep the inputs small. * The distance calculations for this particular map are all hard coded in the program. Need to develop ability to import any map and calibrate flexibly, so the distances can be calculated. * The code violates a lot of good Tcl coding practices, which I did not know when I started out, so it may be sort of obfuscated. ---- '''Goals''' * I have a preliminary network game version that connects to a server, so two clients can play each other from remote network computers, but it has a few problems. * Use Tcl's new serial connection abilities to connect directly to a GPS receiver and plot courses in real time. Possibly on a PDA. The program is able now to read a Garmin .trk file and plot the positions on the map. * A 3-D version using Tkogl, the Tcl extension to OpenGL. ---- [RS]: Thanks for showing this to us! Next question would be, whether it can be downloaded at some place? ---- Here's the code.(I haven't finished pasting it all in yet). You also need these two files saved in the same directory for the images: * http://www.psnw.com/~alcald/largeclr.gif * http://www.psnw.com/~alcald/pic45tra.gif # program for Tcl/Tk (free standing version - not for plug-in) # By Alex Caldwell M.D. # alcald@psnw.com # Jan 5, 1997 # Feb 11, 1999 # Feb 16, 1999 # Feb 21, 1999 global i ;# variable that stores background image data for sectional map global g ;# variable that stores background image data for little gliders global bmp ;# variable that stores bitmap image global IC ;# used in great circle distance calculation global COURSE ;# compass course calculated from the course leg selected global HEADING ;# an array that holds the currently selected contestant's heading global TURNPOINT1 ;# variable that stores the name of turnpoint1 global TURNPOINT2 ;# variable that stores the name of turnpoint2 global turnpoints ;# list that stores the list of turnpoint names global latdegrees ;# list that stores a list of the turnpoint latitude degrees with the minutes truncated. global latmin global londegrees global lonmin global latdeg1 global latmin1 global londeg1 global lonmin1 global latdeg2 global latmin2 global londeg2 global lonmin2 global XAXIS ;# variable that stores the difference between the x coordinates of the two turnpoints. global YAXIS ;# variable that stores the difference between the y coordinates of the two turnpoints. global X1 ;# the x coordinate of the first turnpoint. global X2 ;# the x coordinate of the second turnpoint. global Y1 ;# the y coordinate of the first turnpoint. global Y2 ;# the y coordinate of teh second turnpoint. global SPEED ;# an array that stores the value of each contestant's current speed global ALTITUDE ;# an array that stores the value of each contestant's current altitude global text ;# an array that stores the value of each contestant's contest no. global ID ;# an array that stores the id of each contestant's small altitude text display global shadow ;# an array that stores the id of each contestant's shadow so it can be moved closer as altitude decreases global fileposition global filecounter global filelinenumber global fileid global glider ;# an array that stores the canvas id no.'s of the glider images. global contestno ;# a variable that stores the current contest no. global yfraction ;# I can't remember what this was supposed to do but I was afraid to delete it global xfraction ;# I can't remember what this was supposed to do but I was afraid to delete it global combatants ;# a list of the players by their contest no. global thermals ;# a list of the thermals by no. global thermalstrength ;# an array that stores the strength of each thermal global daystrength ;# a variable that sets the ramndom range for the average thermal strength global autoscroll ;# stores value of variable that controls whether the map scrolls along with the glider global tracking ;# variable that controls whether tracking is on or off global distanceflown ;# array storing the value of distance flown for each glider set contestno IB set yfraction 0 set xfraction 0 set TURNPOINT1 "Avenal" set TURNPOINT2 "Delano" set SPEED(IB) 60 set ALTITUDE(IB) 5000 set HEADING(IB) 0 set fileposition 332 set filecounter 0 set filelinenumber 0 set daystrength 6 set autoscroll on set tracking on ################################################################### set turnpoints {Avenal Corcoran Delano Porterville "New Cuyama"} set latdegrees {36 36 35 36 34} set latmin {0 6.15 44.74 1.78 57} set londegrees {120 119 119 119 119} set lonmin {8 35.69 14.19 3.76 42} #################################################################### proc RandomInit { seed } { global randomSeed set randomSeed $seed } proc Random {} { global randomSeed set randomSeed [expr ($randomSeed*9301 + 49297) % 233280] return [expr $randomSeed/double(233280)] } proc RandomRange { range } { expr int([Random]*$range) } RandomInit [pid] #**************************************************************************************** proc getturnpoint {} { global TURNPOINT1 global TURNPOINT2 global X1 X2 Y1 Y2 global contestno global SPEED global HEADING global daystrength frame .turnpointframe frame .turnpointframe.turnpointframe1 frame .turnpointframe.turnpointframe2 label .turnpointframe.turnpointframe1.label1 -text {Select Turnpoint 1} -relief groove label .turnpointframe.turnpointframe2.label2 -text {Select Turnpoint2 } -relief groove menubutton .turnpointframe.turnpointframe1.menu1 -text $TURNPOINT1 \ -menu .turnpointframe.turnpointframe1.menu1.sub1 -relief raised set men1 [menu .turnpointframe.turnpointframe1.menu1.sub1] $men1 add radio -label Avenal -variable TURNPOINT1 -value Avenal \ -command ".turnpointframe.turnpointframe1.menu1 configure -text Avenal" $men1 add radio -label Corcoran -variable TURNPOINT1 -value Corcoran \ -command ".turnpointframe.turnpointframe1.menu1 configure -text Corcoran" $men1 add radio -label Delano -variable TURNPOINT1 -value Delano \ -command ".turnpointframe.turnpointframe1.menu1 configure -text Delano" $men1 add radio -label Porterville -variable TURNPOINT1 -value Porterville \ -command ".turnpointframe.turnpointframe1.menu1 configure -text Porterville" $men1 add radio -label {New Cuyama} -variable TURNPOINT1 -value {New Cuyama} \ -command ".turnpointframe.turnpointframe1.menu1 configure -text {New Cuyama}" menubutton .turnpointframe.turnpointframe2.menu2 -text $TURNPOINT2 \ -menu .turnpointframe.turnpointframe2.menu2.sub1 -relief raised set men2 [menu .turnpointframe.turnpointframe2.menu2.sub1] $men2 add radio -label Avenal -variable TURNPOINT2 -value Avenal \ -command ".turnpointframe.turnpointframe2.menu2 configure -text Avenal" $men2 add radio -label Corcoran -variable TURNPOINT2 -value Corcoran \ -command ".turnpointframe.turnpointframe2.menu2 configure -text Corcoran" $men2 add radio -label Delano -variable TURNPOINT2 -value Delano \ -command ".turnpointframe.turnpointframe2.menu2 configure -text Delano" $men2 add radio -label Porterville -variable TURNPOINT2 -value Porterville \ -command ".turnpointframe.turnpointframe2.menu2 configure -text Porterville" $men2 add radio -label {New Cuyama} -variable TURNPOINT2 -value {New Cuyama} \ -command ".turnpointframe.turnpointframe2.menu2 configure -text {New Cuyama}" label .turnpointframe.turnpointframe1.contestnolabel -text {Enter Contest No.} label .turnpointframe.turnpointframe1.daystrength -text {Thermal Strength} entry .turnpointframe.turnpointframe2.contestnoentry -textvariable contestno -width 5 menubutton .turnpointframe.turnpointframe2.daystrength -text weak \ -menu .turnpointframe.turnpointframe2.daystrength.m -relief raised menu .turnpointframe.turnpointframe2.daystrength.m .turnpointframe.turnpointframe2.daystrength.m add radio -label weak -variable daystrength -value 6 .turnpointframe.turnpointframe2.daystrength.m add radio -label medium -variable daystrength -value 11 .turnpointframe.turnpointframe2.daystrength.m add radio -label strong -variable daystrength -value 18 button .turnpointframe.updatebutton -text "Start New Leg" -command {destroybuttons getcoord calcdist $latdeg1 $latmin1 $londeg1 $lonmin1 $latdeg2 $latmin2 $londeg2 $lonmin2 calcheading $latdeg1 $latmin1 $londeg1 $lonmin1 $latdeg2 $latmin2 $londeg2 $lonmin2 plotline $latdeg1 $latmin1 $londeg1 $lonmin1 $latdeg2 $latmin2 $londeg2 $lonmin2 showdist} button .turnpointframe.launchglider -text "Launch Glider" -command {launchGlider;destroybuttons;showdist} button .turnpointframe.flybutton -text {Start Flying} -command {displayInfo} scale .turnpointframe.speedscale -from 40 -to 200 -length 250 -variable SPEED($contestno) -orient horizontal \ -label "Speed $contestno - kts" -tickinterval 50 -showvalue true bind . {set SPEED($contestno) [expr $SPEED($contestno) - 1]} bind . {set SPEED($contestno) [expr $SPEED($contestno) + 1]} scale .turnpointframe.headingscale -from -75 -to 75 -length 250 -variable HEADING($contestno) -orient horizontal \ -label "Steer $contestno - L/R" -tickinterval 15 -showvalue true bind . {set HEADING($contestno) [expr $HEADING($contestno) -1]} bind . {set HEADING($contestno) [expr $HEADING($contestno) +1]} button .turnpointframe.gpsbutton1 -text {last GPS fix} -command {gpsdisplay1} button .turnpointframe.gpsbutton2 -text {plot all GPS fixes} -command {gpsdisplay2} pack .frame .turnpointframe -side left pack .turnpointframe.turnpointframe1 .turnpointframe.turnpointframe2 -side left pack .turnpointframe.turnpointframe1.label1 pack .turnpointframe.turnpointframe1.menu1 -padx 5 -pady 5 pack .turnpointframe.turnpointframe2.label2 pack .turnpointframe.turnpointframe2.menu2 -padx 5 -pady 5 pack .turnpointframe.turnpointframe1.contestnolabel pack .turnpointframe.turnpointframe1.daystrength pack .turnpointframe.turnpointframe2.contestnoentry pack .turnpointframe.turnpointframe2.daystrength pack .turnpointframe.updatebutton .turnpointframe.launchglider \ .turnpointframe.flybutton .turnpointframe.gpsbutton1 \ .turnpointframe.gpsbutton2 \ .turnpointframe.speedscale .turnpointframe.headingscale } #********************************************************************************************* proc destroybuttons {} { foreach item [winfo children .frame] { destroy $item } } #********************************************************************************************* # this is for launching a new glider whenever the launch glider button is pressed. # it is always launched at the first turnpoint that is currently selected with 5000 ft of altitude. proc launchGlider { } { global XAXIS YAXIS COURSE ALTITUDE ID shadow contestno combatants X1 Y1 glider text set Y1 [expr $Y1 * 2136] set X1 [expr $X1 * 1759] # this creates the glider image # depending on the direction of flight, a different image is chosen. # this creates the glider image and the shadow image # this creates the shadow of the glider first so the glider will be over it in the object order if {$XAXIS < 0 } { if {$COURSE > 225 } { set shadow($contestno) [.c.canvas create image [expr $X1 + ($XAXIS/2)] [expr $Y1 + ($YAXIS/2)] \ -image glidershadowwest -anchor center -tags "st $contestno"] set glider($contestno) [.c.canvas create image $X1 $Y1 \ -image gliderpairwest -anchor center -tags "st $contestno"] } else { set shadow($contestno) [.c.canvas create image [expr $X1 + ($XAXIS/2)] [expr $Y1 + ($YAXIS/2)] \ -image glidershadowsouthwest -anchor center -tags "st $contestno"] set glider($contestno) [.c.canvas create image $X1 $Y1 \ -image gliderpairsouthwest -anchor center -tags "st $contestno"] } } else { if {$COURSE > 28 && $COURSE < 349} { set shadow($contestno) [.c.canvas create image [expr $X1 + ($XAXIS/2)] [expr $Y1 + ($YAXIS/2)] \ -image glidershadow -anchor center -tags "st $contestno" ] set glider($contestno) [.c.canvas create image $X1 $Y1 \ -image gliderpair -anchor center -tags "st $contestno"] } else { set shadow($contestno) [.c.canvas create image [expr $X1 + ($XAXIS/2)] [expr $Y1 + ($YAXIS/2)] \ -image glidershadownortheast -anchor center -tags "st $contestno" ] set glider($contestno) [.c.canvas create image $X1 $Y1 \ -image gliderpairnortheast -anchor center -tags "st $contestno"] } } # place the contest no near the glider and move it along with it. set text($contestno) [.c.canvas create text [expr $X1 + 22] [expr $Y1 + 3] \ -text "$contestno" -fill red -tags "st $contestno"] # place some text that has the altitude displayed near the glider image set ALTITUDE($contestno) 5000 set ID($contestno) [.c.canvas create text [expr $X1 + 22] [expr $Y1 + 25] \ -text "$ALTITUDE($contestno)" -fill black -tags "st $contestno"] # this raises the gliders and their shadows above the course line .c.canvas raise st line # these lines scroll the map to center the first turnpoint where the new glider is launched. set Y1 [expr $Y1/2136] ... not finished pasting in code yet