let l,e,t=15,f=40,o=30,i=([l,e])=>([t,c])=>l==t&&e==c,a=(l,[e,t],c)=>{l.fillStyle=c,l.fillRect(e,t,1,1)},n=[[f/2,o/2]],s=0,h=[],r=()=>{l=[f,o].map(l=>Math.floor(Math.random()*l)),n.some(i(l))&&r()},d=()=>{if(e=h.shift()||e){const t=n[n.length-1].map((l,t)=>l+e[t]),[a,h]=t;if(n.some(i(t))||a<0||h<0||a>=f||h>=o)return c.font="3px impact",c.fillStyle="#f00",c.textAlign="center",void c.fillText("GAME OVER",20,15);n.push(t),i(t)(l)&&(r(),s++),n=n.slice(3*-s-9)}c.fillStyle="#000",c.fillRect(0,0,f,o),a(c,l,"#09f"),n.forEach(l=>a(c,l,"#fff")),c.font="1px Verdana",c.fillStyle="#fff",c.fillText(s,1,1.5),setTimeout(d,100*.999**(s+1))};c.scale(t,t),c.shadowColor="#000",c.shadowBlur=15,r(),d(),onkeydown=(({keyCode:l})=>{const t=37==l?-1:39==l?1:0,c=38==l?-1:40==l?1:0,[f,o]=h[0]||e||[];(t&&!t!=!f||c&&!c!=!o)&&h.push([t,c])});
// compressed with: https://skalman.github.io/UglifyJS-online/
type Point = [number, number];
type Direction = [number, number];
declare const c: CanvasRenderingContext2D;
let
scale = 15,
w = 40,
h = 30,
pointEq = ([ax, ay]: Point) => ([bx, by]: Point) => ax == bx && ay == by,
drawPoint = (ctx: CanvasRenderingContext2D, [x, y]: Point, color: string) => {
ctx.fillStyle = color;
ctx.fillRect(x, y, 1, 1);
},
// Game state
food: Point,
snakePoints: Point[] = [[w / 2, h / 2 ]],
eatenCount = 0,
snakeDirQueue = [] as Direction[], // queue directions
snakeDir: Direction | undefined,
createFood = () => {
food = [w, h].map(size => Math.floor(Math.random() * size)) as Point;
// if food is under the snake, re-create it
snakePoints.some(pointEq(food)) && createFood();
},
loop = () => {
snakeDir = snakeDirQueue.shift() || snakeDir;
if (snakeDir) { // update game state
const
headPos = snakePoints[snakePoints.length - 1]
.map((value, axis) => value + snakeDir![axis]) as Point,
[x, y] = headPos;
// check if lost
if (
snakePoints.some(pointEq(headPos)) ||
x < 0 ||
y < 0 ||
x >= w ||
y >= h
) {
c.font = '3px impact';
c.fillStyle = '#f00';
c.textAlign = 'center';
c.fillText('GAME OVER', 20, 15);
return; // Exit game loop
}
snakePoints.push(headPos);
// food
if (pointEq(headPos)(food)) {
createFood();
eatenCount++;
}
snakePoints = snakePoints.slice(-eatenCount * 3 - 9);
}
// draw
c.fillStyle = '#000';
c.fillRect(0, 0, w, h);
drawPoint(c, food, '#09f');
snakePoints.forEach(p => drawPoint(c, p, `#fff`));
c.font = '1px Verdana';
c.fillStyle = '#fff';
c.fillText(eatenCount as any, 1, 1.5);
setTimeout(loop, 100 * (0.999 ** (eatenCount + 1)));
};
// run game
c.scale(scale, scale);
c.shadowColor = '#000';
c.shadowBlur = 15;
createFood();
loop();
// user inputs
onkeydown = ({ keyCode: k }) => {
const
x = k == 37 ? -1 : k == 39 ? 1 : 0,
y = k == 38 ? -1 : k == 40 ? 1 : 0,
[nextX, nextY] = snakeDirQueue[0] || snakeDir || [] as any;
// Can't stop and Can't move on itself
(x && !x != !nextX || y && !y != !nextY) && snakeDirQueue.push([x, y]);
};