Skip to content

Commit 8ea5bb6

Browse files
committed
from class component to hooks with useReducer
1 parent cdd62ac commit 8ea5bb6

File tree

1 file changed

+140
-122
lines changed

1 file changed

+140
-122
lines changed

src/App.js

Lines changed: 140 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { Component } from 'react';
1+
import React from 'react';
22
import cs from 'classnames';
33

44
import './App.css';
@@ -33,171 +33,187 @@ const KEY_CODES_MAPPER = {
3333
};
3434

3535
const getRandomNumberFromRange = (min, max) =>
36-
Math.floor(Math.random() * (max - min +1 ) + min);
36+
Math.floor(Math.random() * (max - min + 1) + min);
3737

38-
const getRandomCoordinate = () =>
39-
({
40-
x: getRandomNumberFromRange(1, GRID_SIZE - 1),
41-
y: getRandomNumberFromRange(1, GRID_SIZE - 1),
42-
});
38+
const getRandomCoordinate = () => ({
39+
x: getRandomNumberFromRange(1, GRID_SIZE - 1),
40+
y: getRandomNumberFromRange(1, GRID_SIZE - 1),
41+
});
4342

4443
const isBorder = (x, y) =>
4544
x === 0 || y === 0 || x === GRID_SIZE || y === GRID_SIZE;
4645

47-
const isPosition = (x, y, diffX, diffY) =>
48-
x === diffX && y === diffY;
46+
const isPosition = (x, y, diffX, diffY) => x === diffX && y === diffY;
4947

5048
const isSnake = (x, y, snakeCoordinates) =>
51-
snakeCoordinates.filter(coordinate => isPosition(coordinate.x, coordinate.y, x, y)).length;
49+
snakeCoordinates.filter(coordinate =>
50+
isPosition(coordinate.x, coordinate.y, x, y)
51+
).length;
5252

53-
const getSnakeHead = (snake) =>
54-
snake.coordinates[0];
53+
const getSnakeHead = snake => snake.coordinates[0];
5554

56-
const getSnakeWithoutStub = (snake) =>
55+
const getSnakeWithoutStub = snake =>
5756
snake.coordinates.slice(0, snake.coordinates.length - 1);
5857

59-
const getSnakeTail = (snake) =>
60-
snake.coordinates.slice(1);
58+
const getSnakeTail = snake => snake.coordinates.slice(1);
6159

62-
const getIsSnakeOutside = (snake) =>
60+
const getIsSnakeOutside = snake =>
6361
getSnakeHead(snake).x >= GRID_SIZE ||
6462
getSnakeHead(snake).y >= GRID_SIZE ||
6563
getSnakeHead(snake).x <= 0 ||
6664
getSnakeHead(snake).y <= 0;
6765

68-
const getIsSnakeClumy = (snake) =>
69-
isSnake(getSnakeHead(snake).x, getSnakeHead(snake).y, getSnakeTail(snake));
66+
const getIsSnakeClumy = snake =>
67+
isSnake(
68+
getSnakeHead(snake).x,
69+
getSnakeHead(snake).y,
70+
getSnakeTail(snake)
71+
);
7072

