-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from shibisuriya/feat/multiple-snakes
Feat/multiple snakes
- Loading branch information
Showing
23 changed files
with
7,774 additions
and
449 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
|
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.