-
Notifications
You must be signed in to change notification settings - Fork 15
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
base: module-2
Are you sure you want to change the base?
Changes from all commits
bee70f2
cf27ed2
ec21c5a
9721681
66aec44
5c03c11
2b4d230
8e93356
48ca4d2
4ba03c3
b5a4648
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,18 @@ | ||
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, | ||
}; | ||
|
||
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,117 +64,212 @@ const Cell = ({ x, y, type }) => { | |
height: Config.cellSize, | ||
}} | ||
> | ||
<div className={styles.cell} style={getStyles()}></div> | ||
<div className={styles.cell} style={getStyles()}> | ||
{remaining} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @royantar0311 @MahdiMurshed Check this commit. Is it good? Do I need to modify anything? |
||
(position) => | ||
position.x === x && position.y === y && position.type === type | ||
); | ||
}; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]
); There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
// 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); | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we just want to know whether the cell contains an object or not, do we need to know the type too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My 'objects' array contains both poison and food. So, I need to find out whether an object is a food or poison when the snake eats it. I need to update the length and the score according to the type of the object. I've been using the function in this purpose, so I need it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you are taking full advantage of objects state There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isObject can be more simpler const isObject = useCallback(
({ x, y }) =>
objects.find((position) => position.x === x && position.y === y),
[objects]
); |
||
[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 | ||
) | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
}, []); | ||
|
||
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; | ||
} | ||
}; | ||
window.addEventListener("keydown", handleNavigation); | ||
|
||
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 = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @royantar0311 I forgot to add the remaining time to the food, so I tried it out today. But the time shows undefined. I can't figure out the problem :") |
||
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(<Cell key={`${x}-${y}`} x={x} y={y} type={type} />); | ||
cells.push( | ||
<Cell key={`${x}-${y}`} x={x} y={y} type={type} remaining={remaining} /> | ||
); | ||
} | ||
} | ||
return { score, isObjectOrSnake, cells }; | ||
}; | ||
|
||
//view | ||
const Snake = () => { | ||
const { score, cells } = GetSnake(); | ||
return ( | ||
<div className={styles.container}> | ||
<div | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doesn't it look cleaner ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using the same function for snake, food, and poison to find. So I think this is better -