diff --git a/app/plainsnake/page.tsx b/app/plainsnake/page.tsx new file mode 100644 index 0000000..c5c0483 --- /dev/null +++ b/app/plainsnake/page.tsx @@ -0,0 +1,146 @@ +"use client"; +import cx from "clsx"; +import { useEffect, useState } from "react"; +import _, { set } from "lodash"; + +let interval: NodeJS.Timeout | null = null; +let fruit: number[] = [5, 5]; + +function getHead(direction: string, x: number, y: number): number[] { + switch (direction) { + case "right": + return [y, x + 1]; + case "left": + return [y, x - 1]; + case "up": + return [y - 1, x]; + case "down": + return [y + 1, x]; + } + return [0, 0]; +} + +function addFruit(snakeArg: number[][]) { + let x: number, y: number; + do { + y = Math.floor(Math.random() * 10); + x = Math.floor(Math.random() * 10); + fruit = [y, x]; + } while (snakeArg.some(([sy, sx]) => sy === y && sx === x)); +} + +let direction = "right"; +export default function Home() { + const [snake, setSnake] = useState([ + [1, 1], + [2, 1], + [3, 1], + ]); + const [phase, setPhase] = useState("running"); + + useEffect(() => { + document.addEventListener("keydown", (e) => { + console.log(e.key); + + switch (e.key) { + case "ArrowUp": + direction = direction === "down" ? "down" : "up"; + break; + case "ArrowDown": + direction = direction === "up" ? "up" : "down"; + break; + case "ArrowLeft": + direction = direction === "right" ? "right" : "left"; + break; + case "ArrowRight": + direction = direction === "left" ? "left" : "right"; + break; + } + }); + interval = setInterval(() => { + setSnake((prev) => { + const newHead = getHead(direction, prev[0][1], prev[0][0]); + const [y, x] = newHead; + if ( + y < 0 || + y >= 10 || + x < 0 || + x >= 10 || + prev.some(([sy, sx]) => sy === y && sx === x) + ) { + setPhase("lost"); + return prev; + } + const newSnake = [newHead, ...prev]; + + if (!(x === fruit[1] && y === fruit[0])) { + newSnake.pop(); + } else { + addFruit(newSnake); + } + return newSnake; + }); + }, 200); + return () => { + clearInterval(interval!); + }; + }, []); + + const field = _.times(10, (y) => + _.times(10, (x) => { + if (snake.some(([sy, sx]) => sy === y && sx === x)) { + return "S"; + } + if (fruit[0] === y && fruit[1] === x) { + return "F"; + } + return "_"; + }) + ); + + function handlePlayAgain(): void { + setSnake([ + [1, 1], + [2, 1], + [3, 1], + ]); + setPhase("running"); + direction = "right"; + } + + return ( + + + {field.map((col, cind) => ( + + {col.map((row, rind) => ( + + ))} + + ))} + + {phase === "lost" && ( + + You Lose! + + handlePlayAgain()} + > + Play Again + + + )} + + ); +} diff --git a/app/snake/game.tsx b/app/snake/game.tsx new file mode 100644 index 0000000..591f615 --- /dev/null +++ b/app/snake/game.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { useEffect } from "react"; +import { store } from "./store"; +import { observer } from "mobx-react-lite"; +import cx from "clsx"; + +export const Game = observer(() => { + useEffect(() => { + if (store.phase === "idle") { + store.setup(); + store.run(); + } + }, []); + + return ( + + + {store.field.map((col, cind) => ( + + {col.map((row, rind) => ( + + ))} + + ))} + + {store.phase === "lost" && ( + + You Lose! + store.run()}>Play Again + + )} + + ); +}); diff --git a/app/snake/page.tsx b/app/snake/page.tsx new file mode 100644 index 0000000..767a891 --- /dev/null +++ b/app/snake/page.tsx @@ -0,0 +1,5 @@ +import { Game } from "./game"; + +export default function Home() { + return ; +} diff --git a/app/snake/store.ts b/app/snake/store.ts new file mode 100644 index 0000000..93695aa --- /dev/null +++ b/app/snake/store.ts @@ -0,0 +1,132 @@ +import { makeAutoObservable } from "mobx"; +import _ from "lodash"; + +function getHead(direction: string, x: number, y: number): number[] { + switch (direction) { + case "right": + return [y, x + 1]; + case "left": + return [y, x - 1]; + case "up": + return [y - 1, x]; + case "down": + return [y + 1, x]; + } + return [0, 0]; +} + +class SnakeStore { + field = _.times(10, () => _.times(10, () => "_")); + snake = [ + [1, 1], + [2, 1], + [3, 1], + ]; + fruit = [5, 5]; + direction = "right"; + isRunning = false; + interval: ReturnType | null = null; + phase = "idle"; + + // Initialization + setup() { + if (typeof document === "undefined") { + return; + } + + document.addEventListener("keydown", (e) => { + console.log(e.key); + + switch (e.key) { + case "ArrowUp": + this.direction = this.direction === "down" ? "down" : "up"; + break; + case "ArrowDown": + this.direction = this.direction === "up" ? "up" : "down"; + break; + case "ArrowLeft": + this.direction = this.direction === "right" ? "right" : "left"; + break; + case "ArrowRight": + this.direction = this.direction === "left" ? "left" : "right"; + break; + } + }); + } + + addFruit() { + do { + const y = Math.floor(Math.random() * 10); + const x = Math.floor(Math.random() * 10); + this.fruit = [y, x]; + } while ( + this.snake.some( + ([sy, sx]) => sy === this.fruit[0] && sx === this.fruit[1] + ) + ); + } + + // Processing input + input() {} + + // Updating the game state + update() { + const [_y, _x] = this.snake.at(0)!; + const [y, x] = getHead(this.direction, _x, _y); + + if ( + y < 0 || + y >= 10 || + x < 0 || + x >= 10 || + this.snake.some(([sy, sx]) => sy === y && sx === x) + ) { + this.interval && clearInterval(this.interval); + this.phase = "lost"; + } + + this.snake.unshift([y, x]); + + if (!(x === this.fruit[1] && y === this.fruit[0])) { + this.snake.pop(); + } else { + this.addFruit(); + } + + this.field = _.times(10, (y) => + _.times(10, (x) => { + if (this.snake.some(([sy, sx]) => sy === y && sx === x)) { + return "S"; + } + if (this.fruit[0] === y && this.fruit[1] === x) { + return "F"; + } + return "_"; + }) + ); + } + + // Rendering the game, we probably won't use this, as we will use React to render the game + render() {} + + run() { + this.phase = "running"; + this.interval = setInterval(() => { + this.update(); + }, 200); + this.snake = [ + [1, 1], + [2, 1], + [3, 1], + ]; + + this.fruit = [5, 5]; + this.direction = "right"; + } + + constructor() { + makeAutoObservable(this); + } +} + +export const store = new SnakeStore();