Skip to content

Commit

Permalink
Merge pull request #13 from shibisuriya/feat/multiple-snakes
Browse files Browse the repository at this point in the history
Feat/multiple snakes
  • Loading branch information
shibisuriya authored Sep 29, 2023
2 parents 4fe51ed + 5e8ec34 commit ef0bb9b
Show file tree
Hide file tree
Showing 23 changed files with 7,774 additions and 449 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ jobs:
- name: Install dependencies and build
run: |
yarn install
yarn run build
yarn run build:client
- name: Deploy to gh-pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GH_DEPLOY }}
publish_dir: ./dist # Adjust the path to the build output directory
publish_dir: ./packages/game-client/dist # Adjust the path to the build output directory
4 changes: 4 additions & 0 deletions lerna.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "0.0.0"
}
38 changes: 14 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
{
"name": "js-webpack",
"name": "classic-slither.io",
"version": "1.0.0",
"description": "",
"main": "index.js",
"repository": "https://github.com/shibisuriya/classic-slither.io.git",
"author": "Shibi Suriya <[email protected]>",
"license": "MIT",
"scripts": {
"dev": "webpack serve",
"build": "webpack --mode production",
"prettify": "npx prettier --write '**/*.{js,jsx,ts,tsx,css}'",
"prepare": "husky install"
"prepare": "husky install",
"dev:client": "lerna run dev --scope=game-client",
"build:client": "lerna run build --scope=game-client",
"start:mock-server": "lerna run start --scope=mock-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"workspaces": [
"packages/*"
],
"private": true,
"dependencies": {},
"devDependencies": {
"@babel/core": "^7.23.0",
"@babel/preset-env": "^7.22.9",
"@babel/preset-react": "^7.22.5",
"babel-loader": "^9.1.3",
"css-loader": "^6.8.1",
"html-webpack-plugin": "^5.5.3",
"prettier": "^3.0.3",
"scss-loader": "^0.0.1",
"style-loader": "^3.3.3",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"husky": "^8.0.0",
"lerna": "^7.1.4",
"lint-staged": "^14.0.1"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,css}": "prettier --write"
}
Expand Down
File renamed without changes.
30 changes: 30 additions & 0 deletions packages/game-client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "game-client",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack serve",
"build": "webpack --mode production"
},
"keywords": [],
"author": "Shibi Suriya",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.23.0",
"@babel/preset-env": "^7.22.9",
"@babel/preset-react": "^7.22.5",
"babel-loader": "^9.1.3",
"css-loader": "^6.8.1",
"html-webpack-plugin": "^5.5.3",
"husky": "^8.0.0",
"style-loader": "^3.3.3",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
File renamed without changes.
259 changes: 259 additions & 0 deletions packages/game-client/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import React, { useState, useEffect, useRef } from 'react';
import { generateKey, getOppositeDirection } from './utils';
import { DIRECTIONS, NUMBER_OF_COLUMNS, NUMBER_OF_ROWS, DEFAULT_DIRECTION } from './constants';
import Grid from './Grid';

const SPEED = 1 * 100;

function App() {
const socket = useRef();
useEffect(() => {
// For now I am going to be using a simple websocket server for
// developer & testing purposes.
const { hostname, port } = window.location;
const WS_PORT = 8085;
socket.current = new WebSocket(`ws://${hostname}:${WS_PORT}`);
socket.current.addEventListener('open', (event) => {
console.log('WebSocket connection opened:', event);
});

socket.current.addEventListener('message', (event) => {
const message = event.data;
console.log('message -> ', message);
});

socket.current.addEventListener('close', (event) => {
console.log('WebSocket connection closed:', event);
});

socket.current.addEventListener('error', (event) => {
console.error('WebSocket error:', event);
});

return () => {
// Disconnect...
if (socket.current) {
socket.current.close();
}
};
}, []);

const [snakeId, setSnakeId] = useState(1);
// const playerId = useRef(2);

// 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 defaultDirections = {
1: DIRECTIONS.DOWN,
2: DIRECTIONS.RIGHT,
3: DIRECTIONS.RIGHT,
};

const directions = useRef(defaultDirections);

// Don't keep direction of the snakes insides of useState()...
const initialState = {
1: {
headColor: 'red',
bodyColor: 'black',
hash: {
'0-0': { x: 0, y: 0 },
'0-1': { x: 0, y: 1 },
'0-2': { x: 0, y: 2 },
'0-3': { x: 0, y: 3 },
'0-4': { x: 0, y: 4 },
'0-5': { x: 0, y: 5 },
'0-6': { x: 0, y: 6 },
},
list: ['0-6', '0-5', '0-4', '0-3', '0-2', '0-1', '0-0'],
},
2: {
headColor: 'blue',
bodyColor: 'yellow',
hash: {
'5-0': { x: 5, y: 0 },
'5-1': { x: 5, y: 1 },
'5-2': { x: 5, y: 2 },
'5-3': { x: 5, y: 3 },
'5-4': { x: 5, y: 4 },
'5-5': { x: 5, y: 5 },
'5-6': { x: 5, y: 6 },
'5-7': { x: 5, y: 7 },
},
list: ['5-7', '5-6', '5-5', '5-4', '5-3', '5-2', '5-1', '5-0'],
},
3: {
headColor: 'yellow',
bodyColor: 'black',
hash: {
'10-0': { x: 10, y: 0 },
'10-1': { x: 10, y: 1 },
'10-2': { x: 10, y: 2 },
'10-3': { x: 10, y: 3 },
'10-4': { x: 10, y: 4 },
'10-5': { x: 10, y: 5 },
},
list: ['10-5', '10-4', '10-3', '10-2', '10-1', '10-0'],
},
};
const [snakes, setSnakes] = useState(initialState);

