Skip to content

Instantly share code, notes, and snippets.

@seliverstov-maxim
Last active January 30, 2024 21:13
Show Gist options
  • Save seliverstov-maxim/468b4ef3ead770722d335bdb316eac8a to your computer and use it in GitHub Desktop.
Save seliverstov-maxim/468b4ef3ead770722d335bdb316eac8a to your computer and use it in GitHub Desktop.
snake js for begginers in one file
<!DOCTYPE html>
<html>
<body>
</body>
<style>
table { background-color: #b7bea1; }
td { width: 10px; height: 10px; border: solid 1px #707462; }
</style>
<script>
// DISPLAY MODULE BEGIN
const append = function(name, attrs) {
const tmp = document.createElement(name);
if (attrs.id) { tmp.setAttribute('id', attrs.id) }
document.querySelector(attrs.to).appendChild(tmp);
return tmp;
}
const initDisplay = function() {
append('table', {to: 'body'});
append('tbody', {to: 'table'});
for (let y = 0; y < MAX_Y; y++) {
append('tr', {id: `row_${y}`, to: 'tbody'});
for (let x = 0; x < MAX_X; x++) {
append('td', {id: `cell_${x}_${y}`, to: `#row_${y}`});
}
}
}
const markAsFilled = function(tag) { tag.style.backgroundColor = '#000'; }
const markAsEmpty = function(tag) { tag.style.backgroundColor = '#bbc0a9'; }
const printDisplay = function (field) {
for (let y = 0; y < MAX_Y; y++) {
for (let x = 0; x < MAX_X; x++) {
const td = document.querySelector(`#cell_${x}_${y}`)
switch(field[x][y]) {
case FIELD_SNAKE_VALUE:
case FIELD_APPLE_VALUE: markAsFilled(td); break;
case FIELD_EMPTY_VALUE: markAsEmpty(td); break;
}
}
}
return true;
}
// DISPLAY MODULE END
// GAME CORE BEGIN
const pushToSnake = function(snake, x, y, field) {
const tmp = {x: x, y: y}
snake.push(tmp);
field[tmp.x][tmp.y] = FIELD_SNAKE_VALUE
}
const shiftSnake = function(snake, x, y, field) {
snake.shift();
field[x][y] = FIELD_EMPTY_VALUE
}
const nextCoords = function(coords, direction) {
if (direction === LEFT_DIRECTION) { return wrapCycledField({x: coords.x - 1, y: coords.y}) }
if (direction === RIGHT_DIRECTION) { return wrapCycledField({x: coords.x + 1, y: coords.y}) }
if (direction === UP_DIRECTION) { return wrapCycledField({x: coords.x, y: coords.y - 1}) }
if (direction === DOWN_DIRECTION) { return wrapCycledField({x: coords.x, y: coords.y + 1}) }
}
const wrapCycledField = function(coords) {
if (coords.x >= MAX_X) { return {x: 0, y: coords.y} }
if (coords.y >= MAX_Y) { return {x: coords.x, y: 0} }
if (coords.x < 0) { return {x: MAX_X-1, y: coords.y} }
if (coords.y < 0) { return {x: coords.x, y: MAX_Y - 1} }
return coords;
}
const addNewApple = function(field) {
let x, y;
do {
x = Math.round(Math.random() * (MAX_X - 1));
y = Math.round(Math.random() * (MAX_Y - 1));
} while(field[x][y] !== FIELD_EMPTY_VALUE)
return field[x][y] = FIELD_APPLE_VALUE;
}
const eatAppleIfYouCan = function(snake, direction, field) {
const head = snake[snake.length - 1];
const target = nextCoords(head, direction);
if (!field[target.x]) { return }
if(field[target.x][target.y] === FIELD_APPLE_VALUE) {
pushToSnake(snake, target.x, target.y, field);
addNewApple(field);
}
}
const moveSnake = function(snake, direction, field) {
eatAppleIfYouCan(snake, direction, field)
const head = snake[snake.length - 1];
const tail = snake[0];
const nextStepCoords = nextCoords(head, direction);
switch(field[nextStepCoords.x][nextStepCoords.y]) {
case FIELD_APPLE_VALUE:
pushToSnake(snake, nextStepCoords.x, nextStepCoords.y, field);
shiftSnake(snake, tail.x, tail.y, field);
break;
case FIELD_EMPTY_VALUE:
pushToSnake(snake, nextStepCoords.x, nextStepCoords.y, field);
shiftSnake(snake, tail.x, tail.y, field);
break;
case FIELD_SNAKE_VALUE:
return GAME_OVER;
}
return GAME_NEXT;
}
const gameTick = function(field, snake, direction) { // ENTRANCE TO LOGIC, USED IN WRAPPER
const status = moveSnake(snake, direction, field)
if (status !== GAME_NEXT) { return status }
printDisplay(field);
}
// GAME CORE END
// GAME WRAPPER AND CONTROLLER BEGIN
const canSwitchDirection = function(futureDirection) {
if (!futureDirection) { return }
if ([DOWN_DIRECTION, UP_DIRECTION].indexOf(direction) !== -1 && [DOWN_DIRECTION, UP_DIRECTION].indexOf(futureDirection) !== -1) { return }
if ([LEFT_DIRECTION, RIGHT_DIRECTION].indexOf(direction) !== -1 && [LEFT_DIRECTION, RIGHT_DIRECTION].indexOf(futureDirection) !== -1) { return }
return true
}
const gameWrapper = function() { // TO MANAGE LEVEL UP, GAME OVER
if (canSwitchDirection(futureDirection)) { direction = futureDirection }
const status = gameTick(field, snake, direction);
if (status === GAME_OVER) {
clearInterval(intervalId);
alert('game is over');
window.location.reload();
return;
}
if (status === GAME_LEVEL_UP) {
tickTime -= SPEED_UP_FACTOR;
clearInterval(intervalId);
intervalId = setInterval(gemWrapper, tickTime);
}
}
const matchDirection = function(e) {
switch(e.key) {
case 'ArrowUp': return UP_DIRECTION;
case 'ArrowDown': return DOWN_DIRECTION;
case 'ArrowLeft': return LEFT_DIRECTION;
case 'ArrowRight': return RIGHT_DIRECTION;
}
}
document.addEventListener('keyup', function(e) {
e.preventDefault();
tmp = matchDirection(e);
if(tmp) { futureDirection = tmp }
})
// GAME WRAPPER AND CONTROLLER END
const MAX_Y = 10;
const MAX_X = 10;
const FIELD_EMPTY_VALUE = 0;
const FIELD_SNAKE_VALUE = 1;
const FIELD_APPLE_VALUE = 2;
const LEFT_DIRECTION = 'left';
const RIGHT_DIRECTION = 'right';
const UP_DIRECTION = 'up';
const DOWN_DIRECTION = 'down';
const GAME_NEXT = 0;
const GAME_OVER = 1;
const GAME_LEVEL_UP = 2;
const SPEED_UP_FACTOR = 75;
const snake = Array();
const field = Array(MAX_X).fill().map(() => Array(MAX_Y).fill(0));
const startAt = {x: 0, y: 1}
let direction = RIGHT_DIRECTION; // can be left, right, up, down
let futureDirection = direction;
let tickTime = 500; // in milisec.
pushToSnake(snake, startAt.x, startAt.y, field);
addNewApple(field);
initDisplay(field);
printDisplay(field);
let intervalId = setInterval(gameWrapper, tickTime);
</script>
</html>
@seliverstov-maxim
Copy link
Author

Snake game based on table markup graphic and pure js, primary old syntax - very simple to start.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment