diff --git a/pages/index.js b/pages/index.js
index de83024..2b62d72 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -1,10 +1,10 @@
import dynamic from "next/dynamic";
-import { useEffect, useState, useRef } from "react";
+import { useEffect, useState, useRef, useCallback } from "react";
import styles from "../styles/Snake.module.css";
const Config = {
- height: 25,
- width: 25,
+ height: 15,
+ width: 15,
cellSize: 32,
};
@@ -12,6 +12,7 @@ const CellType = {
Snake: "snake",
Food: "food",
Empty: "empty",
+ Poison: "poison",
};
const Direction = {
@@ -21,7 +22,7 @@ const Direction = {
Bottom: { x: 0, y: 1 },
};
-const Cell = ({ x, y, type }) => {
+const Cell = ({ x, y, type, remaining }) => {
const getStyles = () => {
switch (type) {
case CellType.Snake:
@@ -33,10 +34,20 @@ const Cell = ({ x, y, type }) => {
case CellType.Food:
return {
- backgroundColor: "darkorange",
+ backgroundColor: "tomato",
borderRadius: 20,
width: 32,
height: 32,
+ transform: `scale(${0.5 + remaining / 20})`,
+ };
+
+ case CellType.Poison:
+ return {
+ backgroundColor: "red",
+ borderRadius: 20,
+ width: 32,
+ height: 32,
+ transform: `scale(${0.5 + remaining / 20})`,
};
default:
@@ -53,89 +64,172 @@ const Cell = ({ x, y, type }) => {
height: Config.cellSize,
}}
>
-
+
+ {remaining}
+
);
};
-const getRandomCell = () => ({
+const getRandomCell = (type) => ({
x: Math.floor(Math.random() * Config.width),
y: Math.floor(Math.random() * Config.width),
+ start: Date.now(),
+ type: type,
});
-const Snake = () => {
+//custom hook
+//controller
+const useInterval = (func, dir) => {
+ const timer = useRef(Date.now());
+ const createCallback = useCallback(() => {
+ if (Date.now() - timer.current > dir) {
+ timer.current = Date.now();
+ func();
+ }
+ }, [dir, func]);
+
+ useEffect(() => {
+ const interval = setInterval(createCallback, 1000 / 60);
+ return () => clearInterval(interval);
+ }, [createCallback]);
+};
+const GetSnake = () => {
const getDefaultSnake = () => [
{ x: 8, y: 12 },
{ x: 7, y: 12 },
{ x: 6, y: 12 },
];
- const grid = useRef();
-
// snake[0] is head and snake[snake.length - 1] is tail
const [snake, setSnake] = useState(getDefaultSnake());
const [direction, setDirection] = useState(Direction.Right);
+ const [objects, setObjects] = useState([]);
+ const score = snake.length - 3;
- const [food, setFood] = useState({ x: 4, y: 10 });
- const [score, setScore] = useState(0);
+ const finding = ({ x, y }, arr, type) => {
+ //for snake
+ if (type === undefined)
+ return arr.find((position) => position.x === x && position.y === y);
+ return arr.find(
+ (position) =>
+ position.x === x && position.y === y && position.type === type
+ );
+ };
- // move the snake
- useEffect(() => {
- const runSingleStep = () => {
- setSnake((snake) => {
- const head = snake[0];
- const newHead = { x: head.x + direction.x, y: head.y + direction.y };
+ //checking cells
+ const isObjectOrSnake = useCallback(
+ ({ x, y }, type) => {
+ if (type === CellType.Snake) return finding({ x, y }, snake);
+ else return finding({ x, y }, objects, type);
+ },
+ [objects, snake]
+ );
- // make a new snake by extending head
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
- const newSnake = [newHead, ...snake];
+ //restart the game
+ const resetGame = useCallback(() => {
+ setSnake(getDefaultSnake());
+ setDirection(Direction.Right);
+ setObjects([]);
+ }, []);
- // remove tail
- newSnake.pop();
+ //moving the snake
+ const runSingleStep = useCallback(() => {
+ setSnake((snake) => {
+ const head = snake[0];
+ const newHead = {
+ x: (head.x + direction.x + Config.width) % Config.width,
+ y: (head.y + direction.y + Config.height) % Config.height,
+ };
- return newSnake;
- });
- };
+ // make a new snake by extending head
+ const newSnake = [newHead, ...snake];
- runSingleStep();
- const timer = setInterval(runSingleStep, 500);
+ // remove tail when head doesnt eat food
+ if (!isObjectOrSnake(newHead, CellType.Food)) {
+ newSnake.pop();
+ }
+ //remove again when eats poison
+ if (isObjectOrSnake(newHead, CellType.Poison)) {
+ newSnake.pop();
+ }
+ if (isObjectOrSnake(newHead, CellType.Snake) || score < 0) {
+ resetGame();
+ }
- return () => clearInterval(timer);
- }, [direction, food]);
+ return newSnake;
+ });
+ }, [direction.x, direction.y, isObjectOrSnake, resetGame, score]);
- // update score whenever head touches a food
- useEffect(() => {
- const head = snake[0];
- if (isFood(head)) {
- setScore((score) => {
- return score + 1;
- });
-
- let newFood = getRandomCell();
- while (isSnake(newFood)) {
- newFood = getRandomCell();
+ const addObject = useCallback(
+ (type) => {
+ let newObject = getRandomCell(type);
+ while (
+ isObjectOrSnake(
+ newObject,
+ CellType.Snake ||
+ isObjectOrSnake(newObject, CellType.Food) ||
+ isObjectOrSnake(isObjectOrSnake, CellType.Poison)
+ )
+ ) {
+ newObject = getRandomCell(type);
}
+ console.log(newObject.type);
+ setObjects((currentObjects) => [...currentObjects, newObject]);
+ },
+ [isObjectOrSnake]
+ );
+ const removeObject = useCallback(() => {
+ setObjects((currentObjects) =>
+ currentObjects.filter(
+ (currentObject) => Date.now() - currentObject.start < 10000
+ )
+ );
+ }, []);
- setFood(newFood);
+ // update foods and poisons whenever head touches a food or a poison
+ useEffect(() => {
+ const head = snake[0];
+ if (isObjectOrSnake(head, CellType.Food)) {
+ console.log("ate food");
+ setObjects((currentObjects) =>
+ currentObjects.filter(
+ (currentObject) =>
+ !(currentObject.x === head.x && currentObject.y === head.y)
+ )
+ );
}
- }, [snake]);
+ }, [isObjectOrSnake, snake]);
+
+ useInterval(() => addObject(CellType.Food), 3000);
+ useInterval(() => addObject(CellType.Poison), 1000);
+ useInterval(runSingleStep, 300);
+ useInterval(() => removeObject(), 50);
+ useInterval(() => removeObject(), 100);
+
+ const changeDir = (checkDir, newDir) => {
+ setDirection((direction) => {
+ if (direction != checkDir) return newDir;
+ return direction;
+ });
+ };
useEffect(() => {
const handleNavigation = (event) => {
switch (event.key) {
case "ArrowUp":
- setDirection(Direction.Top);
+ changeDir(Direction.Bottom, Direction.Top);
break;
case "ArrowDown":
- setDirection(Direction.Bottom);
+ changeDir(Direction.Top, Direction.Bottom);
break;
case "ArrowLeft":
- setDirection(Direction.Left);
+ changeDir(Direction.Right, Direction.Left);
break;
case "ArrowRight":
- setDirection(Direction.Right);
+ changeDir(Direction.Left, Direction.Right);
break;
}
};
@@ -143,27 +237,39 @@ const Snake = () => {
return () => window.removeEventListener("keydown", handleNavigation);
}, []);
-
- // ?. is called optional chaining
- // see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
- const isFood = ({ x, y }) => food?.x === x && food?.y === y;
-
- const isSnake = ({ x, y }) =>
- snake.find((position) => position.x === x && position.y === y);
-
const cells = [];
for (let x = 0; x < Config.width; x++) {
for (let y = 0; y < Config.height; y++) {
- let type = CellType.Empty;
- if (isFood({ x, y })) {
+ let type = CellType.Empty,
+ remaining = undefined;
+ if (isObjectOrSnake({ x, y }, CellType.Food)) {
type = CellType.Food;
- } else if (isSnake({ x, y })) {
+ remaining =
+ 10 -
+ Math.round(
+ (Date.now() - finding({ x, y }, objects, type).start) / 1000
+ );
+ } else if (isObjectOrSnake({ x, y }, CellType.Snake)) {
type = CellType.Snake;
+ } else if (isObjectOrSnake({ x, y }, CellType.Poison)) {
+ type = CellType.Poison;
+ remaining =
+ 10 -
+ Math.round(
+ (Date.now() - finding({ x, y }, objects, type).start) / 1000
+ );
}
- cells.push( | );
+ cells.push(
+ |
+ );
}
}
+ return { score, isObjectOrSnake, cells };
+};
+//view
+const Snake = () => {
+ const { score, cells } = GetSnake();
return (