Skip to content

Instantly share code, notes, and snippets.

@andymasteroffish
Created September 11, 2024 21:49
Show Gist options
  • Save andymasteroffish/f1c2ff8da83719840e61f59df111cee7 to your computer and use it in GitHub Desktop.
Save andymasteroffish/f1c2ff8da83719840e61f59df111cee7 to your computer and use it in GitHub Desktop.
Commented 1K Pac-Man by Andy Wallace
pico-8 cartridge // http://www.pico-8.com
version 32
__lua__
-- 1K Pac-Man by Andy Wallace
-- Play it here: https://andymakes.itch.io/1k-pac
-- That page has the <1024 byte compressed source code
-- My links:
-- andymakes.com
-- https://mastodon.art/@andymakes
-- https://andymakesgames.tumblr.com/
-- Created for Pico1k Jam 2024: https://itch.io/jam/pico-1k-2024
-- Solid info on how Pac-Man works here: https://pacman.holenet.info/
-- --------------
-- Variable Names
-- --------------
-- I do my best to have these make sense, but sometimes I just need a letter I have not used yet
-- a abs()
-- r rect()
-- s pset()
-- q band()
-- b bitmask of keyboard input
-- t game time
-- g game running
-- u death timer
-- i object id
-- f frame time for object cycle
-- k current percent of object cycle duration
-- h keyboard input x
-- v keyboard input y
-- l list of objects
-- o object
-- p player
-- w player pixel x
-- z player pixel y
-- c col
-- r row
-- d distance
-- b best dist
-- m best col direction
-- n best row direction
-- j target x
-- k target y
-- x temp x
-- y temp y
-- e mouth angle offset
-- d mouth angle spread
-- q mouth angle
-- shortening the names of functions I use regularly
r=rect
a=abs
s=pset
-- setting the palette
-- color 0 is black, but color 1 is also black! More on this in a bit
-- color 2 is Pac-Man yellow (10)
-- colors 3-6 are the ghosts
pal({0,10,8,9,14,13},1)
-- the game board is drawn as a small grid where one pixel represents one tile
-- each cell gets blown up to 9x9 for the game
cls(12)
r(1,1,6,5,1)
r(8,1,13,5)
r(3,3,11,10)
r(5,5,9,13)
r(1,8,5,13)
r(9,8,13,13)
-- copy the board to the sprite sheet
memcpy(0,24576,16384)
-- game values
t=0 --timer
g=1 --game running (1 means running, 0 means game over)
u=0 --death animation timer. This value goes up when g is 0
-- list of objects
l={}
-- goto anchor for the game loop
::_::
-- this is a bit of tweetcode trickery that is a little faster than `flip()cls()`
?'⁶1⁶c'
-- draw the grid from the sprite sheet
-- blown up and offset, so that multiplying a given grid position by 9 will put it right in the center of that cell
sspr(0,0,16,16,-4,-4,144,144)
-- controls
-- adapted from https://demoman.net/?a=optimizing-for-tweetcarts
q=band
b=btn()
h=q(b,2)/2-q(b,1) --horizontal input (-1, 0 or 1)
v=q(b,8)/8-q(b,4)/4 --vertical input (-1, 0 or 1)
-- spawning objects every 70 frames until we have 5 objects
-- Pac-Man & 4 ghosts
-- objects have:
-- x grid x position
-- y grid y position
-- c horizontal direction
-- r vertical direction
if(t%70<1 and #l<5)add(l,{x=7,y=6,c=0,r=0})
--first object is Pac-Man and we reference it a lot, so storing a reference
p=l[1]
-- loop to draw pellets
-- goes through the grid and checks which black value is there
-- black (0) means empty, black (1) means we have a pellet
for x=0,16do
for y=0,16do
m=sget(x,y)==1 -- check if there is a pellet here
if(m)s(x*9,y*9,7) -- if there is, draw a white pixel on the game board
-- if pac-man is there, set that cell on the sprite sheet to 0
-- but only if m is true so we don't black out the starting position
if(p.x==x and p.y==y and m)sset(x,y,0)
end
end
-- if the game is running, increase the timer
t+=g
-- if g is 0, slowly increment the death animation
u-=(g-1)/99
-- default values before the object loop
f=6 -- frame timer starts at 6 and will go up for each object
i=1 -- object id starts at 1 and goes up each time. Pac-Man is object 2 to match the color palette
-- object loop to update all of the actors
for o in all(l)do
-- the time each object takes to move one cell increases with each loop
f+=2
-- increment the object id
-- id 2 = Pac-Man
-- id 3 = Red Ghost
-- id 4 = Orange Ghost
-- id 5 = Pink Ghost
-- id 6 = Blue Ghost
i+=1
-- if enough time has passed and the object has finished one move cycle, we need to make a decision about where they will go next
-- "<g" ensures that if the game ends with a t value that would trigger this, it will not run because g is 0
if t%f<g then
-- first, adjust the grid position with the current direction
o.x+=o.c
o.y+=o.r
-- j & k are clunky names for the X and Y grid target of this object
-- Pac-Man will have this value set but it will be ignored
-- red ghost targets player
-- this is the basis for every other ghost
j = p.x
k = p.y
--orange ghost targets pacman until he gets close
--this abs value check is a quick approximation of distance
--if Pac-Man is close, target is moved to bottom left corner
if(i==4 and a(j-o.x)+a(k-o.y)<5 )j,k=0,16
-- pink (and blue) ghost target the tile 3 spaces in front of Pac-Man
-- using Pac-Man's current direction
if(i>4)j,k=j+p.c*3,k+p.r*3
-- blue ghost modifies that value using red ghost position
if(i>5)j,k=j+(p.x-l[2].x),k+(p.y-l[2].y)
-- once we have the target, for each possible direction the object can move, figure out which one brings us closest to the target
-- we'll be looking at a few things for each of the 4 directions
-- is is open? No moving through walls
-- did the object come from this tile? No backtracking
-- is this the shortest distance to the goal?
-- current best distance, starts arbitrarily high
b = 99
-- this loop looks at the 9 tiles around the object
-- diagonal directions will be ignored.
for c=-1,1do
for r=-1,1do
-- get the grid position we are examining
x = o.x + c
y = o.y + r
-- get the (approximate) distance from this position to the target
-- "-sgn(o.c*c+o.r*r)*3" is a way to factor in the last movement direction to prevent backtracking. It will greatly increase the distance value if it would backtrack
d = a(j-x) + a(k-y) -sgn(o.c*c+o.r*r)*3
-- for the player, if the direction of this tile macthes the movement input, set distance to -9 to ensure we select it. Player can backtrack
if(c==h and r==v)d=-9
-- before storing a potential best direction, we check 3 things:
-- sget() tests if the grid pixel on this sprite is black (clear). If it is 2 or greater, than it's a wall and we cannot pass
-- "abs(c+r)==1" is what prevents diagonal. Only values like c=-1, r=0 will fit that formula
-- if this distance (d) is less than the current best distance (b), we're good to go!
if(sget(x,y)<2 and a(c+r)==1 and d<b)b,m,n=d,c,r
--the info stored here is
-- b the new best distance
-- m the c value of this direction
-- n the r value of this direction
--we can't write to o.c and o.r just yet because we need to check all 4 directions before modifying those values
end
end
--once we're done checking all 4 directions, the best posssible direction is written to the object
o.c,o.r=m,n
-- done checking things that happen when an object hits a decision frame
end
-- Pac-Man is the first object updated
-- changing one of the input values here ensures that no ghosts will use keyboard input
h=9
-- move and draw the object
-- k is a percentage of the way this object is to the next decision frame
-- k is then multiplied by 9 to match the distance to move across a tile (which is 9x9)
k=t/f%1*9
-- x and y are pixel positions calculated based on the grid position, the direction, and the percentage
-- the game board is 9x the size of the grid
x = o.x*9+o.c*k
y = o.y*9+o.r*k
-- for better collisions, it is useful to store the player's pixel position
-- no other object needs to store this
if(i<3)w,z=x,y
-- if this ID is a ghost and it is close to the player's pixel position, end the game
if(i>2 and a(x-w)+a(y-z)<6)g=0
-- all objects get a circle
circfill(x, y, 3, i)
-- this code controls drawing Pac-Man's mouth
-- I save on if statements by calculating it for ghosts too
-- when the game is over, the angle spread increases, creating the death animation
-- using min to clamp it at 1 isn't really necessary but I had extra characters. Without it, the screen starts to flicker after a while because of excess line draws
d = min(1,sin(t/9)/9+u)
-- draw a bunch of lines using the angle of the current direction as the center point
for e=-d,d,.01 do
q = atan2( o.c, o.r ) + e
-- only actually draw them if the ID is 2 (Pac-Man)
if(i<3)line(x,y, x+cos(q)*4, y+sin(q)*4,0)
end
-- ghosts get a little rectangle under them and two dots for eyes
if i>2then
r(x-3,y,x+3,y+3)
s(x-2,y,7)
s(x+2,y,7)
end
--we're done with everything for this object
end
--once all objects have been drawn and updated we return to the top of our game loop
goto _
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment