aUCBLogo Demos and Tests / pacman


to pacman
   
; written by David Costanzo
   ;
    ; The author disclaims all copyrights associated with this code (hereafter
    ; referred to as the "Work").
    ;
    ; The author makes this dedication for the benefit of the public at large
    ; and to the detriment of the author's heirs and successors. The author
    ; intends this dedication to be an overt act of relinquishment in
    ; perpetuity of all present and future rights under copyright law,
    ; whether vested or contingent, in the Work. The author understands that
    ; such relinquishment of all rights includes the relinquishment of all
    ; rights to enforce (by lawsuit or otherwise) those copyrights in the
    ; Work.
    ;
    ; The author recognizes that, once placed in the public domain, the
    ; Work may be freely reproduced, distributed, transmitted, used,
    ; modified, built upon, or otherwise exploited by anyone for any
    ; purpose, commercial or non-commercial, and in any way, including by
    ; methods that have not yet been invented or conceived.
   
local [GameBoardTemplate GridSize ]
   
InitializeGameData
   
eat_pellet_wav="start.wav
   
you_win_wav="C:\Windows\Media\tada.wav
   
you_die_wav="C:\Windows\Media\chord.wav
   
clearscreen
   
hideturtle
   
disableLineSmooth
   
ConsoleSetfocus
   
GameLoop
end

to BoardSquareIsWall :x :y
   
output (GetBoardSquare :x :y) == "X
end

to DrawEnemy :Enemy
   
localmake "x item :Enemy
   
localmake "y item :Enemy

   
MoveToGridQuarter :x :y

   
setpencolor 0
   
repeat fd :gridsize rt 90 ]
end

to DrawGameBoard
   
local [i j gameboard_row boardsquare]

   
; now draw the game board
   
for [:GameBoardHeight ] [

      
make "gameboard_row item :j :GameBoard
      
for [:GameBoardWidth ] [

          
make "boardsquare item :i :gameboard_row
          
if :boardsquare == "X [
            
DrawWall :i :j
          
]
          
if :boardsquare == ". [
            
DrawPellet :i :j
          
]
      
]
   
]
end

to DrawPellet :X :Y
   
MoveToGridCenter :X :Y

   
setpencolor 0
   
setpensize [2 2]
   
fd 1
   
setpensize [1 1]
end

to DrawPlayer :X :Y
   
MoveToGridCenter :X :Y

   
setpencolor 0
   
circle :GridSize 2
end

to DrawWall :X :Y
   
MoveToGrid :X :Y

   
setpencolor 0
   
repeat fd :gridsize rt 90 ]
end

to EraseEnemy :Enemy
   
localmake "x item :Enemy
   
localmake "y item :Enemy

   
MoveToGridQuarter :x :y

   
setpencolor screencolor
   
repeat fd :GridSize rt 90 ]
end

to ErasePellet :X :Y
   
MoveToGridCenter :X :Y

   
setpencolor screencolor
   
setpensize [2 2]
   
fd 1
   
setpensize [1 1]
end

to ErasePlayer :X :Y
   
MoveToGridCenter :X :Y

   
setpencolor screencolor
   
circle :GridSize 2
end

to GetBoardSquare :x :y
    
output item :x (item :y :GameBoard)
end

to InitGameBoard
   
local [i j gameboardtemplate_row row_array boardsquare]

   
; copy the game board template to the live game board
   
make "TotalPellets  0


   
make "GameBoardWidth 0
   
make "GameBoardHeight  count :GameBoardTemplate

   
make "GameBoard array :GameBoardHeight
   
for [:GameBoardHeight ] [

      
make "gameboardtemplate_row item :j :GameBoardTemplate

      
; add a new array for this row
      
make "row_array array count :gameboardtemplate_row

      
; update the board width
      
if :GameBoardWidth < (count :gameboardtemplate_row) [
          
make "GameBoardWidth (count :gameboardtemplate_row)
      
]

      
for [[count :gameboardtemplate_row] ] [

          
make "boardsquare item :i :gameboardtemplate_row

          
setitem :i :row_array :boardsquare

          
if :boardsquare == ". [
             
make "TotalPellets :TotalPellets 1
          
]
          
if :boardsquare == "P [
             
Make "PlayerStartX :i
             
Make "PlayerStartY :GameBoardHeight :j 1

             
; put an empty space where the player was
             
setitem :i :row_array ",
          
]
          
if :boardsquare == "1 [
             
make "Enemy1 (list :i :GameBoardHeight :j "up)

             
; put a pellet where the enemy was
             
make "TotalPellets :TotalPellets 1
             
setitem :i :row_array ".
          
]
          
if :boardsquare == "2 [
             
make "Enemy2 (list :i :GameBoardHeight :j "up)

             
; put a pellet where the enemy was
             
make "TotalPellets :TotalPellets 1
             
setitem :i :row_array ".
          
]
          
if :boardsquare == "3 [
             
make "Enemy3 (list :i :GameBoardHeight :j "up)

             
; put a pellet where the enemy was
             
make "TotalPellets :TotalPellets 1
             
setitem :i :row_array ".
          
]
          
