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)
8 let sockets
= {}; //socketId --> socket
9 let sendmoveTimeout1
= {},
10 sendmoveTimeout2
= {},
13 const variants
= require("./variants.js");
15 const clearTrySendMove
= (gid
) => {
16 clearTimeout(sendmoveTimeout1
[gid
]);
17 clearTimeout(sendmoveTimeout2
[gid
]);
18 clearInterval(sendmoveRetry
[gid
]);
19 clearTimeout(stopRetry
[gid
]);
22 const send
= (sid
, code
, data
) => {
23 const socket
= sockets
[sid
];
24 // If a player delete local infos and then try to resume a game,
25 // sockets[oppSid] will probably not exist anymore:
26 if (socket
) socket
.send(JSON
.stringify(Object
.assign({ code: code
}, data
)));
29 const Crypto
= require('crypto')
30 function randomString(size
= 8) {
31 return Crypto
.randomBytes(size
).toString('hex').slice(0, size
);
34 wss
.on('connection', function connection(socket
, req
) {
35 const sid
= req
.url
.split("=")[1]; //...?sid=...
36 sockets
[sid
] = socket
;
37 socket
.isAlive
= true;
38 socket
.on('pong', () => socket
.isAlive
= true);
40 function launchGame(vname
, players
, options
) {
41 const gid
= randomString(8);
44 players: players
.map(p
=> {
45 return (!p
? null : {sid: p
.sid
, name: p
.name
});
49 if (players
.every(p
=> p
)) {
50 const gameInfo
= Object
.assign(
51 // Provide seed so that both players initialize with same FEN
52 {seed: Math
.floor(Math
.random() * 1984), gid: gid
},
54 for (let i
of [0, 1]) {
55 send(players
[i
].sid
, "gamestart",
56 Object
.assign({randvar: players
[i
].randvar
}, gameInfo
));
60 // Incomplete players array: do not start game yet
61 send(sid
, "gamecreated", {gid: gid
});
62 // If nobody joins within a minute, delete game
65 if (games
[gid
] && games
[gid
].players
.some(p
=> !p
))
73 socket
.on('message', (msg
) => {
74 const obj
= JSON
.parse(msg
);
76 // Send challenge (may trigger game creation)
78 // Only one challenge per player:
79 if (Object
.keys(challenges
).some(k
=> challenges
[k
].sid
== sid
))
81 let opponent
= undefined,
83 const vname
= obj
.vname
,
84 randvar
= (obj
.vname
== "_random");
85 if (vname
== "_random") {
86 // Pick any current challenge if any
87 const currentChalls
= Object
.keys(challenges
);
88 if (currentChalls
.length
>= 1) {
90 currentChalls
[Math
.floor(Math
.random() * currentChalls
.length
)];
91 opponent
= challenges
[choice
];
94 else if (challenges
[vname
]) {
95 opponent
= challenges
[vname
];
99 delete challenges
[choice
];
100 if (choice
== "_random") {
101 // Pick a variant at random in the list
102 const index
= Math
.floor(Math
.random() * variants
.length
);
103 choice
= variants
[index
].name
;
107 {sid: sid
, name: obj
.name
, randvar: randvar
},
110 if (Math
.random() < 0.5) players
= players
.reverse();
111 launchGame(choice
, players
, {}); //empty options => default
114 // Place challenge and wait. 'randvar' indicate if we play anything
115 challenges
[vname
] = {sid: sid
, name: obj
.name
, randvar: randvar
};
118 // Set FEN after game was created
120 games
[obj
.gid
].fen
= obj
.fen
;
122 // Send back game informations
124 if (!games
[obj
.gid
]) send(sid
, "nogame");
125 else send(sid
, "gameinfo", games
[obj
.gid
]);
130 delete challenges
[obj
.vname
];
134 if (!games
[obj
.gid
]) send(sid
, "closerematch");
136 const myIndex
= (games
[obj
.gid
].players
[0].sid
== sid
? 0 : 1);
137 if (!games
[obj
.gid
].rematch
) games
[obj
.gid
].rematch
= [false, false];
138 games
[obj
.gid
].rematch
[myIndex
] = true;
139 if (games
[obj
.gid
].rematch
[1-myIndex
]) {
140 // Launch new game, colors reversed
141 launchGame(games
[obj
.gid
].vname
,
142 games
[obj
.gid
].players
.reverse(),
143 games
[obj
.gid
].options
);
147 // Rematch cancellation
149 if (games
[obj
.gid
]) {
150 const myIndex
= (games
[obj
.gid
].players
[0].sid
== sid
? 0 : 1);
151 send(games
[obj
.gid
].players
[1-myIndex
].sid
, "closerematch");
154 // Create game vs. friend
157 { sid: obj
.player
.sid
, name: obj
.player
.name
},
161 obj
.player
.color
== 'b' ||
162 (obj
.player
.color
== '' && Math
.random() < 0.5)
164 players
= players
.reverse();
166 launchGame(obj
.vname
, players
, obj
.options
);
169 // Join game vs. friend
171 if (!games
[obj
.gid
]) send(sid
, "jointoolate");
173 // Join a game (started by some other player)
174 const emptySlot
= games
[obj
.gid
].players
.findIndex(p
=> !p
);
175 if (emptySlot
< 0) send(sid
, "jointoolate");
176 games
[obj
.gid
].players
[emptySlot
] = {sid: sid
, name: obj
.name
};
177 const gameInfo
= Object
.assign(
178 // Provide seed so that both players initialize with same FEN
179 {seed: Math
.floor(Math
.random()*1984), gid: obj
.gid
},
181 for (let i
of [0, 1])
182 send(games
[obj
.gid
].players
[i
].sid
, "gamestart", gameInfo
);
185 // Relay a move + update games object
187 // If already received this move: skip
188 if (games
[obj
.gid
].fen
== obj
.fen
) break;
189 // Notify sender that the move is received:
190 send(sid
, "gotmove", {fen: obj
.fen
});
191 games
[obj
.gid
].fen
= obj
.fen
;
192 const playingWhite
= (games
[obj
.gid
].players
[0].sid
== sid
);
193 const oppSid
= games
[obj
.gid
].players
[playingWhite
? 1 : 0].sid
;
195 // NOTE: sending FEN also, to check it in "gotmove" below
196 () => send(oppSid
, "newmove", {moves: obj
.moves
, fen: obj
.fen
});
198 sendmoveTimeout1
[obj
.gid
] = setTimeout(sendMove
, 500);
199 sendmoveTimeout2
[obj
.gid
] = setTimeout(sendMove
, 1500);
200 sendmoveRetry
[obj
.gid
] = setInterval(sendMove
, 5000);
201 stopRetry
[obj
.gid
] = setTimeout(clearTrySendMove
, 31000);
204 if (games
[obj
.gid
].fen
== obj
.fen
) clearTrySendMove(obj
.gid
);
206 // Relay "game ends" message
208 const playingWhite
= (games
[obj
.gid
].players
[0].sid
== sid
);
209 const oppSid
= games
[obj
.gid
].players
[playingWhite
? 1 : 0].sid
;
210 if (obj
.relay
) send(oppSid
, "gameover", { gid: obj
.gid
});
211 games
[obj
.gid
].over
= true;
212 setTimeout( () => delete games
[obj
.gid
], 60000 );
217 socket
.on("close", () => {
219 for (const [key
, value
] of Object
.entries(challenges
)) {
220 if (value
.sid
== sid
) {
221 delete challenges
[key
];
222 break; //only one challenge per player
228 const interval
= setInterval(() => {
229 wss
.clients
.forEach((ws
) => {
230 if (ws
.isAlive
=== false) return ws
.terminate();
235 wss
.on('close', () => clearInterval(interval
));