diff --git a/packages/game-client/src/App.jsx b/packages/game-client/src/App.jsx index 1269dc3..2a35277 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,22 +15,31 @@ function App() { const gameRef = useRef(); const [snakeIdList, setSnakeIdList] = useState([allSnakesSelectOption]); - const [selectedSnake, setSelectedSnake] = useState(allSnakesSelectOption); + const [selectedSnake, setSelectedSnake] = useState(allSnakesSelectOption.id); + const [directions, setDirections] = useState({}); - 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([]); } } + function updateDirectionList(directions) { + setDirections(directions); + } + + function updateSnakeDirection(snakeId, direction) { + setSelectedSnake(snakeId); + gameRef.current.setDirection(snakeId, direction); + } + return ( @@ -74,7 +84,7 @@ function App() { onChange={(val) => { setSelectedSnake(val); }} - options={snakeIdList.map((id) => { + options={snakeIdList.map(({ id }) => { return { value: id, label: id }; })} /> @@ -90,11 +100,27 @@ function App() { + + {snakeIdList.map((snake, index) => { + const { id, headColor, bodyColor } = snake; + return ( + + ); + })} + ); diff --git a/packages/game-client/src/Game.jsx b/packages/game-client/src/Game.jsx index 5d400d4..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 }); @@ -54,9 +57,18 @@ const Game = forwardRef((props, ref) => { }); useEffect(() => { - updateSnakeIdList(Object.keys(snakes)); + updateSnakeIdList( + Object.entries(snakes).map(([key, value]) => { + const { bodyColor, headColor } = value; + return { id: key, bodyColor, headColor }; + }), + ); }, [snakes]); + useEffect(() => { + updateDirectionList(directions); + }, [directions]); + const { addSnakeToTrack, removeSnakeFromTracks, resetSnakeTrack } = useTicks({ moveForward, spawnFood, @@ -64,9 +76,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. } } @@ -81,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 new file mode 100644 index 0000000..649655a --- /dev/null +++ b/packages/game-client/src/components/DirectionSelector.jsx @@ -0,0 +1,46 @@ +import React, { Fragment, useState } from 'react'; +import { Radio, Row, Col, Space } from 'antd'; +import styles from './directionselector.module.css'; + +function DirectionSelector({ id, headColor, bodyColor, direction, updateSnakeDirection }) { + return ( +
+
+
+
+
+
+ { + updateSnakeDirection(id, e.target.value); + }} + value={direction} + > + + +
+ 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/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, 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!');