├── tsconfig.json ├── grid.js ├── grid.ts ├── index.html ├── styles.css ├── food.js ├── food.ts ├── input.ts ├── input.js ├── index.js ├── index.ts ├── snake.js └── snake.ts /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNEXT", 4 | "watch": true, 5 | "lib": ["DOM", "es2017"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /grid.js: -------------------------------------------------------------------------------- 1 | var GRID_SIZE = 21; 2 | export function randomGridPos() { 3 | return { 4 | x: Math.floor(Math.random() * GRID_SIZE) + 1, 5 | y: Math.floor(Math.random() * GRID_SIZE) + 1, 6 | }; 7 | } 8 | export function offGrid(pos) { 9 | return pos.x < 1 || pos.y < 1 || pos.x > GRID_SIZE || pos.y > GRID_SIZE; 10 | } 11 | -------------------------------------------------------------------------------- /grid.ts: -------------------------------------------------------------------------------- 1 | interface Ipos { 2 | x: number; 3 | y: number; 4 | } 5 | var GRID_SIZE: number = 21; 6 | export function randomGridPos(): Ipos { 7 | return { 8 | x: Math.floor(Math.random() * GRID_SIZE) + 1, 9 | y: Math.floor(Math.random() * GRID_SIZE) + 1, 10 | }; 11 | } 12 | 13 | export function offGrid(pos: Ipos): boolean { 14 | return pos.x < 1 || pos.y < 1 || pos.x > GRID_SIZE || pos.y > GRID_SIZE; 15 | } 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | the Snake game 🔵🟡 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | height: 100vh; 6 | width: 100vw; 7 | margin: 0; 8 | background-color: #484848; 9 | } 10 | 11 | #board { 12 | background-color: #ccc; 13 | height: 100vmin; 14 | width: 100vmin; 15 | display: grid; 16 | grid-template-rows: repeat(21, 1fr); 17 | grid-template-columns: repeat(21, 1fr); 18 | } 19 | .snake { 20 | background-color: #42b9e8; 21 | border: 0.2vmin solid #000000; 22 | border-radius: 50%; 23 | } 24 | .food { 25 | background-color: rgb(241, 252, 87); 26 | border-radius: 50%; 27 | border: 0.15vmin solid rgb(0, 0, 0); 28 | } 29 | -------------------------------------------------------------------------------- /food.js: -------------------------------------------------------------------------------- 1 | import { onSnk, expandSnk } from "./snake.js"; 2 | import { randomGridPos } from "./grid.js"; 3 | var EXPAND = 1; 4 | var food = getRandomFood(); 5 | export function update() { 6 | if (onSnk(food)) { 7 | expandSnk(EXPAND); 8 | food = getRandomFood(); 9 | } 10 | } 11 | export function draw(board) { 12 | var foodElement = document.createElement("div"); 13 | foodElement.style.gridRowStart = food.y.toString(); 14 | foodElement.style.gridColumnStart = food.x.toString(); 15 | foodElement.classList.add("food"); 16 | board.appendChild(foodElement); 17 | } 18 | function getRandomFood() { 19 | var newFood; 20 | while (newFood == null || onSnk(newFood)) { 21 | newFood = randomGridPos(); 22 | } 23 | return newFood; 24 | } 25 | -------------------------------------------------------------------------------- /food.ts: -------------------------------------------------------------------------------- 1 | import { onSnk, expandSnk } from "./snake.js"; 2 | import { randomGridPos } from "./grid.js"; 3 | var EXPAND: number = 1; 4 | 5 | interface Ifood { 6 | x: number; 7 | y: number; 8 | } 9 | var food: Ifood = getRandomFood(); 10 | export function update(): void { 11 | if (onSnk(food)) { 12 | expandSnk(EXPAND); 13 | food = getRandomFood(); 14 | } 15 | } 16 | export function draw(board: HTMLElement): void { 17 | var foodElement = document.createElement("div"); 18 | foodElement.style.gridRowStart = food.y.toString(); 19 | foodElement.style.gridColumnStart = food.x.toString(); 20 | foodElement.classList.add("food"); 21 | board.appendChild(foodElement); 22 | } 23 | 24 | function getRandomFood(): Ifood { 25 | var newFood: Ifood; 26 | while (newFood == null || onSnk(newFood)) { 27 | newFood = randomGridPos(); 28 | } 29 | return newFood; 30 | } 31 | -------------------------------------------------------------------------------- /input.ts: -------------------------------------------------------------------------------- 1 | interface Idirection { 2 | x: number; 3 | y: number; 4 | } 5 | 6 | var inputDir: Idirection = { 7 | x: 0, 8 | y: 0, 9 | }; 10 | 11 | var lastInputDir: Idirection = { 12 | x: 0, 13 | y: 0, 14 | }; 15 | 16 | window.addEventListener("keydown", (event) => { 17 | switch (event.key) { 18 | case "ArrowUp": 19 | if (inputDir.y !== 0) break; 20 | inputDir = { x: 0, y: -1 }; 21 | break; 22 | case "ArrowDown": 23 | if (inputDir.y !== 0) break; 24 | inputDir = { x: 0, y: 1 }; 25 | break; 26 | case "ArrowRight": 27 | if (inputDir.x !== 0) break; 28 | inputDir = { x: 1, y: 0 }; 29 | break; 30 | case "ArrowLeft": 31 | if (inputDir.x !== 0) break; 32 | inputDir = { x: -1, y: 0 }; 33 | break; 34 | } 35 | }); 36 | export function getInputDir(): Idirection { 37 | lastInputDir = inputDir; 38 | return inputDir; 39 | } 40 | -------------------------------------------------------------------------------- /input.js: -------------------------------------------------------------------------------- 1 | var inputDir = { 2 | x: 0, 3 | y: 0, 4 | }; 5 | var lastInputDir = { 6 | x: 0, 7 | y: 0, 8 | }; 9 | window.addEventListener("keydown", (event) => { 10 | switch (event.key) { 11 | case "ArrowUp": 12 | if (inputDir.y !== 0) 13 | break; 14 | inputDir = { x: 0, y: -1 }; 15 | break; 16 | case "ArrowDown": 17 | if (inputDir.y !== 0) 18 | break; 19 | inputDir = { x: 0, y: 1 }; 20 | break; 21 | case "ArrowRight": 22 | if (inputDir.x !== 0) 23 | break; 24 | inputDir = { x: 1, y: 0 }; 25 | break; 26 | case "ArrowLeft": 27 | if (inputDir.x !== 0) 28 | break; 29 | inputDir = { x: -1, y: 0 }; 30 | break; 31 | } 32 | }); 33 | export function getInputDir() { 34 | lastInputDir = inputDir; 35 | return inputDir; 36 | } 37 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { draw as drawSnk, SPEED } from "./snake.js"; 2 | import { update as updateSnk } from "./snake.js"; 3 | import { update as updateFood, draw as drawFood } from "./food.js"; 4 | import { offGrid } from "./grid.js"; 5 | import { getSnkHead, snkBitSnk } from "./snake.js"; 6 | var last = 0; 7 | var gameOver = false; 8 | var board = document.getElementById("board"); 9 | function main(current) { 10 | if (gameOver) { 11 | if (confirm("snake dead, try again 🤙")) { 12 | window.location.reload(); 13 | } 14 | return; 15 | } 16 | //! 👆 we return so we dont run code below 17 | window.requestAnimationFrame(main); 18 | var secLastRender = (current - last) / 1000; 19 | if (secLastRender < 1 / SPEED) 20 | return; 21 | // console.log("render"); 22 | last = current; 23 | update(); 24 | draw(); 25 | checkGameOver(); 26 | } 27 | window.requestAnimationFrame(main); 28 | function update() { 29 | updateSnk(); 30 | updateFood(); 31 | } 32 | function draw() { 33 | board.innerHTML = ""; 34 | drawSnk(board); 35 | drawFood(board); 36 | } 37 | function checkGameOver() { 38 | gameOver = offGrid(getSnkHead()) || snkBitSnk(); 39 | } 40 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { draw as drawSnk, SPEED } from "./snake.js"; 2 | import { update as updateSnk } from "./snake.js"; 3 | import { update as updateFood, draw as drawFood } from "./food.js"; 4 | import { offGrid } from "./grid.js"; 5 | import { getSnkHead, snkBitSnk } from "./snake.js"; 6 | 7 | var last = 0; 8 | var gameOver = false; 9 | var board = document.getElementById("board"); 10 | 11 | function main(current: number): void { 12 | if (gameOver) { 13 | if (confirm("snake dead, try again 🤙")) { 14 | window.location.reload(); 15 | } 16 | return; 17 | } 18 | //! 👆 we return so we dont run code below 19 | window.requestAnimationFrame(main); 20 | var secLastRender: number = (current - last) / 1000; 21 | if (secLastRender < 1 / SPEED) return; 22 | // console.log("render"); 23 | last = current; 24 | update(); 25 | draw(); 26 | checkGameOver(); 27 | } 28 | window.requestAnimationFrame(main); 29 | 30 | function update(): void { 31 | updateSnk(); 32 | updateFood(); 33 | } 34 | function draw(): void { 35 | board.innerHTML = ""; 36 | drawSnk(board); 37 | drawFood(board); 38 | } 39 | 40 | function checkGameOver(): void { 41 | gameOver = offGrid(getSnkHead()) || snkBitSnk(); 42 | } 43 | -------------------------------------------------------------------------------- /snake.js: -------------------------------------------------------------------------------- 1 | import { getInputDir } from "./input.js"; 2 | export const SPEED = 10; 3 | var body = [ 4 | { x: 11, y: 11 }, 5 | { x: 11, y: 11 }, 6 | ]; 7 | var newBodySegs = 0; 8 | export function update() { 9 | addSeg(); 10 | var dir = getInputDir(); 11 | for (var i = body.length - 2; i >= 0; i--) { 12 | body[i + 1] = { ...body[i] }; 13 | body[i].x += dir.x; 14 | body[i].y += dir.y; 15 | } 16 | } 17 | export function draw(board) { 18 | body.forEach((e) => { 19 | var snkElement = document.createElement("div"); 20 | snkElement.style.gridRowStart = e.y.toString(); 21 | snkElement.style.gridColumnStart = e.x.toString(); 22 | snkElement.classList.add("snake"); 23 | board.appendChild(snkElement); 24 | }); 25 | } 26 | export function onSnk(pos, { ignoreHead = false } = {}) { 27 | return body.some((seg, index) => { 28 | if ((ignoreHead && index === 0) || (ignoreHead && index === 1)) 29 | return false; //! problem solved by ignoring sec index 30 | return overlapPos(pos, seg); 31 | }); 32 | } 33 | export function expandSnk(EXPAND) { 34 | newBodySegs += EXPAND; 35 | } 36 | function overlapPos(pos1, pos2) { 37 | return pos1.x === pos2.x && pos1.y === pos2.y; 38 | } 39 | function addSeg() { 40 | for (var i = 0; i < newBodySegs; i++) { 41 | body.push({ ...body[body.length - 1] }); 42 | } 43 | newBodySegs = 0; 44 | } 45 | export function getSnkHead() { 46 | return body[0]; 47 | } 48 | export function snkBitSnk() { 49 | return onSnk(body[0], { ignoreHead: true }); 50 | } 51 | -------------------------------------------------------------------------------- /snake.ts: -------------------------------------------------------------------------------- 1 | import { getInputDir } from "./input.js"; 2 | 3 | export const SPEED: number = 10; 4 | interface IBodySeg { 5 | x: number; 6 | y: number; 7 | } 8 | 9 | var body: IBodySeg[] = [ 10 | { x: 11, y: 11 }, 11 | { x: 11, y: 11 }, 12 | ]; 13 | 14 | var newBodySegs: number = 0; 15 | export function update(): void { 16 | addSeg(); 17 | var dir = getInputDir(); 18 | for (var i = body.length - 2; i >= 0; i--) { 19 | body[i + 1] = { ...body[i] }; 20 | body[i].x += dir.x; 21 | body[i].y += dir.y; 22 | } 23 | } 24 | export function draw(board: HTMLElement): void { 25 | body.forEach((e) => { 26 | var snkElement = document.createElement("div"); 27 | snkElement.style.gridRowStart = e.y.toString(); 28 | snkElement.style.gridColumnStart = e.x.toString(); 29 | snkElement.classList.add("snake"); 30 | board.appendChild(snkElement); 31 | }); 32 | } 33 | 34 | export function onSnk(pos: IBodySeg, { ignoreHead = false } = {}): boolean { 35 | return body.some((seg, index) => { 36 | if ((ignoreHead && index === 0) || (ignoreHead && index === 1)) 37 | return false; //! problem solved by ignoring sec index 38 | return overlapPos(pos, seg); 39 | }); 40 | } 41 | export function expandSnk(EXPAND: number): void { 42 | newBodySegs += EXPAND; 43 | } 44 | 45 | function overlapPos(pos1: IBodySeg, pos2: IBodySeg): boolean { 46 | return pos1.x === pos2.x && pos1.y === pos2.y; 47 | } 48 | 49 | function addSeg(): void { 50 | for (var i = 0; i < newBodySegs; i++) { 51 | body.push({ ...body[body.length - 1] }); 52 | } 53 | newBodySegs = 0; 54 | } 55 | 56 | export function getSnkHead(): IBodySeg { 57 | return body[0]; 58 | } 59 | export function snkBitSnk(): boolean { 60 | return onSnk(body[0], { ignoreHead: true }); 61 | } 62 | --------------------------------------------------------------------------------