1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { sample
, randInt
} from "@/utils/alea";
5 export class WildebeestRules
extends ChessRules
{
8 return { x: 10, y: 11 };
14 static get WILDEBEEST() {
19 return ChessRules
.PIECES
.concat([V
.CAMEL
, V
.WILDEBEEST
]);
42 static IsGoodEnpassant(enpassant
) {
43 if (enpassant
!= "-") return !!enpassant
.match(/^([a-j][0-9]{1,2},?)+$/);
48 return ([V
.CAMEL
, V
.WILDEBEEST
].includes(b
[1]) ? "Wildebeest/" : "") + b
;
51 // There may be 2 enPassant squares (if pawn jump 3 squares)
53 const L
= this.epSquares
.length
;
54 if (!this.epSquares
[L
- 1]) return "-"; //no en-passant
56 this.epSquares
[L
- 1].forEach(sq
=> {
57 res
+= V
.CoordsToSquare(sq
) + ",";
59 return res
.slice(0, -1); //remove last comma
62 // En-passant after 2-sq or 3-sq jumps
63 getEpSquare(moveOrSquare
) {
64 if (!moveOrSquare
) return undefined;
65 if (typeof moveOrSquare
=== "string") {
66 const square
= moveOrSquare
;
67 if (square
== "-") return undefined;
69 square
.split(",").forEach(sq
=> {
70 res
.push(V
.SquareToCoords(sq
));
74 // Argument is a move:
75 const move = moveOrSquare
;
76 const [sx
, sy
, ex
] = [move.start
.x
, move.start
.y
, move.end
.x
];
77 if (this.getPiece(sx
, sy
) == V
.PAWN
&& Math
.abs(sx
- ex
) >= 2) {
78 const step
= (ex
- sx
) / Math
.abs(ex
- sx
);
85 if (sx
+ 2 * step
!= ex
) {
94 return undefined; //default
97 getPotentialMovesFrom([x
, y
]) {
98 switch (this.getPiece(x
, y
)) {
100 return this.getPotentialCamelMoves([x
, y
]);
102 return this.getPotentialWildebeestMoves([x
, y
]);
104 return super.getPotentialMovesFrom([x
, y
]);
108 // Pawns jump 2 or 3 squares, and promote to queen or wildebeest
109 getPotentialPawnMoves([x
, y
]) {
110 const color
= this.turn
;
112 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
113 const shiftX
= color
== "w" ? -1 : 1;
114 const startRanks
= color
== "w" ? [sizeX
- 2, sizeX
- 3] : [1, 2];
115 const lastRanks
= color
== "w" ? [0, 1] : [sizeX
- 1, sizeX
-2];
116 let finalPieces
= [V
.PAWN
];
117 if (x
+ shiftX
== lastRanks
[1])
118 Array
.prototype.push
.apply(finalPieces
, [V
.WILDEBEEST
, V
.QUEEN
]);
119 else if (x
+ shiftX
== lastRanks
[0])
120 finalPieces
= [V
.WILDEBEEST
, V
.QUEEN
];
122 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
) {
123 // One square forward
124 for (let piece
of finalPieces
)
126 this.getBasicMove([x
, y
], [x
+ shiftX
, y
], { c: color
, p: piece
})
128 if (startRanks
.includes(x
)) {
129 if (this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
) {
131 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
132 if (x
== startRanks
[0] && this.board
[x
+ 3 * shiftX
][y
] == V
.EMPTY
) {
133 // Three squares jump
134 moves
.push(this.getBasicMove([x
, y
], [x
+ 3 * shiftX
, y
]));
140 for (let shiftY
of [-1, 1]) {
143 y
+ shiftY
< sizeY
&&
144 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
145 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
147 for (let piece
of finalPieces
) {
149 this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
], {
159 const Lep
= this.epSquares
.length
;
160 const epSquare
= this.epSquares
[Lep
- 1];
162 for (let epsq
of epSquare
) {
163 // TODO: some redundant checks
164 if (epsq
.x
== x
+ shiftX
&& Math
.abs(epsq
.y
- y
) == 1) {
165 let enpassantMove
= this.getBasicMove([x
, y
], [epsq
.x
, epsq
.y
]);
166 // WARNING: the captured pawn may be diagonally behind us,
167 // if it's a 3-squares jump and we take on 1st passing square
168 const px
= this.board
[x
][epsq
.y
] != V
.EMPTY
? x : x
- shiftX
;
169 enpassantMove
.vanish
.push({
173 c: this.getColor(px
, epsq
.y
)
175 moves
.push(enpassantMove
);
183 getPotentialCamelMoves(sq
) {
184 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.CAMEL
], 1);
187 getPotentialWildebeestMoves(sq
) {
188 return this.getSlideNJumpMoves(
189 sq
, V
.steps
[V
.KNIGHT
].concat(V
.steps
[V
.CAMEL
]), 1);
194 m
.appear
.length
== 2 && m
.vanish
.length
== 2 &&
195 Math
.abs(m
.end
.y
- m
.start
.y
) == 1 &&
196 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
198 // Castle, king moved by one square only, not directly onto rook
199 return "Wildebeest/castle";
201 return super.getPPpath(m
);
204 // Special Wildebeest castling rules:
205 getCastleMoves([x
, y
]) {
206 const c
= this.getColor(x
, y
);
207 const oppCol
= V
.GetOppCol(c
);
210 const castlingKing
= this.board
[x
][y
].charAt(1);
214 castleSide
++ //"large", then "small"
216 if (this.castleFlags
[c
][castleSide
] >= V
.size
.y
) continue;
217 // Rook and king are on initial position
218 const rookPos
= this.castleFlags
[c
][castleSide
];
219 const range
= (castleSide
== 0 ? [rookPos
, y
] : [y
, rookPos
]);
221 // King and rook must be connected:
222 for (let i
= range
[0] + 1; i
<= range
[1] - 1; i
++) {
223 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
225 const step
= 2 * castleSide
- 1;
226 // No attacks on the path of the king ?
227 for (let i
= range
[0]; i
<= range
[1]; i
++) {
228 if (i
!= rookPos
&& this.isAttacked([x
, i
], oppCol
))
229 continue castlingCheck
;
232 // Do not end in the corner, except if starting square is too near
234 (i
< V
.size
.y
- 1 || y
== V
.size
.y
- 2)
236 // Found a possible castle move:
254 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
255 new PiPo({ x: x
, y: rookPos
, p: V
.ROOK
, c: c
})
266 isAttacked(sq
, color
) {
268 super.isAttacked(sq
, color
) ||
269 this.isAttackedByCamel(sq
, color
) ||
270 this.isAttackedByWildebeest(sq
, color
)
274 isAttackedByCamel(sq
, color
) {
275 return this.isAttackedBySlideNJump(
276 sq
, color
, V
.CAMEL
, V
.steps
[V
.CAMEL
], 1);
279 isAttackedByWildebeest(sq
, color
) {
280 return this.isAttackedBySlideNJump(
281 sq
, color
, V
.WILDEBEEST
, V
.steps
[V
.KNIGHT
].concat(V
.steps
[V
.CAMEL
]), 1);
285 if (this.atLeastOneMove()) return "*";
286 // No valid move: game is lost (stalemate is a win)
287 return this.turn
== "w" ? "0-1" : "1-0";
290 static get VALUES() {
291 return Object
.assign(
292 { c: 3, w: 7 }, //experimental
297 static get SEARCH_DEPTH() {
301 static GenRandInitFen(options
) {
302 if (options
.randomness
== 0) {
304 "rnccwkqbbnr/ppppppppppp/92/92/92/92/92/92/PPPPPPPPPPP/RNBBQKWCCNR " +
309 let pieces
= { w: new Array(11), b: new Array(11) };
311 for (let c
of ["w", "b"]) {
312 if (c
== 'b' && options
.randomness
== 1) {
313 pieces
['b'] = pieces
['w'];
318 let positions
= ArrayFun
.range(11);
320 // Get random squares for bishops + camels (different colors)
321 let randIndexes
= sample(ArrayFun
.range(6), 2).map(i
=> {
324 let bishop1Pos
= positions
[randIndexes
[0]];
325 let camel1Pos
= positions
[randIndexes
[1]];
326 // The second bishop (camel) must be on a square of different color
327 let randIndexes_tmp
= sample(ArrayFun
.range(5), 2).map(i
=> {
330 let bishop2Pos
= positions
[randIndexes_tmp
[0]];
331 let camel2Pos
= positions
[randIndexes_tmp
[1]];
332 for (let idx
of randIndexes
.concat(randIndexes_tmp
).sort((a
, b
) => {
335 // Largest indices first
336 positions
.splice(idx
, 1);
339 let randIndex
= randInt(7);
340 let knight1Pos
= positions
[randIndex
];
341 positions
.splice(randIndex
, 1);
342 randIndex
= randInt(6);
343 let knight2Pos
= positions
[randIndex
];
344 positions
.splice(randIndex
, 1);
346 randIndex
= randInt(5);
347 let queenPos
= positions
[randIndex
];
348 positions
.splice(randIndex
, 1);
350 // Random square for wildebeest
351 randIndex
= randInt(4);
352 let wildebeestPos
= positions
[randIndex
];
353 positions
.splice(randIndex
, 1);
355 let rook1Pos
= positions
[0];
356 let kingPos
= positions
[1];
357 let rook2Pos
= positions
[2];
359 pieces
[c
][rook1Pos
] = "r";
360 pieces
[c
][knight1Pos
] = "n";
361 pieces
[c
][bishop1Pos
] = "b";
362 pieces
[c
][queenPos
] = "q";
363 pieces
[c
][camel1Pos
] = "c";
364 pieces
[c
][camel2Pos
] = "c";
365 pieces
[c
][wildebeestPos
] = "w";
366 pieces
[c
][kingPos
] = "k";
367 pieces
[c
][bishop2Pos
] = "b";
368 pieces
[c
][knight2Pos
] = "n";
369 pieces
[c
][rook2Pos
] = "r";
370 flags
+= V
.CoordToColumn(rook1Pos
) + V
.CoordToColumn(rook2Pos
);
373 pieces
["b"].join("") +
374 "/ppppppppppp/92/92/92/92/92/92/PPPPPPPPPPP/" +
375 pieces
["w"].join("").toUpperCase() +
376 " w 0 " + flags
+ " -"