const app = require('express')(); const http = require('http').Server(app); const io = require('socket.io')(http); const convert = require('color-convert'); app.get('/', function (req, res) { res.sendFile(__dirname + '/client.html'); }); function randomColor() { return '#' + convert.hsl.hex([randInt(0, 256), randInt(50, 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 players = {}; const history = {}; let upc = 0; let ic = 0; setInterval(function () { //main loop Object.keys(players).forEach(function (name) { const p = 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 = 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(history).forEach(function (name2) { if (p.alive && name !== name2) { const h2 = history[name2]; let lastpoint = h2.list[h2.i0]; if(!lastpoint) return; for (let di = 1; di < HSIZE; 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){ ic++; if (intersects(lastpos, p.pos, lastpoint, point)) { process.stdout.write(`\r${name} collided with ${name2}\n`); p.alive = false; break; } } lastpoint = point; } else { break; } } } }); } if(p.starting > 0) p.starting -= 20; } }); io.emit('players', players); upc++; }, 20); setInterval(function () { process.stdout.write(`\rtick ${20 * upc * 20 / 1000}/20 ms (${ic} intersect/s)`); upc = 0; ic = 0; }, 1000); io.on('connection', function (socket) { socket.name = '#' + ('0000' + randInt(0, 10000)).slice(-4); process.stdout.write(`\r${socket.name} connected\n`); 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 }; history[socket.name] = { i0: -1, list: new Array(HSIZE) }; socket.emit('info', { self:players[socket.name], players:players, history:history, hsize:HSIZE }); socket.on('disconnect', function () { delete players[socket.name]; delete history[socket.name]; process.stdout.write(`\r${socket.name} disconnected\n`); io.emit('players', players); }); socket.on('update', function (newp) { const p = players[newp.name]; if (!p) { socket.disconnect(); return; } if (!p.alive) return; if (newp.color === 'new') players[newp.name].color = randomColor(); if (newp.angleSpd > 0) players[newp.name].angleSpd = MAX_ANGLE_SPEED; else if (newp.angleSpd < 0) players[newp.name].angleSpd = -MAX_ANGLE_SPEED; else players[newp.name].angleSpd = 0; }); }); http.listen(3001, function () { console.log('listening on *:3001\n'); });