Last active
June 1, 2019 23:47
-
-
Save kjperkin/08157a98cd297c4fcd403c5bf7d33f21 to your computer and use it in GitHub Desktop.
Snake
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
<canvas id="canvas" width="500" height="500"></canvas> |
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
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 | |
); |
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
canvas { | |
border: 1px solid black; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment