Created
July 11, 2022 08:14
-
-
Save daveknights/91460eac5098a1cacd20203d56b73b7a to your computer and use it in GitHub Desktop.
Two player penalty shoot out game using Socket IO and P5 JS.
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
const socket = io(); | |
const gravity = 0.2; | |
let form, label, nameInput, joinBtn, playerName, joined, waiting, playerNames, result, resultText, playerLeaves; | |
let player, keeper, attempts, dropSpeed, ballWidth, ballX, ballShotX, ballY, ballShotY, shoot, goalie, goalieX, goalieY, checkSave; | |
const stripeData = [ | |
{colour: '#4e801c', y: 190, h: 80}, | |
{colour: '#679931', y: 270, h: 97}, | |
{colour: '#4e801c', y: 367, h: 110}, | |
{colour: '#679931', y: 477, h: 123} | |
]; | |
function preload() { | |
keeper = loadImage('gloves.png'); | |
} | |
function drawGoal() { | |
noFill(); | |
strokeWeight(10); | |
stroke(255); | |
rect(200, 20, 400, 180, 2); | |
} | |
function drawPitch() { | |
noStroke(); | |
for (const stripe of stripeData) { | |
fill(stripe.colour) | |
rect(0, stripe.y, 800, stripe.h); | |
} | |
fill('white'); | |
ellipse(400, 550, 30, 20); | |
stroke('white'); | |
strokeWeight(3); | |
line(0, 190, 800, 190); | |
strokeWeight(5); | |
line(120, 190, 100, 300); | |
line(680, 190, 700, 300); | |
strokeWeight(6); | |
line(100, 300, 700, 300); | |
} | |
function aimTarget() { | |
stroke(50,255,120); | |
strokeWeight(2); | |
noFill(); | |
circle(mouseX, mouseY, 28); | |
line(mouseX, mouseY - 18, mouseX, mouseY + 18); | |
line(mouseX - 18, mouseY, mouseX + 18, mouseY); | |
} | |
function ball(bX, bY, width) { | |
fill('white'); | |
strokeWeight(1); | |
stroke(210,210,210); | |
circle(bX, bY, width); | |
stroke(0); | |
} | |
function onTarget() { | |
if (ballShotX >= 225 && ballShotX <= 576 | |
&& ballShotY >= 45 && ballShotY < 170) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function isShotSaved(gkX, gkY) { | |
const ballDistance = dist(gkX, gkY, ballShotX, ballShotY); | |
let isAGoal; | |
checkSave = true; | |
// 44 is ball diameter/2 (19) + keeper diameter/2 (25) | |
if (ballDistance < 44 || onTarget() === false) { | |
isAGoal = false; | |
} else if (onTarget()) { | |
isAGoal = true; | |
} | |
!goalie && socket.emit('is a goal', [player, isAGoal]); | |
} | |
function scoreBoard(player, scoreX) { | |
const green = color(50, 255, 50); | |
const red = color(255, 75, 75); | |
fill('black'); | |
noStroke(); | |
textSize(20); | |
textAlign(LEFT); | |
text(playerNames[player], scoreX + 5, 30); | |
fill('white'); | |
stroke(0); | |
strokeWeight(1); | |
rect(scoreX + 5, 40, 110, 20); | |
for (let i = 0; i < 5; i++) { | |
let scoreColour = 'grey'; | |
if (attempts[player][i] !== undefined) { | |
scoreColour = attempts[player][i] === 'goal' ? green : red; | |
} | |
fill(scoreColour); | |
circle(scoreX += 20, 50, 10); | |
} | |
} | |
function drawScores() { | |
scoreBoard('one', 10); | |
scoreBoard('two', 670); | |
} | |
function setup() { | |
const canvas = createCanvas(800, 600); | |
canvas.position(innerWidth/2 - width/2); | |
// New player joining form | |
label = createElement('label', 'Enter your first name:'); | |
nameInput = createInput(); | |
nameInput.attribute('autofocus', true); | |
joinBtn = createButton('Join game'); | |
joinBtn.attribute('type', 'button'); | |
form = createElement('form'); | |
form.attribute('onsubmit', 'event.preventDefault();'); | |
form.child(label); | |
form.child(nameInput); | |
form.child(joinBtn); | |
form.position(innerWidth/2 - 240); | |
joinBtn.mouseClicked(joinBtnClicked); | |
///////////////////////////////////// | |
playerLeaves = false; | |
joined = false; | |
result = false; | |
dropSpeed = 3; | |
ballWidth = 60; | |
ballX = 400; | |
ballY = 525; | |
goalieX = width/2; | |
goalieY = 100; | |
shoot = false; | |
attempts = { | |
one: [], | |
two: [] | |
}; | |
socket.on('player number', num => { | |
player = num; | |
socket.emit('send player name', [player, playerName]); | |
}); | |
socket.on('is goalie', isGoalie => goalie = isGoalie); | |
socket.on('receive goalie position', ([x, y]) => { | |
goalieX = x; | |
goalieY = y; | |
}); | |
socket.on('waiting', () => waiting = true); | |
socket.on('start', names => { | |
waiting = false; | |
playerNames = names; | |
}); | |
socket.on('reset ball', () => { | |
ballWidth = 60; | |
ballX = 400; | |
ballY = 525; | |
checkSave = false; | |
}); | |
socket.on('receive ball target', ([x, y]) => { | |
ballShotX = x; | |
ballShotY = y; | |
shoot = true; | |
}); | |
socket.on('receive attempts', playerAttempts => { | |
attempts = playerAttempts; | |
console.log(player, goalie); | |
socket.emit('switch positions', [player, goalie ? false : true]); | |
}); | |
socket.on('receive scores', playerScores => { | |
const opponenet = player === 'one' ? 'two' : 'one'; | |
result = true; | |
switch (true) { | |
case playerScores[player] > playerScores[opponenet]: | |
resultText = `You won ${playerScores[player]} : ${playerScores[opponenet]}`; | |
break; | |
case playerScores[player] < playerScores[opponenet]: | |
resultText = `You lost ${playerScores[opponenet]} : ${playerScores[player]}`; | |
break; | |
default: | |
resultText = `It's a draw - ${playerScores[player]} : ${playerScores[opponenet]}`; | |
break; | |
} | |
}); | |
socket.on('player leaves', () => { | |
playerLeaves = true; | |
socket.emit('delete room'); | |
}); | |
} | |
function draw() { | |
switch (true) { | |
case (waiting === true): | |
background(200); | |
textSize(32); | |
textAlign(CENTER); | |
stroke(0); | |
text('Waiting for opponent...', width/2, 50); | |
break; | |
case result === true: | |
background(200); | |
fill(0); | |
textSize(32); | |
textAlign(CENTER); | |
text(resultText, width/2, 50); | |
break; | |
case playerLeaves === true: | |
background(200); | |
fill(0); | |
textSize(32); | |
textAlign(CENTER); | |
stroke(0); | |
text('Opponent has left the game', width/2, 50); | |
break; | |
case joined === true && waiting === false: | |
background(200); | |
drawGoal(); | |
drawPitch(); | |
image(keeper, goalieX - 25, goalieY - 25, 50, 50); | |
ball(ballX, ballY, ballWidth); | |
!goalie && aimTarget(); | |
drawScores(); | |
if ((floor(ballX) === floor(ballShotX) || ceil(ballX) === ceil(ballShotX)) && ballY < 165) { | |
!checkSave && isShotSaved(goalieX, goalieY); | |
// Ball drops to ground | |
ballY += dropSpeed; | |
dropSpeed += gravity; | |
ballY >= 165 && (ballY -= 8); | |
} else if ((floor(ballX) === floor(ballShotX) || ceil(ballX) === ceil(ballShotX)) && ballY > 164) { | |
// Ball rolls on ground | |
!checkSave && isShotSaved(goalieX, goalieY); | |
} | |
if (shoot === true) { | |
ballX = lerp(ballX, ballShotX, 0.1); | |
ballY = lerp(ballY, ballShotY, 0.1); | |
ballWidth = lerp(ballWidth, 38, 0.1); | |
if (floor(ballX) === floor(ballShotX) || ceil(ballX) === ceil(ballShotX)) { | |
shoot = false; | |
} | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
function joinBtnClicked() { | |
if (nameInput.value().length) { | |
socket.emit('setup'); | |
playerName = nameInput.value(); | |
form.remove(); | |
joined = true; | |
} | |
} | |
function mousePressed() { | |
joined === true && (!goalie && socket.emit('send ball target' , [mouseX, mouseY])); | |
} | |
function mouseMoved() { | |
goalie && socket.emit('send goalie position' , [mouseX, mouseY]); | |
} |
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
const express = require('express'); | |
const path = require('path'); | |
const http = require('http'); | |
const socketio = require('socket.io'); | |
const app = express(); | |
const server = http.createServer(app); | |
const io = socketio(server); | |
const PORT = process.env.PORT || 3000; | |
const rooms = {}; | |
const gameState = { | |
'one': { | |
name: '', | |
currentPosition: null, | |
attempts: [], | |
score: 0 | |
}, | |
'two': { | |
name: '', | |
currentPosition: null, | |
attempts: [], | |
score: 0 | |
}, | |
}; | |
let currentRoom = ''; | |
let numPlayers = 0; | |
const getRoom = socket => { | |
let thisRoom = ''; | |
for (const socketRoom of socket.rooms) { | |
if (socketRoom in rooms) { | |
thisRoom = socketRoom; | |
break; | |
} | |
} | |
return thisRoom; | |
}; | |
app.use(express.static(path.join(__dirname, 'public'))); | |
server.listen(PORT, () => console.log(`Server running on port ${PORT}`)) | |
io.on('connection', socket => { | |
socket.on('setup', () => { | |
if (numPlayers === 0) { | |
socket.join(socket.id); | |
currentRoom = socket.id; | |
rooms[socket.id] = {}; | |
rooms[socket.id].gameState = JSON.parse(JSON.stringify(gameState)); | |
rooms[socket.id].gameState.one.currentPosition = 'penalty taker'; | |
socket.emit('waiting'); | |
socket.emit('player number', 'one'); | |
socket.emit('is goalie', false); | |
} else if (numPlayers === 1) { | |
socket.join(currentRoom); | |
rooms[currentRoom].gameState.two.currentPosition = 'goalie'; | |
socket.emit('player number', 'two'); | |
socket.emit('is goalie', true); | |
currentRoom = ''; | |
} | |
numPlayers++; | |
}); | |
socket.on('send player name', ([number, name]) => { | |
const thisRoom = getRoom(socket); | |
rooms[thisRoom].gameState[number].name = name; | |
if(numPlayers === 2) { | |
io.to(thisRoom).emit('start', {'one': rooms[thisRoom].gameState.one.name, 'two': rooms[thisRoom].gameState.two.name}); | |
numPlayers = 0; | |
} | |
}); | |
socket.on('send goalie position', position => { | |
io.to(getRoom(socket)).emit('receive goalie position', position); | |
}); | |
socket.on('send ball target', target => { | |
io.to(getRoom(socket)).emit('receive ball target', target); | |
}); | |
socket.on('is a goal', ([player, hasScored]) => { | |
const thisRoom = getRoom(socket); | |
rooms[thisRoom].gameState[player].attempts.push(hasScored ? 'goal' : 'miss'); | |
hasScored && (rooms[thisRoom].gameState[player].score += 1); | |
io.to(thisRoom).emit('receive attempts', {'one': rooms[thisRoom].gameState.one.attempts, 'two': rooms[thisRoom].gameState.two.attempts}); | |
}); | |
socket.on('switch positions', ([player, switchGoalie]) => { | |
const thisRoom = getRoom(socket); | |
rooms[thisRoom].gameState[player].currentPosition = switchGoalie ? 'goalie' : 'penaly taker'; | |
setTimeout(() => { | |
socket.emit('is goalie', rooms[thisRoom].gameState[player].currentPosition === 'goalie' ? true : false); | |
io.to(thisRoom).emit('reset ball'); | |
if (rooms[thisRoom].gameState.one.attempts.length + rooms[thisRoom].gameState.two.attempts.length === 10) { | |
io.to(thisRoom).emit('receive scores', {'one': rooms[thisRoom].gameState.one.score, 'two': rooms[thisRoom].gameState.two.score}); | |
} | |
}, 2000); | |
}); | |
socket.on('disconnect', () => io.emit('player leaves')); | |
socket.on('delete room', () => delete rooms[getRoom(socket)]); | |
}); |
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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Penalties</title> | |
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.js"></script> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<script src="/socket.io/socket.io.js"></script> | |
<script src="sketch.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
form { | |
background: rgb(200,200,200); | |
font-family: Arial, Helvetica, sans-serif; | |
padding: 100px; | |
} | |
label { | |
display: block; | |
margin-bottom: 5px; | |
} | |
input { | |
border: solid 2px grey; | |
border-right: none; | |
height: 27px; | |
padding: 0 10px; | |
} | |
input:focus { | |
outline: none; | |
} | |
button { | |
background: #4e801c; | |
border: 0; | |
border-bottom: solid 1px #4e801c; | |
color: white; | |
cursor: pointer; | |
font-size: 1rem; | |
height: 31px; | |
padding: 0 20px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment