Attempt to fix Eightpieces
[vchess.git] / client / src / variants / Shogun.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3
4 export class ShogunRules extends ChessRules {
5
6 static get CAPTAIN() {
7 return 'c';
8 }
9 static get GENERAL() {
10 return 'g';
11 }
12 static get ARCHBISHOP() {
13 return 'a';
14 }
15 static get MORTAR() {
16 return 'm';
17 }
18 static get DUCHESS() {
19 return 'f';
20 }
21
22 static get PIECES() {
23 return (
24 ChessRules.PIECES
25 .concat([V.CAPTAIN, V.GENERAL, V.ARCHBISHOP, V.MORTAR, V.DUCHESS])
26 );
27 }
28
29 getPpath(b) {
30 return "Shogun/" + b;
31 }
32
33 getReservePpath(index, color) {
34 return "Shogun/" + color + V.RESERVE_PIECES[index];
35 }
36
37 static IsGoodFen(fen) {
38 if (!ChessRules.IsGoodFen(fen)) return false;
39 const fenParsed = V.ParseFen(fen);
40 // 5) Check reserves
41 if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
42 return false;
43 return true;
44 }
45
46 static ParseFen(fen) {
47 const fenParts = fen.split(" ");
48 return Object.assign(
49 ChessRules.ParseFen(fen),
50 { reserve: fenParts[5] }
51 );
52 }
53
54 static GenRandInitFen(options) {
55 return ChessRules.GenRandInitFen(options) + " 0000000000";
56 }
57
58 getFen() {
59 return super.getFen() + " " + this.getReserveFen();
60 }
61
62 getFenForRepeat() {
63 return super.getFenForRepeat() + "_" + this.getReserveFen();
64 }
65
66 getReserveFen() {
67 let counts = new Array(10);
68 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
69 counts[i] = this.reserve["w"][V.RESERVE_PIECES[i]];
70 counts[5 + i] = this.reserve["b"][V.RESERVE_PIECES[i]];
71 }
72 return counts.join("");
73 }
74
75 setOtherVariables(fen) {
76 super.setOtherVariables(fen);
77 // Also init reserves (used by the interface to show landable pieces)
78 const reserve =
79 V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10));
80 this.reserve = {
81 w: {
82 [V.PAWN]: reserve[0],
83 [V.ROOK]: reserve[1],
84 [V.KNIGHT]: reserve[2],
85 [V.BISHOP]: reserve[3],
86 [V.DUCHESS]: reserve[4]
87 },
88 b: {
89 [V.PAWN]: reserve[5],
90 [V.ROOK]: reserve[6],
91 [V.KNIGHT]: reserve[7],
92 [V.BISHOP]: reserve[8],
93 [V.DUCHESS]: reserve[9]
94 }
95 };
96 }
97
98 getColor(i, j) {
99 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
100 return this.board[i][j].charAt(0);
101 }
102
103 getPiece(i, j) {
104 if (i >= V.size.x) return V.RESERVE_PIECES[j];
105 return this.board[i][j].charAt(1);
106 }
107
108 // Ordering on reserve pieces
109 static get RESERVE_PIECES() {
110 return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.DUCHESS];
111 }
112
113 getReserveMoves([x, y]) {
114 const color = this.turn;
115 const iZone = (color == 'w' ? [3, 4, 5, 6, 7] : [0, 1, 2, 3, 4]);
116 const p = V.RESERVE_PIECES[y];
117 if (this.reserve[color][p] == 0) return [];
118 let moves = [];
119 for (let i of iZone) {
120 for (let j = 0; j < V.size.y; j++) {
121 if (this.board[i][j] == V.EMPTY) {
122 let mv = new Move({
123 appear: [
124 new PiPo({
125 x: i,
126 y: j,
127 c: color,
128 p: p
129 })
130 ],
131 vanish: [],
132 start: { x: x, y: y }, //a bit artificial...
133 end: { x: i, y: j }
134 });
135 moves.push(mv);
136 }
137 }
138 }
139 return moves;
140 }
141
142 static get MapUnpromoted() {
143 return {
144 f: 'q',
145 r: 'm',
146 b: 'a',
147 p: 'c',
148 n: 'g'
149 };
150 }
151
152 getPotentialMovesFrom([x, y]) {
153 if (x >= V.size.x)
154 // Reserves, outside of board: x == sizeX(+1)
155 return this.getReserveMoves([x, y]);
156 // Standard moves
157 const piece = this.getPiece(x, y);
158 const sq = [x, y];
159 if (piece == V.KING) return super.getPotentialKingMoves(sq);
160 let moves = [];
161 switch (piece) {
162 // Unpromoted
163 case V.PAWN:
164 return this.getPotentialPawnMoves(sq);
165 case V.ROOK:
166 moves = super.getPotentialRookMoves(sq);
167 break;
168 case V.KNIGHT:
169 moves = super.getPotentialKnightMoves(sq);
170 break;
171 case V.BISHOP:
172 moves = super.getPotentialBishopMoves(sq);
173 break;
174 case V.DUCHESS:
175 moves = this.getPotentialDuchessMoves(sq);
176 break;
177 }
178 if ([V.ROOK, V.KNIGHT, V.BISHOP, V.DUCHESS].includes(piece)) {
179 let extraMoves = [];
180 // Check that no promoted form is already on board:
181 const promotedForm = V.MapUnpromoted[piece];
182 const c = this.turn;
183 if (
184 this.board.some(b =>
185 b.some(cell =>
186 cell[0] == c && cell[1] == promotedForm)
187 )
188 ) {
189 return moves;
190 }
191 const promotionZone = (this.turn == 'w' ? [0, 1, 2] : [5, 6, 7]);
192 moves.forEach(m => {
193 if (
194 promotionZone.includes(m.end.x) ||
195 promotionZone.includes(m.start.x)
196 ) {
197 let newMove = JSON.parse(JSON.stringify(m));
198 newMove.appear[0].p = promotedForm;
199 extraMoves.push(newMove);
200 }
201 });
202 return moves.concat(extraMoves);
203 }
204 switch (piece) {
205 // Promoted
206 case V.CAPTAIN: return this.getPotentialCaptainMoves(sq);
207 case V.MORTAR: return this.getPotentialMortarMoves(sq);
208 case V.GENERAL: return this.getPotentialGeneralMoves(sq);
209 case V.ARCHBISHOP: return this.getPotentialArchbishopMoves(sq);
210 case V.QUEEN: return super.getPotentialQueenMoves(sq);
211 }
212 return []; //never reached
213 }
214
215 getPotentialPawnMoves([x, y]) {
216 // NOTE: apply promotion freely, but not on en-passant
217 const c = this.turn;
218 const oppCol = V.GetOppCol(c);
219 const forward = (c == 'w' ? -1 : 1);
220 const initialRank = (c == 'w' ? 6 : 1);
221 let moves = [];
222 // Pawn push
223 let [i, j] = [x + forward, y];
224 if (this.board[i][j] == V.EMPTY) {
225 moves.push(this.getBasicMove([x, y], [i, j]));
226 if (x == initialRank && this.board[i + forward][j] == V.EMPTY)
227 moves.push(this.getBasicMove([x, y], [i + forward, j]));
228 }
229 // Captures
230 for (let shiftY of [-1, 1]) {
231 [i, j] = [x + forward, y + shiftY];
232 if (
233 V.OnBoard(i, j) &&
234 this.board[i][j] != V.EMPTY &&
235 this.getColor(i, j) == oppCol
236 ) {
237 moves.push(this.getBasicMove([x, y], [i, j]));
238 }
239 }
240 let extraMoves = [];
241 const promotionZone = (this.turn == 'w' ? [1, 2] : [5, 6]);
242 const lastRank = (c == 'w' ? 0 : 7);
243 moves.forEach(m => {
244 if (m.end.x == lastRank)
245 // Force promotion
246 m.appear[0].p = V.CAPTAIN;
247 else if (promotionZone.includes(m.end.x)) {
248 let newMove = JSON.parse(JSON.stringify(m));
249 newMove.appear[0].p = V.CAPTAIN;
250 extraMoves.push(newMove);
251 }
252 });
253 return (
254 moves.concat(extraMoves)
255 .concat(super.getEnpassantCaptures([x, y], forward))
256 );
257 }
258
259 getPotentialDuchessMoves(sq) {
260 return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 1);
261 }
262
263 getPotentialCaptainMoves(sq) {
264 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
265 return super.getSlideNJumpMoves(sq, steps, 1);
266 }
267
268 getPotentialMortarMoves(sq) {
269 return (
270 super.getSlideNJumpMoves(sq, V.steps[V.ROOK])
271 .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], 1))
272 );
273 }
274
275 getPotentialGeneralMoves(sq) {
276 const steps =
277 V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]);
278 return super.getSlideNJumpMoves(sq, steps, 1);
279 }
280
281 getPotentialArchbishopMoves(sq) {
282 return (
283 super.getSlideNJumpMoves(sq, V.steps[V.BISHOP])
284 .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], 1))
285 );
286 }
287
288 isAttacked(sq, color) {
289 return (
290 super.isAttacked(sq, color) ||
291 this.isAttackedByDuchess(sq, color) ||
292 this.isAttackedByCaptain(sq, color) ||
293 this.isAttackedByMortar(sq, color) ||
294 this.isAttackedByGeneral(sq, color) ||
295 this.isAttackedByArchbishop(sq, color)
296 );
297 }
298
299 isAttackedByDuchess(sq, color) {
300 return (
301 super.isAttackedBySlideNJump(
302 sq, color, V.DUCHESS, V.steps[V.BISHOP], 1)
303 );
304 }
305
306 isAttackedByCaptain(sq, color) {
307 const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]);
308 return (
309 super.isAttackedBySlideNJump(sq, color, V.CAPTAIN, steps, 1)
310 );
311 }
312
313 isAttackedByMortar(sq, color) {
314 return (
315 super.isAttackedBySlideNJump(sq, color, V.MORTAR, V.steps[V.ROOK]) ||
316 super.isAttackedBySlideNJump(
317 sq, color, V.MORTAR, V.steps[V.KNIGHT], 1)
318 );
319 }
320
321 isAttackedByGeneral(sq, color) {
322 const steps =
323 V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]);
324 return (
325 super.isAttackedBySlideNJump(sq, color, V.GENERAL, steps, 1)
326 );
327 }
328
329 isAttackedByArchbishop(sq, color) {
330 return (
331 super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP])
332 ||
333 super.isAttackedBySlideNJump(
334 sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], 1)
335 );
336 }
337
338 getAllValidMoves() {
339 let moves = super.getAllPotentialMoves();
340 const color = this.turn;
341 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
342 moves = moves.concat(
343 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
344 );
345 }
346 return this.filterValid(moves);
347 }
348
349 atLeastOneMove() {
350 if (!super.atLeastOneMove()) {
351 // Search one reserve move
352 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
353 let moves = this.filterValid(
354 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
355 );
356 if (moves.length > 0) return true;
357 }
358 return false;
359 }
360 return true;
361 }
362
363 static get MapPromoted() {
364 return {
365 q: 'f',
366 m: 'r',
367 a: 'b',
368 c: 'p',
369 g: 'n'
370 };
371 }
372
373 getUnpromotedForm(piece) {
374 if (Object.keys(V.MapPromoted).includes(piece))
375 return V.MapPromoted[piece];
376 return piece;
377 }
378
379 postPlay(move) {
380 super.postPlay(move);
381 // Skip castle:
382 if (move.vanish.length == 2 && move.appear.length == 2) return;
383 const color = move.appear[0].c;
384 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--;
385 else if (move.vanish.length == 2)
386 this.reserve[color][this.getUnpromotedForm(move.vanish[1].p)]++;
387 }
388
389 postUndo(move) {
390 super.postUndo(move);
391 if (move.vanish.length == 2 && move.appear.length == 2) return;
392 const color = this.turn;
393 if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++;
394 else if (move.vanish.length == 2)
395 this.reserve[color][this.getUnpromotedForm(move.vanish[1].p)]--;
396 }
397
398 static get SEARCH_DEPTH() {
399 return 2;
400 }
401
402 static get VALUES() {
403 return (
404 Object.assign(
405 {
406 c: 4,
407 g: 5,
408 a: 7,
409 m: 7,
410 f: 2
411 },
412 ChessRules.VALUES
413 )
414 );
415 }
416
417 evalPosition() {
418 let evaluation = super.evalPosition();
419 // Add reserves:
420 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
421 const p = V.RESERVE_PIECES[i];
422 evaluation += this.reserve["w"][p] * V.VALUES[p];
423 evaluation -= this.reserve["b"][p] * V.VALUES[p];
424 }
425 return evaluation;
426 }
427
428 getNotation(move) {
429 if (move.vanish.length > 0) return super.getNotation(move);
430 // Rebirth:
431 const piece =
432 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
433 return piece + "@" + V.CoordsToSquare(move.end);
434 }
435
436 };