1 import { ChessRules
, PiPo
, Move
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { sample
, shuffle
} from "@/utils/alea";
5 export class ShogiRules
extends ChessRules
{
7 static get HasFlags() {
11 static get HasEnpassant() {
15 static get Monochrome() {
23 static get Notoodark() {
28 // If current side is under check: lost
29 return this.underCheck(this.turn
);
32 static IsGoodFen(fen
) {
33 if (!ChessRules
.IsGoodFen(fen
)) return false;
34 const fenParsed
= V
.ParseFen(fen
);
36 if (!fenParsed
.reserve
|| !fenParsed
.reserve
.match(/^[0-9]{14,14}$/))
41 static ParseFen(fen
) {
42 const fenParts
= fen
.split(" ");
44 ChessRules
.ParseFen(fen
),
45 { reserve: fenParts
[3] }
49 // pawns, rooks, knights, bishops and king kept from ChessRules
53 static get SILVER_G() {
64 static get P_KNIGHT() {
67 static get P_SILVER() {
70 static get P_LANCE() {
76 static get P_BISHOP() {
99 getPpath(b
, color
, score
, orientation
) {
100 // 'i' for "inversed":
101 const suffix
= (b
[0] == orientation
? "" : "i");
102 return "Shogi/" + b
+ suffix
;
105 getPPpath(m
, orientation
) {
108 m
.appear
[0].c
+ m
.appear
[0].p
,
116 static GenRandInitFen(options
) {
117 if (options
.randomness
== 0) {
119 "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL " +
123 // Randomization following these indications:
124 // http://www.shogi.net/shogi-l/Archive/2007/Nmar16-02.txt
125 let pieces1
= { w: new Array(4), b: new Array(4) };
126 let positions2
= { w: new Array(2), b: new Array(2) };
127 for (let c
of ["w", "b"]) {
128 if (c
== 'b' && options
.randomness
== 1) {
129 pieces1
['b'] = JSON
.parse(JSON
.stringify(pieces1
['w'])).reverse();
131 JSON
.parse(JSON
.stringify(positions2
['w'])).reverse()
135 let positions
= shuffle(ArrayFun
.range(4));
136 const composition
= ['s', 's', 'g', 'g'];
137 for (let i
= 0; i
< 4; i
++) pieces1
[c
][positions
[i
]] = composition
[i
];
138 positions2
[c
] = sample(ArrayFun
.range(9), 2).sort();
143 pieces1
["b"].slice(0, 2).join("") +
145 pieces1
["b"].slice(2, 4).join("") +
149 (positions2
['b'][0] || "") + 'r' +
150 (positions2
['b'][1] - positions2
['b'][0] - 1 || "") + 'b' +
151 (8 - positions2
['b'][1] || "")
153 "/ppppppppp/9/9/9/PPPPPPPPP/" +
155 (positions2
['w'][0] || "") + 'B' +
156 (positions2
['w'][1] - positions2
['w'][0] - 1 || "") + 'R' +
157 (8 - positions2
['w'][1] || "")
161 pieces1
["w"].slice(0, 2).join("").toUpperCase() +
163 pieces1
["w"].slice(2, 4).join("").toUpperCase() +
166 " w 0 00000000000000"
171 return super.getFen() + " " + this.getReserveFen();
175 return super.getFenForRepeat() + "_" + this.getReserveFen();
179 let counts
= new Array(14);
180 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
181 counts
[i
] = this.reserve
["w"][V
.RESERVE_PIECES
[i
]];
182 counts
[7 + i
] = this.reserve
["b"][V
.RESERVE_PIECES
[i
]];
184 return counts
.join("");
187 setOtherVariables(fen
) {
188 super.setOtherVariables(fen
);
189 // Also init reserves (used by the interface to show landable pieces)
191 V
.ParseFen(fen
).reserve
.split("").map(x
=> parseInt(x
, 10));
194 [V
.PAWN
]: reserve
[0],
195 [V
.ROOK
]: reserve
[1],
196 [V
.BISHOP
]: reserve
[2],
197 [V
.GOLD_G
]: reserve
[3],
198 [V
.SILVER_G
]: reserve
[4],
199 [V
.KNIGHT
]: reserve
[5],
200 [V
.LANCE
]: reserve
[6]
203 [V
.PAWN
]: reserve
[7],
204 [V
.ROOK
]: reserve
[8],
205 [V
.BISHOP
]: reserve
[9],
206 [V
.GOLD_G
]: reserve
[10],
207 [V
.SILVER_G
]: reserve
[11],
208 [V
.KNIGHT
]: reserve
[12],
209 [V
.LANCE
]: reserve
[13]
215 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
216 return this.board
[i
][j
].charAt(0);
220 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
221 return this.board
[i
][j
].charAt(1);
225 return { x: 9, y: 9};
228 getReservePpath(index
, color
, orientation
) {
230 "Shogi/" + color
+ V
.RESERVE_PIECES
[index
] +
231 (color
!= orientation
? 'i' : '')
235 // Ordering on reserve pieces
236 static get RESERVE_PIECES() {
238 [V
.PAWN
, V
.ROOK
, V
.BISHOP
, V
.GOLD_G
, V
.SILVER_G
, V
.KNIGHT
, V
.LANCE
]
242 getReserveMoves([x
, y
]) {
243 const color
= this.turn
;
244 const p
= V
.RESERVE_PIECES
[y
];
246 var oppCol
= V
.GetOppCol(color
);
248 [...Array(9).keys()].filter(j
=>
249 [...Array(9).keys()].every(i
=> {
251 this.board
[i
][j
] == V
.EMPTY
||
252 this.getColor(i
, j
) != color
||
253 this.getPiece(i
, j
) != V
.PAWN
258 if (this.reserve
[color
][p
] == 0) return [];
260 const forward
= color
== 'w' ? -1 : 1;
261 const lastRanks
= color
== 'w' ? [0, 1] : [8, 7];
262 for (let i
= 0; i
< V
.size
.x
; i
++) {
264 (i
== lastRanks
[0] && [V
.PAWN
, V
.KNIGHT
, V
.LANCE
].includes(p
)) ||
265 (i
== lastRanks
[1] && p
== V
.KNIGHT
)
269 for (let j
= 0; j
< V
.size
.y
; j
++) {
271 this.board
[i
][j
] == V
.EMPTY
&&
272 (p
!= V
.PAWN
|| allowedFiles
.includes(j
))
284 start: { x: x
, y: y
}, //a bit artificial...
288 // Do not drop on checkmate:
291 this.underCheck(oppCol
) && !this.atLeastOneMove("noReserve")
303 getPotentialMovesFrom([x
, y
]) {
305 // Reserves, outside of board: x == sizeX(+1)
306 return this.getReserveMoves([x
, y
]);
308 switch (this.getPiece(x
, y
)) {
310 return this.getPotentialPawnMoves([x
, y
]);
312 return this.getPotentialRookMoves([x
, y
]);
314 return this.getPotentialKnightMoves([x
, y
]);
316 return this.getPotentialBishopMoves([x
, y
]);
318 return this.getPotentialSilverMoves([x
, y
]);
320 return this.getPotentialLanceMoves([x
, y
]);
322 return super.getPotentialKingMoves([x
, y
]);
324 return this.getPotentialDragonMoves([x
, y
]);
326 return this.getPotentialHorseMoves([x
, y
]);
332 return this.getPotentialGoldMoves([x
, y
]);
334 return []; //never reached
337 // Modified to take promotions into account
338 getSlideNJumpMoves([x
, y
], steps
, options
) {
339 options
= options
|| {};
340 const color
= this.turn
;
341 const oneStep
= options
.oneStep
;
342 const forcePromoteOnLastRank
= options
.force
;
343 const promoteInto
= options
.promote
;
344 const lastRanks
= (color
== 'w' ? [0, 1, 2] : [9, 8, 7]);
346 outerLoop: for (let step
of steps
) {
349 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
350 if (i
!= lastRanks
[0] || !forcePromoteOnLastRank
)
351 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
352 if (!!promoteInto
&& lastRanks
.includes(i
)) {
355 [x
, y
], [i
, j
], { c: color
, p: promoteInto
})
358 if (oneStep
) continue outerLoop
;
362 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
])) {
363 if (i
!= lastRanks
[0] || !forcePromoteOnLastRank
)
364 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
365 if (!!promoteInto
&& lastRanks
.includes(i
)) {
368 [x
, y
], [i
, j
], { c: color
, p: promoteInto
})
376 getPotentialGoldMoves(sq
) {
377 const forward
= (this.turn
== 'w' ? -1 : 1);
378 return this.getSlideNJumpMoves(
380 V
.steps
[V
.ROOK
].concat([ [forward
, 1], [forward
, -1] ]),
385 getPotentialPawnMoves(sq
) {
386 const forward
= (this.turn
== 'w' ? -1 : 1);
388 this.getSlideNJumpMoves(
400 getPotentialSilverMoves(sq
) {
401 const forward
= (this.turn
== 'w' ? -1 : 1);
402 return this.getSlideNJumpMoves(
404 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
412 getPotentialKnightMoves(sq
) {
413 const forward
= (this.turn
== 'w' ? -2 : 2);
414 return this.getSlideNJumpMoves(
416 [ [forward
, 1], [forward
, -1] ],
425 getPotentialLanceMoves(sq
) {
426 const forward
= (this.turn
== 'w' ? -1 : 1);
427 return this.getSlideNJumpMoves(
437 getPotentialRookMoves(sq
) {
438 return this.getSlideNJumpMoves(
439 sq
, V
.steps
[V
.ROOK
], { promote: V
.P_ROOK
});
442 getPotentialBishopMoves(sq
) {
443 return this.getSlideNJumpMoves(
444 sq
, V
.steps
[V
.BISHOP
], { promote: V
.P_BISHOP
});
447 getPotentialDragonMoves(sq
) {
449 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
450 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], { oneStep: true }))
454 getPotentialHorseMoves(sq
) {
456 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
457 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
], { oneStep: true }))
461 isAttacked(sq
, color
) {
463 this.isAttackedByPawn(sq
, color
) ||
464 this.isAttackedByRook(sq
, color
) ||
465 this.isAttackedByDragon(sq
, color
) ||
466 this.isAttackedByKnight(sq
, color
) ||
467 this.isAttackedByBishop(sq
, color
) ||
468 this.isAttackedByHorse(sq
, color
) ||
469 this.isAttackedByLance(sq
, color
) ||
470 this.isAttackedBySilver(sq
, color
) ||
471 this.isAttackedByGold(sq
, color
) ||
472 this.isAttackedByKing(sq
, color
)
476 isAttackedByGold([x
, y
], color
) {
477 const shift
= (color
== 'w' ? 1 : -1);
478 for (let step
of V
.steps
[V
.ROOK
].concat([[shift
, 1], [shift
, -1]])) {
479 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
482 this.board
[i
][j
] != V
.EMPTY
&&
483 this.getColor(i
, j
) == color
&&
484 [V
.GOLD_G
, V
.P_PAWN
, V
.P_SILVER
, V
.P_KNIGHT
, V
.P_LANCE
]
485 .includes(this.getPiece(i
, j
))
493 isAttackedBySilver([x
, y
], color
) {
494 const shift
= (color
== 'w' ? 1 : -1);
495 return this.isAttackedBySlideNJump(
496 sq
, color
, V
.SILVER
, V
.steps
[V
.BISHOP
].concat([ [shift
, 0] ]), 1);
499 isAttackedByPawn([x
, y
], color
) {
500 const shift
= (color
== 'w' ? 1 : -1);
501 return this.isAttackedBySlideNJump(sq
, color
, V
.PAWN
, [ [shift
, 0] ], 1);
504 isAttackedByKnight(sq
, color
) {
505 const forward
= (color
== 'w' ? 2 : -2);
506 return this.isAttackedBySlideNJump(
507 sq
, color
, V
.KNIGHT
, [ [forward
, 1], [forward
, -1] ], 1);
510 isAttackedByLance(sq
, color
) {
511 const forward
= (color
== 'w' ? 1 : -1);
512 return this.isAttackedBySlideNJump(sq
, color
, V
.LANCE
, [[forward
, 0]]);
515 isAttackedByDragon(sq
, color
) {
517 this.isAttackedBySlideNJump(sq
, color
, V
.P_ROOK
, V
.steps
[V
.ROOK
]) ||
518 this.isAttackedBySlideNJump(sq
, color
, V
.P_ROOK
, V
.steps
[V
.BISHOP
], 1)
522 isAttackedByHorse(sq
, color
) {
524 this.isAttackedBySlideNJump(sq
, color
, V
.P_BISHOP
, V
.steps
[V
.BISHOP
]) ||
525 this.isAttackedBySlideNJump(sq
, color
, V
.P_BISHOP
, V
.steps
[V
.ROOK
], 1)
530 let moves
= super.getAllPotentialMoves();
531 const color
= this.turn
;
532 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
533 moves
= moves
.concat(
534 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
537 return this.filterValid(moves
);
540 atLeastOneMove(noReserve
) {
541 if (!super.atLeastOneMove()) {
543 // Search one reserve move
544 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
545 let moves
= this.filterValid(
546 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
548 if (moves
.length
> 0) return true;
556 static get P_CORRESPONDANCES() {
567 static MayDecode(piece
) {
568 if (Object
.keys(V
.P_CORRESPONDANCES
).includes(piece
))
569 return V
.P_CORRESPONDANCES
[piece
];
574 super.postPlay(move);
575 const color
= move.appear
[0].c
;
576 if (move.vanish
.length
== 0)
577 // Drop unpromoted piece:
578 this.reserve
[color
][move.appear
[0].p
]--;
579 else if (move.vanish
.length
== 2)
580 // May capture a promoted piece:
581 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]++;
585 super.postUndo(move);
586 const color
= this.turn
;
587 if (move.vanish
.length
== 0)
588 this.reserve
[color
][move.appear
[0].p
]++;
589 else if (move.vanish
.length
== 2)
590 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]--;
593 static get SEARCH_DEPTH() {
597 static get VALUES() {
598 // TODO: very arbitrary and wrong
618 let evaluation
= super.evalPosition();
620 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
621 const p
= V
.RESERVE_PIECES
[i
];
622 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
623 evaluation
-= this.reserve
["b"][p
] * V
.VALUES
[p
];
629 const finalSquare
= V
.CoordsToSquare(move.end
);
630 if (move.vanish
.length
== 0) {
632 const piece
= move.appear
[0].p
.toUpperCase();
633 return (piece
!= 'P' ? piece : "") + "@" + finalSquare
;
635 const piece
= move.vanish
[0].p
.toUpperCase();
637 (piece
!= 'P' || move.vanish
.length
== 2 ? piece : "") +
638 (move.vanish
.length
== 2 ? "x" : "") +
641 move.appear
[0].p
!= move.vanish
[0].p
642 ? "=" + move.appear
[0].p
.toUpperCase()