From d23a44747db947fb0ca1c6d57f34df47cbdc0e73 Mon Sep 17 00:00:00 2001 From: HunteRoi Date: Sun, 6 Dec 2020 15:53:52 +0100 Subject: [PATCH] Format, ease game's difficulty, update readme, remove fake P key Allow multiple letters to appear at once, increase vowels probability to drop, decrease letters probability to drop from .05 to .03, fix leaderboard not displayed yes when the word was found --- README.md | 9 ++++- food.js | 94 ++++++++++++++++++++++++---------------------- game.js | 87 ++++++++++++++++++++++-------------------- grid.js | 16 +++++--- hangman.js | 74 ++++++++++++++++++------------------ index.html | 45 +++++++++++----------- input.js | 44 +++++++++++----------- points.js | 18 ++++----- snake.js | 62 +++++++++++++++--------------- style.css | 108 ++++++++++++++++++++++++++++------------------------- 10 files changed, 294 insertions(+), 263 deletions(-) diff --git a/README.md b/README.md index 40ea511..18f6484 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +# HangSnake This is my little gift for my girlfriend to play some old games her younger self liked very much. -Enjoy! \ No newline at end of file +Enjoy! + +## Note +You need to run this from a server. I'm using the plugin "Live Server" to run & dispose of a server from Visual Studio Code anytime I need it. + +## To do +If I've got some spare time, I might think to properly implement a pause button using the P key (yes, only setting the `inputDirection` back to `{ x: 0, y: 0 }` is not enough). I'd also want to get rid of those `
` generation and use a ``. Also want to display a real snake instead of blocks. But this will be for a different story! diff --git a/food.js b/food.js index e6ef32a..52911d8 100644 --- a/food.js +++ b/food.js @@ -3,73 +3,79 @@ import { getRandomGridPosition } from './grid.js'; import { update as grantPoints } from './points.js'; import { update as updateLetters } from './hangman.js'; -const CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZAEIOUYAEIOUYAEIOUYAEIOUY'; const APPLE = { type: 'apple', points: 1 }; const BUG = { type: 'bug', lifespan: 30, points: 3 }; const EXPANSION_RATE = 1; -const OCCASIONAL_FOOD_PROBABILITY = 0.05; +const OCCASIONAL_FOOD_PROBABILITY = 0.03; let food = { ...getRandomFoodPosition(), ...APPLE }; -let occasionalFood = null; +const occasionalFoods = []; export function update() { - if (occasionalFood !== null) { - if (onSnake(occasionalFood)) { - const isOk = updateLetters(occasionalFood.letter); - if (isOk) grantPoints(occasionalFood.points); - else grantPoints(occasionalFood.points * -1); - - expandSnake(EXPANSION_RATE); - occasionalFood = null; - } else { - if (occasionalFood && occasionalFood.lifespan > 0) { - occasionalFood.lifespan -= 1; - } else { - occasionalFood = null; - } - } - } - if (shouldGenerateOccasionalFood()) { - occasionalFood = { ...getRandomFoodPosition(), ...BUG, letter: getRandomLetter() }; + const toRemove = []; + occasionalFoods.forEach((occasionalFood, index) => { + if (onSnake(occasionalFood)) { + const isOk = updateLetters(occasionalFood.letter); + grantPoints(isOk ? occasionalFood.points : occasionalFood.points * -1); + expandSnake(EXPANSION_RATE); + toRemove.push(index); + } else { + if (occasionalFood.lifespan > 0) { + occasionalFood.lifespan -= 1; + } else { + toRemove.push(index); + } } + }); + toRemove.forEach((i) => occasionalFoods.splice(i, 1)); + if (shouldGenerateOccasionalFood()) { + occasionalFoods.push({ + ...getRandomFoodPosition(), + ...BUG, + letter: getRandomLetter(), + }); + } - if (onSnake(food)) { - grantPoints(food.points); - expandSnake(EXPANSION_RATE); - food = { ...getRandomFoodPosition(), ...APPLE }; - } + if (onSnake(food)) { + grantPoints(food.points); + expandSnake(EXPANSION_RATE); + food = { ...getRandomFoodPosition(), ...APPLE }; + } } export function draw(gameboard) { - drawFood(gameboard, food); + drawFood(gameboard, food); - if (occasionalFood !== null) { - drawFood(gameboard, occasionalFood); - } + if (occasionalFoods.length !== 0) { + occasionalFoods.forEach((occasionalFood) => + drawFood(gameboard, occasionalFood) + ); + } } function drawFood(gameboard, food) { - const foodElement = document.createElement('div'); - if (food.letter) foodElement.innerText = food.letter; - foodElement.style.gridRowStart = food.y; - foodElement.style.gridColumnStart = food.x; - foodElement.classList.add(food.type); - gameboard.appendChild(foodElement); + const foodElement = document.createElement('div'); + if (food.letter) foodElement.innerText = food.letter; + foodElement.style.gridRowStart = food.y; + foodElement.style.gridColumnStart = food.x; + foodElement.classList.add(food.type); + gameboard.appendChild(foodElement); } function getRandomFoodPosition() { - let newFoodPosition; - while (newFoodPosition == null || onSnake(newFoodPosition)) { - newFoodPosition = getRandomGridPosition(); - } - return newFoodPosition; + let newFoodPosition; + while (newFoodPosition == null || onSnake(newFoodPosition)) { + newFoodPosition = getRandomGridPosition(); + } + return newFoodPosition; } function shouldGenerateOccasionalFood() { - return Math.random() < OCCASIONAL_FOOD_PROBABILITY && occasionalFood === null; + return Math.random() < OCCASIONAL_FOOD_PROBABILITY; } function getRandomLetter() { - return CHARACTERS.charAt(Math.floor(Math.random() * CHARACTERS.length)); -} \ No newline at end of file + return CHARACTERS.charAt(Math.floor(Math.random() * CHARACTERS.length)); +} diff --git a/game.js b/game.js index 802b47b..35dd087 100644 --- a/game.js +++ b/game.js @@ -1,67 +1,74 @@ -import { draw as drawSnake, update as updateSnake, getSpeed, getSnakeHead, snakeIntersection } from './snake.js'; +import { + draw as drawSnake, + update as updateSnake, + getSpeed, + getSnakeHead, + snakeIntersection, +} from './snake.js'; import { draw as drawFood, update as updateFood } from './food.js'; import { draw as drawPoints, getPoints } from './points.js'; import { draw as drawHangman, hasFoundTheWord } from './hangman.js'; import { outsideGrid } from './grid.js'; -const gameboard = document.getElementById("gameboard"); -const pointsboard = document.getElementById("pointsboard"); -const hangmanboard = document.getElementById("hangmanboard"); -const lettersboard = document.getElementById("lettersboard"); -const leaderboard = document.getElementById("leaderboard"); +const gameboard = document.getElementById('gameboard'); +const pointsboard = document.getElementById('pointsboard'); +const hangmanboard = document.getElementById('hangmanboard'); +const lettersboard = document.getElementById('lettersboard'); +const leaderboard = document.getElementById('leaderboard'); let lastRenderTime = 0; let gameOver = false; function main(currentTime) { - const hasFoundIt = hasFoundTheWord(); - if (gameOver || hasFoundIt) { - if (hasFoundIt) { - document.getElementById("app").display = "none"; - document.getElementById("victory").display = "flex"; - } + const hasFoundIt = hasFoundTheWord(); + if (gameOver || hasFoundIt) { + if (hasFoundIt) { + document.getElementById('app').display = 'none'; + document.getElementById('victory').display = 'flex'; + } - const profile = prompt("What is your name?") || "Unknown"; - const profileEntry = { points: getPoints(), wordFound: hasFoundTheWord() }; - window.localStorage.setItem(profile, JSON.stringify(profileEntry)) + const profile = prompt('What is your name?') || 'Unknown'; + const profileEntry = { points: getPoints(), wordFound: hasFoundTheWord() }; + window.localStorage.setItem(profile, JSON.stringify(profileEntry)); - if (confirm(`${(hasFoundIt ? "You won!" : "You lost.")} Press ok to restart.`)) { - window.location = "/"; - } - return; + if ( + confirm(`${hasFoundIt ? 'You won!' : 'You lost.'} Press ok to restart.`) + ) { + window.location = '/'; } + return; + } + + window.requestAnimationFrame(main); - window.requestAnimationFrame(main); - - const secondsSinceLastRender = (currentTime - lastRenderTime) / 1000; - if (secondsSinceLastRender < 1/getSpeed()) return; + const secondsSinceLastRender = (currentTime - lastRenderTime) / 1000; + if (secondsSinceLastRender < 1 / getSpeed()) return; - lastRenderTime = currentTime; - update(); - draw(); + lastRenderTime = currentTime; + update(); + draw(); } window.requestAnimationFrame(main); function update() { - updateSnake(); - updateFood(); - checkDeath(); + updateSnake(); + updateFood(); + checkDeath(); } function draw() { - gameboard.innerHTML = ""; - pointsboard.innerHTML = ""; - hangmanboard.innerHTML = ""; - lettersboard.innerHTML = ""; - leaderboard.innerHTML = ""; + gameboard.innerHTML = ''; + pointsboard.innerHTML = ''; + hangmanboard.innerHTML = ''; + lettersboard.innerHTML = ''; + leaderboard.innerHTML = ''; - drawSnake(gameboard); - drawFood(gameboard); - drawPoints(pointsboard); - drawHangman(hangmanboard, lettersboard, leaderboard); + drawSnake(gameboard); + drawFood(gameboard); + drawPoints(pointsboard); + drawHangman(hangmanboard, lettersboard, leaderboard); } function checkDeath() { - gameOver = outsideGrid(getSnakeHead()) - || snakeIntersection(); + gameOver = outsideGrid(getSnakeHead()) || snakeIntersection(); } diff --git a/grid.js b/grid.js index e0ef7f1..1b1e6d6 100644 --- a/grid.js +++ b/grid.js @@ -1,13 +1,17 @@ const GRID_SIZE = 25; export function getRandomGridPosition() { - return { - x: Math.floor(Math.random() * GRID_SIZE) + 1, - y: Math.floor(Math.random() * GRID_SIZE) + 1 - } + return { + x: Math.floor(Math.random() * GRID_SIZE) + 1, + y: Math.floor(Math.random() * GRID_SIZE) + 1, + }; } export function outsideGrid(position) { - return position.x < 1 || position.x > GRID_SIZE - || position.y < 1 || position.y > GRID_SIZE; + return ( + position.x < 1 || + position.x > GRID_SIZE || + position.y < 1 || + position.y > GRID_SIZE + ); } diff --git a/hangman.js b/hangman.js index 6db8854..2028d08 100644 --- a/hangman.js +++ b/hangman.js @@ -2,47 +2,49 @@ const givenLetters = []; const sentence = 'INSERT A PHRASE HERE'.split('') ?? []; export function update(newLetter) { - if (!givenLetters.includes(newLetter)) { - givenLetters.push(newLetter); - } - return sentence.includes(newLetter); + if (!givenLetters.includes(newLetter)) { + givenLetters.push(newLetter); + } + return sentence.includes(newLetter); } export function draw(hangmanboard, lettersboard, leaderboard) { - sentence.forEach(l => { - const letterElement = document.createElement('span'); - if (l === ' ') { - letterElement.innerText = '-'; - } else if (givenLetters.includes(l)) { - letterElement.innerText = l; - } else { - letterElement.innerText = '_'; - } - letterElement.innerText += ' '; - letterElement.classList.add('letter'); - hangmanboard.appendChild(letterElement); - }); + sentence.forEach((l) => { + const letterElement = document.createElement('span'); + if (l === ' ') { + letterElement.innerText = '-'; + } else if (givenLetters.includes(l)) { + letterElement.innerText = l; + } else { + letterElement.innerText = '_'; + } + letterElement.innerText += ' '; + letterElement.classList.add('letter'); + hangmanboard.appendChild(letterElement); + }); - givenLetters.forEach(l => { - if (!sentence.includes(l)) { - lettersboard.innerText += `${l} `; - } - }); + givenLetters.forEach((l) => { + if (!sentence.includes(l)) { + lettersboard.innerText += l; + } + }); - const allProfiles = Object.keys(localStorage); - const allEntries = allProfiles - .map(profile => { - let entry = JSON.parse(localStorage.getItem(profile)); - return { profile, ...entry }; - }) - .sort((current, previous) => previous.points - current.points); - allEntries.forEach(entry => { - const leaderboardElement = document.createElement("li"); - leaderboardElement.innerHTML = `

