Skip to content

Commit e85ce2a

Browse files
author
Dhananjoy
committedMar 21, 2022
Finish module #1 + extras
1 parent 691dd08 commit e85ce2a

File tree

2 files changed

+94
-82
lines changed

2 files changed

+94
-82
lines changed
 

‎pages/index.js

+90-82
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import dynamic from "next/dynamic";
2-
import { useEffect, useState, useCallback } from "react";
2+
import { useEffect, useState, useCallback, useRef } from "react";
33
import styles from "../styles/Snake.module.css";
44

55
const Config = {
@@ -21,7 +21,7 @@ const Direction = {
2121
Bottom: { x: 0, y: 1 },
2222
};
2323

24-
const Cell = ({ x, y, type }) => {
24+
const Cell = ({ x, y, type, remaining }) => {
2525
const getStyles = () => {
2626
switch (type) {
2727
case CellType.Snake:
@@ -33,18 +33,21 @@ const Cell = ({ x, y, type }) => {
3333

3434
case CellType.Food:
3535
return {
36-
backgroundColor: "darkorange",
36+
backgroundColor: "tomato",
3737
borderRadius: 20,
3838
width: 32,
3939
height: 32,
40+
transform: `scale(${0.5 + remaining / 20})`,
4041
};
4142

4243
default:
4344
return {};
4445
}
4546
};
47+
4648
return (
4749
<div
50+
key={`${x}-${y}`}
4851
className={styles.cellContainer}
4952
style={{
5053
left: x * Config.cellSize,
@@ -53,7 +56,9 @@ const Cell = ({ x, y, type }) => {
5356
height: Config.cellSize,
5457
}}
5558
>
56-
<div className={styles.cell} style={getStyles()}></div>
59+
<div className={styles.cell} style={getStyles()}>
60+
{remaining}
61+
</div>
5762
</div>
5863
);
5964
};
@@ -64,10 +69,25 @@ const getRandomCell = () => ({
6469
createdAt: Date.now(),
6570
});
6671

67-
const getInitialFoods = () => [{ x: 4, y: 10, createdAt: Date.now() }];
68-
6972
const getInitialDirection = () => Direction.Right;
7073

74+
const useInterval = (callback, duration) => {
75+
const time = useRef(0);
76+
77+
const wrappedCallback = useCallback(() => {
78+
// don't call callback() more than once within `duration`
79+
if (Date.now() - time.current >= duration) {
80+
time.current = Date.now();
81+
callback();
82+
}
83+
}, [callback, duration]);
84+
85+
useEffect(() => {
86+
const interval = setInterval(wrappedCallback, 1000 / 60);
87+
return () => clearInterval(interval);
88+
}, [wrappedCallback, duration]);
89+
};
90+
7191
const useSnake = () => {
7292
const getDefaultSnake = () => [
7393
{ x: 8, y: 12 },
@@ -79,7 +99,7 @@ const useSnake = () => {
7999
const [snake, setSnake] = useState(getDefaultSnake());
80100
const [direction, setDirection] = useState(getInitialDirection());
81101

82-
const [foods, setFoods] = useState(getInitialFoods());
102+
const [foods, setFoods] = useState([]);
83103

84104
const score = snake.length - 3;
85105

@@ -88,7 +108,7 @@ const useSnake = () => {
88108

89109
// resets the snake ,foods, direction to initial values
90110
const resetGame = useCallback(() => {
91-
setFoods(getInitialFoods());
111+
setFoods([]);
92112
setDirection(getInitialDirection());
93113
}, []);
94114

@@ -99,6 +119,19 @@ const useSnake = () => {
99119
);
100120
}, []);
101121

122+
// ?. is called optional chaining
123+
// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
124+
const isFood = useCallback(
125+
({ x, y }) => foods.some((food) => food.x === x && food.y === y),
126+
[foods]
127+
);
128+
129+
const isSnake = useCallback(
130+
({ x, y }) =>
131+
snake.find((position) => position.x === x && position.y === y),
132+
[snake]
133+
);
134+
102135
const addFood = useCallback(() => {
103136
let newFood = getRandomCell();
104137
while (isSnake(newFood) || isFood(newFood)) {
@@ -108,68 +141,46 @@ const useSnake = () => {
108141
}, [isFood, isSnake]);
109142

110143
// move the snake
111-
useEffect(() => {
112-
const runSingleStep = () => {
113-
setSnake((snake) => {
114-
const head = snake[0];
115-
116-
// 0 <= a % b < b
117-
// so new x will always be inside the grid
118-
const newHead = {
119-
x: (head.x + direction.x + Config.height) % Config.height,
120-
y: (head.y + direction.y + Config.width) % Config.width,
121-
};
122-
123-
// make a new snake by extending head
124-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
125-
const newSnake = [newHead, ...snake];
126-
127-
// reset the game if the snake hit itself
128-
if (isSnake(newHead)) {
129-
resetGame();
130-
return getDefaultSnake();
131-
}
132-
133-
// remove tail from the increased size snake
134-
// only if the newHead isn't a food
135-
if (!isFood(newHead)) {
136-
newSnake.pop();
137-
} else {
138-
setFoods((currentFoods) =>
139-
currentFoods.filter(
140-
(food) => !(food.x === newHead.x && food.y === newHead.y)
141-
)
142-
);
143-
}
144-
145-
return newSnake;
146-
});
147-
};
144+
const runSingleStep = useCallback(() => {
145+
setSnake((snake) => {
146+
const head = snake[0];
147+
148+
// 0 <= a % b < b
149+
// so new x will always be inside the grid
150+
const newHead = {
151+
x: (head.x + direction.x + Config.height) % Config.height,
152+
y: (head.y + direction.y + Config.width) % Config.width,
153+
};
154+
155+
// make a new snake by extending head
156+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
157+
const newSnake = [newHead, ...snake];
158+
159+
// reset the game if the snake hit itself
160+
if (isSnake(newHead)) {
161+
resetGame();
162+
return getDefaultSnake();
163+
}
148164

149-
runSingleStep();
150-
const timer = setInterval(runSingleStep, 500);
165+
// remove tail from the increased size snake
166+
// only if the newHead isn't a food
167+
if (!isFood(newHead)) {
168+
newSnake.pop();
169+
} else {
170+
setFoods((currentFoods) =>
171+
currentFoods.filter(
172+
(food) => !(food.x === newHead.x && food.y === newHead.y)
173+
)
174+
);
175+
}
151176

152-
return () => clearInterval(timer);
153-
}, [direction, foods, isFood, resetGame, isSnake]);
177+
return newSnake;
178+
});
179+
}, [direction, isFood, isSnake, resetGame]);
154180

155-
useEffect(() => {
156-
// add a food in a 3s interval
157-
const createFoodIntervalId = setInterval(() => {
158-
addFood();
159-
}, 3000);
160-
161-
// run the remove function each second,
162-
// but the function will decide which foods are
163-
// older than 10s and delete them
164-
const removeFoodIntervalId = setInterval(() => {
165-
removeFoods();
166-
}, 1000);
167-
168-
return () => {
169-
clearInterval(createFoodIntervalId);
170-
clearInterval(removeFoodIntervalId);
171-
};
172-
}, [addFood, removeFoods]);
181+
useInterval(runSingleStep, 200);
182+
useInterval(addFood, 3000);
183+
useInterval(removeFoods, 100);
173184

174185
useEffect(() => {
175186
const handleDirection = (direction, oppositeDirection) => {
@@ -204,29 +215,26 @@ const useSnake = () => {
204215
return () => window.removeEventListener("keydown", handleNavigation);
205216
}, []);
206217

207-
// ?. is called optional chaining
208-
// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
209-
const isFood = useCallback(
210-
({ x, y }) => foods.some((food) => food.x === x && food.y === y),
211-
[foods]
212-
);
213-
214-
const isSnake = useCallback(
215-
({ x, y }) =>
216-
snake.find((position) => position.x === x && position.y === y),
217-
[snake]
218-
);
219-
220218
const cells = [];
221219
for (let x = 0; x < Config.width; x++) {
222220
for (let y = 0; y < Config.height; y++) {
223-
let type = CellType.Empty;
221+
let type = CellType.Empty,
222+
remaining = undefined;
224223
if (isFood({ x, y })) {
225224
type = CellType.Food;
225+
remaining =
226+
10 -
227+
Math.round(
228+
(Date.now() -
229+
foods.find((food) => food.x === x && food.y === y).createdAt) /
230+
1000
231+
);
226232
} else if (isSnake({ x, y })) {
227233
type = CellType.Snake;
228234
}
229-
cells.push(<Cell key={`${x}-${y}`} x={x} y={y} type={type} />);
235+
cells.push(
236+
<Cell key={`${x}-${y}`} x={x} y={y} type={type} remaining={remaining} />
237+
);
230238
}
231239
}
232240

‎styles/Snake.module.css

+4
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,8 @@
3434
.cell {
3535
width: 100%;
3636
height: 100%;
37+
display: flex;
38+
justify-content: center;
39+
align-items: center;
40+
color: white;
3741
}

0 commit comments

Comments
 (0)