Shop OBEX P1 Docs P2 Docs Learn Events
Pacman for the Propeller — Parallax Forums

Pacman for the Propeller

Dr_AculaDr_Acula Posts: 5,484
edited 2012-10-01 16:14 in Propeller 1
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.
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          
800 x 600 - 103K

Comments

  • mindrobotsmindrobots Posts: 6,506
    edited 2012-09-29 06:18
    Well done, good doctor!!Marvels like this make me wonder if Chip ever imagined Pacman playing on a Propeller (especially with a touch screen) when he created it.Edit: and then with my marvelous iPad, I can't get a space or CR in between the last ! And the M in Marvels!! :0)
  • HumanoidoHumanoido Posts: 5,770
    edited 2012-09-29 06:43
    That's very impressive with the touch screen. If you want to run the standard version, a thread is here.

    http://forums.parallax.com/showthread.php?119075-Hydra-NES-Pacman-Emulator
  • HumanoidoHumanoido Posts: 5,770
    edited 2012-09-29 06:51
    mindrobots wrote: »
    Well done, good doctor!!Marvels like this make me wonder if Chip ever imagined Pacman playing on a Propeller (especially with a touch screen) when he created it.Edit: and then with my marvelous iPad, I can't get a space or CR in between the last ! And the M in Marvels!! :0)
    I agree he really did a great job! His skill has continually increased since he began posting and I believe he started at a very high level. I don't have the ipad in front of me but I recall a similar thing where I solved it by copy-paste. If the <CR> is not working, do a reset.
  • Invent-O-DocInvent-O-Doc Posts: 768
    edited 2012-09-29 07:10
    Very cool project. It was fun to see the code.
  • TylerSkylerTylerSkyler Posts: 72
    edited 2012-09-29 11:49
    Truly remarkable. Good work, but I need you to stop doing amazing stuff with the touchscreen, it is making me look bad(I can't even get mine to display text :lol:).
  • potatoheadpotatohead Posts: 10,261
    edited 2012-09-29 13:01
    Oh, I want a touch screen now. Very well done! And a nice demonstration of SPIN doing what it does well too.
  • localrogerlocalroger Posts: 3,451
    edited 2012-09-29 13:39
    Totally excellent implementation.
  • average joeaverage joe Posts: 795
    edited 2012-09-29 15:19
    Killer work Doc! Makes me wish I had more time for coding!
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2012-10-01 16:14
    Thankyou for all the kind words. It has been a lot of fun coding this and now playing it!
Sign In or Register to comment.