+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
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<VariantRules.RESERVE_PIECES.length; i++)
- {
- myReservePiecesArray.push(h('div',
+ 'class': {
+ 'game': true,
+ 'clearer': true,
+ },
+ },
+ [_.range(sizeX).map(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<VariantRules.RESERVE_PIECES.length; i++)
- {
- oppReservePiecesArray.push(h('div',
- {
- 'class': {'board':true, ['board'+sizeY]:true},
- attrs: { id: this.getSquareId({x:sizeX+(1-shiftIdx),y:i}) }
- },
- [
- h('img',
+ elems.push(
+ h(
+ 'img',
+ {
+ 'class': {
+ 'piece': true,
+ 'ghost': !!this.selectedPiece
+ && this.selectedPiece.parentNode.id == "sq-"+ci+"-"+cj,
+ },
+ attrs: {
+ src: "/images/pieces/" +
+ V.getPpath(this.vr.board[ci][cj]) + ".svg",
+ },
+ }
+ )
+ );
+ }
+ if (this.hints && hintSquares[ci][cj])
{
- 'class': {"piece":true, "reserve":true},
- attrs: {
- "src": "/images/pieces/" +
- this.vr.getReservePpath(oppCol,i) + ".svg",
- }
- }),
- h('sup',
- {"class": { "reserve-count": true } },
- [ this.vr.reserve[oppCol][VariantRules.RESERVE_PIECES[i]] ]
- )
- ]));
- }
- let reserves = h('div',
- {
- 'class':{
- 'game': true,
- "reserve-div": true,
- },
- },
- [
- h('div',
+ elems.push(
+ h(
+ 'img',
+ {
+ 'class': {
+ 'mark-square': true,
+ },
+ attrs: {
+ src: "/images/mark.svg",
+ },
+ }
+ )
+ );
+ }
+ return h(
+ 'div',
{
'class': {
- 'row': true,
- "reserve-row-1": true,
+ '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.gameOver
+ && !this.vr.enlightened[this.userColor][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}),
},
},
- myReservePiecesArray
- ),
- h('div',
- { 'class': { 'row': true }},
- oppReservePiecesArray
- )
- ]
+ elems
+ );
+ })
);
- elementArray.push(reserves);
+ }), choices]
+ );
+ let elementArray = [choices, gameDiv];
+ if (!!this.vr.reserve)
+ {
+ const shiftIdx = (this.userColor=="w" ? 0 : 1);
+ let myReservePiecesArray = [];
+ for (let i=0; i<V.RESERVE_PIECES.length; i++)
+ {
+ myReservePiecesArray.push(h('div',
+ {
+ 'class': {'board':true, ['board'+sizeY]:true},
+ attrs: { id: this.getSquareId({x:sizeX+shiftIdx,y:i}) }
+ },
+ [
+ h('img',
+ {
+ 'class': {"piece":true, "reserve":true},
+ attrs: {
+ "src": "/images/pieces/" +
+ this.vr.getReservePpath(this.userColor,i) + ".svg",
+ }
+ }),
+ h('sup',
+ {"class": { "reserve-count": true } },
+ [ this.vr.reserve[this.userColor][V.RESERVE_PIECES[i]] ]
+ )
+ ]));
+ }
+ let oppReservePiecesArray = [];
+ const oppCol = this.vr.getOppCol(this.userColor);
+ for (let i=0; i<V.RESERVE_PIECES.length; i++)
+ {
+ oppReservePiecesArray.push(h('div',
+ {
+ 'class': {'board':true, ['board'+sizeY]:true},
+ attrs: { id: this.getSquareId({x:sizeX+(1-shiftIdx),y:i}) }
+ },
+ [
+ h('img',
+ {
+ 'class': {"piece":true, "reserve":true},
+ attrs: {
+ "src": "/images/pieces/" +
+ this.vr.getReservePpath(oppCol,i) + ".svg",
+ }
+ }),
+ h('sup',
+ {"class": { "reserve-count": true } },
+ [ this.vr.reserve[oppCol][V.RESERVE_PIECES[i]] ]
+ )
+ ]));
}
- // Show current FEN (just below board, lower right corner)
-// (if mode != Dark ...)
- elementArray.push(
+ let reserves = h('div',
+ {
+ 'class':{
+ 'game': true,
+ "reserve-div": true,
+ },
+ },
+ [
h('div',
{
- attrs: { id: "fen-div" },
- "class": { "section-content": true },
+ 'class': {
+ 'row': true,
+ "reserve-row-1": true,
+ },
},
- [
- h('p',
- {
- attrs: { id: "fen-string" },
- domProps: { innerHTML: this.vr.getBaseFen() },
- "class": { "text-center": true },
- }
- )
- ]
+ myReservePiecesArray
+ ),
+ h('div',
+ { 'class': { 'row': true }},
+ oppReservePiecesArray
)
- );
+ ]
+ );
+ elementArray.push(reserves);
+ }
+ return h(
+ 'div',
+ {
+ 'class': {
+ "col-sm-12":true,
+ "col-md-10":true,
+ "col-md-offset-1":true,
+ "col-lg-8":true,
+ "col-lg-offset-2":true,
+ },
+ // NOTE: click = mousedown + mouseup
on: {
mousedown: this.mousedown,
mousemove: this.mousemove,
touchmove: this.mousemove,
touchend: this.mouseup,
},
-
-
- // TODO: "chessground-like" component
- // Get the identifier of a HTML table cell from its numeric coordinates o.x,o.y.
+ },
+ elementArray
+ );
+ },
+ methods: {
+ // Get the identifier of a HTML square from its numeric coordinates o.x,o.y.
getSquareId: function(o) {
// NOTE: a separator is required to allow any size of board
return "sq-" + o.x + "-" + o.y;
this.selectedPiece.style.zIndex = 3000;
const startSquare = this.getSquareFromId(e.target.parentNode.id);
this.possibleMoves = [];
- if (this.score == "*")
- {
-
-// TODO: essentially adapt this (all other things do not change much)
-// if inside a real game, mycolor should be provided ? (simplest way)
-
- const color = ["friend","problem"].includes(this.mode)
- ? this.vr.turn
- : this.mycolor;
- if (this.vr.canIplay(color,startSquare))
- this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare);
- }
+ const color = this.analyze || this.gameOver
+ ? this.vr.turn
+ : this.userColor;
+ 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);
this.play(move);
}, 250);
},
+ play: function(move, programmatic) {
+ if (!!programmatic) //computer or human opponent
+ return this.animateMove(move);
+ // Not programmatic, or animation is over
+ this.vr.play(move);
+ if (this.sound == 2)
+ new Audio("/sounds/move.mp3").play().catch(err => {});
+ // 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);
+ },
+ },
+})
// 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 },
+ }
+ )
+ ]
+ )
+ );
<div id="pgn-div" class="section-content">
<a id="download" href: "#"></a>
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);
- }
- },
},
})
//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();
+// }