365 lines
11 KiB
JavaScript
365 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
process.title = 'mapguesser-multi';
|
|
|
|
class MultiGame {
|
|
static ROUND_TIMEOUT_DEFAULT = 120000;
|
|
static ROUND_TIMEOUT_MINIMUM = 15000;
|
|
static ROUND_TIMEOUT_DIVIDER = 2;
|
|
static ROUND_TIMEOUT_OFFSET = 500;
|
|
|
|
constructor() {
|
|
this.rooms = new Map();
|
|
}
|
|
|
|
cleanupRooms() {
|
|
this.rooms.forEach(function (room, roomId) {
|
|
var lastValidDate = new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
|
|
if (room.updated < lastValidDate) {
|
|
this.rooms.delete(roomId);
|
|
}
|
|
});
|
|
}
|
|
|
|
connectToRoom(roomId, token, connection) {
|
|
if (!this.rooms.has(roomId) || !this.rooms.get(roomId).members.has(token)) {
|
|
return;
|
|
}
|
|
|
|
var room = this.rooms.get(roomId)
|
|
var member = room.members.get(token);
|
|
member.connection = connection;
|
|
|
|
this._sendInitialData(room, member, token);
|
|
}
|
|
|
|
createRoom(roomId) {
|
|
this.rooms.set(roomId, { members: new Map(), rounds: [], currentRound: -1, updated: new Date() });
|
|
}
|
|
|
|
joinRoom(roomId, token, userName) {
|
|
if (!this.rooms.has(roomId)) {
|
|
console.error('Room does not exist!')
|
|
return;
|
|
}
|
|
|
|
var room = this.rooms.get(roomId);
|
|
room.updated = new Date();
|
|
|
|
if (room.members.has(token)) {
|
|
return;
|
|
}
|
|
|
|
var data = { userName: userName };
|
|
var self = this;
|
|
room.members.forEach(function (member) {
|
|
self._sendToMember(member, 'member_joined', data);
|
|
});
|
|
|
|
room.members.set(token, { userName: userName, connection: null });
|
|
}
|
|
|
|
startGame(roomId, places) {
|
|
if (!this.rooms.has(roomId)) {
|
|
//TODO: send something back
|
|
console.log('Room does not exist!')
|
|
return;
|
|
}
|
|
|
|
var room = this.rooms.get(roomId);
|
|
room.updated = new Date();
|
|
|
|
var rounds = [];
|
|
places.forEach(function (place) {
|
|
rounds.push({
|
|
place: place,
|
|
results: new Map(),
|
|
timeout: MultiGame.ROUND_TIMEOUT_DEFAULT,
|
|
timeoutStarted: null,
|
|
timeoutHandler: null
|
|
})
|
|
});
|
|
|
|
room.rounds = rounds;
|
|
|
|
this.nextRound(roomId, 0);
|
|
}
|
|
|
|
guess(roomId, token, guessPosition, distance, score) {
|
|
if (!this.rooms.has(roomId)) {
|
|
//TODO: send something back
|
|
console.log('Room does not exist!')
|
|
return;
|
|
}
|
|
|
|
var room = this.rooms.get(roomId);
|
|
room.updated = new Date();
|
|
|
|
var round = room.rounds[room.currentRound];
|
|
var member = room.members.get(token);
|
|
var allResults = this._collectResultsInRound(room, round);
|
|
|
|
this._broadcastGuess(room, member.userName, guessPosition, distance, score);
|
|
|
|
round.results.set(token, { guessPosition: guessPosition, distance: distance, score: score });
|
|
|
|
this._setNewTimeout(room, round);
|
|
|
|
return allResults;
|
|
}
|
|
|
|
nextRound(roomId, currentRound) {
|
|
if (!this.rooms.has(roomId)) {
|
|
//TODO: send something back
|
|
console.log('Room does not exist!')
|
|
return;
|
|
}
|
|
|
|
var room = this.rooms.get(roomId);
|
|
room.updated = new Date();
|
|
|
|
room.currentRound = currentRound;
|
|
|
|
var round = room.rounds[room.currentRound];
|
|
|
|
round.timeoutStarted = new Date();
|
|
var self = this;
|
|
round.timeoutHandler = setTimeout(function () {
|
|
self._endRound(room, round);
|
|
}, round.timeout + MultiGame.ROUND_TIMEOUT_OFFSET);
|
|
|
|
var data = {};
|
|
data.place = { panoId: round.place.panoId, pov: round.place.pov };
|
|
data.timeout = round.timeout;
|
|
|
|
var self = this;
|
|
room.members.forEach(function (member) {
|
|
self._sendToMember(member, 'new_round', data);
|
|
});
|
|
}
|
|
|
|
_setNewTimeout(room, round) {
|
|
clearTimeout(round.timeoutHandler);
|
|
|
|
if (room.members.size === round.results.size) {
|
|
round.timeout = 0;
|
|
round.timeoutStarted = new Date();
|
|
|
|
this._endRound(room, round);
|
|
} else {
|
|
round.timeout = round.timeout - (new Date() - round.timeoutStarted);
|
|
if (round.timeout > MultiGame.ROUND_TIMEOUT_DIVIDER * MultiGame.ROUND_TIMEOUT_MINIMUM) {
|
|
round.timeout = round.timeout / MultiGame.ROUND_TIMEOUT_DIVIDER;
|
|
} else if (round.timeout > MultiGame.ROUND_TIMEOUT_MINIMUM) {
|
|
round.timeout = MultiGame.ROUND_TIMEOUT_MINIMUM;
|
|
}
|
|
round.timeoutStarted = new Date();
|
|
var self = this;
|
|
round.timeoutHandler = setTimeout(function () {
|
|
self._endRound(room, round);
|
|
}, round.timeout + MultiGame.ROUND_TIMEOUT_OFFSET);
|
|
|
|
this._broadcastTimeout(room, round);
|
|
}
|
|
}
|
|
|
|
_endRound(room, round) {
|
|
var allResults = this._collectResultsInRound(room, round);
|
|
var self = this;
|
|
room.members.forEach(function (member, token) {
|
|
var result = { guessPosition: null, distance: null, score: 0 };
|
|
|
|
if (round.results.has(token)) {
|
|
result = round.results.get(token);
|
|
} else {
|
|
round.results.set(token, result);
|
|
}
|
|
|
|
var data = { position: round.place.position, result: result, allResults: allResults };
|
|
self._sendToMember(member, 'end_round', data);
|
|
});
|
|
}
|
|
|
|
_sendInitialData(room, member, token) {
|
|
var data = {};
|
|
|
|
if (room.currentRound >= 0) {
|
|
var round = room.rounds[room.currentRound];
|
|
|
|
data.place = round.place;
|
|
data.timeout = round.timeout - (new Date() - round.timeoutStarted);
|
|
}
|
|
|
|
data.history = [];
|
|
for (var i = 0; i <= room.currentRound; ++i) {
|
|
var round = room.rounds[i];
|
|
|
|
if (i === room.currentRound && !round.results.has(token)) {
|
|
continue;
|
|
}
|
|
|
|
var result = { guessPosition: null, distance: null, score: 0 };
|
|
var allResults = [];
|
|
|
|
round.results.forEach(function (currentResult, currentToken) {
|
|
if (token === currentToken) {
|
|
result = currentResult;
|
|
return;
|
|
}
|
|
|
|
allResults.push({ userName: room.members.get(currentToken).userName, guessPosition: currentResult.guessPosition, distance: currentResult.distance, score: currentResult.score });
|
|
});
|
|
|
|
data.history.push({
|
|
position: round.place.position,
|
|
result: result,
|
|
allResults: allResults
|
|
});
|
|
}
|
|
|
|
data.members = [];
|
|
room.members.forEach(function (currentMember) {
|
|
data.members.push({ userName: currentMember.userName, me: member === currentMember });
|
|
});
|
|
|
|
data.readyToContinue = room.currentRound >= 0 && room.members.size === room.rounds[room.currentRound].results.size
|
|
|
|
this._sendToMember(member, 'initialize', data);
|
|
}
|
|
|
|
_collectResultsInRound(room, round) {
|
|
var results = [];
|
|
round.results.forEach(function (result, token) {
|
|
results.push({
|
|
userName: room.members.get(token).userName,
|
|
guessPosition: result.guessPosition,
|
|
distance: result.distance,
|
|
score: result.score
|
|
});
|
|
});
|
|
|
|
return results;
|
|
}
|
|
|
|
_broadcastTimeout(room, round) {
|
|
var self = this;
|
|
room.members.forEach(function (member) {
|
|
self._sendToMember(member, 'timeout_changed', { timeout: round.timeout });
|
|
});
|
|
}
|
|
|
|
_broadcastGuess(room, userName, guessPosition, distance, score) {
|
|
var data = { userName: userName, guessPosition: guessPosition, distance: distance, score: score };
|
|
var round = room.rounds[room.currentRound];
|
|
var self = this;
|
|
room.members.forEach(function (member, token) {
|
|
if (!round.results.has(token)) {
|
|
return;
|
|
}
|
|
|
|
self._sendToMember(member, 'guess', data);
|
|
});
|
|
}
|
|
|
|
_sendToMember(member, type, data) {
|
|
if (!member.connection) {
|
|
return;
|
|
}
|
|
|
|
if (member.connection.readyState !== ws.OPEN) {
|
|
member.connection = null;
|
|
return;
|
|
}
|
|
|
|
member.connection.send(JSON.stringify({ type: type, data: data }));
|
|
}
|
|
}
|
|
|
|
require('dotenv').config();
|
|
|
|
var
|
|
net = require('net'),
|
|
ws = require('ws');
|
|
|
|
var multiGame = new MultiGame();
|
|
|
|
//TODO: following should be in a separate class/function
|
|
|
|
var tcpServer = net.createServer(function (socket) {
|
|
socket.on('data', function (data) {
|
|
try {
|
|
data = JSON.parse(data);
|
|
} catch (e) {
|
|
console.error('Cannot parse data: ' + data);
|
|
return;
|
|
}
|
|
|
|
var response = { data: null };
|
|
switch (data.func) {
|
|
case 'create_room':
|
|
response.data = multiGame.createRoom(data.args.roomId);
|
|
|
|
break;
|
|
|
|
case 'join_room':
|
|
response.data = multiGame.joinRoom(data.args.roomId, data.args.token, data.args.userName);
|
|
|
|
break;
|
|
|
|
case 'start_game':
|
|
response.data = multiGame.startGame(data.args.roomId, data.args.places);
|
|
|
|
break
|
|
|
|
case 'guess':
|
|
response.data = multiGame.guess(data.args.roomId, data.args.token, data.args.guessPosition, data.args.distance, data.args.score);
|
|
|
|
break;
|
|
|
|
case 'next_round':
|
|
response.data = multiGame.nextRound(data.args.roomId, data.args.currentRound);
|
|
|
|
break;
|
|
}
|
|
|
|
socket.write(JSON.stringify(response));
|
|
socket.end();
|
|
});
|
|
});
|
|
tcpServer.on('listening', function () {
|
|
console.log('[INFO] TCP server started');
|
|
});
|
|
tcpServer.listen(process.env.MULTI_INTERNAL_PORT);
|
|
|
|
var wsServer = new ws.Server({ port: process.env.MULTI_WS_PORT });
|
|
wsServer.on('connection', function (connection, request) {
|
|
console.log('[INFO] New WS connection: ' + request.connection.remoteAddress);
|
|
|
|
connection.on('message', function (data) {
|
|
try {
|
|
data = JSON.parse(data);
|
|
} catch (e) {
|
|
console.error('Cannot parse data: ' + data);
|
|
return;
|
|
}
|
|
|
|
switch (data.func) {
|
|
case 'connect_to_room':
|
|
multiGame.connectToRoom(data.args.roomId, data.args.token, connection);
|
|
break;
|
|
}
|
|
});
|
|
|
|
connection.on('close', function () {
|
|
console.log('[INFO] WS connection ended: ' + request.connection.remoteAddress);
|
|
});
|
|
});
|
|
wsServer.on('listening', function () {
|
|
console.log('[INFO] WS server started');
|
|
});
|
|
|
|
setInterval(function () {
|
|
multiGame.cleanupRooms();
|
|
}, 24 * 60 * 60 * 1000);
|