Pacman for the Propeller
Dr_Acula
Posts: 5,484
Pacman coded in Spin starting from scratch. Take some bitmaps, move them around, work out collisions, maze logic, ghost artificial intelligence and integrate with a touchscreen it is quite a playable game. Speed is a little slower than the usual game to compensate for touchscreen being a little less responsive than buttons.
Video is very blurry - the autofocus on the camera is not great at only 10cm distance. Screenshot gives a better idea of the resolution.
Video is very blurry - the autofocus on the camera is not great at only 10cm distance. Screenshot gives a better idea of the resolution.
CON ' http://home.comcast.net/~jpittman2/pacman/pacmandossier.html#CH3_What_Tile_Am_I_In _clkmode = xtal1 + pll16x ' use crystal x 16 _xinfreq = 5_000_000 xoffset = 8 ' so can move the whole screen around. yoffset = 16 pacmanspeed = 250 ' max 255 otherwise skips pixels and misses the centre point for collisions and goes through walls OBJ tch: "Touch" ' touchscreen driver Q: "SPIN_TrigPack" 'v2.0 CompElit Ltd. VAR long Actor[40] ' 5 actors, 8 longs per actor (dir, speed, x, y, offset in current dir, mode, spare,spare long tick long Adjacent[4] ' adjacent tile numbers to an actor long touchx long touchy long PacmanNewDir long Distance[4] ' distances to targets up down left right long seconds byte pacmouth byte scattermode ' on or off byte scattercounter byte scaredcounter long random long PacmanAlive long score long ghostseatencounter long cherry PUB Main | touchtest ' debug value tch.BeginProgram MainPacman repeat tch.SetWarmBoot ' clears screen, sets a warm boot and reboots PUB MainPacman | i tch.Clearscreen(%00000000_00000000) ' so something happens immediately tch.ClearRam tch.drawfastsetup ' setup fast drawing tch.SDBMPtoRam(string("pac8back.bmp")) ' file 2. File is a .bmp image tch.arraydim((8*7*64)/2) ' file 3 8x8 tiles, 8 wide, 7 high, this is in words so divide by 2 - file 3 CreateTiles(3,2,8,8,7) ' pixels all one after the other in an array tch.SDBMPtoRam(string("pacfont.bmp")) ' file 4 font bitmap (8x8 font) tch.arraydim((16*16*64)/2) ' file 5 is the font tiles CreateTiles(5,4,8,16,16) ' create 8x8 font tiles tch.SDBMPtoRam(string("pac16.bmp")) ' file 6 all the ghosts and pacman (16x16 tiles) tch.arraydim((8*8*256)/2) ' file 7 pixels all in a row CreateTiles(7,6,16,8,8) ' pixels all one after the other in an array tch.SDBMPtoRam(string("pacstart.bmp")) ' file 8 startup screen tch.DrawBMPRam(8,0,0) ' draw the picture tch.pause1ms(2000) ' startup delay score := 0 repeat tch.drawfastsetup ' must do again if do any data transfers DrawMaze(3) ' draw maze using file 3 Pactext(string("PACMAN"),5,0*8,0*8) ' draw text using file 5 Pactext(string("HI SCORE"),5,10*8,0*8) DrawTile16(7,9,2*8,34*8) ' draw number of lives left DrawTile16(7,9,4*8,34*8) ' draw number of lives left DrawTile16(7,48,24*8,34*8) ' draw fruit Setup ' setup the ghosts PacmanAlive := true repeat until PacmanAlive == false repeat i from 0 to 32 step 8 MoveActor(i) PacmanCollision ' has he hit a ghost? UpdatePacMouth ' update counter for pacman's mouth etc ReadScreen ' check for inputs UpdateTick ' update the tick counter and do once a second events tch.pause1ms(8) ' shorten this if calculations take too long. 17ms is 1/60th of a second. Change something once a second to test working Pactext(string("GAME OVER!"),5,9*8,20*8) tch.pause1ms(1000) PUB UpdateTick tick +=1 ' 1/30th of a second if tick>29 tick:= 0 seconds +=1 ' add 1 to second counter SecondsEvents PUB UpdatePacMouth case tick 0,5,10,15,20,25:pacmouth ^= 1 ' xor swaps 0 or 1 PUB ReadScreen ' read screen but not on every tick as it takes time case tick 0,5,10,15,20,25: ReadTouchscreen PUB SecondsEvents ' do things that are only once a second 'DrawTile8(5,seconds,0,0) ' for debugging this should change once a second ScatterLogic ScaredLogic GhostLeavePen PrintScore(7*8,1*8) if score >30 and cherry == false cherry := true DrawTile16(7,48,18*8,16*8+4) ' draw cherry PUB ScaredLogic | i if scaredcounter > 0 scaredcounter -= 1 if scaredcounter == 0 ' reset ghosts to normal ghostseatencounter := 0 repeat i from 0 to 24 step 8 if Actor[i+5] == 3 ' if scared Actor[i+5] := 0 ' back to chase Actor[i+1]:=(pacmanspeed * 95)/100 ' speed back to normal PUB ScatterLogic | i scattercounter +=1 case scattermode '0 is chase, 1 is scatter to four corners 0: if scattercounter == 20 ' 20 secs then 7 secs scattercounter := 0 scattermode := 1 ' scattermode 'tch.text(string("scatter")) ReverseGhosts repeat i from 5 to 29 step 8 if actor[i] == 0 actor[i] := 2 ' change from chase to scatter 1:if scattercounter == 8 scattercounter := 0 scattermode := 0 'tch.text(string("chase")) ReverseGhosts repeat i from 5 to 29 step 8 if actor[i] == 2 actor[i] := 0 ' change from scatter to chase PUB ReverseGhosts| a,newdir ' for scatter and chase mode reversals the ghost directions change ' if experimenting with changes to the reverse code, disable the collision between pacman and ghosts ' let all the ghosts out and let it run for a while and check no ghosts going through walls ' I think it works with actor[a+4] disabled but centreactor enabled repeat a from 0 to 24 step 8 case actor[a+5] ' if chase or scatter but not if eyes or in cage or scared 0,2:CentreActor(a) ' centre the actor ( - do need this! ?cancel this - moves ghost too much and no need as a change in y doesn't need an x change 'Actor[a+4] := 0 ' offset is zero (- do need this! ?try cancelling this because ghost moves away and takes longer to catch) GetAdjacent(a) ' find out what is around case actor[a] 0: newdir := 1 1: newdir := 0 2: newdir := 3 3: newdir := 2 if availablepath(newdir) == true actor[a] := newdir ' change direction if allowed to Actor[a+6] := true ' recent change in direction PUB ReadTouchscreen | org_x,org_y,pointx,pointy,r,theta tch.SelectSPIGroup ' talk to the spi touchscreen touchy := tch.TouchYPercent ' decode 0-100% touchx := tch.TouchXPercent ' decode 0-100% tch.SelectMemGroup ' disconnect spi and talk to display tch.drawfastsetup if touchx <> 255 and touchy<>255 ' a valid touch if not 255, returns 0-100 ' do some trig, divide the screen into four quarters and work out the angle from the centre point org_x := q.Qval(50) ' centre point org_y := q.Qval(50) ' centre point pointx := q.Qval(touchx) ' point touched pointy := q.Qval(touchy) ' point touched 'r := q.Qradius(pointx - org_x, pointy - org_y) 'tch.text(Q.QvalToStr(r)) ' debugging theta := Q.Deg_ATAN2(pointx - org_x, org_y - pointy) ' pointy and orgy swapped as 0,0 is top left of the screenpolar coordinates with correct sign theta += q.Qval(180) ' rotate 180 degrees and remove negative numbers, 0=9 o'clock, 90=6 o'clock, 180 = 3 o'clock, 270 = 12 o'clock 'tch.text(Q.QvalToStr(theta)) case theta>>16 ' convert to integer 0..44: PacmanNewDir := 2 ' move left 45..134: PacmanNewDir := 1 ' move down 135..224:PacmanNewDir := 3 ' move right 225..314:PacmanNewDir := 0 ' move up 315..360:PacmanNewDir := 2 ' move left PUB CreateTiles(destination,source,tilesize,ntileswide ,ntileshigh) | s,d,soffset,doffset,tilex,tiley,npixels,tilesizeti mesntileswide ' pixels linear so faster drawing ' take a bitmap made up of tiles and reorder so all the tile data is contiguous. Quicker to find tiles and to display them s := tch.getramlocation(source)+ $36 ' location in external ram and add $36 for bitmap header d := tch.getramlocation(destination) ' destination npixels := tilesize*tilesize tilesizetimesntileswide := tilesize*ntileswide ' faster if precalculate this repeat tiley from 0 to ntileshigh-1 repeat tilex from 0 to ntileswide-1 soffset := (tilex*tilesize) + (tiley*ntileswide) * npixels doffset := ((tiley*ntileswide)+tilex) * npixels repeat tilesize tch.ramwordscopy(d+doffset,s+soffset,tilesize) soffset += tilesizetimesntileswide doffset += tilesize PUB DrawTile8(f,tilenumber,x,y) ' file, tilenumber, x, y. Optimised for 8x8 tiles - can do bitshifts instead of divides/multiplies tch.drawFast(x+xoffset,y+yoffset,x+7+xoffset,y+7+y offset) tilenumber := tilenumber << 6 ' multiply by 64 to get the start address tch.ramtodisplay(tch.getramlocation(f)+tilenumber, 64) ' display PUB DrawTile16(f,tilenumber,x,y) ' file, tilenumber, x,y. Optimised for 16x16 tiles (bitshifts faster than multiply) tch.drawfast(x+xoffset,y+yoffset,x+15+xoffset,y+15 +yoffset) tilenumber := tilenumber << 8 ' multiply by 256 to get the startaddress tch.ramtodisplay(tch.getramlocation(f)+tilenumber, 256) ' display PUB DrawMaze(f) | x,y,c,n ' 28 wide, 36 high tch.drawfastsetup repeat y from 0 to (288-8) step 8 n := (28*y)>>3 ' width times y and divide by 8 repeat x from 0 to (224-8) step 8 c := byte[@maze][n] ' faster as add is faster than multiply n++ drawtile8(f,c,x,y) PUB PacText(stringptr,f,x,y) 'string, ram file (array) to print from, x,y tch.drawfastsetup repeat strsize(stringptr) DrawTile8(f,byte[stringptr++],x,y) x += 8 PUB Setup scattercounter := 0 scattermode := 0 cherry := false ' Blinky Actor[0] := 2 ' UDLR = 0 1 2 3 Actor[1] := (pacmanspeed * 95)/100 ' speed must never be more than 255 otherwise skips pixels and misses walls Actor[2] := 14*8 ' startup for Blinky Actor[3] := 14*8+4 ' tiley Actor[4] := 0 ' offset (add speed to this each clock tick and use to recalculate the tile. Increment tile if in the next one) Actor[5] := 0 ' mode 0 = chase ,1 = in cage, 2 = scatter to corner, 3 = scared Actor[6] := false ' hasn't changed in the last tile ' Pinky Actor[8] := 0 ' UDLR = 0 1 2 3 Actor[9] := (pacmanspeed * 95)/100 ' speed Actor[10] := 12*8 Actor[11] := 17*8+4 Actor[12] := 0 Actor[13] := 1 ' 0= chase, 1= in cage,2 = scatter Actor[14] := false ' hasn't changed in the last tile ' Inky Actor[16] := 1 ' UDLR = 0 1 2 3 Actor[17] := (pacmanspeed * 95)/100 ' speed Actor[18] := 14*8 Actor[19] := 17*8+4 Actor[20] := 0 ' offset Actor[21] := 1 ' mode Actor[22] := false ' hasn't changed in the last tile ' Clyde Actor[24] := 0 ' UDLR = 0 1 2 3 Actor[25] := (pacmanspeed * 95)/100 ' speed Actor[26] := 16*8 Actor[27] := 17*8+4 Actor[28] := 0 ' offset Actor[29] := 1 ' mode Actor[30] := false ' hasn't changed in the last tile ' Pacman Actor[32] := 2 ' UDLR = 0 1 2 3 PacmanNewDir := Actor[32] Actor[33] := pacmanspeed ' speed Actor[34] := 14*8+4 Actor[35] := 26*8+4 Actor[36] := 0 Actor[37] := 2 ' startup PUB DrawActor(A) | tile ' tile is 0-15 wide and high, and centre is at 8,8 case Actor[a+5] ' mode 0,1,2: ' chase or cage or scatter Case A 0: DrawTile16(7,16+pacmouth+Actor[0]<<1,Actor[2]-8,Actor[3]-8) ' Blinky (pacmouth used to move ghosts feet) 8: DrawTile16(7,24+pacmouth+Actor[8]<<1,Actor[10]-8,Actor[11]-8) ' Pinky 16: DrawTile16(7,32+pacmouth+Actor[16]<<1,Actor[18]-8,Actor[19]-8) ' Inky 24: DrawTile16(7,40+pacmouth+Actor[24]<<1,Actor[26]-8,Actor[27]-8) ' Clyde 3:if scaredcounter > 2 ' blue DrawTile16(7,12+pacmouth,Actor[A+2]-8,Actor[A+3]-8) ' blue ghost for all four, white if scaredcounter = 1 if scaredcounter == 1 or scaredcounter ==2' white DrawTile16(7,14+pacmouth,Actor[A+2]-8,Actor[A+3]-8) ' white ghost 4:if actor[A+5] == 4 ' dead ghost's eyes DrawTile16(7,60+Actor[A],Actor[A+2]-8,Actor[A+3]-8) if A == 32 DrawPacman PUB DrawPacman DrawTile16(7,Actor[32]*3+pacmouth,Actor[34]-8,Actor[35]-8) ' Pacman PUB MoveActor(A) | oldloc,newloc,x,y,oldtilex,oldtiley,startx,starty, finx,finy,i,j,oldtile,newtile ' add speed to offset. If over one pixel, move and subtract a pixels worth ' 256 in offset is one pixel, eg if speed is 256 then moving one pixel per frame ' if this moves into a new tile ' work out where to go next ' redraw old tile that has just left ' if not a new tile ' ' draw a black line behind the ghost ' redraw the ghost oldtilex := Actor[A+2]>>3 'old tilex oldtiley := Actor[A+3]>>3 'old tiley oldtile := GetTile(A) ' old tile number, store for later Actor[A+4] += Actor[A+1] ' offset + speed oldloc :=0 newloc :=0 if Actor[A+4] > 255 ' offset over 255 so move a pixel if Actor[A+0] == 0 or Actor[A+0] == 1 ' up or down oldloc := Actor[A+3] ' old y position case Actor[A+0] 0: newloc := Actor[A+3] - Actor[A+4]>>8 ' up new y position 1: newloc := Actor[A+3] + Actor[A+4]>>8 ' down new y position Actor[A+3] := newloc if Actor[A+0] == 2 or Actor[A+0] == 3 ' left or right oldloc := Actor[A+2] case Actor[A+0] 2: newloc := Actor[A+2] - Actor[A+4]>>8 ' left new x position 3: newloc := Actor[A+2] + Actor[A+4]>>8 ' right new x position Actor[A+2] := newloc Actor[A+4] := Actor[A+4] // 256 ' get remainder and store back in the offset array if ||(newloc - oldloc) > 0 ' moved a pixel or more so need to redraw the ghost and a few black lines behind for less flicker case Actor[A+0] ' direction 0: tch.clearrectangle(Actor[A+2]+xoffset-8,newloc+7+yoffset,Actor[A+2]+xoffset+7,oldloc+7+yoffset,0) 1: tch.clearrectangle(Actor[A+2]+xoffset-8,oldloc+yoffset-8,Actor[A+2]+xoffset+7,newloc+yoffset-8,0) 2: tch.clearrectangle(newloc+xoffset+7,Actor[A+3]+yoffset-8,oldloc+xoffset+7,Actor[A+3]+7+yoffset,0) 3: tch.clearrectangle(oldloc+xoffset-8,Actor[A+3]+yoffset-8,newloc+xoffset-8,Actor[A+3]+7+yoffset,0) ' use color $FFFF to debug and very slow speed DrawActor(A) if ||(newloc>>3 - oldloc>>3) > 0 ' moved to a new tile so redraw all the background around startx := oldtilex-1 ' tiles nearby redrawn (with edge exceptions) startx #>= 0 ' limit min to 0 starty := oldtiley-1 starty #>= 0 ' limit min to 0 finx := oldtilex +1 finx <#= 27 ' limit max to 27 finy := oldtiley +1 finy <#= 35 ' limit max to 35 repeat i from startx to finx repeat j from starty to finy RedrawTile(i,j) DrawActor(A) GobblePill(A) actor[A+6] := false ' reset change in the last tile ' work out walls and other collisions. Pass centre, returns tiles on 4 edges (ie touching centre+-4) ' also can go down paths if they are available Tunnel(A) ' check if in the tunnel GetAdjacent(A) ' find out what is around me if actor[A+2] // 8 == 4 and actor[A+3] // 8 == 4 and actor[A+5] <> 1 ' not in pen, centred in a square so can do AI and collisions. May need to change if speed >255 case A 0: BlinkyAI 8: PinkyAI 16: InkyAI 24: ClydeAI if actor[a+5] == 1 ' in the ghost pen so different AI GhostPenAI(A) if A == 32 PacmanAI if Actor[33] == 0' pacman is stationary so won't have been redrawn by Drawactor above so force a redraw DrawPacman PUB GhostPenAI(A) if Actor[A+3] < 17*8 Actor[A] := 1 ' change to down if Actor[A+3] => 18*8 Actor[A] := 0 ' change to up PUB CentreActor(A) ' put back in the middle of a tile, ie 4,4 then 12,12 Actor[A+4] := 0 ' offset is zero Actor[A+2] := ((Actor[A+2]>>3)<<3)+4 Actor[A+3] := ((Actor[A+3]>>3)<<3)+4 PUB RedrawTile(tilex,tiley) ' draw tile x,y drawtile8(3,byte[@maze][tiley*28+tilex],(tilex<<3),tiley<<3) PUB GetTile(A) ' pass actor 0,8,16 etc. top left is 0,0. This centres on tile 1,1 and there are 28 columns so returns tile 29 result := (Actor[A+3]>>3) * 28 + (Actor[A+2]>>3) PUB GetAdjacent(A) | cx,cy ' look for +- 5 for adjacent tiles cx := Actor[A+2] cy := Actor[A+3] Adjacent[0] := byte[@maze][((cy-5)>>3) * 28 + (cx>>3)] ' tile up Adjacent[1] := byte[@maze][((cy+5)>>3) * 28 + (cx>>3)] ' tile down Adjacent[2] := byte[@maze][(cy>>3) * 28 + ((cx-5)>>3)] ' tile left Adjacent[3] := byte[@maze][(cy>>3) * 28 + ((cx+5)>>3)] ' tile right PUB GobblePill(A) | tx,ty,i if A == 32 ' this is pacman so can eat pills tx:= Actor[A+2]>>3 ty:= Actor[A+3]>>3 if byte[@maze][ty*28+tx] == 18 ' found a pill byte[@maze][ty*28+tx] := 16 ' delete it (and add to score etc) score += 10 if byte[@maze][ty*28+tx] == 19 ' found a power pill byte[@maze][ty*28+tx] := 16 ' delete it (and add to score etc) GhostsScared score += 50 PUB GhostsScared | i scaredcounter := 10 ' number of seconds to stay in this mode ReverseGhosts repeat i from 0 to 24 step 8 case Actor[i+5] 0,2:Actor[i+5] := 3 ' if chase or scatter then change to scared Actor[i+1] := (pacmanspeed * 50)/100 ' slow down ghosts so easier to catch PUB BlinkyAI | act' pass the Adjacent array. Works out what is allowed and what Blinky needs to do ' Of all the ghosts' targeting schemes for chase mode, Blinky's is the most simple and direct, ' using Pac-Man's current tile as his target. ' if a wall, exclude directions can travel ' if a choice of directions work out which is allowed ' needs a target tile - either pacman, or the cage (if dead) or the scatter corner. Mode contains this act := 0 ' actor number, added as a variable so easier to translate this to other ghosts if actor[act+6] ==false ' hasn't changed direction in the last tile case Actor[act+5] 0:BestPathToTarget(act+0,Actor[32+2]>>3,Actor[32+3]>>3)' target is pacman's x.y location 2:BestPathToTarget(act+0,26,1) ' scatter mode so blinky's target top right 3:RandomPath(act) ' scared so move randomly 4:GhostToPen(act) ' ghost has died, eyes to the pen PUB PinkyAI | act,tx,ty ' target is 4 squares ahead of where pacman is in the direction he is going act := 8 ' actor number, added as a variable so easier to translate this to other ghosts tx := Actor[32+2]>>3 ' pacman x ty := Actor[32+3]>>3 ' pacman y case Actor[32] ' direction pacman is going 0: ty -=4 1: ty +=4 2: tx -= 4 3: tx += 4 if actor[act+6] ==false ' hasn't changed direction in the last tile case Actor[act+5] 0:BestPathToTarget(act+0,tx,ty)' target is pacman's x.y location 2:BestPathToTarget(act+0,0,0) ' scatter mode so blinky's target top left 3:RandomPath(act) ' scared so move randomly and more slowly 4:GhostToPen(act) ' ghost has died, eyes to the pen PUB InkyAI | act,px,py,tx,ty,bx,by ' To determine Inky's target, we must first establish an intermediate offset two tiles in front of Pac-Man ' in the direction he is moving (represented by the tile bracketed in green above). Now imagine drawing a ' vector from the center of the red ghost's current tile to the center of the offset tile, then double ' the vector length by extending it out just as far again beyond the offset tile. The tile this new, ' extendend vector points to is Inky's actual target as shown above. act :=16 ' inky px := Actor[32+2]>>3 ' pacman x py := Actor[32+3]>>3 ' pacman y bx := Actor[2]>>3 ' blinky x by := Actor[3]>>3 ' blinky y case Actor[32] ' pacman direction. px and py are now the intermediate target 0: py -=2 1: py +=2 2: px -=2 3: px +=2 tx := bx + (px-bx)*2 ' see rules above ty := by + (py-by)*2 if actor[act+6] ==false ' hasn't changed direction in the last tile case Actor[act+5] 0:BestPathToTarget(act+0,tx,ty)' target 2:BestPathToTarget(act+0,27,36) ' scatter mode so inky is bottom right 3:RandomPath(act) ' scared so move randomly 4:GhostToPen(act) ' ghost has died, eyes to the pen PUB ClydeAI | act,px,py,cx,cy,d,tx,ty 'During chase mode, Clyde's targeting logic changes based on his proximity to Pac-Man (represented by the ' green target tile above). He first calculates the Euclidean distance between his tile and Pac-Man's tile. ' If the distance between them is eight tiles or more, Clyde targets Pac-Man directly just as Blinky does. 'If the distance between them is less than eight tiles, however, Clyde switches his target to the tile he ' normally uses during scatter mode and heads for his corner until he gets far enough away to start targeting ' Pac-Man again. act :=24 ' clyde px := Actor[32+2]>>3 ' pacman x py := Actor[32+3]>>3 ' pacman y cx := Actor[24+2]>>3 ' clyde x cy := Actor[24+3]>>3 ' clyde y d := (px-cx)*(px-cx)+(py-cy)*(py-cy) ^^d ' square root = distance tx:=px ' target pacman ty:=py ' unless in scatter mode or too close to pacman, but watch out if pacman is between him and the target! if d < 9 tx:= 0 ' target bottom left corner ty:=36 if actor[act+6] ==false ' hasn't changed direction in the last tile case Actor[act+5] 0:BestPathToTarget(act+0,tx,ty)' move to target 2:BestPathToTarget(act+0,0,36) ' scatter mode so inky is bottom right 3:RandomPath(act) ' move randomly 4:GhostToPen(act) ' ghost has died, eyes to the pen PUB BestPathToTarget(A,tx,ty) | i,sx,sy,newdirvalue,newdir' pass actor and target x and target y, returns direction 0,1,2,3 best direction ' better to look for available routes (16,18,19) than walls (many numbers) ' http://home.comcast.net/~jpittman2/pacman/pacmandossier.html#CH3_What_Tile_Am_I_In Intersections ' search the Adjacent array and exclude. Work out distance sx := Actor[A+2] >>3 ' source actor x sy := Actor[A+3] >>3 ' source actor y repeat i from 0 to 3 Distance[i] := 10000 ' nominal high value to start with if AvailablePath(i) == true ' can go this way case i ' u,d,l,r 0:Distance[i] := GetDistance(sx,sy-1,tx,ty)' distance Blinky to Pacman from square above 1:Distance[i] := GetDistance(sx,sy+1,tx,ty) ' distance from square below 2:Distance[i] := GetDistance(sx-1,sy,tx,ty) ' distance from square to the left 3:Distance[i] := GetDistance(sx+1,sy,tx,ty) ' distance from square to the right ' now find the shortest one. If it involves a direction change do this. But not if involves a reverse direction ' make the reverse of the current direction not available with a large value case Actor[A+0] ' exclude reverses as these are not allowed 0:Distance[1] := 10000 ' going up so exclude down as a possibility 1:Distance[0] := 10000 ' going down so exclude up as a possibility 2:Distance[3] := 10000 ' going left so exclude right 3:Distance[2] := 10000 ' going right so exclude left ' now there may be only one or two left. Work out the shortest distance. newdirvalue := 10000 newdir := Actor[A+0] ' start with the existing direction repeat i from 0 to 3 if Distance[i] < newdirvalue newdirvalue := Distance[i] newdir := i ' get the shortest (or only allowed) direction if Actor[A+0] <> newdir ' direction changes Actor[A+0] := newdir ' this is the new direction CentreActor(A+0) Actor[A+6] := true ' recent change in direction PUB RandomPath(A) | i,reversedir ' move ghost pseudo randomly if scared case Actor[A] ' current direction so exclude reverse as an option 0:reversedir := 1 1:reversedir := 0 2:reversedir := 3 3:reversedir := 2 ?random ' get a new random number i := random>>30 ' random number 0 to 3 repeat 4 if availablepath(i) == true and i <> reversedir and actor[A+6] <> true ' not reverse and no recent change Actor[A] := i CentreActor(A+0) Actor[A+6] := true ' recent change in direction i += 1 if i > 3 i:= 0 PUB GhostToPen(A) ' return eyes to the pen BestPathToTarget(A,14,14) ' return to the door, then ?other code puts ghost in the pen if actor[a+2]>>3 == 14 and actor[a+3]>>3 == 14 actor[a+5] := 0 ' back to chase mode actor[a+1] := (pacmanspeed * 95)/100 ' restore speed PUB PacmanAI ' controlled by user but write as AI for debugging ' know what is around if availablepath(0) == false and actor[32] == 0 actor[33] := 0 ' hit a wall going up so stop moving if availablepath(1) == false and actor[32] == 1 actor[33] := 0 ' hit a wall going down so stop moving if availablepath(2) == false and actor[32] == 2 actor[33] := 0 ' hit a wall going left so stop moving if availablepath(3) == false and actor[32] == 3 actor[33] := 0 ' hit a wall going right so stop moving if availablepath(pacmannewdir) == true and actor[32] <> pacmannewdir Actor[32] := pacmannewdir ' moving new direction CentreActor(32) ' recentre pacman if move direction Actor[33] := pacmanspeed ' start moving again Actor[36] := 0 ' offset is zero PUB PacmanCollision | px,py,i ' ignore ghost collisions, they are ghosts and pass through each other! ' can gobble up more than one ghost if two happen to be in the same square and both scared px := Actor[32+2]>>3 ' pacman x py := Actor[32+3]>>3 ' pacman y repeat i from 0 to 24 step 8 if px == (Actor[i+2]>>3) and py == (Actor[i+3]>>3) case Actor[i+5] ' ghost mode 0,2:PacmanAlive := false ' pacman has died! 3: Actor[i+5] := 4 ' ghost was blue so ghost dies and is converted to eyes Actor[i+1] := pacmanspeed ' eyes back to full speed ScaredGhostScore PUB ScaredGhostScore ' pass ghosteatencounter case ghostseatencounter 0:score += 200 1:score += 400 2:score += 600 3:score += 800 ghostseatencounter +=1 PUB GhostLeavePen case score 0..290: ' do nothing 300..590: ReleaseGhost(8) ' pinky 600..1190: ReleaseGhost(8) ' pinky ReleaseGhost(16) ' inky 1200..1790:ReleaseGhost(8) ' pinky ReleaseGhost(16) ' inky ReleaseGhost(24) ' clyde PUB ReleaseGhost(A) if Actor[A+5] == 1 ' still in the pen so let him out Actor[A+5] := 0 ' chase mode Actor[A+2] := 12*8+(A<<1) ' x plus a small offset so ghosts not on top of each other if all let out at once Actor[A+3] := 14*8+4 ' y Actor[A] := 3 ' move right RedrawPen ' redraw the ghost pen PUB RedrawPen | i,j repeat i from 10 to 17 repeat j from 15 to 19 RedrawTile(i,j) PUB GetDistance(sx,sy,tx,ty) result := (sx-tx)*(sx-tx)+(sy-ty)*(sy-ty) ' no need to do the square root PUB AvailablePath(n) ' pass n=0 up, 1=down, 2=left, 3=right case Adjacent[n] 16,18,19:result := true ' can go this way other: result := false ' a wall or something PUB Tunnel(A) | tx,ty,i,j tx:= Actor[A+2]>>3 ty:= Actor[A+3]>>3 if tx == 0 and ty == 17 and Actor[A] == 2 ' moving left through left tunnel Actor[A+2] := 27<<3 ' move to other side repeat i from 0 to 1 ' erase old picture repeat j from 16 to 18 RedrawTile(i,j) if tx == 27 and ty ==17 and Actor[A] == 3 ' moving right through right tunnel Actor[A+2] := 0<<3 ' move to other side repeat i from 25 to 27 repeat j from 16 to 18 RedrawTile(i,j) PUB PrintScore(x,y) | number,c number := score repeat 6 c := (||(number // 10)) c += "0" ' could maybe add some leading zero suppression here? DrawTile8(5,c,x,y) number /= 10 x -= 8 ' http://home.comcast.net/~jpittman2/pacman/pacmandossier.html#CH3_What_Tile_Am_I_In DAT maze ' tile number ' 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 byte 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 ,16,16,16,16,16,16,16,16,16,16,16 ' 0 byte 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 ,16,16,16,16,16,16,16,16,16,16,16 ' 1 byte 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 ,16,16,16,16,16,16,16,16,16,16,16 ' 2 byte 25,21,21,21,21,21,21,21,21,21,21,21,21,30,29,21,21 ,21,21,21,21,21,21,21,21,21,21,26 ' 3 byte 23,18,18,18,18,18,18,18,18,18,18,18,18,48,47,18,18 ,18,18,18,18,18,18,18,18,18,18,24 ' 4 byte 23,18,41,46,46,42,18,41,46,46,46,42,18,48,47,18,41 ,46,46,46,42,18,41,46,46,42,18,24 ' 5 byte 23,19,48,16,16,47,18,48,16,16,16,47,18,48,47,18,48 ,16,16,16,47,18,48,16,16,47,19,24 ' 6 byte 23,18,43,45,45,44,18,43,45,45,45,44,18,43,44,18,43 ,45,45,45,44,18,43,45,45,44,18,24 ' 7 byte 23,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18 ,18,18,18,18,18,18,18,18,18,18,24 ' 8 byte 23,18,41,46,46,42,18,41,42,18,41,46,46,46,46,46,46 ,42,18,41,42,18,41,46,46,42,18,24 ' 9 byte 23,18,43,45,45,44,18,48,47,18,43,45,45,38,37,45,45 ,44,18,48,47,18,43,45,45,44,18,24 ' 10 byte 23,18,18,18,18,18,18,48,47,18,18,18,18,48,47,18,18 ,18,18,48,47,18,18,18,18,18,18,24 ' 11 byte 27,22,22,22,22,42,18,48,39,46,46,42,16,48,47,16,41 ,46,46,40,47,18,41,22,22,22,22,28 ' 12 byte 16,16,16,16,16,23,18,48,37,45,45,44,16,43,44,16,43 ,45,45,38,47,18,24,16,16,16,16,16 ' 13 byte 16,16,16,16,16,23,18,48,47,16,16,16,16,16,16,16,16 ,16,16,48,47,18,24,16,16,16,16,16 ' 14 byte 16,16,16,16,16,23,18,48,47,16,50,22,54,20,20,55,22 ,51,16,48,47,18,24,16,16,16,16,16 ' 15 byte 21,21,21,21,21,44,18,43,44,16,24,16,16,16,16,16,16 ,23,16,43,44,18,43,21,21,21,21,21 ' 16 byte 16,16,16,16,16,16,18,16,16,16,24,16,16,16,16,16,16 ,23,16,16,16,18,16,16,16,16,16,16 ' 17 byte 22,22,22,22,22,42,18,41,42,16,24,16,16,16,16,16,16 ,23,16,41,42,18,41,22,22,22,22,22 ' 18 byte 16,16,16,16,16,23,18,48,47,16,52,21,21,21,21,21,21 ,53,16,48,47,18,24,16,16,16,16,16 ' 19 byte 16,16,16,16,16,23,18,48,47,16,16,16,16,16,16,16,16 ,16,16,48,47,18,24,16,16,16,16,16 ' 20 byte 16,16,16,16,16,23,18,48,47,16,41,46,46,46,46,46,46 ,42,16,48,47,18,24,16,16,16,16,16 ' 21 byte 25,21,21,21,21,44,18,43,44,16,43,45,45,38,37,45,45 ,44,16,43,44,18,43,21,21,21,21,26 ' 22 byte 23,18,18,18,18,18,18,18,18,18,18,18,18,48,47,18,18 ,18,18,18,18,18,18,18,18,18,18,24 ' 23 byte 23,18,41,46,46,42,18,41,46,46,46,42,18,48,47,18,41 ,46,46,46,42,18,41,46,46,42,18,24 ' 24 byte 23,18,43,45,38,47,18,43,45,45,45,44,18,43,44,18,43 ,45,45,45,44,18,48,37,45,44,18,24 ' 25 byte 23,19,18,18,48,47,18,18,18,18,18,18,18,16,16,18,18 ,18,18,18,18,18,48,47,18,18,19,24 ' 26 byte 34,46,42,18,48,47,18,41,42,18,41,46,46,46,46,46,46 ,42,18,41,42,18,48,47,18,41,46,36 ' 27 byte 33,45,44,18,43,44,18,48,47,18,43,45,45,38,37,45,45 ,44,18,48,47,18,43,44,18,43,45,35 ' 28 byte 23,18,18,18,18,18,18,48,47,18,18,18,18,48,47,18,18 ,18,18,48,47,18,18,18,18,18,18,24 ' 29 byte 23,18,41,46,46,46,46,40,39,46,46,42,18,48,47,18,41 ,46,46,40,39,46,46,46,46,42,18,24 ' 30 byte 23,18,43,45,45,45,45,45,45,45,45,44,18,43,44,18,43 ,45,45,45,45,45,45,45,45,44,18,24 ' 31 byte 23,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18 ,18,18,18,18,18,18,18,18,18,18,24 ' 32 byte 27,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22 ,22,22,22,22,22,22,22,22,22,22,28 ' 33 byte 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 ,16,16,16,16,16,16,16,16,16,16,16 ' 34 byte 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 ,16,16,16,16,16,16,16,16,16,16,16 ' 35
Comments
http://forums.parallax.com/showthread.php?119075-Hydra-NES-Pacman-Emulator