import gui $include "keysyms.icn" $define MAXHEIGHT 572 #playfield height/width dimensions $define MAXWIDTH 500 $define VOFFSET 60 $define VSTARSOFF VOFFSET-36 $define SPEEDINC 0.1 #acceleration constant $define MAXSPEED 5.0 #maximum speed $define NEGMAXSPEED -5.0 #negative of max speed $define RPTMIN 7 $define RMINSIZE 15 #min asteroid size (pixels) $define RMAXSIZE 30 #max asteroid size (pixels) $define ROIDPROBABILITY 960 #how often new asteroids come into the playing field (lower is more often) $define MAXROIDS 10 #maximum allowed number of asteroids $define RMAXSPEED 2.5 #maximum speed of asteroids $define MAXBLASTS 10 #no more than this many laser blasts at a time $define MAXBLASTRANGE 40 #how far the blasts travel before they fizzle out $define BLAST_RADIUS 36 #how far away a legal collision is from an asteroid $define DELAY_TIME 33 #length of a timeslice (milliseconds) record point(x,y) #a point on the screen record rtrack( #asteroid tracking record x, y, #center coordinates angle, #angle of rotation (not used) points, #array polygon point coordinates (relative to the center) size, speed, #size / speed of asteroid offset, #precomputed multiplier, saves computations offset2x, #same active) #boolean, active or not record btrack( #blast tracking record x, y, #center coordinates angle, #angle of rotation of blast flag, #active / inactive traveled, #how far the blast has gone deactnext) #flag this to be deactivated next time slice class Blasteroids_Game : Component(starsdone, tapped, gamestart, shipx, shipy, c, newwin, backwin, lookups, lookupc, currblast, blasts, blastwin, roids, currroids, roidwin, explodewins, shiphit, speedx, speedy, angle, currspeed, oldshipx, oldshipy, masterspeed, oldangle, negx, negy, redrawship, x1, y1, firsttime, livesleft, livesdone, score, oldscore, num_ships, num_range, chk_explode, chk_sound, num_torpedos, num_speed, num_rotation, num_level, left_pressed, right_pressed, up_pressed, down_pressed) method resize() x_spec := "100%" y_spec := "100%" w_spec := "100%" h_spec := "100%" self.Component.resize() end method display() if starsdone = 0 then { drawstars() newwin := cwin explodewins := [] every put(explodewins, Clone(newwin,"fg=" || ("red"|"orange"|"orangish red"|"black"|"white"))) &window := newwin #probably only one of these is strictly neccesary? CopyArea(newwin, backwin, 0, 0, MAXWIDTH, MAXHEIGHT) #CopyArea(newwin, roidwin, 0, 0, 600, 600) #CopyArea(newwin, blastwin, 0, 0, 600, 600) } end method drawstars() starclones := [] every put(starclones, Clone(cwin,"fg=" || ("vivid white"|"gray"|"light gray"|"dark gray"|"black"))) FillRectangle(starclones[5], 0, VSTARSOFF, MAXWIDTH, MAXHEIGHT) #at every point... every x_loc := 0 to MAXWIDTH do { every y_loc := 0 to 3 do { #draw a randomly colored star in a random location DrawPoint(starclones[(?100-1)/25+1], x_loc, ?500 + VOFFSET) } } newwin := cwin CopyArea(newwin, backwin, 0, 0, MAXWIDTH, MAXHEIGHT) starsdone := 1 end method reset_everything() eraseship() eraseblasts() eraseroids() currblast := 0 currroids := 0 redrawship := 5 x1 := (MAXWIDTH / 2) shipx := (MAXWIDTH / 2) y1 := (MAXHEIGHT / 2) shipy := (MAXHEIGHT / 2) #and going nowhere speedx := 0 speedy := 0 masterspeed := 0 oldshipx := 0 oldshipy := 0 #flag, (0 or 1), if the ship is drawn, the delay has already been # done, otherwise, we delay when the blasts / asteroids are done # number of times to redraw the ship when it isn't moving redrawship := 5 #angle of the ship (true angle = angle -1) angle := 1 oldangle := 0 firsttime := 1 livesleft := 5 livesdone := 0 starsdone := 0 if tapped = 1 then gamestart := 0 tapped := 0 score := 0 oldscore := -1 #kill any straggling asteroids or blasts every blasts[1 to MAXBLASTS].flag := 0 every roids[1 to MAXROIDS].active := 0 end method handle_event(e) local pqz if tapped = 1 then { tapped := 0 reset_everything() drawstars() } if firsttime = 1 & starsdone = 1 then { firsttime := 0 } if gamestart = 1 then { case e of { " ": { #space bar, same as left click if shiphit = 0 then { redrawship := 5 eraseblasts() eraseship() initblast(x1, y1, angle - 1) eraseblasts() drawship(x1, y1, angle) } } "d" | "D" : { Bg("black") Fg("white") GotoRC(10, 1) WWrite(cwin, " ") GotoRC(10, 1) WWrite(cwin, "Here: ", num_ships, " ", num_level, " ", num_rotation) } "q" | "Q": { exit() } Key_Left: { angle -:= 5 if(angle <= 0) then angle +:= 360 left_pressed := 1 } Key_Right: { angle +:= 5 if(angle > 360) then angle -:= 360 right_pressed := 1 } -(Key_Left)-128: { left_pressed := &null } -(Key_Right)-128: { right_pressed := &null } -(Key_Up)-128: { up_pressed := &null } -(Key_Down)-128: { down_pressed := &null } Key_Up: { speedx +:= lookupc[angle] * SPEEDINC speedy +:= lookups[angle] * SPEEDINC if speedx > MAXSPEED then speedx := MAXSPEED if speedx < (NEGMAXSPEED) then speedx := NEGMAXSPEED if speedy > MAXSPEED then speedy := MAXSPEED if speedy < (NEGMAXSPEED) then speedy := NEGMAXSPEED up_pressed := 1 } Key_Down: { speedx -:= lookupc[angle] * SPEEDINC speedy -:= lookups[angle] * SPEEDINC if(speedx > MAXSPEED) then speedx := MAXSPEED if(speedx < (NEGMAXSPEED)) then speedx := NEGMAXSPEED if(speedy > MAXSPEED) then speedy := MAXSPEED if(speedy < (NEGMAXSPEED)) then speedy := NEGMAXSPEED down_pressed := 1 } } main_game_loop() } end method main_game_loop() local px, py, maxspeed, dist local doskip, intdist, delayed, starclones, temp randomize() delayed := 0 # redrawship := 1 #everything is drawn in white, may change later on Fg("white") if livesdone=0 then { every pqz := 1 to livesleft do { drawship2((((pqz-1)*34)+18) , 42) } } #game loop while *Pending() = 0 do { if score ~= oldscore then { oldscore := score Bg("black") GotoRC(4, 30) WWrite(cwin, score) } if (shipx~= x1 | shipy ~= y1 | oldangle ~= angle) & (shiphit = 0)then{ #we're redrawing it here, no need to elsewhere redrawship := 0 oldangle := angle #finally, draw the ship eraseblasts() drawship(x1, y1, angle-1) drawblasts() WDelay(DELAY_TIME) #already did delay, don't do it again delayed := 1 } else if shiphit > 0 then { redrawship := doexplodeship() x1 := MAXWIDTH / 2 y1 := MAXHEIGHT / 2 } x1 +:= speedx y1 +:= speedy if(x1 > MAXWIDTH - 20) then x1 := 20 if(x1 < 20) then x1 := MAXWIDTH - 20 if(y1 > MAXHEIGHT - 20) then y1 := 20 + VOFFSET if(y1 < (20 + VOFFSET)) then y1 := MAXHEIGHT - 20 #randomly put in a new asteroid, up to the max #may change rules for this based on a kind of level system if currroids < MAXROIDS then { if ?1000 > ROIDPROBABILITY then { eraseblasts() # eraseship() eraseroids() initroid() eraseroids() # drawship(x1, y1, angle-1) drawblasts() } } #thinking I ought to split these boys up, would save on if statements if currblast > 0 | currroids > 0 then { if currblast > 0 then eraseblasts() if currroids > 0 then eraseroids() if redrawship > 0 then eraseship() if currroids > 0 then animroids() if currblast > 0 then animblasts() if redrawship > 0 then { drawship(x1, y1, angle-1) redrawship -:= 1 } if currroids > 0 then drawroids() if currblast > 0 then drawblasts() if delayed = 0 then WDelay(DELAY_TIME) else delayed := 0 } if \left_pressed then handle_event(Key_Left) if \right_pressed then handle_event(Key_Right) if \up_pressed then handle_event(Key_Up) if \down_pressed then handle_event(Key_Down) } #aha! they did something! end method doexplodeship() DrawCircle(explodewins[shiphit%3+1], shipx, shipy, 24-shiphit) shiphit-:=1 speedx := 0 speedy := 0 if shiphit = 0 then { eraseship() shipx := MAXWIDTH / 2 shipy := (MAXHEIGHT + VOFFSET) / 2 return 1 } return 0 end method eraseship() #copy background over area where ship is (done before animating it) CopyArea(backwin, newwin, shipx-24, shipy-24, 48,48, shipx-24, shipy-24) end method eraseship2(x, y) CopyArea(backwin, newwin, x-17, y-17, 34, 34, x-17, y-17) end method drawship2(x, y) local cs, si, si16, si10, cs16, cs10, rot rot := 270 cs := lookupc[rot+1] si := lookups[rot+1] si16 := 16*si cs16 := 16*cs si10 := 10*si cs10 := 10*cs #do rotation (using x, y as center), and draw the 'ship' #might be a faster way (w/out so many multiplies... DrawLine(x - cs16, y-si16, x+(4*cs), y+(4*si)) DrawLine(x-cs10-si16, y + cs16-si10, x, y, x-cs10+si16, y-cs16-si10) DrawLine(x+si16-cs16, y-cs16-si16, x+si16, y-cs16) DrawLine(x-cs16-si16, y+cs16-si16, x-si16, y+cs16) DrawPoint(x+cs10, y+si10) DrawCircle(x+cs10, y+si10, 6) end method drawship(x, y, rot) local cs, si, si16, si10, cs16, cs10, count #make sure rot (angle of rotation) is between 0 and 359 rot := rot % 360 if rot < 0 then rot := rot + 360 #erase CopyArea(backwin, newwin, shipx-24, shipy-24, 48, 48, shipx-24, shipy-24) #use a lookup table for speed cs := lookupc[rot+1] si := lookups[rot+1] si16 := 16*si cs16 := 16*cs si10 := 10*si cs10 := 10*cs #do rotation (using x, y as center), and draw the 'ship' #might be a faster way (w/out so many multiplies... DrawLine(x - cs16, y-si16, x+(4*cs), y+(4*si)) DrawLine(x-cs10-si16, y + cs16-si10, x, y, x-cs10+si16, y-cs16-si10) DrawLine(x+si16-cs16, y-cs16-si16, x+si16, y-cs16) DrawLine(x-cs16-si16, y+cs16-si16, x-si16, y+cs16) DrawPoint(x+cs10, y+si10) DrawCircle(x+cs10, y+si10, 6) #reset globals defining ship location shipx := x shipy := y end method initblast (x, y, angle) #start up a blast is any more are allowed local gowhere, nblast if currblast < MAXBLASTS then { #one more blast to keep track of currblast +:= 1 angle %:= 360 if angle < 0 then angle +:= 360 x +:= integer(24 * lookupc[angle+1]) y +:= integer(24 * lookups[angle+1]) #shop around for an open slot in the blasts array gowhere := nextopen() #set all pertinent variables blasts[gowhere].flag := 1 blasts[gowhere].deactnext := 0 blasts[gowhere].x := x blasts[gowhere].y := y blasts[gowhere].angle := angle + 1 blasts[gowhere].traveled := 0 } end method nextopen() #pretty simple, return next open slot in blasts array local n every n := 1 to MAXBLASTS do { if blasts[n].flag = 0 then return n } return MAXBLASTS end method animblasts() #do the relevant animation for a blast local nblast, x, y, si, cs if currblast > 0 then { every b := blasts[1 to MAXBLASTS] do { if b.flag = 1 then { #move it along its angle at a rate of 6 pixels/time slice si := lookups[b.angle] cs := lookupc[b.angle] b.x +:= 6*cs b.y +:= 6*si b.traveled +:= 1 if b.x > MAXWIDTH - 20 then b.x := 20 if b.y > MAXHEIGHT - 20 then b.y := 20 + VOFFSET if b.x < 20 then b.x := MAXWIDTH - 20 if b.y < 20 + VOFFSET then b.y := MAXHEIGHT - 20 if (b.deactnext = 1) | (b.traveled > MAXBLASTRANGE) then { #kill it if the fates have so decided b.flag := 0 b.x := -500 b.y := -500 currblast -:= 1 } } } } end method eraseblasts() local b every b := blasts[ 1 to MAXBLASTS ] do { if b.flag = 1 then CopyArea(blastwin, newwin, b.x-24, b.y-24, 48, 48, b.x-24, b.y-24) } end method drawblasts() #nice work, Dr. J local b, si, cs, x, y, cs5, cs10, si5, si10 &window := newwin every b := blasts[ 1 to MAXBLASTS ] do { if b.flag = 1 then { x := b.x y := b.y si := lookups[b.angle] cs := lookupc[b.angle] cs5 := cs*5 si10 := si*10 cs10 := cs*10 si5 := si*5 DrawSegment(x + (-cs5-si10), y + cs10 - si5, x + (cs5-si10), y + cs10 + si5, x + ((-cs5)+si10), y - (cs10 + si5), x + cs5 + si10, y + (-cs10+si5) ) } } end method nextroid() #see nextopen() local n if roids[n := 1 to MAXROIDS].active=0 then return n return MAXROIDS end method initroid() # Some speed sacrificed for readability. # This is not a speed critical section of code. local x, y, angle, gowhere, ptemp, offset, temp, cs, si, xt, yt ptemp := list(8) if currroids < MAXROIDS then { currroids +:= 1 gowhere := nextroid() #pick a random wall to start it from case ?4 of { 1: { #left x := 31 y := ?(MAXHEIGHT - 62 - VOFFSET) + 31 + VOFFSET #angle between -90 and 90 (270 - 90) angle := ?180 - 90 if angle < 0 then angle +:= 360 angle +:= 1 } 2: { #top y := 31 + VOFFSET x := ?(MAXWIDTH - 62) + 31 #angle between 0 and 180 (1 to 181, but the lists are 1 based #so lookups[1] = sin(0) angle := ?180 + 1 } 3: { #right x := (MAXWIDTH - 31) y := ?(MAXHEIGHT - 62 - VOFFSET) + 31 + VOFFSET #angle between 91 and 271 angle := ?181 + 90 } 4: { #bottom y := (MAXHEIGHT - 31) x := ?(MAXWIDTH - 62) + 31 #angle between 181 and 360 angle := ?180 + 180 } } #initialize relevant variables from the case structure roids[gowhere].x := x roids[gowhere].y := y roids[gowhere].angle := angle #the 'size' must be between RMINSIZE and RMAXSIZE roids[gowhere].size := ?(RMAXSIZE-RMINSIZE) + RMINSIZE #each point of the asteroid must be at least RPTMIN distance from #the center of the asteroid every ptemp[1 to 8] := ?(roids[gowhere].size - RPTMIN) + RPTMIN every temp := 1 to 8 do { #rotate ptemp[1] by 45 degrees, ptemp[2] by 90, ptemp[3] by 135,... #and save them as their relative position to the center of the asteroid #much faster than computing it every time it is drawn #(doesn't work for the ship since its angle constantly changes) cs := lookupc[(temp*45)%360 +1] si := lookups[(temp*45)%360 +1] roids[gowhere].points[temp].x := ptemp[temp] * cs roids[gowhere].points[temp].y := ptemp[temp] * si } #random speed roids[gowhere].speed := (?1.0)*RMAXSPEED + .01 roids[gowhere].active := 1 #offset changes, to save the floating point arithmetic each time, save it #in memory, used when erasing the asteroids, small tweak, but every little bit helps offset := roids[gowhere].offset := integer(1.5 * roids[gowhere].size) roids[gowhere].offset2x := 2*offset } &window := newwin end method animroids() local roid, x, y, si, cs if currroids > 0 then { every roid := roids[ 1 to MAXROIDS ] do { #animate every active asteroid if roid.active = 1 then { si := lookups[roid.angle] | { write("lookups failed ", image(roid.angle)); fail } cs := lookupc[roid.angle] | { write("lookupc failed ", image(roid.angle)); fail } #translate, along its trajectory, the asteroid roid.x +:= roid.speed*cs roid.y +:= roid.speed*si if roid.x > MAXWIDTH - 20 then roid.x := 20 if roid.y > MAXHEIGHT - 20 then roid.y := 31 + VOFFSET if roid.x < 20 then roid.x := MAXWIDTH - 20 if roid.y < 31 + VOFFSET then roid.y := MAXHEIGHT - 20 if roiddie(roid) = 1 then { #kill it, if the fates have so decided roid.active := 0 roid.x := -500 roid.y := -500 currroids -:= 1 } } } } end method eraseroids() #every active asteroid has the background area copied over it local roid, topy every roid := roids[ 1 to MAXROIDS ] do { if roid.active = 1 then { if((roid.y-roid.offset) 0 then { every x := 1 to MAXBLASTS do { if blasts[x].flag = 1 then { if -BLAST_RADIUS < roid.x-blasts[x].x < BLAST_RADIUS & -BLAST_RADIUS < roid.y-blasts[x].y < BLAST_RADIUS then { blasts[x].deactnext := 1 score +:= 100 return 1 } } } } else return 0 end initially self.Component.initially() randomize() starsdone := 0 tapped := 0 gamestart := 0 score := 0 lookups := list(360, 0.0) lookupc := list(360, 0.0) blasts := list(MAXBLASTS) roids := list(MAXROIDS) currblast := 0 currroids := 0 redrawship := 5 c := point(MAXWIDTH/2, MAXHEIGHT/2) shiphit := 0 #the background windows (data for erasure) blastwin := roidwin := backwin := WOpen("canvas=hidden", "size="||MAXWIDTH||","||MAXHEIGHT) | stop("Cannot Open Hidden Canvas") every roids[1 to MAXROIDS] := rtrack(-500, -500, 0, list(8), 0, 0, 0, 0, 0) every roids[1 to MAXROIDS].points[1 to 8] := point(0,0) every blasts[1 to MAXBLASTS] := btrack(-500, -500, 0, 0, 0) #initialize sine/cosine tables every x1 := 0 to 359 do { lookups[x1+1] := sin(dtor(x1)) lookupc[x1+1] := cos(dtor(x1)) } #initially ship is in the middle... x1 := (MAXWIDTH / 2) shipx := (MAXWIDTH / 2) y1 := (MAXHEIGHT / 2) shipy := (MAXHEIGHT / 2) #and going nowhere speedx := 0 speedy := 0 masterspeed := 0 oldshipx := 0 oldshipy := 0 #flag, (0 or 1), if the ship is drawn, the delay has already been done, #otherwise, we delay when the blasts / asteroids are done #number of times to redraw the ship when it isn't moving redrawship := 5 #angle of the ship (true angle = angle -1) angle := 1 oldangle := 0 firsttime := 1 livesleft := 5 livesdone := 0 score := 0 oldscore := -1 num_ships := 3 num_range := 10 num_speed := 5 num_torpedos := 3 num_rotation := 5 num_level := 1 end