1 import { ChessRules
} from "@/base_rules";
3 export class XiangqiRules
extends ChessRules
{
5 // NOTE (TODO?) scanKings() could be more efficient (in Jangqi too)
7 static get Monochrome() {
11 static get Notoodark() {
17 // Draw all inter-squares lines, shifted:
18 for (let i
= 0; i
< V
.size
.x
; i
++)
19 lines
.push([[i
+0.5, 0.5], [i
+0.5, V
.size
.y
-0.5]]);
20 for (let j
= 0; j
< V
.size
.y
; j
++)
21 lines
.push([[0.5, j
+0.5], [V
.size
.x
-0.5, j
+0.5]]);
23 lines
.push([[0.5, 3.5], [2.5, 5.5]]);
24 lines
.push([[0.5, 5.5], [2.5, 3.5]]);
25 lines
.push([[9.5, 3.5], [7.5, 5.5]]);
26 lines
.push([[9.5, 5.5], [7.5, 3.5]]);
28 lines
.push([[4.5, 0.5], [5.5, 8.5]]);
29 lines
.push([[5.5, 0.5], [4.5, 8.5]]);
33 static get HasFlags() {
37 static get HasEnpassant() {
41 static get LoseOnRepetition() {
45 static get ELEPHANT() {
53 static get ADVISOR() {
58 return [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.ELEPHANT
, V
.ADVISOR
, V
.KING
, V
.CANNON
];
62 return "Xiangqi/" + b
;
66 return { x: 10, y: 9};
69 getPotentialMovesFrom(sq
) {
71 const piece
= this.getPiece(sq
[0], sq
[1]);
74 moves
= this.getPotentialPawnMoves(sq
);
77 moves
= super.getPotentialRookMoves(sq
);
80 moves
= this.getPotentialKnightMoves(sq
);
83 moves
= this.getPotentialElephantMoves(sq
);
86 moves
= this.getPotentialAdvisorMoves(sq
);
89 moves
= this.getPotentialKingMoves(sq
);
92 moves
= this.getPotentialCannonMoves(sq
);
95 if (piece
!= V
.KING
&& this.kingPos
['w'][1] != this.kingPos
['b'][1])
97 if (this.kingPos
['w'][1] == this.kingPos
['b'][1]) {
98 const colKing
= this.kingPos
['w'][1];
99 let intercept
= 0; //count intercepting pieces
100 for (let i
= this.kingPos
['b'][0] + 1; i
< this.kingPos
['w'][0]; i
++) {
101 if (this.board
[i
][colKing
] != V
.EMPTY
) intercept
++;
103 if (intercept
>= 2) return moves
;
104 // intercept == 1 (0 is impossible):
105 // Any move not removing intercept is OK
106 return moves
.filter(m
=> {
108 // From another column?
109 m
.start
.y
!= colKing
||
110 // From behind a king? (including kings themselves!)
111 m
.start
.x
<= this.kingPos
['b'][0] ||
112 m
.start
.x
>= this.kingPos
['w'][0] ||
113 // Intercept piece moving: must remain in-between
115 m
.end
.y
== colKing
&&
116 m
.end
.x
> this.kingPos
['b'][0] &&
117 m
.end
.x
< this.kingPos
['w'][0]
122 // piece == king: check only if move.end.y == enemy king column
123 const color
= this.getColor(sq
[0], sq
[1]);
124 const oppCol
= V
.GetOppCol(color
);
125 // colCheck == -1 if unchecked, 1 if checked and occupied,
126 // 0 if checked and clear
128 return moves
.filter(m
=> {
129 if (m
.end
.y
!= this.kingPos
[oppCol
][1]) return true;
133 for (let i
= this.kingPos
['b'][0] + 1; i
< this.kingPos
['w'][0]; i
++) {
134 if (this.board
[i
][m
.end
.y
] != V
.EMPTY
) {
139 return colCheck
== 1;
141 // Check already done:
142 return colCheck
== 1;
146 getPotentialPawnMoves([x
, y
]) {
147 const c
= this.getColor(x
, y
);
148 const shiftX
= (c
== 'w' ? -1 : 1);
149 const crossedRiver
= (c
== 'w' && x
<= 4 || c
== 'b' && x
>= 5);
150 const lastRank
= (c
== 'w' && x
== 0 || c
== 'b' && x
== 9);
152 if (!lastRank
) steps
.push([shiftX
, 0]);
154 if (y
> 0) steps
.push([0, -1]);
155 if (y
< 9) steps
.push([0, 1]);
157 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep");
160 knightStepsFromRookStep(step
) {
161 if (step
[0] == 0) return [ [1, 2*step
[1]], [-1, 2*step
[1]] ];
162 return [ [2*step
[0], 1], [2*step
[0], -1] ];
165 getPotentialKnightMoves([x
, y
]) {
167 for (let rookStep
of ChessRules
.steps
[V
.ROOK
]) {
168 const [i
, j
] = [x
+ rookStep
[0], y
+ rookStep
[1]];
169 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
170 Array
.prototype.push
.apply(steps
,
171 // These moves might be impossible, but need to be checked:
172 this.knightStepsFromRookStep(rookStep
));
175 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep");
178 getPotentialElephantMoves([x
, y
]) {
180 const c
= this.getColor(x
, y
);
181 for (let bishopStep
of ChessRules
.steps
[V
.BISHOP
]) {
182 const [i
, j
] = [x
+ bishopStep
[0], y
+ bishopStep
[1]];
183 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
184 const [newX
, newY
] = [x
+ 2*bishopStep
[0], y
+ 2*bishopStep
[1]];
185 if ((c
== 'w' && newX
>= 5) || (c
== 'b' && newX
<= 4))
186 // A priori valid (elephant don't cross the river)
187 steps
.push(bishopStep
.map(s
=> 2*s
));
188 // "out of board" checks delayed to next method
191 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep");
194 getPotentialAdvisorMoves([x
, y
]) {
195 // Diagonal steps inside palace
196 const c
= this.getColor(x
, y
);
199 (c
== 'w' && x
!= V
.size
.x
- 2) ||
202 // In a corner: only one step available
204 const direction
= (c
== 'w' ? -1 : 1);
205 if ((c
== 'w' && x
== V
.size
.x
- 1) || (c
== 'b' && x
== 0)) {
207 if (y
== 3) step
= [direction
, 1];
208 else step
= [direction
, -1];
212 if (y
== 3) step
= [-direction
, 1];
213 else step
= [-direction
, -1];
215 return super.getSlideNJumpMoves([x
, y
], [step
], "oneStep");
217 // In the middle of the palace:
219 super.getSlideNJumpMoves([x
, y
], ChessRules
.steps
[V
.BISHOP
], "oneStep")
223 getPotentialKingMoves([x
, y
]) {
224 // Orthogonal steps inside palace
225 const c
= this.getColor(x
, y
);
228 (c
== 'w' && x
!= V
.size
.x
- 2) ||
231 // On the edge: only two steps available
233 if (x
< (c
== 'w' ? V
.size
.x
- 1 : 2)) steps
.push([1, 0]);
234 if (x
> (c
== 'w' ? V
.size
.x
- 3 : 0)) steps
.push([-1, 0]);
235 if (y
> 3) steps
.push([0, -1]);
236 if (y
< 5) steps
.push([0, 1]);
237 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep");
239 // In the middle of the palace:
241 super.getSlideNJumpMoves([x
, y
], ChessRules
.steps
[V
.ROOK
], "oneStep")
245 // NOTE: duplicated from Shako (TODO?)
246 getPotentialCannonMoves([x
, y
]) {
247 const oppCol
= V
.GetOppCol(this.turn
);
249 // Look in every direction until an obstacle (to jump) is met
250 for (const step
of V
.steps
[V
.ROOK
]) {
253 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
254 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
258 // Then, search for an enemy
261 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
265 if (V
.OnBoard(i
, j
) && this.getColor(i
, j
) == oppCol
)
266 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
271 // (King) Never attacked by advisor, since it stays in the palace
272 // Also, never attacked by elephants since they don't cross the river.
273 isAttacked(sq
, color
) {
275 this.isAttackedByPawn(sq
, color
) ||
276 super.isAttackedByRook(sq
, color
) ||
277 this.isAttackedByKnight(sq
, color
) ||
278 this.isAttackedByCannon(sq
, color
)
282 isAttackedByPawn([x
, y
], color
) {
283 // The pawn necessarily crossed the river (attack on king)
284 const shiftX
= (color
== 'w' ? 1 : -1); //shift from king
285 return super.isAttackedBySlideNJump(
286 [x
, y
], color
, V
.PAWN
, [[shiftX
, 0], [0, 1], [0, -1]], "oneStep");
289 knightStepsFromBishopStep(step
) {
290 return [ [2*step
[0], step
[1]], [step
[0], 2*step
[1]] ];
293 isAttackedByKnight([x
, y
], color
) {
294 // Check bishop steps: if empty, look continuation knight step
296 for (let s
of ChessRules
.steps
[V
.BISHOP
]) {
297 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
300 this.board
[i
][j
] == V
.EMPTY
302 Array
.prototype.push
.apply(steps
, this.knightStepsFromBishopStep(s
));
306 super.isAttackedBySlideNJump([x
, y
], color
, V
.KNIGHT
, steps
, "oneStep")
310 // NOTE: duplicated from Shako (TODO?)
311 isAttackedByCannon([x
, y
], color
) {
312 // Reversed process: is there an obstacle in line,
313 // and a cannon next in the same line?
314 for (const step
of V
.steps
[V
.ROOK
]) {
315 let [i
, j
] = [x
+step
[0], y
+step
[1]];
316 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
320 if (V
.OnBoard(i
, j
)) {
321 // Keep looking in this direction
324 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
330 this.getPiece(i
, j
) == V
.CANNON
&&
331 this.getColor(i
, j
) == color
341 if (this.atLeastOneMove()) return "*";
343 const color
= this.turn
;
344 // No valid move: I lose!
345 return (color
== "w" ? "0-1" : "1-0");
348 static get VALUES() {
362 for (let i
= 0; i
< V
.size
.x
; i
++) {
363 for (let j
= 0; j
< V
.size
.y
; j
++) {
364 if (this.board
[i
][j
] != V
.EMPTY
) {
365 const c
= this.getColor(i
, j
);
366 const sign
= (c
== 'w' ? 1 : -1);
367 const piece
= this.getPiece(i
, j
);
368 let pieceEval
= V
.VALUES
[this.getPiece(i
, j
)];
372 (c
== 'w' && i
<= 4) ||
376 // Pawn crossed the river: higher value
379 evaluation
+= sign
* pieceEval
;
386 static get SEARCH_DEPTH() {
390 static GenRandInitFen() {
391 // No randomization here (TODO?)
392 return "rneakaenr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNEAKAENR w 0";
396 let notation
= super.getNotation(move);
397 if (move.vanish
.length
== 2 && move.vanish
[0].p
== V
.PAWN
)
398 notation
= "P" + notation
.substr(1);