1 import { ChessRules
} from "@/base_rules";
3 export const VariantRules
= class CheckeredRules
extends ChessRules
{
5 const checkered_codes
= {
12 if (b
[0] == "c") return checkered_codes
[b
[1]];
13 return ChessRules
.board2fen(b
);
17 // Tolerate upper-case versions of checkered pieces (why not?)
18 const checkered_pieces
= {
30 if (Object
.keys(checkered_pieces
).includes(f
))
31 return "c" + checkered_pieces
[f
];
32 return ChessRules
.fen2board(f
);
36 return ChessRules
.PIECES
.concat(["s", "t", "u", "c", "o"]);
40 return (b
[0] == "c" ? "Checkered/" : "") + b
;
43 setOtherVariables(fen
) {
44 super.setOtherVariables(fen
);
45 // Local stack of non-capturing checkered moves:
47 const cmove
= fen
.split(" ")[5];
48 if (cmove
== "-") this.cmoves
.push(null);
51 start: ChessRules
.SquareToCoords(cmove
.substr(0, 2)),
52 end: ChessRules
.SquareToCoords(cmove
.substr(2))
57 static IsGoodFen(fen
) {
58 if (!ChessRules
.IsGoodFen(fen
)) return false;
59 const fenParts
= fen
.split(" ");
60 if (fenParts
.length
!= 6) return false;
61 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
66 static IsGoodFlags(flags
) {
67 // 4 for castle + 16 for pawns
68 return !!flags
.match(/^[01]{20,20}$/);
72 super.setFlags(fenflags
); //castleFlags
74 w: [...Array(8).fill(true)], //pawns can move 2 squares?
75 b: [...Array(8).fill(true)]
77 const flags
= fenflags
.substr(4); //skip first 4 digits, for castle
78 for (let c
of ["w", "b"]) {
79 for (let i
= 0; i
< 8; i
++)
80 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
85 return [this.castleFlags
, this.pawnFlags
];
88 disaggregateFlags(flags
) {
89 this.castleFlags
= flags
[0];
90 this.pawnFlags
= flags
[1];
94 if (move.appear
[0].c
== "c" && move.vanish
.length
== 1)
95 return { start: move.start
, end: move.end
};
99 canTake([x1
, y1
], [x2
, y2
]) {
100 const color1
= this.getColor(x1
, y1
);
101 const color2
= this.getColor(x2
, y2
);
102 // Checkered aren't captured
106 (color1
!= "c" || color2
!= this.turn
)
110 // Post-processing: apply "checkerization" of standard moves
111 getPotentialMovesFrom([x
, y
]) {
112 let standardMoves
= super.getPotentialMovesFrom([x
, y
]);
113 const lastRank
= this.turn
== "w" ? 0 : 7;
114 // King has to be treated differently (for castles)
115 if (this.getPiece(x
, y
) == V
.KING
) return standardMoves
;
117 standardMoves
.forEach(m
=> {
118 if (m
.vanish
[0].p
== V
.PAWN
) {
120 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
121 !this.pawnFlags
[this.turn
][m
.start
.y
]
123 return; //skip forbidden 2-squares jumps
125 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
126 m
.vanish
.length
== 2 &&
127 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
129 return; //checkered pawns cannot take en-passant
132 if (m
.vanish
.length
== 1) moves
.push(m
);
135 // A capture occured (m.vanish.length == 2)
139 m
.appear
[0].p
!= m
.vanish
[1].p
&& //avoid promotions (already treated):
140 (m
.vanish
[0].p
!= V
.PAWN
|| m
.end
.x
!= lastRank
)
142 // Add transformation into captured piece
143 let m2
= JSON
.parse(JSON
.stringify(m
));
144 m2
.appear
[0].p
= m
.vanish
[1].p
;
152 canIplay(side
, [x
, y
]) {
153 return side
== this.turn
&& [side
, "c"].includes(this.getColor(x
, y
));
156 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
157 oppositeMoves(m1
, m2
) {
160 m2
.appear
[0].c
== "c" &&
161 m2
.appear
.length
== 1 &&
162 m2
.vanish
.length
== 1 &&
163 m1
.start
.x
== m2
.end
.x
&&
164 m1
.end
.x
== m2
.start
.x
&&
165 m1
.start
.y
== m2
.end
.y
&&
166 m1
.end
.y
== m2
.start
.y
171 if (moves
.length
== 0) return [];
172 const color
= this.turn
;
173 const L
= this.cmoves
.length
; //at least 1: init from FEN
174 return moves
.filter(m
=> {
175 if (this.oppositeMoves(this.cmoves
[L
- 1], m
)) return false;
177 const res
= !this.underCheck(color
);
184 const oppCol
= V
.GetOppCol(this.turn
);
185 let potentialMoves
= [];
186 for (let i
= 0; i
< V
.size
.x
; i
++) {
187 for (let j
= 0; j
< V
.size
.y
; j
++) {
188 // NOTE: just testing == color isn't enough because of checkred pieces
189 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) != oppCol
) {
190 Array
.prototype.push
.apply(
192 this.getPotentialMovesFrom([i
, j
])
197 return this.filterValid(potentialMoves
);
201 const oppCol
= V
.GetOppCol(this.turn
);
202 for (let i
= 0; i
< V
.size
.x
; i
++) {
203 for (let j
= 0; j
< V
.size
.y
; j
++) {
204 // NOTE: just testing == color isn't enough because of checkred pieces
205 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) != oppCol
) {
206 const moves
= this.getPotentialMovesFrom([i
, j
]);
207 if (moves
.length
> 0) {
208 for (let k
= 0; k
< moves
.length
; k
++) {
209 if (this.filterValid([moves
[k
]]).length
> 0) return true;
218 isAttackedByPawn([x
, y
], colors
) {
219 for (let c
of colors
) {
220 const color
= c
== "c" ? this.turn : c
;
221 let pawnShift
= color
== "w" ? 1 : -1;
222 if (x
+ pawnShift
>= 0 && x
+ pawnShift
< 8) {
223 for (let i
of [-1, 1]) {
227 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
228 this.getColor(x
+ pawnShift
, y
+ i
) == c
239 return this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"]);
242 getCheckSquares(color
) {
243 // Artifically change turn, for checkered pawns
244 this.turn
= V
.GetOppCol(color
);
245 const kingAttacked
= this.isAttacked(this.kingPos
[color
], [
249 let res
= kingAttacked
250 ? [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))] //need to duplicate!
256 updateVariables(move) {
257 super.updateVariables(move);
258 // Does this move turn off a 2-squares pawn flag?
259 const secondRank
= [1, 6];
260 if (secondRank
.includes(move.start
.x
) && move.vanish
[0].p
== V
.PAWN
)
261 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
265 if (this.atLeastOneMove())
269 const color
= this.turn
;
270 // Artifically change turn, for checkered pawns
271 this.turn
= V
.GetOppCol(this.turn
);
272 const res
= this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"])
277 this.turn
= V
.GetOppCol(this.turn
);
283 // Just count material for now, considering checkered neutral (...)
284 for (let i
= 0; i
< V
.size
.x
; i
++) {
285 for (let j
= 0; j
< V
.size
.y
; j
++) {
286 if (this.board
[i
][j
] != V
.EMPTY
) {
287 const sqColor
= this.getColor(i
, j
);
288 if (["w","b"].includes(sqColor
)) {
289 const sign
= sqColor
== "w" ? 1 : -1;
290 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
298 static GenRandInitFen(randomness
) {
299 return ChessRules
.GenRandInitFen(randomness
)
300 // Add 16 pawns flags + empty cmove:
301 .replace(" w 0 1111", " w 0 11111111111111111111 -");
304 static ParseFen(fen
) {
305 return Object
.assign({}, ChessRules
.ParseFen(fen
), {
306 cmove: fen
.split(" ")[5]
311 const L
= this.cmoves
.length
;
312 const cmoveFen
= !this.cmoves
[L
- 1]
314 : ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].start
) +
315 ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].end
);
316 return super.getFen() + " " + cmoveFen
;
320 let fen
= super.getFlagsFen();
322 for (let c
of ["w", "b"]) {
323 for (let i
= 0; i
< 8; i
++) fen
+= this.pawnFlags
[c
][i
] ? "1" : "0";
328 // TODO (design): this cmove update here or in (un)updateVariables ?
330 this.cmoves
.push(this.getCmove(move));
340 if (move.appear
.length
== 2) {
342 if (move.end
.y
< move.start
.y
) return "0-0-0";
346 // Translate final square
347 const finalSquare
= V
.CoordsToSquare(move.end
);
349 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
350 if (piece
== V
.PAWN
) {
353 if (move.vanish
.length
> 1) {
355 const startColumn
= V
.CoordToColumn(move.start
.y
);
361 move.appear
[0].p
.toUpperCase();
364 notation
= finalSquare
;
365 if (move.appear
.length
> 0 && piece
!= move.appear
[0].p
)
367 notation
+= "=" + move.appear
[0].p
.toUpperCase();
373 piece
.toUpperCase() +
374 (move.vanish
.length
> 1 ? "x" : "") +
376 (move.vanish
.length
> 1 ? "=" + move.appear
[0].p
.toUpperCase() : "")