1- import React , { Component } from 'react' ;
1+ import React from 'react' ;
22import cs from 'classnames' ;
33
44import './App.css' ;
@@ -33,171 +33,187 @@ const KEY_CODES_MAPPER = {
3333} ;
3434
3535const 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
4443const 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
5048const 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
7173const 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