1 import { ChessRules
} from "@/base_rules";
3 export class XiangqiRules
extends ChessRules
{
5 static get Monochrome() {
9 static get Notoodark() {
15 // Draw all inter-squares lines, shifted:
16 for (let i
= 0; i
< V
.size
.x
; i
++)
17 lines
.push([[i
+0.5, 0.5], [i
+0.5, V
.size
.y
-0.5]]);
18 for (let j
= 0; j
< V
.size
.y
; j
++)
19 lines
.push([[0.5, j
+0.5], [V
.size
.x
-0.5, j
+0.5]]);
21 lines
.push([[0.5, 3.5], [2.5, 5.5]]);
22 lines
.push([[0.5, 5.5], [2.5, 3.5]]);
23 lines
.push([[9.5, 3.5], [7.5, 5.5]]);
24 lines
.push([[9.5, 5.5], [7.5, 3.5]]);
26 lines
.push([[4.5, 0.5], [5.5, 8.5]]);
27 lines
.push([[5.5, 0.5], [4.5, 8.5]]);
31 static get HasFlags() {
35 static get HasEnpassant() {
39 static get LoseOnRepetition() {
43 static get ELEPHANT() {
51 static get ADVISOR() {
56 return [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.ELEPHANT
, V
.ADVISOR
, V
.KING
, V
.CANNON
];
60 return "Xiangqi/" + b
;
64 return { x: 10, y: 9};
67 getPotentialMovesFrom(sq
) {
69 const piece
= this.getPiece(sq
[0], sq
[1]);
72 moves
= this.getPotentialPawnMoves(sq
);
75 moves
= super.getPotentialRookMoves(sq
);
78 moves
= this.getPotentialKnightMoves(sq
);
81 moves
= this.getPotentialElephantMoves(sq
);
84 moves
= this.getPotentialAdvisorMoves(sq
);
87 moves
= this.getPotentialKingMoves(sq
);
90 moves
= this.getPotentialCannonMoves(sq
);
93 if (piece
!= V
.KING
&& this.kingPos
['w'][1] != this.kingPos
['b'][1])
95 if (this.kingPos
['w'][1] == this.kingPos
['b'][1]) {
96 const colKing
= this.kingPos
['w'][1];
97 let intercept
= 0; //count intercepting pieces
98 for (let i
= this.kingPos
['b'][0] + 1; i
< this.kingPos
['w'][0]; i
++) {
99 if (this.board
[i
][colKing
] != V
.EMPTY
) intercept
++;
101 if (intercept
>= 2) return moves
;
102 // intercept == 1 (0 is impossible):
103 // Any move not removing intercept is OK
104 return moves
.filter(m
=> {
106 // From another column?
107 m
.start
.y
!= colKing
||
108 // From behind a king? (including kings themselves!)
109 m
.start
.x
<= this.kingPos
['b'][0] ||
110 m
.start
.x
>= this.kingPos
['w'][0] ||
111 // Intercept piece moving: must remain in-between
113 m
.end
.y
== colKing
&&
114 m
.end
.x
> this.kingPos
['b'][0] &&
115 m
.end
.x
< this.kingPos
['w'][0]
120 // piece == king: check only if move.end.y == enemy king column
121 const color
= this.getColor(sq
[0], sq
[1]);
122 const oppCol
= V
.GetOppCol(color
);
123 // colCheck == -1 if unchecked, 1 if checked and occupied,
124 // 0 if checked and clear
126 return moves
.filter(m
=> {
127 if (m
.end
.y
!= this.kingPos
[oppCol
][1]) return true;
131 for (let i
= this.kingPos
['b'][0] + 1; i
< this.kingPos
['w'][0]; i
++) {
132 if (this.board
[i
][m
.end
.y
] != V
.EMPTY
) {
137 return colCheck
== 1;
139 // Check already done:
140 return colCheck
== 1;
144 getPotentialPawnMoves([x
, y
]) {
145 const c
= this.getColor(x
, y
);
146 const shiftX
= (c
== 'w' ? -1 : 1);
147 const crossedRiver
= (c
== 'w' && x
<= 4 || c
== 'b' && x
>= 5);
148 const lastRank
= (c
== 'w' && x
== 0 || c
== 'b' && x
== 9);
150 if (!lastRank
) steps
.push([shiftX
, 0]);
152 if (y
> 0) steps
.push([0, -1]);
153 if (y
< 9) steps
.push([0, 1]);
155 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep");
158 knightStepsFromRookStep(step
) {
159 if (step
[0] == 0) return [ [1, 2*step
[1]], [-1, 2*step
[1]] ];
160 return [ [2*step
[0], 1], [2*step
[0], -1] ];
163 getPotentialKnightMoves([x
, y
]) {
165 for (let rookStep
of ChessRules
.steps
[V
.ROOK
]) {
166 const [i
, j
] = [x
+ rookStep
[0], y
+ rookStep
[1]];
167 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
168 Array
.prototype.push
.apply(steps
,
169 // These moves might be impossible, but need to be checked:
170 this.knightStepsFromRookStep(rookStep
));
173 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep");
176 getPotentialElephantMoves([x
, y
]) {
178 const c
= this.getColor(x
, y
);
179 for (let bishopStep
of ChessRules
.steps
[V
.BISHOP
]) {
180 const [i
, j
] = [x
+ bishopStep
[0], y
+ bishopStep
[1]];
181 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
182 const [newX
, newY
] = [x
+ 2*bishopStep
[0], y
+ 2*bishopStep
[1]];
183 if ((c
== 'w' && newX
>= 5) || (c
== 'b' && newX
<= 4))
184 // A priori valid (elephant don't cross the river)
185 steps
.push(bishopStep
.map(s
=> 2*s
));
186 // "out of board" checks delayed to next method
189 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep");
192 insidePalace(x
, y
, c
) {
194 (y
>= 3 && y
<= 5) &&
196 (c
== 'w' && x
>= 7) ||
202 getPotentialAdvisorMoves([x
, y
]) {
203 // Diagonal steps inside palace
205 const c
= this.getColor(x
, y
);
206 for (let s
of ChessRules
.steps
[V
.BISHOP
]) {
207 if (this.insidePalace(x
+ s
[0], y
+ s
[1], c
)) steps
.push(s
);
209 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep");
212 getPotentialKingMoves([x
, y
]) {
213 // Orthogonal steps inside palace
215 const c
= this.getColor(x
, y
);
216 for (let s
of ChessRules
.steps
[V
.ROOK
]) {
217 if (this.insidePalace(x
+ s
[0], y
+ s
[1], c
)) steps
.push(s
);
219 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep");
222 // NOTE: duplicated from Shako (TODO?)
223 getPotentialCannonMoves([x
, y
]) {
224 const oppCol
= V
.GetOppCol(this.turn
);
226 // Look in every direction until an obstacle (to jump) is met
227 for (const step
of V
.steps
[V
.ROOK
]) {
230 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
231 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
235 // Then, search for an enemy
238 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
242 if (V
.OnBoard(i
, j
) && this.getColor(i
, j
) == oppCol
)
243 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
248 // (King) Never attacked by advisor, since it stays in the palace
249 // Also, never attacked by elephants since they don't cross the river.
250 isAttacked(sq
, color
) {
252 this.isAttackedByPawn(sq
, color
) ||
253 super.isAttackedByRook(sq
, color
) ||
254 this.isAttackedByKnight(sq
, color
) ||
255 this.isAttackedByCannon(sq
, color
)
259 isAttackedByPawn([x
, y
], color
) {
260 // The pawn necessarily crossed the river (attack on king)
261 const shiftX
= (color
== 'w' ? 1 : -1); //shift from king
262 return super.isAttackedBySlideNJump(
263 [x
, y
], color
, V
.PAWN
, [[shiftX
, 0], [0, 1], [0, -1]], "oneStep");
266 knightStepsFromBishopStep(step
) {
267 return [ [2*step
[0], step
[1]], [step
[0], 2*step
[1]] ];
270 isAttackedByKnight([x
, y
], color
) {
271 // Check bishop steps: if empty, look continuation knight step
273 for (let s
of ChessRules
.steps
[V
.BISHOP
]) {
274 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
277 this.board
[i
][j
] == V
.EMPTY
279 Array
.prototype.push
.apply(steps
, this.knightStepsFromBishopStep(s
));
283 super.isAttackedBySlideNJump([x
, y
], color
, V
.KNIGHT
, steps
, "oneStep")
287 // NOTE: duplicated from Shako (TODO?)
288 isAttackedByCannon([x
, y
], color
) {
289 // Reversed process: is there an obstacle in line,
290 // and a cannon next in the same line?
291 for (const step
of V
.steps
[V
.ROOK
]) {
292 let [i
, j
] = [x
+step
[0], y
+step
[1]];
293 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
297 if (V
.OnBoard(i
, j
)) {
298 // Keep looking in this direction
301 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
307 this.getPiece(i
, j
) == V
.CANNON
&&
308 this.getColor(i
, j
) == color
318 if (this.atLeastOneMove()) return "*";
320 const color
= this.turn
;
321 // No valid move: I lose!
322 return (color
== "w" ? "0-1" : "1-0");
325 static get VALUES() {
339 for (let i
= 0; i
< V
.size
.x
; i
++) {
340 for (let j
= 0; j
< V
.size
.y
; j
++) {
341 if (this.board
[i
][j
] != V
.EMPTY
) {
342 const c
= this.getColor(i
, j
);
343 const sign
= (c
== 'w' ? 1 : -1);
344 const piece
= this.getPiece(i
, j
);
345 let pieceEval
= V
.VALUES
[this.getPiece(i
, j
)];
349 (c
== 'w' && i
<= 4) ||
353 // Pawn crossed the river: higher value
356 evaluation
+= sign
* pieceEval
;
363 static get SEARCH_DEPTH() {
367 static GenRandInitFen() {
368 // No randomization here (TODO?)
369 return "rneakaenr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNEAKAENR w 0";