Create a gist now

Instantly share code, notes, and snippets.

Pac-Man in 140byt.es

An oldskool remake of the classic arcade game Pac-Man almost completely made with 140 bytes snippets. Collect all points in all six levels to win the game. Avoid the ghosts. Click here to play the game.

My getFirstElement is based on @jed's cssSelect. Everything else is created from scratch. Tested with Opera, Firefox and Internet Explorer 8.

Optional graphics are supported and can be downloaded here.

function(l, k) //level object, keyCode
{
l[l.x] = 0; //remove player from level
l.p += l[ //increase points
l.x += //walk
l.d = //calculate walking direction
k - 37 >> 2 || //if no cursor key was pressed or
l[l.x + ( //check new position
k = k % 2 //calculate new walking direction
? k - 38 //-1 is left, +1 is right
: (k - 39) * l.w //-width is up, +width is down
)] & 4 //if it's not possible to use new direction
? l[l.x + l.d //then check old direction
] & 4 //if it's a wall
? 0 //stop
: l.d //else use old direction
: k //else use new direction
] == 2; //if walking into some gold
l.e |= l[l.x] & 8;
l[l.x] = 1 //place player in level
}
function(l,k){l[l.x]=0;l.p+=l[l.x+=l.d=k-37>>2||l[l.x+(k=k%2?k-38:(k-39)*l.w)]&4?l[l.x+l.d]&4?0:l.d:k]==2;l.e|=l[l.x]&8;l[l.x]=1}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2011 Thiemo Mättig <http://maettig.com/>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "pacMan",
"description": "Pac-Man made with 140 bytes snippets.",
"keywords": [
"game",
"pacman"
]
}
<!DOCTYPE html>
<title>Pac-Man in 140byt.es by @maettig</title>
<pre>LOADING...</pre>
<h2></h2>
<span id="points">0</span> POINTS
<style type="text/css">
* { color: #FFF; font-family: Consolas, monospace; text-shadow: 0 0 .2em #FFF; }
body { background: #000; margin: 4em; }
h2 { font-size: 3em; margin: .2em 0; }
img { border: 0; font-size: 32px; height: 32px; padding: 1px; vertical-align: top; width: 32px; }
pre { font-size: 58px; line-height: 32px; margin: 0; }
#points { font-size: 2em; line-height: 2em; }
.air { color: #222; text-shadow: 0 0 .2em #222; }
.player { color: #FD0; text-shadow: 0 0 .2em #FD0; }
.wall { color: #30F; text-shadow: 0 0 .2em #30F; }
.ghost { color: #A0F; text-shadow: 0 0 .2em #A0F; }
</style>
<script type="text/javascript">
// 138 bytes
var getFirstElement = function(a)
{
a = /([#.]?)(.*)/.exec(a || '*');
a = document['getElement' + (a[1] > '-' ? 'sByClassName' : a[1] ? 'ById' : 'sByTagName')](a[2]);
return a[0] || a
}
// 69 bytes
var getOuterHTML = function(a)
{
return a.outerHTML || XMLSerializer().serializeToString(a)
}
// 126 bytes
var parseLevel = function(l, w)
{
l = l.join ? l[0].length > 1 //if it's an array of strings per row
? l.join('\n') : l.join('') : l; //then join rows else join characters
w = l.indexOf('\n') + 1; //calculate row length
// It would be possible to calculate player position with `l.indexOf(1)` but that's 7 bytes longer.
// It would be possible to count gold with `l.split(2).length - 1` but that's 11 bytes longer.
l = l.split(''); //convert to character array
l.w = w; //store width
l.c = l.d = l.p = 0; //initialize gold count, player walking direction, points
return l
}
// 110 bytes
var initLevel = function(l)
{
for (var i = g = []; i < l.length; )
{
l[i] & 8 && //if found a ghost then
g.push({ //append to the array of ghosts
x: i, //ghost position
d: 0 //ghost walking direction (0 = north)
});
if (l[i] & 1) l.x = i; //store player position
l.c += l[i++] == 2 //count gold
}
return g
}
// 136 bytes
var loadSprites = function(t, f) //theme, filename with $1 placeholder
{
for (var i in t)
with (new Image()) //new is required in IE
{
alt = i;
onload = function()
{
t[this.alt] = getOuterHTML(this)
}
src = t[i].replace(/.*"(.*)".*/, f)
}
}
// 88 bytes
var renderLevel = function(l, t) //level object, optional theme object
{
for (var c, h = '', x = 0; x < l.length; x++)
h += t && t[c = l[x] & 8 || l[x]] //ghosts can sit on gold, render the ghost only
? t[c] : c; //use theme or original character (e.g. the newline)
return h
}
// 129 bytes
var updatePlayer = function(l, k)
{
l[l.x] = 0;
l.p += l[l.x += l.d = k - 37 >> 2 || l[l.x + (k = k % 2 ? k - 38 : (k - 39) * l.w)] & 4 ? l[l.x + l.d] & 4 ? 0 : l.d : k] == 2;
l.e |= l[l.x] & 8;
l[l.x] = 1
}
// 134 bytes
var updateGhosts = function(a, l, g, d)
{
for (g in a)
l[a[g].x] &= 7; //remove all ghosts first
for (g in a)
{
g = a[g];
d = g.d % 4;
d = d % 2 ? d - 2 : (d - 1) * l.w; //calculate walking direction
l[g.x + d] & 4 //if walking into a wall
? g.d++ //rotate ghost by 90 degree
: l.e |= 1 & l[ //else check if hitting the player
g.x += d]; //and walk
l[g.x] |= 8 //place ghost
}
}
var levels = [parseLevel([
'44444444444', //no ghosts
'42222222224',
'42444242424',
'42412242424',
'42444242424',
'42222242224',
'44444444444']),
parseLevel([
'4444444444444', //1 ghost
'4222224222224',
'4244424244424',
'4242228222424',
'4242424442424',
'4242421242424',
'4242444242424',
'4242222222424',
'4244424244424',
'4222224222224',
'4444444444444']),
parseLevel([
'444444444444444', //2 ghosts
'424222222222224',
'424244444242444',
'422222222222224',
'424244444242424',
'424242212242424',
'424242444442424',
'422222222222224',
'444242444442424',
'482222222222484',
'444444444444444']),
parseLevel([
'4444444444444444444', //classic with 2 ghosts
'4222242228222422224',
'4244242444442424424',
'4242222222222222424',
'4242442440442442424',
'4222222408042222224',
'4242442444442442424',
'4242222221222222424',
'4244242444442424424',
'4222242222222422224',
'4444444444444444444']),
parseLevel([
'444444444444444', //3 ghosts
'422222282222224',
'424444424444424',
'424222424822424',
'424242424242424',
'422242212242224',
'424242424242424',
'424228424222424',
'424444424444424',
'422222222222224',
'444444444444444']),
parseLevel([
'4444444444444444444', //5 ghosts
'4222222248222222224',
'4244444242444442444',
'4242222282222222224',
'4242444442444442424',
'4242422242482222424',
'4242424242424244424',
'4222222222222228224',
'4244424242424242424',
'4242222842422242424',
'4242444442444442424',
'4222222222222222424',
'4442444442424444424',
'4222222122422222224',
'4444444444444444444'])];
var theme = {
0: '<span class="air">\u2219</span>', //doesn't need to be checked
1: '<span class="player">\u263B</span>', //can be checked with &1
2: '<span class="gold">\u2219</span>', //can be checked with &2
4: '<span class="wall">\u25A1</span>', //can be checked with &4
8: '<span class="ghost">\u00A4</span>', //can be checked with &8 or >7
'\n': '<br>' //required for IE
}
loadSprites(theme, 'sprite-$1.gif'); //load optional GIF files from the same directory
document.onkeydown = function(e)
{
level.keyCode = (e || window.event).keyCode
}
var ghosts, level, number = 0;
window.setInterval(function()
{
if (!level && levels[number])
ghosts = initLevel(level = levels[number++]);
if (level.p >= level.c)
{
if (level.p < level.c-- + 4) //wait a few moments before loading next level
return;
level = 0 //unload current level when won
}
if (!level || level.e) //stop updating when lost or won
return;
updatePlayer(level, level.keyCode || 0);
updateGhosts(ghosts, level);
getFirstElement('PRE').innerHTML = renderLevel(level, theme);
getFirstElement('#points').innerHTML = level.p;
getFirstElement('H2').innerHTML = level.e ? 'GAME OVER!' : level.p < level.c ? '' : 'YOU WIN!'
}, 250);
</script>
@jed
jed commented Nov 22, 2011

here's a demo.

@p01
p01 commented Nov 22, 2011

getFirstElement in 134bytes:

function(a){a=/(\W?)(.*)/.exec(a);return(a=document['getElement'+(a[1]=='#'?'ById':a[1]?'sByClassName':'sByTagName')](a[2]))&&a[0]||a}
@maettig
Owner
maettig commented Nov 22, 2011

It's so easy. Thank you. Enough room to add the possibility to omit the parameter. In this case it returns the first element in the document (which is the HTMLHtmlElement in most browsers but the DOCTYPE as an HTMLCommentElement in IE). This is 139 bytes. I tried to allow the parameter '*' instead or in addition to leaving out the parameter but if I do so it always becomes bigger than 140 bytes.

function(a){a=/(\W?)(.*)/.exec(a||' *');a=document['getElement'+(a[1]>'-'?'sByClassName':a[1]<1?'sByTagName':'ById')](a[2]);return a[0]||a}
@qfox
qfox commented Nov 22, 2011

Some basics...

if(x)y; could be x&&y;
if(x)y;else z; could be x?y:z;

More specific cases:

I think k>36&&k<41 could be k-36<5

So with the above, the ifs first become:

l[l.x+l.d]=='#' && l.d=0; 
k-36<5&&l[l.x+(k=k%2?k-38:(k-39)*l.w)]!='#'&&l.d=k;

I think there are some more "easy" gains to be had but I don't really have the time right now to dive deeper into it...

@maettig
Owner
maettig commented Nov 22, 2011

@qfox, you are right, all if can be replaced with &&. Thank you. Edit: To bad, using && does not save a byte. if(a)b=c; needs to be a&&(b=c);. And k-36<5 = k-36+36<5+36 = k<41 so it's not the same.

@p01, I think I found an almost perfect solution for my getFirstElement function (138 bytes). Now it's possible to use IDs (e.g. '#top'), class names (e.g. '.item'), tag names (e.g. 'pre'), the special class name '*' and no parameter (does the same as '*').

function(a){a=/([#.]?)(.*)/.exec(a||'*');a=document['getElement'+(a[1]>'-'?'sByClassName':a[1]?'ById':'sByTagName')](a[2]);return a[0]||a}
@maettig
Owner
maettig commented Nov 23, 2011

After some bit fiddling I found that k>36&&k<41?true:false is identical to k-37>>2?false:true. I saved some additional bytes by rearranging the code without changing the game logic. I did an update. Next step? Adding enemies? Graphical tiles? A bunch of 140 bytes GIFs? Edit: While looking at what I did I saw it's possible to move l.d=... down to where it's used. 127 bytes.

@maettig
Owner
maettig commented Nov 30, 2011

New features: A ghost is walking around. Internet Explorer is supported. Graphics are supported. Unfortunately, I have no idea how to upload binary files at GitHub so you have to download this ZIP archive to see my bunch of tiny GIF images (each below 140 bytes, of course) in action. Next step: Multiple ghosts.

@tsaniel
tsaniel commented Dec 1, 2011

The bad thing is that the control is lagged.

@maettig
Owner
maettig commented Dec 1, 2011

You are right. I was always wondering since I started working on this. Now I found the reason. I was doing the main loop in the wrong order: It was waiting for a key → rendering the level → processing the key. Now it is waiting for a key → processing the key → rendering the level.

@maettig
Owner
maettig commented Jan 2, 2012

Major update to my "Pac-Man in @140bytes" adventure: It's a full game now, including multiple levels and ghosts. Click here to play it.

@tsaniel
tsaniel commented Jan 2, 2012

Great.

@maettig
Owner
maettig commented Jan 16, 2012

I introduced a bug while golfing my updateGhosts function down. Some ghosts in the last level got stuck. Now I'm back at 152 bytes for that function.

@p01
p01 commented Apr 13, 2012

Sorry I haven't tried but why not replace the following code in updateGhosts

for(g=a.length;g--;)
  l[a[g].x]=a[g].b;
for(g in a)
{
  g=a[g];
  //...

by

for(g in a)
{
  g=a[g];
  l[g.x]=g.b;
  //...
@maettig
Owner
maettig commented Apr 14, 2012

When the paths of two ghosts intersect the first stores a backup of the ground (which is either air or gold) and the second stores a backup of the first ghost. I have to restore these backups in reverse order.

I think I can drop this backup stuff. Edit: I did it. I introduced this when my array was made of characters. But I switched to integers. Now a ghost (8) on gold (2) can be stored as 10. Setting a ghost is |8, removing a ghost is &7.

@p01
p01 commented Apr 17, 2012

Nice touch with the bitmask. I replaced:

for(g=a.length;g--;)
  l[a[g].x]&=7;
for(g in a)
{
  g=a[g];
  //...

by

for(g in a)
{
  g=a[g];
  l[g.x]&=7;
  //...

and played a couple of levels without noticing any glitch.

@maettig
Owner
maettig commented Apr 17, 2012

Thanks. There is a tiny glitch unfortunately. If the paths of two ghosts intersect one of the ghosts disappears for one frame.

If your looking for a new challenge, my decodeMinecraftColors needs to be golfed down by 10 bytes.

@aemkei
aemkei commented Jun 14, 2015

Wow: that was more than three years ago!

Now we reanimate this awesome work and hacked the HTML+JS down to 460 bytes:

https://github.com/codegolf/pac-man/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment