From: Benjamin Auder Date: Thu, 10 Jan 2019 22:39:44 +0000 (+0100) Subject: Reorganize and completed board component. Now need to finish game component X-Git-Url: https://git.auder.net/assets/doc/current/git-favicon.png?a=commitdiff_plain;h=e5dc87e0e8f2d53a910b2b42ed2a0a39ea6787aa;p=vchess.git Reorganize and completed board component. Now need to finish game component --- diff --git a/public/javascripts/components/board.js b/public/javascripts/components/board.js index 2861cf7f..6110cfb8 100644 --- a/public/javascripts/components/board.js +++ b/public/javascripts/components/board.js @@ -1,3 +1,12 @@ +Vue.component('my-board', { + // Last move cannot be guessed from here, and is required to highlight squares + // gotoMove : juste set FEN depuis FEN stocké dans le coup (TODO) + // send event after each move (or undo), to notify what was played + // also notify end of game (which returns here later through prop...) + props: ["fen","moveToPlay","moveToUndo", + "analyze","lastMove","orientation","userColor","gameOver"], + data: function () { + return { hints: (!localStorage["hints"] ? true : localStorage["hints"] === "1"), bcolor: localStorage["bcolor"] || "lichess", //lichess, chesscom or chesstempo possibleMoves: [], //filled after each valid click/dragstart @@ -6,236 +15,241 @@ incheck: [], start: {}, //pixels coordinates + id of starting square (click or drag) vr: null, //object to check moves, store them, FEN.. - orientation: "w", //useful if click on "flip board" - - -// TODO: watch for property change "fen" -// send event after each move, to notify what was played - - const [sizeX,sizeY] = [V.size.x,V.size.y]; + }; + }, + watch: { + // NOTE: maybe next 3 should be encapsulated in object to be watched (?) + fen: function(newFen) { + this.vr = new VariantRules(newFen); + }, + moveToPlay: function(move) { + this.play(move, "animate"); + }, + moveToUndo: function(move) { + this.undo(move); + }, + }, + created: function() { + this.vr = new VariantRules(this.fen); + }, + render(h) { + const [sizeX,sizeY] = [V.size.x,V.size.y]; // Precompute hints squares to facilitate rendering let hintSquares = doubleArray(sizeX, sizeY, false); this.possibleMoves.forEach(m => { hintSquares[m.end.x][m.end.y] = true; }); // Also precompute in-check squares let incheckSq = doubleArray(sizeX, sizeY, false); this.incheck.forEach(sq => { incheckSq[sq[0]][sq[1]] = true; }); - const choices = h('div', - { - attrs: { "id": "choices" }, - 'class': { 'row': true }, - style: { - "display": this.choices.length>0?"block":"none", - "top": "-" + ((sizeY/2)*squareWidth+squareWidth/2) + "px", - "width": (this.choices.length * squareWidth) + "px", - "height": squareWidth + "px", - }, + const choices = h( + 'div', + { + attrs: { "id": "choices" }, + 'class': { 'row': true }, + style: { + "display": this.choices.length>0?"block":"none", + "top": "-" + ((sizeY/2)*squareWidth+squareWidth/2) + "px", + "width": (this.choices.length * squareWidth) + "px", + "height": squareWidth + "px", }, - this.choices.map( m => { //a "choice" is a move - return h('div', - { - 'class': { - 'board': true, - ['board'+sizeY]: true, - }, - style: { - 'width': (100/this.choices.length) + "%", - 'padding-bottom': (100/this.choices.length) + "%", - }, + }, + this.choices.map(m => { //a "choice" is a move + return h('div', + { + 'class': { + 'board': true, + ['board'+sizeY]: true, + }, + style: { + 'width': (100/this.choices.length) + "%", + 'padding-bottom': (100/this.choices.length) + "%", }, - [h('img', - { - attrs: { "src": '/images/pieces/' + - VariantRules.getPpath(m.appear[0].c+m.appear[0].p) + '.svg' }, - 'class': { 'choice-piece': true }, - on: { - "click": e => { this.play(m); this.choices=[]; }, - // NOTE: add 'touchstart' event to fix a problem on smartphones - "touchstart": e => { this.play(m); this.choices=[]; }, - }, - }) - ] - ); - }) - ); - // Create board element (+ reserves if needed by variant or mode) - const lm = this.vr.lastMove; - const showLight = this.hints && variant.name!="Dark" && - (this.mode != "idle" || - (this.vr.moves.length > 0 && this.cursor==this.vr.moves.length)); - const gameDiv = h('div', - { - 'class': { - 'game': true, - 'clearer': true, }, - }, - [_.range(sizeX).map(i => { - let ci = (this.mycolor=='w' ? i : sizeX-i-1); - return h( - 'div', + [h('img', { - 'class': { - 'row': true, + attrs: { "src": '/images/pieces/' + + V.getPpath(m.appear[0].c+m.appear[0].p) + '.svg' }, + 'class': { 'choice-piece': true }, + on: { + "click": e => { this.play(m); this.choices=[]; }, + // NOTE: add 'touchstart' event to fix a problem on smartphones + "touchstart": e => { this.play(m); this.choices=[]; }, }, - style: { 'opacity': this.choices.length>0?"0.5":"1" }, - }, - _.range(sizeY).map(j => { - let cj = (this.mycolor=='w' ? j : sizeY-j-1); - let elems = []; - if (this.vr.board[ci][cj] != VariantRules.EMPTY && (variant.name!="Dark" - || this.score!="*" || this.vr.enlightened[this.mycolor][ci][cj])) - { - elems.push( - h( - 'img', - { - 'class': { - 'piece': true, - 'ghost': !!this.selectedPiece - && this.selectedPiece.parentNode.id == "sq-"+ci+"-"+cj, - }, - attrs: { - src: "/images/pieces/" + - VariantRules.getPpath(this.vr.board[ci][cj]) + ".svg", - }, - } - ) - ); - } - if (this.hints && hintSquares[ci][cj]) - { - elems.push( - h( - 'img', - { - 'class': { - 'mark-square': true, - }, - attrs: { - src: "/images/mark.svg", - }, - } - ) - ); - } - return h( - 'div', - { - 'class': { - 'board': true, - ['board'+sizeY]: true, - 'light-square': (i+j)%2==0, - 'dark-square': (i+j)%2==1, - [this.bcolor]: true, - 'in-shadow': variant.name=="Dark" && this.score=="*" - && !this.vr.enlightened[this.mycolor][ci][cj], - 'highlight': showLight && !!lm && _.isMatch(lm.end, {x:ci,y:cj}), - 'incheck': showLight && incheckSq[ci][cj], - }, - attrs: { - id: this.getSquareId({x:ci,y:cj}), - }, - }, - elems - ); }) - ); - }), choices] - ); - if (!!this.vr.reserve) + ] + ); + }) + ); + // Create board element (+ reserves if needed by variant or mode) + const lm = this.lastMove; + const showLight = this.hints && variant.name != "Dark"; + const gameDiv = h( + 'div', { - const shiftIdx = (this.mycolor=="w" ? 0 : 1); - let myReservePiecesArray = []; - for (let i=0; i { + let ci = (this.orientation=='w' ? i : sizeX-i-1); + return h( + 'div', { - 'class': {'board':true, ['board'+sizeY]:true}, - attrs: { id: this.getSquareId({x:sizeX+shiftIdx,y:i}) } + 'class': { + 'row': true, + }, + style: { 'opacity': this.choices.length>0?"0.5":"1" }, }, - [ - h('img', + _.range(sizeY).map(j => { + let cj = (this.orientation=='w' ? j : sizeY-j-1); + let elems = []; + if (this.vr.board[ci][cj] != V.EMPTY && (variant.name!="Dark" + || this.gameOver || this.vr.enlightened[this.userColor][ci][cj])) { - 'class': {"piece":true, "reserve":true}, - attrs: { - "src": "/images/pieces/" + - this.vr.getReservePpath(this.mycolor,i) + ".svg", - } - }), - h('sup', - {"class": { "reserve-count": true } }, - [ this.vr.reserve[this.mycolor][VariantRules.RESERVE_PIECES[i]] ] - ) - ])); - } - let oppReservePiecesArray = []; - const oppCol = this.vr.getOppCol(this.mycolor); - for (let i=0; i {}); + // Is opponent in check? + this.incheck = this.vr.getCheckSquares(this.vr.turn); + const eog = this.vr.getCurrentScore(); + if (eog != "*") + { + // TODO: notify end of game (give score) + } + }, + undo: function(move) { + this.vr.undo(move); + if (this.sound == 2) + new Audio("/sounds/undo.mp3").play().catch(err => {}); + this.incheck = this.vr.getCheckSquares(this.vr.turn); + }, + }, +}) diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js index 84088c05..2b527d93 100644 --- a/public/javascripts/components/game.js +++ b/public/javascripts/components/game.js @@ -54,6 +54,25 @@ Vue.component('my-game', { // TODO: controls: abort, clear, resign, draw (avec confirm box) // et si partie terminée : (mode analyse) just clear, back / play // + flip button toujours disponible + // Show current FEN (just below board, lower right corner) +// (if mode != Dark ...) + elementArray.push( + h('div', + { + attrs: { id: "fen-div" }, + "class": { "section-content": true }, + }, + [ + h('p', + { + attrs: { id: "fen-string" }, + domProps: { innerHTML: this.vr.getBaseFen() }, + "class": { "text-center": true }, + } + ) + ] + ) + );
@@ -264,83 +283,6 @@ Vue.component('my-game', { this.compWorker.postMessage(["askmove"]); }, // OK, these last functions can stay here (?!) - play: function(move, programmatic) { - if (!move) - { - // Navigate after game is over - if (this.cursor >= this.moves.length) - return; //already at the end - move = this.moves[this.cursor++]; - } - if (!!programmatic) //computer or human opponent - return this.animateMove(move); - // Not programmatic, or animation is over - if (this.mode == "human" && this.vr.turn == this.mycolor) - this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid})); - - - // TODO: play move, and stack it on this.moves (if a move was provided; otherwise just navigate) - - if (this.score == "*") //TODO: I don't like this if() - { - // Emergency check, if human game started "at the same time" - // TODO: robustify this... - if (this.mode == "human" && !!move.computer) - return; - this.vr.play(move, "ingame"); - // Is opponent in check? - this.incheck = this.vr.getCheckSquares(this.vr.turn); - if (this.sound == 2) - new Audio("/sounds/move.mp3").play().catch(err => {}); - if (this.mode == "computer") - { - // Send the move to web worker (TODO: including his own moves?!) - this.compWorker.postMessage(["newmove",move]); - } - const eog = this.vr.getCurrentScore(); - if (eog != "*") - { - if (["human","computer"].includes(this.mode)) - this.endGame(eog); - else - { - // Just show score on screen (allow undo) - this.score = eog; - this.showScoreMsg(); - } - } - } -// else -// { -// VariantRules.PlayOnBoard(this.vr.board, move); -// this.$forceUpdate(); //TODO: ?! -// } - if (["human","computer","friend"].includes(this.mode)) - this.updateStorage(); //after our moves and opponent moves - if (this.mode == "computer" && this.vr.turn != this.mycolor && this.score == "*") - this.playComputerMove(); - }, - // TODO: merge two next functions - undo: function() { - // Navigate after game is over - if (this.cursor == 0) - return; //already at the beginning - if (this.cursor == this.vr.moves.length) - this.incheck = []; //in case of... - const move = this.vr.moves[--this.cursor]; - VariantRules.UndoOnBoard(this.vr.board, move); - this.$forceUpdate(); //TODO: ?! - }, - undoInGame: function() { - const lm = this.vr.lastMove; - if (!!lm) - { - this.vr.undo(lm); - if (this.sound == 2) - new Audio("/sounds/undo.mp3").play().catch(err => {}); - this.incheck = this.vr.getCheckSquares(this.vr.turn); - } - }, }, }) @@ -356,3 +298,26 @@ Vue.component('my-game', { //TODO: confirm dialog with "opponent offers draw", avec possible bouton "prevent future offers" + bouton "proposer nulle" //+ bouton "abort" avec score == "?" + demander confirmation pour toutes ces actions, //comme sur lichess + +// send move from here: +//if (this.mode == "human" && this.vr.turn == this.mycolor) + //this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid})); + // TODO: play move, and stack it on this.moves (if a move was provided; otherwise just navigate) + +// if (["human","computer","friend"].includes(this.mode)) +// this.updateStorage(); //after our moves and opponent moves +// if (this.mode == "computer" && this.vr.turn != this.mycolor && this.score == "*") +// this.playComputerMove(); +// if (this.mode == "computer") +// { +// // Send the move to web worker (TODO: including his own moves?!) +// this.compWorker.postMessage(["newmove",move]); +// } +// if (["human","computer"].includes(this.mode)) +// this.endGame(eog); +// else +// { +// // Just show score on screen (allow undo) +// this.score = eog; +// this.showScoreMsg(); +// } diff --git a/public/javascripts/components/problems.js b/public/javascripts/components/problems.js index 897babbe..9a14c709 100644 --- a/public/javascripts/components/problems.js +++ b/public/javascripts/components/problems.js @@ -38,7 +38,8 @@ Vue.component('my-problems', { {{ curProb.instructions }}

- + //TODO: use my-game in analyze mode ? +

{{ translations["Show solution"] }}