On the way to simplify : gameState + gameInfo everywhere = game
[vchess.git] / client / src / views / Game.vue
CommitLineData
a6bddfc6
BA
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
4ce15fd9
BA
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...)
7b626bdd 9 // TODO: [in game] send move + elapsed time (in milliseconds); in case of "lastate" message too
ba82879c
BA
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"
a6bddfc6 15-->
a6088c90
BA
16<template lang="pug">
17.row
18 .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
d6c1bf37 19 BaseGame(:vname="game.vname" :game="game" :analyze="analyze" :vr="vr"
ba82879c 20 ref="basegame" @newmove="processMove")
a6088c90
BA
21 .button-group(v-if="mode!='analyze'")
22 button(@click="offerDraw") Draw
23 button(@click="abortGame") Abort
24 button(@click="resign") Resign
25 div(v-if="mode=='corr'")
26 textarea(v-show="score=='*' && vr.turn==mycolor" v-model="corrMsg")
27 div(v-show="cursor>=0") {{ moves[cursor].message }}
28</template>
29
30<script>
46284a2f 31import BaseGame from "@/components/BaseGame.vue";
a6088c90
BA
32//import Chat from "@/components/Chat.vue";
33//import MoveList from "@/components/MoveList.vue";
34import { store } from "@/store";
d2634386 35import { GameStorage } from "@/utils/storage";
a6088c90
BA
36
37export default {
38 name: 'my-game',
39 components: {
40 BaseGame,
41 },
f7121527 42 // gameRef: to find the game in (potentially remote) storage
a6088c90 43 // mode: "live" or "corr" (correspondance game), or "analyze"
a6088c90
BA
44 data: function() {
45 return {
46 st: store.state,
d2634386 47 gameRef: {id: "", rid: ""}, //given in URL (rid = remote ID)
d6c1bf37 48 game: {}, //passed to BaseGame
4fe5664d 49 vr: null, //TODO
ce87ac6a 50 mode: "analyze", //mutable
d2634386
BA
51 drawOfferSent: false, //did I just ask for draw? (TODO: draw variables?)
52 people: [], //potential observers (TODO)
a6088c90
BA
53 };
54 },
ba82879c
BA
55 computed: {
56 analyze: function() {
57 return this.mode == "analyze";
58 },
59 },
a6088c90 60 watch: {
f7121527 61 '$route' (to, from) {
4fe5664d
BA
62 if (!!to.params["id"])
63 {
64 this.gameRef.id = to.params["id"];
65 this.gameRef.rid = to.query["rid"];
66 this.loadGame();
67 }
a6088c90
BA
68 },
69 },
a6088c90 70 created: function() {
f7121527 71 if (!!this.$route.params["id"])
a6088c90 72 {
f7121527
BA
73 this.gameRef.id = this.$route.params["id"];
74 this.gameRef.rid = this.$route.query["rid"];
b196f8ea 75 this.loadGame();
f7121527 76 }
f7121527
BA
77 // TODO: how to know who is observing ? Send message to everyone with game ID ?
78 // and then just listen to (dis)connect events
79
3b450453
BA
80
81 // server always send "connect on " + URL ; then add to observers if game...
82 // detect multiple tabs connected (when connect ask server if my SID is already in use)
83// router when access a game page tell to server I joined + game ID (no need rid)
84// and ask server for current joined (= observers)
85// when send to chat (or a move), reach only this group (send gid along)
86
87 // --> doivent être enregistrés comme observers au niveau du serveur...
88 // non: poll users + events startObserving / stopObserving
89
90
a6088c90
BA
91 // TODO: also handle "draw accepted" (use opponents array?)
92 // --> must give this info also when sending lastState...
93 // and, if all players agree then OK draw (end game ...etc)
94 const socketMessageListener = msg => {
95 const data = JSON.parse(msg.data);
96 let L = undefined;
97 switch (data.code)
98 {
f7121527
BA
99 case "newmove":
100 // TODO: observer on dark games must see all board ? Or alternate ? (seems better)
101 // ...or just see nothing as on buho21
4fe5664d 102 this.$refs["basegame"].play(
d6c1bf37 103 data.move, this.game.vname!="Dark" ? "animate" : null);
a6088c90
BA
104 break;
105 case "pong": //received if we sent a ping (game still alive on our side)
106 if (this.gameRef.id != data.gameId)
f7121527 107 break; //games IDs don't match: the game is definitely over...
a6088c90
BA
108 this.oppConnected = true;
109 // Send our "last state" informations to opponent(s)
110 L = this.vr.moves.length;
111 Object.keys(this.opponents).forEach(oid => {
112 this.conn.send(JSON.stringify({
113 code: "lastate",
114 oppid: oid,
115 gameId: this.gameRef.id,
116 lastMove: (L>0?this.vr.moves[L-1]:undefined),
117 movesCount: L,
118 }));
119 });
120 break;
f7121527 121 // TODO: refactor this, because at 3 or 4 players we may have missed 2 or 3 moves
a6088c90
BA
122 case "lastate": //got opponent infos about last move
123 L = this.vr.moves.length;
124 if (this.gameRef.id != data.gameId)
125 break; //games IDs don't match: nothing we can do...
126 // OK, opponent still in game (which might be over)
127 if (this.score != "*")
128 {
129 // We finished the game (any result possible)
130 this.conn.send(JSON.stringify({
131 code: "lastate",
132 oppid: data.oppid,
133 gameId: this.gameRef.id,
134 score: this.score,
135 }));
136 }
137 else if (!!data.score) //opponent finished the game
138 this.endGame(data.score);
139 else if (data.movesCount < L)
140 {
141 // We must tell last move to opponent
142 this.conn.send(JSON.stringify({
143 code: "lastate",
144 oppid: this.opponent.id,
145 gameId: this.gameRef.id,
146 lastMove: this.vr.moves[L-1],
147 movesCount: L,
148 }));
149 }
150 else if (data.movesCount > L) //just got last move from him
151 this.play(data.lastMove, "animate");
152 break;
153 case "resign": //..you won!
154 this.endGame(this.mycolor=="w"?"1-0":"0-1");
155 break;
156 // TODO: also use (dis)connect info to count online players?
157 case "gameconnect":
158 case "gamedisconnect":
159 if (this.mode=="human")
160 {
161 const online = (data.code == "connect");
162 // If this is an opponent ?
163 if (!!this.opponents[data.id])
164 this.opponents[data.id].online = online;
165 else
166 {
167 // Or an observer ?
168 if (!online)
169 delete this.people[data.id];
170 else
171 this.people[data.id] = data.name;
172 }
173 }
174 break;
175 }
176 };
177 const socketCloseListener = () => {
178 this.conn.addEventListener('message', socketMessageListener);
179 this.conn.addEventListener('close', socketCloseListener);
180 };
181 if (!!this.conn)
182 {
183 this.conn.onmessage = socketMessageListener;
184 this.conn.onclose = socketCloseListener;
185 }
186 },
187 // dans variant.js (plutôt room.js) conn gère aussi les challenges
188 // et les chats dans chat.js. Puis en webRTC, repenser tout ça.
189 methods: {
190 offerDraw: function() {
191 if (!confirm("Offer draw?"))
192 return;
193 // Stay in "draw offer sent" state until next move is played
194 this.drawOfferSent = true;
195 if (this.subMode == "corr")
196 {
197 // TODO: set drawOffer on in game (how ?)
198 }
199 else //live game
200 {
201 this.opponents.forEach(o => {
202 if (!!o.online)
203 {
204 try {
205 this.conn.send(JSON.stringify({code: "draw", oppid: o.id}));
206 } catch (INVALID_STATE_ERR) {
207 return;
208 }
209 }
210 });
211 }
212 },
213 // + conn handling: "draw" message ==> agree for draw (if we have "drawOffered" at true)
214 receiveDrawOffer: function() {
215 //if (...)
216 // TODO: ignore if preventDrawOffer is set; otherwise show modal box with option "prevent future offers"
217 // if accept: send message "draw"
218 },
219 abortGame: function() {
220 if (!confirm("Abort the game?"))
221 return;
222 //+ bouton "abort" avec score == "?" + demander confirmation pour toutes ces actions,
223 //send message: "gameOver" avec score "?"
224 },
225 resign: function(e) {
226 if (!confirm("Resign the game?"))
227 return;
228 if (this.mode == "human" && this.oppConnected(this.oppid))
229 {
230 try {
231 this.conn.send(JSON.stringify({code: "resign", oppid: this.oppid}));
232 } catch (INVALID_STATE_ERR) {
233 return;
234 }
235 }
236 this.endGame(this.mycolor=="w"?"0-1":"1-0");
237 },
b196f8ea
BA
238 // 4 cases for loading a game:
239 // - from localStorage (one running game I play)
240 // - from indexedDB (one completed live game)
241 // - from server (one correspondance game I play[ed] or not)
242 // - from remote peer (one live game I don't play, finished or not)
243 loadGame: async function() {
d6c1bf37
BA
244 GameStorage.get(this.gameRef, (game) => {
245 this.gameInfo =
246 this.vname = game.vname;
247 this.mode = game.mode;
248 const vModule = await import("@/variants/" + this.vname + ".js");
249 window.V = vModule.VariantRules;
250 this.vr = new V(this.gameInfo.fen);
251 });
4fe5664d
BA
252// // Poll all players except me (if I'm playing) to know online status.
253// // --> Send ping to server (answer pong if players[s] are connected)
254// if (this.gameInfo.players.some(p => p.sid == this.st.user.sid))
255// {
256// this.game.players.forEach(p => {
257// if (p.sid != this.st.user.sid)
258// this.st.conn.send(JSON.stringify({code:"ping", oppid:p.sid}));
259// });
260// }
a6088c90
BA
261 },
262 oppConnected: function(uid) {
b196f8ea 263 return this.opponents.some(o => o.id == uid && o.online);
a6088c90 264 },
ce87ac6a
BA
265 processMove: function(move) {
266 // TODO: process some opponent's move
267 },
a6088c90
BA
268 },
269};
270</script>