1 const params
= require("./parameters.js");
2 const WebSocket
= require("ws");
3 const wss
= new WebSocket
.Server(
4 {port: params
.socket_port
, path: params
.socket_path
});
6 let challenges
= {}; //variantName --> socketId, name
7 let games
= {}; //gameId --> gameInfo (vname, fen, players, options, time)
8 let sockets
= {}; //socketId --> socket
9 const variants
= require("./variants.js");
10 const Crypto
= require("crypto");
11 const randstrSize
= 8;
13 function send(sid
, code
, data
) {
14 const socket
= sockets
[sid
];
15 // If a player deletes local infos and then tries to resume a game,
16 // sockets[oppSid] will probably not exist anymore:
17 if (socket
) socket
.send(JSON
.stringify(Object
.assign({ code: code
}, data
)));
20 wss
.on("connection", (socket
, req
) => {
21 const sid
= req
.url
.split("=")[1]; //...?sid=...
22 sockets
[sid
] = socket
;
23 socket
.isAlive
= true;
24 socket
.on("pong", () => socket
.isAlive
= true);
26 function launchGame(vname
, players
, options
) {
28 Crypto
.randomBytes(randstrSize
).toString("hex").slice(0, randstrSize
);
31 players: players
.map(p
=> {
32 return (!p
? null : {sid: p
.sid
, name: p
.name
});
37 if (players
.every(p
=> p
)) {
38 const gameInfo
= Object
.assign(
39 // Provide seed so that both players initialize with same FEN
40 {seed: Math
.floor(Math
.random() * 1984), gid: gid
},
42 for (const p
of players
) {
45 Object
.assign({randvar: p
.randvar
}, gameInfo
));
49 // Incomplete players array: do not start game yet
50 send(sid
, "gamecreated", {gid: gid
});
51 // If nobody joins within 5 minutes, delete game
54 if (games
[gid
] && games
[gid
].players
.some(p
=> !p
))
62 socket
.on("message", (msg
) => {
63 const obj
= JSON
.parse(msg
);
65 // Send challenge (may trigger game creation)
67 let opponent
= undefined,
69 const vname
= obj
.vname
,
70 randvar
= (obj
.vname
== "_random");
71 if (vname
== "_random") {
72 // Pick any current challenge if possible
73 const currentChalls
= Object
.keys(challenges
);
74 if (currentChalls
.length
>= 1) {
76 currentChalls
[Math
.floor(Math
.random() * currentChalls
.length
)];
77 opponent
= challenges
[choice
];
80 else if (challenges
[vname
]) {
81 opponent
= challenges
[vname
];
85 delete challenges
[choice
];
86 if (choice
== "_random") {
87 // Pick a variant at random in the list
88 const index
= Math
.floor(Math
.random() * variants
.length
);
89 choice
= variants
[index
].name
;
93 {sid: sid
, name: obj
.name
, randvar: randvar
},
96 if (Math
.random() < 0.5) players
= players
.reverse();
97 launchGame(choice
, players
, {}); //empty options => default
100 // Place challenge and wait. 'randvar' indicate if we play anything
101 challenges
[vname
] = {sid: sid
, name: obj
.name
, randvar: randvar
};
104 // Set FEN after game was created (received twice)
106 games
[obj
.gid
].fen
= obj
.fen
;
108 // Send back game informations
110 if (!games
[obj
.gid
]) send(sid
, "nogame");
111 else send(sid
, "gameinfo", games
[obj
.gid
]);
116 delete challenges
[obj
.vname
];
120 if (!games
[obj
.gid
]) send(sid
, "closerematch");
122 const myIndex
= (games
[obj
.gid
].players
[0].sid
== sid
? 0 : 1);
123 if (!games
[obj
.gid
].rematch
) games
[obj
.gid
].rematch
= [false, false];
124 games
[obj
.gid
].rematch
[myIndex
] = true;
125 if (games
[obj
.gid
].rematch
[1-myIndex
]) {
126 // Launch new game, colors reversed
127 launchGame(games
[obj
.gid
].vname
,
128 games
[obj
.gid
].players
.reverse(),
129 games
[obj
.gid
].options
);
133 // Rematch cancellation
135 if (games
[obj
.gid
]) {
136 const myIndex
= (games
[obj
.gid
].players
[0].sid
== sid
? 0 : 1);
137 send(games
[obj
.gid
].players
[1-myIndex
].sid
, "closerematch");
140 // Create game vs. friend
143 {sid: obj
.player
.sid
, name: obj
.player
.name
},
147 obj
.player
.color
== 'b' ||
148 (obj
.player
.color
== '' && Math
.random() < 0.5)
150 players
= players
.reverse();
152 launchGame(obj
.vname
, players
, obj
.options
);
155 // Join game vs. friend
157 if (!games
[obj
.gid
]) send(sid
, "jointoolate");
159 // Join a game (started by some other player)
160 const emptySlot
= games
[obj
.gid
].players
.findIndex(p
=> !p
);
161 if (emptySlot
< 0) send(sid
, "jointoolate");
162 games
[obj
.gid
].players
[emptySlot
] = {sid: sid
, name: obj
.name
};
163 const gameInfo
= Object
.assign(
164 // Provide seed so that both players initialize with same FEN
165 {seed: Math
.floor(Math
.random()*1984), gid: obj
.gid
},
167 for (const p
of games
[obj
.gid
].players
)
168 send(p
.sid
, "gamestart", gameInfo
);
171 // Relay a move + update games object
173 games
[obj
.gid
].fen
= obj
.fen
;
174 games
[obj
.gid
].time
= Date
.now(); //update timestamp in case of
175 const playingWhite
= (games
[obj
.gid
].players
[0].sid
== sid
);
176 const oppSid
= games
[obj
.gid
].players
[playingWhite
? 1 : 0].sid
;
177 send(oppSid
, "newmove", {moves: obj
.moves
});
179 // Relay "game ends" message
182 const playingWhite
= (games
[obj
.gid
].players
[0].sid
== sid
);
183 const oppSid
= games
[obj
.gid
].players
[playingWhite
? 1 : 0].sid
;
184 send(oppSid
, "gameover", { gid: obj
.gid
});
186 // 2 minutes timeout for rematch:
187 setTimeout(() => delete games
[obj
.gid
], 2 * 60000);
191 socket
.on("close", () => {
193 for (const [key
, value
] of Object
.entries(challenges
)) {
194 if (value
.sid
== sid
) {
195 delete challenges
[key
];
196 break; //only one challenge per player
202 const heartbeat
= setInterval(() => {
203 wss
.clients
.forEach((ws
) => {
204 if (ws
.isAlive
=== false) return ws
.terminate();
210 // Every 24 hours, scan games and remove if last move older than 24h
211 const dayInMillisecs
= 24 * 60 * 60 * 1000;
212 const killOldGames
= setInterval(() => {
213 const now
= Date
.now();
214 Object
.keys(games
).forEach(gid
=> {
215 if (now
- games
[gid
].time
>= dayInMillisecs
) delete games
[gid
];
219 // TODO: useful code here?
220 wss
.on("close", () => {
221 clearInterval(heartbeat
);
222 clearInterval(killOldGames
);