Add state indicators for game seek/playing
[vchess.git] / public / javascripts / components / game.js
index 20d0f47..f5db337 100644 (file)
@@ -1,3 +1,5 @@
+// TODO: use indexedDB instead of localStorage: more flexible.
+
 Vue.component('my-game', {
        data: function() {
                return {
@@ -12,6 +14,7 @@ Vue.component('my-game', {
                        mode: "idle", //human, computer or idle (when not playing)
                        oppid: "", //opponent ID in case of HH game
                        oppConnected: false,
+                       seek: false,
                };
        },
        render(h) {
@@ -24,19 +27,39 @@ Vue.component('my-game', {
                let squareWidth = !!square00
                        ? parseFloat(window.getComputedStyle(square00).width.slice(0,-2))
                        : 0;
+               const playingHuman = (this.mode == "human");
+               const playingComp = (this.mode == "computer");
                let actionArray = [
                        h('button',
                                {
-                                       on: { click: () => this.newGame("human") },
+                                       on: {
+                                               click: () => {
+                                                       if (this.seek)
+                                                               delete localStorage["newgame"]; //cancel game seek
+                                                       else
+                                                       {
+                                                               localStorage["newgame"] = variant;
+                                                               this.newGame("human");
+                                                       }
+                                                       this.seek = !this.seek;
+                                               }
+                                       },
                                        attrs: { "aria-label": 'New game VS human' },
-                                       'class': { "tooltip":true },
+                                       'class': {
+                                               "tooltip": true,
+                                               "seek": this.seek,
+                                               "playing": playingHuman,
+                                       },
                                },
                                [h('i', { 'class': { "material-icons": true } }, "accessibility")]),
                        h('button',
                                {
                                        on: { click: () => this.newGame("computer") },
                                        attrs: { "aria-label": 'New game VS computer' },
-                                       'class': { "tooltip":true },
+                                       'class': {
+                                               "tooltip":true,
+                                               "playing": playingComp,
+                                       },
                                },
                                [h('i', { 'class': { "material-icons": true } }, "computer")])
                ];
@@ -137,7 +160,7 @@ Vue.component('my-game', {
                                                                        )
                                                                );
                                                        }
-                                                       const lm = this.vr.lastMove; //TODO: interruptions (FEN local storage..)
+                                                       const lm = this.vr.lastMove;
                                                        const highlight = !!lm && _.isMatch(lm.end, {x:ci,y:cj}); //&& _.isMatch(lm.start, {x:ci,y:cj})
                                                        return h(
                                                                'div',
@@ -305,7 +328,7 @@ Vue.component('my-game', {
                        // random enough (TODO: function)
                        : (Date.now().toString(36) + Math.random().toString(36).substr(2, 7)).toUpperCase();
                this.conn = new WebSocket(url + "/?sid=" + this.myid + "&page=" + variant);
-               this.conn.onopen = () => {
+               const socketOpenListener = () => {
                        if (continuation)
                        {
                                // TODO: check FEN integrity with opponent
@@ -314,8 +337,14 @@ Vue.component('my-game', {
                                // Send ping to server, which answers pong if opponent is connected
                                this.conn.send(JSON.stringify({code:"ping", oppid:this.oppId}));
                        }
+                       else if (localStorage.getItem("newgame") === variant)
+                       {
+                               // New game request has been cancelled on disconnect
+                               this.seek = true;
+                               this.newGame("human");
+                       }
                };
-               this.conn.onmessage = msg => {
+               const socketMessageListener = msg => {
                        const data = JSON.parse(msg.data);
                        switch (data.code)
                        {
@@ -339,11 +368,23 @@ Vue.component('my-game', {
                                        break;
                        }
                };
+               const socketCloseListener = () => {
+                       console.log("Lost connection -- reconnect");
+                       this.conn = new WebSocket(url + "/?sid=" + this.myid + "&page=" + variant);
+                       this.conn.addEventListener('open', socketOpenListener);
+                       this.conn.addEventListener('message', socketMessageListener);
+                       this.conn.addEventListener('close', socketCloseListener);
+               };
+               this.conn.onopen = socketOpenListener;
+               this.conn.onmessage = socketMessageListener;
+               this.conn.onclose = socketCloseListener;
        },
        methods: {
                endGame: function(message) {
                        this.endofgame = message;
-                       document.getElementById("modal-control").checked = true;
+                       let modalBox = document.getElementById("modal-control");
+                       modalBox.checked = true;
+                       setTimeout(() => { modalBox.checked = false; }, 2000);
                        if (this.mode == "human")
                                this.clearStorage();
                        this.mode = "idle";
@@ -351,7 +392,13 @@ Vue.component('my-game', {
                },
                resign: function() {
                        if (this.mode == "human" && this.oppConnected)
-                               this.conn.send(JSON.stringify({code: "resign", oppid: this.oppid}));
+                       {
+                               try {
+                                       this.conn.send(JSON.stringify({code: "resign", oppid: this.oppid}));
+                               } catch (INVALID_STATE_ERR) {
+                                       return; //resign failed for some reason...
+                               }
+                       }
                        this.endGame("Try again!");
                },
                updateStorage: function() {
@@ -378,8 +425,14 @@ Vue.component('my-game', {
                        {
                                // Send game request and wait..
                                this.clearStorage(); //in case of
-                               this.conn.send(JSON.stringify({code:"newgame", fen:fen}));
-                               document.getElementById("modal-control2").checked = true;
+                               try {
+                                       this.conn.send(JSON.stringify({code:"newgame", fen:fen}));
+                               } catch (INVALID_STATE_ERR) {
+                                       return; //nothing achieved
+                               }
+                               let modalBox = document.getElementById("modal-control2");
+                               modalBox.checked = true;
+                               setTimeout(() => { modalBox.checked = false; }, 2000);
                                return;
                        }
                        this.vr = new VariantRules(fen);
@@ -396,6 +449,8 @@ Vue.component('my-game', {
                                this.oppid = oppId;
                                this.oppConnected = true;
                                this.mycolor = color;
+                               this.seek = false;
+                               delete localStorage["newgame"];
                        }
                        else //against computer
                        {
@@ -529,7 +584,7 @@ Vue.component('my-game', {
                                try {
                                        this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid}));
                                } catch(INVALID_STATE_ERR) {
-                                       return; //abort also if we lost connection
+                                       return; //abort also if sending failed
                                }
                        }
                        new Audio("/sounds/chessmove1.mp3").play();