├── 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 |
--------------------------------------------------------------------------------