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 const cmove
= fen
.split(" ")[4];
42 const piece
= cmove
.charAt(0);
43 const startEnd
= cmove
.substr(1).split(";");
44 const start
= startEnd
[0].split(",");
45 const end
= startEnd
[1].split(",");
46 this.moves
.push(new Move({
47 appear: [ new PiPo({c:"c", p:piece
, x:end
[0], y:end
[1]}) ],
48 vanish: [ new PiPo({c:"c", p:piece
, x:start
[0], y:start
[1]}) ]
56 ChessRules
.GetFlags(fen
), //castle
58 "w": new Array(8), //pawns can move 2 squares
62 const fenFlags
= fen
.split(" ")[1].substr(4); //skip first 4 digits, for castle
63 for (let c
of ['w','b'])
65 for (let i
=0; i
<8; i
++)
66 flags
[1][c
][i
] = (fenFlags
.charAt((c
=='w'?0:8)+i
) == '1');
71 // can color1 take color2?
72 canTake(color1
, color2
)
74 // Checkered aren't captured
75 return color1
!= color2
&& color2
!= 'c' && (color1
!= 'c' || color2
!= this.turn
);
78 // Build regular move(s) from its initial and destination squares; tr: transformation
79 getBasicMove(sx
, sy
, ex
, ey
, tr
)
81 if (this.board
[ex
][ey
] == VariantRules
.EMPTY
)
83 // No capture, standard move construction
84 return [super.getBasicMove(sx
,sy
,ex
,ey
,tr
)];
86 let moves
= []; //captures: generally 2 choices, unless 'tr' is specified or piece==king
87 const startPiece
= this.getPiece(sx
,sy
);
88 const endPiece
= this.getPiece(ex
,ey
);
89 const startColor
= this.getColor(sx
,sy
);
90 const endColor
= this.getColor(ex
,ey
);
91 for (let piece
of !!tr
? [tr
] :
92 (startPiece
==VariantRules
.KING
? VariantRules
.KING : _
.uniq([startPiece
,endPiece
])))
99 c: startPiece
==VariantRules
.KING
? startColor : 'c',
123 // Generic method to find possible moves of non-pawn pieces ("sliding or jumping")
124 getSlideNJumpMoves(x
, y
, color
, steps
, oneStep
)
127 let [sizeX
,sizeY
] = VariantRules
.size
;
129 for (var loop
=0; loop
<steps
.length
; loop
++)
131 var step
= steps
[loop
];
134 while (i
>=0 && i
<sizeX
&& j
>=0 && j
<sizeY
135 && this.board
[i
][j
] == VariantRules
.EMPTY
)
137 moves
.push(this.getBasicMove(x
, y
, i
, j
)[0]); //no capture
138 if (oneStep
!== undefined)
143 if (i
>=0 && i
<8 && j
>=0 && j
<8 && this.canTake(color
, this.getColor(i
,j
)))
144 moves
= moves
.concat(this.getBasicMove(x
, y
, i
, j
));
149 // What are the pawn moves from square x,y considering color "color" ?
150 getPotentialPawnMoves(x
, y
, color
)
153 var V
= VariantRules
;
154 let [sizeX
,sizeY
] = VariantRules
.size
;
155 const c
= (color
== 'c' ? this.turn : color
);
156 const shift
= (c
== "w" ? -1 : 1);
157 let startRank
= (c
== "w" ? sizeY
-2 : 1);
158 let lastRank
= (c
== "w" ? 0 : sizeY
-1);
160 if (x
+shift
>= 0 && x
+shift
< sizeX
&& x
+shift
!= lastRank
)
163 if (this.board
[x
+shift
][y
] == V
.EMPTY
)
165 moves
.push(this.getBasicMove(x
, y
, x
+shift
, y
)[0]);
166 if (x
==startRank
&& this.board
[x
+2*shift
][y
] == V
.EMPTY
&& this.flags
[1][c
][y
])
169 moves
.push(this.getBasicMove(x
, y
, x
+2*shift
, y
)[0]);
173 if (y
>0 && this.canTake(this.getColor(x
,y
), this.getColor(x
+shift
,y
-1))
174 && this.board
[x
+shift
][y
-1] != V
.EMPTY
)
176 moves
= moves
.concat(this.getBasicMove(x
, y
, x
+shift
, y
-1));
178 if (y
<sizeY
-1 && this.canTake(this.getColor(x
,y
), this.getColor(x
+shift
,y
+1))
179 && this.board
[x
+shift
][y
+1] != V
.EMPTY
)
181 moves
= moves
.concat(this.getBasicMove(x
, y
, x
+shift
, y
+1));
185 if (x
+shift
== lastRank
)
188 let promotionPieces
= [V
.ROOK
,V
.KNIGHT
,V
.BISHOP
,V
.QUEEN
];
189 promotionPieces
.forEach(p
=> {
191 if (this.board
[x
+shift
][y
] == V
.EMPTY
)
192 moves
.push(this.getBasicMove(x
, y
, x
+shift
, y
, p
)[0]);
194 if (y
>0 && this.canTake(this.getColor(x
,y
), this.getColor(x
+shift
,y
-1))
195 && this.board
[x
+shift
][y
-1] != V
.EMPTY
)
197 moves
= moves
.concat(this.getBasicMove(x
, y
, x
+shift
, y
-1, p
));
199 if (y
<sizeY
-1 && this.canTake(this.getColor(x
,y
), this.getColor(x
+shift
,y
+1))
200 && this.board
[x
+shift
][y
+1] != V
.EMPTY
)
202 moves
= moves
.concat(this.getBasicMove(x
, y
, x
+shift
, y
+1, p
));
208 const Lep
= this.epSquares
.length
;
209 const epSquare
= Lep
>0 ? this.epSquares
[Lep
-1] : undefined;
210 if (!!epSquare
&& epSquare
.x
== x
+shift
&& Math
.abs(epSquare
.y
- y
) == 1)
212 let epStep
= epSquare
.y
- y
;
213 var enpassantMove
= this.getBasicMove(x
, y
, x
+shift
, y
+epStep
)[0];
214 enpassantMove
.vanish
.push({
218 c: this.getColor(x
,y
+epStep
)
220 enpassantMove
.appear
[0].c
= 'c';
221 moves
.push(enpassantMove
);
227 getCastleMoves(x
,y
,c
)
229 if (x
!= (c
=="w" ? 7 : 0) || y
!= this.INIT_COL_KING
[c
])
230 return []; //x isn't first rank, or king has moved (shortcut)
232 const V
= VariantRules
;
235 const oppCol
= this.getOppCol(c
);
238 const finalSquares
= [ [2,3], [6,5] ]; //king, then rook
240 for (let castleSide
=0; castleSide
< 2; castleSide
++) //large, then small
242 if (!this.flags
[0][c
][castleSide
])
244 // If this code is reached, rooks and king are on initial position
246 // Nothing on the path of the king (and no checks; OK also if y==finalSquare)?
247 let step
= finalSquares
[castleSide
][0] < y
? -1 : 1;
248 for (i
=y
; i
!=finalSquares
[castleSide
][0]; i
+=step
)
250 if (this.isAttacked([x
,i
], oppCol
) || (this.board
[x
][i
] != V
.EMPTY
&&
251 // NOTE: next check is enough, because of chessboard constraints
252 (this.getColor(x
,i
) != c
|| ![V
.KING
,V
.ROOK
].includes(this.getPiece(x
,i
)))))
254 continue castlingCheck
;
258 // Nothing on the path to the rook?
259 step
= castleSide
== 0 ? -1 : 1;
260 for (i
= y
+ step
; i
!= this.INIT_COL_ROOK
[c
][castleSide
]; i
+= step
)
262 if (this.board
[x
][i
] != V
.EMPTY
)
263 continue castlingCheck
;
265 const rookPos
= this.INIT_COL_ROOK
[c
][castleSide
];
267 // Nothing on final squares, except maybe king and castling rook?
270 if (this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
271 this.getPiece(x
,finalSquares
[castleSide
][i
]) != V
.KING
&&
272 finalSquares
[castleSide
][i
] != rookPos
)
274 continue castlingCheck
;
278 // If this code is reached, castle is valid
279 moves
.push( new Move({
281 new PiPo({x:x
,y:finalSquares
[castleSide
][0],p:V
.KING
,c:c
}),
282 new PiPo({x:x
,y:finalSquares
[castleSide
][1],p:V
.ROOK
,c:c
})],
284 new PiPo({x:x
,y:y
,p:V
.KING
,c:c
}),
285 new PiPo({x:x
,y:rookPos
,p:V
.ROOK
,c:c
})],
286 end: Math
.abs(y
- rookPos
) <= 2
288 : {x:x
, y:y
+ 2 * (castleSide
==0 ? -1 : 1)}
297 return ((color
=='w' && this.movesCount
%2==0) || color
=='c'
298 || (color
=='b' && this.movesCount
%2==1))
299 && [color
,'c'].includes(this.getColor(sq
[0], sq
[1]));
302 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
303 oppositeMoves(m1
, m2
)
305 return m1
.appear
.length
== 1 && m2
.appear
.length
== 1
306 && m1
.vanish
.length
== 1 && m2
.vanish
.length
== 1
307 // && _.isEqual(m1.appear[0], m2.vanish[0]) //fails in HH case
308 // && _.isEqual(m1.vanish[0], m2.appear[0]);
309 && m1
.start
.x
== m2
.end
.x
&& m1
.end
.x
== m2
.start
.x
310 && m1
.start
.y
== m2
.end
.y
&& m1
.end
.y
== m2
.start
.y
311 && m1
.appear
[0].c
== m2
.vanish
[0].c
&& m1
.appear
[0].p
== m2
.vanish
[0].p
312 && m1
.vanish
[0].c
== m2
.appear
[0].c
&& m1
.vanish
[0].p
== m2
.appear
[0].p
;
317 if (moves
.length
== 0)
319 let color
= this.getColor( moves
[0].start
.x
, moves
[0].start
.y
);
320 return moves
.filter(m
=> {
321 const L
= this.moves
.length
;
322 if (L
> 0 && this.oppositeMoves(this.moves
[L
-1], m
))
324 return !this.underCheck(m
, color
);
328 isAttackedByPawn([x
,y
], c
)
330 const color
= (c
=="c" ? this.turn : c
);
331 let pawnShift
= (color
=="w" ? 1 : -1);
332 if (x
+pawnShift
>=0 && x
+pawnShift
<8)
334 for (let i
of [-1,1])
336 if (y
+i
>=0 && y
+i
<8 && this.getPiece(x
+pawnShift
,y
+i
)==VariantRules
.PAWN
337 && this.getColor(x
+pawnShift
,y
+i
)==c
)
348 const color
= c
== 'c' ? this.turn : c
;
350 let res
= this.isAttacked(this.kingPos
[color
], this.getOppCol(color
))
351 || this.isAttacked(this.kingPos
[color
], 'c'); //TODO: quite inefficient...
356 updateVariables(move)
358 const piece
= this.getPiece(move.start
.x
,move.start
.y
);
359 const c
= this.getColor(move.start
.x
,move.start
.y
);
361 if (c
!= 'c') //checkered not concerned by castle flags
363 const firstRank
= (c
== "w" ? 7 : 0);
364 // Update king position + flags
365 if (piece
== VariantRules
.KING
&& move.appear
.length
> 0)
367 this.kingPos
[c
][0] = move.appear
[0].x
;
368 this.kingPos
[c
][1] = move.appear
[0].y
;
369 this.flags
[0][c
] = [false,false];
372 const oppCol
= this.getOppCol(c
);
373 const oppFirstRank
= 7 - firstRank
;
374 if (move.start
.x
== firstRank
//our rook moves?
375 && this.INIT_COL_ROOK
[c
].includes(move.start
.y
))
377 const flagIdx
= move.start
.y
== this.INIT_COL_ROOK
[c
][0] ? 0 : 1;
378 this.flags
[0][c
][flagIdx
] = false;
380 else if (move.end
.x
== oppFirstRank
//we took opponent rook?
381 && this.INIT_COL_ROOK
[c
].includes(move.end
.y
))
383 const flagIdx
= move.end
.y
== this.INIT_COL_ROOK
[oppCol
][0] ? 0 : 1;
384 this.flags
[0][oppCol
][flagIdx
] = false;
388 // Does it turn off a 2-squares pawn flag?
389 const secondRank
= [1,6];
390 if (secondRank
.includes(move.start
.x
) && move.vanish
[0].p
== VariantRules
.PAWN
)
391 this.flags
[1][move.start
.x
==6 ? "w" : "b"][move.start
.y
] = false;
396 super.play(move, ingame
);
398 this.moves
.push(move); //needed for turn indication for checkered pieces
409 if (!this.isAttacked(this.kingPos
[color
], this.getOppCol(color
))
410 && !this.isAttacked(this.kingPos
[color
], 'c'))
415 return color
== "w" ? "0-1" : "1-0";
420 const [sizeX
,sizeY
] = VariantRules
.size
;
422 //Just count material for now, considering checkered neutral (...)
423 for (let i
=0; i
<sizeX
; i
++)
425 for (let j
=0; j
<sizeY
; j
++)
427 if (this.board
[i
][j
] != VariantRules
.EMPTY
)
429 const sqColor
= this.getColor(i
,j
);
430 const sign
= sqColor
== "w" ? 1 : (sqColor
=="b" ? -1 : 0);
431 evaluation
+= sign
* VariantRules
.VALUES
[this.getPiece(i
,j
)];
438 static GenRandInitFen()
440 let fen
= ChessRules
.GenRandInitFen();
441 return fen
.replace(/ - 0$/, "1111111111111111 - 0 -");
446 let fen
= super.getFen() + " ";
447 const L
= this.moves
.length
;
448 if (L
> 0 && this.moves
[L
-1].vanish
.length
==1 && this.moves
[L
-1].appear
[0].c
=="c")
450 // Ok, un-cancellable checkered move
451 fen
+= this.moves
[L
-1].appear
[0].p
452 + this.moves
[L
-1].start
.x
+ "," + this.moves
[L
-1].start
.y
+ ";"
453 + this.moves
[L
-1].end
.x
+ "," + this.moves
[L
-1].end
.y
;
462 // Add castling flags
463 for (let c
of ['w','b'])
465 for (let i
=0; i
<2; i
++)
466 fen
+= this.flags
[0][c
][i
] ? '1' : '0';
469 for (let c
of ['w','b'])
471 for (let i
=0; i
<8; i
++)
472 fen
+= this.flags
[1][c
][i
] ? '1' : '0';
479 if (move.appear
.length
== 2)
482 if (move.end
.y
< move.start
.y
)
488 // Translate final square
490 String
.fromCharCode(97 + move.end
.y
) + (VariantRules
.size
[0]-move.end
.x
);
492 let piece
= this.getPiece(move.start
.x
, move.start
.y
);
493 if (piece
== VariantRules
.PAWN
)
497 if (move.vanish
.length
> 1)
500 let startColumn
= String
.fromCharCode(97 + move.start
.y
);
501 notation
= startColumn
+ "x" + finalSquare
+ "=" + move.appear
[0].p
.toUpperCase();
504 notation
= finalSquare
;
505 if (move.appear
.length
> 0 && piece
!= move.appear
[0].p
) //promotion
506 notation
+= "=" + move.appear
[0].p
.toUpperCase();
513 return piece
.toUpperCase() + (move.vanish
.length
> 1 ? "x" : "") + finalSquare
514 + (move.vanish
.length
> 1 ? "=" + move.appear
[0].p
.toUpperCase() : "");