Skip to content

Instantly share code, notes, and snippets.

@embarq
Created February 11, 2020 13:37
Show Gist options
  • Save embarq/30eadd435d6c7ce53316ad59f311e408 to your computer and use it in GitHub Desktop.
Save embarq/30eadd435d6c7ce53316ad59f311e408 to your computer and use it in GitHub Desktop.
Solution to Codewars kata "Snake Collision" https://www.codewars.com/kata/5ac616ccbc72620a6a000096/javascript
// @ts-check
/**
* @param {Array<{ x: number, y: number }>} body snake body coords
* @param {{ x: number, y: number }} cell snake cell to check with
* @returns {{ x: number, y: number }} coords of the cell where collision occured(if any)
*/
function findCollision(body, cell) {
const { x: x1, y: y1 } = cell;
return body.find(({ x: x0, y: y0 }) => (x0 === x1 && y0 === y1));
}
/**
* @param {{ x: number, y: number }} cell snake body coords
* @param {[number, number]} limits
* @returns {boolean}
*/
function doesCollideWithBorders(cell, limits) {
const [ xMax, yMax ] = limits;
const { x, y } = cell;
return (
x < 0 || y < 0 ||
x >= xMax || y >= yMax
);
}
/**
* @param {string[]} field
* @param {{ x: number, y: number }} coords
* @param {string} char
*/
function printCell(field, coords, char) {
const row = field[coords.y].split('');
row[coords.x] = char;
field[coords.y] = row.join('');
}
/**
* @param {{ x: number, y: number }[]} snake
* @param {string[]} field
*/
function printSnakeToField(snake, field) {
for (let cell of snake) {
printCell(field, cell, 'o')
}
}
/**
* Moves cell in a direction by 1 point
* @param {{ x: number, y: number }} cell
* @param {string} direction
* @returns {{ x: number, y: number }}
*/
function moveCell(cell, direction) {
const { x: x0, y: y0 } = cell;
switch(direction) {
case 'U': return { x: x0, y: y0 - 1 };
case 'D': return { x: x0, y: y0 + 1 };
case 'L': return { x: x0 - 1, y: y0 };
case 'R': return { x: x0 + 1, y: y0 };
}
}
/**
* @param {string[]} field game field representation
* @param {string} moves snake moves
* @param {number} mvs ?
* @returns {[number, number, number] | number} [x, y, N]
* x, y - coords of the cell where a collision ocurred
* N - number of steps before the collision occured
*/
function snakeCollision(field, moves, mvs=1) {
/** @type [number, number] */
const FIELD_LIMITS = [ field[0].length, field.length ];
let snake = [ { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 2, y: 0 } ];
let stepsTracked = 0;
let snakeMoves = moves;
// prepend default direction to get better structure for moves
if (/[A-Z]/.test(moves[0]) === false) {
snakeMoves = `R ${moves}`;
}
const moveSteps = snakeMoves.split(/\s/).reduce((_moves, move, i, allMoves) => {
if (/[A-Z]/.test(move)) {
const steps = Number(allMoves[i + 1]);
_moves.push({ direction: move, steps });
}
return _moves;
}, []);
for (let {direction, steps} of moveSteps) {
let step = 0;
while(step < steps) {
stepsTracked++;
const head = snake[snake.length - 1];
const nextCell = moveCell(head, direction);
const collision = findCollision(snake, nextCell);
if (collision != null) {
const { x, y } = collision;
return [ x, y, stepsTracked ];
}
if (doesCollideWithBorders(nextCell, FIELD_LIMITS)) {
const { x, y } = head;
return [ x, y, stepsTracked ];
}
const fieldCell = field[nextCell.y][nextCell.x];
if (fieldCell == '$') {
// remove food token from the field
printCell(field, nextCell, '-');
} else {
// If next snake cell is not a food cell - remove snake tail
const removedCell = snake.shift();
printCell(field, removedCell, '-');
}
printSnakeToField(snake, field);
field = field;
snake.push(nextCell);
step++;
};
}
return -1;
}
if (require) {
const { runTests } = require('./test');
runTests(snakeCollision);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment