Last active
June 2, 2023 02:26
-
-
Save shakkurcwb/d30610f490b6b90bb8a5f7f9f55fa7c9 to your computer and use it in GitHub Desktop.
Definitely not a Snake Game (JavaScript)
This file contains 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
body { | |
text-align: center; | |
} | |
#game-screen { | |
border: 1px solid #000000; | |
} | |
.title { | |
font-size: 20px; | |
font-weight: bold; | |
} | |
.sub-title { | |
font-size: 16px; | |
font-weight: bold; | |
} |
This file contains 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
<!DOCTYPE html> | |
<html> | |
<head> | |
<link rel="stylesheet" type="text/css" href="game.css"> | |
</head> | |
<body> | |
<div> | |
<p class="title">Current Score: <strong id="score">0</strong></p> | |
<p class="sub-title">Max Score: <strong id="max-score">0</strong></p> | |
</div> | |
<canvas id="game-screen" width="400" height="300"></canvas> | |
<script src="game.js"></script> | |
</body> | |
</html> |
This file contains 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
var ctx = document.getElementById('game-screen').getContext('2d'); | |
var minWidth = 0; | |
var minHeight = 0; | |
var maxWidth = 400; | |
var maxHeight = 300; | |
var boxWidth = 20; | |
var boxHeight = 20; | |
var config = { | |
defaultFoodSpawn: false, | |
defaultPlayerSpawn: false, | |
selfCollisionCausesGameOver: false, | |
outOfBoundsCausesGameOver: false, | |
}; | |
var styles = { | |
grid: { | |
primaryColor: 'red', | |
secondaryColor: 'black', | |
}, | |
player: { | |
headColor: 'purple', | |
bodyColor: 'blue', | |
}, | |
food: { | |
color: 'green', | |
}, | |
}; | |
// Score | |
var score = 0; | |
var scoreElement = document.getElementById('score'); | |
var scoreConstants = { | |
movement: 10, | |
food: 100, | |
selfCollision: -100, | |
outOfBounds: -100, | |
}; | |
function updateScoreElement() { | |
scoreElement.innerHTML = score; | |
} | |
var maxScore = 0; | |
var maxScoreElement = document.getElementById('max-score'); | |
function updateMaxScoreElement() { | |
maxScoreElement.innerHTML = maxScore; | |
} | |
if (localStorage.getItem('maxScore')) { | |
maxScore = parseInt(localStorage.getItem('maxScore')); | |
} | |
function persistMaxScore() { | |
localStorage.setItem('maxScore', maxScore); | |
} | |
// Player Position | |
var movementDirection = "right"; | |
var safePlayerPosition = { | |
x: 0, | |
y: 0, | |
}; | |
var playerPosition = safePlayerPosition; | |
if (!config.defaultPlayerSpawn) { | |
playerPosition = randomPlayerPosition(); | |
} | |
var playerPath = []; | |
playerPath.push(playerPosition); | |
// Food Position | |
var safeFoodPosition = { | |
x: maxWidth - boxWidth, | |
y: maxHeight - boxHeight, | |
}; | |
var foodPosition = safeFoodPosition; | |
if (!config.defaultFoodSpawn) { | |
foodPosition = randomFoodPosition(); | |
} | |
function initGame() { | |
console.log('initGame'); | |
drawGrid(); | |
drawPlayer(); | |
drawFood(); | |
renderScores(); | |
document.addEventListener("keydown", movePlayer); | |
} | |
function drawGrid() { | |
console.log('drawGrid'); | |
count = 0; | |
for (var x = minWidth; x < maxWidth; x += boxWidth) { | |
for (var y = minHeight; y < maxHeight; y += boxHeight) { | |
if (count % 2 == 0) { | |
ctx.fillStyle = styles.grid.primaryColor; | |
} else { | |
ctx.fillStyle = styles.grid.secondaryColor; | |
} | |
ctx.fillRect(x, y, boxWidth, boxHeight); | |
count += 1; | |
} | |
} | |
} | |
function drawPlayer() { | |
console.log('drawPlayer'); | |
playerPath.forEach(function (body, idx) { | |
if (idx === 0) { | |
ctx.fillStyle = styles.player.headColor; | |
console.log('@head at ' + body.x + ', ' + body.y); | |
} else { | |
ctx.fillStyle = styles.player.bodyColor; | |
} | |
ctx.fillRect(body.x, body.y, boxWidth, boxHeight); | |
}); | |
} | |
function drawFood() { | |
console.log('drawFood'); | |
ctx.fillStyle = styles.food.color; | |
ctx.fillRect(foodPosition.x, foodPosition.y, boxWidth, boxHeight); | |
} | |
function randomWidth() { | |
var possibleWidths = []; | |
for (var x = minWidth; x < maxWidth; x += boxWidth) { | |
possibleWidths.push(x); | |
} | |
return possibleWidths[Math.floor(Math.random() * possibleWidths.length)]; | |
} | |
function randomHeight() { | |
var possibleHeights = []; | |
for (var x = minHeight; x < maxHeight; x += boxWidth) { | |
possibleHeights.push(x); | |
} | |
return possibleHeights[Math.floor(Math.random() * possibleHeights.length)]; | |
} | |
function randomPosition() { | |
var x = randomWidth(); | |
var y = randomHeight(); | |
return { x, y }; | |
} | |
function randomPlayerPosition() { | |
console.log('randomPlayerPosition'); | |
var position = randomPosition(); | |
console.log('@player at: ' + position.x + ', ' + position.y); | |
return position; | |
} | |
function randomFoodPosition() { | |
console.log('randomFoodPosition'); | |
var position = randomPosition(); | |
// confirm x, y does not conflict with player's path | |
playerPath.forEach(function (body) { | |
if (position.x === body.x && position.y === body.y) { | |
console.log('@food conflict: ' + position.x + ', ' + position.y); | |
position = randomPosition(); | |
} | |
}); | |
console.log('@food at: ' + position.x + ', ' + position.y); | |
// @todo: still possible that we have a conflicting position | |
return { x: position.x, y: position.y }; | |
} | |
function isValidMovement(event) { | |
console.log('isValidMovement'); | |
var validKeyCodes = [ | |
37, 38, 39, 40 | |
]; | |
return validKeyCodes.includes(event.keyCode); | |
} | |
function movePlayer(event) { | |
console.log('movePlayer'); | |
if (!isValidMovement(event)) { | |
return; | |
} | |
console.log('@from: ' + movementDirection); | |
guessMovementDirection(event); | |
console.log('@to: ' + movementDirection); | |
var newHead = guessPlayerMovement(); | |
var stopGame = false; | |
if (!stopGame) { | |
stopGame = handleOutOfBoundsCollision(newHead); | |
console.log('@stopGame: handleOutOfBoundsCollision', stopGame); | |
} | |
if (!stopGame) { | |
stopGame = handleSelfCollision(newHead); | |
console.log('@stopGame: handleSelfCollision', stopGame); | |
} | |
if (!stopGame) { | |
stopGame = handleFoodCollision(newHead); | |
console.log('@stopGame: handleFoodCollision', stopGame); | |
} | |
if (!stopGame) { | |
// in case nothing interrupt the movement, we can move the player | |
playerPath.unshift(newHead); | |
// bonus for movement | |
score += scoreConstants.movement; | |
// re-draw player | |
drawPlayer(); | |
// re-draw food | |
drawFood(); | |
// display scores on UI | |
renderScores(); | |
} | |
if (stopGame) { | |
resetGame(); | |
} | |
} | |
function guessMovementDirection(event) { | |
console.log('guessMovementDirection'); | |
// some logic to avoid moving backwards | |
if (event.keyCode === 37 && movementDirection !== "right") { | |
movementDirection = "left"; | |
} else if (event.keyCode === 38 && movementDirection !== "down") { | |
movementDirection = "up"; | |
} else if (event.keyCode === 39 && movementDirection !== "left") { | |
movementDirection = "right"; | |
} else if (event.keyCode === 40 && movementDirection !== "up") { | |
movementDirection = "down"; | |
} | |
return movementDirection; | |
} | |
function guessPlayerMovement() { | |
// initialize as current head | |
var newHead = { | |
x: playerPath[0].x, | |
y: playerPath[0].y, | |
} | |
// then move player's head according to direction | |
if (movementDirection === "left") { | |
newHead.x -= boxWidth; | |
} | |
if (movementDirection === "up") { | |
newHead.y -= boxHeight; | |
} | |
if (movementDirection === "right") { | |
newHead.x += boxWidth; | |
} | |
if (movementDirection === "down") { | |
newHead.y += boxHeight; | |
} | |
return newHead; | |
} | |
function isFoodCollision(position) { | |
if (position.x === foodPosition.x && position.y === foodPosition.y) { | |
return true; | |
} | |
return false; | |
} | |
function handleFoodCollision(position) { | |
console.log('handleFoodCollision'); | |
if (isFoodCollision(position)) { | |
score += scoreConstants.food; | |
foodPosition = randomFoodPosition(); | |
} | |
return false; | |
} | |
function handleOutOfBoundsCollision(position) { | |
console.log('handleOutOfBoundsCollision'); | |
// make a copy of position (new player's head) | |
var originalPosition = Object.assign({}, position); | |
// check if player is out of bounds then adjust position accordingly | |
if (position.x < minWidth) { | |
position.x = maxWidth - boxWidth; | |
} | |
if (position.x > maxWidth - boxWidth) { | |
position.x = minWidth; | |
} | |
if (position.y < minHeight) { | |
position.y = maxHeight - boxHeight; | |
} | |
if (position.y > maxHeight - boxHeight) { | |
position.y = minHeight; | |
} | |
// if position is different than original, then player is out of bounds | |
if (originalPosition.x !== position.x || originalPosition.y !== position.y) { | |
if (config.outOfBoundsCausesGameOver) { | |
alert('out of bounds - game over!'); | |
return true; | |
} | |
if (!config.outOfBoundsCausesGameOver) { | |
// punish player for going out of bounds | |
score += scoreConstants.outOfBounds; | |
} | |
} | |
return false; | |
} | |
function handleSelfCollision(position) { | |
console.log('handleSelfCollision'); | |
var stopGame = false; | |
// check if player is colliding with itself | |
playerPath.forEach(function (body, idx) { | |
if (position.x === body.x && position.y === body.y) { | |
if (config.selfCollisionCausesGameOver) { | |
alert('self collision - game over!'); | |
stopGame = true; | |
} | |
if (!config.selfCollisionCausesGameOver) { | |
// punish player for self collision | |
score += scoreConstants.selfCollision; | |
} | |
} | |
}); | |
return stopGame; | |
} | |
function resetGame() { | |
console.log('resetGame'); | |
movementDirection = "right"; | |
playerPosition = safePlayerPosition; | |
if (!config.defaultPlayerSpawn) { | |
playerPosition = randomPlayerPosition(); | |
} | |
playerPath = []; | |
playerPath.push(playerPosition); | |
foodPosition = safeFoodPosition; | |
if (!config.defaultFoodSpawn) { | |
foodPosition = randomFoodPosition(); | |
} | |
drawGrid(); | |
drawPlayer(); | |
drawFood(); | |
score = 0; | |
renderScores(); | |
} | |
function renderScores() { | |
console.log('renderScores'); | |
updateScoreElement(); | |
if (score > maxScore) { | |
maxScore = score; | |
persistMaxScore(); | |
} | |
updateMaxScoreElement(); | |
} | |
initGame(); |
Author
shakkurcwb
commented
May 16, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment