const app = require('express')(); const http = require('http').Server(app); const io = require('socket.io')(http); const convert = require('color-convert'); const fs = require('fs'); app.get('*', function (req, res) { if (req.originalUrl === '/') res.sendFile(`${__dirname}/client/index.html`); else if (fs.existsSync(`${__dirname}/client${req.originalUrl}`)) res.sendFile(`${__dirname}/client${req.originalUrl}`); else res.status(404).send('file not found'); }); function randomColor() { return '#' + convert.hsl.hex([randInt(0, 256), randInt(150, 200), 256]); } function randInt(min, max) { return Math.floor(min + Math.random() * (max - min)); } function intersects(a1, a2, b1, b2) { let det, gamma, lambda; det = (a2.x - a1.x) * (b2.y - b1.y) - (b2.x - b1.x) * (a2.y - a1.y); if (det === 0) { return false; } else { lambda = ((b2.y - b1.y) * (b2.x - a1.x) + (b1.x - b2.x) * (b2.y - a1.y)) / det; gamma = ((a1.y - a2.y) * (b2.x - a1.x) + (a2.x - a1.x) * (b2.y - a1.y)) / det; return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1); } } function dist(a1, a2) { return Math.sqrt(Math.pow(a1.x - a2.x, 2) + Math.pow(a1.y - a2.y, 2)); } function clone(a) { return {x: parseFloat(a.x), y: parseFloat(a.y)}; } const HSIZE = 300; const MAXX = 1600; const MAXY = 900; const SPD = 5; const MAX_ANGLE_SPEED = Math.PI / 32; const rooms = {}; let updateCount = 0; let intersectCount = 0; let playerCount = 0; setInterval(function () { //main loop Object.keys(rooms).forEach(function (rname) { const room = rooms[rname]; const pl = Object.keys(room.players); if (room.waiting > 0) { room.waiting -= 20; if (room.waiting <= 0) { //restart process.stdout.write(`\rroom #${rname} > restarting game \n`); room.history = {}; pl.forEach(function (name) { const p = room.players[name]; room.players[name] = { pos: { x: randInt(100, MAXX - 100), y: randInt(100, MAXY - 100) }, angle: Math.random() * Math.PI, angleSpd: 0, color: p.color, name: p.name, alive: true, starting: 2000, score: p.score }; room.history[name] = { i0: -1, list: new Array(HSIZE) } }); io.to(rname).emit('players', room.players); io.to(rname).emit('history', room.history); io.to(rname).emit('lock', false); } return; } pl.forEach(function (name) { const p = room.players[name]; if (p.alive) { const lastpos = clone(p.pos); p.pos.x += SPD * Math.cos(p.angle); p.pos.y += SPD * Math.sin(p.angle); p.angle += p.angleSpd; let exempt = false; if (p.pos.x > MAXX) { p.pos.x = 0; exempt = true; } if (p.pos.x < 0) { p.pos.x = MAXX; exempt = true; } if (p.pos.y > MAXY) { p.pos.y = 0; exempt = true; } if (p.pos.y < 0) { p.pos.y = MAXY; exempt = true; } if (p.starting <= 0) { const h = room.history[name]; h.i0 = (h.i0 + 1) % HSIZE; h.list[h.i0] = clone(p.pos); } if (!exempt && p.starting <= 0) { const ds = dist(lastpos, p.pos); if (ds <= SPD * 0.9) process.stdout.write(`\r${name} moved too slowly : ${ds} [${lastpos.x},${lastpos.y}]->[${p.pos.x},${p.pos.y}]\n`); if (ds >= SPD * 1.1) process.stdout.write(`\r${name} moved too quickly : ${ds} [${lastpos.x},${lastpos.y}]->[${p.pos.x},${p.pos.y}]\n`); Object.keys(room.history).forEach(function (name2) { if (p.alive && name !== name2) { const h2 = room.history[name2]; let lastpoint = h2.list[h2.i0]; if (!lastpoint) return; for (let di = 1; di < HSIZE - 1; di++) { const point = h2.list[(h2.i0 - di + HSIZE) % HSIZE]; if (point !== undefined) { if (Math.abs(lastpoint.x - point.x) < 1600 * .9 && Math.abs(lastpoint.y - point.y) < 900 * .9) { intersectCount++; if (intersects(lastpos, p.pos, lastpoint, point)) { process.stdout.write(`\rroom #${rname} > ${name} collided with ${name2} \n`); p.alive = false; break; } } lastpoint = point; } else { break; } } } }); } if (p.starting > 0) p.starting -= 20; } }); const palive = pl.filter(p => room.players[p].alive); if (pl.length > 1 && palive.length === 1) { //end of game room.players[palive[0]].score++; room.waiting = 3000; process.stdout.write(`\rroom #${rname} > ${palive[0]} won \n`); io.to(rname).emit('lock', true); } io.to(rname).emit('players', room.players); room.historyTimer -= 20; if (room.historyTimer <= 0) { io.to(rname).emit('history', room.history); room.historyTimer += 1000; } }); updateCount++; }, 20); setInterval(function () { process.stdout.write(`\rtick ${20 * updateCount * 20 / 1000}/20 ms (${Object.keys(rooms).length} rooms) (${playerCount} players) (${intersectCount} intersect/s) `); updateCount = 0; intersectCount = 0; }, 1000); io.on('connection', function (socket) { socket.name = '#' + ('0000' + randInt(0, 10000)).slice(-4); process.stdout.write(`\r${socket.name} connected \n`); playerCount++; socket.on('room', function (name) { if (socket.room) return; if (name) { socket.room = name; } else { socket.room = ('000000' + randInt(0, 1000000)).slice(-4); } socket.join(socket.room); if (!rooms[socket.room]) { rooms[socket.room] = { waiting: 0, players: {}, history: {}, historyTimer: 0 } } rooms[socket.room].players[socket.name] = { pos: { x: randInt(100, MAXX - 100), y: randInt(100, MAXY - 100) }, angle: Math.random() * Math.PI, angleSpd: 0, color: randomColor(), name: socket.name, alive: true, starting: 2000, score: 0 }; rooms[socket.room].history[socket.name] = { i0: -1, list: new Array(HSIZE) }; socket.emit('info', { room: socket.room, self: rooms[socket.room].players[socket.name], players: rooms[socket.room].players, history: rooms[socket.room].history, hsize: HSIZE }); process.stdout.write(`\rroom #${socket.room} > ${socket.name} connected \n`); }); socket.on('history', function () { if (socket.room) socket.emit('history', rooms[socket.room].history); }); socket.on('disconnect', function () { playerCount--; if (!socket.room) return; delete rooms[socket.room].players[socket.name]; delete rooms[socket.room].history[socket.name]; process.stdout.write(`\rroom #${socket.room} > ${socket.name} disconnected \n`); if (Object.keys(rooms[socket.room].players).length === 0) { delete rooms[socket.room]; process.stdout.write(`\rroom #${socket.room} deleted \n`); } }); socket.on('update', function (newp) { if (!socket.room) return; const p = rooms[socket.room].players[newp.name]; if (!p) { socket.disconnect(); return; } if (!p.alive) return; if (newp.color === 'new') rooms[socket.room].players[newp.name].color = randomColor(); if (newp.angleSpd > 0) rooms[socket.room].players[newp.name].angleSpd = MAX_ANGLE_SPEED; else if (newp.angleSpd < 0) rooms[socket.room].players[newp.name].angleSpd = -MAX_ANGLE_SPEED; else rooms[socket.room].players[newp.name].angleSpd = 0; }); }); http.listen(3001, function () { console.log('listening on *:3001\n'); });