On update + clocks thinking
[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 break;
102 case "pong": //received if we sent a ping (game still alive on our side)
103 if (this.gameRef.id != data.gameId)
104 break; //games IDs don't match: the game is definitely over...
105 this.oppConnected = true;
106 // Send our "last state" informations to opponent(s)
107 L = this.vr.moves.length;
108 Object.keys(this.opponents).forEach(oid => {
109 this.st.conn.send(JSON.stringify({
110 code: "lastate",
111 oppid: oid,
112 gameId: this.gameRef.id,
113 lastMove: (L>0?this.vr.moves[L-1]:undefined),
114 movesCount: L,
115 }));
116 });
117 break;
118 // TODO: refactor this, because at 3 or 4 players we may have missed 2 or 3 moves
119 case "lastate": //got opponent infos about last move
120 L = this.vr.moves.length;
121 if (this.gameRef.id != data.gameId)
122 break; //games IDs don't match: nothing we can do...
123 // OK, opponent still in game (which might be over)
124 if (this.score != "*")
125 {
126 // We finished the game (any result possible)
127 this.st.conn.send(JSON.stringify({
128 code: "lastate",
129 oppid: data.oppid,
130 gameId: this.gameRef.id,
131 score: this.score,
132 }));
133 }
134 else if (!!data.score) //opponent finished the game
135 this.endGame(data.score);
136 else if (data.movesCount < L)
137 {
138 // We must tell last move to opponent
139 this.st.conn.send(JSON.stringify({
140 code: "lastate",
141 oppid: this.opponent.id,
142 gameId: this.gameRef.id,
143 lastMove: this.vr.moves[L-1],
144 movesCount: L,
145 }));
146 }
147 else if (data.movesCount > L) //just got last move from him
148 this.play(data.lastMove, "animate");
149 break;
150 case "resign": //..you won!
151 this.endGame(this.game.mycolor=="w"?"1-0":"0-1");
152 break;
153 // TODO: also use (dis)connect info to count online players?
154 case "gameconnect":
155 case "gamedisconnect":
156 if (this.mode=="human")
157 {
158 const online = (data.code == "connect");
159 // If this is an opponent ?
160 if (!!this.opponents[data.id])
161 this.opponents[data.id].online = online;
162 else
163 {
164 // Or an observer ?
165 if (!online)
166 delete this.people[data.id];
167 else
168 this.people[data.id] = data.name;
169 }
170 }
171 break;
172 }
173 };
174 const socketCloseListener = () => {
175 this.st.conn.addEventListener('message', socketMessageListener);
176 this.st.conn.addEventListener('close', socketCloseListener);
177 };
178 this.st.conn.onmessage = socketMessageListener;
179 this.st.conn.onclose = socketCloseListener;
180 },
181 // dans variant.js (plutôt room.js) conn gère aussi les challenges
182 // et les chats dans chat.js. Puis en webRTC, repenser tout ça.
183 methods: {
184 offerDraw: function() {
185 if (!confirm("Offer draw?"))
186 return;
187 // Stay in "draw offer sent" state until next move is played
188 this.drawOfferSent = true;
189 if (this.subMode == "corr")
190 {
191 // TODO: set drawOffer on in game (how ?)
192 }
193 else //live game
194 {
195 this.opponents.forEach(o => {
196 if (!!o.online)
197 {
198 try {
199 this.st.conn.send(JSON.stringify({code: "draw", oppid: o.id}));
200 } catch (INVALID_STATE_ERR) {
201 return;
202 }
203 }
204 });
205 }
206 },
207 // + conn handling: "draw" message ==> agree for draw (if we have "drawOffered" at true)
208 receiveDrawOffer: function() {
209 //if (...)
210 // TODO: ignore if preventDrawOffer is set; otherwise show modal box with option "prevent future offers"
211 // if accept: send message "draw"
212 },
213 abortGame: function() {
214 if (!confirm("Abort the game?"))
215 return;
216 //+ bouton "abort" avec score == "?" + demander confirmation pour toutes ces actions,
217 //send message: "gameOver" avec score "?"
218 // ==> BaseGame must listen to game.score change, and call "endgame(score)" in this case
219 },
220 resign: function(e) {
221 if (!confirm("Resign the game?"))
222 return;
223 if (this.mode == "human" && this.oppConnected(this.oppid))
224 {
225 try {
226 this.st.conn.send(JSON.stringify({code: "resign", oppid: this.oppid}));
227 } catch (INVALID_STATE_ERR) {
228 return;
229 }
230 }
231 this.endGame(this.mycolor=="w"?"0-1":"1-0");
232 },
233 // 4 cases for loading a game:
234 // - from localStorage (one running game I play)
235 // - from indexedDB (one completed live game)
236 // - from server (one correspondance game I play[ed] or not)
237 // - from remote peer (one live game I don't play, finished or not)
238 loadGame: function() {
239 GameStorage.get(this.gameRef, async (game) => {
240 this.game = Object.assign({},
241 game,
242 // NOTE: assign mycolor here, since BaseGame could also bs VS computer
243 {mycolor: [undefined,"w","b"][1 + game.players.findIndex(
244 p => p.sid == this.st.user.sid)]},
245 );
246 const vModule = await import("@/variants/" + game.vname + ".js");
247 window.V = vModule.VariantRules;
248 this.vr = new V(game.fen);
249 // Post-processing: decorate each move with current FEN:
250 // (to be able to jump to any position quickly)
251 game.moves.forEach(move => {
252 vr.play(move); //side-effect: set move.fen
253 });
254 this.vr.re_init(game.fen);
255 });
256// // Poll all players except me (if I'm playing) to know online status.
257// // --> Send ping to server (answer pong if players[s] are connected)
258// if (this.gameInfo.players.some(p => p.sid == this.st.user.sid))
259// {
260// this.game.players.forEach(p => {
261// if (p.sid != this.st.user.sid)
262// this.st.conn.send(JSON.stringify({code:"ping", oppid:p.sid}));
263// });
264// }
265 },
266 // TODO: refactor this old "oppConnected" logic
267 oppConnected: function(uid) {
268 return this.opponents.some(o => o.id == uid && o.online);
269 },
270 processMove: function(move) {
271 if (!this.game.mycolor)
272 return; //I'm just an observer
273 // TODO: update storage (corr or live),
274 GameStorage.update({
275 fen: move: clocks: ................ // TODO
276 });
277 // Send move ("newmove" event) to opponent(s) (if ours)
278 if (move.disappear[0].c == this.game.mycolor)
279 {
280 this.game.players.forEach(p => {
281 if (p.sid != this.st.user.sid)
282 this.st.conn.send("newmove", {target:p.sid, move:move});
283 });
284 }
285 },
286 gameOver: function(score) {
287 // TODO: GameStorage.update with score
288 // NOTE: this update function should also work for corr games
289 },
290 },
291};
292</script>
293
294// TODO: utiliser "started" (renommer) pour se souvenir du timestamp où un user a commencé à réfléchir à un coup. Le sauvegarder dans update aussi...
295
296// variable initime à récupérer de game aussi (et pas "started")
297// si initime à -1 ou undefined alors pas commencé. Abort possible à tout moment avec message
298// Sorry I have to go / Game seems over / Game is not interesting
299
300move.clock à màj avec (current) clock - temps de réflexion (now() - initime) + increment
301après chaque coup dans une partie live ou corr non terminée.
302--> donc à Game.update on passe directement clock
303
304code "T" pour score "perte au temps" ?