1
- import React , { Component } from 'react' ;
1
+ import React from 'react' ;
2
2
import cs from 'classnames' ;
3
3
4
4
import './App.css' ;
@@ -33,171 +33,187 @@ const KEY_CODES_MAPPER = {
33
33
} ;
34
34
35
35
const getRandomNumberFromRange = ( min , max ) =>
36
- Math . floor ( Math . random ( ) * ( max - min + 1 ) + min ) ;
36
+ Math . floor ( Math . random ( ) * ( max - min + 1 ) + min ) ;
37
37
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
+ } ) ;
43
42
44
43
const isBorder = ( x , y ) =>
45
44
x === 0 || y === 0 || x === GRID_SIZE || y === GRID_SIZE ;
46
45
47
- const isPosition = ( x , y , diffX , diffY ) =>
48
- x === diffX && y === diffY ;
46
+ const isPosition = ( x , y , diffX , diffY ) => x === diffX && y === diffY ;
49
47
50
48
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 ;
52
52
53
- const getSnakeHead = ( snake ) =>
54
- snake . coordinates [ 0 ] ;
53
+ const getSnakeHead = snake => snake . coordinates [ 0 ] ;
55
54
56
- const getSnakeWithoutStub = ( snake ) =>
55
+ const getSnakeWithoutStub = snake =>
57
56
snake . coordinates . slice ( 0 , snake . coordinates . length - 1 ) ;
58
57
59
- const getSnakeTail = ( snake ) =>
60
- snake . coordinates . slice ( 1 ) ;
58
+ const getSnakeTail = snake => snake . coordinates . slice ( 1 ) ;
61
59
62
- const getIsSnakeOutside = ( snake ) =>
60
+ const getIsSnakeOutside = snake =>
63
61
getSnakeHead ( snake ) . x >= GRID_SIZE ||
64
62
getSnakeHead ( snake ) . y >= GRID_SIZE ||
65
63
getSnakeHead ( snake ) . x <= 0 ||
66
64
getSnakeHead ( snake ) . y <= 0 ;
67
65
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
+ ) ;
70
72
71
73
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
83
79
) ;
84
80
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
+ } ) ;
92
95
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
+ }
109
142
} ;
110
143
111
- const applyGameOver = ( prevState ) => ( {
144
+ const initialState = {
112
145
playground : {
113
- isGameOver : true
146
+ direction : DIRECTIONS . RIGHT ,
147
+ isGameOver : false ,
114
148
} ,
115
- } ) ;
116
-
117
- const doChangeDirection = ( direction ) => ( ) => ( {
118
- playground : {
119
- direction,
149
+ snake : {
150
+ coordinates : [ getRandomCoordinate ( ) ] ,
120
151
} ,
121
- } ) ;
152
+ snack : {
153
+ coordinate : getRandomCoordinate ( ) ,
154
+ } ,
155
+ } ;
122
156
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 ) ;
140
159
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
+ } ;
143
168
144
- window . addEventListener ( 'keyup' , this . onChangeDirection , false ) ;
145
- }
169
+ React . useEffect ( ( ) => {
170
+ window . addEventListener ( 'keyup' , onChangeDirection , false ) ;
146
171
147
- componentWillUnmount ( ) {
148
- clearInterval ( this . interval ) ;
172
+ return ( ) =>
173
+ window . removeEventListener ( 'keyup' , onChangeDirection , false ) ;
174
+ } , [ ] ) ;
149
175
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
+ } ;
152
182
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 ) ;
158
184
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 ] ) ;
164
187
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
+ } ;
184
199
185
- const Grid = ( { isGameOver, snake, snack } ) =>
200
+ const Grid = ( { isGameOver, snake, snack } ) => (
186
201
< div >
187
- { GRID . map ( y =>
202
+ { GRID . map ( y => (
188
203
< Row
189
204
y = { y }
190
205
key = { y }
191
206
snake = { snake }
192
207
snack = { snack }
193
208
isGameOver = { isGameOver }
194
209
/>
195
- ) }
210
+ ) ) }
196
211
</ div >
212
+ ) ;
197
213
198
- const Row = ( { isGameOver, snake, snack, y } ) =>
214
+ const Row = ( { isGameOver, snake, snack, y } ) => (
199
215
< div className = "grid-row" >
200
- { GRID . map ( x =>
216
+ { GRID . map ( x => (
201
217
< Cell
202
218
x = { x }
203
219
y = { y }
@@ -206,10 +222,12 @@ const Row = ({ isGameOver, snake, snack, y }) =>
206
222
snack = { snack }
207
223
isGameOver = { isGameOver }
208
224
/>
209
- ) }
225
+ ) ) }
210
226
</ div >
227
+ ) ;
211
228
212
- const Cell = ( { isGameOver, snake, snack, x, y } ) =>
229
+ const Cell = ( { isGameOver, snake, snack, x, y } ) => (
213
230
< div className = { getCellCs ( isGameOver , snake , snack , x , y ) } />
231
+ ) ;
214
232
215
- export default App ;
233
+ export default App ;
0 commit comments