const GAME_STATE = (function() {
let canvas = document.getElementById('gameboard');
let scoreBoard = document.querySelector('.scoreboard');
let highscoreLabel = document.querySelector('.highscore');
canvas.width = 800;
canvas.height = 600;
let ctx = canvas.getContext('2d'),
gameBoardHeight = canvas.height,
gameBoardWidth = canvas.width,
player,
snake,
snakeHead,
apple;
function init() {
definePlayerProperties();
defineSnakeProperties();
defineAppleProperties();
update();
displayGameboard();
bindEvents();
}
function dispose() {
unbindEvents();
canvas.style.display = 'none';
GAME.init();
}
function displayGameboard() {
canvas.style.display = 'block';
}
function definePlayerProperties() {
player = {
points: 0,
name: '',
isSaved: false
};
}
function defineSnakeProperties() {
snake = {
body: [{
x: 60,
y: 60,
color:'red',
lastPosX: null,
lastPosY: null
}],
direction: 'right',
blockSize: 20
}
snakeHead = snake.body[0];
for( let i = 0; i < 100; i++ ) {
snake.body.push({ color:'blue' });
}
}
function defineAppleProperties() {
apple = {
x: Math.floor( Math.random() * gameBoardWidth ),
y: Math.floor( Math.random() * gameBoardHeight ),
color: 'red',
isReachable: false,
isOutsideSnakeBody: function() {
snake.body.forEach( (bodySegment) => {
if( ( bodySegment.x === this.x ) && ( bodySegment.y === this.y ) ) {
return false;
}
});
return true;
},
setValidPosition: function() {
while( this.isReachable === false ){
if( (( this.x % snake.blockSize === 0 ) && ( this.y % snake.blockSize === 0 )) && this.isOutsideSnakeBody() ) {
this.isReachable = true;
}
else {
this.x = Math.floor( Math.random() * gameBoardWidth );
this.y = Math.floor( Math.random() * gameBoardHeight );
}
}
}
};
apple.setValidPosition();
}
function handleInput( event ) {
if(snake.direction === 'right' && event.keyCode === 37) {
return;
}
else if(snake.direction === 'left' && event.keyCode === 39) {
return;
}
else if(snake.direction === 'up' && event.keyCode === 40) {
return;
}
else if(snake.direction === 'down' && event.keyCode === 38) {
return;
}
switch ( event.keyCode ) {
case 38: /* Up arrow was pressed */
snake.direction = 'up';
break;
case 40: /* Down arrow was pressed */
snake.direction = 'down';
break;
case 37: /* Left arrow was pressed */
snake.direction = 'left';
break;
case 39: /* Right arrow was pressed */
snake.direction = 'right';
break;
}
}
function update() {
calculateSnakePosition();
ctx.clearRect(0, 0, canvas.width, canvas.height);
snake.body.forEach( (s) => {
draw(s);
});
draw(apple);
drawGrid();
displayScore();
move();
checkForCollisions();
}
function draw( o ) {
if( o.x != undefined || o.x != null ) {
ctx.beginPath();
ctx.fillStyle = o.color;
ctx.fillRect( o.x, o.y, snake.blockSize, snake.blockSize );
ctx.closePath();
}
}
function drawGrid() {
for( let y = snake.blockSize; y < 600; y += snake.blockSize ) {
ctx.beginPath();
ctx.moveTo( 0, y );
ctx.lineTo( 800, y );
ctx.stroke();
}
for( let x = snake.blockSize; x < 800; x += snake.blockSize ) {
ctx.beginPath();
ctx.moveTo( x, 0 );
ctx.lineTo( x, 600 );
ctx.stroke();
}
}
function move() {
switch ( snake.direction ) {
case 'left':
snakeHead.x -= snake.blockSize;
break;
case 'right':
snakeHead.x += snake.blockSize;
break;
case 'down':
snakeHead.y += snake.blockSize;
break;
case 'up':
snakeHead.y -= snake.blockSize;
break;
}
}
function displayScore() {
scoreBoard.innerHTML = 'Points: ' + player.points;
if( localStorage.getItem('highscores') ) {
let storageHighscoresItemsArr = [ ];
let storageHighscoresItems = JSON.parse(localStorage.getItem('highscores'));
storageHighscoresItems.forEach( ( item ) => {
storageHighscoresItemsArr.push( item.score );
})
let highscore = Math.max( ...storageHighscoresItemsArr );
highscoreLabel.innerHTML = 'Highscore: ' + highscore;
}
}
function calculateSnakePosition() {
for(let i = 1; i < snake.body.length; i++) {
snake.body[i].x = snake.body[i - 1].lastPosX;
snake.body[i].y = snake.body[i - 1].lastPosY;
snake.body[i - 1].lastPosX = snake.body[i - 1].x;
snake.body[i - 1].lastPosY = snake.body[i - 1].y;
}
}
function checkForCollisions() {
if( snakeHead.x >= gameBoardWidth ) {
snakeHead.x = 0;
}
else if( snakeHead.x <= -10 ) {
snakeHead.x = gameBoardWidth;
}
else if( snakeHead.y <= -10 ) {
snakeHead.y = gameBoardHeight;
}
else if( snakeHead.y >= gameBoardHeight ) {
snakeHead.y = 0;
}
if(( snakeHead.x === apple.x ) && ( snakeHead.y === apple.y )) {
player.points += 10;
defineAppleProperties();
snake.body.push( {color:'blue' });
}
for(let i = 1; i < snake.body.length; i++) {
if((snakeHead.x === snake.body[i].x) && (snakeHead.y === snake.body[i].y)) {
GAME.setState('GAMEOVER');
dispose();
}
}
}
function bindEvents() {
addEventListener( 'keydown', e => handleInput(e), true );
}
function unbindEvents() {
removeEventListener( 'keydown', handleInput, true );
}
function getPlayer() {
return player;
}
return {
init: init,
update: update,
getPlayer: getPlayer
};
})();
const GAMEOVER_STATE = (function() {
let restartBtn = document.getElementById('restart'),
gameOverScreen = document.querySelector('.gameover-screen'),
finalScoreLabel = document.querySelector('.final-score'),
playerNameInput = document.querySelector('.player-name'),
highscoreBoardBtn = document.getElementById('highscores-btn');
function init() {
bindEvents();
gameOverScreen.style.display = 'block';
finalScoreLabel.innerHTML = 'Your score: ' + GAME_STATE.getPlayer().points;
}
function dispose() {
gameOverScreen.style.display = 'none';
savePlayerToStorage()
unbindEvents();
GAME.init();
}
let savePlayerToStorage = function() {
if(GAME_STATE.getPlayer().isSaved === false) {
GAME_STATE.getPlayer().isSaved = true;
GAME_STATE.getPlayer().name = playerNameInput.value;
let highscores = [];
if(localStorage.getItem('highscores')) {
highscores = highscores.concat(JSON.parse(localStorage.getItem('highscores')));
}
highscores.push({name: GAME_STATE.getPlayer().name, score: GAME_STATE.getPlayer().points});
localStorage.setItem('highscores', JSON.stringify(highscores));
}
};
function bindEvents() {
restartBtn.addEventListener( 'click', restartGame, false );
highscoreBoardBtn.addEventListener('click', showHighscores, false );
}
function unbindEvents() {
restartBtn.removeEventListener( 'click', restartGame, false );
highscoreBoardBtn.removeEventListener('click', showHighscores, false );
}
function restartGame() {
GAME.setState('GAME');
dispose();
}
function showHighscores() {
GAME.setState('HIGHSCORES');
dispose();
}
return {
init: init
}
})()
let HIGHSCORES_STATE = (function() {
let highscoresBoard = document.querySelector('.highscores-board'),
highscoresList = document.querySelector('.highscores-list'),
highscoresReturnBtn = document.getElementById('return-from-highscores');
function init() {
highscoresBoard.style.display = 'flex';
highscoresList.innerHTML = '';
bindEvents();
showHighscoresList();
}
function dispose() {
highscoresBoard.style.display = 'none';
unbindEvents();
GAME.init();
}
function showHighscoresList() {
let storageHighscoresItems = JSON.parse(localStorage.getItem('highscores'));
let storageHighscoresItemsArr = [];
storageHighscoresItems.forEach(( item ) => {
storageHighscoresItemsArr.push( item );
})
storageHighscoresItemsArr.sort(function (a, b) {
if (a.score > b.score) {
return 1;
}
if (a.score < b.score) {
return -1;
}
return 0;
});
storageHighscoresItemsArr.reverse();
for(let i = 0; i < 10; i++) {
if(storageHighscoresItemsArr[i]) {
let li = document.createElement('li');
highscoresList.appendChild(li);
li.innerHTML = (storageHighscoresItemsArr[i].name || 'Noname') +
' - ' + storageHighscoresItemsArr[i].score;
}
}
}
function bindEvents() {
highscoresReturnBtn.addEventListener( 'click', goBack, false );
}
function unbindEvents() {
highscoresReturnBtn.removeEventListener( 'click', goBack, false );
}
function goBack() {
GAME.setState('GAMEOVER');
dispose();
}
return {
init: init
}
})();
let GAME = (function() {
let state = 'GAME';
function init() {
if( state === 'GAME' ) {
GAME_STATE.init();
}
else if( state === 'GAMEOVER') {
GAMEOVER_STATE.init();
}
else if( state === 'HIGHSCORES' ) {
HIGHSCORES_STATE.init();
}
}
function setState( st ) {
state = st;
}
function getState() {
return state;
}
let loop = function() {
if( state === 'GAME' ) {
GAME_STATE.update();
}
setTimeout( loop, 50 );
}
return {
getState: getState,
setState: setState,
loop: loop,
init: init
};
})();
GAME.init();
GAME.loop();
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
height: 100%;
}
.game-wrap {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.board {
background: white;
border: 2px black solid;
}
.label {
width: 800px;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.gameover-screen {
height: 600px;
width: 800px;
background: #FFFACD;
display: none;
}
.gameover-screen ul {
list-style: none;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.gameover-screen ul li {
margin-bottom: 50px;
}
.final-score {
font-size: 32px;
}
.highscores-board {
display: flex;
flex-direction: column;
align-items: center;
height: 600px;
width: 800px;
color: white;
background: #1A1406;
display: none;
}
.highscores-board ol {
margin-top: 35px;
}
.highscores-board ol li {
font-size: 36px;
margin-bottom: 10px;
}
.btn {
display: inline-block;
margin: 0 auto;
text-align: center;
width: 100%;
height: 100%;
border: 0;
-moz-border-radius:0px;
-webkit-border-radius:0px;
border-radius:2px;
display:inline-block;
cursor:pointer;
color:#ffffff;
font-size:17px;
text-decoration:none;
}
.reset-btn {
background-color:#53b56d;
}
.highscores-btn {
background-color:#2A6BD4;
}
.return-btn {
width: 400px;
height: 100px;
background-color: #D9C750;
}
<!DOCTYPE html>
<html>
<head lang="en-Us">
<meta charset="utf-8">
<title>Snake</title>
<link rel="stylesheet" href="css/main.css">
</head>
<body>
</div>
<div class="game-wrap">
<div class="label">
<p class="scoreboard">Points: 0</p>
<p class="highscore">Highscore: 0</p>
</div>
<div class="gameover-screen">
<ul>
<li><p class="final-score">Your score: </p></li>
<li><label>Your name: </label>
<input class="player-name" type="text" maxlength="6"></li>
<li><button class="btn reset-btn" id="restart">Restart</button></li>
<li><button class="btn highscores-btn" id="highscores-btn">Highscores</button></li>
</ul>
</div>
<div class="highscores-board">
<ol class="highscores-list"></ol>
<button class="btn return-btn" id="return-from-highscores">Back</button>
</div>
<canvas id="gameboard" class="board"></canvas>
</div>
<script src="js/game.js"></script>
</body>
</html>
I've decided to take your advice and improve the style of my code.I would like to know what do you think of it now,and maybe what's more needs to be changed.Though I'm not sure if I refactored it correctly because now my JS script has 100 more lines of code.
I've divided my code into 4 modules:
1.GAME_STATE - responible for everything while player is not dead.Updates data,draws on the canvas
2.GAMEOVER_STATE - updates localStorage and handles user input on the Game Over screen
3.HIGHSCORES_STATE - updates the Highscores List
4.GAME - initialazes and updates a modules,depending on it's 'state' varaible