/** * @type {HTMLCanvasElement} */ const canvas = document.querySelector('.snek') canvas.width = 400 canvas.height = 300 canvas.style.border = '2px solid #FF0844' canvas.style.width = '800px' canvas.style.height = '600px' canvas.style.margin = '0 auto' /** * @type {HTMLDivElement} */ const container = document.querySelector('.snek-container') container.style.display = 'flex' container.style.justifyContent = 'center' container.style.textAlign = 'center' container.style.flexDirection = 'column' /** * @type {HTMLButtonElement} */ const button = document.querySelector('.snek-play') button.style.width = '200px' button.style.background = 'green' button.style.alignSelf = 'center' button.style.display = 'none' /** * @type {HTMLDivElement} */ const points = document.querySelector('.snek-points') points.style.fontSize = '50px' points.style.color = '#FF0844' points.innerHTML = 0 button.addEventListener('click', () => { state = getInitialState() button.style.display = 'none' }) const ctx = canvas.getContext('2d') const GRID_SIZE = 10 const Directions = { Left: 'LEFT', Up: 'UP', Right: 'RIGHT', Down: 'DOWN', } const Keys = { Left: 37, Up: 38, Right: 39, Down: 40, } const DirectionKeys = { [Keys.Left]: Directions.Left, [Keys.Up]: Directions.Up, [Keys.Right]: Directions.Right, [Keys.Down]: Directions.Down, } const initialBodyLength = 3 const getInitialState = () => { const initialState = { x: canvas.width / 2, y: canvas.height / 2, direction: Directions.Up, alive: true, body: [], bodyLength: 3, currentFoodPosition: getFoodPosition(), } return initialState } let state = getInitialState() function getFoodPosition() { return { x: Math.floor(Math.random() * (canvas.width / GRID_SIZE)) * GRID_SIZE, y: Math.floor(Math.random() * (canvas.height / GRID_SIZE)) * GRID_SIZE, } } const Movements = { [Directions.Left]: () => { const nextPos = leftPosition() if (nextPos>= 0) { state.x = nextPos } else { state.alive = false } }, [Directions.Up]: () => { const nextPos = upPosition() if (nextPos>= 0) { state.y = nextPos } else { state.alive = false } }, [Directions.Right]: () => { const nextPos = rightPosition() if (nextPos < canvas.width) { state.x = nextPos } else { state.alive = false } }, [Directions.Down]: () => { const nextPos = downPosition() if (nextPos < canvas.height) { state.y = nextPos } else { state.alive = false } }, } function leftPosition() { return state.x - GRID_SIZE } function rightPosition() { return state.x + GRID_SIZE } function upPosition() { return state.y - GRID_SIZE } function downPosition() { return state.y + GRID_SIZE } function drawSnake() { const {x, y, direction, alive, body, currentFoodPosition, bodyLength} = state if (alive) { // Snek is hungry if (x === currentFoodPosition.x && y === currentFoodPosition.y) { state.bodyLength++ state.currentFoodPosition = getFoodPosition() points.innerHTML = state.bodyLength - initialBodyLength } // To hungry, eat self if (body.some(b => b.x === x && b.y === y)) { // dead state.alive = false } const movement = Movements[direction] movement && movement() ctx.clearRect(0, 0, canvas.width, canvas.height) } else { ctx.font = '20px Georgia' ctx.textBaseline = 'middle' ctx.textAlign = 'center' ctx.fillText('Game over', canvas.width / 2, canvas.height / 2) button.style.display = 'block' } body.push({x, y}) if (body.length> bodyLength) { var itemToRemove = body.shift() ctx.clearRect(itemToRemove.x, itemToRemove.x, GRID_SIZE, GRID_SIZE) } ctx.fillStyle = 'green' body.forEach(({x, y}) => ctx.fillRect(x, y, GRID_SIZE, GRID_SIZE)) ctx.fillRect(x, y, GRID_SIZE, GRID_SIZE) ctx.fillStyle = 'red' ctx.fillRect( currentFoodPosition.x, currentFoodPosition.y, GRID_SIZE, GRID_SIZE, ) } /** * * @param {KeyboardEvent} e */ function changeDirection(e) { if (DirectionKeys[e.keyCode]) { const currentDirection = state.direction const nextDirection = DirectionKeys[e.keyCode] if (canMove(currentDirection, nextDirection)) { state.direction = DirectionKeys[e.keyCode] } } } /** * * @param {String} currentDirection * @param {String} nextDirection */ function canMove(currentDirection, nextDirection) { if (currentDirection === Directions.Up) { return nextDirection !== Directions.Down } if (currentDirection === Directions.Left) { return nextDirection !== Directions.Right } if (currentDirection === Directions.Right) { return nextDirection !== Directions.Left } if (currentDirection === Directions.Down) { return nextDirection !== Directions.Up } } window.setInterval(drawSnake, 100) window.addEventListener('keydown', changeDirection)