Complete GameStorage.update. TODO: move transmission
[vchess.git] / client / src / views / Game.vue
... / ...
CommitLineData
1<!-- TODO: component Game, + handle players + observers connect/disconnect
2 event = "gameconnect" ...etc
3 connect/disconnect with sid+name (ID not required); name slightly redundant but easier
4quand on arrive dans la partie, on poll les sids pour savoir qui est en ligne (ping)
5(éventuel échange lastate avec les connectés, pong ...etc)
6ensuite quand qqun se deco il suffit d'écouter "disconnect"
7pareil quand quelqu'un reco.
8(c'est assez rudimentaire et écoute trop de messages, mais dans un premier temps...)
9 // TODO: [in game] send move + elapsed time (in milliseconds); in case of "lastate" message too
10// TODO: if I'm an observer and player(s) disconnect/reconnect, how to find me ?
11// onClick :: ask full game to remote player, and register as an observer in game
12// (use gameId to communicate)
13// on landing on game :: if gameId not found locally, check remotely
14// ==> il manque un param dans game : "remoteId"
15-->
16<template lang="pug">
17.row
18 .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
19 BaseGame(:game="game" :vr="vr" ref="basegame"
20 @newmove="processMove" @gameover="gameOver")
21 .button-group(v-if="game.mode!='analyze'")
22 button(@click="offerDraw") Draw
23 button(@click="abortGame") Abort
24 button(@click="resign") Resign
25 div(v-if="game.mode=='corr'")
26 textarea(v-show="score=='*' && vr.turn==game.mycolor" v-model="corrMsg")
27 div(v-show="cursor>=0") {{ moves[cursor].message }}
28</template>
29
30<script>
31import BaseGame from "@/components/BaseGame.vue";
32//import Chat from "@/components/Chat.vue";
33//import MoveList from "@/components/MoveList.vue";
34import { store } from "@/store";
35import { GameStorage } from "@/utils/storage";
36
37export default {
38 name: 'my-game',
39 components: {
40 BaseGame,
41 },
42 // gameRef: to find the game in (potentially remote) storage
43 data: function() {
44 return {
45 st: store.state,
46 gameRef: { //given in URL (rid = remote ID)
47 id: "",
48 rid: ""
49 },
50 game: { }, //passed to BaseGame
51 vr: null, //"variant rules" object initialized from FEN
52 drawOfferSent: false, //did I just ask for draw? (TODO: draw variables?)
53 people: [ ], //potential observers (TODO)
54 };
55 },
56 watch: {
57 '$route' (to, from) {
58 if (!!to.params["id"])
59 {
60 this.gameRef.id = to.params["id"];
61 this.gameRef.rid = to.query["rid"];
62 this.loadGame();
63 }
64 },
65 },
66 created: function() {
67 if (!!this.$route.params["id"])
68 {
69 this.gameRef.id = this.$route.params["id"];
70 this.gameRef.rid = this.$route.query["rid"];
71 this.loadGame();
72 }
73 // TODO: how to know who is observing ? Send message to everyone with game ID ?
74 // and then just listen to (dis)connect events
75
76
77 // server always send "connect on " + URL ; then add to observers if game...
78 // detect multiple tabs connected (when connect ask server if my SID is already in use)
79// router when access a game page tell to server I joined + game ID (no need rid)
80// and ask server for current joined (= observers)
81// when send to chat (or a move), reach only this group (send gid along)
82
83 // --> doivent être enregistrés comme observers au niveau du serveur...
84 // non: poll users + events startObserving / stopObserving
85 // (à faire au niveau du routeur ?)
86
87
88 // TODO: also handle "draw accepted" (use opponents array?)
89 // --> must give this info also when sending lastState...
90 // and, if all players agree then OK draw (end game ...etc)
91 const socketMessageListener = msg => {
92 const data = JSON.parse(msg.data);
93 let L = undefined;
94 switch (data.code)
95 {
96 case "newmove":
97 // TODO: observer on dark games must see all board ? Or alternate ? (seems better)
98 // ...or just see nothing as on buho21
99 this.$refs["basegame"].play(
100 data.move, this.game.vname!="Dark" ? "animate" : null);
101 this.processMove(data.move);
102
103
104
105// TODO:
106// send filtered_move + elapsed time
107// receive same. (update clock) + update (our) initime if it's my turn
108// + update fen (using vr.getFen())
109
110
111
112
113 break;
114 case "pong": //received if we sent a ping (game still alive on our side)
115 if (this.gameRef.id != data.gameId)
116 break; //games IDs don't match: the game is definitely over...
117 this.oppConnected = true;
118 // Send our "last state" informations to opponent(s)
119 L = this.vr.moves.length;
120 Object.keys(this.opponents).forEach(oid => {
121 this.st.conn.send(JSON.stringify({
122 code: "lastate",
123 oppid: oid,
124 gameId: this.gameRef.id,
125 lastMove: (L>0?this.vr.moves[L-1]:undefined),
126 movesCount: L,
127 }));
128 });
129 break;
130 // TODO: refactor this, because at 3 or 4 players we may have missed 2 or 3 moves
131 case "lastate": //got opponent infos about last move
132 L = this.vr.moves.length;
133 if (this.gameRef.id != data.gameId)
134 break; //games IDs don't match: nothing we can do...
135 // OK, opponent still in game (which might be over)
136 if (this.score != "*")
137 {
138 // We finished the game (any result possible)
139 this.st.conn.send(JSON.stringify({
140 code: "lastate",
141 oppid: data.oppid,
142 gameId: this.gameRef.id,
143 score: this.score,
144 }));
145 }
146 else if (!!data.score) //opponent finished the game
147 this.endGame(data.score);
148 else if (data.movesCount < L)
149 {
150 // We must tell last move to opponent
151 this.st.conn.send(JSON.stringify({
152 code: "lastate",
153 oppid: this.opponent.id,
154 gameId: this.gameRef.id,
155 lastMove: this.vr.moves[L-1],
156 movesCount: L,
157 }));
158 }
159 else if (data.movesCount > L) //just got last move from him
160 this.play(data.lastMove, "animate");
161 break;
162 case "resign": //..you won!
163 this.endGame(this.game.mycolor=="w"?"1-0":"0-1");
164 break;
165 // TODO: also use (dis)connect info to count online players?
166 case "gameconnect":
167 case "gamedisconnect":
168 if (this.mode=="human")
169 {
170 const online = (data.code == "connect");
171 // If this is an opponent ?
172 if (!!this.opponents[data.id])
173 this.opponents[data.id].online = online;
174 else
175 {
176 // Or an observer ?
177 if (!online)
178 delete this.people[data.id];
179 else
180 this.people[data.id] = data.name;
181 }
182 }
183 break;
184 }
185 };
186 const socketCloseListener = () => {
187 this.st.conn.addEventListener('message', socketMessageListener);
188 this.st.conn.addEventListener('close', socketCloseListener);
189 };
190 this.st.conn.onmessage = socketMessageListener;
191 this.st.conn.onclose = socketCloseListener;
192 },
193 // dans variant.js (plutôt room.js) conn gère aussi les challenges
194 // et les chats dans chat.js. Puis en webRTC, repenser tout ça.
195 methods: {
196 offerDraw: function() {
197 if (!confirm("Offer draw?"))
198 return;
199 // Stay in "draw offer sent" state until next move is played
200 this.drawOfferSent = true;
201 if (this.subMode == "corr")
202 {
203 // TODO: set drawOffer on in game (how ?)
204 }
205 else //live game
206 {
207 this.opponents.forEach(o => {
208 if (!!o.online)
209 {
210 try {
211 this.st.conn.send(JSON.stringify({code: "draw", oppid: o.id}));
212 } catch (INVALID_STATE_ERR) {
213 return;
214 }
215 }
216 });
217 }
218 },
219 // + conn handling: "draw" message ==> agree for draw (if we have "drawOffered" at true)
220 receiveDrawOffer: function() {
221 //if (...)
222 // TODO: ignore if preventDrawOffer is set; otherwise show modal box with option "prevent future offers"
223 // if accept: send message "draw"
224 },
225 abortGame: function() {
226 if (!confirm("Abort the game?"))
227 return;
228 //+ bouton "abort" avec score == "?" + demander confirmation pour toutes ces actions,
229 //send message: "gameOver" avec score "?"
230 // ==> BaseGame must listen to game.score change, and call "endgame(score)" in this case
231 },
232 resign: function(e) {
233 if (!confirm("Resign the game?"))
234 return;
235 if (this.mode == "human" && this.oppConnected(this.oppid))
236 {
237 try {
238 this.st.conn.send(JSON.stringify({code: "resign", oppid: this.oppid}));
239 } catch (INVALID_STATE_ERR) {
240 return;
241 }
242 }
243 this.endGame(this.mycolor=="w"?"0-1":"1-0");
244 },
245 // 4 cases for loading a game:
246 // - from localStorage (one running game I play)
247 // - from indexedDB (one completed live game)
248 // - from server (one correspondance game I play[ed] or not)
249 // - from remote peer (one live game I don't play, finished or not)
250 loadGame: function() {
251 GameStorage.get(this.gameRef, async (game) => {
252 this.game = Object.assign({},
253 game,
254 // NOTE: assign mycolor here, since BaseGame could also bs VS computer
255 {mycolor: [undefined,"w","b"][1 + game.players.findIndex(
256 p => p.sid == this.st.user.sid)]},
257 );
258 const vModule = await import("@/variants/" + game.vname + ".js");
259 window.V = vModule.VariantRules;
260 this.vr = new V(game.fen);
261 // Post-processing: decorate each move with current FEN:
262 // (to be able to jump to any position quickly)
263 game.moves.forEach(move => {
264 move.color = this.vr.turn;
265 vr.play(move);
266 move.fen = this.vr.getFen();
267 });
268 this.vr.re_init(game.fen);
269 });
270// // Poll all players except me (if I'm playing) to know online status.
271// // --> Send ping to server (answer pong if players[s] are connected)
272// if (this.gameInfo.players.some(p => p.sid == this.st.user.sid))
273// {
274// this.game.players.forEach(p => {
275// if (p.sid != this.st.user.sid)
276// this.st.conn.send(JSON.stringify({code:"ping", oppid:p.sid}));
277// });
278// }
279 },
280 // TODO: refactor this old "oppConnected" logic
281 oppConnected: function(uid) {
282 return this.opponents.some(o => o.id == uid && o.online);
283 },
284 // Post-process a move (which was just played)
285 processMove: function(move) {
286 if (!this.game.mycolor)
287 return; //I'm just an observer
288 // Update storage (corr or live)
289 const colorIdx = ["w","b","g","r"][move.color];
290 // https://stackoverflow.com/a/38750895
291 const allowed_fields = ["appear", "vanish", "start", "end"];
292 const filtered_move = Object.keys(move)
293 .filter(key => allowed_fields.includes(key))
294 .reduce((obj, key) => {
295 obj[key] = raw[key];
296 return obj;
297 }, {});
298 // Send move ("newmove" event) to opponent(s) (if ours)
299 // (otherwise move.elapsed is supposed to be already transmitted)
300 if (move.color == this.game.mycolor)
301 {
302 const elapsed = Date.now() - GameStorage.getInitime();
303 this.game.players.forEach(p => {
304 if (p.sid != this.st.user.sid)
305 this.st.conn.send("newmove",
306 {
307 target: p.sid,
308 move: Object.assign({}, filtered_move, {elapsed: elapsed}),
309 });
310 });
311 move.elapsed = elapsed;
312 }
313 GameStorage.update({
314 colorIdx: colorIdx,
315 move: filtered_move,
316 fen: move.fen,
317 elapsed: move.elapsed,
318 increment: this.game.increment, //redundant but faster
319 initime: (this.vr.turn == this.game.mycolor), //it's my turn
320 });
321 },
322 // NOTE: this update function should also work for corr games
323 gameOver: function(score) {
324 GameStorage.update({
325 score: score,
326 });
327 },
328 },
329};
330</script>
331
332<!-- TODO:
333// Abort possible à tout moment avec message
334// Sorry I have to go / Game seems over / Game is not interesting
335// code "T" pour score "perte au temps" ?
336-->