Yellow vs. Red, a roguelike game. Movement with mouse. Enemies are red, health green, level exits black. Walls can be moved like sokoban. Gaining enough experience or clearing level gives you extra he…
for(b="Z=document.body.children[0_X=WgetContE2dh=p=9;b=999;a=fc0U=555;s=111;m=f11;o=192;DV=y=gcc~~(Math.random()*cKh/++l;d={};e=[_w/l;0;t++<b*l;=-w-w+ym}}gJd?d[g]:!?s:!?o:3)?f:w}L(cuV;v=y;xQiXiyQjYjVy#m&&4)eAce(e.indexOf(g),1f;++k%p?0:h/l}#oh/7;f}#w&&V-uy-vN=Gw,f#s?):t!Gx=u,y=vd[uv];;tJeq=e[t]At(aiB0_jB1_=(i<x?i+Si>x?i-Si)(j<y?j+Sj>y?j-Sj))==ah-=7}else{)!?g=i6(j6):0;)=;m;d[ij]}}}h<1||l>palert(h<1?:(::)Dh=pU;Ki=p*2-1;Wwidth=WheighI;jj+8gg+8f;=g*v=j*30(-pj-pN#w||#s?,vP9P9):+3,v+3P3P3K}s;TEYvs.R LFl@HFh@XFk@MFM++,X.fon25KWonclick=L;L()",c=46;c>=0;)b=b.replace(RegExp("#/678@ABDEFGJKNPQSUVW_".charAt(c),"g"),"\042X.fillStyle=#++a+d[g]=function R(Rect(u){T(gt=);>300?1:0:0;if(for()<270?-1:=c.clientreturn d[xy]=ab)e[t]=g+x(30,=f=0;=+=+3)-1l)+1+<i;+ .spli=~~q[M=l=kext(+&&( in )}+y)),2/c?(1:;w=I=xZ.];".split("")[c--]);eval(b)
Zm9yKGI9Ilo9ZG9jdW1lbnQuYm9keS5jaGlsZHJlblswX1g9V2dldENvbnRFMmQBEmg9cD05O2IePTk5OTthPQFmYzABVT01NTU7cz0xMTE7bT0BZjExATtvPTE5MjtEVj15PWcfBgtjDxRjGH5+KE1hdGgucmFuZG9tKCkqY0toLysrbDtkPXt9O2U9W193L2w7FREwO3QrKzxiKmw7Gz0aLXccGi13K3kSBW19GX0GEBhnSmQ/ZFtnXToFIRo/czohGj9vOgszKT9mOnd9BkwoYw91Vjt2PXk7eFFpF1gWaRN5UWoXWRZqExEQVgR5EhQjbSYmCzQpD2VBY2UoZS5pbmRleE9mKGcpLDESBWY7KytrJXA/MDpoL2x9FCNvD2gvNzsRZn0jdyYmEFYtdRx5LXZOPUcFdywRZhIjcz8LKTp0IUd4PXUseT12EmRbdQR2XR47GTsVdEplD3E9ZVt0XUF0KGESaUIwX2pCMV8UED0oaTx4P2krU2k+eD9pLVNpKQQoajx5P2orU2o+eT9qLVNqKSk9PWEPaC09N31lbHNlexApIR4/Zz1pNgQoajYpOjA7FBApPR4bOwVtO2RbaQRqXR59fX0UaDwxfHxsPnAPYWxlcnQoaDwxPwE6KAE6ATopARJEH2g9cFU7C0tpPXAqMi0xO1d3aWR0aD1XaGVpZ2gRSTsVah9qKzgVZx9nKzgDZjsOPWcqHXY9aiodHTMwEgMoERAtcBxqLXBOEiN3fHwjcz8OLHZQOVA5KToOKzMsdiszUDNQM0t9A3M7AlRFWXZzLlIgTEZsQEhGaEBYRmtATUZNKyssHVguZm9uETI1S1dvbmNsaWNrPUw7CxJMKCkiLGM9NDY7Yz49MDspYj1iLnJlcGxhY2UoUmVnRXhwKCIBAgMEBQYLDg8QERITFBUWFxgZGhscHR4fIy82NzhAQUJERUZHSktOUFFTVVZXXyIuY2hhckF0KGMpLCJnIiksIlwwNDIDWC5maWxsAwJTdHlsZT0BIwErAythKwNkW2ddPQNmdW5jdGlvbiADUigDAlJlY3QodQMpewNUKGcDdD0DKTsDPjMwMD8xOjA6MDsDaWYoA2ZvcigDKTwyNzA/LTE6Az1jLmNsaWVudAMPcmV0dXJuIANkW3gEeV09YQMLYikDD2VbdF09ZwMreAQoAzMwLAM9ZgM9MDsDET0DKz0DKwszKS0xAwtsKSsxAys8aTsPAysBIAMuc3BsaQM9fn5xWwNNPWw9awNleHQoAQMBKwMeJiYoAyBpbiADKX0DK3kpKQMsMgMvYz8oAzE6Azt3PUkDPXgDWi4DXTsiLnNwbGl0KCIDIilbYy0tXSk7ZXZhbChiKQ==
/*
2010-09-10
Yellow vs. Red, a roguelike game in 1024 bytes of JavaScript:
by Nik Coughlin:
@nrknthuk
nrkn.com@gmail.com
http://nrkn.tumblr.com/
for full description, game algorithm, minification techniques and a more heavily commented version of this file see
http://nrkn.tumblr.com/YvsR/
*/
// short references to document and canvas to save some bytes
Z = document.body.children[0];
X = Z.getContext("2d");
// setting (h)ealth and view(p)ort radius to 9 at the same time saves some bytes
h = p = 9;
// (b)ig number constant
// (f)loor color - light grey
b = f = 999;
// pl(a)yer color - bright yellow. We put the leading # on in the function that sets fillStyle
a = "fc0";
// (I)nitial and current (w)all color (dark grey), canvas width and height, also used to VERY roughly approximate half of (b)ig number
// we need to save the initial wall color so the game can be reset when the player wins/dies
w = I = 555;
// (s)tair color - close to black. looks better than black and 000 would get changed to 0 when we concatenated it with # to make a color
s = 111;
// (m)onster color - red. f11 looks better than f00 and same number of bytes
m = "f11";
// p(o)tion color - green. nice green and because there are no characters in the hex range we can just make it a number, saves two "s
o = 192;
// (M)oves, (l)evel, (k)ills, player (x) location, player (y) location, (g)eneral variable, initialized to 0
M = l = k = x = y = g = 0;
/*
this function is actually two functions combined to save some bytes used by the overhead of declaring functions, if an arg c is passed
then it returns a random number, if not then it moves to the next dungeon level
*/
function R( c ){
//returns a non-negative integer less than c, double tilde instead of Math.floor saves some bytes (by doing a binary ones complement twice haha)
if( c ) return ~~( Math.random() * c );
//increment the level number and give player some extra health for clearing the level at the same time
h += ++l;
//reset the dungeon
d={};
//reset the enemies list
e=[];
//change the wall color
w += l;
//generate some enemies, more on higher levels, postincrement t in the test part of the for loop to save couple bytes
for( t = 0; t++ < b * l; ){
/*
get a key representing a location as x and y separated by @, for example -34@302. We can omit some parentheses around the first
part because of evaluation order etc. but have to leave them around the last part. at the same time assign it to g so we can
reuse it on the next line - this saves two bytes compared to assigning it to t separately
*/
e[ t ] = g = R( b ) - w + x + a + ( R( b ) - w + y );
//set the dungeon tile at this location to be a (m)onster
d[ g ] = m //always omit semicolons when they appear before a closing brace to save bytes
}
//set the dungeon tile at the player location to be the (a)t symbol
d[ x + a + y ] = a
}
/*
we don't bother generating the dungeon ahead of time, it would require some extra code, we just generate a tile every time one is
asked for, or if we've been asked for this (T)ile before return the stored value. this means that the size of the dungeon is only
limited by JavaScript number precision and the memory on the player's computer
c is a location key x and y separated by @, for example -34@302.
we save a lot of bytes here by nesting ternary operators and combining return and assignment
*/
function T( g ){
//check for the key and if it exists return it
return g in d ? d[ g ] :
//it didn't exist, so set it to something while returning the set value
d[ g ] =
//if these random odds are met then it's (s)tairs
!R( b ) ? s :
//otherwise if these odds, a p(o)tion
!R( b ) ? o :
//otherwise maybe a floor
R( 3 ) ? f :
//if all else fails make it a wall
w
}
//main game (L)oop function is fired on keyup, c is the keyup event arg
function L( c ){
//store the players current location
u = x;
v = y;
//compare mouse click to player location and see if we should change x or y
x += c ? ( i = c.clientX ) < 270 ? -1 : i > 300 ? 1 : 0 : 0;
y += c ? ( j = c.clientY ) < 270 ? -1 : j > 300 ? 1 : 0 : 0;
/*
get the dungeon tile at the location that the player is trying to move to and at the same time store that location in g as we need
to use it again in a moment
*/
t = T( g = x + a + y );
//if it's a monster at the tile the player is trying to move onto then 75% chance of killing it
if( t == m && R( 4 ) ) {
//delete it from the (e)nemy list - one of the more expensive things in the code!
e.splice( e.indexOf( g ), 1 );
//set the dungeon tile where it was to be (f)loor
d[ g ] = f;
/*
preincrement the players kills, every 9 kills gives the player enough experience to go up a character level, when this happens
award them extra health, more on higher levels. cheaper to use a ternary and not assign the return to anything than an if here.
note the 0 which is just a dummy placeholder to make the ternary syntax valid
*/
++k % p ? 0 : h += l
}
//the player is trying to walk onto a tile containing a p(o)tion
if ( t == o ) {
//award the player a random amount of health which is at least 1, more on higher levels
h += R( l ) + 1;
//set t to be a floor tile, otherwise later on the collision detection will stop the player from moving onto this square
t = f
}
/*
if the wall can be pushed (free space on other side) then move it. we make some pretty nasty use of evalution order and
replacing if statements with &&.
*/
t == w && T( g = x - u + x + a + ( y - v + y ) ) == f && ( d[ g ] = w, t = f );
/*
if this tile is (s)tairs then (R)eset and increment the level
otherwise if the tile isn't floor (ie player can't move here) restore x and y from their backed up values so that the player
doesn't move
*/
t == s ? R() : t != f && ( x = u, y = v );
/*
in the dungeon, set the tile that the player was on last to be floor and the one that they're on now to be the (a)t symbol. if they
didn't move then this will set the same tile twice in a row but that doesn't matter
*/
d[ u + a + v ] = f;
d[ x + a + y ] = a;
//ok iterate through the enemies
for( t in e ) {
/*
e[ g ] contains a string that stores the monster location, for example -34@302. split this on the (a)t symbol and get an array
with the first element being x and the second being y, then use double tilde because it's a cheap way to get JavaScript to
treat them as numbers without changing the sign or anything
*/
q = e[ t ].split( a );
i = ~~q[ 0 ];
j = ~~q[ 1 ];
/*
woah it's not as bad as it looks, honestly. we're creating one of those location keys like -34@302 and we're comparing the player
and the enemy locations to find which tile 1 step away from the monster is closest to the player, then putting it in t so it can
get used later, then checking if that location has the player and if so maybe hit them.
*/
if( T( g = ( i < x ? i + 1 : i > x ? i - 1 : i ) + a + ( j < y ? j + 1 : j > y ? j - 1 : j ) ) == a )
//deduct a random amount of health, more on higher levels
h -= R( l ) + 1;
else {
/*
so the player wasn't there. if the monster is trying to move onto a tile that isn't floor they'd be blocked, so pick a random
adjacent tile instead
*/
T( g ) != f ? g = i + R( 3 ) - 1 + a + ( j + R( 3 ) - 1 ) : 0;
//if the tile they're trying to move to is a floor
if( T( g ) == f ) {
//update the enemy with the new location
e[ t ] = g;
//set the dungeon tile at the new location to be a monster
d[ g ] = m;
//set the dungeon tile at the old location to be floor
d[ i + a + j ] = f
}
}
}
//if the player has either died or made it to level 10
if ( h < 1 || l > p ) {
//if their health is 0 they died, otherwise they must have won. give them a message telling them which. we have to check that health
//is < 1 rather than just if it's falsey because more than one monster might have hit them on the turn that killed them, taking their
//health down to a negative number which being non-zero will evaluate to true
alert( h < 1 ? ":(" : ":)" );
//start a new game, resetting things like (M)oves, (l)evel, (k)ills, (h)ealth, current (w)all color and finally (R)eset the level state
M = l = k = 0;
h = p;
w = I;
R()
}
//calculate the viewport size from the radius stored in p
i = p * 2 - 1;
//setting height and width clears the canvas
Z.width = Z.height = I;
//loop over each of the viewport rows
for ( j = 0; j++ < i; )
//loop over each of the viewport columns
for ( g = 0; g++ < i; ) {
//set fillStyle to floor color
X.fillStyle = "#" + f;
//draw floor tile
X.fillRect( u = g * 30, v = j * 30, 30, 30 );
//set fillStyle color to whatever is at the current tile that we're trying to draw
X.fillStyle = "#" + ( t = T( g - p + x + a + ( j - p + y ) ) );
//little bit expensive but nicer looking, walls or stairs take up nearly the whole tile, player, monster and potion are a bit smaller
t == w || t == s ? X.fillRect( u, v, 29, 29 ) : X.fillRect( u + 3, v + 3, 23, 23 )
}
//use the stairs color for the info text
X.fillStyle = "#" + s;
//draw info text, title followed by current dungeon (l)evel, (h)ealth, (k)ills and (M)oves
X.fillText( "Yvs.R L" + l + " H" + h + " X" + k + " M" + M++, 30, X.font = 25 )
}
//setup the mouse handler
Z.onclick = L;
//first time it's run, set inital values, enemies etc.
R();
L()