#!/usr/bin/python # Python spacewar # Steve Holland, 2001 from Tkinter import * from math import * from time import * from string import * # parameters: rubber_wall=0 wrap_wall=1 sun_radius=15 planet_radius=7 ship_radius=6 key_deltav=2 bullet_velocity=80 bullet_timeout=3 # seconds max_timestep=0.2 # seconds min_timestep=0.01 # seconds (must be smaller than min_timestep) shieldrecoveryrate=.5 # percent per second energyrecoveryrate=10.0 # percent per second bulletenergy=20.0 # percent hitdamage=30.0 # percent bouncedamage=15.0 # percent K=150.0 # gravitational constant # flags for allowable bounces shipbounce=1 planetbounce=1 sunbounce=1 class graphel: graphid=None coordlist=(0.0,0.0,0.0,0.0) # relative to origin at (0,0) class object: # constants # for graphictype SUN = 0 SHIP = 1 PLANET = 2 BULLET = 3 # variables name="" mass=1.0 # mass pos = [0.0, 0.0] # x position, y position vel = [0.0, 0.0] # x velocity, y velocity orient = -40 # orientation, degrees graphicid = None # link to on-screen graphic list (class graphel) graphictype = SUN timeout=0.0 collision_radius=3.0 shields=100.0 energy=100.0 #flags fixed=0 # not allowed to move deathflag=0 # non-zero indicates impending death # end class object def addvec(a,b): res=[] if type(b) != type(0) and type(b)!=type(0.0) : for i in range(len(a)): res=res+[a[i]+b[i],] else : for i in range(len(a)): res=res+[a[i]+b,] return res # end function addvec def subvec(a,b): res=[] if type(b) != type(0) and type(b)!=type(0.0) : for i in range(len(a)): res=res+[a[i]-b[i],] else : for i in range(len(a)): res=res+[a[i]-b,] return res # end function subvec def divvec(a,b): res=[] if type(b) != type(0) and type(b)!=type(0.0) : for i in range(len(a)): res=res+[a[i]/b[i],] else : for i in range(len(a)): res=res+[a[i]/b,] return res # end function divvec def mulvec(a,b): res=[] if type(b) != type(0) and type(b)!=type(0.0) : for i in range(len(a)): res=res+[a[i]*b[i],] else : for i in range(len(a)): res=res+[a[i]*b,] return res # end function mulvec def dotvec(a,b): # dot product res=0.0 for i in range(len(a)): res=res+a[i]*b[i] return res # end function dotvec def findship(name) : for obj in objlist : if obj.name == name : return obj return None # end function findship def bounce(targ,coll): # coll and targ are class objects involved in # collision # returns non-zero if bounce actually occurred # (should reduce shields) # zero indicates objects still in contact, but # bounce already took place dist=subvec(coll.pos,targ.pos) direc=divvec(dist,r) # Unit vector from targ -> coll # negative closing velocity indicates objects approaching each other closing_velocity=dotvec(subvec(coll.vel,targ.vel),direc) if (closing_velocity <= 0.0) : coll_component=dotvec(coll.vel,direc) targ_component=dotvec(targ.vel,direc) coll_component_new=(2*targ.mass*targ_component-targ.mass*coll_component+coll.mass*coll_component)/(targ.mass+coll.mass) targ_component_new=(2*coll.mass*coll_component-coll.mass*targ_component+targ.mass*targ_component)/(targ.mass+coll.mass) if (not coll.fixed) : coll.vel=addvec(coll.vel,mulvec(direc,(coll_component_new-coll_component))) if (not targ.fixed) : targ.vel=addvec(targ.vel,mulvec(direc,(targ_component_new-targ_component))) return 1 return 0 # end function bounce def screencoords(coords,pos): global scrwidth,scrheight res=() for i in range(len(coords)/2): res=res+(coords[2*i]+scrwidth/2+pos[0],) res=res+(coords[2*i+1]+scrheight/2-pos[1],) return res # end function screencoords() def shipaccel(ship): global key_deltav ship.vel=addvec(ship.vel,(key_deltav*sin(ship.orient*pi/180),key_deltav*cos(ship.orient*pi/180))) def ship0accel(event): ship0=findship("ship0") if ship0 : shipaccel(ship0) def ship1accel(event): ship1=findship("ship1") if ship1 : shipaccel(ship1) def shipfire(ship): global draw,objlist,bullet_velocity,bullet_timeout,gametime # create bullet if ship.energy < bulletenergy : return # not enough energy ship.energy=ship.energy-bulletenergy bullet=object() bullet.mass=.0001 bullet.graphictype=object.BULLET bullet.pos=[0.0,0.0] bullet.pos[0]=ship.pos[0]+ship_radius*2*sin(ship.orient*pi/180) bullet.pos[1]=ship.pos[1]+ship_radius*2*cos(ship.orient*pi/180) bullet.vel=[0.0,0.0] bullet.vel[0]=ship.vel[0]+bullet_velocity*sin(ship.orient*pi/180) bullet.vel[1]=ship.vel[1]+bullet_velocity*cos(ship.orient*pi/180) bullet.timeout=gametime+bullet_timeout drawgraphic(draw,bullet) objlist.append(bullet) # end function shipfire def ship0fire(event): ship0=findship("ship0") if ship0 : shipfire(ship0) def ship1fire(event): ship1=findship("ship1") if ship1 : shipfire(ship1) def turnright(ship): ship.orient=ship.orient+30 calc_coords(ship) def turnleft(ship): ship.orient=ship.orient-30 calc_coords(ship) def ship1left(event): ship1=findship("ship1") if ship1 : turnleft(ship1) def ship1right(event): ship1=findship("ship1") if ship1 : turnright(ship1) def ship0left(event): ship0=findship("ship0") if ship0 : turnleft(ship0) def ship0right(event): ship0=findship("ship0") if ship0 : turnright(ship0) def quit(event): global quitflag quitflag=1 def restart(event): global restartflag restartflag=1 def calc_coords(obj): # calculate coordinates of points to be rendered on display global ship_radius,planet_radius,sun_radius if obj.graphictype==object.SUN: # circle obj.graphicid[0].coordlist=(-sun_radius,-sun_radius,sun_radius,sun_radius) elif obj.graphictype==object.PLANET: # circle obj.graphicid[0].coordlist=(-planet_radius,-planet_radius,planet_radius,planet_radius) elif obj.graphictype==object.SHIP: # circle obj.graphicid[0].coordlist=(-ship_radius,-ship_radius,ship_radius,ship_radius) #arrow # x = sin theta # y = -cos theta obj.graphicid[1].coordlist=(ship_radius*sin(pi/180*(obj.orient-150)), -ship_radius*cos(pi/180*(obj.orient-150)), ship_radius*sin(pi/180*obj.orient), -ship_radius*cos(pi/180*obj.orient), ship_radius*sin(pi/180*(obj.orient+150)), -ship_radius*cos(pi/180*(obj.orient+150))) elif obj.graphictype==object.BULLET: obj.graphicid[0].coordlist=(-1,0,1,0,0,0,0,-1,0,2) # last 2 should be 1, but must be 2 because of bug in TK # end function calc_coords def drawgraphic(drawarea,obj): # create the graphic object for obj (class object), on spec'd drawarea global scrwidth,scrheight # drawing commands if obj.graphictype==object.SUN: obj.graphicid=[]; suncircle=graphel(); obj.graphicid.append(suncircle) calc_coords(obj) suncircle.graphid=drawarea.create_oval(screencoords(suncircle.coordlist,obj.pos),width=3,fill="yellow",outline="white") elif obj.graphictype==object.PLANET: obj.graphicid=[] plcircle=graphel(); obj.graphicid.append(plcircle) calc_coords(obj) plcircle.graphid=drawarea.create_oval(screencoords(plcircle.coordlist,obj.pos),width=2,outline="white",fill="cyan") elif obj.graphictype==object.SHIP: obj.graphicid=[] outercircle=graphel() obj.graphicid.append(outercircle) arrowline=graphel() obj.graphicid.append(arrowline) calc_coords(obj) outercircle.graphid=drawarea.create_oval(screencoords(outercircle.coordlist,obj.pos),width=1,outline="white") arrowline.graphid=drawarea.create_line(screencoords(arrowline.coordlist,obj.pos),width=1,fill="white") elif obj.graphictype==object.BULLET: obj.graphicid=[] cross=graphel() obj.graphicid.append(cross) calc_coords(obj) cross.graphid=drawarea.create_line(screencoords(cross.coordlist,obj.pos),width=1,fill="white") # end of function drawgraphic() def movegraphic(drawarea,obj): global scrwidth,scrheight for part in obj.graphicid: if abs(obj.pos[0]) > scrwidth/2 or abs(obj.pos[1]) > scrheight/2: drawarea.coords(part.graphid,-1,-1,-1,-1); else : scrcoords=screencoords(part.coordlist,obj.pos) # what we want to do is: #drawarea.coords(part.graphid,screencoords(part.coordlist,obj.pos)) # but drawarea.coords can't(!) take a vector arg ??? # workaround: if len(scrcoords)==4: drawarea.coords(part.graphid,scrcoords[0],scrcoords[1],scrcoords[2],scrcoords[3]) elif len(scrcoords)==6: drawarea.coords(part.graphid,scrcoords[0],scrcoords[1],scrcoords[2],scrcoords[3],scrcoords[4],scrcoords[5]) elif len(scrcoords)==8: drawarea.coords(part.graphid,scrcoords[0],scrcoords[1],scrcoords[2],scrcoords[3],scrcoords[4],scrcoords[5],scrcoords[6],scrcoords[7]) elif len(scrcoords)==10: drawarea.coords(part.graphid,scrcoords[0],scrcoords[1],scrcoords[2],scrcoords[3],scrcoords[4],scrcoords[5],scrcoords[6],scrcoords[7],scrcoords[8],scrcoords[9]) else : print "error -- len(scrcoords)=%d" % len(scrcoords) # end function movegraphic ### MAIN PROGRAM BEGINS HERE ### # variable initializations quitflag=0 restartflag=0 dt=0.0 # magic constant for tkinter.dooneevent() TCL_DONT_WAIT=2 # our master list of objects objlist=[] # initialize Tkinter root=Tk() scrwidth=600 scrheight=400 # create drawing area draw=Canvas(root,width=scrwidth,height=scrheight,background='black') # Get keypresses root.bind("w",ship0accel) root.bind("z",ship0fire) root.bind("a",ship0left) root.bind("s",ship0right) root.bind("",ship1accel) root.bind("",ship1fire) root.bind("",ship1left) root.bind("",ship1right) root.bind("",restart) root.bind("q",quit) draw.pack() # create status line labelframe=Frame(root); labelframe.pack(); # create labels on status line ship0label=Label(labelframe,text="",width=28,anchor='w'); ship0label.pack(side=LEFT); restartlabel=Label(labelframe,anchor='center',width=28) restartlabel.pack(side=LEFT); ship1label=Label(labelframe,text="",width=28,anchor='w'); ship1label.pack(side=RIGHT); # loop forever while (not quitflag): gametime=0.0 # current time into running game, seconds # create objects... # Sun sun = object() sun.mass=1000 sun.graphictype=object.SUN sun.fixed=1 sun.collision_radius=sun_radius drawgraphic(draw,sun) objlist.append(sun); # Planet planet1=object() planet1.mass=100 planet1.graphictype=object.PLANET planet1.pos=[0,50] planet1.vel=[-52,0] planet1.collision_radius=planet_radius drawgraphic(draw,planet1) objlist.append(planet1) # Ship #0 ship0=object() ship0.name="ship0" ship0.mass=10 ship0.graphictype=object.SHIP ship0.pos=[-100.0,0.0] ship0.vel=[0.0,-40.0] ship0.orient=90.0 ship0.collision_radius=ship_radius drawgraphic(draw,ship0) objlist.append(ship0) # Ship #1 ship1=object() ship1.name="ship1" ship1.mass=10 ship1.graphictype=object.SHIP ship1.pos=[100.0,0.0] ship1.vel=[0.0,40.0] ship1.collision_radius=ship_radius ship1.orient=-90.0 drawgraphic(draw,ship1) objlist.append(ship1) # change status line message restartlabel.configure(text="press q to quit") lasttime=time(); curtime=time(); while (not restartflag and not quitflag): # let GUI process events... ret=1 while ret != 0: ret=tkinter.dooneevent(TCL_DONT_WAIT) dt=0 while (dt <= min_timestep): curtime=time() dt=curtime-lasttime if (dt > max_timestep): dt=max_timestep # upper-bound amount of game time passable per step if (dt < min_timestep): sleep(min_timestep-dt) # if we're going too fast, let the CPU rest lasttime=curtime gametime=gametime+dt # Grim reaper for expired objects obj=0 while obj < len(objlist) : if objlist[obj].deathflag or (objlist[obj].timeout != 0.0 and objlist[obj].timeout < gametime) : # object has died or timed out -- destroy it for cnt in range(len(objlist[obj].graphicid)) : draw.delete(objlist[obj].graphicid[cnt].graphid) if objlist[obj]==ship0 or objlist[obj]==ship1 : # change status line message if a ship got killed restartlabel.configure(text="press to restart") objlist.remove(objlist[obj]) else : obj=obj+1 # Calculate acceleration; move objects for obj in range(len(objlist)): # adjust shields, energy if objlist[obj].shields < 100.0 : objlist[obj].shields=objlist[obj].shields + shieldrecoveryrate*dt if objlist[obj].shields > 100.0 : objlist[obj].shields = 100.0 if objlist[obj].energy < 100.0 : objlist[obj].energy=objlist[obj].energy + energyrecoveryrate*dt if objlist[obj].energy > 100.0 : objlist[obj].energy = 100.0 if objlist[obj].fixed: continue # no motion allowed accel=(0.0, 0.0); for puller in range(len(objlist)): if puller==obj: continue #dist=[objlist[puller].pos[0]-objlist[obj].pos[0],objlist[puller].pos[1]-objlist[obj].pos[1]] dist=subvec(objlist[puller].pos,objlist[obj].pos) #r2=dist[0]*dist[0]+dist[1]*dist[1] r2=dotvec(dist,dist) # avoid div/0 if r2 < 1.0 : r2=1.0 r=sqrt(r2) # accel=accel+K*objlist[puller].mass/r2 * (dist/r) #accel=(accel[0]+K*objlist[puller].mass/r2*dist[0]/r , accel[1]+K*objlist[puller].mass/r2*dist[1]/r) accel=addvec(accel,mulvec(dist,K*objlist[puller].mass/r2/r)) #objlist[obj].vel=objlist[obj].vel + accel*dt #objlist[obj].pos=objlist[obj].pos+objlist[obj].vel*dt #objlist[obj].pos=[objlist[obj].pos[0]+objlist[obj].vel[0]*dt,objlist[obj].pos[1]+objlist[obj].vel[1]*dt] objlist[obj].pos=addvec(addvec(objlist[obj].pos,mulvec(objlist[obj].vel,dt)),mulvec(accel,dt*dt)) #objlist[obj].vel=[objlist[obj].vel[0]+accel[0]*dt,objlist[obj].vel[1]+accel[1]*dt] objlist[obj].vel=addvec(objlist[obj].vel,mulvec(accel,dt)) # handle objects beyond edge of display if rubber_wall : # screen edge is bouncy if (objlist[obj].pos[0] < -scrwidth/2) : objlist[obj].pos[0]=-(objlist[obj].pos[0]+scrwidth/2) - scrwidth/2 objlist[obj].vel[0]=-objlist[obj].vel[0] elif (objlist[obj].pos[0] > scrwidth/2) : objlist[obj].pos[0]=-(objlist[obj].pos[0]-scrwidth/2) + scrwidth/2 objlist[obj].vel[0]=-objlist[obj].vel[0] elif (objlist[obj].pos[1] < -scrheight/2) : objlist[obj].pos[1]=-(objlist[obj].pos[1]+scrheight/2) - scrheight/2 objlist[obj].vel[1]=-objlist[obj].vel[1] elif (objlist[obj].pos[1] > scrheight/2) : objlist[obj].pos[1]=-(objlist[obj].pos[1]-scrheight/2) + scrheight/2 objlist[obj].vel[1]=-objlist[obj].vel[1] elif wrap_wall : # screen edge wraps around to other side if (objlist[obj].pos[0] < -scrwidth/2) : objlist[obj].pos[0]=objlist[obj].pos[0]+scrwidth elif (objlist[obj].pos[0] > scrwidth/2) : objlist[obj].pos[0]=objlist[obj].pos[0]-scrwidth elif (objlist[obj].pos[1] < -scrheight/2) : objlist[obj].pos[1]=objlist[obj].pos[1]+scrheight elif (objlist[obj].pos[1] > scrheight/2) : objlist[obj].pos[1]=objlist[obj].pos[1]-scrheight # Move visible graphic movegraphic(draw,objlist[obj]) # Collision detect for coll in objlist : for targ in objlist : # These next lines will disable collision detect #if (1) : # continue if (coll == targ) : continue dist=subvec(coll.pos,targ.pos) if (r < 0.01): r=0.01 # avoid div/0 r=sqrt(dist[0]*dist[0]+dist[1]*dist[1]) # Pythagoras if coll != targ and r <= coll.collision_radius+targ.collision_radius: if coll.graphictype==object.SHIP and targ.graphictype==object.SUN : if sunbounce : if bounce(targ,coll) : coll.shields=coll.shields-bouncedamage if (coll.shields < 0) : coll.deathflag=1 coll.shields=0 else : coll.deathflag=1 elif coll.graphictype==object.SHIP and targ.graphictype==object.PLANET : if planetbounce : if bounce(targ,coll) : coll.shields=coll.shields-bouncedamage if (coll.shields < 0) : coll.deathflag=1 coll.shields=0 else : coll.deathflag=1 elif coll.graphictype==object.SHIP and targ.graphictype==object.BULLET : targ.deathflag=1 if coll.shields < hitdamage: coll.shields=0 coll.deathflag=1 else: coll.shields=coll.shields-hitdamage elif coll.graphictype==object.PLANET and targ.graphictype==object.SUN : if bounce(targ,coll) : coll.shields=coll.shields-bouncedamage if (coll.shields < 0.0) : coll.shields=0.0 coll.deathflag=1 elif coll.graphictype==object.BULLET and targ.graphictype==object.SUN : coll.deathflag=1 elif coll.graphictype==object.BULLET and targ.graphictype==object.PLANET : coll.deathflag=1 elif coll.graphictype==object.SHIP and targ.graphictype==object.SHIP : if shipbounce : if bounce(targ,coll) : # perform bounce coll.shields=coll.shields-bouncedamage targ.shields=targ.shields-bouncedamage if (coll.shields < 0.0) : coll.deathflag=1 coll.shields=0 if (targ.shields < 0.0) : targ.deathflag=1 targ.shields=0 else : coll.deathflag=1 targ.deathflag=1 # redraw labels ship0label.configure(text="Shields: %3.f%% Energy: %3.0f%%" % (ship0.shields,ship0.energy)) ship1label.configure(text="Shields: %3.f%% Energy: %3.0f%%" % (ship1.shields,ship1.energy)) # clean up -- destroy all objects while (len(objlist)) : myobj=objlist[0] for cnt in range(len(myobj.graphicid)) : draw.delete(myobj.graphicid[cnt].graphid) objlist.remove(myobj) # loop back and restart game restartflag=0