1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
, sample
} from "@/utils/alea";
5 export class ShakoRules
extends ChessRules
{
6 static get PawnSpecs() {
11 initShift: { w: 2, b: 2 },
13 ChessRules
.PawnSpecs
.promotions
.concat([V
.ELEPHANT
, V
.CANNON
])
18 static get ELEPHANT() {
27 return ChessRules
.PIECES
.concat([V
.ELEPHANT
, V
.CANNON
]);
31 const prefix
= [V
.ELEPHANT
, V
.CANNON
].includes(b
[1]) ? "Shako/" : "";
55 return { x: 10, y: 10};
58 getPotentialMovesFrom([x
, y
]) {
59 switch (this.getPiece(x
, y
)) {
61 return this.getPotentialElephantMoves([x
, y
]);
63 return this.getPotentialCannonMoves([x
, y
]);
65 return super.getPotentialMovesFrom([x
, y
]);
69 getPotentialElephantMoves([x
, y
]) {
70 return this.getSlideNJumpMoves([x
, y
], V
.steps
[V
.ELEPHANT
], "oneStep");
73 getPotentialCannonMoves([x
, y
]) {
74 const oppCol
= V
.GetOppCol(this.turn
);
76 // Look in every direction until an obstacle (to jump) is met
77 for (const step
of V
.steps
[V
.ROOK
]) {
80 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
81 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
85 // Then, search for an enemy
88 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
92 if (V
.OnBoard(i
, j
) && this.getColor(i
, j
) == oppCol
)
93 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
98 getCastleMoves([x
, y
]) {
99 const c
= this.getColor(x
, y
);
100 if (x
!= (c
== "w" ? V
.size
.x
- 2 : 1) || y
!= this.INIT_COL_KING
[c
])
101 return []; //x isn't second rank, or king has moved (shortcut)
104 const oppCol
= V
.GetOppCol(c
);
108 const finalSquares
= [
115 castleSide
++ //large, then small
117 if (this.castleFlags
[c
][castleSide
] >= V
.size
.y
) continue;
118 // If this code is reached, rook and king are on initial position
120 const rookPos
= this.castleFlags
[c
][castleSide
];
122 // Nothing on the path of the king ? (and no checks)
123 const castlingPiece
= this.getPiece(x
, rookPos
);
124 const finDist
= finalSquares
[castleSide
][0] - y
;
125 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
129 this.isAttacked([x
, i
], oppCol
) ||
130 (this.board
[x
][i
] != V
.EMPTY
&&
131 // NOTE: next check is enough, because of chessboard constraints
132 (this.getColor(x
, i
) != c
||
133 ![V
.KING
, castlingPiece
].includes(this.getPiece(x
, i
))))
135 continue castlingCheck
;
138 } while (i
!= finalSquares
[castleSide
][0]);
140 // Nothing on the path to the rook?
141 step
= castleSide
== 0 ? -1 : 1;
142 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
143 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
146 // Nothing on final squares, except maybe king and castling rook?
147 for (i
= 0; i
< 2; i
++) {
149 finalSquares
[castleSide
][i
] != rookPos
&&
150 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
152 this.getPiece(x
, finalSquares
[castleSide
][i
]) != V
.KING
||
153 this.getColor(x
, finalSquares
[castleSide
][i
]) != c
156 continue castlingCheck
;
160 // If this code is reached, castle is valid
166 y: finalSquares
[castleSide
][0],
172 y: finalSquares
[castleSide
][1],
178 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
179 new PiPo({ x: x
, y: rookPos
, p: castlingPiece
, c: c
})
182 Math
.abs(y
- rookPos
) <= 2
183 ? { x: x
, y: rookPos
}
184 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
192 isAttacked(sq
, color
) {
194 super.isAttacked(sq
, color
) ||
195 this.isAttackedByElephant(sq
, color
) ||
196 this.isAttackedByCannon(sq
, color
)
200 isAttackedByElephant(sq
, color
) {
202 this.isAttackedBySlideNJump(
203 sq
, color
, V
.ELEPHANT
, V
.steps
[V
.ELEPHANT
], "oneStep"
208 isAttackedByCannon([x
, y
], color
) {
209 // Reversed process: is there an obstacle in line,
210 // and a cannon next in the same line?
211 for (const step
of V
.steps
[V
.ROOK
]) {
212 let [i
, j
] = [x
+step
[0], y
+step
[1]];
213 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
217 if (V
.OnBoard(i
, j
)) {
218 // Keep looking in this direction
221 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
227 this.getPiece(i
, j
) == V
.CANNON
&&
228 this.getColor(i
, j
) == color
237 updateCastleFlags(move, piece
) {
238 const c
= V
.GetOppCol(this.turn
);
239 const firstRank
= (c
== "w" ? V
.size
.x
- 2 : 1);
240 // Update castling flags if rooks are moved
241 const oppCol
= this.turn
;
242 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
244 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
246 move.start
.x
== firstRank
&& //our rook moves?
247 this.castleFlags
[c
].includes(move.start
.y
)
249 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
250 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
252 // NOTE: not "else if" because a rook could take an opposing rook
254 move.end
.x
== oppFirstRank
&& //we took opponent rook?
255 this.castleFlags
[oppCol
].includes(move.end
.y
)
257 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
258 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
262 static get VALUES() {
263 return Object
.assign(
269 static get SEARCH_DEPTH() {
273 static GenRandInitFen(randomness
) {
274 if (randomness
== 0) {
276 "c8c/ernbqkbnre/pppppppppp/91/91/91/91/PPPPPPPPPP/ERNBQKBNRE/C8C " +
281 let pieces
= { w: new Array(10), b: new Array(10) };
283 // Shuffle pieces on second (and before-last rank if randomness == 2)
284 for (let c
of ["w", "b"]) {
285 if (c
== 'b' && randomness
== 1) {
286 pieces
['b'] = pieces
['w'];
291 let positions
= ArrayFun
.range(10);
293 // Get random squares for bishops + elephants
294 const be1Pos
= sample([0, 2, 4, 6, 8], 2);
295 const be2Pos
= sample([1, 3, 5, 7, 9], 2);
296 const bishop1Pos
= be1Pos
[0];
297 const bishop2Pos
= be2Pos
[0];
298 const elephant1Pos
= be1Pos
[1];
299 const elephant2Pos
= be2Pos
[1];
300 // Remove chosen squares
301 (be1Pos
.concat(be2Pos
)).sort((x
, y
) => y
- x
).forEach(pos
=> {
302 positions
.splice(pos
, 1);
305 let randIndex
= randInt(6);
306 const knight1Pos
= positions
[randIndex
];
307 positions
.splice(randIndex
, 1);
308 randIndex
= randInt(5);
309 const knight2Pos
= positions
[randIndex
];
310 positions
.splice(randIndex
, 1);
312 randIndex
= randInt(4);
313 const queenPos
= positions
[randIndex
];
314 positions
.splice(randIndex
, 1);
316 const rook1Pos
= positions
[0];
317 const kingPos
= positions
[1];
318 const rook2Pos
= positions
[2];
320 pieces
[c
][elephant1Pos
] = "e";
321 pieces
[c
][rook1Pos
] = "r";
322 pieces
[c
][knight1Pos
] = "n";
323 pieces
[c
][bishop1Pos
] = "b";
324 pieces
[c
][queenPos
] = "q";
325 pieces
[c
][kingPos
] = "k";
326 pieces
[c
][bishop2Pos
] = "b";
327 pieces
[c
][knight2Pos
] = "n";
328 pieces
[c
][rook2Pos
] = "r";
329 pieces
[c
][elephant2Pos
] = "e";
330 flags
+= V
.CoordToColumn(rook1Pos
) + V
.CoordToColumn(rook2Pos
);
332 // Add turn + flags + enpassant
334 "c8c/" + pieces
["b"].join("") +
335 "/pppppppppp/91/91/91/91/PPPPPPPPPP/" +
336 pieces
["w"].join("").toUpperCase() + "/C8C" +
337 " w 0 " + flags
+ " -"