X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Fcomponents%2Fgame.js;h=9d7bb1fbf7da57e47621d5c34b55e713665648fd;hb=1a788978e3682ab54b77af3edfe38e0b371edbc4;hp=ded309186a15647eec972086ff08ed5b8008a7c7;hpb=c794dbb87592782913af0a09784ed25e019e4d10;p=vchess.git diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js index ded30918..9d7bb1fb 100644 --- a/public/javascripts/components/game.js +++ b/public/javascripts/components/game.js @@ -27,10 +27,7 @@ Vue.component('my-game', { watch: { problem: function(p, pp) { // 'problem' prop changed: update board state - // TODO: FEN + turn + flags + rappel instructions / solution on click sous l'échiquier - // TODO: trouver moyen de passer la situation des reserves pour Crazyhouse, - // et l'état des captures pour Grand... bref compléter le descriptif de l'état. - this.newGame("problem", p.fen, p.fen.split(" ")[2]); + this.newGame("problem", p.fen, V.ParseFen(p.fen).turn); }, }, render(h) { @@ -105,7 +102,7 @@ Vue.component('my-game', { : (smallScreen ? 31 : 37); if (this.mode == "human") { - let connectedIndic = h( + const connectedIndic = h( 'div', { "class": { @@ -122,7 +119,7 @@ Vue.component('my-game', { ); elementArray.push(connectedIndic); } - let turnIndic = h( + const turnIndic = h( 'div', { "class": { @@ -138,7 +135,7 @@ Vue.component('my-game', { } ); elementArray.push(turnIndic); - let settingsBtn = h( + const settingsBtn = h( 'button', { on: { click: this.showSettings }, @@ -157,7 +154,27 @@ Vue.component('my-game', { [h('i', { 'class': { "material-icons": true } }, "settings")] ); elementArray.push(settingsBtn); - let choices = h('div', + if (this.mode == "problem") + { + // Show problem instructions + elementArray.push( + h('div', + { + attrs: { id: "instructions-div" }, + "class": { "section-content": true }, + }, + [ + h('p', + { + attrs: { id: "problem-instructions" }, + domProps: { innerHTML: this.problem.instructions } + } + ) + ] + ) + ); + } + const choices = h('div', { attrs: { "id": "choices" }, 'class': { 'row': true }, @@ -195,7 +212,7 @@ Vue.component('my-game', { const lm = this.vr.lastMove; const showLight = this.hints && (this.mode!="idle" || this.cursor==this.vr.moves.length); - let gameDiv = h('div', + const gameDiv = h('div', { 'class': { 'game': true }, }, @@ -410,7 +427,6 @@ Vue.component('my-game', { ); elementArray.push(reserves); } - const eogMessage = this.getEndgameMessage(this.score); const modalEog = [ h('input', { @@ -437,7 +453,7 @@ Vue.component('my-game', { { attrs: { "id": "eogMessage" }, "class": { "section": true }, - domProps: { innerHTML: eogMessage }, + domProps: { innerHTML: this.endgameMessage }, } ) ] @@ -713,7 +729,7 @@ Vue.component('my-game', { actionArray ); elementArray.push(actions); - if (this.score != "*") + if (this.score != "*" && this.pgnTxt.length > 0) { elementArray.push( h('div', @@ -749,6 +765,32 @@ Vue.component('my-game', { } else if (this.mode != "idle") { + if (this.mode == "problem") + { + // Show problem solution (on click) + elementArray.push( + h('div', + { + attrs: { id: "solution-div" }, + "class": { "section-content": true }, + }, + [ + h('h3', + { + domProps: { innerHTML: "Show solution" }, + on: { click: "toggleShowSolution" } + } + ), + h('p', + { + attrs: { id: "problem-solution" }, + domProps: { innerHTML: this.problem.solution } + } + ) + ] + ) + ); + } // Show current FEN elementArray.push( h('div', @@ -760,7 +802,7 @@ Vue.component('my-game', { h('p', { attrs: { id: "fen-string" }, - domProps: { innerHTML: this.vr.getFen() } + domProps: { innerHTML: this.vr.getBaseFen() } } ) ] @@ -790,19 +832,32 @@ Vue.component('my-game', { elementArray ); }, + computed: { + endgameMessage: function() { + let eogMessage = "Unfinished"; + switch (this.score) + { + case "1-0": + eogMessage = "White win"; + break; + case "0-1": + eogMessage = "Black win"; + break; + case "1/2": + eogMessage = "Draw"; + break; + } + return eogMessage; + }, + }, created: function() { const url = socketUrl; - const continuation = (localStorage.getItem("variant") === variant); - this.myid = (continuation ? localStorage.getItem("myid") : getRandString()); - if (!continuation) - { - // HACK: play a small silent sound to allow "new game" sound later - // if tab not focused (TODO: does it really work ?!) - new Audio("/sounds/silent.mp3").play().then(() => {}).catch(err => {}); - } + const humanContinuation = (localStorage.getItem("variant") === variant); + const computerContinuation = (localStorage.getItem("comp-variant") === variant); + this.myid = (humanContinuation ? localStorage.getItem("myid") : getRandString()); this.conn = new WebSocket(url + "/?sid=" + this.myid + "&page=" + variant); const socketOpenListener = () => { - if (continuation) + if (humanContinuation) //game VS human has priority { const fen = localStorage.getItem("fen"); const mycolor = localStorage.getItem("mycolor"); @@ -812,10 +867,12 @@ Vue.component('my-game', { // Send ping to server (answer pong if opponent is connected) this.conn.send(JSON.stringify({code:"ping",oppid:this.oppid})); } - else if (localStorage.getItem("newgame") === variant) + else if (computerContinuation) { - // New game request has been cancelled on disconnect - this.newGame("human", undefined, undefined, undefined, undefined, "reconnect"); + const fen = localStorage.getItem("comp-fen"); + const mycolor = localStorage.getItem("comp-mycolor"); + const moves = JSON.parse(localStorage.getItem("comp-moves")); + this.newGame("computer", fen, mycolor, undefined, moves, true); } }; const socketMessageListener = msg => { @@ -910,6 +967,12 @@ Vue.component('my-game', { }; }, methods: { + toggleShowSolution: function() { + let problemSolution = document.getElementById("problem-solution"); + problemSolution.style.display = problemSolution.style.display == "none" + ? "block" + : "none"; + }, download: function() { let content = document.getElementById("pgn-game").innerHTML; content = content.replace(/
/g, "\n"); @@ -920,56 +983,53 @@ Vue.component('my-game', { encodeURIComponent(content); downloadAnchor.click(); }, - endGame: function(score) { - this.score = score; + showScoreMsg: function() { let modalBox = document.getElementById("modal-eog"); modalBox.checked = true; + setTimeout(() => { modalBox.checked = false; }, 2000); + }, + endGame: function(score) { + this.score = score; + this.showScoreMsg(); // Variants may have special PGN structure (so next function isn't defined here) this.pgnTxt = this.vr.getPGN(this.mycolor, this.score, this.fenStart, this.mode); - setTimeout(() => { modalBox.checked = false; }, 2000); - if (this.mode == "human") + if (["human","computer"].includes(this.mode)) this.clearStorage(); this.mode = "idle"; this.cursor = this.vr.moves.length; //to navigate in finished game this.oppid = ""; }, - getEndgameMessage: function(score) { - let eogMessage = "Unfinished"; - switch (this.score) + setStorage: function() { + if (this.mode=="human") { - case "1-0": - eogMessage = "White win"; - break; - case "0-1": - eogMessage = "Black win"; - break; - case "1/2": - eogMessage = "Draw"; - break; + localStorage.setItem("myid", this.myid); + localStorage.setItem("oppid", this.oppid); } - return eogMessage; - }, - setStorage: function() { - localStorage.setItem("myid", this.myid); - localStorage.setItem("variant", variant); - localStorage.setItem("mycolor", this.mycolor); - localStorage.setItem("oppid", this.oppid); - localStorage.setItem("fenStart", this.fenStart); - localStorage.setItem("moves", JSON.stringify(this.vr.moves)); - localStorage.setItem("fen", this.vr.getFen()); + // 'prefix' = "comp-" to resume games vs. computer + const prefix = (this.mode=="computer" ? "comp-" : ""); + localStorage.setItem(prefix+"variant", variant); + localStorage.setItem(prefix+"mycolor", this.mycolor); + localStorage.setItem(prefix+"fenStart", this.fenStart); + localStorage.setItem(prefix+"moves", JSON.stringify(this.vr.moves)); + localStorage.setItem(prefix+"fen", this.vr.getFen()); }, updateStorage: function() { - localStorage.setItem("moves", JSON.stringify(this.vr.moves)); - localStorage.setItem("fen", this.vr.getFen()); + const prefix = (this.mode=="computer" ? "comp-" : ""); + localStorage.setItem(prefix+"moves", JSON.stringify(this.vr.moves)); + localStorage.setItem(prefix+"fen", this.vr.getFen()); }, clearStorage: function() { - delete localStorage["variant"]; - delete localStorage["myid"]; - delete localStorage["mycolor"]; - delete localStorage["oppid"]; - delete localStorage["fenStart"]; - delete localStorage["fen"]; - delete localStorage["moves"]; + if (this.mode=="human") + { + delete localStorage["myid"]; + delete localStorage["oppid"]; + } + const prefix = (this.mode=="computer" ? "comp-" : ""); + delete localStorage[prefix+"variant"]; + delete localStorage[prefix+"mycolor"]; + delete localStorage[prefix+"fenStart"]; + delete localStorage[prefix+"fen"]; + delete localStorage[prefix+"moves"]; }, // HACK because mini-css tooltips are persistent after click... getRidOfTooltip: function(elt) { @@ -999,7 +1059,6 @@ Vue.component('my-game', { if (this.seek) { this.conn.send(JSON.stringify({code:"cancelnewgame"})); - delete localStorage["newgame"]; //cancel game seek this.seek = false; } else @@ -1036,56 +1095,74 @@ Vue.component('my-game', { if (!!storageVariant && storageVariant !== variant) return alert("Finish your " + storageVariant + " game first!"); // Send game request and wait.. - localStorage["newgame"] = variant; - this.seek = true; - this.clearStorage(); //in case of try { this.conn.send(JSON.stringify({code:"newgame", fen:fen})); } catch (INVALID_STATE_ERR) { return; //nothing achieved } - if (continuation !== "reconnect") //TODO: bad HACK... + this.seek = true; + let modalBox = document.getElementById("modal-newgame"); + modalBox.checked = true; + setTimeout(() => { modalBox.checked = false; }, 2000); + return; + } + if (mode == "computer" && !continuation) + { + const storageVariant = localStorage.getItem("comp-variant"); + if (!!storageVariant && storageVariant !== variant) { - let modalBox = document.getElementById("modal-newgame"); - modalBox.checked = true; - setTimeout(() => { modalBox.checked = false; }, 2000); + if (!confirm("Unfinished " + storageVariant + + " computer game will be erased")) + { + return; + } } - return; + } + if (this.mode == "computer" && mode == "human") + { + // Save current computer game to resume it later + this.setStorage(); } this.vr = new VariantRules(fen, moves || []); this.score = "*"; this.pgnTxt = ""; //redundant with this.score = "*", but cleaner this.mode = mode; - this.incheck = []; //in case of - this.fenStart = (continuation ? localStorage.getItem("fenStart") : fen); + if (continuation && moves.length > 0) //NOTE: "continuation": redundant test + { + const lastMove = moves[moves.length-1]; + this.vr.undo(lastMove); + this.incheck = this.vr.getCheckSquares(lastMove); + this.vr.play(lastMove, "ingame"); + } + else + this.incheck = []; + if (continuation) + { + const prefix = (mode=="computer" ? "comp-" : ""); + this.fenStart = localStorage.getItem(prefix+"fenStart"); + } + else + this.fenStart = fen; if (mode=="human") { // Opponent found! if (!continuation) //not playing sound on game continuation { if (this.sound >= 1) - new Audio("/sounds/newgame.mp3").play().then(() => {}).catch(err => {}); + new Audio("/sounds/newgame.mp3").play().catch(err => {}); document.getElementById("modal-newgame").checked = false; } this.oppid = oppId; this.oppConnected = !continuation; this.mycolor = color; this.seek = false; - if (!!moves && moves.length > 0) //imply continuation - { - const lastMove = moves[moves.length-1]; - this.vr.undo(lastMove); - this.incheck = this.vr.getCheckSquares(lastMove); - this.vr.play(lastMove, "ingame"); - } - delete localStorage["newgame"]; this.setStorage(); //in case of interruptions } else if (mode == "computer") { this.mycolor = Math.random() < 0.5 ? 'w' : 'b'; if (this.mycolor == 'b') - setTimeout(this.playComputerMove, 500); + this.playComputerMove(); } //else: against a (IRL) friend or problem solving: nothing more to do }, @@ -1139,11 +1216,16 @@ Vue.component('my-game', { this.selectedPiece.style.top = 0; this.selectedPiece.style.display = "inline-block"; this.selectedPiece.style.zIndex = 3000; - let startSquare = this.getSquareFromId(e.target.parentNode.id); - const iCanPlay = this.mode!="idle" && - (["friend","problem"].includes(this.mode) || - this.vr.canIplay(this.mycolor,startSquare)); - this.possibleMoves = iCanPlay ? this.vr.getPossibleMovesFrom(startSquare) : []; + const startSquare = this.getSquareFromId(e.target.parentNode.id); + this.possibleMoves = []; + if (this.mode != "idle") + { + const color = ["friend","problem"].includes(this.mode) + ? this.vr.turn + : this.mycolor; + if (this.vr.canIplay(color,startSquare)) + this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare); + } // Next line add moving piece just after current image // (required for Crazyhouse reserve) e.target.parentNode.insertBefore(this.selectedPiece, e.target.nextSibling); @@ -1230,7 +1312,7 @@ Vue.component('my-game', { squares.item(i).style.zIndex = "auto"; movingPiece.style = {}; //required e.g. for 0-0 with KR swap this.play(move); - }, 200); + }, 250); }, play: function(move, programmatic) { if (!move) @@ -1249,7 +1331,7 @@ Vue.component('my-game', { if (this.mode == "human" && this.vr.turn == this.mycolor) this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid})); if (this.sound == 2) - new Audio("/sounds/chessmove1.mp3").play().then(() => {}).catch(err => {}); + new Audio("/sounds/chessmove1.mp3").play().catch(err => {}); if (this.mode != "idle") { this.incheck = this.vr.getCheckSquares(move); //is opponent in check? @@ -1260,16 +1342,25 @@ Vue.component('my-game', { VariantRules.PlayOnBoard(this.vr.board, move); this.$forceUpdate(); //TODO: ?! } - if (this.mode == "human") + if (["human","computer"].includes(this.mode)) this.updateStorage(); //after our moves and opponent moves if (this.mode != "idle") { const eog = this.vr.checkGameOver(); if (eog != "*") - this.endGame(eog); + { + if (["human","computer"].includes(this.mode)) + this.endGame(eog); + else + { + // Just show score on screen (allow undo) + this.score = eog; + this.showScoreMsg(); + } + } } if (this.mode == "computer" && this.vr.turn != this.mycolor) - setTimeout(this.playComputerMove, 500); + this.playComputerMove; }, undo: function() { // Navigate after game is over