2861cf7fd6ce787ddb646c324d98d5fbe6d8c19d
1 hints: (!localStorage
["hints"] ? true : localStorage
["hints"] === "1"),
2 bcolor: localStorage
["bcolor"] || "lichess", //lichess, chesscom or chesstempo
3 possibleMoves: [], //filled after each valid click/dragstart
4 choices: [], //promotion pieces, or checkered captures... (as moves)
5 selectedPiece: null, //moving piece (or clicked piece)
7 start: {}, //pixels coordinates + id of starting square (click or drag)
8 vr: null, //object to check moves, store them, FEN..
9 orientation: "w", //useful if click on "flip board"
12 // TODO: watch for property change "fen"
13 // send event after each move, to notify what was played
15 const [sizeX
,sizeY
] = [V
.size
.x
,V
.size
.y
];
16 // Precompute hints squares to facilitate rendering
17 let hintSquares
= doubleArray(sizeX
, sizeY
, false);
18 this.possibleMoves
.forEach(m
=> { hintSquares
[m
.end
.x
][m
.end
.y
] = true; });
19 // Also precompute in-check squares
20 let incheckSq
= doubleArray(sizeX
, sizeY
, false);
21 this.incheck
.forEach(sq
=> { incheckSq
[sq
[0]][sq
[1]] = true; });
22 const choices
= h('div',
24 attrs: { "id": "choices" },
25 'class': { 'row': true },
27 "display": this.choices
.length
>0?"block":"none",
28 "top": "-" + ((sizeY
/2)*squareWidth+squareWidth/2) + "px",
29 "width": (this.choices
.length
* squareWidth
) + "px",
30 "height": squareWidth
+ "px",
33 this.choices
.map( m
=> { //a "choice" is a move
38 ['board'+sizeY
]: true,
41 'width': (100/this.choices
.length
) + "%",
42 'padding-bottom': (100/this.choices
.length
) + "%",
47 attrs: { "src": '/images/pieces/' +
48 VariantRules
.getPpath(m
.appear
[0].c
+m
.appear
[0].p
) + '.svg' },
49 'class': { 'choice-piece': true },
51 "click": e
=> { this.play(m
); this.choices
=[]; },
52 // NOTE: add 'touchstart' event to fix a problem on smartphones
53 "touchstart": e
=> { this.play(m
); this.choices
=[]; },
60 // Create board element (+ reserves if needed by variant or mode)
61 const lm
= this.vr
.lastMove
;
62 const showLight
= this.hints
&& variant
.name
!="Dark" &&
63 (this.mode
!= "idle" ||
64 (this.vr
.moves
.length
> 0 && this.cursor
==this.vr
.moves
.length
));
65 const gameDiv
= h('div',
72 [_
.range(sizeX
).map(i
=> {
73 let ci
= (this.mycolor
=='w' ? i : sizeX
-i
-1);
80 style: { 'opacity': this.choices
.length
>0?"0.5":"1" },
82 _
.range(sizeY
).map(j
=> {
83 let cj
= (this.mycolor
=='w' ? j : sizeY
-j
-1);
85 if (this.vr
.board
[ci
][cj
] != VariantRules
.EMPTY
&& (variant
.name
!="Dark"
86 || this.score
!="*" || this.vr
.enlightened
[this.mycolor
][ci
][cj
]))
94 'ghost': !!this.selectedPiece
95 && this.selectedPiece
.parentNode
.id
== "sq-"+ci
+"-"+cj
,
98 src: "/images/pieces/" +
99 VariantRules
.getPpath(this.vr
.board
[ci
][cj
]) + ".svg",
105 if (this.hints
&& hintSquares
[ci
][cj
])
115 src: "/images/mark.svg",
126 ['board'+sizeY
]: true,
127 'light-square': (i
+j
)%2==0,
128 'dark-square': (i
+j
)%2==1,
130 'in-shadow': variant
.name
=="Dark" && this.score
=="*"
131 && !this.vr
.enlightened
[this.mycolor
][ci
][cj
],
132 'highlight': showLight
&& !!lm
&& _
.isMatch(lm
.end
, {x:ci
,y:cj
}),
133 'incheck': showLight
&& incheckSq
[ci
][cj
],
136 id: this.getSquareId({x:ci
,y:cj
}),
145 if (!!this.vr
.reserve
)
147 const shiftIdx
= (this.mycolor
=="w" ? 0 : 1);
148 let myReservePiecesArray
= [];
149 for (let i
=0; i
<VariantRules
.RESERVE_PIECES
.length
; i
++)
151 myReservePiecesArray
.push(h('div',
153 'class': {'board':true, ['board'+sizeY
]:true},
154 attrs: { id: this.getSquareId({x:sizeX
+shiftIdx
,y:i
}) }
159 'class': {"piece":true, "reserve":true},
161 "src": "/images/pieces/" +
162 this.vr
.getReservePpath(this.mycolor
,i
) + ".svg",
166 {"class": { "reserve-count": true } },
167 [ this.vr
.reserve
[this.mycolor
][VariantRules
.RESERVE_PIECES
[i
]] ]
171 let oppReservePiecesArray
= [];
172 const oppCol
= this.vr
.getOppCol(this.mycolor
);
173 for (let i
=0; i
<VariantRules
.RESERVE_PIECES
.length
; i
++)
175 oppReservePiecesArray
.push(h('div',
177 'class': {'board':true, ['board'+sizeY
]:true},
178 attrs: { id: this.getSquareId({x:sizeX
+(1-shiftIdx
),y:i
}) }
183 'class': {"piece":true, "reserve":true},
185 "src": "/images/pieces/" +
186 this.vr
.getReservePpath(oppCol
,i
) + ".svg",
190 {"class": { "reserve-count": true } },
191 [ this.vr
.reserve
[oppCol
][VariantRules
.RESERVE_PIECES
[i
]] ]
195 let reserves
= h('div',
207 "reserve-row-1": true,
213 { 'class': { 'row': true }},
214 oppReservePiecesArray
218 elementArray
.push(reserves
);
220 // Show current FEN (just below board, lower right corner)
221 // (if mode != Dark ...)
225 attrs: { id: "fen-div" },
226 "class": { "section-content": true },
231 attrs: { id: "fen-string" },
232 domProps: { innerHTML: this.vr
.getBaseFen() },
233 "class": { "text-center": true },
240 mousedown: this.mousedown
,
241 mousemove: this.mousemove
,
242 mouseup: this.mouseup
,
243 touchstart: this.mousedown
,
244 touchmove: this.mousemove
,
245 touchend: this.mouseup
,
249 // TODO: "chessground-like" component
250 // Get the identifier of a HTML table cell from its numeric coordinates o.x,o.y.
251 getSquareId: function(o
) {
252 // NOTE: a separator is required to allow any size of board
253 return "sq-" + o
.x
+ "-" + o
.y
;
256 getSquareFromId: function(id
) {
257 let idParts
= id
.split('-');
258 return [parseInt(idParts
[1]), parseInt(idParts
[2])];
260 mousedown: function(e
) {
261 e
= e
|| window
.event
;
264 while (!ingame
&& elem
!== null)
266 if (elem
.classList
.contains("game"))
271 elem
= elem
.parentElement
;
273 if (!ingame
) //let default behavior (click on button...)
275 e
.preventDefault(); //disable native drag & drop
276 if (!this.selectedPiece
&& e
.target
.classList
.contains("piece"))
278 // Next few lines to center the piece on mouse cursor
279 let rect
= e
.target
.parentNode
.getBoundingClientRect();
281 x: rect
.x
+ rect
.width
/2,
282 y: rect
.y
+ rect
.width
/2,
283 id: e
.target
.parentNode
.id
285 this.selectedPiece
= e
.target
.cloneNode();
286 this.selectedPiece
.style
.position
= "absolute";
287 this.selectedPiece
.style
.top
= 0;
288 this.selectedPiece
.style
.display
= "inline-block";
289 this.selectedPiece
.style
.zIndex
= 3000;
290 const startSquare
= this.getSquareFromId(e
.target
.parentNode
.id
);
291 this.possibleMoves
= [];
292 if (this.score
== "*")
295 // TODO: essentially adapt this (all other things do not change much)
296 // if inside a real game, mycolor should be provided ? (simplest way)
298 const color
= ["friend","problem"].includes(this.mode
)
301 if (this.vr
.canIplay(color
,startSquare
))
302 this.possibleMoves
= this.vr
.getPossibleMovesFrom(startSquare
);
304 // Next line add moving piece just after current image
305 // (required for Crazyhouse reserve)
306 e
.target
.parentNode
.insertBefore(this.selectedPiece
, e
.target
.nextSibling
);
309 mousemove: function(e
) {
310 if (!this.selectedPiece
)
312 e
= e
|| window
.event
;
313 // If there is an active element, move it around
314 if (!!this.selectedPiece
)
316 const [offsetX
,offsetY
] = !!e
.clientX
317 ? [e
.clientX
,e
.clientY
] //desktop browser
318 : [e
.changedTouches
[0].pageX
, e
.changedTouches
[0].pageY
]; //smartphone
319 this.selectedPiece
.style
.left
= (offsetX
-this.start
.x
) + "px";
320 this.selectedPiece
.style
.top
= (offsetY
-this.start
.y
) + "px";
323 mouseup: function(e
) {
324 if (!this.selectedPiece
)
326 e
= e
|| window
.event
;
327 // Read drop target (or parentElement, parentNode... if type == "img")
328 this.selectedPiece
.style
.zIndex
= -3000; //HACK to find square from final coords
329 const [offsetX
,offsetY
] = !!e
.clientX
330 ? [e
.clientX
,e
.clientY
]
331 : [e
.changedTouches
[0].pageX
, e
.changedTouches
[0].pageY
];
332 let landing
= document
.elementFromPoint(offsetX
, offsetY
);
333 this.selectedPiece
.style
.zIndex
= 3000;
334 // Next condition: classList.contains(piece) fails because of marks
335 while (landing
.tagName
== "IMG")
336 landing
= landing
.parentNode
;
337 if (this.start
.id
== landing
.id
)
339 // A click: selectedPiece and possibleMoves are already filled
342 // OK: process move attempt
343 let endSquare
= this.getSquareFromId(landing
.id
);
344 let moves
= this.findMatchingMoves(endSquare
);
345 this.possibleMoves
= [];
346 if (moves
.length
> 1)
347 this.choices
= moves
;
348 else if (moves
.length
==1)
350 // Else: impossible move
351 this.selectedPiece
.parentNode
.removeChild(this.selectedPiece
);
352 delete this.selectedPiece
;
353 this.selectedPiece
= null;
355 findMatchingMoves: function(endSquare
) {
356 // Run through moves list and return the matching set (if promotions...)
358 this.possibleMoves
.forEach(function(m
) {
359 if (endSquare
[0] == m
.end
.x
&& endSquare
[1] == m
.end
.y
)
364 animateMove: function(move) {
365 let startSquare
= document
.getElementById(this.getSquareId(move.start
));
366 let endSquare
= document
.getElementById(this.getSquareId(move.end
));
367 let rectStart
= startSquare
.getBoundingClientRect();
368 let rectEnd
= endSquare
.getBoundingClientRect();
369 let translation
= {x:rectEnd
.x
-rectStart
.x
, y:rectEnd
.y
-rectStart
.y
};
371 document
.querySelector("#" + this.getSquareId(move.start
) + " > img.piece");
372 // HACK for animation (with positive translate, image slides "under background")
373 // Possible improvement: just alter squares on the piece's way...
374 squares
= document
.getElementsByClassName("board");
375 for (let i
=0; i
<squares
.length
; i
++)
377 let square
= squares
.item(i
);
378 if (square
.id
!= this.getSquareId(move.start
))
379 square
.style
.zIndex
= "-1";
381 movingPiece
.style
.transform
= "translate(" + translation
.x
+ "px," +
382 translation
.y
+ "px)";
383 movingPiece
.style
.transitionDuration
= "0.2s";
384 movingPiece
.style
.zIndex
= "3000";
386 for (let i
=0; i
<squares
.length
; i
++)
387 squares
.item(i
).style
.zIndex
= "auto";
388 movingPiece
.style
= {}; //required e.g. for 0-0 with KR swap