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 = (dataAccess, 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_accept',
|
70 | 'chess_move',
|
71 | 'chess_game_end'];
|
72 |
|
73 | const messageType = msg.value.content.type;
|
74 | const isSituationMsg = relevantMessageTypes.find(m => m === messageType);
|
75 | return isSituationMsg !== undefined;
|
76 | }
|
77 |
|
78 | function getSituationObservable(gameId) {
|
79 |
|
80 | const gameMessages = backlinkUtils.getFilteredBackLinks(gameId, {
|
81 | filter: isSituationalChessMessage,
|
82 | });
|
83 |
|
84 | const msgRoot = getRootMessage(gameId);
|
85 |
|
86 | const players = computed([msgRoot], (msg) => {
|
87 |
|
88 | if (!msg) return null;
|
89 | else return MutantUtils.promiseToMutant(getPlayers(msg))
|
90 | }
|
91 | );
|
92 |
|
93 | const rematchState = getRematchState(gameId, gameMessages);
|
94 |
|
95 | return computed([msgRoot, myIdent, players, gameMessages, gameMessages.sync, rematchState], (
|
96 | rootMessage, ident, p, gameMessagesBacklinks, isSynced, rematchInfo
|
97 |
|
98 | ) => {
|
99 | if (!rootMessage || !isSynced || !p) return null;
|
100 | return makeSituation(gameId, rootMessage, ident, p, gameMessagesBacklinks, rematchInfo)
|
101 | });
|
102 | }
|
103 |
|
104 | function getRootMessage(gameId) {
|
105 | var result = Value();
|
106 |
|
107 | dataAccess.getInviteMessage(gameId, (err, msg) => {
|
108 | if (msg) {
|
109 | result.set(msg);
|
110 | } else {
|
111 | console.log(err);
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 | return null;
|
118 | }
|
119 | });
|
120 |
|
121 | return result;
|
122 | }
|
123 |
|
124 | function getRematchState(gameId, gameMessagesObservable) {
|
125 | return computed([gameMessagesObservable], (gameMessages) => {
|
126 |
|
127 | if (!gameMessages) return [];
|
128 |
|
129 | const rematchInvites = gameMessages.filter(msg => msg.value.content.type === "chess_invite" && msg.value.content.root === gameId);
|
130 |
|
131 | const gameStates = rematchInvites.map(msg => {
|
132 |
|
133 | var situation = getSituationSummaryObservable(msg.key);
|
134 |
|
135 | return computed([situation], gameState => {
|
136 |
|
137 |
|
138 |
|
139 | if (!gameState) return {
|
140 | status: "pending"
|
141 | };
|
142 |
|
143 | return {
|
144 | gameId: msg.key,
|
145 | status: gameState.status.status === "invited" ? "invited" : "accepted",
|
146 | isMyInvite: msg.value.author === myIdent
|
147 | }
|
148 | })
|
149 |
|
150 | });
|
151 |
|
152 | return MutantArray(gameStates);
|
153 | });
|
154 | }
|
155 |
|
156 | function getSituationSummaryObservable(gameId) {
|
157 | return computed([getSituationObservable(gameId)], (situation) => {
|
158 | if (situation == null) {
|
159 | return null;
|
160 | }
|
161 | return situationToSummary(situation);
|
162 | });
|
163 | }
|
164 |
|
165 | function getSituation(gameId) {
|
166 | return MutantUtils.mutantToPromise(getSituationObservable(gameId));
|
167 | }
|
168 |
|
169 | function makeMove(
|
170 | gameId,
|
171 | ply,
|
172 | originSquare,
|
173 | destinationSquare,
|
174 | promotion,
|
175 | pgnMove,
|
176 | fen,
|
177 | respondsTo,
|
178 | ) {
|
179 | const post = {
|
180 | type: 'chess_move',
|
181 | ply,
|
182 | root: gameId,
|
183 | orig: originSquare,
|
184 | dest: destinationSquare,
|
185 | pgnMove,
|
186 | fen,
|
187 | };
|
188 |
|
189 | if (promotion) {
|
190 | post.promotion = promotion;
|
191 | }
|
192 |
|
193 | if (respondsTo) {
|
194 | post.branch = respondsTo;
|
195 | }
|
196 |
|
197 | return new Promise((resolve, reject) => {
|
198 | dataAccess.publishPublicChessMessage(post, (err, msg) => {
|
199 | if (err) {
|
200 | reject(err);
|
201 | } else {
|
202 | resolve(msg);
|
203 | }
|
204 | });
|
205 | });
|
206 | }
|
207 |
|
208 | function addPropertyIfNotEmpty(obj, key, value) {
|
209 | if (value) {
|
210 | obj[key] = value;
|
211 | }
|
212 | }
|
213 |
|
214 | function resignGame(gameId, respondsTo) {
|
215 | const post = {
|
216 | type: 'chess_game_end',
|
217 | status: 'resigned',
|
218 | root: gameId,
|
219 | };
|
220 |
|
221 | addPropertyIfNotEmpty(post, 'branch', respondsTo);
|
222 |
|
223 | return new Promise((resolve, reject) => {
|
224 | dataAccess.publishPublicChessMessage(post, (err, msg) => {
|
225 | if (err) {
|
226 | reject(err);
|
227 | } else {
|
228 | resolve(msg);
|
229 | }
|
230 | });
|
231 | });
|
232 | }
|
233 |
|
234 | function endGame(
|
235 | gameId,
|
236 | status,
|
237 | winner,
|
238 | fen,
|
239 | ply,
|
240 | originSquare,
|
241 | destinationSquare,
|
242 | pgnMove,
|
243 | respondsTo,
|
244 | ) {
|
245 | return new Promise((resolve, reject) => {
|
246 | const post = {
|
247 | type: 'chess_game_end',
|
248 | status,
|
249 | ply,
|
250 | fen,
|
251 | root: gameId,
|
252 | };
|
253 |
|
254 |
|
255 |
|
256 | addPropertyIfNotEmpty(post, 'winner', winner);
|
257 | addPropertyIfNotEmpty(post, 'ply', ply);
|
258 | addPropertyIfNotEmpty(post, 'orig', originSquare);
|
259 | addPropertyIfNotEmpty(post, 'dest', destinationSquare);
|
260 | addPropertyIfNotEmpty(post, 'pgnMove', pgnMove);
|
261 | addPropertyIfNotEmpty(post, 'branch', respondsTo);
|
262 |
|
263 | dataAccess.publishPublicChessMessage(post, (err, msg) => {
|
264 | if (err) {
|
265 | reject(err);
|
266 | } else {
|
267 | resolve(msg);
|
268 | }
|
269 | });
|
270 | });
|
271 | }
|
272 |
|
273 | return {
|
274 | getPlayers,
|
275 | getSituation,
|
276 | getSituationObservable,
|
277 | getSituationSummaryObservable,
|
278 | getSmallGameSummary,
|
279 | makeMove,
|
280 | resignGame,
|
281 | endGame,
|
282 | };
|
283 | };
|