top of page

Live Pixel Occupation Game

This project is a cooperative multiplayer game where multiple users are divided into two teams (red and blue) competing to cover the largest area on a shared canvas. Neutral users can interfere by painting over the colored areas back to black.

Gameplay:

  • Team Assignment: At the start, all players are randomly assigned to either the red or blue team.

  • User Interface: Each user's video is displayed below their cursor.

  • Game Start: Players initiate the game by clicking the "Start Game" button.

  • Timer: A 1-minute timer dictates the game duration.

  • Objective: Team members click on the canvas to paint pixels in their team’s color.

  • Neutral Role: Non-team players can paint over any colored area to revert it to black.

  • Outcome: At the end of the game, an alert window displays the results based on the painted area.

Motivation: The inspiration for this project stems from my appreciation for co-op games, especially during the pandemic when such games helped maintain connections with friends despite physical distances. The experience of playing together as a team was invaluable, and I wanted to create a simple yet engaging game that allows users to build friendships through teamwork.


Future Enhancements:

  • Improved Game Start: Ensure the game begins only when all users have clicked the start button.

  • Audio Integration: Add sound effects to enhance the gameplay experience.

  • Complexity and Playfulness: Introduce more sophisticated mechanics and interactive elements to make the game more engaging.


Here are some design sketches:




Here is the code:

 

// index.html

<html> <head> <script type="text/javascript" src="/socket.io/socket.io.js"></script> <script type="text/javascript"> var socket = io.connect(); socket.on('connect', function() { console.log("Connected"); }); // set up color var color; socket.on('users', function(users) { for (let i = 0; i < users.length; i++) { console.log(users[i]); if (users[i].id == socket.id) { if (users[i].team == "blue") { console.log("blue"); color = "#0000FF"; } else if (users[i].team == "red") { console.log("red"); color = "#FF0000"; } else { console.log("viewer"); color = "#000000"; } } } }); // Receive users' videos socket.on('image', function(imagedata) { if (document.getElementById(imagedata.id) != null) { document.getElementById('theimage' + imagedata.id).src = imagedata.image; } }); window.addEventListener('load', function() { // get users' video let video = document.getElementById('thevideo'); let constraints = { audio: false, video: true } navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { video.srcObject = stream; video.onloadedmetadata = function(e) { video.play(); draw(); }; }) .catch(function(err) { /* Handle the error */ alert(err); }); // the canvas for user video var thecanvas = document.getElementById('mycanvas'); var context = thecanvas.getContext('2d'); let videoSize = 6; let videoFramerate = 5; // Draw Loop function draw() { context.clearRect(0, 0, thecanvas.width, thecanvas.height); context.drawImage(video, 0, 0, video.width / videoSize, video.height / videoSize); var dataToSend = { image: thecanvas.toDataURL(), id: socket.id }; socket.emit('image', dataToSend); } setInterval(function() { draw(); }, 1000 / videoFramerate); // As mouse moves, the data is sent to append the video under cursor document.body.addEventListener('mousemove', function(evt) { var dataToSend = { x: evt.clientX, y: evt.clientY, id: socket.id, color: color }; socket.emit('mouse', dataToSend); }); // main canvas for game var maincanvas = document.getElementById('maincanvas'); var maincontext = maincanvas.getContext('2d'); maincanvas.width = window.outerWidth; maincanvas.height = window.innerHeight - 50; maincontext.fillRect(0, 0, maincanvas.width, maincanvas.height); maincontext.fillStyle = color; // mouse click event. As user click on the canvas, the data is sent for drawing the square maincanvas.addEventListener('click', function(evt) { var canvasRect = maincanvas.getBoundingClientRect(); y = evt.clientY - canvasRect.top; x = evt.clientX - canvasRect.left; sendmouse(x, y, color); // send data draw1(x, y, color); // draw the square on main canvas }, false); var sendmouse = function(xval, yval, color) { socket.emit('othermouse', { x: xval, y: yval, color: color }); }; socket.on('othermouse', function(data) { odraw(data.x, data.y, data.color); }); var draw1 = function(xval, yval, color) { maincontext.fillStyle = color; maincontext.fillRect(xval, yval, 20, 20); }; var odraw = function(xval, yval, color) { maincontext.fillStyle = color; maincontext.fillRect(xval, yval, 20, 20); }; // As user clicks start socket.on('start', function() { maincontext.fillStyle = "#000000"; maincontext.fillRect(0, 0, maincanvas.width, maincanvas.height); var n = 60; var tm = setInterval(countDown, 1000); // display the 1 minute timer function countDown() { n--; if (n == 0) { clearInterval(tm); } document.getElementById("div_timer").innerHTML = n; } // set 1 minute timer and count red and blue setTimeout(function() { var redCount = 0; var blueCount = 0; var winner; document.getElementById("start").innerHTML = "restart"; var result = maincontext.getImageData(0, 0, maincanvas.width, maincanvas.height); for (var i = 0, n = result.data.length; i < n; i += 4) { var red = result.data[i]; var blue = result.data[i + 2]; if (red > blue) { redCount++; } if (blue > red) { blueCount++; } } if (blueCount > redCount) { winner = "Blue Wins"; console.log("blue wins"); } else if (blueCount == redCount) { winner = "It's a Tie"; console.log("tie"); } else { winner = "Red Wins"; console.log("red wins"); } alert("Here is Result\nBlue has: " + (blueCount / 400) + "\nRed has: " + (redCount / 400) + "\n" + winner); }, 60000); document.getElementById("start").innerHTML = "don't click/refresh"; }); }); // append the video under cursor socket.on('mouse', function(data) { let newDiv; if (document.getElementById(data.id) == null) { newDiv = document.createElement("div"); newDiv.id = data.id; newDiv.innerHTML = '<img id="theimage' + data.id + '" width="70" height="70" style="-webkit-transform: scaleX(-1);transform: scaleX(-1);">'; newDiv.style.position = "absolute"; newDiv.style.width = "70px"; newDiv.style.height = "70px"; newDiv.style.borderRadius = "10px"; newDiv.style.overflow = "hidden"; newDiv.style.backgroundColor = data.color; } else { newDiv = document.getElementById(data.id); } newDiv.style.top = data.y + 10; newDiv.style.left = data.x + 10; document.body.appendChild(newDiv); }); // check when the user is ready function Ready() { socket.emit("ready", true); } </script> </head> <body> <button id="start" style="background-color: #00FF00; border: none; color: black;padding: 8px 32px; text-align: center;display: inline-block;font-size: 16px;margin: 1px 1px;cursor: pointer;" onclick="Ready()">Start Game</button> <div id="div_timer" style="position:fixed; top:5; left:650; font-size:24; font-style: bold;">60 Seconds Timer</div> <video id="thevideo" width="640 " height="480 " muted hidden></video> <canvas id="maincanvas"></canvas> <canvas width="100 " height="100 " id="mycanvas" hidden></canvas> </body> </html>


 