7173
const getIsSnakeEating = ({ snake, snack }) =>
72-
isPosition(getSnakeHead(snake).x, getSnakeHead(snake).y, snack.coordinate.x, snack.coordinate.y);
73-
74-
const getCellCs = (isGameOver, snake, snack, x, y) =>
75-
cs(
76-
'grid-cell',
77-
{
78-
'grid-cell-border': isBorder(x, y),
79-
'grid-cell-snake': isSnake(x, y, snake.coordinates),
80-
'grid-cell-snack': isPosition(x, y, snack.coordinate.x, snack.coordinate.y),
81-
'grid-cell-hit': isGameOver && isPosition(x, y, getSnakeHead(snake).x, getSnakeHead(snake).y),
82-
}
74+
isPosition(
75+
getSnakeHead(snake).x,
76+
getSnakeHead(snake).y,
77+
snack.coordinate.x,
78+
snack.coordinate.y
8379
);
8480

85-
const applySnakePosition = (prevState) => {
86-
const isSnakeEating = getIsSnakeEating(prevState);
87-
88-
const snakeHead = DIRECTION_TICKS[prevState.playground.direction](
89-
getSnakeHead(prevState.snake).x,
90-
getSnakeHead(prevState.snake).y,
91-
);
81+
const getCellCs = (isGameOver, snake, snack, x, y) =>
82+
cs('grid-cell', {
83+
'grid-cell-border': isBorder(x, y),
84+
'grid-cell-snake': isSnake(x, y, snake.coordinates),
85+
'grid-cell-snack': isPosition(
86+
x,
87+
y,
88+
snack.coordinate.x,
89+
snack.coordinate.y
90+
),
91+
'grid-cell-hit':
92+
isGameOver &&
93+
isPosition(x, y, getSnakeHead(snake).x, getSnakeHead(snake).y),
94+
});
9295

93-
const snakeTail = isSnakeEating
94-
? prevState.snake.coordinates
95-
: getSnakeWithoutStub(prevState.snake);
96-
97-
const snackCoordinate = isSnakeEating
98-
? getRandomCoordinate()
99-
: prevState.snack.coordinate;
100-
101-
return {
102-
snake: {
103-
coordinates: [snakeHead, ...snakeTail],
104-
},
105-
snack: {
106-
coordinate: snackCoordinate,
107-
},
108-
};
96+
const reducer = (state, action) => {
97+
switch (action.type) {
98+
case 'SNAKE_CHANGE_DIRECTION':
99+
return {
100+
...state,
101+
playground: {
102+
...state.playground,
103+
direction: action.direction,
104+
},
105+
};
106+
case 'SNAKE_MOVE':
107+
const isSnakeEating = getIsSnakeEating(state);
108+
109+
const snakeHead = DIRECTION_TICKS[state.playground.direction](
110+
getSnakeHead(state.snake).x,
111+
getSnakeHead(state.snake).y
112+
);
113+
114+
const snakeTail = isSnakeEating
115+
? state.snake.coordinates
116+
: getSnakeWithoutStub(state.snake);
117+
118+
const snackCoordinate = isSnakeEating
119+
? getRandomCoordinate()
120+
: state.snack.coordinate;
121+
122+
return {
123+
...state,
124+
snake: {
125+
coordinates: [snakeHead, ...snakeTail],
126+
},
127+
snack: {
128+
coordinate: snackCoordinate,
129+
},
130+
};
131+
case 'GAME_OVER':
132+
return {
133+
...state,
134+
playground: {
135+
...state.playground,
136+
isGameOver: true,
137+
},
138+
};
139+
default:
140+
throw new Error();
141+
}
109142
};
110143

111-
const applyGameOver = (prevState) => ({
144+
const initialState = {
112145
playground: {
113-
isGameOver: true
146+
direction: DIRECTIONS.RIGHT,
147+
isGameOver: false,
114148
},
115-
});
116-
117-
const doChangeDirection = (direction) => () => ({
118-
playground: {
119-
direction,
149+
snake: {
150+
coordinates: [getRandomCoordinate()],
120151
},
121-
});
152+
snack: {
153+
coordinate: getRandomCoordinate(),
154+
},
155+
};
122156

123-
class App extends Component {
124-
constructor(props) {
125-
super(props);
126-
127-
this.state = {
128-
playground: {
129-
direction: DIRECTIONS.RIGHT,
130-
isGameOver: false,
131-
},
132-
snake: {
133-
coordinates: [getRandomCoordinate()],
134-
},
135-
snack: {
136-
coordinate: getRandomCoordinate(),
137-
}
138-
};
139-
}
157+
const App = () => {
158+
const [state, dispatch] = React.useReducer(reducer, initialState);
140159

141-
componentDidMount() {
142-
this.interval = setInterval(this.onTick, TICK_RATE);
160+
const onChangeDirection = event => {
161+
if (KEY_CODES_MAPPER[event.keyCode]) {
162+
dispatch({
163+
type: 'SNAKE_CHANGE_DIRECTION',
164+
direction: KEY_CODES_MAPPER[event.keyCode],
165+
});
166+
}
167+
};
143168

144-
window.addEventListener('keyup', this.onChangeDirection, false);
145-
}
169+
React.useEffect(() => {
170+
window.addEventListener('keyup', onChangeDirection, false);
146171

147-
componentWillUnmount() {
148-
clearInterval(this.interval);
172+
return () =>
173+
window.removeEventListener('keyup', onChangeDirection, false);
174+
}, []);
149175

150-
window.removeEventListener('keyup', this.onChangeDirection, false);
151-
}
176+
React.useEffect(() => {
177+
const onTick = () => {
178+
getIsSnakeOutside(state.snake) || getIsSnakeClumy(state.snake)
179+
? dispatch({ type: 'GAME_OVER' })
180+
: dispatch({ type: 'SNAKE_MOVE' });
181+
};
152182

153-
onChangeDirection = (event) => {
154-
if (KEY_CODES_MAPPER[event.keyCode]) {
155-
this.setState(doChangeDirection(KEY_CODES_MAPPER[event.keyCode]));
156-
}
157-
}
183+
const interval = setInterval(onTick, TICK_RATE);
158184

159-
onTick = () => {
160-
getIsSnakeOutside(this.state.snake) || getIsSnakeClumy(this.state.snake)
161-
? this.setState(applyGameOver)
162-
: this.setState(applySnakePosition);
163-
}
185+
return () => clearInterval(interval);
186+
}, [state]);
164187

165-
render() {
166-
const {
167-
snake,
168-
snack,
169-
playground,
170-
} = this.state;
171-
172-
return (
173-
<div className="app">
174-
<h1>Snake!</h1>
175-
<Grid
176-
snake={snake}
177-
snack={snack}
178-
isGameOver={playground.isGameOver}
179-
/>
180-
</div>
181-
);
182-
}
183-
}
188+
return (
189+
<div className="app">
190+
<h1>Snake!</h1>
191+
<Grid
192+
snake={state.snake}
193+
snack={state.snack}
194+
isGameOver={state.playground.isGameOver}
195+
/>
196+
</div>
197+
);
198+
};
184199

185-
const Grid = ({ isGameOver, snake, snack }) =>
200+
const Grid = ({ isGameOver, snake, snack }) => (
186201
<div>
187-
{GRID.map(y =>
202+
{GRID.map(y => (
188203
<Row
189204
y={y}
190205
key={y}
191206
snake={snake}
192207
snack={snack}
193208
isGameOver={isGameOver}
194209
/>
195-
)}
210+
))}
196211
</div>
212+
);
197213

198-
const Row = ({ isGameOver, snake, snack, y }) =>
214+
const Row = ({ isGameOver, snake, snack, y }) => (
199215
<div className="grid-row">
200-
{GRID.map(x =>
216+
{GRID.map(x => (
201217
<Cell
202218
x={x}
203219
y={y}
@@ -206,10 +222,12 @@ const Row = ({ isGameOver, snake, snack, y }) =>
206222
snack={snack}
207223
isGameOver={isGameOver}
208224
/>
209-
)}
225+
))}
210226
</div>
227+
);
211228

212-
const Cell = ({ isGameOver, snake, snack, x, y }) =>
229+
const Cell = ({ isGameOver, snake, snack, x, y }) => (
213230
<div className={getCellCs(isGameOver, snake, snack, x, y)} />
231+
);
214232

215-
export default App;
233+
export default App;

0 commit comments

Comments
 (0)