From 3556c47cb4a4c1404f94d449eed83e88a3359ee8 Mon Sep 17 00:00:00 2001 From: Shibi Suriya Date: Fri, 10 Nov 2023 09:36:36 +0530 Subject: [PATCH 1/2] fix: fix a bug with snake collision + implemented steps control --- packages/game-client/src/App.jsx | 24 ++++++---- packages/game-client/src/Game.jsx | 11 +++-- .../src/components/DirectionSelector.jsx | 47 +++++++++++++++++++ .../components/directionselector.module.css | 30 ++++++++++++ packages/game-client/src/constants.js | 4 +- packages/game-client/src/hooks/useSnakes.js | 5 +- 6 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 packages/game-client/src/components/DirectionSelector.jsx create mode 100644 packages/game-client/src/components/directionselector.module.css diff --git a/packages/game-client/src/App.jsx b/packages/game-client/src/App.jsx index 1269dc3..2177cd8 100644 --- a/packages/game-client/src/App.jsx +++ b/packages/game-client/src/App.jsx @@ -3,6 +3,7 @@ import Game from './Game'; import { Divider, Space, Checkbox, Flex, Select, Button } from 'antd'; import { stringToBoolean } from './utils'; import { allSnakesSelectOption } from './constants'; +import DirectionSelector from './components/DirectionSelector'; function App() { const [recordInLocalStorage, setRecordInLocalStorage] = useState( @@ -14,16 +15,15 @@ function App() { const gameRef = useRef(); const [snakeIdList, setSnakeIdList] = useState([allSnakesSelectOption]); - const [selectedSnake, setSelectedSnake] = useState(allSnakesSelectOption); + const [selectedSnake, setSelectedSnake] = useState(allSnakesSelectOption.id); - function updateSnakeIdList(ids) { - if (ids.length > 0) { - if (!ids.includes(selectedSnake) && selectedSnake !== allSnakesSelectOption) { - const [firstId] = ids; - setSelectedSnake(firstId); + function updateSnakeIdList(snakes) { + if (snakes.length > 0) { + if (!snakes.find(({ id }) => id === selectedSnake) && selectedSnake !== allSnakesSelectOption.id) { + const [{ id }] = snakes; + setSelectedSnake(id); } - - setSnakeIdList([allSnakesSelectOption].concat(ids)); + setSnakeIdList([allSnakesSelectOption].concat(snakes)); } else { setSelectedSnake(null); setSnakeIdList([]); @@ -74,7 +74,7 @@ function App() { onChange={(val) => { setSelectedSnake(val); }} - options={snakeIdList.map((id) => { + options={snakeIdList.map(({ id }) => { return { value: id, label: id }; })} /> @@ -90,6 +90,12 @@ function App() { + + {snakeIdList.map((snake, index) => { + const { id, headColor, bodyColor } = snake; + return ; + })} + { }); useEffect(() => { - updateSnakeIdList(Object.keys(snakes)); + updateSnakeIdList( + Object.entries(snakes).map(([key, value]) => { + const { bodyColor, headColor } = value; + return { id: key, bodyColor, headColor }; + }), + ); }, [snakes]); const { addSnakeToTrack, removeSnakeFromTracks, resetSnakeTrack } = useTicks({ @@ -64,9 +69,9 @@ const Game = forwardRef((props, ref) => { isGamePaused, }); - function nextMove(snakeId = allSnakesSelectOption) { + function nextMove(snakeId = allSnakesSelectOption.id) { if (snakeId) { - moveForward(snakeId === allSnakesSelectOption ? Object.keys(snakes) : [snakeId]); // Move all the snakes available one step forward. + moveForward(snakeId === allSnakesSelectOption.id ? Object.keys(snakes) : [snakeId]); // Move all the snakes available one step forward. } } diff --git a/packages/game-client/src/components/DirectionSelector.jsx b/packages/game-client/src/components/DirectionSelector.jsx new file mode 100644 index 0000000..12f7fef --- /dev/null +++ b/packages/game-client/src/components/DirectionSelector.jsx @@ -0,0 +1,47 @@ +import React, { Fragment, useState } from 'react'; +import { Radio, Row, Col, Space } from 'antd'; +import styles from './directionselector.module.css'; + +function DirectionSelector({ headColor, bodyColor, direction, updateDirection }) { + const [currentDirection, setCurrentDirection] = useState('north'); + return ( +
+
+
+
+
+
+ { + setCurrentDirection(e.target.value); + }} + value={currentDirection} + > + + +
+ W +
+ + +
+ N +
+ +
+ S +
+ + +
+ E +
+ +
+
+
+
+ ); +} + +export default DirectionSelector; diff --git a/packages/game-client/src/components/directionselector.module.css b/packages/game-client/src/components/directionselector.module.css new file mode 100644 index 0000000..2cd8919 --- /dev/null +++ b/packages/game-client/src/components/directionselector.module.css @@ -0,0 +1,30 @@ +.radio-button { + margin: 10px; + padding: 6px; +} + +.colors-container { + margin-top: 10px; + display: flex; + justify-content: center; + align-items: center; +} + +.head { + width: 50px; + height: 50px; +} + +.body { + width: 30px; + height: 30px; +} + +.id { + text-align: center; +} + +.container { + border: 1px solid black; + border-radius: 15px; +} diff --git a/packages/game-client/src/constants.js b/packages/game-client/src/constants.js index 9c2f4ac..0dd4786 100644 --- a/packages/game-client/src/constants.js +++ b/packages/game-client/src/constants.js @@ -5,7 +5,7 @@ const GRID_WIDTH = 30 * 13; const GRID_HEIGHT = 30 * 13; const CELL_DIMENSION = 30; -export const allSnakesSelectOption = 'All'; +export const allSnakesSelectOption = { id: 'All' }; if (GRID_HEIGHT % CELL_DIMENSION !== 0) { throw new Error('GRID_HEIGHT is not divislbe by CELL_DIMENSION'); @@ -49,7 +49,7 @@ const FOOD_TICKS = { // if they are unique. areValuesUnique(SNAKE_TICKS); -const DEFAULT_TRACK = SNAKE_TICKS.ONE.TYPE; +const DEFAULT_TRACK = SNAKE_TICKS.QUARTER.TYPE; const FOOD_EFFECTS = { GROW: 'grow', diff --git a/packages/game-client/src/hooks/useSnakes.js b/packages/game-client/src/hooks/useSnakes.js index 852dcf4..d43938b 100644 --- a/packages/game-client/src/hooks/useSnakes.js +++ b/packages/game-client/src/hooks/useSnakes.js @@ -121,7 +121,10 @@ const useSnakes = ({ // Remove the tail to make it look like the snake has moved forward. const tailKey = snakesRef.current[snakeId].list.pop(); // mutates. - delete snakesRef.current[snakeId].hash[tailKey]; + if (tailKey !== newHeadKey) { + // When snake bites its own tail... It survives! + delete snakesRef.current[snakeId].hash[tailKey]; + } } } else { throw new Error('The id mentioned is not in the hash!'); From a6abe8b098c93fab98193d4914ccc296d135c3c7 Mon Sep 17 00:00:00 2001 From: Shibi Suriya Date: Fri, 10 Nov 2023 10:23:46 +0530 Subject: [PATCH 2/2] fix: implemented direction wise step control --- packages/game-client/src/App.jsx | 22 ++++++++++++++++++- packages/game-client/src/Game.jsx | 12 ++++++++-- packages/game-client/src/app.module.css | 2 +- .../src/components/DirectionSelector.jsx | 15 ++++++------- .../game-client/src/hooks/useDirection.js | 12 +++++----- 5 files changed, 46 insertions(+), 17 deletions(-) diff --git a/packages/game-client/src/App.jsx b/packages/game-client/src/App.jsx index 2177cd8..2a35277 100644 --- a/packages/game-client/src/App.jsx +++ b/packages/game-client/src/App.jsx @@ -16,6 +16,7 @@ function App() { const [snakeIdList, setSnakeIdList] = useState([allSnakesSelectOption]); const [selectedSnake, setSelectedSnake] = useState(allSnakesSelectOption.id); + const [directions, setDirections] = useState({}); function updateSnakeIdList(snakes) { if (snakes.length > 0) { @@ -30,6 +31,15 @@ function App() { } } + function updateDirectionList(directions) { + setDirections(directions); + } + + function updateSnakeDirection(snakeId, direction) { + setSelectedSnake(snakeId); + gameRef.current.setDirection(snakeId, direction); + } + return ( @@ -93,7 +103,16 @@ function App() { {snakeIdList.map((snake, index) => { const { id, headColor, bodyColor } = snake; - return ; + return ( + + ); })} ); diff --git a/packages/game-client/src/Game.jsx b/packages/game-client/src/Game.jsx index 1d6dbfd..6778517 100644 --- a/packages/game-client/src/Game.jsx +++ b/packages/game-client/src/Game.jsx @@ -18,13 +18,16 @@ import styles from './app.module.css'; // } const Game = forwardRef((props, ref) => { - const { showCellId, isGamePaused, updateSnakeIdList } = props; + const { showCellId, isGamePaused, updateSnakeIdList, updateDirectionList } = props; const [snakeId, setSnakeId] = useState(1); // Keep the direction of the snakes inside useRef since we don't // want to force rerender of the component when the user changes // the direction. - const { getDirection, onLeft, onRight, onUp, onDown, setDirection } = useDirection(defaultDirections, 1); + const { getDirection, onLeft, onRight, onUp, onDown, setDirection, directions } = useDirection( + defaultDirections, + 1, + ); useInput({ snakes, onUp, onDown, onLeft, onRight, snakeId }); @@ -62,6 +65,10 @@ const Game = forwardRef((props, ref) => { ); }, [snakes]); + useEffect(() => { + updateDirectionList(directions); + }, [directions]); + const { addSnakeToTrack, removeSnakeFromTracks, resetSnakeTrack } = useTicks({ moveForward, spawnFood, @@ -86,6 +93,7 @@ const Game = forwardRef((props, ref) => { nextMove, prevMove, spawnFood, + setDirection, }; }, [], diff --git a/packages/game-client/src/app.module.css b/packages/game-client/src/app.module.css index a23a64a..9dff16d 100644 --- a/packages/game-client/src/app.module.css +++ b/packages/game-client/src/app.module.css @@ -1,6 +1,6 @@ .game { display: flex; - align-items: center; justify-content: center; + margin-top: 30px; height: 100vh; } diff --git a/packages/game-client/src/components/DirectionSelector.jsx b/packages/game-client/src/components/DirectionSelector.jsx index 12f7fef..649655a 100644 --- a/packages/game-client/src/components/DirectionSelector.jsx +++ b/packages/game-client/src/components/DirectionSelector.jsx @@ -2,8 +2,7 @@ import React, { Fragment, useState } from 'react'; import { Radio, Row, Col, Space } from 'antd'; import styles from './directionselector.module.css'; -function DirectionSelector({ headColor, bodyColor, direction, updateDirection }) { - const [currentDirection, setCurrentDirection] = useState('north'); +function DirectionSelector({ id, headColor, bodyColor, direction, updateSnakeDirection }) { return (
@@ -13,28 +12,28 @@ function DirectionSelector({ headColor, bodyColor, direction, updateDirection })
{ - setCurrentDirection(e.target.value); + updateSnakeDirection(id, e.target.value); }} - value={currentDirection} + value={direction} >
- W + W
- N + N
- S + S
- E + E
diff --git a/packages/game-client/src/hooks/useDirection.js b/packages/game-client/src/hooks/useDirection.js index 26564b2..d949a14 100644 --- a/packages/game-client/src/hooks/useDirection.js +++ b/packages/game-client/src/hooks/useDirection.js @@ -1,10 +1,11 @@ -import React, { useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { DIRECTIONS } from '../constants'; import { getOppositeDirection } from '../helpers'; import { cloneDeep } from 'lodash'; const useDirection = (initialState, snakeId) => { - const directions = useRef(cloneDeep({ ...initialState })); // useRef is changing the supplied object :( + const directionsRef = useRef(cloneDeep({ ...initialState })); // useRef is changing the supplied object :( + const [directions, setDirections] = useState(directionsRef.current); const onUp = () => { const direction = getDirection(snakeId); @@ -44,15 +45,16 @@ const useDirection = (initialState, snakeId) => { }; const setDirection = (snakeId, direction) => { - directions.current[snakeId] = direction; + directionsRef.current[snakeId] = direction; + setDirections({ ...directionsRef.current }); }; const getDirection = (snakeId) => { - return directions.current[snakeId]; + return directionsRef.current[snakeId]; }; return { - directions: directions.current, + directions, onUp, onDown, onLeft,