A real-time multiplayer Pong game featuring Firebase-powered matchmaking, spectator support, and synchronized gameplay. Players join a queue system and are automatically matched for competitive Pong matches with live spectator viewing capabilities.
bong/
└── index.html # Complete multiplayer Pong game
const firebaseConfig = {
apiKey: "AIzaSyALJtZBGc540MEHi_e67P3qgOKJGxxbpiw",
authDomain: "joshbruceonline-pong.firebaseapp.com",
databaseURL: "https://joshbruceonline-pong-default-rtdb.europe-west1.firebasedatabase.app",
projectId: "joshbruceonline-pong",
storageBucket: "joshbruceonline-pong.appspot.com",
messagingSenderId: "296966054099",
appId: "1:296966054099:web:df08fa55967e7aa5253426"
};
// Firebase Realtime Database schema
{
"pong-game": {
"players": {
"[playerId]": {
"name": "Player ABC",
"score": 0
}
},
"games": {
"[gameId]": {
"player1": { "name": "Player 1", "score": 0 },
"player2": { "name": "Player 2", "score": 0 },
"gameStarted": false,
"ball": { "x": 300, "y": 300, "radius": 10, "dx": 2, "dy": 2 },
"paddle1": { "x": 10, "y": 250, "width": 10, "height": 100, "dy": 5, "score": 0 },
"paddle2": { "x": 580, "y": 250, "width": 10, "height": 100, "dy": 5, "score": 0 }
}
}
}
}
// Game configuration
const GAME_WIDTH = 600;
const GAME_HEIGHT = 600;
const WIN_SCORE = 11; // First to 11 points wins
const COUNTDOWN_DURATION = 3000; // 3-second countdown
const GAME_TIMEOUT = 60000; // 1-minute cleanup timer
// Ball movement and collision detection
function updateGameObjects(game) {
var ball = game.ball;
var paddle1 = game.paddle1;
var paddle2 = game.paddle2;
// Update ball position
ball.x += ball.dx;
ball.y += ball.dy;
// Paddle collision detection
if (ball.y + ball.radius > paddle1.y &&
ball.y - ball.radius < paddle1.y + paddle1.height &&
ball.dx < 0) {
ball.dx *= -1; // Reverse X direction
}
// Wall collision detection
if (ball.y + ball.radius > GAME_HEIGHT || ball.y - ball.radius < 0) {
ball.dy *= -1; // Reverse Y direction
}
// Scoring detection
if (ball.x - ball.radius < 0) {
paddle2.score++; // Player 2 scores
resetBall();
}
if (ball.x + ball.radius > GAME_WIDTH) {
paddle1.score++; // Player 1 scores
resetBall();
}
}
// Automatic player addition to queue
function addPlayer() {
var newPlayerRef = playersRef.push();
playerId = newPlayerRef.key;
newPlayerRef.set({
name: 'Player ' + playerId.substr(-3), // Generate display name
score: 0
});
}
// Matchmaking system
function dequeueAndJoinGame() {
playersRef.once('value', function(snapshot) {
var players = snapshot.val();
var playerIds = Object.keys(players);
var nextPlayers = playerIds.slice(0, 2); // Take first 2 players
if (nextPlayers.length === 2) {
createGameSession(nextPlayers, players);
startCountdown();
}
});
}
// Synchronized game loop
function gameLoop() {
gameRef.on('value', function(snapshot) {
var game = snapshot.val();
updateGameObjects(game); // Update physics
drawGameObjects(game); // Render frame
if (game.gameStarted) {
requestAnimationFrame(gameLoop); // Continue loop
}
});
}
// Keyboard controls for both players
function handleKeyDown(event) {
if (event.key === 'w' || event.key === 'ArrowUp') {
arrowUpKey = true;
} else if (event.key === 's' || event.key === 'ArrowDown') {
arrowDownKey = true;
}
}
// Player-specific paddle movement
if (playerId === 'player1') {
if (arrowUpKey && paddle1.y > 0) {
paddle1.y -= paddle1.dy; // Move up
}
if (arrowDownKey && paddle1.y + paddle1.height < GAME_HEIGHT) {
paddle1.y += paddle1.dy; // Move down
}
}
// Game object rendering
function drawGameObjects(game) {
var ball = game.ball;
var paddle1 = game.paddle1;
var paddle2 = game.paddle2;
// Clear previous frame
ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
// Draw ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = '#333';
ctx.fill();
ctx.closePath();
// Draw paddles
ctx.fillStyle = '#333';
ctx.fillRect(paddle1.x, paddle1.y, paddle1.width, paddle1.height);
ctx.fillRect(paddle2.x, paddle2.y, paddle2.width, paddle2.height);
// Draw scores
ctx.font = '30px Arial';
ctx.fillText(paddle1.score, GAME_WIDTH / 4, 50);
ctx.fillText(paddle2.score, (3 * GAME_WIDTH) / 4, 50);
}
/* Game layout */
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #222;
color: #fff;
font-family: Arial, sans-serif;
}
#game-container {
width: 600px;
height: 600px;
background-color: #f1f1f1;
float: left;
margin-right: 200px;
}
/* Countdown display */
#countdown {
font-size: 300px;
font-weight: bold;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: red;
z-index: 999;
display: none;
}
// Real-time spectator tracking
function updateSpectatorsList(players) {
spectatorsList.innerHTML = '';
for (var playerId in players) {
var listItem = document.createElement('li');
listItem.classList.add('spectator-item');
var playerName = players[playerId].name;
listItem.innerText = playerName;
spectatorsList.appendChild(listItem);
}
}
// Listen for player queue changes
playersRef.on('value', function(snapshot) {
var players = snapshot.val();
updateSpectatorsList(players); // Update spectator display
});
function startCountdown() {
var countdownElement = document.getElementById('countdown');
countdownElement.style.display = 'block';
var countdown = 3;
var countdownInterval = setInterval(function() {
if (countdown === 0) {
clearInterval(countdownInterval);
countdownElement.style.display = 'none';
gameRef.child('gameStarted').set(true); // Start game
startGame();
} else {
countdownElement.innerText = countdown;
countdown--;
}
}, 1000);
}
function endGame() {
gameStarted = false;
var winningPlayer = game.paddle1.score === WIN_SCORE ? 'Player 1' : 'Player 2';
alert(winningPlayer + ' wins!');
removePlayer();
// Cleanup game data after timeout
setTimeout(function() {
gamesRef.child(gameId).remove();
}, GAME_TIMEOUT);
}
// Optimized game loop with requestAnimationFrame
function gameLoop() {
gameRef.on('value', function(snapshot) {
var game = snapshot.val();
updateGameObjects(game);
drawGameObjects(game);
if (game.gameStarted) {
requestAnimationFrame(gameLoop); // 60fps targeting
}
});
}
// Efficient Firebase queries
playersRef.once('value', function(snapshot) {
// Single read for matchmaking
});
gameRef.on('value', function(snapshot) {
// Real-time game state updates
});
/* High contrast game elements */
.game-container {
background-color: #f1f1f1; /* Light background */
}
/* Clear paddle and ball visibility */
ctx.fillStyle = '#333'; /* Dark game objects */
Bong represents an impressive implementation of real-time multiplayer Pong with sophisticated Firebase integration. The automatic matchmaking, spectator system, and synchronized gameplay create an engaging competitive gaming experience. The technical architecture demonstrates strong understanding of real-time web applications and multiplayer game development.
Technical Rating: 8.7/10