// server.js

var fs = require('fs'); // Express is a node module for building HTTP servers var express = require('express'); var app = express(); // Tell Express to look in the "public" folder for any files first app.use(express.static('public')); // If the user just goes to the "route" / then run this function app.get('/', function(req, res) { res.send('Hello World!') }); // Here is the actual HTTP server // In this case, HTTPS (secure) server var https = require('https'); // Security options - key and certificate var options = { key: fs.readFileSync('star_itp_io.key'), cert: fs.readFileSync('star_itp_io.pem') }; var httpServer = https.createServer(options, app); httpServer.listen(443); var io = require('socket.io')(httpServer); var users = []; var numUsers = 0; var maxNumUsers = 4; var blue = 0; var red = 0; io.sockets.on('connection', function(socket) { console.log("We have a new client: " + socket.id); users.push(socket); numUsers++; let team; if (numUsers <= maxNumUsers) { if (blue < red || blue == red) { team = "blue"; blue++; } else { team = "red"; red++; } } else { team = "viewer"; } var userdata = []; var data = { id: socket.id, team: team, ready: false }; userdata.push(data); io.emit('users', userdata); socket.on('ready', function() { let everyoneReady = true; for (var i = 0; i < userdata.length; i++) { if (userdata[i].id == socket.id) { userdata[i].ready = true; } else { if (usedata[i].ready == false) { everyoneReady = false; } } } if (everyoneReady) { io.emit('start', ""); } }); socket.on('othermouse', function(data) { io.emit('othermouse', data); }); socket.on('mouse', function(data) { io.emit('mouse', data); }); socket.on('image', function(data) { io.emit('image', data); }); socket.on('disconnect', function() { console.log("Client has disconnected " + socket.id); numUsers--; for (var i = 0; i < userdata.length; i++) { if (userdata[i].id == socket.id) { if (userdata[i].team == "red") { red--; } if (userdata[i].team == "blue") { blue--; } } } console.log(red + " " + blue); }); } );



  • LinkedIn
  • Instagram
bottom of page