#!/usr/bin/env node const http = require("http"); const express = require("express"); const socketio = require("socket.io") const PORT = (() => { if (process.argv.length == 3) { return +process.argv[2]; } else if (process.argv.length == 2) { return 8080; } else { console.log("Usage: server.js [port]"); process.exit(1); } })(); const app = express(); const server = http.Server(app); const io = socketio(server); // gameid -> {pl: [socket]*{1,2}, started: Bool, nextServe: Int, score: [Int, Int]} const games = new Map(); function genId() { for (let i = 0; i < 10; i++) { // 2176782336 == 36^6 const id = ("000000" + (2176782336 * Math.random() | 0).toString(36)).slice(-6); if (!games.has(id)) return id; } return ""; } function validId(id) { return /^[0-9a-z]{6}$/.test(id); } app.get("/", (req, res) => { const gameid = genId(); if (gameid == "") { res.sendStatus(500); return; } res.redirect("/game/" + gameid); }); app.param("gameid", (req, res, next, gameid) => { if (!validId(gameid)) { res.sendStatus(404); } else { next(); } }); app.get("/game/:gameid", (req, res) => { res.sendFile(__dirname + "/game.html"); }); const staticFiles = [ "/game.css", "/game.js", /^\/snd\//, ]; app.get(staticFiles, (req, res) => { res.sendFile(__dirname + req.path); }); io.on("connection", (conn) => { let gameid, gameobj; conn.on("open", (id) => { console.log("open " + id); if (!validId(id)) { console.log(" invalid"); conn.emit("redirect", "/"); conn.disconnect(true); return; } gameobj = games.get(id); if (gameobj != undefined) { if (gameobj.pl.length == 1 && !gameobj.started) { console.log(" serve"); gameid = id; gameobj.pl.push(conn); gameobj.pl[0].emit("join"); gameobj.pl[0].emit("status", "Get ready..."); gameobj.pl[1].emit("status", "Get ready..."); gameobj.pl[0].emit("serve", "left", 0.5, 0.5, true); gameobj.pl[1].emit("serve", "right", 0.5, 0.5, true); gameobj.started = true; setTimeout(() => { if (!gameobj || gameobj.pl.length != 2) return; gameobj.pl[0].emit("status", ""); gameobj.pl[1].emit("status", ""); gameobj.pl[0].emit(gameobj.nextServe == 0 ? "startBall" : "start"); gameobj.pl[1].emit(gameobj.nextServe == 1 ? "startBall" : "start"); gameobj.nextServe = 1 - gameobj.nextServe; }, 2000); } else { console.log(" full"); gameobj = undefined; conn.emit("status", "Game full! Opening new game..."); setTimeout(() => { conn.emit("redirect", "/"); conn.disconnect(true); }, 2000); } } else { console.log(" new"); gameid = id; gameobj = {pl: [conn], started: false, nextServe: 0, score: [0, 0]}; games.set(gameid, gameobj); conn.emit("status", "Waiting for other player..."); } }); conn.on("disconnect", () => { if (gameobj == undefined) return; console.log("stop " + gameid); const newpl = []; for (const p of gameobj.pl) { if (p != conn) { p.emit("stop"); p.emit("leave"); p.emit("status", "Other player left."); setTimeout(() => { p.emit("redirect", "/"); p.disconnect(true); }, 2000); newpl.push(p); } } if (newpl.length == 0) { console.log(" delete"); games.delete(gameid); } else { gameobj.pl = newpl; } }); conn.on("padvec", (pos, vel) => { if (!gameobj) return; for (const p of gameobj.pl) { if (p != conn) { p.emit("padvec", pos, vel); } } }); conn.on("ballvec", (x, y, vx, vy) => { if (!gameobj) return; for (const p of gameobj.pl) { if (p != conn) { p.emit("ballvec", 1 - x, y, -vx, vy); } } }); conn.on("bounce", () => { if (!gameobj) return; for (const p of gameobj.pl) { if (p != conn) { p.emit("bounce"); } } }); conn.on("ballout", () => { if (!gameobj || gameobj.pl.length != 2) return; gameobj.pl[0].emit("stop"); gameobj.pl[1].emit("stop"); gameobj.pl[0].emit("status", "Ball out!"); gameobj.pl[1].emit("status", "Ball out!"); for (let i = 0; i < gameobj.pl.length; i++) { if (gameobj.pl[i] != conn) { gameobj.score[i]++; gameobj.pl[i].emit("othermiss"); } } gameobj.pl[0].emit("score", gameobj.score); gameobj.pl[1].emit("score", gameobj.score); setTimeout(() => { if (!gameobj || gameobj.pl.length != 2) return; gameobj.pl[0].emit("status", "Get ready..."); gameobj.pl[1].emit("status", "Get ready..."); gameobj.pl[0].emit("serve", "left", 0.5, 0.5, false); gameobj.pl[1].emit("serve", "right", 0.5, 0.5, false); setTimeout(() => { if (!gameobj || gameobj.pl.length != 2) return; gameobj.pl[0].emit("status", ""); gameobj.pl[1].emit("status", ""); gameobj.pl[0].emit(gameobj.nextServe == 0 ? "startBall" : "start"); gameobj.pl[1].emit(gameobj.nextServe == 1 ? "startBall" : "start"); gameobj.nextServe = 1 - gameobj.nextServe; }, 2000); }, 2000); }); }); server.listen(PORT, () => console.log("Listening on port " + PORT));