1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
4 export class ShogiRules
extends ChessRules
{
5 static get HasFlags() {
9 static get HasEnpassant() {
13 static IsGoodFen(fen
) {
14 if (!ChessRules
.IsGoodFen(fen
)) return false;
15 const fenParsed
= V
.ParseFen(fen
);
17 if (!fenParsed
.reserve
|| !fenParsed
.reserve
.match(/^[0-9]{14,14}$/))
22 static ParseFen(fen
) {
23 const fenParts
= fen
.split(" ");
25 ChessRules
.ParseFen(fen
),
26 { reserve: fenParts
[3] }
30 // pawns, rooks, knights, bishops and king kept from ChessRules
34 static get SILVER_G() {
45 static get P_KNIGHT() {
48 static get P_SILVER() {
51 static get P_LANCER() {
57 static get P_BISHOP() {
80 getPpath(b
, color
, score
, orientation
) {
81 // 'i' for "inversed":
82 const suffix
= (b
[0] == orientation
? "" : "i");
83 return "Shogi/" + b
+ suffix
;
86 getPPpath(m
, orientation
) {
89 m
.appear
[0].c
+ m
.appear
[0].p
,
97 static GenRandInitFen() {
98 // No randomization for now:
100 "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL " +
106 return super.getFen() + " " + this.getReserveFen();
110 return super.getFenForRepeat() + "_" + this.getReserveFen();
114 let counts
= new Array(14);
115 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
116 counts
[i
] = this.reserve
["w"][V
.RESERVE_PIECES
[i
]];
117 counts
[7 + i
] = this.reserve
["b"][V
.RESERVE_PIECES
[i
]];
119 return counts
.join("");
122 setOtherVariables(fen
) {
123 super.setOtherVariables(fen
);
124 const fenParsed
= V
.ParseFen(fen
);
125 // Also init reserves (used by the interface to show landable pieces)
128 [V
.PAWN
]: parseInt(fenParsed
.reserve
[0]),
129 [V
.ROOK
]: parseInt(fenParsed
.reserve
[1]),
130 [V
.BISHOP
]: parseInt(fenParsed
.reserve
[2]),
131 [V
.GOLD_G
]: parseInt(fenParsed
.reserve
[3]),
132 [V
.SILVER_G
]: parseInt(fenParsed
.reserve
[4]),
133 [V
.KNIGHT
]: parseInt(fenParsed
.reserve
[5]),
134 [V
.LANCER
]: parseInt(fenParsed
.reserve
[6])
137 [V
.PAWN
]: parseInt(fenParsed
.reserve
[7]),
138 [V
.ROOK
]: parseInt(fenParsed
.reserve
[8]),
139 [V
.BISHOP
]: parseInt(fenParsed
.reserve
[9]),
140 [V
.GOLD_G
]: parseInt(fenParsed
.reserve
[10]),
141 [V
.SILVER_G
]: parseInt(fenParsed
.reserve
[11]),
142 [V
.KNIGHT
]: parseInt(fenParsed
.reserve
[12]),
143 [V
.LANCER
]: parseInt(fenParsed
.reserve
[13])
149 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
150 return this.board
[i
][j
].charAt(0);
154 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
155 return this.board
[i
][j
].charAt(1);
159 return { x: 9, y: 9};
162 getReservePpath(index
, color
, orientation
) {
164 "Shogi/" + color
+ V
.RESERVE_PIECES
[index
] +
165 (color
!= orientation
? 'i' : '')
169 // Ordering on reserve pieces
170 static get RESERVE_PIECES() {
172 [V
.PAWN
, V
.ROOK
, V
.BISHOP
, V
.GOLD_G
, V
.SILVER_G
, V
.KNIGHT
, V
.LANCER
]
176 getReserveMoves([x
, y
]) {
177 const color
= this.turn
;
178 const p
= V
.RESERVE_PIECES
[y
];
180 var oppCol
= V
.GetOppCol(color
);
182 [...Array(9).keys()].filter(j
=>
183 [...Array(9).keys()].every(i
=> {
185 this.board
[i
][j
] == V
.EMPTY
||
186 this.getColor(i
, j
) != color
||
187 this.getPiece(i
, j
) != V
.PAWN
192 if (this.reserve
[color
][p
] == 0) return [];
194 const forward
= color
== 'w' ? -1 : 1;
195 const lastRanks
= color
== 'w' ? [0, 1] : [8, 7];
196 for (let i
= 0; i
< V
.size
.x
; i
++) {
198 (i
== lastRanks
[0] && [V
.PAWN
, V
.KNIGHT
, V
.LANCER
].includes(p
)) ||
199 (i
== lastRanks
[1] && p
== V
.KNIGHT
)
203 for (let j
= 0; j
< V
.size
.y
; j
++) {
205 this.board
[i
][j
] == V
.EMPTY
&&
206 (p
!= V
.PAWN
|| allowedFiles
.includes(j
))
218 start: { x: x
, y: y
}, //a bit artificial...
222 // Do not drop on checkmate:
224 const res
= (this.underCheck(oppCol
) && !this.atLeastOneMove());
235 getPotentialMovesFrom([x
, y
]) {
237 // Reserves, outside of board: x == sizeX(+1)
238 return this.getReserveMoves([x
, y
]);
240 switch (this.getPiece(x
, y
)) {
242 return this.getPotentialPawnMoves([x
, y
]);
244 return this.getPotentialRookMoves([x
, y
]);
246 return this.getPotentialKnightMoves([x
, y
]);
248 return this.getPotentialBishopMoves([x
, y
]);
250 return this.getPotentialSilverMoves([x
, y
]);
252 return this.getPotentialLancerMoves([x
, y
]);
254 return this.getPotentialKingMoves([x
, y
]);
256 return this.getPotentialDragonMoves([x
, y
]);
258 return this.getPotentialHorseMoves([x
, y
]);
264 return this.getPotentialGoldMoves([x
, y
]);
266 return []; //never reached
269 // Modified to take promotions into account
270 getSlideNJumpMoves([x
, y
], steps
, options
) {
271 options
= options
|| {};
272 const color
= this.turn
;
273 const oneStep
= options
.oneStep
;
274 const forcePromoteOnLastRank
= options
.force
;
275 const promoteInto
= options
.promote
;
276 const lastRanks
= (color
== 'w' ? [0, 1, 2] : [9, 8, 7]);
278 outerLoop: for (let step
of steps
) {
281 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
282 if (i
!= lastRanks
[0] || !forcePromoteOnLastRank
)
283 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
284 if (!!promoteInto
&& lastRanks
.includes(i
)) {
287 [x
, y
], [i
, j
], { c: color
, p: promoteInto
})
290 if (oneStep
) continue outerLoop
;
294 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
])) {
295 if (i
!= lastRanks
[0] || !forcePromoteOnLastRank
)
296 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
297 if (!!promoteInto
&& lastRanks
.includes(i
)) {
300 [x
, y
], [i
, j
], { c: color
, p: promoteInto
})
308 getPotentialGoldMoves(sq
) {
309 const forward
= (this.turn
== 'w' ? -1 : 1);
310 return this.getSlideNJumpMoves(
312 V
.steps
[V
.ROOK
].concat([ [forward
, 1], [forward
, -1] ]),
317 getPotentialPawnMoves(sq
) {
318 const forward
= (this.turn
== 'w' ? -1 : 1);
320 this.getSlideNJumpMoves(
332 getPotentialSilverMoves(sq
) {
333 const forward
= (this.turn
== 'w' ? -1 : 1);
334 return this.getSlideNJumpMoves(
336 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
344 getPotentialKnightMoves(sq
) {
345 const forward
= (this.turn
== 'w' ? -2 : 2);
346 return this.getSlideNJumpMoves(
348 [ [forward
, 1], [forward
, -1] ],
357 getPotentialRookMoves(sq
) {
358 return this.getSlideNJumpMoves(
359 sq
, V
.steps
[V
.ROOK
], { promote: V
.P_ROOK
});
362 getPotentialBishopMoves(sq
) {
363 return this.getSlideNJumpMoves(
364 sq
, V
.steps
[V
.BISHOP
], { promote: V
.P_BISHOP
});
367 getPotentialLancerMoves(sq
) {
368 const forward
= (this.turn
== 'w' ? -1 : 1);
369 return this.getSlideNJumpMoves(
370 sq
, [[forward
, 0]], { promote: V
.P_LANCER
});
373 getPotentialDragonMoves(sq
) {
375 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
376 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], { oneStep: true }))
380 getPotentialHorseMoves(sq
) {
382 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
383 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
], { oneStep: true }))
387 getPotentialKingMoves(sq
) {
388 return this.getSlideNJumpMoves(
390 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
395 isAttacked(sq
, color
) {
397 this.isAttackedByPawn(sq
, color
) ||
398 this.isAttackedByRook(sq
, color
) ||
399 this.isAttackedByDragon(sq
, color
) ||
400 this.isAttackedByKnight(sq
, color
) ||
401 this.isAttackedByBishop(sq
, color
) ||
402 this.isAttackedByHorse(sq
, color
) ||
403 this.isAttackedByLancer(sq
, color
) ||
404 this.isAttackedBySilver(sq
, color
) ||
405 this.isAttackedByGold(sq
, color
) ||
406 this.isAttackedByKing(sq
, color
)
410 isAttackedByGold([x
, y
], color
) {
411 const shift
= (color
== 'w' ? 1 : -1);
412 for (let step
of V
.steps
[V
.ROOK
].concat([[shift
, 1], [shift
, -1]])) {
413 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
416 this.board
[i
][j
] != V
.EMPTY
&&
417 this.getColor(i
, j
) == color
&&
418 [V
.GOLD_G
, V
.P_PAWN
, V
.P_SILVER
, V
.P_KNIGHT
, V
.P_LANCER
]
419 .includes(this.getPiece(i
, j
))
427 isAttackedBySilver([x
, y
], color
) {
428 const shift
= (color
== 'w' ? 1 : -1);
429 for (let step
of V
.steps
[V
.BISHOP
].concat([[shift
, 0]])) {
430 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
433 this.board
[i
][j
] != V
.EMPTY
&&
434 this.getColor(i
, j
) == color
&&
435 this.getPiece(i
, j
) == V
.SILVER_G
443 isAttackedByPawn([x
, y
], color
) {
444 const shift
= (color
== 'w' ? 1 : -1);
445 const [i
, j
] = [x
+ shift
, y
];
448 this.board
[i
][j
] != V
.EMPTY
&&
449 this.getColor(i
, j
) == color
&&
450 this.getPiece(i
, j
) == V
.PAWN
454 isAttackedByKnight(sq
, color
) {
455 const forward
= (color
== 'w' ? 2 : -2);
456 return this.isAttackedBySlideNJump(
457 sq
, color
, V
.KNIGHT
, [[forward
, 1], [forward
, -1]], "oneStep");
460 isAttackedByLancer(sq
, color
) {
461 const forward
= (color
== 'w' ? 1 : -1);
462 return this.isAttackedBySlideNJump(sq
, color
, V
.LANCER
, [[forward
, 0]]);
465 isAttackedByDragon(sq
, color
) {
467 this.isAttackedBySlideNJump(sq
, color
, V
.P_ROOK
, V
.steps
[V
.ROOK
]) ||
468 this.isAttackedBySlideNJump(
469 sq
, color
, V
.P_ROOK
, V
.steps
[V
.BISHOP
], "oneStep")
473 isAttackedByHorse(sq
, color
) {
475 this.isAttackedBySlideNJump(sq
, color
, V
.P_BISHOP
, V
.steps
[V
.BISHOP
]) ||
476 this.isAttackedBySlideNJump(
477 sq
, color
, V
.P_BISHOP
, V
.steps
[V
.ROOK
], "oneStep")
482 let moves
= super.getAllPotentialMoves();
483 const color
= this.turn
;
484 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
485 moves
= moves
.concat(
486 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
489 return this.filterValid(moves
);
493 if (!super.atLeastOneMove()) {
494 // Search one reserve move
495 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
496 let moves
= this.filterValid(
497 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
499 if (moves
.length
> 0) return true;
506 static get P_CORRESPONDANCES() {
517 static MayDecode(piece
) {
518 if (Object
.keys(V
.P_CORRESPONDANCES
).includes(piece
))
519 return V
.P_CORRESPONDANCES
[piece
];
524 super.postPlay(move);
525 const color
= move.appear
[0].c
;
526 if (move.vanish
.length
== 0)
527 // Drop unpromoted piece:
528 this.reserve
[color
][move.appear
[0].p
]--;
529 else if (move.vanish
.length
== 2)
530 // May capture a promoted piece:
531 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]++;
535 super.postUndo(move);
536 const color
= this.turn
;
537 if (move.vanish
.length
== 0)
538 this.reserve
[color
][move.appear
[0].p
]++;
539 else if (move.vanish
.length
== 2)
540 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]--;
543 static get SEARCH_DEPTH() {
547 static get VALUES() {
548 // TODO: very arbitrary and wrong
568 let evaluation
= super.evalPosition();
570 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
571 const p
= V
.RESERVE_PIECES
[i
];
572 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
573 evaluation
-= this.reserve
["b"][p
] * V
.VALUES
[p
];
579 const finalSquare
= V
.CoordsToSquare(move.end
);
580 if (move.vanish
.length
== 0) {
582 const piece
= move.appear
[0].p
.toUpperCase();
583 return (piece
!= 'P' ? piece : "") + "@" + finalSquare
;
585 const piece
= move.vanish
[0].p
.toUpperCase();
587 (piece
!= 'P' || move.vanish
.length
== 2 ? piece : "") +
588 (move.vanish
.length
== 2 ? "x" : "") +
591 move.appear
[0].p
!= move.vanish
[0].p
592 ? "=" + move.appear
[0].p
.toUpperCase()