Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module #2 (Nowshin Owishi) #29

Open
wants to merge 11 commits into
base: module-2
Choose a base branch
from
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 99 additions & 46 deletions pages/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import dynamic from "next/dynamic";
import { useEffect, useState, useRef } from "react";
import { useEffect, useState, useRef, useCallback, useMemo } from "react";
import styles from "../styles/Snake.module.css";

const Config = {
height: 25,
width: 25,
height: 15,
width: 15,
cellSize: 32,
};

Expand Down Expand Up @@ -61,9 +61,12 @@ const Cell = ({ x, y, type }) => {
const getRandomCell = () => ({
x: Math.floor(Math.random() * Config.width),
y: Math.floor(Math.random() * Config.width),
start: Date.now(),
});

const Snake = () => {
//custom hook
//controller
const UseSnake = () => {
Copy link

@MahdiMurshed MahdiMurshed Apr 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix naming convention

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UseSnake is not a hook here. I'm not sure, should I remove the term 'Use'?

const getDefaultSnake = () => [
{ x: 8, y: 12 },
{ x: 7, y: 12 },
Expand All @@ -75,67 +78,118 @@ const Snake = () => {
const [snake, setSnake] = useState(getDefaultSnake());
const [direction, setDirection] = useState(Direction.Right);

const [food, setFood] = useState({ x: 4, y: 10 });
const [score, setScore] = useState(0);

// move the snake
useEffect(() => {
const runSingleStep = () => {
setSnake((snake) => {
const head = snake[0];
const newHead = { x: head.x + direction.x, y: head.y + direction.y };
const [foods, setFoods] = useState([]);
const score = snake.length - 3;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as snake state is different from objects state,it makes sense to have a different function for checking snake.And it also makes the code more readable

  const isSnake = useCallback(
    ({ x, y }) =>
      snake.find((position) => position.x === x && position.y === y),
    [snake]
  );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I give an upvote to this. @owishiboo your function can work with all three types but loses simplicity. poison and food are similiar and we put them to one object state, but snake is different so we can keep it seperate fully.
well there is no right or wrong here, it comes down to your opinion.

// make a new snake by extending head
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
const newSnake = [newHead, ...snake];
// ?. is called optional chaining
const isFood = useCallback(
({ x, y }) =>
foods.find((position) => position.x === x && position.y === y),
[foods]
);

// remove tail
const isSnake = useCallback(
({ x, y }) =>
snake.find((position) => position.x === x && position.y === y),
[snake]
);
//restart the game
const resetGame = useCallback(() => {
setSnake(getDefaultSnake());
setDirection(Direction.Right);
setFoods([]);
}, []);
//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,
};

// make a new snake by extending head
const newSnake = [newHead, ...snake];

// remove tail when head doesnt eat food
if (!isFood(newHead)) {
newSnake.pop();
}
if (isSnake(newHead)) {
resetGame();
}

return newSnake;
});
};

runSingleStep();
const timer = setInterval(runSingleStep, 500);

return () => clearInterval(timer);
}, [direction, food]);
return newSnake;
});
}, [direction.x, direction.y, isFood, isSnake, resetGame]);

//add new food
const addFood = () => {
let newFood = getRandomCell();
while (isSnake(newFood) || isFood(newFood)) {
newFood = getRandomCell();
}
setFoods((currentFoods) => [...currentFoods, newFood]);
};
//remove food
const removeFood = useCallback(()=>{
setFoods((currentFoods)=> {
currentFoods.filter((food) => Date.now() - food.start <10000)
})
},[setFoods])
// 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();
setFoods((currentFoods) =>
currentFoods.filter((food) => food.x !== head.x && food.y !== head.y)
);
}
}, [isFood, snake]);

const UseInterval = (func, dir) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this outside of useSnake

const timer = useRef(Date.now());
const createCallback = useCallback(() => {
if (Date.now() - timer.current > dir) {
timer.current = Date.now();
func();
}
}, [dir, func]);

setFood(newFood);
}
}, [snake]);
useEffect(() => {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Corei13 why does removefood() doesn't work? it says 'Cannot read properties of undefined (reading 'find')'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@owishiboo because on line 136-138, you are setting setFoods(undefined). Why? Because you are calling setFoods(currentFoords => { expression }. Correct form would be either
a. setFoods(currentFoords => expression (without {} or
b. setFoods(currentFoords => { return expression }

const interval = setInterval(createCallback, 1000 / 60);
return () => clearInterval(interval);
}, [createCallback]);
};

UseInterval(addFood, 3000);
UseInterval(runSingleStep, 300);
UseInterval(removeFood,5000);

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;
}
};
Expand All @@ -144,13 +198,12 @@ 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);
return { score, isFood, isSnake };
};

//view
const Snake = () => {
const { score, isFood, isSnake } = UseSnake();
const cells = [];
for (let x = 0; x < Config.width; x++) {
for (let y = 0; y < Config.height; y++) {
Expand Down