1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { shuffle
} from "@/utils/alea";
5 export class ShogiRules
extends ChessRules
{
6 static get HasFlags() {
10 static get HasEnpassant() {
14 static get Monochrome() {
18 static IsGoodFen(fen
) {
19 if (!ChessRules
.IsGoodFen(fen
)) return false;
20 const fenParsed
= V
.ParseFen(fen
);
22 if (!fenParsed
.reserve
|| !fenParsed
.reserve
.match(/^[0-9]{14,14}$/))
27 static ParseFen(fen
) {
28 const fenParts
= fen
.split(" ");
30 ChessRules
.ParseFen(fen
),
31 { reserve: fenParts
[3] }
35 // pawns, rooks, knights, bishops and king kept from ChessRules
39 static get SILVER_G() {
50 static get P_KNIGHT() {
53 static get P_SILVER() {
56 static get P_LANCE() {
62 static get P_BISHOP() {
85 getPpath(b
, color
, score
, orientation
) {
86 // 'i' for "inversed":
87 const suffix
= (b
[0] == orientation
? "" : "i");
88 return "Shogi/" + b
+ suffix
;
91 getPPpath(m
, orientation
) {
94 m
.appear
[0].c
+ m
.appear
[0].p
,
102 static GenRandInitFen(randomness
) {
103 if (randomness
== 0) {
105 "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL " +
109 let pieces
= { w: new Array(9), b: new Array(9) };
110 for (let c
of ["w", "b"]) {
111 if (c
== 'b' && randomness
== 1) {
112 pieces
['b'] = pieces
['w'];
115 let positions
= shuffle(ArrayFun
.range(9));
116 const composition
= ['l', 'l', 'n', 'n', 's', 's', 'g', 'g', 'k'];
117 for (let i
= 0; i
< 9; i
++) pieces
[c
][positions
[i
]] = composition
[i
];
120 pieces
["b"].join("") +
121 "/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/" +
122 pieces
["w"].join("").toUpperCase() +
123 " w 0 00000000000000"
128 return super.getFen() + " " + this.getReserveFen();
132 return super.getFenForRepeat() + "_" + this.getReserveFen();
136 let counts
= new Array(14);
137 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
138 counts
[i
] = this.reserve
["w"][V
.RESERVE_PIECES
[i
]];
139 counts
[7 + i
] = this.reserve
["b"][V
.RESERVE_PIECES
[i
]];
141 return counts
.join("");
144 setOtherVariables(fen
) {
145 super.setOtherVariables(fen
);
146 const fenParsed
= V
.ParseFen(fen
);
147 // Also init reserves (used by the interface to show landable pieces)
150 [V
.PAWN
]: parseInt(fenParsed
.reserve
[0]),
151 [V
.ROOK
]: parseInt(fenParsed
.reserve
[1]),
152 [V
.BISHOP
]: parseInt(fenParsed
.reserve
[2]),
153 [V
.GOLD_G
]: parseInt(fenParsed
.reserve
[3]),
154 [V
.SILVER_G
]: parseInt(fenParsed
.reserve
[4]),
155 [V
.KNIGHT
]: parseInt(fenParsed
.reserve
[5]),
156 [V
.LANCE
]: parseInt(fenParsed
.reserve
[6])
159 [V
.PAWN
]: parseInt(fenParsed
.reserve
[7]),
160 [V
.ROOK
]: parseInt(fenParsed
.reserve
[8]),
161 [V
.BISHOP
]: parseInt(fenParsed
.reserve
[9]),
162 [V
.GOLD_G
]: parseInt(fenParsed
.reserve
[10]),
163 [V
.SILVER_G
]: parseInt(fenParsed
.reserve
[11]),
164 [V
.KNIGHT
]: parseInt(fenParsed
.reserve
[12]),
165 [V
.LANCE
]: parseInt(fenParsed
.reserve
[13])
171 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
172 return this.board
[i
][j
].charAt(0);
176 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
177 return this.board
[i
][j
].charAt(1);
181 return { x: 9, y: 9};
184 getReservePpath(index
, color
, orientation
) {
186 "Shogi/" + color
+ V
.RESERVE_PIECES
[index
] +
187 (color
!= orientation
? 'i' : '')
191 // Ordering on reserve pieces
192 static get RESERVE_PIECES() {
194 [V
.PAWN
, V
.ROOK
, V
.BISHOP
, V
.GOLD_G
, V
.SILVER_G
, V
.KNIGHT
, V
.LANCE
]
198 getReserveMoves([x
, y
]) {
199 const color
= this.turn
;
200 const p
= V
.RESERVE_PIECES
[y
];
202 var oppCol
= V
.GetOppCol(color
);
204 [...Array(9).keys()].filter(j
=>
205 [...Array(9).keys()].every(i
=> {
207 this.board
[i
][j
] == V
.EMPTY
||
208 this.getColor(i
, j
) != color
||
209 this.getPiece(i
, j
) != V
.PAWN
214 if (this.reserve
[color
][p
] == 0) return [];
216 const forward
= color
== 'w' ? -1 : 1;
217 const lastRanks
= color
== 'w' ? [0, 1] : [8, 7];
218 for (let i
= 0; i
< V
.size
.x
; i
++) {
220 (i
== lastRanks
[0] && [V
.PAWN
, V
.KNIGHT
, V
.LANCE
].includes(p
)) ||
221 (i
== lastRanks
[1] && p
== V
.KNIGHT
)
225 for (let j
= 0; j
< V
.size
.y
; j
++) {
227 this.board
[i
][j
] == V
.EMPTY
&&
228 (p
!= V
.PAWN
|| allowedFiles
.includes(j
))
240 start: { x: x
, y: y
}, //a bit artificial...
244 // Do not drop on checkmate:
246 const res
= (this.underCheck(oppCol
) && !this.atLeastOneMove());
257 getPotentialMovesFrom([x
, y
]) {
259 // Reserves, outside of board: x == sizeX(+1)
260 return this.getReserveMoves([x
, y
]);
262 switch (this.getPiece(x
, y
)) {
264 return this.getPotentialPawnMoves([x
, y
]);
266 return this.getPotentialRookMoves([x
, y
]);
268 return this.getPotentialKnightMoves([x
, y
]);
270 return this.getPotentialBishopMoves([x
, y
]);
272 return this.getPotentialSilverMoves([x
, y
]);
274 return this.getPotentialLanceMoves([x
, y
]);
276 return this.getPotentialKingMoves([x
, y
]);
278 return this.getPotentialDragonMoves([x
, y
]);
280 return this.getPotentialHorseMoves([x
, y
]);
286 return this.getPotentialGoldMoves([x
, y
]);
288 return []; //never reached
291 // Modified to take promotions into account
292 getSlideNJumpMoves([x
, y
], steps
, options
) {
293 options
= options
|| {};
294 const color
= this.turn
;
295 const oneStep
= options
.oneStep
;
296 const forcePromoteOnLastRank
= options
.force
;
297 const promoteInto
= options
.promote
;
298 const lastRanks
= (color
== 'w' ? [0, 1, 2] : [9, 8, 7]);
300 outerLoop: for (let step
of steps
) {
303 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
304 if (i
!= lastRanks
[0] || !forcePromoteOnLastRank
)
305 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
306 if (!!promoteInto
&& lastRanks
.includes(i
)) {
309 [x
, y
], [i
, j
], { c: color
, p: promoteInto
})
312 if (oneStep
) continue outerLoop
;
316 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
])) {
317 if (i
!= lastRanks
[0] || !forcePromoteOnLastRank
)
318 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
319 if (!!promoteInto
&& lastRanks
.includes(i
)) {
322 [x
, y
], [i
, j
], { c: color
, p: promoteInto
})
330 getPotentialGoldMoves(sq
) {
331 const forward
= (this.turn
== 'w' ? -1 : 1);
332 return this.getSlideNJumpMoves(
334 V
.steps
[V
.ROOK
].concat([ [forward
, 1], [forward
, -1] ]),
339 getPotentialPawnMoves(sq
) {
340 const forward
= (this.turn
== 'w' ? -1 : 1);
342 this.getSlideNJumpMoves(
354 getPotentialSilverMoves(sq
) {
355 const forward
= (this.turn
== 'w' ? -1 : 1);
356 return this.getSlideNJumpMoves(
358 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
366 getPotentialKnightMoves(sq
) {
367 const forward
= (this.turn
== 'w' ? -2 : 2);
368 return this.getSlideNJumpMoves(
370 [ [forward
, 1], [forward
, -1] ],
379 getPotentialLanceMoves(sq
) {
380 const forward
= (this.turn
== 'w' ? -1 : 1);
381 return this.getSlideNJumpMoves(
391 getPotentialRookMoves(sq
) {
392 return this.getSlideNJumpMoves(
393 sq
, V
.steps
[V
.ROOK
], { promote: V
.P_ROOK
});
396 getPotentialBishopMoves(sq
) {
397 return this.getSlideNJumpMoves(
398 sq
, V
.steps
[V
.BISHOP
], { promote: V
.P_BISHOP
});
401 getPotentialDragonMoves(sq
) {
403 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
404 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], { oneStep: true }))
408 getPotentialHorseMoves(sq
) {
410 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
411 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
], { oneStep: true }))
415 getPotentialKingMoves(sq
) {
416 return this.getSlideNJumpMoves(
418 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
423 isAttacked(sq
, color
) {
425 this.isAttackedByPawn(sq
, color
) ||
426 this.isAttackedByRook(sq
, color
) ||
427 this.isAttackedByDragon(sq
, color
) ||
428 this.isAttackedByKnight(sq
, color
) ||
429 this.isAttackedByBishop(sq
, color
) ||
430 this.isAttackedByHorse(sq
, color
) ||
431 this.isAttackedByLance(sq
, color
) ||
432 this.isAttackedBySilver(sq
, color
) ||
433 this.isAttackedByGold(sq
, color
) ||
434 this.isAttackedByKing(sq
, color
)
438 isAttackedByGold([x
, y
], color
) {
439 const shift
= (color
== 'w' ? 1 : -1);
440 for (let step
of V
.steps
[V
.ROOK
].concat([[shift
, 1], [shift
, -1]])) {
441 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
444 this.board
[i
][j
] != V
.EMPTY
&&
445 this.getColor(i
, j
) == color
&&
446 [V
.GOLD_G
, V
.P_PAWN
, V
.P_SILVER
, V
.P_KNIGHT
, V
.P_LANCE
]
447 .includes(this.getPiece(i
, j
))
455 isAttackedBySilver([x
, y
], color
) {
456 const shift
= (color
== 'w' ? 1 : -1);
457 for (let step
of V
.steps
[V
.BISHOP
].concat([[shift
, 0]])) {
458 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
461 this.board
[i
][j
] != V
.EMPTY
&&
462 this.getColor(i
, j
) == color
&&
463 this.getPiece(i
, j
) == V
.SILVER_G
471 isAttackedByPawn([x
, y
], color
) {
472 const shift
= (color
== 'w' ? 1 : -1);
473 const [i
, j
] = [x
+ shift
, y
];
476 this.board
[i
][j
] != V
.EMPTY
&&
477 this.getColor(i
, j
) == color
&&
478 this.getPiece(i
, j
) == V
.PAWN
482 isAttackedByKnight(sq
, color
) {
483 const forward
= (color
== 'w' ? 2 : -2);
484 return this.isAttackedBySlideNJump(
485 sq
, color
, V
.KNIGHT
, [[forward
, 1], [forward
, -1]], "oneStep");
488 isAttackedByLance(sq
, color
) {
489 const forward
= (color
== 'w' ? 1 : -1);
490 return this.isAttackedBySlideNJump(sq
, color
, V
.LANCE
, [[forward
, 0]]);
493 isAttackedByDragon(sq
, color
) {
495 this.isAttackedBySlideNJump(sq
, color
, V
.P_ROOK
, V
.steps
[V
.ROOK
]) ||
496 this.isAttackedBySlideNJump(
497 sq
, color
, V
.P_ROOK
, V
.steps
[V
.BISHOP
], "oneStep")
501 isAttackedByHorse(sq
, color
) {
503 this.isAttackedBySlideNJump(sq
, color
, V
.P_BISHOP
, V
.steps
[V
.BISHOP
]) ||
504 this.isAttackedBySlideNJump(
505 sq
, color
, V
.P_BISHOP
, V
.steps
[V
.ROOK
], "oneStep")
510 let moves
= super.getAllPotentialMoves();
511 const color
= this.turn
;
512 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
513 moves
= moves
.concat(
514 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
517 return this.filterValid(moves
);
521 if (!super.atLeastOneMove()) {
522 // Search one reserve move
523 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
524 let moves
= this.filterValid(
525 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
527 if (moves
.length
> 0) return true;
534 static get P_CORRESPONDANCES() {
545 static MayDecode(piece
) {
546 if (Object
.keys(V
.P_CORRESPONDANCES
).includes(piece
))
547 return V
.P_CORRESPONDANCES
[piece
];
552 super.postPlay(move);
553 const color
= move.appear
[0].c
;
554 if (move.vanish
.length
== 0)
555 // Drop unpromoted piece:
556 this.reserve
[color
][move.appear
[0].p
]--;
557 else if (move.vanish
.length
== 2)
558 // May capture a promoted piece:
559 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]++;
563 super.postUndo(move);
564 const color
= this.turn
;
565 if (move.vanish
.length
== 0)
566 this.reserve
[color
][move.appear
[0].p
]++;
567 else if (move.vanish
.length
== 2)
568 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]--;
571 static get SEARCH_DEPTH() {
575 static get VALUES() {
576 // TODO: very arbitrary and wrong
596 let evaluation
= super.evalPosition();
598 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
599 const p
= V
.RESERVE_PIECES
[i
];
600 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
601 evaluation
-= this.reserve
["b"][p
] * V
.VALUES
[p
];
607 const finalSquare
= V
.CoordsToSquare(move.end
);
608 if (move.vanish
.length
== 0) {
610 const piece
= move.appear
[0].p
.toUpperCase();
611 return (piece
!= 'P' ? piece : "") + "@" + finalSquare
;
613 const piece
= move.vanish
[0].p
.toUpperCase();
615 (piece
!= 'P' || move.vanish
.length
== 2 ? piece : "") +
616 (move.vanish
.length
== 2 ? "x" : "") +
619 move.appear
[0].p
!= move.vanish
[0].p
620 ? "=" + move.appear
[0].p
.toUpperCase()