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