Skip to content

Commit

Permalink
Merge pull request #26 from shibisuriya/feat/step-controls
Browse files Browse the repository at this point in the history
Feat/step controls
  • Loading branch information
shibisuriya authored Nov 10, 2023
2 parents 8f86f5d + a6abe8b commit f15da77
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 23 deletions.
44 changes: 35 additions & 9 deletions packages/game-client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 (
<Fragment>
<Space>
Expand Down Expand Up @@ -74,7 +84,7 @@ function App() {
onChange={(val) => {
setSelectedSnake(val);
}}
options={snakeIdList.map((id) => {
options={snakeIdList.map(({ id }) => {
return { value: id, label: id };
})}
/>
Expand All @@ -90,11 +100,27 @@ function App() {
</Space>
</Flex>
<Divider dashed />
<Flex justify="space-evenly">
{snakeIdList.map((snake, index) => {
const { id, headColor, bodyColor } = snake;
return (
<DirectionSelector
key={index}
id={id}
headColor={headColor}
bodyColor={bodyColor}
direction={directions[id]}
updateSnakeDirection={updateSnakeDirection}
/>
);
})}
</Flex>
<Game
ref={gameRef}
showCellId={showCellId}
isGamePaused={isGamePaused}
updateSnakeIdList={updateSnakeIdList}
updateDirectionList={updateDirectionList}
/>
</Fragment>
);
Expand Down
23 changes: 18 additions & 5 deletions packages/game-client/src/Game.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 });

Expand Down Expand Up @@ -54,19 +57,28 @@ 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,
getAllSnakeIds,
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.
}
}

Expand All @@ -81,6 +93,7 @@ const Game = forwardRef((props, ref) => {
nextMove,
prevMove,
spawnFood,
setDirection,
};
},
[],
Expand Down
2 changes: 1 addition & 1 deletion packages/game-client/src/app.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.game {
display: flex;
align-items: center;
justify-content: center;
margin-top: 30px;
height: 100vh;
}
46 changes: 46 additions & 0 deletions packages/game-client/src/components/DirectionSelector.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={styles.container}>
<div className={styles['colors-container']}>
<div className={styles.head} style={{ backgroundColor: headColor }}></div>
<div className={styles.body} style={{ backgroundColor: bodyColor }}></div>
</div>
<div>
<Radio.Group
onChange={(e) => {
updateSnakeDirection(id, e.target.value);
}}
value={direction}
>
<Row justify="center" align="middle">
<Col span={6}>
<div className={styles['radio-button']}>
<Radio.Button value={'left'}>W</Radio.Button>
</div>
</Col>
<Col span={6}>
<div className={styles['radio-button']}>
<Radio.Button value={'up'}>N</Radio.Button>
</div>

<div className={styles['radio-button']}>
<Radio.Button value={'down'}>S</Radio.Button>
</div>
</Col>
<Col span={6}>
<div className={styles['radio-button']}>
<Radio.Button value={'right'}>E</Radio.Button>
</div>
</Col>
</Row>
</Radio.Group>
</div>
</div>
);
}

export default DirectionSelector;
30 changes: 30 additions & 0 deletions packages/game-client/src/components/directionselector.module.css
Original file line number Diff line number Diff line change
@@ -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;
}
4 changes: 2 additions & 2 deletions packages/game-client/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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',
Expand Down
12 changes: 7 additions & 5 deletions packages/game-client/src/hooks/useDirection.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion packages/game-client/src/hooks/useSnakes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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!');
Expand Down

0 comments on commit f15da77

Please sign in to comment.