if :boardsquare == "4 [
             
make "Enemy4 (list :i :GameBoardHeight :j "up)

             
; put a pellet where the enemy was
             
make "TotalPellets :TotalPellets 1
             
setitem :i :row_array ".
          
]
      
]

      
; set this row in the game board (and flip the board vertically)
      
setitem :GameBoardHeight :j :GameBoard :row_array
   
]
   
make "TotalPelletsRemaining :TotalPellets
end

to InitializeGameData
   
make "GameBoardTemplate
   
{   XXXXXXXXXXXXXXXXX
      
X3......X......1X
      
X.XXXXX.X.XXXXX.X
      
X...X.......X...X
      
XXX.X.XXXXX.X.XXX
      
X...4.......2...X
      
X.XXXXXXXXXXXXX.X
      
X...X.......X...X
      
X.X.X.XX.XX.X.X.X
      
X.X...X...X...X.X
      
X.XXXXX.X.XXXXX.X
      
X.......X.......X
      
XXX.XXXXXXXXX.XXX
      
X...X.......X...X
      
X.X.X.XXXXX.X.X.X
      
X.X.X.......X.X.X
      
X.X.XXX.X.XXX.X.X
      
X.......X.......X
      
X.XXX.XXXXX.XXX.X
      
XP..............X
      
XXXXXXXXXXXXXXXXX
   
}
   
make "GridSize 25
end

to MoveEnemy :Enemy
   
localmake "PositionX item :Enemy
   
localmake "PositionY item :Enemy
   
localmake "Direction item :Enemy
   
localmake "NewDirection "still

   
; Choose a new direction for the enemy
   ; The directions are chosen such that
   ; 1) The enemy prefers to move in a straight line
   ; 2) The enemy doubles-back as a last resort
   ; 3) The enemy moves somewhat randomly


   
localmake "DirectionList [left up down right]

   
if :Direction == "left [
      
make "DirectionList pick [
         
[left up   down right]
         
[left down up   right]
         
[up   down left right]
      
]
   
]
   
if :Direction == "right [
      
make "DirectionList pick [
         
[right up    down  left]
         
[right down  up    left]
         
[down  up    right left]
      
]
   
]
   
if :Direction == "up [
      
make "DirectionList pick [
         
[up    left  right down]
         
[up    right left  down]
         
[right up    left  down]
      
]
   
]
   
if :Direction == "down [
      
make "DirectionList pick [
         
[down left  right up]
         
[down right left  up]
         
[left down  right up]
      
]
   
]
   
make "NewDirection TryToMoveInDirection :PositionX :PositionY :DirectionList

   
if :NewDirection == "left [
      
output (list :PositionX 1  :PositionY       :NewDirection)
   
]
   
if :NewDirection == "right [
      
output (list :PositionX 1  :PositionY       :NewDirection)
   
]
   
if :NewDirection == "up [
      
output (list :PositionX      :PositionY 1   :NewDirection)
   
]
   
if :NewDirection == "down [
      
output (list :PositionX      :PositionY 1   :NewDirection)
   
]

   
output :Enemy
end

to MoveToGrid :X :Y
    
penup
    
setxy :GridSize * (:X  :GameBoardWidth 2)     :GridSize * (:Y :GameBoardHeight 2)
    
pendown
end

to MoveToGridCenter :X :Y
    
penup
    
MoveToGridPlusOffset :X :Y 2
    
pendown
end

to MoveToGridPlusOffset :X :Y :Offset
    
penup
    
setxy :GridSize * (:X :GameBoardWidth +  :Offset)  :GridSize * (:Y :GameBoardHeight :Offset)
    
pendown
end

to MoveToGridQuarter :X :Y
    
penup
    
MoveToGridPlusOffset :X :Y 4
    
pendown
end

to PlayerEnemyCollision :Enemy
   
output and (:PlayerX == item :Enemy)  (:PlayerY == item :Enemy)
end

to ProcessKeyEvent :Key
    
if :Key==WXK_UP
   
[IsGoingUp=true IsGoingRight=false IsGoingLeft=false IsGoingDown=false]
    
if :Key==WXK_RIGHT
   
[IsGoingUp=false IsGoingRight=true IsGoingLeft=false IsGoingDown=false]
    
if :Key==WXK_LEFT
   
[IsGoingUp=false IsGoingRight=false IsGoingLeft=true IsGoingDown=false]
    
if :Key==WXK_DOWN
   
[IsGoingUp=false IsGoingRight=false IsGoingLeft=false IsGoingDown=true]
    
