1 | const computed = require('mutant/computed');
|
2 | const MutantUtils = require('./mutant_utils')();
|
3 | const MutantArray = require('mutant/array');
|
4 | const makeSituation = require('./model/situation');
|
5 | const Value = require('mutant/value');
|
6 |
|
7 | module.exports = (sbot, myIdent, backlinkUtils, socialCtrl) => {
|
8 |
|
9 | function getPlayers(gameRootMessage) {
|
10 | return new Promise((resolve, reject) => {
|
11 | const authorId = gameRootMessage.author;
|
12 | const invited = gameRootMessage.content.inviting;
|
13 |
|
14 | const authorColour = gameRootMessage.content.myColor === 'white' ? gameRootMessage.content.myColor : 'black';
|
15 | const players = {};
|
16 |
|
17 | Promise.all([authorId, invited].map(socialCtrl.getDisplayName))
|
18 | .then((names) => {
|
19 | players[authorId] = {};
|
20 | players[authorId].colour = authorColour;
|
21 | [players[authorId].name] = names;
|
22 | players[authorId].id = authorId;
|
23 |
|
24 | players[invited] = {};
|
25 | players[invited].colour = authorColour === 'white' ? 'black' : 'white';
|
26 | [, players[invited].name] = names;
|
27 | players[invited].id = invited;
|
28 |
|
29 | resolve(players);
|
30 | });
|
31 | });
|
32 | }
|
33 |
|
34 | function situationToSummary(gameSituation) {
|
35 | const summary = {
|
36 | gameId: gameSituation.gameId,
|
37 | fen: gameSituation.fen,
|
38 | players: gameSituation.players,
|
39 | toMove: gameSituation.toMove,
|
40 | status: gameSituation.status,
|
41 | lastMove: gameSituation.lastMove,
|
42 | check: gameSituation.check,
|
43 | lastUpdateTime: gameSituation.lastUpdateTime,
|
44 | coloursToPlayer: gameSituation.coloursToPlayer
|
45 | };
|
46 |
|
47 | return summary;
|
48 | }
|
49 |
|
50 | |
51 |
|
52 |
|
53 |
|
54 | function getSmallGameSummary(gameId) {
|
55 | return MutantUtils.mutantToPromise(getSituationSummaryObservable(gameId));
|
56 | }
|
57 |
|
58 | |
59 |
|
60 |
|
61 |
|
62 |
|
63 | function isSituationalChessMessage(msg) {
|
64 | if (!msg.value || !msg.value.content) {
|
65 | return false;
|
66 | }
|
67 |
|
68 | const relevantMessageTypes = [
|
69 | 'chess_invite',
|
70 | 'chess_invite_accept',
|
71 | 'chess_move',
|
72 | 'chess_game_end'];
|
73 |
|
74 | const messageType = msg.value.content.type;
|
75 | const isSituationMsg = relevantMessageTypes.find(m => m === messageType);
|
76 | return isSituationMsg !== undefined;
|
77 | }
|
78 |
|
79 | function getSituationObservable(gameId) {
|
80 |
|
81 | const gameMessages = backlinkUtils.getFilteredBackLinks(gameId, {
|
82 | filter: isSituationalChessMessage,
|
83 | });
|
84 |
|
85 | const msgRoot = getRootMessage(gameId);
|
86 |
|
87 | const players = computed([msgRoot], (msg) => {
|
88 |
|
89 | if (!msg) return null;
|
90 | else return MutantUtils.promiseToMutant(getPlayers(msg))
|
91 | }
|
92 | );
|
93 |
|
94 | const rematchState = getRematchState(gameId, gameMessages);
|
95 |
|
96 | return computed([msgRoot, myIdent, players, gameMessages, gameMessages.sync, rematchState], (
|
97 | rootMessage, ident, p, gameMessagesBacklinks, isSynced, rematchInfo
|
98 |
|
99 | ) => {
|
100 | if (!rootMessage || !isSynced || !p) return null;
|
101 | return makeSituation(gameId, rootMessage, ident, p, gameMessagesBacklinks, rematchInfo)
|
102 | });
|
103 | }
|
104 |
|
105 | function getRootMessage(gameId) {
|
106 | var result = Value();
|
107 |
|
108 | sbot.get(gameId, (err, msg) => {
|
109 | if (msg) {
|
110 | result.set(msg);
|
111 | } else {
|
112 | console.log(err);
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 | return null;
|
119 | }
|
120 | });
|
121 |
|
122 | return result;
|
123 | }
|
124 |
|
125 | function getRematchState(gameId, gameMessagesObservable) {
|
126 | return computed([gameMessagesObservable], (gameMessages) => {
|
127 |
|
128 | if (!gameMessages) return [];
|
129 |
|
130 | const rematchInvites = gameMessages.filter(msg => msg.value.content.type === "chess_invite" && msg.value.content.root === gameId);
|
131 |
|
132 | const gameStates = rematchInvites.map(msg => {
|
133 |
|
134 | var situation = getSituationSummaryObservable(msg.key);
|
135 |
|
136 | return computed([situation], gameState => {
|
137 |
|
138 |
|
139 |
|
140 | if (!gameState) return {
|
141 | status: "pending"
|
142 | };
|
143 |
|
144 | return {
|
145 | gameId: msg.key,
|
146 | status: gameState.status.status === "invited" ? "invited" : "accepted",
|
147 | isMyInvite: msg.value.author === myIdent
|
148 | }
|
149 | })
|
150 |
|
151 | });
|
152 |
|
153 | return MutantArray(gameStates);
|
154 | });
|
155 | }
|
156 |
|
157 | function getSituationSummaryObservable(gameId) {
|
158 | return computed([getSituationObservable(gameId)], (situation) => {
|
159 | if (situation == null) {
|
160 | return null;
|
161 | }
|
162 | return situationToSummary(situation);
|
163 | });
|
164 | }
|
165 |
|
166 | function getSituation(gameId) {
|
167 | return MutantUtils.mutantToPromise(getSituationObservable(gameId));
|
168 | }
|
169 |
|
170 | function makeMove(
|
171 | gameId,
|
172 | ply,
|
173 | originSquare,
|
174 | destinationSquare,
|
175 | promotion,
|
176 | pgnMove,
|
177 | fen,
|
178 | respondsTo,
|
179 | ) {
|
180 | const post = {
|
181 | type: 'chess_move',
|
182 | ply,
|
183 | root: gameId,
|
184 | orig: originSquare,
|
185 | dest: destinationSquare,
|
186 | pgnMove,
|
187 | fen,
|
188 | };
|
189 |
|
190 | if (promotion) {
|
191 | post.promotion = promotion;
|
192 | }
|
193 |
|
194 | if (respondsTo) {
|
195 | post.branch = respondsTo;
|
196 | }
|
197 |
|
198 | return new Promise((resolve, reject) => {
|
199 | sbot.publish(post, (err, msg) => {
|
200 | if (err) {
|
201 | reject(err);
|
202 | } else {
|
203 | resolve(msg);
|
204 | }
|
205 | });
|
206 | });
|
207 | }
|
208 |
|
209 | function addPropertyIfNotEmpty(obj, key, value) {
|
210 | if (value) {
|
211 | obj[key] = value;
|
212 | }
|
213 | }
|
214 |
|
215 | function resignGame(gameId, respondsTo) {
|
216 | const post = {
|
217 | type: 'chess_game_end',
|
218 | status: 'resigned',
|
219 | root: gameId,
|
220 | };
|
221 |
|
222 | addPropertyIfNotEmpty(post, 'branch', respondsTo);
|
223 |
|
224 | return new Promise((resolve, reject) => {
|
225 | sbot.publish(post, (err, msg) => {
|
226 | if (err) {
|
227 | reject(err);
|
228 | } else {
|
229 | resolve(msg);
|
230 | }
|
231 | });
|
232 | });
|
233 | }
|
234 |
|
235 | function endGame(
|
236 | gameId,
|
237 | status,
|
238 | winner,
|
239 | fen,
|
240 | ply,
|
241 | originSquare,
|
242 | destinationSquare,
|
243 | pgnMove,
|
244 | respondsTo,
|
245 | ) {
|
246 | return new Promise((resolve, reject) => {
|
247 | const post = {
|
248 | type: 'chess_game_end',
|
249 | status,
|
250 | ply,
|
251 | fen,
|
252 | root: gameId,
|
253 | };
|
254 |
|
255 |
|
256 |
|
257 | addPropertyIfNotEmpty(post, 'winner', winner);
|
258 | addPropertyIfNotEmpty(post, 'ply', ply);
|
259 | addPropertyIfNotEmpty(post, 'orig', originSquare);
|
260 | addPropertyIfNotEmpty(post, 'dest', destinationSquare);
|
261 | addPropertyIfNotEmpty(post, 'pgnMove', pgnMove);
|
262 | addPropertyIfNotEmpty(post, 'branch', respondsTo);
|
263 |
|
264 | sbot.publish(post, (err, msg) => {
|
265 | if (err) {
|
266 | reject(err);
|
267 | } else {
|
268 | resolve(msg);
|
269 | }
|
270 | });
|
271 | });
|
272 | }
|
273 |
|
274 | return {
|
275 | getPlayers,
|
276 | getSituation,
|
277 | getSituationObservable,
|
278 | getSituationSummaryObservable,
|
279 | getSmallGameSummary,
|
280 | makeMove,
|
281 | resignGame,
|
282 | endGame,
|
283 | };
|
284 | };
|