1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
3 export class Checkered2Rules
extends ChessRules
{
6 const checkered_codes
= {
13 if (b
[0] == "c") return checkered_codes
[b
[1]];
14 return ChessRules
.board2fen(b
);
18 // Tolerate upper-case versions of checkered pieces (why not?)
19 const checkered_pieces
= {
31 if (Object
.keys(checkered_pieces
).includes(f
))
32 return "c" + checkered_pieces
[f
];
33 return ChessRules
.fen2board(f
);
37 return ChessRules
.PIECES
.concat(["s", "t", "u", "c", "o"]);
41 return (b
[0] == "c" ? "Checkered/" : "") + b
;
44 setOtherVariables(fen
) {
45 super.setOtherVariables(fen
);
46 // Local stack of non-capturing checkered moves:
48 const cmove
= V
.ParseFen(fen
).cmove
;
49 if (cmove
== "-") this.cmoves
.push(null);
52 start: ChessRules
.SquareToCoords(cmove
.substr(0, 2)),
53 end: ChessRules
.SquareToCoords(cmove
.substr(2))
58 static IsGoodFen(fen
) {
59 if (!ChessRules
.IsGoodFen(fen
)) return false;
60 const fenParts
= fen
.split(" ");
61 if (fenParts
.length
!= 6) return false;
62 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
67 static IsGoodFlags(flags
) {
68 // 4 for castle + 16 for pawns
69 return !!flags
.match(/^[a-z]{4,4}[01]{16,16}$/);
73 super.setFlags(fenflags
); //castleFlags
75 w: [...Array(8)], //pawns can move 2 squares?
78 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
79 for (let c
of ["w", "b"]) {
80 for (let i
= 0; i
< 8; i
++)
81 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
86 return [this.castleFlags
, this.pawnFlags
];
89 disaggregateFlags(flags
) {
90 this.castleFlags
= flags
[0];
91 this.pawnFlags
= flags
[1];
94 getEpSquare(moveOrSquare
) {
95 if (typeof moveOrSquare
!== "object" || moveOrSquare
.appear
[0].c
!= 'c')
96 return super.getEpSquare(moveOrSquare
);
97 // Checkered move: no en-passant
102 if (move.appear
[0].c
== "c" && move.vanish
.length
== 1)
103 return { start: move.start
, end: move.end
};
107 canTake([x1
, y1
], [x2
, y2
]) {
108 const color1
= this.getColor(x1
, y1
);
109 const color2
= this.getColor(x2
, y2
);
110 // Checkered aren't captured
114 (color1
!= "c" || color2
!= this.turn
)
118 // Post-processing: apply "checkerization" of standard moves
119 getPotentialMovesFrom([x
, y
]) {
120 let standardMoves
= super.getPotentialMovesFrom([x
, y
]);
121 const lastRank
= this.turn
== "w" ? 0 : 7;
122 // King is treated differently: it never turn checkered
123 if (this.getPiece(x
, y
) == V
.KING
) return standardMoves
;
125 standardMoves
.forEach(m
=> {
126 if (m
.vanish
[0].p
== V
.PAWN
) {
128 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
129 !this.pawnFlags
[this.turn
][m
.start
.y
]
131 return; //skip forbidden 2-squares jumps
134 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
135 m
.vanish
.length
== 2 &&
136 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
138 return; //checkered pawns cannot take en-passant
141 if (m
.vanish
.length
== 1)
145 // A capture occured (m.vanish.length == 2)
149 // Avoid promotions (already treated):
150 m
.appear
[0].p
!= m
.vanish
[1].p
&&
151 (m
.vanish
[0].p
!= V
.PAWN
|| m
.end
.x
!= lastRank
)
153 // Add transformation into captured piece
154 let m2
= JSON
.parse(JSON
.stringify(m
));
155 m2
.appear
[0].p
= m
.vanish
[1].p
;
163 getPotentialPawnMoves([x
, y
]) {
164 let moves
= super.getPotentialPawnMoves([x
, y
]);
165 // Post-process: set right color for checkered moves
166 if (this.getColor(x
, y
) == 'c') {
168 m
.appear
[0].c
= 'c'; //may be done twice if capture
175 canIplay(side
, [x
, y
]) {
176 return side
== this.turn
&& [side
, "c"].includes(this.getColor(x
, y
));
179 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
180 oppositeMoves(m1
, m2
) {
183 m2
.appear
[0].c
== "c" &&
184 m2
.appear
.length
== 1 &&
185 m2
.vanish
.length
== 1 &&
186 m1
.start
.x
== m2
.end
.x
&&
187 m1
.end
.x
== m2
.start
.x
&&
188 m1
.start
.y
== m2
.end
.y
&&
189 m1
.end
.y
== m2
.start
.y
194 if (moves
.length
== 0) return [];
195 const color
= this.turn
;
196 const L
= this.cmoves
.length
; //at least 1: init from FEN
197 return moves
.filter(m
=> {
198 if (this.oppositeMoves(this.cmoves
[L
- 1], m
)) return false;
200 const res
= !this.underCheck(color
);
207 const oppCol
= V
.GetOppCol(this.turn
);
208 let potentialMoves
= [];
209 for (let i
= 0; i
< V
.size
.x
; i
++) {
210 for (let j
= 0; j
< V
.size
.y
; j
++) {
211 // NOTE: just testing == color isn't enough because of checkered pieces
212 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) != oppCol
) {
213 Array
.prototype.push
.apply(
215 this.getPotentialMovesFrom([i
, j
])
220 return this.filterValid(potentialMoves
);
224 const oppCol
= V
.GetOppCol(this.turn
);
225 for (let i
= 0; i
< V
.size
.x
; i
++) {
226 for (let j
= 0; j
< V
.size
.y
; j
++) {
227 // NOTE: just testing == color isn't enough because of checkered pieces
228 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) != oppCol
) {
229 const moves
= this.getPotentialMovesFrom([i
, j
]);
230 if (moves
.length
> 0) {
231 for (let k
= 0; k
< moves
.length
; k
++) {
232 if (this.filterValid([moves
[k
]]).length
> 0) return true;
241 // colors: array, generally 'w' and 'c' or 'b' and 'c'
242 isAttacked(sq
, colors
) {
243 if (!Array
.isArray(colors
)) colors
= [colors
];
245 this.isAttackedByPawn(sq
, colors
) ||
246 this.isAttackedByRook(sq
, colors
) ||
247 this.isAttackedByKnight(sq
, colors
) ||
248 this.isAttackedByBishop(sq
, colors
) ||
249 this.isAttackedByQueen(sq
, colors
) ||
250 this.isAttackedByKing(sq
, colors
)
254 isAttackedByPawn([x
, y
], colors
) {
255 for (let c
of colors
) {
256 const color
= (c
== "c" ? this.turn : c
);
257 let pawnShift
= color
== "w" ? 1 : -1;
258 if (x
+ pawnShift
>= 0 && x
+ pawnShift
< 8) {
259 for (let i
of [-1, 1]) {
263 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
264 this.getColor(x
+ pawnShift
, y
+ i
) == c
274 isAttackedBySlideNJump([x
, y
], colors
, piece
, steps
, oneStep
) {
275 for (let step
of steps
) {
276 let rx
= x
+ step
[0],
278 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
284 this.getPiece(rx
, ry
) === piece
&&
285 colors
.includes(this.getColor(rx
, ry
))
293 isAttackedByRook(sq
, colors
) {
294 return this.isAttackedBySlideNJump(sq
, colors
, V
.ROOK
, V
.steps
[V
.ROOK
]);
297 isAttackedByKnight(sq
, colors
) {
298 return this.isAttackedBySlideNJump(
307 isAttackedByBishop(sq
, colors
) {
308 return this.isAttackedBySlideNJump(
309 sq
, colors
, V
.BISHOP
, V
.steps
[V
.BISHOP
]);
312 isAttackedByQueen(sq
, colors
) {
313 return this.isAttackedBySlideNJump(
317 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
321 isAttackedByKing(sq
, colors
) {
322 return this.isAttackedBySlideNJump(
326 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
332 return this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"]);
336 const color
= this.turn
;
337 // Artifically change turn, for checkered pawns
338 this.turn
= V
.GetOppCol(color
);
344 let res
= kingAttacked
345 ? [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))]
352 super.postPlay(move);
353 // Does this move turn off a 2-squares pawn flag?
355 [1, 6].includes(move.start
.x
) &&
356 move.vanish
[0].p
== V
.PAWN
&&
357 Math
.abs(move.end
.x
- move.start
.x
) == 2
359 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
361 this.cmoves
.push(this.getCmove(move));
365 super.postUndo(move);
370 if (this.atLeastOneMove()) return "*";
371 const color
= this.turn
;
372 // Artifically change turn, for checkered pawns
373 this.turn
= V
.GetOppCol(this.turn
);
374 const res
= this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"])
379 this.turn
= V
.GetOppCol(this.turn
);
385 // Just count material for now, considering checkered neutral (...)
386 for (let i
= 0; i
< V
.size
.x
; i
++) {
387 for (let j
= 0; j
< V
.size
.y
; j
++) {
388 if (this.board
[i
][j
] != V
.EMPTY
) {
389 const sqColor
= this.getColor(i
, j
);
390 if (["w","b"].includes(sqColor
)) {
391 const sign
= sqColor
== "w" ? 1 : -1;
392 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
400 static GenRandInitFen(randomness
) {
401 // Add 16 pawns flags + empty cmove:
402 return ChessRules
.GenRandInitFen(randomness
)
403 .slice(0, -2) + "1111111111111111 - -";
406 static ParseFen(fen
) {
407 return Object
.assign(
408 ChessRules
.ParseFen(fen
),
409 { cmove: fen
.split(" ")[5] }
414 const L
= this.cmoves
.length
;
418 : ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].start
) +
419 ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].end
)
424 return super.getFen() + " " + this.getCmoveFen();
428 return super.getFenForRepeat() + "_" + this.getCmoveFen();
432 let fen
= super.getFlagsFen();
434 for (let c
of ["w", "b"])
435 for (let i
= 0; i
< 8; i
++) fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
439 static get SEARCH_DEPTH() {
444 if (move.appear
.length
== 2) {
446 if (move.end
.y
< move.start
.y
) return "0-0-0";
450 const finalSquare
= V
.CoordsToSquare(move.end
);
451 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
453 if (piece
== V
.PAWN
) {
455 if (move.vanish
.length
> 1) {
457 const startColumn
= V
.CoordToColumn(move.start
.y
);
458 notation
= startColumn
+ "x" + finalSquare
;
459 } else notation
= finalSquare
;
463 piece
.toUpperCase() +
464 (move.vanish
.length
> 1 ? "x" : "") +
467 if (move.appear
[0].p
!= move.vanish
[0].p
)
468 notation
+= "=" + move.appear
[0].p
.toUpperCase();