Skip to content

Commit

Permalink
snake without mobx forgotten files
Browse files Browse the repository at this point in the history
  • Loading branch information
Aleksei Gorshkov committed Mar 8, 2024
1 parent 733e7e6 commit d9c9eb2
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 0 deletions.
146 changes: 146 additions & 0 deletions app/plainsnake/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<main className="flex justify-center items-center flex-col flex-1">
<div className="flex flex-col gap-2">
{field.map((col, cind) => (
<div className="flex gap-2" key={cind}>
{col.map((row, rind) => (
<div
className={cx(
"w-6 h-6 flex justify-center items-center rounded",
{
"bg-green-200": row === "S",
"bg-red-200": row === "F",
"bg-slate-100": row === "_",
}
)}
key={rind}
></div>
))}
</div>
))}
</div>
{phase === "lost" && (
<div className="mt-12 text-center">
You Lose!
<br />
<button
className="bg-slate-200 px-4 py-2 rounded"
onClick={() => handlePlayAgain()}
>
Play Again
</button>
</div>
)}
</main>
);
}
45 changes: 45 additions & 0 deletions app/snake/game.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<main className="flex justify-center items-center flex-col h-screen">
<div className="flex flex-col gap-2">
{store.field.map((col, cind) => (
<div className="flex gap-2" key={cind}>
{col.map((row, rind) => (
<div
className={cx(
"w-6 h-6 flex justify-center items-center rounded",
{
"bg-green-200": row === "S",
"bg-red-200": row === "F",
"bg-slate-100": row === "_",
}
)}
key={rind}
></div>
))}
</div>
))}
</div>
{store.phase === "lost" && (
<div className="mt-12">
You Lose!
<button onClick={() => store.run()}>Play Again</button>
</div>
)}
</main>
);
});
5 changes: 5 additions & 0 deletions app/snake/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Game } from "./game";

export default function Home() {
return <Game />;
}
132 changes: 132 additions & 0 deletions app/snake/store.ts
Original file line number Diff line number Diff line change
@@ -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<typeof setInterval> | 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();

0 comments on commit d9c9eb2

Please sign in to comment.