4fef56536307d8d170b34d42c8f82d16114b6df7
1 import { ChessRules
} from "@/base_rules";
3 export class XiangqiRules
extends ChessRules
{
9 // NOTE (TODO?) scanKings() could be more efficient (in Jangqi too)
11 static get Monochrome() {
15 static get Notoodark() {
21 // Draw all inter-squares lines, shifted:
22 for (let i
= 0; i
< V
.size
.x
; i
++)
23 lines
.push([[i
+0.5, 0.5], [i
+0.5, V
.size
.y
-0.5]]);
24 for (let j
= 0; j
< V
.size
.y
; j
++)
25 lines
.push([[0.5, j
+0.5], [V
.size
.x
-0.5, j
+0.5]]);
27 lines
.push([[0.5, 3.5], [2.5, 5.5]]);
28 lines
.push([[0.5, 5.5], [2.5, 3.5]]);
29 lines
.push([[9.5, 3.5], [7.5, 5.5]]);
30 lines
.push([[9.5, 5.5], [7.5, 3.5]]);
32 lines
.push([[4.5, 0.5], [5.5, 8.5]]);
33 lines
.push([[5.5, 0.5], [4.5, 8.5]]);
37 static get HasFlags() {
41 static get HasEnpassant() {
45 static get LoseOnRepetition() {
49 static get ELEPHANT() {
57 static get ADVISOR() {
62 return [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.ELEPHANT
, V
.ADVISOR
, V
.KING
, V
.CANNON
];
66 return "Xiangqi/" + b
;
70 return { x: 10, y: 9};
73 getPotentialMovesFrom(sq
) {
75 const piece
= this.getPiece(sq
[0], sq
[1]);
78 moves
= this.getPotentialPawnMoves(sq
);
81 moves
= super.getPotentialRookMoves(sq
);
84 moves
= this.getPotentialKnightMoves(sq
);
87 moves
= this.getPotentialElephantMoves(sq
);
90 moves
= this.getPotentialAdvisorMoves(sq
);
93 moves
= this.getPotentialKingMoves(sq
);
96 moves
= this.getPotentialCannonMoves(sq
);
99 if (piece
!= V
.KING
&& this.kingPos
['w'][1] != this.kingPos
['b'][1])
101 if (this.kingPos
['w'][1] == this.kingPos
['b'][1]) {
102 const colKing
= this.kingPos
['w'][1];
103 let intercept
= 0; //count intercepting pieces
104 for (let i
= this.kingPos
['b'][0] + 1; i
< this.kingPos
['w'][0]; i
++) {
105 if (this.board
[i
][colKing
] != V
.EMPTY
) intercept
++;
107 if (intercept
>= 2) return moves
;
108 // intercept == 1 (0 is impossible):
109 // Any move not removing intercept is OK
110 return moves
.filter(m
=> {
112 // From another column?
113 m
.start
.y
!= colKing
||
114 // From behind a king? (including kings themselves!)
115 m
.start
.x
<= this.kingPos
['b'][0] ||
116 m
.start
.x
>= this.kingPos
['w'][0] ||
117 // Intercept piece moving: must remain in-between
119 m
.end
.y
== colKing
&&
120 m
.end
.x
> this.kingPos
['b'][0] &&
121 m
.end
.x
< this.kingPos
['w'][0]
126 // piece == king: check only if move.end.y == enemy king column
127 const color
= this.getColor(sq
[0], sq
[1]);
128 const oppCol
= V
.GetOppCol(color
);
129 // colCheck == -1 if unchecked, 1 if checked and occupied,
130 // 0 if checked and clear
132 return moves
.filter(m
=> {
133 if (m
.end
.y
!= this.kingPos
[oppCol
][1]) return true;
137 for (let i
= this.kingPos
['b'][0] + 1; i
< this.kingPos
['w'][0]; i
++) {
138 if (this.board
[i
][m
.end
.y
] != V
.EMPTY
) {
143 return colCheck
== 1;
145 // Check already done:
146 return colCheck
== 1;
150 getPotentialPawnMoves([x
, y
]) {
151 const c
= this.getColor(x
, y
);
152 const shiftX
= (c
== 'w' ? -1 : 1);
153 const crossedRiver
= (c
== 'w' && x
<= 4 || c
== 'b' && x
>= 5);
154 const lastRank
= (c
== 'w' && x
== 0 || c
== 'b' && x
== 9);
156 if (!lastRank
) steps
.push([shiftX
, 0]);
158 if (y
> 0) steps
.push([0, -1]);
159 if (y
< 9) steps
.push([0, 1]);
161 return super.getSlideNJumpMoves([x
, y
], steps
, 1);
164 knightStepsFromRookStep(step
) {
165 if (step
[0] == 0) return [ [1, 2*step
[1]], [-1, 2*step
[1]] ];
166 return [ [2*step
[0], 1], [2*step
[0], -1] ];
169 getPotentialKnightMoves([x
, y
]) {
171 for (let rookStep
of ChessRules
.steps
[V
.ROOK
]) {
172 const [i
, j
] = [x
+ rookStep
[0], y
+ rookStep
[1]];
173 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
174 Array
.prototype.push
.apply(steps
,
175 // These moves might be impossible, but need to be checked:
176 this.knightStepsFromRookStep(rookStep
));
179 return super.getSlideNJumpMoves([x
, y
], steps
, 1);
182 getPotentialElephantMoves([x
, y
]) {
184 const c
= this.getColor(x
, y
);
185 for (let bishopStep
of ChessRules
.steps
[V
.BISHOP
]) {
186 const [i
, j
] = [x
+ bishopStep
[0], y
+ bishopStep
[1]];
187 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
188 const [newX
, newY
] = [x
+ 2*bishopStep
[0], y
+ 2*bishopStep
[1]];
189 if ((c
== 'w' && newX
>= 5) || (c
== 'b' && newX
<= 4))
190 // A priori valid (elephant don't cross the river)
191 steps
.push(bishopStep
.map(s
=> 2*s
));
192 // "out of board" checks delayed to next method
195 return super.getSlideNJumpMoves([x
, y
], steps
, 1);
198 getPotentialAdvisorMoves([x
, y
]) {
199 // Diagonal steps inside palace
200 const c
= this.getColor(x
, y
);
203 (c
== 'w' && x
!= V
.size
.x
- 2) ||
206 // In a corner: only one step available
208 const direction
= (c
== 'w' ? -1 : 1);
209 if ((c
== 'w' && x
== V
.size
.x
- 1) || (c
== 'b' && x
== 0)) {
211 if (y
== 3) step
= [direction
, 1];
212 else step
= [direction
, -1];
216 if (y
== 3) step
= [-direction
, 1];
217 else step
= [-direction
, -1];
219 return super.getSlideNJumpMoves([x
, y
], [step
], 1);
221 // In the middle of the palace:
223 super.getSlideNJumpMoves([x
, y
], ChessRules
.steps
[V
.BISHOP
], 1)
227 getPotentialKingMoves([x
, y
]) {
228 // Orthogonal steps inside palace
229 const c
= this.getColor(x
, y
);
232 (c
== 'w' && x
!= V
.size
.x
- 2) ||
235 // On the edge: only two steps available
237 if (x
< (c
== 'w' ? V
.size
.x
- 1 : 2)) steps
.push([1, 0]);
238 if (x
> (c
== 'w' ? V
.size
.x
- 3 : 0)) steps
.push([-1, 0]);
239 if (y
> 3) steps
.push([0, -1]);
240 if (y
< 5) steps
.push([0, 1]);
241 return super.getSlideNJumpMoves([x
, y
], steps
, 1);
243 // In the middle of the palace:
245 super.getSlideNJumpMoves([x
, y
], ChessRules
.steps
[V
.ROOK
], 1)
249 // NOTE: duplicated from Shako (TODO?)
250 getPotentialCannonMoves([x
, y
]) {
251 const oppCol
= V
.GetOppCol(this.turn
);
253 // Look in every direction until an obstacle (to jump) is met
254 for (const step
of V
.steps
[V
.ROOK
]) {
257 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
258 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
262 // Then, search for an enemy
265 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
269 if (V
.OnBoard(i
, j
) && this.getColor(i
, j
) == oppCol
)
270 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
275 // (King) Never attacked by advisor, since it stays in the palace
276 // Also, never attacked by elephants since they don't cross the river.
277 isAttacked(sq
, color
) {
279 this.isAttackedByPawn(sq
, color
) ||
280 super.isAttackedByRook(sq
, color
) ||
281 this.isAttackedByKnight(sq
, color
) ||
282 this.isAttackedByCannon(sq
, color
)
286 isAttackedByPawn([x
, y
], color
) {
287 // The pawn necessarily crossed the river (attack on king)
288 const shiftX
= (color
== 'w' ? 1 : -1); //shift from king
289 return super.isAttackedBySlideNJump(
290 [x
, y
], color
, V
.PAWN
, [[shiftX
, 0], [0, 1], [0, -1]], 1);
293 knightStepsFromBishopStep(step
) {
294 return [ [2*step
[0], step
[1]], [step
[0], 2*step
[1]] ];
297 isAttackedByKnight([x
, y
], color
) {
298 // Check bishop steps: if empty, look continuation knight step
300 for (let s
of ChessRules
.steps
[V
.BISHOP
]) {
301 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
304 this.board
[i
][j
] == V
.EMPTY
306 Array
.prototype.push
.apply(steps
, this.knightStepsFromBishopStep(s
));
310 super.isAttackedBySlideNJump([x
, y
], color
, V
.KNIGHT
, steps
, 1)
314 // NOTE: duplicated from Shako (TODO?)
315 isAttackedByCannon([x
, y
], color
) {
316 // Reversed process: is there an obstacle in line,
317 // and a cannon next in the same line?
318 for (const step
of V
.steps
[V
.ROOK
]) {
319 let [i
, j
] = [x
+step
[0], y
+step
[1]];
320 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
324 if (V
.OnBoard(i
, j
)) {
325 // Keep looking in this direction
328 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
334 this.getPiece(i
, j
) == V
.CANNON
&&
335 this.getColor(i
, j
) == color
345 if (this.atLeastOneMove()) return "*";
347 const color
= this.turn
;
348 // No valid move: I lose!
349 return (color
== "w" ? "0-1" : "1-0");
352 static get VALUES() {
366 for (let i
= 0; i
< V
.size
.x
; i
++) {
367 for (let j
= 0; j
< V
.size
.y
; j
++) {
368 if (this.board
[i
][j
] != V
.EMPTY
) {
369 const c
= this.getColor(i
, j
);
370 const sign
= (c
== 'w' ? 1 : -1);
371 const piece
= this.getPiece(i
, j
);
372 let pieceEval
= V
.VALUES
[this.getPiece(i
, j
)];
376 (c
== 'w' && i
<= 4) ||
380 // Pawn crossed the river: higher value
383 evaluation
+= sign
* pieceEval
;
390 static get SEARCH_DEPTH() {
394 static GenRandInitFen() {
395 // No randomization here (TODO?)
396 return "rneakaenr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNEAKAENR w 0";
400 let notation
= super.getNotation(move);
401 if (move.vanish
.length
== 2 && move.vanish
[0].p
== V
.PAWN
)
402 notation
= "P" + notation
.substr(1);