"use strict"; var boardRatio = 1.5; var ballRadius = 0.01, padWidth = 0.01, padHeight = 0.15, padX = 0.01; var padBaseSpeed = 0.8; var gameId; var cvs, ctx, cvsW, cvsH, bdW, bdH, bdltX, bdltY; var socket; var playing = false, flip = false; var ballX, ballY, ballVX, ballVY; var padPos, padVel, pad2Pos, pad2Vel; function redraw() { ctx.fillStyle = "#000000"; ctx.fillRect(0, 0, cvsW, cvsH); redrawFast(); } function calcBallX() {return bdltX + bdW * (flip ? 1 - ballX : ballX);} function calcBallY() {return bdltY + bdH * ballY;} function calcPadY() {return bdltY + bdH * ((flip ? pad2Pos : padPos) - padHeight / 2);} function calcPad2Y() {return bdltY + bdH * ((flip ? padPos : pad2Pos) - padHeight / 2);} function redrawFast() { ctx.strokeStyle = "#ffffff"; ctx.lineWidth = 2; ctx.strokeRect(bdltX - 1, bdltY - 1, bdW + 2, bdH + 2); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(bdltX + bdW / 2 | 0, bdltY); ctx.lineTo(bdltX + bdW / 2 | 0, bdltY + bdH); ctx.stroke(); if (playing) { ctx.fillStyle = "#ffffff"; ctx.beginPath(); ctx.arc(calcBallX(), calcBallY(), ballRadius * bdW, 0, 2 * Math.PI); ctx.rect(bdltX + bdW * padX, calcPadY(), bdW * padWidth, bdH * padHeight); ctx.rect(bdltX + bdW * (1 - padX - padWidth), calcPad2Y(), bdW * padWidth, bdH * padHeight); ctx.fill(); } } function undrawElements() { ctx.fillStyle = "#000000"; ctx.beginPath(); ctx.rect( calcBallX() - ballRadius * bdW - 2, calcBallY() - ballRadius * bdW - 2, 2 * ballRadius * bdW + 4, 2 * ballRadius * bdW + 4); ctx.rect( bdltX + bdW * padX - 2, calcPadY() - 2, bdW * padWidth + 4, bdH * padHeight + 4); ctx.rect( bdltX + bdW * (1 - padX - padWidth) - 2, calcPad2Y() - 2, bdW * padWidth + 4, bdH * padHeight + 4); ctx.fill(); } function resizeHandler() { var rect = cvs.getBoundingClientRect(); cvsW = cvs.width = rect.width; cvsH = cvs.height = rect.height; // I believe this stuff is correct. if (cvsH * boardRatio > cvsW) { bdW = cvsW; bdH = bdW / boardRatio | 0; bdltX = 2; bdltY = (cvsH - bdH) / 2 + 2 | 0; bdW -= 4; bdH -= 4; } else { bdH = cvsH; bdW = boardRatio * bdH | 0; bdltX = (cvsW - bdW) / 2 + 2 | 0; bdltY = 2; bdW -= 4; bdH -= 4; } redraw(); } function displayStatus(msg) { document.getElementById("status").innerHTML = msg; } function displayScore(score) { document.getElementById("score").classList.remove("invisible"); document.getElementById("score-left").innerHTML = score[0]; document.getElementById("score-right").innerHTML = score[1]; setTimeout(function() { document.getElementById("score").classList.add("invisible"); }, 2000); } function openGame() { socket = io(); socket.on("redirect", function(url) { location.href = url; }); socket.on("status", function(msg) { displayStatus(msg); }); socket.on("score", function(score) { displayScore(score); }); socket.on("serve", function(side, x, y) { playing = true; flip = side == "right"; ballX = x; ballY = y; padPos = pad2Pos = 0.5; redraw(); }); socket.on("stop", function() { playing = false; stopPhysicsLoop(); stopGraphicsLoop(); redraw(); }); socket.on("startBall", function() { initPhysics(); ballVX = -0.5 / boardRatio; ballVY = -0.5; startPhysicsLoop(); startGraphicsLoop(); }); socket.on("start", function() { initPhysics(); ballVX = 0.5 / boardRatio; ballVY = -0.5; startPhysicsLoop(); startGraphicsLoop(); }); socket.on("padvec", function(pos, vel) { pad2Pos = pos; pad2Vel = vel; }); socket.on("ballvec", function(x, y, vx, vy) { ballX = x; ballY = y; ballVX = vx; ballVY = vy; }); socket.emit("open", gameId); } var graphicsLoopId = null, physicsLoopId = null; function startGraphicsLoop() { // undrawElements(); redraw(); // TODO: redrawFast graphicsLoopId = requestAnimationFrame(startGraphicsLoop); } function stopGraphicsLoop() { if (graphicsLoopId == null) return; cancelAnimationFrame(graphicsLoopId); graphicsLoopId = null; } function startPhysicsLoop() { var interval = 1 / 120; physicsLoopId = setInterval(function() { if (advancePhysics(interval)) { socket.emit("ballvec", ballX, ballY, ballVX, ballVY); } if (ballX < -ballRadius) { socket.emit("ballout"); stopPhysicsLoop(); } }, interval * 1000); } function stopPhysicsLoop() { if (physicsLoopId == null) return; clearInterval(physicsLoopId); physicsLoopId = null; } function initPhysics() { ballX = ballY = 0.5; ballVX = ballVY = 0; padPos = pad2Pos = 0.5; padVel = pad2Vel = 0; } // Returns whether a new ballvec should be sent function advancePhysics(deltaT) { var sendBallvec = false; var newballX = ballX + ballVX * deltaT; var newballY = ballY + ballVY * deltaT; var newpadPos = padPos + padVel * deltaT; var newpad2Pos = pad2Pos + pad2Vel * deltaT; var t; if (newballY <= ballRadius) { t = (ballRadius - ballY) / ballVY; } else if (newballY >= 1 - ballRadius) { t = (1 - ballRadius - ballY) / ballVY; } else { t = -1; } if (t >= 0) { newballY = ballY + ballVY * (2 * t - deltaT); ballVY = -ballVY; } if (newballX <= padX + padWidth + ballRadius && Math.abs(newballY - padPos) < padHeight / 2 + ballRadius) { t = (padX + padWidth + ballRadius - ballX) / ballVX; sendBallvec = true; // rebound from *our* pad } else if (newballX >= 1 - (padX + padWidth + ballRadius) && Math.abs(newballY - pad2Pos) < padHeight / 2 + ballRadius) { t = (1 - (padX + padWidth + ballRadius) - ballX) / ballVX; } else { t = -1; } if (t >= 0) { newballX = ballX + ballVX * (2 * t - deltaT); ballVX = -ballVX; } // If the ball has come to our side, send a ballvec if (ballX >= 0.5 && newballX < 0.5) { sendBallvec = true; } ballX = newballX; ballY = newballY; padPos = newpadPos; pad2Pos = newpad2Pos; return sendBallvec; } function setupBindings() { window.addEventListener("keydown", function(ev) { if (ev.key == "w" || ev.key == "ArrowUp") { padVel = -padBaseSpeed; socket.emit("padvec", padPos, padVel); } else if (ev.key == "s" || ev.key == "ArrowDown") { padVel = padBaseSpeed; socket.emit("padvec", padPos, padVel); } }); window.addEventListener("keyup", function(ev) { if (ev.key == "w" || ev.key == "ArrowUp" || ev.key == "s" || ev.key == "ArrowDown") { padVel = 0; socket.emit("padvec", padPos, padVel); } }); } window.addEventListener("load", function() { gameId = document.location.href.replace(/.*\//, ""); cvs = document.getElementById("cvs"); ctx = cvs.getContext("2d"); resizeHandler(); setupBindings(); openGame(); }); window.addEventListener("resize", resizeHandler);