-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e55c7a0
commit 11b5296
Showing
10 changed files
with
845 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,27 @@ | ||
# multiplayer-snake-game | ||
# Multiplayer Snake Game | ||
|
||
Screenshot on PC | ||
|
||
 | ||
|
||
Screenshot on phone | ||
|
||
 | ||
|
||
## Run | ||
|
||
``` shell | ||
yarn install | ||
yarn start | ||
firefox http://localhost:8000 | ||
``` | ||
|
||
## Features | ||
|
||
- [X] simple, clear and responsive UI | ||
- [x] multiplayer and observers | ||
|
||
## History | ||
|
||
- Version 1.0.0a | ||
- alpha version |
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,22 @@ | ||
{ | ||
"name": "snake", | ||
"version": "1.0.0", | ||
"main": "server.js", | ||
"repository": "https://github.com/archtaurus/snake.git", | ||
"author": "Zhao Xin <7176466@qq.com>", | ||
"license": "MIT", | ||
"description": "multiplayer snake game with socket.io", | ||
"keywords": [ | ||
"socket.io", | ||
"multiplayer", | ||
"snake", | ||
"game" | ||
], | ||
"scripts": { | ||
"start": "npx nodemon server.js" | ||
}, | ||
"dependencies": { | ||
"express": "^4.17.1", | ||
"socket.io": "^4.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 |
---|---|---|
@@ -0,0 +1,68 @@ | ||
const client = io() | ||
const context = canvas.getContext('2d') | ||
const backgroudColor = '#231f20' | ||
const playerColor = '#ccc' | ||
const enemyColor = '#666' | ||
const player1Color = '#c33' | ||
const player2Color = '#33c' | ||
const appleColor = '#e66916' | ||
let canvasSize | ||
|
||
function drawGame(game) { | ||
const cellSize = canvasSize / game.grids | ||
context.fillStyle = backgroudColor | ||
context.fillRect(0, 0, canvasSize, canvasSize) | ||
|
||
const isPlayer = client.id == game.players[0].id || client.id == game.players[1].id | ||
|
||
context.fillStyle = isPlayer ? (game.players[0].id == client.id ? playerColor : enemyColor) : player1Color | ||
game.players[0].snake.map((cell) => context.fillRect(cellSize * cell.x, cellSize * cell.y, cellSize, cellSize)) | ||
|
||
context.fillStyle = isPlayer ? (game.players[1].id == client.id ? playerColor : enemyColor) : player2Color | ||
game.players[1].snake.map((cell) => context.fillRect(cellSize * cell.x, cellSize * cell.y, cellSize, cellSize)) | ||
|
||
context.fillStyle = appleColor | ||
context.fillRect(cellSize * game.apple.x, cellSize * game.apple.y, cellSize, cellSize) | ||
|
||
// if (!isPlayer) { | ||
// document.getElementById('player1score').innerText = game.players[0].snake.length | ||
// document.getElementById('player2score').innerText = game.players[1].snake.length | ||
// } | ||
|
||
if (isPlayer) { | ||
document.getElementById('player1score').classList.value = 'player' | ||
document.getElementById('player2score').classList.value = 'enemy' | ||
|
||
if (game.players[0].id == client.id) { | ||
document.getElementById('player1score').innerText = game.players[0].snake.length | ||
document.getElementById('player2score').innerText = game.players[1].snake.length | ||
} else { | ||
document.getElementById('player1score').innerText = game.players[1].snake.length | ||
document.getElementById('player2score').innerText = game.players[0].snake.length | ||
} | ||
} else { | ||
document.getElementById('player1score').classList.value = 'player1' | ||
document.getElementById('player1score').innerText = game.players[0].snake.length | ||
|
||
document.getElementById('player2score').classList.value = 'player2' | ||
document.getElementById('player2score').innerText = game.players[1].snake.length | ||
} | ||
// if (game.players[0].id == client.id) { | ||
// } else { | ||
// document.getElementById('player1score').innerText = game.players[1].snake.length | ||
// document.getElementById('player2score').innerText = game.players[0].snake.length | ||
// } | ||
|
||
if (!isPlayer) document.getElementById('controller').classList.add('hidden') | ||
} | ||
|
||
const resize = () => { | ||
canvasSize = canvas.height = canvas.width | ||
} | ||
|
||
window.addEventListener('resize', resize) | ||
window.addEventListener('keydown', (e) => client.emit('keydown', e.key)) | ||
|
||
client.on('game', (game) => drawGame(game)) | ||
|
||
resize() |
Binary file not shown.
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,25 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Multiplayer Snake Game</title> | ||
<link rel="stylesheet" href="/style.css" /> | ||
</head> | ||
<body> | ||
<h1>Multiplayer Snake Game</h1> | ||
<canvas id="canvas"></canvas> | ||
<p id="scores"> | ||
<span id="player1score">3</span> | ||
<span id="player2score">3</span> | ||
</p> | ||
<div id="controller"> | ||
<button id="up" onclick="client.emit('keydown', 'ArrowUp')">▲</button> | ||
<button id="left" onclick="client.emit('keydown', 'ArrowLeft')">◀</button> | ||
<button id="right" onclick="client.emit('keydown', 'ArrowRight')">▶</button> | ||
<button id="down" onclick="client.emit('keydown', 'ArrowDown')">▼</button> | ||
</div> | ||
<script src="/socket.io/socket.io.min.js"></script> | ||
<script src="/client.js"></script> | ||
</body> | ||
</html> |
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,105 @@ | ||
* { | ||
margin: 0px; | ||
padding: 0px; | ||
box-sizing: border-box; | ||
} | ||
|
||
body { | ||
padding: 8px; | ||
height: 100vh; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
color: #ccc; | ||
background-color: #333; | ||
} | ||
h1 { | ||
margin-top: 1em; | ||
} | ||
canvas { | ||
margin-top: 2em; | ||
width: 100%; | ||
max-width: 600px; | ||
border: 1px solid #ccc; | ||
} | ||
#scores { | ||
margin-top: 2em; | ||
max-width: 600px; | ||
width: 100%; | ||
display: flex; | ||
justify-content: space-around; | ||
gap: 8px; | ||
font-size: 1.5em; | ||
} | ||
|
||
#controller { | ||
width: 160px; | ||
height: 120px; | ||
margin-top: 2rem; | ||
display: none; | ||
/* gap: 8px; */ | ||
grid-template-areas: | ||
'. U U .' | ||
'L L R R' | ||
'. D D .'; | ||
} | ||
#controller button { | ||
font-size: 1.5em; | ||
border: 2px solid #ccc; | ||
background-color: #666; | ||
color: #ccc; | ||
border-radius: 100px; | ||
width: 50px; | ||
height: 50px; | ||
outline: none; | ||
} | ||
#controller button:hover { | ||
color: #666; | ||
background-color: yellowgreen; | ||
} | ||
|
||
#up { | ||
grid-area: U; | ||
} | ||
#left { | ||
grid-area: L; | ||
} | ||
#right { | ||
grid-area: R; | ||
} | ||
#down { | ||
grid-area: D; | ||
} | ||
.hidden { | ||
display: none !important; | ||
} | ||
@media only screen and (max-width: 600px) { | ||
body { | ||
justify-content: start !important; | ||
} | ||
h1 { | ||
font-size: 1.5em; | ||
} | ||
#controller { | ||
display: grid; | ||
} | ||
} | ||
|
||
#scores span { | ||
padding: 0.5rem 1rem; | ||
} | ||
|
||
.player1 { | ||
background-color: #c33; | ||
} | ||
.player2 { | ||
background-color: #33c; | ||
} | ||
.player { | ||
background-color: #ccc; | ||
color: #333; | ||
} | ||
.enemy { | ||
background-color: #666; | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,111 @@ | ||
const express = require('express') | ||
const app = express() | ||
app.use(express.static('public')) | ||
const host = process.env.HOST || '0.0.0.0' | ||
const port = process.env.PORT || 8000 | ||
const server = app.listen(port, host, () => console.log(`server started at http://${host}:${port}/`)) | ||
const io = require('socket.io')(server, { | ||
cors: { | ||
origin: '*', | ||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', | ||
preflightContinue: false, | ||
optionsSuccessStatus: 204, | ||
}, | ||
}) | ||
const game = { | ||
grids: 20, | ||
apple: { x: 10, y: 10 }, | ||
players: [ | ||
{ | ||
id: '', | ||
key: '', | ||
pos: { x: 3, y: 1 }, | ||
vel: { x: 1, y: 0 }, | ||
snake: [ | ||
{ x: 3, y: 1 }, | ||
{ x: 2, y: 1 }, | ||
{ x: 1, y: 1 }, | ||
], | ||
}, | ||
{ | ||
id: '', | ||
key: '', | ||
pos: { x: 16, y: 18 }, | ||
vel: { x: -1, y: 0 }, | ||
snake: [ | ||
{ x: 16, y: 18 }, | ||
{ x: 17, y: 18 }, | ||
{ x: 18, y: 18 }, | ||
], | ||
}, | ||
], | ||
} | ||
|
||
const player1 = game.players[0] | ||
const player2 = game.players[1] | ||
|
||
io.on('connection', (client) => { | ||
// 有空位即注册为玩家,否则是观众 | ||
if (!player1.id) player1.id = client.id | ||
else if (!player2.id) player2.id = client.id | ||
else return | ||
client.on('keydown', (key) => { | ||
if (player1.id == client.id) player1.key = key | ||
else player2.key = key | ||
}) | ||
client.on('disconnect', () => { | ||
if (player1.id == client.id) player1.id = '' | ||
else player2.id = '' | ||
}) | ||
}) | ||
|
||
function randomInRange(start, end) { | ||
return Math.floor(Math.random() * (end - start + 1) + start) | ||
} | ||
|
||
setInterval(() => { | ||
let reset = false | ||
|
||
if (player1.key == 'ArrowDown' && player1.vel.y == 0) player1.vel = { x: 0, y: 1 } | ||
else if (player1.key == 'ArrowUp' && player1.vel.y == 0) player1.vel = { x: 0, y: -1 } | ||
else if (player1.key == 'ArrowLeft' && player1.vel.x == 0) player1.vel = { x: -1, y: 0 } | ||
else if (player1.key == 'ArrowRight' && player1.vel.x == 0) player1.vel = { x: 1, y: 0 } | ||
player1.key = '' | ||
|
||
player1.pos.x = (player1.pos.x + player1.vel.x + game.grids) % game.grids | ||
player1.pos.y = (player1.pos.y + player1.vel.y + game.grids) % game.grids | ||
player1.snake.unshift({ x: player1.pos.x, y: player1.pos.y }) | ||
if (player1.pos.x != game.apple.x || player1.pos.y != game.apple.y) player1.snake.pop() | ||
else reset = true | ||
|
||
if ( | ||
player1.snake.length > 3 && | ||
(player1.snake.slice(1).some((c) => player1.pos.x == c.x && player1.pos.y == c.y) || | ||
player1.snake.slice(1).some((c) => player1.pos.x == c.x && player1.pos.y == c.y)) | ||
) | ||
player1.snake.pop() | ||
|
||
if (player2.key == 'ArrowDown' && player2.vel.y == 0) player2.vel = { x: 0, y: 1 } | ||
else if (player2.key == 'ArrowUp' && player2.vel.y == 0) player2.vel = { x: 0, y: -1 } | ||
else if (player2.key == 'ArrowLeft' && player2.vel.x == 0) player2.vel = { x: -1, y: 0 } | ||
else if (player2.key == 'ArrowRight' && player2.vel.x == 0) player2.vel = { x: 1, y: 0 } | ||
player2.key = '' | ||
player2.pos.x = (player2.pos.x + player2.vel.x + game.grids) % game.grids | ||
player2.pos.y = (player2.pos.y + player2.vel.y + game.grids) % game.grids | ||
player2.snake.unshift({ x: player2.pos.x, y: player2.pos.y }) | ||
if (player2.pos.x != game.apple.x || player2.pos.y != game.apple.y) player2.snake.pop() | ||
else reset = true | ||
if ( | ||
player2.snake.length > 3 && | ||
(player1.snake.slice(1).some((c) => player2.pos.x == c.x && player2.pos.y == c.y) || | ||
player2.snake.slice(1).some((c) => player2.pos.x == c.x && player2.pos.y == c.y)) | ||
) | ||
player2.snake.pop() | ||
|
||
if (reset) { | ||
game.apple.x = randomInRange(0, game.grids - 1) | ||
game.apple.y = randomInRange(0, game.grids - 1) | ||
} | ||
|
||
io.emit('game', game) | ||
}, 200) |
Oops, something went wrong.