Add Xiangqi
[vchess.git] / client / src / variants / Xiangqi.js
1 import { ChessRules } from "@/base_rules";
2
3 export class XiangqiRules extends ChessRules {
4
5 static get Monochrome() {
6 return true;
7 }
8
9 static get Notoodark() {
10 return true;
11 }
12
13 static get Lines() {
14 let lines = [];
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]]);
20 // Add palaces:
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]]);
25 // Show river:
26 lines.push([[4.5, 0.5], [5.5, 8.5]]);
27 lines.push([[5.5, 0.5], [4.5, 8.5]]);
28 return lines;
29 }
30
31 static get HasFlags() {
32 return false;
33 }
34
35 static get HasEnpassant() {
36 return false;
37 }
38
39 static get ELEPHANT() {
40 return "e";
41 }
42
43 static get CANNON() {
44 return "c";
45 }
46
47 static get ADVISOR() {
48 return "a";
49 }
50
51 static get PIECES() {
52 return [V.PAWN, V.ROOK, V.KNIGHT, V.ELEPHANT, V.ADVISOR, V.KING, V.CANNON];
53 }
54
55 getPpath(b) {
56 return "Xiangqi/" + b;
57 }
58
59 static get size() {
60 return { x: 10, y: 9};
61 }
62
63 getPotentialMovesFrom(sq) {
64 switch (this.getPiece(sq[0], sq[1])) {
65 case V.PAWN: return this.getPotentialPawnMoves(sq);
66 case V.ROOK: return super.getPotentialRookMoves(sq);
67 case V.KNIGHT: return this.getPotentialKnightMoves(sq);
68 case V.ELEPHANT: return this.getPotentialElephantMoves(sq);
69 case V.ADVISOR: return this.getPotentialAdvisorMoves(sq);
70 case V.KING: return this.getPotentialKingMoves(sq);
71 case V.CANNON: return this.getPotentialCannonMoves(sq);
72 }
73 return []; //never reached
74 }
75
76 getPotentialPawnMoves([x, y]) {
77 const c = this.getColor(x, y);
78 const shiftX = (c == 'w' ? -1 : 1);
79 const crossedRiver = (c == 'w' && x <= 4 || c == 'b' && x >= 5);
80 const lastRank = (c == 'w' && x == 0 || c == 'b' && x == 9);
81 let steps = [];
82 if (!lastRank) steps.push([shiftX, 0]);
83 if (crossedRiver) {
84 if (y > 0) steps.push([0, -1]);
85 if (y < 9) steps.push([0, 1]);
86 }
87 return super.getSlideNJumpMoves([x, y], steps, "oneStep");
88 }
89
90 knightStepsFromRookStep(step) {
91 if (step[0] == 0) return [ [1, 2*step[1]], [-1, 2*step[1]] ];
92 return [ [2*step[0], 1], [2*step[0], -1] ];
93 }
94
95 getPotentialKnightMoves([x, y]) {
96 let steps = [];
97 for (let rookStep of ChessRules.steps[V.ROOK]) {
98 const [i, j] = [x + rookStep[0], y + rookStep[1]];
99 if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
100 Array.prototype.push.apply(steps,
101 // These moves might be impossible, but need to be checked:
102 this.knightStepsFromRookStep(rookStep));
103 }
104 }
105 return super.getSlideNJumpMoves([x, y], steps, "oneStep");
106 }
107
108 getPotentialElephantMoves([x, y]) {
109 let steps = [];
110 const c = this.getColor(x, y);
111 for (let bishopStep of ChessRules.steps[V.BISHOP]) {
112 const [i, j] = [x + bishopStep[0], y + bishopStep[1]];
113 if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
114 const [newX, newY] = [x + 2*bishopStep[0], y + 2*bishopStep[1]];
115 if ((c == 'w' && newX >= 5) || (c == 'b' && newX <= 4))
116 // A priori valid (elephant don't cross the river)
117 steps.push(bishopStep.map(s => 2*s));
118 // "out of board" checks delayed to next method
119 }
120 }
121 return super.getSlideNJumpMoves([x, y], steps, "oneStep");
122 }
123
124 insidePalace(x, y, c) {
125 return (
126 (y >= 3 && y <= 5) &&
127 (
128 (c == 'w' && x >= 7) ||
129 (c == 'b' && x <= 2)
130 )
131 );
132 }
133
134 getPotentialAdvisorMoves([x, y]) {
135 // Diagonal steps inside palace
136 let steps = [];
137 const c = this.getColor(x, y);
138 for (let s of ChessRules.steps[V.BISHOP]) {
139 if (this.insidePalace(x + s[0], y + s[1], c)) steps.push(s);
140 }
141 return super.getSlideNJumpMoves([x, y], steps, "oneStep");
142 }
143
144 getPotentialKingMoves([x, y]) {
145 // Orthogonal steps inside palace
146 let steps = [];
147 const c = this.getColor(x, y);
148 for (let s of ChessRules.steps[V.ROOK]) {
149 if (this.insidePalace(x + s[0], y + s[1], c)) steps.push(s);
150 }
151 return super.getSlideNJumpMoves([x, y], steps, "oneStep");
152 }
153
154 // NOTE: duplicated from Shako (TODO?)
155 getPotentialCannonMoves([x, y]) {
156 const oppCol = V.GetOppCol(this.turn);
157 let moves = [];
158 // Look in every direction until an obstacle (to jump) is met
159 for (const step of V.steps[V.ROOK]) {
160 let i = x + step[0];
161 let j = y + step[1];
162 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
163 moves.push(this.getBasicMove([x, y], [i, j]));
164 i += step[0];
165 j += step[1];
166 }
167 // Then, search for an enemy
168 i += step[0];
169 j += step[1];
170 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
171 i += step[0];
172 j += step[1];
173 }
174 if (V.OnBoard(i, j) && this.getColor(i, j) == oppCol)
175 moves.push(this.getBasicMove([x, y], [i, j]));
176 }
177 return moves;
178 }
179
180 // (King) Never attacked by advisor, since it stays in the palace
181 // Also, never attacked by elephants since they don't cross the river.
182 isAttacked(sq, color) {
183 return (
184 this.isAttackedByPawn(sq, color) ||
185 super.isAttackedByRook(sq, color) ||
186 this.isAttackedByKnight(sq, color) ||
187 this.isAttackedByCannon(sq, color)
188 );
189 }
190
191 isAttackedByPawn([x, y], color) {
192 // The pawn necessarily crossed the river (attack on king)
193 const shiftX = (color == 'w' ? 1 : -1); //shift from king
194 for (let s of [[shiftX, 0], [0, 1], [0, -1]]) {
195 const [i, j] = [x + s[0], y + s[1]];
196 if (
197 this.board[i][j] != V.EMPTY &&
198 this.getColor(i, j) == color &&
199 this.getPiece(i, j) == V.PAWN
200 ) {
201 return true;
202 }
203 }
204 return false;
205 }
206
207 knightStepsFromBishopStep(step) {
208 return [ [2*step[0], step[1]], [step[0], 2*step[1]] ];
209 }
210
211 isAttackedByKnight([x, y], color) {
212 // Check bishop steps: if empty, look continuation knight step
213 let steps = [];
214 for (let s of ChessRules.steps[V.BISHOP]) {
215 const [i, j] = [x + s[0], y + s[1]];
216 if (
217 V.OnBoard(i, j) &&
218 this.board[i][j] == V.EMPTY
219 ) {
220 Array.prototype.push.apply(steps, this.knightStepsFromBishopStep(s));
221 }
222 }
223 return (
224 super.isAttackedBySlideNJump([x, y], color, V.KNIGHT, steps, "oneStep")
225 );
226 }
227
228 // NOTE: duplicated from Shako (TODO?)
229 isAttackedByCannon([x, y], color) {
230 // Reversed process: is there an obstacle in line,
231 // and a cannon next in the same line?
232 for (const step of V.steps[V.ROOK]) {
233 let [i, j] = [x+step[0], y+step[1]];
234 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
235 i += step[0];
236 j += step[1];
237 }
238 if (V.OnBoard(i, j)) {
239 // Keep looking in this direction
240 i += step[0];
241 j += step[1];
242 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
243 i += step[0];
244 j += step[1];
245 }
246 if (
247 V.OnBoard(i, j) &&
248 this.getPiece(i, j) == V.CANNON &&
249 this.getColor(i, j) == color
250 ) {
251 return true;
252 }
253 }
254 }
255 return false;
256 }
257
258 static get VALUES() {
259 return {
260 p: 1,
261 r: 9,
262 n: 4,
263 e: 2.5,
264 a: 2,
265 c: 4.5,
266 k: 1000
267 };
268 }
269
270 evalPosition() {
271 let evaluation = 0;
272 for (let i = 0; i < V.size.x; i++) {
273 for (let j = 0; j < V.size.y; j++) {
274 if (this.board[i][j] != V.EMPTY) {
275 const c = this.getColor(i, j);
276 const sign = (c == 'w' ? 1 : -1);
277 const piece = this.getPiece(i, j);
278 let pieceEval = V.VALUES[this.getPiece(i, j)];
279 if (
280 piece == V.PAWN &&
281 (
282 (c == 'w' && i <= 4) ||
283 (c == 'b' && i >= 5)
284 )
285 ) {
286 // Pawn crossed the river: higher value
287 pieceEval++;
288 }
289 evaluation += sign * pieceEval;
290 }
291 }
292 }
293 return evaluation;
294 }
295
296 static GenRandInitFen() {
297 // No randomization here (TODO?)
298 return "rneakaenr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNEAKAENR w 0";
299 }
300
301 };