Files
snex.io/server.js

283 lines
8.2 KiB
JavaScript

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');
});