From 07052665845283c65b50a76537669d0602ba436b Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Mon, 6 Apr 2020 11:35:32 +0200 Subject: [PATCH] Experimental in-page analyze + show rules from Game page --- TODO | 11 -- client/src/App.vue | 9 +- client/src/components/BaseGame.vue | 66 ++++++--- client/src/components/ComputerGame.vue | 1 + client/src/components/MoveList.vue | 4 +- client/src/styles/TODO | 3 + client/src/translations/rules/Arena/es.pug | 2 +- client/src/utils/printDiagram.js | 11 ++ client/src/views/Analyse.vue | 4 +- client/src/views/Game.vue | 147 +++++++++++++++++++-- client/src/views/Rules.vue | 24 +--- 11 files changed, 211 insertions(+), 71 deletions(-) create mode 100644 client/src/styles/TODO diff --git a/TODO b/TODO index a4ffcfd7..219aa835 100644 --- a/TODO +++ b/TODO @@ -1,16 +1,5 @@ Chakart :) -// mode analyse + charger rules dans page modal - -+ bouton analyse en vert ! -// TODO: rules button in Game Page :: modal just show rules text, easy -// // TODO: analyse mode in Game Page :: just stay, pass in analyze, easy -// // --> indicateur isConnected: vérifie aussi que pas en mode analyse ?! non... -// // Attention si coup reçu pendant mode analyse faut d'abord sortir du mode -// // (qu'on soit joueur ou spectateur) - -Ball --> capture ballon prend à distance ?! bof, si on a ballon et capture ennemi : lui passe la balle ? - Ambiguous chess https://www.chessvariants.com/mvopponent.dir/ambiguous-chess.html Need special highlight square --> similar to enlightened for Dark, in Board.vue diff --git a/client/src/App.vue b/client/src/App.vue index def45262..eef7169f 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -116,6 +116,9 @@ body .row > div padding: 0 +a + text-decoration: underline + header width: 100% display: flex @@ -185,6 +188,7 @@ nav justify-content: flex-start & > a display: inline-block + text-decoration: none color: #2c3e50 &.router-link-exact-active color: #42b983 @@ -200,10 +204,6 @@ nav & > #leftMenu margin-top: 42px padding-bottom: 5px - & > a - color: #2c3e50 - &.router-link-exact-active - color: #42b983 & > #rightMenu padding-top: 5px border-top: 1px solid darkgrey @@ -266,6 +266,7 @@ footer align-self: center; &:link color: #2c3e50 + text-decoration: none &:visited, &:hover color: #2c3e50 text-decoration: none diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index 572c80ad..a0bcef0a 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -14,7 +14,7 @@ div#baseGame ref="board" :vr="vr" :last-move="lastMove" - :analyze="game.mode=='analyze'" + :analyze="mode=='analyze'" :score="game.score" :user-color="game.mycolor" :orientation="orientation" @@ -40,6 +40,7 @@ div#baseGame img.inline(src="/images/icons/play.svg") button(@click="gotoEnd()") img.inline(src="/images/icons/fast-forward.svg") + p(v-show="showFen") {{ (!!vr ? vr.getFen() : "") }} #movesList MoveList( :show="showMoves" @@ -52,7 +53,7 @@ div#baseGame :cursor="cursor" @download="download" @showrules="showRules" - @analyze="analyzePosition" + @analyze="toggleAnalyze" @goto-move="gotoMove" @reset-arrows="resetArrows" ) @@ -84,6 +85,7 @@ export default { vr: null, //VariantRules object, game state endgameMessage: "", orientation: "w", + mode: "", score: "*", //'*' means 'unfinished' moves: [], cursor: -1, //index of the move just played @@ -111,6 +113,12 @@ export default { : "" ); }, + showFen: function() { + return ( + this.mode == "analyze" && + this.$router.currentRoute.path.indexOf("/analyse") === -1 + ); + }, // TODO: is it OK to pass "computed" as properties? // Also, some are seemingly not recomputed when vr is initialized. showMoves: function() { @@ -192,14 +200,15 @@ export default { this.$refs["board"].cancelResetArrows(); }, showRules: function() { - //this.$router.push("/variants/" + this.game.vname); - window.open("#/variants/" + this.game.vname, "_blank"); //better + // The button is here only on Game page: + document.getElementById("modalRules").checked = true; }, re_setVariables: function(game) { if (!game) game = this.game; //in case of... this.endgameMessage = ""; // "w": default orientation for observed games this.orientation = game.mycolor || "w"; + this.mode = game.mode || game.type; //TODO: merge... this.moves = JSON.parse(JSON.stringify(game.moves || [])); // Post-processing: decorate each move with notation and FEN this.vr = new V(game.fenStart); @@ -217,6 +226,7 @@ export default { this.vr.play(m); const checkSquares = this.vr.getCheckSquares(); if (checkSquares.length > 0) m.notation += "+"; + if (idxM == Lm - 1) m.fen = this.vr.getFen(); if (idx == L - 1 && idxM == Lm - 1) { this.incheck = checkSquares; const score = this.vr.getCurrentScore(); @@ -243,14 +253,29 @@ export default { if (index >= 0) this.lastMove = this.moves[index]; else this.lastMove = null; }, - analyzePosition: function() { - let newUrl = - "/analyse/" + - this.game.vname + - "/?fen=" + - this.vr.getFen().replace(/ /g, "_"); - if (!!this.game.mycolor) newUrl += "&side=" + this.game.mycolor; - window.open("#" + newUrl); + toggleAnalyze: function() { + if (this.mode != "analyze") { + // Enter analyze mode: + this.gameMode = this.mode; //was not 'analyze' + this.mode = "analyze"; + this.gameCursor = this.cursor; + this.gameMoves = JSON.parse(JSON.stringify(this.moves)); + document.getElementById("analyzeBtn").classList.add("active"); + } + else { + // Exit analyze mode: + this.mode = this.gameMode ; + this.cursor = this.gameCursor; + this.moves = this.gameMoves; + let fen = this.game.fenStart; + if (this.cursor >= 0) { + let mv = this.moves[this.cursor]; + if (!Array.isArray(mv)) mv = [mv]; + fen = mv[mv.length-1].fen; + } + this.vr = new V(fen); + document.getElementById("analyzeBtn").classList.remove("active"); + } }, download: function() { const content = this.getPgn(); @@ -407,7 +432,7 @@ export default { smove.notation = this.vr.getNotation(smove); smove.unambiguous = V.GetUnambiguousNotation(smove); this.vr.play(smove); - if (!!this.lastMove) { + if (this.inMultimove && !!this.lastMove) { if (!Array.isArray(this.lastMove)) this.lastMove = [this.lastMove, smove]; else this.lastMove.push(smove); @@ -472,7 +497,7 @@ export default { else this.lastMove.notation += "#"; } } - if (score != "*" && this.game.mode == "analyze") { + if (score != "*" && this.mode == "analyze") { const message = getScoreMessage(score); // Just show score on screen (allow undo) this.showEndgameMsg(score + " . " + this.st.tr[message]); @@ -488,7 +513,7 @@ export default { this.emitFenIfAnalyze(); this.inMultimove = false; this.score = computeScore(); - if (this.game.mode != "analyze" && !navigate) { + if (this.mode != "analyze" && !navigate) { if (!noemit) { // Post-processing (e.g. computer play). const L = this.moves.length; @@ -526,16 +551,19 @@ export default { // Forbid playing outside analyze mode, except if move is received. // Sufficient condition because Board already knows which turn it is. if ( - this.game.mode != "analyze" && + this.mode != "analyze" && !navigate && !received && (this.game.score != "*" || this.cursor < this.moves.length - 1) ) { return; } - // To play a received move, cursor must be at the end of the game: - if (received && this.cursor < this.moves.length - 1) - this.gotoEnd(); + if (!!received) { + if (this.mode == "analyze") this.toggleAnalyze(); + if (this.cursor < this.moves.length - 1) + // To play a received move, cursor must be at the end of the game: + this.gotoEnd(); + } playMove(); }, cancelCurrentMultimove: function() { diff --git a/client/src/components/ComputerGame.vue b/client/src/components/ComputerGame.vue index 1cd37ab0..44bfb6a6 100644 --- a/client/src/components/ComputerGame.vue +++ b/client/src/components/ComputerGame.vue @@ -71,6 +71,7 @@ export default { game.players = [{ name: "Myself" }, { name: "Computer" }]; if (game.mycolor == "b") game.players = game.players.reverse(); game.score = "*"; //finished games are removed + game.mode = this.gameInfo.mode; this.currentUrl = document.location.href; //to avoid playing outside page this.game = game; this.$refs["basegame"].re_setVariables(game); diff --git a/client/src/components/MoveList.vue b/client/src/components/MoveList.vue index 6c999755..ccb07758 100644 --- a/client/src/components/MoveList.vue +++ b/client/src/components/MoveList.vue @@ -27,7 +27,7 @@ div :aria-label="st.tr['Resize board']" ) img.inline(src="/images/icons/resize.svg") - button.tooltip( + button#analyzeBtn.tooltip( v-if="canAnalyze" @click="$emit('analyze')" :aria-label="st.tr['Analyse']" @@ -240,6 +240,8 @@ span#rulesBtn button margin: 0 + &.active + background-color: #50E99A #aboveMoves button padding-bottom: 5px diff --git a/client/src/styles/TODO b/client/src/styles/TODO new file mode 100644 index 00000000..e3f897ee --- /dev/null +++ b/client/src/styles/TODO @@ -0,0 +1,3 @@ +@import "./styles/_variables.scss"; +https://css-tricks.com/how-to-import-a-sass-file-into-every-vue-component-in-an-app/ +--> Stop duplicating CSS diff --git a/client/src/translations/rules/Arena/es.pug b/client/src/translations/rules/Arena/es.pug index 67366aa3..999f861d 100644 --- a/client/src/translations/rules/Arena/es.pug +++ b/client/src/translations/rules/Arena/es.pug @@ -39,7 +39,7 @@ p. h3 Fuente p - | La + | La a(href="https://www.chessvariants.com/32turn.dir/arenachess.html") | variante Arena | en chessvariants.com. diff --git a/client/src/utils/printDiagram.js b/client/src/utils/printDiagram.js index 9ebdeed6..00ff2c79 100644 --- a/client/src/utils/printDiagram.js +++ b/client/src/utils/printDiagram.js @@ -100,3 +100,14 @@ export function getDiagram(args) { } return boardDiv; } + +// Method to replace diagrams in loaded HTML +export function replaceByDiag(match, p1, p2) { + const diagParts = p2.split(" "); + return getDiagram({ + position: diagParts[0], + marks: diagParts[1], + orientation: diagParts[2], + shadow: diagParts[3] + }); +} diff --git a/client/src/views/Analyse.vue b/client/src/views/Analyse.vue index 2f087b99..39022e8a 100644 --- a/client/src/views/Analyse.vue +++ b/client/src/views/Analyse.vue @@ -64,7 +64,9 @@ export default { if (!routeFen) this.alertAndQuit("Missing FEN"); else { this.gameRef.fen = routeFen.replace(/_/g, " "); - // orientation is optional: taken from FEN if missing + // orientation is optional: taken from FEN if missing. + // NOTE: currently no internal usage of 'side', but could be used by + // manually settings the URL (TODO?). const orientation = this.$route.query["side"]; this.initialize(orientation); } diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index fe487653..0d7a4ee9 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -1,5 +1,14 @@ <template lang="pug"> main + input#modalRules.modal(type="checkbox") + div#rulesDiv( + role="dialog" + data-checkbox="modalRules" + ) + .card + label.modal-close(for="modalRules") + h4#variantNameInGame(@click="gotoRules") {{ game.vname }} + div(v-html="rulesContent") input#modalScore.modal(type="checkbox") div#scoreDiv( role="dialog" @@ -67,7 +76,7 @@ main button.refuseBtn(@click="cancelMove()") span {{ st.tr["Cancel"] }} .row - #aboveBoard.col-sm-12.col-md-9.col-md-offset-3.col-lg-10.col-lg-offset-2 + #aboveBoard.col-sm-12 span.variant-cadence(v-if="game.type!='import'") {{ game.cadence }} span.variant-name {{ game.vname }} span#nextGame( @@ -144,6 +153,7 @@ main span.time-separator(v-if="!!virtualClocks[0][1]") : span.time-right(v-if="!!virtualClocks[0][1]") | {{ virtualClocks[0][1] }} + span.separator span.time( v-if="game.score=='*'" :class="{yourturn: !!vr && vr.turn == 'b'}" @@ -172,7 +182,7 @@ import { extractTime } from "@/utils/timeControl"; import { getRandString } from "@/utils/alea"; import { getScoreMessage } from "@/utils/scoring"; import { getFullNotation } from "@/utils/notation"; -import { getDiagram } from "@/utils/printDiagram"; +import { getDiagram, replaceByDiag } from "@/utils/printDiagram"; import { processModalClick } from "@/utils/modalClick"; import { playMove, getFilteredMove } from "@/utils/playUndo"; import { ArrayFun } from "@/utils/array"; @@ -194,6 +204,7 @@ export default { // virtualClocks will be initialized from true game.clocks virtualClocks: [], vr: null, //"variant rules" object initialized from FEN + rulesContent: "", drawOffer: "", rematchId: "", rematchOffer: "", @@ -253,7 +264,7 @@ export default { this.toggleChat("close") }); }); - ["rematchDiv", "scoreDiv"].forEach( + ["rulesDiv", "rematchDiv", "scoreDiv"].forEach( (eltName) => { document.getElementById(eltName) .addEventListener("click", processModalClick); @@ -307,6 +318,9 @@ export default { isLargeScreen: function() { return window.innerWidth >= 500; }, + gotoRules: function() { + this.$router.push("/variants/" + this.game.vname); + }, participateInChat: function(p) { return Object.keys(p.tmpIds).some(x => p.tmpIds[x].focus) && !!p.name; }, @@ -348,6 +362,7 @@ export default { if (!!chatComp) chatComp.chats = []; this.virtualClocks = [[0,0], [0,0]]; this.vr = null; + this.rulesContent = ""; this.drawOffer = ""; this.lastateAsked = false; this.rematchOffer = ""; @@ -1228,6 +1243,19 @@ export default { await import("@/variants/" + game.vname + ".js") .then((vModule) => { window.V = vModule[game.vname + "Rules"]; + // (AJAX) Request to get rules content (plain text, HTML) + this.rulesContent = + require( + "raw-loader!@/translations/rules/" + + game.vname + "/" + + this.st.lang + ".pug" + ) + // Next two lines fix a weird issue after last update (2019-11) + .replace(/\\n/g, " ") + .replace(/\\"/g, '"') + .replace('module.exports = "', "") + .replace(/"$/, "") + .replace(/(fen:)([^:]*):/g, replaceByDiag); this.loadGame(game, callback); }); }, @@ -1585,6 +1613,15 @@ export default { padding: 15px 0 max-width: 430px +#rulesDiv > .card + padding: 5px 0 + max-width: 75% + max-height: 100% + @media screen and (max-width: 1024px) + max-width: 85% + @media screen and (max-width: 767px) + max-width: 100% + p.score-section font-size: 1.3em span.score @@ -1603,9 +1640,6 @@ p.score-section #playersInfo > p margin: 0 -@media screen and (min-width: 768px) - #actions - width: 300px @media screen and (max-width: 767px) .game width: 100% @@ -1624,12 +1658,8 @@ button @media screen and (max-width: 767px) height: 18px -@media screen and (max-width: 767px) - #aboveBoard - text-align: center -@media screen and (min-width: 768px) - #aboveBoard - margin-left: 30% +#aboveBoard + text-align: center .variant-cadence padding-right: 10px @@ -1644,6 +1674,12 @@ span#nextGame display: inline-block margin-right: 10px +span.separator + display: inline-block + margin: 0 + padding: 0 + width: 10px + span.name font-size: 1.5rem padding: 0 3px @@ -1713,4 +1749,91 @@ button.acceptBtn background-color: lightgreen button.refuseBtn background-color: red + +h4#variantNameInGame + cursor: pointer + text-align: center + text-decoration: underline + font-weight: bold +</style> + +<style lang="sass"> +// TODO: next is duplicated from Rules/. Merge ? How ? ... + +figure.diagram-container + margin: 15px 0 15px 0 + text-align: center + width: 100% + display: block + .diagram + display: block + width: 50% + min-width: 240px + margin-left: auto + margin-right: auto + .diag12 + float: left + width: 40% + margin-left: calc(10% - 20px) + margin-right: 40px + @media screen and (max-width: 630px) + float: none + margin: 0 auto 10px auto + .diag22 + float: left + width: 40% + margin-right: calc(10% - 20px) + @media screen and (max-width: 630px) + float: none + margin: 0 auto + figcaption + display: block + clear: both + padding-top: 5px + font-size: 0.8em + +p.boxed + background-color: #FFCC66 + padding: 5px + +.bigfont + font-size: 1.2em + +.bold + font-weight: bold + +.stageDelimiter + color: purple + +// To show (new) pieces, and/or there values... +figure.showPieces > img + width: 50px + +figure.showPieces > figcaption + color: #6C6C6C + +.section-title + padding: 0 + +.section-title > h4 + padding: 5px + +ol, ul:not(.browser-default) + padding-left: 20px + +ul:not(.browser-default) + margin-top: 5px + +ul:not(.browser-default) > li + list-style-type: disc + +table + margin: 15px auto + +.italic + font-style: italic + +img.img-center + display: block + margin: 0 auto 15px auto </style> diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue index 127d1db7..c48feb08 100644 --- a/client/src/views/Rules.vue +++ b/client/src/views/Rules.vue @@ -42,7 +42,7 @@ main <script> import ComputerGame from "@/components/ComputerGame.vue"; import { store } from "@/store"; -import { getDiagram } from "@/utils/printDiagram"; +import { replaceByDiag } from "@/utils/printDiagram"; import { CompgameStorage } from "@/utils/compgameStorage"; export default { name: "my-rules", @@ -89,7 +89,7 @@ export default { .replace(/\\"/g, '"') .replace('module.exports = "', "") .replace(/"$/, "") - .replace(/(fen:)([^:]*):/g, this.replaceByDiag) + .replace(/(fen:)([^:]*):/g, replaceByDiag) ); } }, @@ -98,20 +98,6 @@ export default { if (this.display != "rules") this.display = "rules"; else if (this.gameInProgress) this.display = "computer"; }, - parseFen(fen) { - const fenParts = fen.split(" "); - return { - position: fenParts[0], - marks: fenParts[1], - orientation: fenParts[2], - shadow: fenParts[3] - }; - }, - // Method to replace diagrams in loaded HTML - replaceByDiag: function(match, p1, p2) { - const args = this.parseFen(p2); - return getDiagram(args); - }, re_setVariant: async function(vname) { await import("@/variants/" + vname + ".js") .then((vModule) => { @@ -164,12 +150,6 @@ export default { <!-- NOTE: not scoped here, because HTML is injected (TODO) --> <style lang="sass"> -.warn - padding: 3px - color: red - background-color: lightgrey - font-weight: bold - h4#variantName text-align: center font-weight: bold -- 2.44.0