Created
September 11, 2024 21:49
-
-
Save andymasteroffish/f1c2ff8da83719840e61f59df111cee7 to your computer and use it in GitHub Desktop.
Commented 1K Pac-Man by Andy Wallace
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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