1 class CheckeredRules
extends ChessRules
6 return b
[0]=='c' ? "Checkered/"+b : b
;
10 const checkered_codes
= {
18 return checkered_codes
[b
[1]];
19 return ChessRules
.board2fen(b
);
23 const checkered_pieces
= {
30 if (Object
.keys(checkered_pieces
).includes(f
))
31 return 'c'+checkered_pieces
[f
];
32 return ChessRules
.fen2board(f
);
37 super.initVariables(fen
);
38 // Decode last non-capturing checkered move (if any)
39 // TODO: since now we store moves list, this can disappear
40 const cmove
= fen
.split(" ")[4];
43 const piece
= cmove
.charAt(0);
44 const startEnd
= cmove
.substr(1).split(";");
45 const start
= startEnd
[0].split(",");
46 const end
= startEnd
[1].split(",");
47 this.moves
.push(new Move({
48 appear: [ new PiPo({c:"c", p:piece
, x:end
[0], y:end
[1]}) ],
49 vanish: [ new PiPo({c:"c", p:piece
, x:start
[0], y:start
[1]}) ]
57 ChessRules
.GetFlags(fen
), //castle
59 "w": new Array(8), //pawns can move 2 squares
63 const fenFlags
= fen
.split(" ")[1].substr(4); //skip first 4 digits, for castle
64 for (let c
of ['w','b'])
66 for (let i
=0; i
<8; i
++)
67 flags
[1][c
][i
] = (fenFlags
.charAt((c
=='w'?0:8)+i
) == '1');
72 // can color1 take color2?
73 canTake(color1
, color2
)
75 // Checkered aren't captured
76 return color1
!= color2
&& color2
!= 'c' && (color1
!= 'c' || color2
!= this.turn
);
79 // Build regular move(s) from its initial and destination squares; tr: transformation
80 getBasicMove(sx
, sy
, ex
, ey
, tr
)
82 if (this.board
[ex
][ey
] == VariantRules
.EMPTY
)
84 // No capture, standard move construction
85 return [super.getBasicMove(sx
,sy
,ex
,ey
,tr
)];
87 let moves
= []; //captures: generally 2 choices, unless 'tr' is specified or piece==king
88 const startPiece
= this.getPiece(sx
,sy
);
89 const endPiece
= this.getPiece(ex
,ey
);
90 const startColor
= this.getColor(sx
,sy
);
91 const endColor
= this.getColor(ex
,ey
);
92 for (let piece
of !!tr
? [tr
] :
93 (startPiece
==VariantRules
.KING
? VariantRules
.KING : _
.uniq([startPiece
,endPiece
])))
100 c: startPiece
==VariantRules
.KING
? startColor : 'c',
124 // Generic method to find possible moves of non-pawn pieces ("sliding or jumping")
125 getSlideNJumpMoves(x
, y
, color
, steps
, oneStep
)
128 let [sizeX
,sizeY
] = VariantRules
.size
;
130 for (var loop
=0; loop
<steps
.length
; loop
++)
132 var step
= steps
[loop
];
135 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
136 && this.board
[i
][j
] == VariantRules
.EMPTY
)
138 moves
.push(this.getBasicMove(x
, y
, i
, j
)[0]); //no capture
139 if (oneStep
!== undefined)
144 if (i
>=0 && i
<8 && j
>=0 && j
<8 && this.canTake(color
, this.getColor(i
,j
)))
145 moves
= moves
.concat(this.getBasicMove(x
, y
, i
, j
));
150 // What are the pawn moves from square x,y considering color "color" ?
151 getPotentialPawnMoves(x
, y
, color
)
154 var V
= VariantRules
;
155 let [sizeX
,sizeY
] = VariantRules
.size
;
156 const c
= (color
== 'c' ? this.turn : color
);
157 const shift
= (c
== "w" ? -1 : 1);
158 let startRank
= (c
== "w" ? sizeY
-2 : 1);
159 let lastRank
= (c
== "w" ? 0 : sizeY
-1);
161 if (x
+shift
>= 0 && x
+shift
< sizeX
&& x
+shift
!= lastRank
)
164 if (this.board
[x
+shift
][y
] == V
.EMPTY
)
166 moves
.push(this.getBasicMove(x
, y
, x
+shift
, y
)[0]);
167 if (x
==startRank
&& this.board
[x
+2*shift
][y
] == V
.EMPTY
&& this.flags
[1][c
][y
])
170 moves
.push(this.getBasicMove(x
, y
, x
+2*shift
, y
)[0]);
174 if (y
>0 && this.canTake(this.getColor(x
,y
), this.getColor(x
+shift
,y
-1))
175 && this.board
[x
+shift
][y
-1] != V
.EMPTY
)
177 moves
= moves
.concat(this.getBasicMove(x
, y
, x
+shift
, y
-1));
179 if (y
<sizeY
-1 && this.canTake(this.getColor(x
,y
), this.getColor(x
+shift
,y
+1))
180 && this.board
[x
+shift
][y
+1] != V
.EMPTY
)
182 moves
= moves
.concat(this.getBasicMove(x
, y
, x
+shift
, y
+1));
186 if (x
+shift
== lastRank
)
189 let promotionPieces
= [V
.ROOK
,V
.KNIGHT
,V
.BISHOP
,V
.QUEEN
];
190 promotionPieces
.forEach(p
=> {
192 if (this.board
[x
+shift
][y
] == V
.EMPTY
)
193 moves
.push(this.getBasicMove(x
, y
, x
+shift
, y
, p
)[0]);
195 if (y
>0 && this.canTake(this.getColor(x
,y
), this.getColor(x
+shift
,y
-1))
196 && this.board
[x
+shift
][y
-1] != V
.EMPTY
)
198 moves
= moves
.concat(this.getBasicMove(x
, y
, x
+shift
, y
-1, p
));
200 if (y
<sizeY
-1 && this.canTake(this.getColor(x
,y
), this.getColor(x
+shift
,y
+1))
201 && this.board
[x
+shift
][y
+1] != V
.EMPTY
)
203 moves
= moves
.concat(this.getBasicMove(x
, y
, x
+shift
, y
+1, p
));
209 const Lep
= this.epSquares
.length
;
210 const epSquare
= Lep
>0 ? this.epSquares
[Lep
-1] : undefined;
211 if (!!epSquare
&& epSquare
.x
== x
+shift
&& Math
.abs(epSquare
.y
- y
) == 1)
213 let epStep
= epSquare
.y
- y
;
214 var enpassantMove
= this.getBasicMove(x
, y
, x
+shift
, y
+epStep
)[0];
215 enpassantMove
.vanish
.push({
219 c: this.getColor(x
,y
+epStep
)
221 enpassantMove
.appear
[0].c
= 'c';
222 moves
.push(enpassantMove
);
228 getCastleMoves(x
,y
,c
)
230 if (x
!= (c
=="w" ? 7 : 0) || y
!= this.INIT_COL_KING
[c
])
231 return []; //x isn't first rank, or king has moved (shortcut)
233 const V
= VariantRules
;
236 const oppCol
= this.getOppCol(c
);
239 const finalSquares
= [ [2,3], [6,5] ]; //king, then rook
241 for (let castleSide
=0; castleSide
< 2; castleSide
++) //large, then small
243 if (!this.flags
[0][c
][castleSide
])
245 // If this code is reached, rooks and king are on initial position
247 // Nothing on the path of the king (and no checks; OK also if y==finalSquare)?
248 let step
= finalSquares
[castleSide
][0] < y
? -1 : 1;
249 for (i
=y
; i
!=finalSquares
[castleSide
][0]; i
+=step
)
251 if (this.isAttacked([x
,i
], oppCol
) || (this.board
[x
][i
] != V
.EMPTY
&&
252 // NOTE: next check is enough, because of chessboard constraints
253 (this.getColor(x
,i
) != c
|| ![V
.KING
,V
.ROOK
].includes(this.getPiece(x
,i
)))))
255 continue castlingCheck
;
259 // Nothing on the path to the rook?
260 step
= castleSide
== 0 ? -1 : 1;
261 for (i
= y
+ step
; i
!= this.INIT_COL_ROOK
[c
][castleSide
]; i
+= step
)
263 if (this.board
[x
][i
] != V
.EMPTY
)
264 continue castlingCheck
;
266 const rookPos
= this.INIT_COL_ROOK
[c
][castleSide
];
268 // Nothing on final squares, except maybe king and castling rook?
271 if (this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
272 this.getPiece(x
,finalSquares
[castleSide
][i
]) != V
.KING
&&
273 finalSquares
[castleSide
][i
] != rookPos
)
275 continue castlingCheck
;
279 // If this code is reached, castle is valid
280 moves
.push( new Move({
282 new PiPo({x:x
,y:finalSquares
[castleSide
][0],p:V
.KING
,c:c
}),
283 new PiPo({x:x
,y:finalSquares
[castleSide
][1],p:V
.ROOK
,c:c
})],
285 new PiPo({x:x
,y:y
,p:V
.KING
,c:c
}),
286 new PiPo({x:x
,y:rookPos
,p:V
.ROOK
,c:c
})],
287 end: Math
.abs(y
- rookPos
) <= 2
289 : {x:x
, y:y
+ 2 * (castleSide
==0 ? -1 : 1)}
298 return ((color
=='w' && this.movesCount
%2==0) || color
=='c'
299 || (color
=='b' && this.movesCount
%2==1))
300 && [color
,'c'].includes(this.getColor(sq
[0], sq
[1]));
303 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
304 oppositeMoves(m1
, m2
)
306 return m1
.appear
.length
== 1 && m2
.appear
.length
== 1
307 && m1
.vanish
.length
== 1 && m2
.vanish
.length
== 1
308 // && _.isEqual(m1.appear[0], m2.vanish[0]) //fails in HH case
309 // && _.isEqual(m1.vanish[0], m2.appear[0]);
310 && m1
.start
.x
== m2
.end
.x
&& m1
.end
.x
== m2
.start
.x
311 && m1
.start
.y
== m2
.end
.y
&& m1
.end
.y
== m2
.start
.y
312 && m1
.appear
[0].c
== m2
.vanish
[0].c
&& m1
.appear
[0].p
== m2
.vanish
[0].p
313 && m1
.vanish
[0].c
== m2
.appear
[0].c
&& m1
.vanish
[0].p
== m2
.appear
[0].p
;
318 if (moves
.length
== 0)
320 let color
= this.getColor( moves
[0].start
.x
, moves
[0].start
.y
);
321 return moves
.filter(m
=> {
322 const L
= this.moves
.length
;
323 if (L
> 0 && this.oppositeMoves(this.moves
[L
-1], m
))
325 return !this.underCheck(m
, color
);
329 isAttackedByPawn([x
,y
], c
)
331 const color
= (c
=="c" ? this.turn : c
);
332 let pawnShift
= (color
=="w" ? 1 : -1);
333 if (x
+pawnShift
>=0 && x
+pawnShift
<8)
335 for (let i
of [-1,1])
337 if (y
+i
>=0 && y
+i
<8 && this.getPiece(x
+pawnShift
,y
+i
)==VariantRules
.PAWN
338 && this.getColor(x
+pawnShift
,y
+i
)==c
)
349 const color
= c
== 'c' ? this.turn : c
;
351 let res
= this.isAttacked(this.kingPos
[color
], this.getOppCol(color
))
352 || this.isAttacked(this.kingPos
[color
], 'c'); //TODO: quite inefficient...
357 getCheckSquares(move, c
)
360 const kingAttacked
= this.isAttacked(this.kingPos
[c
], this.getOppCol(c
))
361 || this.isAttacked(this.kingPos
[c
], 'c');
362 let res
= kingAttacked
363 ? [ JSON
.parse(JSON
.stringify(this.kingPos
[c
])) ] //need to duplicate!
369 updateVariables(move)
371 const piece
= this.getPiece(move.start
.x
,move.start
.y
);
372 const c
= this.getColor(move.start
.x
,move.start
.y
);
374 if (c
!= 'c') //checkered not concerned by castle flags
376 const firstRank
= (c
== "w" ? 7 : 0);
377 // Update king position + flags
378 if (piece
== VariantRules
.KING
&& move.appear
.length
> 0)
380 this.kingPos
[c
][0] = move.appear
[0].x
;
381 this.kingPos
[c
][1] = move.appear
[0].y
;
382 this.flags
[0][c
] = [false,false];
385 const oppCol
= this.getOppCol(c
);
386 const oppFirstRank
= 7 - firstRank
;
387 if (move.start
.x
== firstRank
//our rook moves?
388 && this.INIT_COL_ROOK
[c
].includes(move.start
.y
))
390 const flagIdx
= move.start
.y
== this.INIT_COL_ROOK
[c
][0] ? 0 : 1;
391 this.flags
[0][c
][flagIdx
] = false;
393 else if (move.end
.x
== oppFirstRank
//we took opponent rook?
394 && this.INIT_COL_ROOK
[c
].includes(move.end
.y
))
396 const flagIdx
= move.end
.y
== this.INIT_COL_ROOK
[oppCol
][0] ? 0 : 1;
397 this.flags
[0][oppCol
][flagIdx
] = false;
401 // Does it turn off a 2-squares pawn flag?
402 const secondRank
= [1,6];
403 if (secondRank
.includes(move.start
.x
) && move.vanish
[0].p
== VariantRules
.PAWN
)
404 this.flags
[1][move.start
.x
==6 ? "w" : "b"][move.start
.y
] = false;
409 super.play(move, ingame
);
411 this.moves
.push(move); //needed for turn indication for checkered pieces
422 if (!this.isAttacked(this.kingPos
[color
], this.getOppCol(color
))
423 && !this.isAttacked(this.kingPos
[color
], 'c'))
428 return color
== "w" ? "0-1" : "1-0";
433 const [sizeX
,sizeY
] = VariantRules
.size
;
435 //Just count material for now, considering checkered neutral (...)
436 for (let i
=0; i
<sizeX
; i
++)
438 for (let j
=0; j
<sizeY
; j
++)
440 if (this.board
[i
][j
] != VariantRules
.EMPTY
)
442 const sqColor
= this.getColor(i
,j
);
443 const sign
= sqColor
== "w" ? 1 : (sqColor
=="b" ? -1 : 0);
444 evaluation
+= sign
* VariantRules
.VALUES
[this.getPiece(i
,j
)];
451 static GenRandInitFen()
453 let fen
= ChessRules
.GenRandInitFen();
454 return fen
.replace(/ - 0$/, "1111111111111111 - 0 -");
459 let fen
= super.getFen() + " ";
460 const L
= this.moves
.length
;
461 if (L
> 0 && this.moves
[L
-1].vanish
.length
==1 && this.moves
[L
-1].appear
[0].c
=="c")
463 // Ok, un-cancellable checkered move
464 fen
+= this.moves
[L
-1].appear
[0].p
465 + this.moves
[L
-1].start
.x
+ "," + this.moves
[L
-1].start
.y
+ ";"
466 + this.moves
[L
-1].end
.x
+ "," + this.moves
[L
-1].end
.y
;
475 // Add castling flags
476 for (let c
of ['w','b'])
478 for (let i
=0; i
<2; i
++)
479 fen
+= this.flags
[0][c
][i
] ? '1' : '0';
482 for (let c
of ['w','b'])
484 for (let i
=0; i
<8; i
++)
485 fen
+= this.flags
[1][c
][i
] ? '1' : '0';
492 if (move.appear
.length
== 2)
495 if (move.end
.y
< move.start
.y
)
501 // Translate final square
503 String
.fromCharCode(97 + move.end
.y
) + (VariantRules
.size
[0]-move.end
.x
);
505 let piece
= this.getPiece(move.start
.x
, move.start
.y
);
506 if (piece
== VariantRules
.PAWN
)
510 if (move.vanish
.length
> 1)
513 let startColumn
= String
.fromCharCode(97 + move.start
.y
);
514 notation
= startColumn
+ "x" + finalSquare
+ "=" + move.appear
[0].p
.toUpperCase();
517 notation
= finalSquare
;
518 if (move.appear
.length
> 0 && piece
!= move.appear
[0].p
) //promotion
519 notation
+= "=" + move.appear
[0].p
.toUpperCase();
526 return piece
.toUpperCase() + (move.vanish
.length
> 1 ? "x" : "") + finalSquare
527 + (move.vanish
.length
> 1 ? "=" + move.appear
[0].p
.toUpperCase() : "");