'use strict'; process.title = 'mapguesser-multi'; class State { static OPEN = 1; static PLACE_RECEIVED = 2; static GUESS_SENT = 3; } class MultiGame { 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); } 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, state: State.OPEN, 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() }) }); 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 = this.rooms.get(roomId).members.get(token); this._sendResultsUntilNow(room, member); round.results.set(member.userName, { guessPosition: guessPosition, distance: distance, score: score }); member.state = State.GUESS_SENT; this._broadcastGuess(room, member.userName, guessPosition, distance, score); } 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]; var data = {}; data.place = { panoId: round.place.panoId, pov: round.place.pov }; if (room.currentRound > 0) { data.result = { position: room.rounds[room.currentRound - 1].place.position }; } var self = this; room.members.forEach(function (member) { self._sendToMember(member, 'new_round', data); member.state = State.PLACE_RECEIVED; }); } _sendInitialData(room, member) { var data = {}; if (room.currentRound >= 0) { data.place = room.rounds[room.currentRound].place; } data.history = []; for (var i = 0; i < room.currentRound; ++i) { var round = room.rounds[i]; var result; if (round.results.has(member.userName)) { result = round.results.get(member.userName); } else { result = { guessPosition: null, distance: null, score: 0 }; } data.history.push({ position: round.place.position, guessPosition: result.guessPosition, distance: result.distance, score: result.score }); } data.members = []; room.members.forEach(function (currentMember) { data.members.push({ userName: currentMember.userName, me: member === currentMember }); }); this._sendToMember(member, 'initialize', data); } _sendResultsUntilNow(room, member) { if (member.state !== State.GUESS_SENT) { return; } var round = room.rounds[room.currentRound]; var results = []; round.results.forEach(function (result, userName) { results.push({ userName: userName, guessPosition: result.guessPosition, distance: result.distance, score: result.score }); }); this._sendToMember(member, 'results', results); } _broadcastGuess(room, userName, guessPosition, distance, score) { var data = { userName: userName, guessPosition: guessPosition, distance: distance, score: score }; room.members.forEach(function (member) { if (!member.state !== State.GUESS_SENT) { return; } this._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; } switch (data.func) { case 'create_room': multiGame.createRoom(data.args.roomId); break; case 'join_room': multiGame.joinRoom(data.args.roomId, data.args.token, data.args.userName); break; case 'start_game': multiGame.startGame(data.args.roomId, data.args.places); break case 'guess': multiGame.guess(data.args.roomId, data.args.token, data.args.guessPosition, data.args.distance, data.args.score); break; case 'next_round': multiGame.nextRound(data.args.roomId, data.args.currentRound); break; } socket.write('OK'); 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);