082d93917c85a438fc271b736a57066b3063aa9c
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 IsGoodFen(fen
) {
15 if (!ChessRules
.IsGoodFen(fen
)) return false;
16 const fenParsed
= V
.ParseFen(fen
);
18 if (!fenParsed
.reserve
|| !fenParsed
.reserve
.match(/^[0-9]{14,14}$/))
23 static ParseFen(fen
) {
24 const fenParts
= fen
.split(" ");
26 ChessRules
.ParseFen(fen
),
27 { reserve: fenParts
[3] }
31 // pawns, rooks, knights, bishops and king kept from ChessRules
35 static get SILVER_G() {
46 static get P_KNIGHT() {
49 static get P_SILVER() {
52 static get P_LANCE() {
58 static get P_BISHOP() {
81 getPpath(b
, color
, score
, orientation
) {
82 // 'i' for "inversed":
83 const suffix
= (b
[0] == orientation
? "" : "i");
84 return "Shogi/" + b
+ suffix
;
87 getPPpath(m
, orientation
) {
90 m
.appear
[0].c
+ m
.appear
[0].p
,
98 static GenRandInitFen(randomness
) {
99 if (randomness
== 0) {
101 "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL " +
105 let pieces
= { w: new Array(9), b: new Array(9) };
106 for (let c
of ["w", "b"]) {
107 if (c
== 'b' && randomness
== 1) {
108 pieces
['b'] = pieces
['w'];
111 let positions
= shuffle(ArrayFun
.range(9));
112 const composition
= ['l', 'l', 'n', 'n', 's', 's', 'g', 'g', 'k'];
113 for (let i
= 0; i
< 9; i
++) pieces
[c
][positions
[i
]] = composition
[i
];
116 pieces
["b"].join("") +
117 "/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/" +
118 pieces
["w"].join("").toUpperCase() +
119 " w 0 00000000000000"
124 return super.getFen() + " " + this.getReserveFen();
128 return super.getFenForRepeat() + "_" + this.getReserveFen();
132 let counts
= new Array(14);
133 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
134 counts
[i
] = this.reserve
["w"][V
.RESERVE_PIECES
[i
]];
135 counts
[7 + i
] = this.reserve
["b"][V
.RESERVE_PIECES
[i
]];
137 return counts
.join("");
140 setOtherVariables(fen
) {
141 super.setOtherVariables(fen
);
142 const fenParsed
= V
.ParseFen(fen
);
143 // Also init reserves (used by the interface to show landable pieces)
146 [V
.PAWN
]: parseInt(fenParsed
.reserve
[0]),
147 [V
.ROOK
]: parseInt(fenParsed
.reserve
[1]),
148 [V
.BISHOP
]: parseInt(fenParsed
.reserve
[2]),
149 [V
.GOLD_G
]: parseInt(fenParsed
.reserve
[3]),
150 [V
.SILVER_G
]: parseInt(fenParsed
.reserve
[4]),
151 [V
.KNIGHT
]: parseInt(fenParsed
.reserve
[5]),
152 [V
.LANCE
]: parseInt(fenParsed
.reserve
[6])
155 [V
.PAWN
]: parseInt(fenParsed
.reserve
[7]),
156 [V
.ROOK
]: parseInt(fenParsed
.reserve
[8]),
157 [V
.BISHOP
]: parseInt(fenParsed
.reserve
[9]),
158 [V
.GOLD_G
]: parseInt(fenParsed
.reserve
[10]),
159 [V
.SILVER_G
]: parseInt(fenParsed
.reserve
[11]),
160 [V
.KNIGHT
]: parseInt(fenParsed
.reserve
[12]),
161 [V
.LANCE
]: parseInt(fenParsed
.reserve
[13])
167 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
168 return this.board
[i
][j
].charAt(0);
172 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
173 return this.board
[i
][j
].charAt(1);
177 return { x: 9, y: 9};
180 getReservePpath(index
, color
, orientation
) {
182 "Shogi/" + color
+ V
.RESERVE_PIECES
[index
] +
183 (color
!= orientation
? 'i' : '')
187 // Ordering on reserve pieces
188 static get RESERVE_PIECES() {
190 [V
.PAWN
, V
.ROOK
, V
.BISHOP
, V
.GOLD_G
, V
.SILVER_G
, V
.KNIGHT
, V
.LANCE
]
194 getReserveMoves([x
, y
]) {
195 const color
= this.turn
;
196 const p
= V
.RESERVE_PIECES
[y
];
198 var oppCol
= V
.GetOppCol(color
);
200 [...Array(9).keys()].filter(j
=>
201 [...Array(9).keys()].every(i
=> {
203 this.board
[i
][j
] == V
.EMPTY
||
204 this.getColor(i
, j
) != color
||
205 this.getPiece(i
, j
) != V
.PAWN
210 if (this.reserve
[color
][p
] == 0) return [];
212 const forward
= color
== 'w' ? -1 : 1;
213 const lastRanks
= color
== 'w' ? [0, 1] : [8, 7];
214 for (let i
= 0; i
< V
.size
.x
; i
++) {
216 (i
== lastRanks
[0] && [V
.PAWN
, V
.KNIGHT
, V
.LANCE
].includes(p
)) ||
217 (i
== lastRanks
[1] && p
== V
.KNIGHT
)
221 for (let j
= 0; j
< V
.size
.y
; j
++) {
223 this.board
[i
][j
] == V
.EMPTY
&&
224 (p
!= V
.PAWN
|| allowedFiles
.includes(j
))
236 start: { x: x
, y: y
}, //a bit artificial...
240 // Do not drop on checkmate:
242 const res
= (this.underCheck(oppCol
) && !this.atLeastOneMove());
253 getPotentialMovesFrom([x
, y
]) {
255 // Reserves, outside of board: x == sizeX(+1)
256 return this.getReserveMoves([x
, y
]);
258 switch (this.getPiece(x
, y
)) {
260 return this.getPotentialPawnMoves([x
, y
]);
262 return this.getPotentialRookMoves([x
, y
]);
264 return this.getPotentialKnightMoves([x
, y
]);
266 return this.getPotentialBishopMoves([x
, y
]);
268 return this.getPotentialSilverMoves([x
, y
]);
270 return this.getPotentialLanceMoves([x
, y
]);
272 return this.getPotentialKingMoves([x
, y
]);
274 return this.getPotentialDragonMoves([x
, y
]);
276 return this.getPotentialHorseMoves([x
, y
]);
282 return this.getPotentialGoldMoves([x
, y
]);
284 return []; //never reached
287 // Modified to take promotions into account
288 getSlideNJumpMoves([x
, y
], steps
, options
) {
289 options
= options
|| {};
290 const color
= this.turn
;
291 const oneStep
= options
.oneStep
;
292 const forcePromoteOnLastRank
= options
.force
;
293 const promoteInto
= options
.promote
;
294 const lastRanks
= (color
== 'w' ? [0, 1, 2] : [9, 8, 7]);
296 outerLoop: for (let step
of steps
) {
299 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
300 if (i
!= lastRanks
[0] || !forcePromoteOnLastRank
)
301 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
302 if (!!promoteInto
&& lastRanks
.includes(i
)) {
305 [x
, y
], [i
, j
], { c: color
, p: promoteInto
})
308 if (oneStep
) continue outerLoop
;
312 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
])) {
313 if (i
!= lastRanks
[0] || !forcePromoteOnLastRank
)
314 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
315 if (!!promoteInto
&& lastRanks
.includes(i
)) {
318 [x
, y
], [i
, j
], { c: color
, p: promoteInto
})
326 getPotentialGoldMoves(sq
) {
327 const forward
= (this.turn
== 'w' ? -1 : 1);
328 return this.getSlideNJumpMoves(
330 V
.steps
[V
.ROOK
].concat([ [forward
, 1], [forward
, -1] ]),
335 getPotentialPawnMoves(sq
) {
336 const forward
= (this.turn
== 'w' ? -1 : 1);
338 this.getSlideNJumpMoves(
350 getPotentialSilverMoves(sq
) {
351 const forward
= (this.turn
== 'w' ? -1 : 1);
352 return this.getSlideNJumpMoves(
354 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
362 getPotentialKnightMoves(sq
) {
363 const forward
= (this.turn
== 'w' ? -2 : 2);
364 return this.getSlideNJumpMoves(
366 [ [forward
, 1], [forward
, -1] ],
375 getPotentialRookMoves(sq
) {
376 return this.getSlideNJumpMoves(
377 sq
, V
.steps
[V
.ROOK
], { promote: V
.P_ROOK
});
380 getPotentialBishopMoves(sq
) {
381 return this.getSlideNJumpMoves(
382 sq
, V
.steps
[V
.BISHOP
], { promote: V
.P_BISHOP
});
385 getPotentialLanceMoves(sq
) {
386 const forward
= (this.turn
== 'w' ? -1 : 1);
387 return this.getSlideNJumpMoves(
388 sq
, [[forward
, 0]], { promote: V
.P_LANCE
});
391 getPotentialDragonMoves(sq
) {
393 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
394 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], { oneStep: true }))
398 getPotentialHorseMoves(sq
) {
400 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
401 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
], { oneStep: true }))
405 getPotentialKingMoves(sq
) {
406 return this.getSlideNJumpMoves(
408 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
413 isAttacked(sq
, color
) {
415 this.isAttackedByPawn(sq
, color
) ||
416 this.isAttackedByRook(sq
, color
) ||
417 this.isAttackedByDragon(sq
, color
) ||
418 this.isAttackedByKnight(sq
, color
) ||
419 this.isAttackedByBishop(sq
, color
) ||
420 this.isAttackedByHorse(sq
, color
) ||
421 this.isAttackedByLance(sq
, color
) ||
422 this.isAttackedBySilver(sq
, color
) ||
423 this.isAttackedByGold(sq
, color
) ||
424 this.isAttackedByKing(sq
, color
)
428 isAttackedByGold([x
, y
], color
) {
429 const shift
= (color
== 'w' ? 1 : -1);
430 for (let step
of V
.steps
[V
.ROOK
].concat([[shift
, 1], [shift
, -1]])) {
431 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
434 this.board
[i
][j
] != V
.EMPTY
&&
435 this.getColor(i
, j
) == color
&&
436 [V
.GOLD_G
, V
.P_PAWN
, V
.P_SILVER
, V
.P_KNIGHT
, V
.P_LANCE
]
437 .includes(this.getPiece(i
, j
))
445 isAttackedBySilver([x
, y
], color
) {
446 const shift
= (color
== 'w' ? 1 : -1);
447 for (let step
of V
.steps
[V
.BISHOP
].concat([[shift
, 0]])) {
448 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
451 this.board
[i
][j
] != V
.EMPTY
&&
452 this.getColor(i
, j
) == color
&&
453 this.getPiece(i
, j
) == V
.SILVER_G
461 isAttackedByPawn([x
, y
], color
) {
462 const shift
= (color
== 'w' ? 1 : -1);
463 const [i
, j
] = [x
+ shift
, y
];
466 this.board
[i
][j
] != V
.EMPTY
&&
467 this.getColor(i
, j
) == color
&&
468 this.getPiece(i
, j
) == V
.PAWN
472 isAttackedByKnight(sq
, color
) {
473 const forward
= (color
== 'w' ? 2 : -2);
474 return this.isAttackedBySlideNJump(
475 sq
, color
, V
.KNIGHT
, [[forward
, 1], [forward
, -1]], "oneStep");
478 isAttackedByLance(sq
, color
) {
479 const forward
= (color
== 'w' ? 1 : -1);
480 return this.isAttackedBySlideNJump(sq
, color
, V
.LANCE
, [[forward
, 0]]);
483 isAttackedByDragon(sq
, color
) {
485 this.isAttackedBySlideNJump(sq
, color
, V
.P_ROOK
, V
.steps
[V
.ROOK
]) ||
486 this.isAttackedBySlideNJump(
487 sq
, color
, V
.P_ROOK
, V
.steps
[V
.BISHOP
], "oneStep")
491 isAttackedByHorse(sq
, color
) {
493 this.isAttackedBySlideNJump(sq
, color
, V
.P_BISHOP
, V
.steps
[V
.BISHOP
]) ||
494 this.isAttackedBySlideNJump(
495 sq
, color
, V
.P_BISHOP
, V
.steps
[V
.ROOK
], "oneStep")
500 let moves
= super.getAllPotentialMoves();
501 const color
= this.turn
;
502 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
503 moves
= moves
.concat(
504 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
507 return this.filterValid(moves
);
511 if (!super.atLeastOneMove()) {
512 // Search one reserve move
513 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
514 let moves
= this.filterValid(
515 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
517 if (moves
.length
> 0) return true;
524 static get P_CORRESPONDANCES() {
535 static MayDecode(piece
) {
536 if (Object
.keys(V
.P_CORRESPONDANCES
).includes(piece
))
537 return V
.P_CORRESPONDANCES
[piece
];
542 super.postPlay(move);
543 const color
= move.appear
[0].c
;
544 if (move.vanish
.length
== 0)
545 // Drop unpromoted piece:
546 this.reserve
[color
][move.appear
[0].p
]--;
547 else if (move.vanish
.length
== 2)
548 // May capture a promoted piece:
549 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]++;
553 super.postUndo(move);
554 const color
= this.turn
;
555 if (move.vanish
.length
== 0)
556 this.reserve
[color
][move.appear
[0].p
]++;
557 else if (move.vanish
.length
== 2)
558 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]--;
561 static get SEARCH_DEPTH() {
565 static get VALUES() {
566 // TODO: very arbitrary and wrong
586 let evaluation
= super.evalPosition();
588 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
589 const p
= V
.RESERVE_PIECES
[i
];
590 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
591 evaluation
-= this.reserve
["b"][p
] * V
.VALUES
[p
];
597 const finalSquare
= V
.CoordsToSquare(move.end
);
598 if (move.vanish
.length
== 0) {
600 const piece
= move.appear
[0].p
.toUpperCase();
601 return (piece
!= 'P' ? piece : "") + "@" + finalSquare
;
603 const piece
= move.vanish
[0].p
.toUpperCase();
605 (piece
!= 'P' || move.vanish
.length
== 2 ? piece : "") +
606 (move.vanish
.length
== 2 ? "x" : "") +
609 move.appear
[0].p
!= move.vanish
[0].p
610 ? "=" + move.appear
[0].p
.toUpperCase()