if :Key==WXK_ESCAPE [IsDone      "true     ]
end

to SetBoardSquare :x :y :value
    
setitem :x item :y :GameBoard :value
end

to StartKeyboardCapture
   
OnChar [ignore readChar]
   
OnKeyDown [ProcessKeyEvent keyboardvalue]
end

to EndKeyboardCapture
   
OnChar []
   
OnKeyDown []
end

to TryToMoveInDirection :X :Y :DirectionList
  
foreach :DirectionList [
    
if and (== "left)  (not BoardSquareIsWall :X :Y) [
       
output ?
    
]
    
if and (== "right) (not BoardSquareIsWall :X :Y) [
       
output ?
    
]
    
if and (== "up)    (not BoardSquareIsWall :X     :Y 1) [
       
output ?
    
]
    
if and (== "down)  (not BoardSquareIsWall :X     :Y 1) [
       
output ?
    
]
  
]
end

to UpdateNextFrame
   
localmake "NewPlayerX :PlayerX
   
localmake "NewPlayerY :PlayerY

   
if :IsGoingRight [make "NewPlayerX :NewPlayerX 1]
   
if :IsGoingLeft  [make "NewPlayerX :NewPlayerX 1]

   
if or (:NewPlayerX == :PlayerX)
      
(BoardSquareIsWall :NewPlayerX :NewPlayerY)
   
[   ; left-right didn't work out.  Try the up-down
      
make "NewPlayerX :PlayerX
      
if :IsGoingUp   [make "NewPlayerY :NewPlayerY 1]
      
if :IsGoingDown [make "NewPlayerY :NewPlayerY 1]
   
]
   
localmake "currentsquare GetBoardSquare :NewPlayerX :NewPlayerY

   
if :currentsquare == ".
   
[   ErasePlayer :PlayerX :PlayerY

      
Make "PlayerX :NewPlayerX
      
Make "PlayerY :NewPlayerY

      
; eat the pellet
      
ErasePellet :PlayerX :PlayerY
      
make "TotalPelletsRemaining :TotalPelletsRemaining 1
      
SetBoardSquare :PlayerX :PlayerY ",
      
playwave eat_pellet_wav 1

      
; if we just ate the last pellet, then do something cool
      
if :TotalPelletsRemaining == 0
      
[   make "IsDone "true
         
playwave you_win_wav 1

         
repeat 20
         
[   ; move outside the game board
            
penup
            
MoveToGrid 0 0
            
pendown
            
setfloodcolor (list random 256 random 256 random 256)
            
fill

            
; move inside the game board
            
MoveToGridCenter :PlayerX :PlayerY
            
setfloodcolor (list random 256 random 256 random 256)
            
fill

            
wait 6
         
]
         
stop
      
]
      
; draw the pac-turtle
      
DrawPlayer :PlayerX :PlayerY
   
]

   
if :currentsquare == ",
   
[   ErasePlayer :PlayerX :PlayerY
      
Make "PlayerX :NewPlayerX
      
Make "PlayerY :NewPlayerY
      
DrawPlayer :PlayerX :PlayerY
   
]
   
if (or (PlayerEnemyCollision :Enemy1)
   
(PlayerEnemyCollision :Enemy2)
   
(PlayerEnemyCollision :Enemy3)
   
(PlayerEnemyCollision :Enemy4))
   
[   make "IsDone "true
      
playwave you_die_wav 0
      
stop
   
]
   
; now, move the enemy toward the player

   
make "EnemyWaitCounter :EnemyWaitCounter 1
   
if :EnemyWaitCounter == 0
   
[   EraseEnemy :Enemy1
      
EraseEnemy :Enemy2
      
EraseEnemy :Enemy3
      
EraseEnemy :Enemy4

      
make "Enemy1 MoveEnemy :Enemy1
      
make "Enemy2 MoveEnemy :Enemy2
      
make "Enemy3 MoveEnemy :Enemy3
      
make "Enemy4 MoveEnemy :Enemy4

      
DrawEnemy  :Enemy1
      
DrawEnemy  :Enemy2
      
DrawEnemy  :Enemy3
      
DrawEnemy  :Enemy4

      
make "EnemyWaitCounter :EnemyWaitCounterMax
   
]
   
if (or (PlayerEnemyCollision :Enemy1)
   
(PlayerEnemyCollision :Enemy2)
   
(PlayerEnemyCollision :Enemy3)
   
(PlayerEnemyCollision :Enemy4))
   
[   make "IsDone "true
      
playwave you_die_wav 0
      
stop
   
]
   
updateGraph
end

to GameLoop
   
localmake "IsGoingUp    "false
   
localmake "IsGoingRight "false
   
localmake "IsGoingLeft  "false
   
localmake "IsGoingDown  "false

   
local [GameBoard GameBoardWidth GameBoardHeight]
   
local [PlayerStartX PlayerStartY]
   
local [Enemy1 Enemy2 Enemy3 Enemy4]
   
local [TotalPellets TotalPelletsRemaining]
   
InitGameBoard

   
localmake "EnemyWaitCounterMax 2
   
localmake "EnemyWaitCounter    :EnemyWaitCounterMax

   
localmake "PlayerX :PlayerStartX
   
localmake "PlayerY :PlayerStartY

   
DrawGameBoard
   
DrawPlayer :PlayerX :PlayerY
   
DrawEnemy  :Enemy1
   
DrawEnemy  :Enemy2
   
DrawEnemy  :Enemy3
   
DrawEnemy  :Enemy4

   
StartKeyboardCapture

   
localmake "IsDone "false
   
while not :IsDone ]
   
[   UpdateNextFrame
      
waitMS 150
      
dispatchMessages
   
]
   
EndKeyboardCapture
end