const getDirection = (snakeId) => {
return directions.current[snakeId];
};

const moveSnakeForward = (snakeId) => {
setSnakes((prevSnakes) => {
const resetSnake = (snakeId) => {
setDirection(snakeId, defaultDirections[snakeId]); // Set to the default initial direction.
return { ...snakes, [snakeId]: initialState[snakeId] };
};

if (snakeId in prevSnakes) {
const updatedHash = { ...prevSnakes[snakeId].hash };
const updatedList = [...prevSnakes[snakeId].list];

// Remove tail.
const tailKey = updatedList.pop(); // mutates.
delete updatedHash[tailKey];

// Create new head using prev head.
const [headKey] = updatedList;
const head = updatedHash[headKey];

const direction = getDirection(snakeId);

let newHead;
let newHeadKey;
if (direction == DIRECTIONS.RIGHT) {
newHead = { x: head.x, y: head.y + 1 };
newHeadKey = generateKey(newHead.x, newHead.y);
updatedHash[newHeadKey] = newHead;
updatedList.unshift(newHeadKey);
} else if (direction == DIRECTIONS.UP) {
newHead = { x: head.x - 1, y: head.y };
newHeadKey = generateKey(newHead.x, newHead.y);
updatedHash[newHeadKey] = newHead;
updatedList.unshift(newHeadKey);
} else if (direction == DIRECTIONS.DOWN) {
newHead = { x: head.x + 1, y: head.y };
newHeadKey = generateKey(newHead.x, newHead.y);
updatedHash[newHeadKey] = newHead;
updatedList.unshift(newHeadKey);
} else if (direction == DIRECTIONS.LEFT) {
newHead = { x: head.x, y: head.y - 1 };
newHeadKey = generateKey(newHead.x, newHead.y);
updatedHash[newHeadKey] = newHead;
updatedList.unshift(newHeadKey);
}
if (newHeadKey in prevSnakes[snakeId].hash) {
// Snake collided with itself.
return resetSnake(snakeId);
} else if (
newHead.x < NUMBER_OF_ROWS &&
newHead.x >= 0 &&
newHead.y >= 0 &&
newHead.y < NUMBER_OF_COLUMNS
) {
// Snake moved.
return { ...prevSnakes, [snakeId]: { ...snakes[snakeId], hash: updatedHash, list: updatedList } };
} else {
// Snake collided with the wall...
return resetSnake(snakeId);
}
} else {
throw new Error('The id mentioned is not in the hash!');
}
});
};

const setDirection = (snakeId, direction) => {
directions.current[snakeId] = direction;
};

const up = (snakeId) => {
const direction = getDirection(snakeId);
if (direction == DIRECTIONS.UP) {
// moving up only.
return;
} else if (getOppositeDirection(direction) !== DIRECTIONS.UP) {
setDirection(snakeId, DIRECTIONS.UP);
}
};

const down = (snakeId) => {
const direction = getDirection(snakeId);
if (direction == DIRECTIONS.DOWN) {
return;
} else if (getOppositeDirection(direction) !== DIRECTIONS.DOWN) {
setDirection(snakeId, DIRECTIONS.DOWN);
}
};

const right = (snakeId) => {
const direction = getDirection(snakeId);
if (direction == DIRECTIONS.RIGHT) {
return;
} else if (getOppositeDirection(direction) !== DIRECTIONS.RIGHT) {
setDirection(snakeId, DIRECTIONS.RIGHT);
}
};

const left = () => {
const direction = getDirection(snakeId);
if (direction == DIRECTIONS.LEFT) {
return;
} else if (getOppositeDirection(direction) !== DIRECTIONS.LEFT) {
setDirection(snakeId, DIRECTIONS.LEFT);
}
};

useEffect(() => {
const timer = setInterval(() => {
moveSnakeForward(snakeId);
moveSnakeForward(snakeId + 1);
moveSnakeForward(snakeId + 2);
}, SPEED);
const abortController = new AbortController();

// Can only change the direction, won't make the snake move in
// a particular direction.
document.addEventListener(
'keydown',
(event) => {
const key = event.key.toLowerCase();
if (['w', 'arrowup'].includes(key)) {
up(snakeId);
} else if (['s', 'arrowdown'].includes(key)) {
down(snakeId);
} else if (['a', 'arrowleft'].includes(key)) {
left(snakeId);
} else if (['d', 'arrowright'].includes(key)) {
right(snakeId);
}
},
{ signal: abortController.signal },
);

return () => {
abortController.abort();
clearInterval(timer);
};
}, [snakes]);

return (
<div>
<select value={snakeId} onChange={(e) => setSnakeId(e.target.value)}>
{Object.keys(snakes).map((snakeId, index) => (
<option value={snakeId} key={index}>
{snakeId}
</option>
))}
</select>
<Grid snakes={snakes} />
</div>
);
}

export default App;
Loading

0 comments on commit ef0bb9b

Please sign in to comment.