Skip to content

Instantly share code, notes, and snippets.

@kjperkin
Last active June 1, 2019 23:47
Show Gist options
  • Save kjperkin/08157a98cd297c4fcd403c5bf7d33f21 to your computer and use it in GitHub Desktop.
Save kjperkin/08157a98cd297c4fcd403c5bf7d33f21 to your computer and use it in GitHub Desktop.
Snake
<canvas id="canvas" width="500" height="500"></canvas>
const BLACK_COLOR = 'black';
const FOOD_R = 0;
const FOOD_G = 128;
const FOOD_B = 0;
const FOOD_A = 255;
const FOOD_COLOR = `rgba(${FOOD_R}, ${FOOD_G}, ${FOOD_B}, ${FOOD_A})`;
const NORTH = 0;
const EAST = 1;
const SOUTH = 2;
const WEST = 3;
const SEGMENT_SIZE_PIXELS = 5;
const PEN_WIDTH_SEGMENTS = 50;
const PEN_HEIGHT_SEGMENTS = 50;
const KEYMAP = {
'ArrowUp': setOrientation(NORTH),
'ArrowRight': setOrientation(EAST),
'ArrowDown': setOrientation(SOUTH),
'ArrowLeft': setOrientation(WEST)
};
let [headX, headY] = [PEN_WIDTH_SEGMENTS/2, PEN_HEIGHT_SEGMENTS/2];
let [tailX, tailY] = [headX, headY];
let nextTailX, nextTailY, savedTailX, savedTailY, savedTailOrientation;
let length = 1;
let orientation = randomOrientation();
let millisPerTick = 250;
const CANVAS = document.getElementById('canvas');
CANVAS.width = PEN_WIDTH_SEGMENTS*SEGMENT_SIZE_PIXELS;
CANVAS.height = PEN_HEIGHT_SEGMENTS*SEGMENT_SIZE_PIXELS;
CANVAS.style.width = PEN_WIDTH_SEGMENTS*1.5*SEGMENT_SIZE_PIXELS + 'px';
CANVAS.style.height = PEN_HEIGHT_SEGMENTS*1.5*SEGMENT_SIZE_PIXELS + 'px';
const CONTEXT = CANVAS.getContext('2d');
function fromSegments(...args) {
return args.map(v => v*SEGMENT_SIZE_PIXELS)
}
function toSegments(...args) {
return args.map(v => v/SEGMENT_SIZE_PIXELS)
}
function drawSegment(x, y, orientation, ctx) {
ctx.fillStyle = `rgb(${orientation}, 0, 0)`;
ctx.fillRect(...fromSegments(x, y, 1, 1));
}
function randomOrientation() {
return Math.floor(Math.random()*4);
}
function randomX() {
return Math.floor(Math.random()*PEN_WIDTH_SEGMENTS);
}
function randomY() {
return Math.floor(Math.random()*PEN_HEIGHT_SEGMENTS);
}
function drawFood(ctx) {
let x = randomX(), y = randomY();
while (!isEmpty(getPixel(x, y, ctx))) {
x = randomX(), y = randomY();
}
ctx.fillStyle = FOOD_COLOR;
ctx.fillRect(...fromSegments(x, y, 1, 1));
}
function next(x, y, orientation) {
switch (orientation) {
case NORTH:
return [x, y-1];
case EAST:
return [x+1, y];
case SOUTH:
return [x, y+1];
case WEST:
return [x-1, y];
}
}
function getPixel(x, y, ctx) {
return ctx.getImageData(x*SEGMENT_SIZE_PIXELS, y*SEGMENT_SIZE_PIXELS, 1, 1).data;
}
function isFood(pixel) {
return pixel[1] === FOOD_G;
}
function isEmpty(pixel) {
const alpha = pixel[3];
return alpha === 0 || alpha === 1;
}
function isInBounds(x, y) {
return 0 <= x && x < PEN_WIDTH_SEGMENTS && 0 <= y && y < PEN_HEIGHT_SEGMENTS;
}
function getOrientation(x, y, ctx) {
return ctx.getImageData(x*SEGMENT_SIZE_PIXELS, y*SEGMENT_SIZE_PIXELS, 1, 1).data[0];
}
function canMove(x, y, o, ctx) {
const [nx, ny] = next(x, y, o);
return canMoveTo(nx, ny, ctx);
}
function canMoveTo(nx, ny, ctx) {
const pixel = getPixel(nx, ny, ctx);
return (isInBounds(nx, ny) && (isEmpty(pixel) || isFood(pixel)));
}
function erase(x, y, ctx) {
ctx.clearRect(...fromSegments(x, y, 1, 1));
}
function setOrientation(newOrientation) {
return () => {
/*if (orientation === newOrientation) {
snake();
} else */if (length > 1 && (orientation + 2) % 4 === newOrientation) {
// Prevent going back the way we came if we are
// longer than one segment
return;
} else {
orientation = newOrientation;
drawSegment(headX, headY, newOrientation, CONTEXT);
snake();
}
};
};
function handleKey(event) {
const handler = KEYMAP[event.key];
if (handler) {
handler();
}
}
window.addEventListener('keydown', handleKey, true);
drawSegment(headX, headY, orientation, CONTEXT);
drawFood(CONTEXT);
function snake() {
[savedTailX, savedTailY] = [tailX, tailY];
savedTailOrientation = getOrientation(tailX, tailY, CONTEXT);
erase(tailX, tailY, CONTEXT);
[tailX, tailY] = next(tailX, tailY, savedTailOrientation);
if (canMove(headX, headY, orientation, CONTEXT)) {
[headX, headY] = next(headX, headY, orientation);
if (isFood(getPixel(headX, headY, CONTEXT))) {
length++;
drawSegment(savedTailX, savedTailY, savedTailOrientation, CONTEXT);
[tailX, tailY] = [savedTailX, savedTailY];
drawFood(CONTEXT);
clearInterval(interval);
millisPerTick = Math.max(75, millisPerTick-2);
interval = setInterval(snake, millisPerTick);
}
drawSegment(headX, headY, orientation, CONTEXT);
} else {
drawSegment(savedTailX, savedTailY, savedTailOrientation, CONTEXT);
clearInterval(interval);
window.removeEventListener('keydown', handleKey, true)
}
}
let interval = setInterval(
snake,
millisPerTick
);
canvas {
border: 1px solid black;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment