Skip to content

Instantly share code, notes, and snippets.

@daveknights
Created July 11, 2022 08:14
Show Gist options
  • Save daveknights/91460eac5098a1cacd20203d56b73b7a to your computer and use it in GitHub Desktop.
Save daveknights/91460eac5098a1cacd20203d56b73b7a to your computer and use it in GitHub Desktop.
Two player penalty shoot out game using Socket IO and P5 JS.
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]);
}
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)]);
});
<!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>
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