${entry.profile || "Unknown"} - ${entry.points} (found: ${entry.hasFoundTheWord ? 'yes' : 'no'})

`; - leaderboard.appendChild(leaderboardElement); - }); + const allProfiles = Object.keys(localStorage); + const allEntries = allProfiles + .map((profile) => { + let entry = JSON.parse(localStorage.getItem(profile)); + return { profile, ...entry }; + }) + .sort((current, previous) => previous.points - current.points); + allEntries.forEach((entry) => { + const leaderboardElement = document.createElement('li'); + leaderboardElement.innerHTML = `

${entry.profile || 'Unknown'} → ${entry.points} (found: ${entry.wordFound ? 'yes' : 'no'})

`; + leaderboard.appendChild(leaderboardElement); + }); } export function hasFoundTheWord() { - return sentence.filter(l => l !== ' ').every(l => givenLetters.includes(l)); -} \ No newline at end of file + return sentence + .filter((l) => l !== ' ') + .every((l) => givenLetters.includes(l)); +} diff --git a/index.html b/index.html index ed9f7e9..5bdf819 100644 --- a/index.html +++ b/index.html @@ -1,31 +1,32 @@ - - - - + + + HangSnake - + - - + +
-
-
-

HangSnake

-
    - -

    Points:

    -

    To find:

    -

    Letters:

    -
    +
    +
    +

    HangSnake

    +
      + +

      Points:

      +

      To find:

      +

      Letters:

      +
      -

      VICTORYYYYYY

      -

      NICE JOB ♥

      - success image +

      VICTORYYYYYY

      +

      NICE JOB !

      + success image
      - - - \ No newline at end of file + + diff --git a/input.js b/input.js index 6d4d9a2..784aed0 100644 --- a/input.js +++ b/input.js @@ -1,30 +1,28 @@ let inputDirection = { x: 0, y: 0 }; let lastInputDirection = { x: 0, y: 0 }; -window.addEventListener('keydown', e => { - switch(e.key) { - case "ArrowUp": - if (lastInputDirection.y !== 0) break; - inputDirection = { x: 0, y: -1 }; - break; - case "ArrowDown": - if (lastInputDirection.y !== 0) break; - inputDirection = { x: 0, y: 1 }; - break; - case "ArrowLeft": - if (lastInputDirection.x !== 0) break; - inputDirection = { x: -1, y: 0 }; - break; - case "ArrowRight": - if (lastInputDirection.x !== 0) break; - inputDirection = { x: 1, y: 0 }; - break; - case "p": - inputDirection = { x: 0, y: 0 }; - } +window.addEventListener('keydown', (e) => { + switch (e.key) { + case 'ArrowUp': + if (lastInputDirection.y !== 0) break; + inputDirection = { x: 0, y: -1 }; + break; + case 'ArrowDown': + if (lastInputDirection.y !== 0) break; + inputDirection = { x: 0, y: 1 }; + break; + case 'ArrowLeft': + if (lastInputDirection.x !== 0) break; + inputDirection = { x: -1, y: 0 }; + break; + case 'ArrowRight': + if (lastInputDirection.x !== 0) break; + inputDirection = { x: 1, y: 0 }; + break; + } }); export function getInputDirection() { - lastInputDirection = inputDirection; - return inputDirection; + lastInputDirection = inputDirection; + return inputDirection; } diff --git a/points.js b/points.js index 1cb25fc..1b4a490 100644 --- a/points.js +++ b/points.js @@ -1,19 +1,19 @@ let points = 0; export function getPoints() { - return points; + return points; } export function update(newPoints = 1) { - points += newPoints; + points += newPoints; } export function draw(pointsboard) { - pointsboard.innerText = points; + pointsboard.innerText = points; - if (points < 0) { - pointsboard.style.color = "red"; - } else { - pointsboard.style.color = "inherit"; - } -} \ No newline at end of file + if (points < 0) { + pointsboard.style.color = 'red'; + } else { + pointsboard.style.color = 'inherit'; + } +} diff --git a/snake.js b/snake.js index fb5490c..eff49c1 100644 --- a/snake.js +++ b/snake.js @@ -1,68 +1,68 @@ import { getInputDirection } from './input.js'; -const steps = [10, 20, 30, 40]; +const steps = [10, 20, 30, 40, 50]; const DEFAULT_SNAKE_SPEED = 5; const HEAD = 0; -const snakeBody = [{ x: 13, y: 13}]; +const snakeBody = [{ x: 13, y: 13 }]; let snakeSpeed = DEFAULT_SNAKE_SPEED; let newSegments = 0; export function getSpeed() { - return snakeSpeed; + return snakeSpeed; } export function update() { - addSegment(); + addSegment(); - const direction = getInputDirection(); + const direction = getInputDirection(); - for (let i = snakeBody.length - 2; i >= HEAD; i--) { - snakeBody[i + 1] = { ...snakeBody[i] }; - } - snakeBody[HEAD].x += direction.x; - snakeBody[HEAD].y += direction.y; + for (let i = snakeBody.length - 2; i >= HEAD; i--) { + snakeBody[i + 1] = { ...snakeBody[i] }; + } + snakeBody[HEAD].x += direction.x; + snakeBody[HEAD].y += direction.y; } export function draw(gameboard) { - snakeBody.forEach((element, index) => { - const snakeElement = document.createElement('div'); - snakeElement.style.gridRowStart = element.y; - snakeElement.style.gridColumnStart = element.x; - snakeElement.classList.add('snake'); - gameboard.appendChild(snakeElement); - }); + snakeBody.forEach((element, index) => { + const snakeElement = document.createElement('div'); + snakeElement.style.gridRowStart = element.y; + snakeElement.style.gridColumnStart = element.x; + snakeElement.classList.add('snake'); + gameboard.appendChild(snakeElement); + }); } export function expandSnake(amount) { - newSegments += amount; + newSegments += amount; } export function onSnake(position, { ignoreHead = false } = {}) { - return snakeBody.some((element, index) => { - if (ignoreHead && index === HEAD) return false; - return equalPosition(element, position); - }); + return snakeBody.some((element, index) => { + if (ignoreHead && index === HEAD) return false; + return equalPosition(element, position); + }); } export function getSnakeHead() { - return snakeBody[HEAD]; + return snakeBody[HEAD]; } export function snakeIntersection() { - return onSnake(getSnakeHead(), { ignoreHead: true }); + return onSnake(getSnakeHead(), { ignoreHead: true }); } function equalPosition(pos1, pos2) { - return pos1.x === pos2.x && pos1.y === pos2.y; + return pos1.x === pos2.x && pos1.y === pos2.y; } function addSegment() { - for(let i = 0; i < newSegments; i++) { - snakeBody.push({ ...snakeBody[snakeBody.length-1] }); + for (let i = 0; i < newSegments; i++) { + snakeBody.push({ ...snakeBody[snakeBody.length - 1] }); - if (steps.includes(snakeBody.length)) { - snakeSpeed++; - } + if (steps.includes(snakeBody.length)) { + snakeSpeed++; } - newSegments = 0; + } + newSegments = 0; } diff --git a/style.css b/style.css index d2cf0fd..606a643 100644 --- a/style.css +++ b/style.css @@ -1,87 +1,93 @@ * { - -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Safari */ - -khtml-user-select: none; /* Konqueror HTML */ - -moz-user-select: none; /* Old versions of Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ - user-select: none; /* Non-prefixed version, currently + -webkit-touch-callout: none; + /* iOS Safari */ + -webkit-user-select: none; + /* Safari */ + -khtml-user-select: none; + /* Konqueror HTML */ + -moz-user-select: none; + /* Old versions of Firefox */ + -ms-user-select: none; + /* Internet Explorer/Edge */ + user-select: none; + /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */ } body { - height: 100vh; - width: 100vw; - background-color: black; - color: white; - margin: 0; - display: flex; - justify-content: center; - align-items: center; + height: 100vh; + width: 100vw; + background-color: black; + color: white; + margin: 0; + display: flex; + justify-content: center; + align-items: center; } #app { - display: flex; - justify-content: center; - align-items: center; + display: flex; + justify-content: center; + align-items: center; } #title { - text-align: center; - text-decoration: underline; + text-align: center; + text-decoration: underline; } -#app > :not(:first-child) { - margin-left: 1vw; +#app> :not(:first-child) { + margin-left: 1vw; } #gameboard { - background-color: #cccccc; - width: 100vmin; - height: 90vmin; - display: grid; - grid-template-rows: repeat(25, 1fr); - grid-template-columns: repeat(25, 1fr); + background-color: #cccccc; + width: 100vmin; + height: 90vmin; + display: grid; + grid-template-rows: repeat(25, 1fr); + grid-template-columns: repeat(25, 1fr); } .snake { - background-color: hsl(200, 100%, 50%); - border: .25vmin solid black; + background-color: hsl(200, 100%, 50%); + border: .25vmin solid black; } .apple { - width: 1vmin; - height: 1vmin; - border-radius: 50%; - background-color: crimson; - border: .25vmin solid black; - margin: 1.2vmin; + width: 1vmin; + height: 1vmin; + border-radius: 50%; + background-color: crimson; + border: .25vmin solid black; + margin: 1.2vmin; } .bug { - background-color: grey; - border: .25vmin solid black; - display: flex; - justify-content: center; - align-items: center; + background-color: grey; + border: .25vmin solid black; + display: flex; + justify-content: center; + align-items: center; } .wrong { - text-decoration: line-through; + text-decoration: line-through; } #victory { - display: none; - flex-direction: column; - justify-content: center; - align-items: center; + display: none; + flex-direction: column; + justify-content: center; + align-items: center; } -#victory > * { - font-size: 5vw; - line-height: 1vw; +#victory>* { + font-size: 5vw; + line-height: 1vw; } -#victory > img { - width: 50%; - height: 80%; +#victory>img { + width: 50%; + height: 80%; } \ No newline at end of file