-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.mjs
198 lines (162 loc) · 4.74 KB
/
index.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// Imports.
import http from 'http';
import path from 'path';
import url from 'url';
import express from 'express';
import uuid from 'node-uuid';
import ws from 'ws';
import Game from './game.mjs';
import LeagueTable from './league-table.mjs';
import Player from './player.mjs';
import Tournament from './tournament.mjs';
// Members.
const NUM_TEAMS = 4;
const MOVE_TIMEOUT = 3000; // milliseconds.
const TIMEOUT_MOVE = 'TIMEOUT';
const PORT = process.env.PORT || 3000;
const INDEX = path.join(process.cwd(), 'index.html');
const ticTacClients = new Map(); // id -> { player, socket }
let resolvePlayerMove;
let viewerSocket;
// Express Server.
const server = express()
.use((request, response) => response.sendFile(INDEX));
// HTTP Server.
const httpServer = http.createServer(server);
// WebSocket Server.
const webSocketServer = new ws.Server({ server: httpServer });
// Start listening.
httpServer.listen(PORT);
// Event Handlers.
webSocketServer.on('connection', async (socket, request) => {
const { query } = url.parse(request.url, true);
const { name } = query;
if (name === 'viewer') {
console.log('The tournament viewer has connected.');
viewerSocket = socket;
return;
}
console.log(`Team "${name}" has connected.`);
const socketId = uuid.v4();
const player = new Player({ name, socketId });
ticTacClients.set(socketId, { player, socket });
/*
* Event Handlers.
*/
// Listen for messages from this player.
socket.onmessage = message => {
const clientMessage = JSON.parse(message.data);
if (clientMessage.type === 'move') {
const name = getPlayerNameBySocketId(socketId);
console.log(`Team "${name}" sent a move: ${clientMessage.move}.`);
resolvePlayerMove(clientMessage.move);
}
};
// Clean up.
socket.onclose = () => {
const name = getPlayerNameBySocketId(socketId);
console.log(`Team "${name}" has disconnected.`);
};
/*
* Logic start.
*/
// Don't start until we have enough players.
if (ticTacClients.size === NUM_TEAMS) {
run();
}
});
/*
* Helper Functions.
*/
const run = async () => {
// Create the tournament.
const playerList = [];
ticTacClients.forEach(value => playerList.push(value.player));
const tournament = Tournament(playerList);
const leagueTable = new LeagueTable(playerList);
updateUITable(leagueTable.values);
// Play all the games!
let { value: currentGame, done: isTournamentComplete } = tournament.next();
while (!isTournamentComplete) {
// Play the current game.
while (!currentGame.isOver) {
const { activePlayer, player1, player2, gameState } = currentGame;
const socket = getSocketById(activePlayer.socketId);
const move = await getMove(socket, gameState);
if (move !== TIMEOUT_MOVE) {
currentGame.update(move); // note: also advances the turn.
}
else {
// Timed out.
currentGame.advanceTurn();
}
updateUIGame({
team1: { name: player1.team.name, token: player1.token },
team2: { name: player2.team.name, token: player2.token },
gameState: currentGame.gameState,
});
}
// The game is over!
// Send a "winner" message.
updateUIGameResult(currentGame.result);
// Update the league table.
leagueTable.update(currentGame.result);
updateUITable(leagueTable.values);
// And move on to the next game.
({ value: currentGame, done: isTournamentComplete} = tournament.next());
}
};
const updateUIGame = gameInfo => {
if (!viewerSocket) { return; }
viewerSocket.send(JSON.stringify({
type: 'gameUpdate',
gameInfo,
}));
};
const updateUITable = tableInfo => {
if (!viewerSocket) { return; }
viewerSocket.send(JSON.stringify({
type: 'tableUpdate',
tableInfo,
}));
};
const updateUIGameResult = result => {
if (!viewerSocket) { return; }
const gameResult = {
isTie: result.isTie,
winner: result.winner.name,
loser: result.loser.name,
};
viewerSocket.send(JSON.stringify({
type: 'gameResult',
result: gameResult,
}));
};
const getMove = (socket, gameState) => {
if (socket.readyState === 1) { // Open
// Prompt.
socket.send(JSON.stringify({
type: 'makeMove',
gameState,
}));
}
// Wait for response.
const playerMove = new Promise((resolve, reject) => {
resolvePlayerMove = resolve;
});
const timeout = getMoveTimeout();
return Promise.race([playerMove, timeout]);
};
const getMoveTimeout = () => {
return new Promise(resolve => {
setTimeout(resolve, MOVE_TIMEOUT, TIMEOUT_MOVE);
});
};
const getPlayerNameBySocketId = socketId => {
const { player } = ticTacClients.get(socketId);
return player.name;
}
const getSocketById = socketId => {
const { socket } = ticTacClients.get(socketId);
return socket;
}