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
[6 + 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 const color
= this.turn
;
272 const oneStep
= options
.oneStep
;
273 const forcePromoteOnLastRank
= options
.force
;
274 const promoteInto
= options
.promote
;
275 const lastRanks
= (color
== 'w' ? [0, 1, 2] : [9, 8, 7]);
277 outerLoop: for (let step
of steps
) {
280 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
281 if (i
!= lastRanks
[0] || !forcePromoteOnLastRank
)
282 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
283 if (!!promoteInto
&& lastRanks
.includes(i
)) {
286 [x
, y
], [i
, j
], { c: color
, p: promoteInto
})
289 if (oneStep
) continue outerLoop
;
293 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
])) {
294 if (i
!= lastRanks
[0] || !forcePromoteOnLastRank
)
295 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
296 if (!!promoteInto
&& lastRanks
.includes(i
)) {
299 [x
, y
], [i
, j
], { c: color
, p: promoteInto
})
307 getPotentialGoldMoves(sq
) {
308 const forward
= (this.turn
== 'w' ? -1 : 1);
309 return this.getSlideNJumpMoves(
311 V
.steps
[V
.ROOK
].concat([ [forward
, 1], [forward
, -1] ]),
316 getPotentialPawnMoves(sq
) {
317 const forward
= (this.turn
== 'w' ? -1 : 1);
319 this.getSlideNJumpMoves(
331 getPotentialSilverMoves(sq
) {
332 const forward
= (this.turn
== 'w' ? -1 : 1);
333 return this.getSlideNJumpMoves(
335 V
.steps
[V
.BISHOP
].concat([ [forward
, 0] ]),
343 getPotentialKnightMoves(sq
) {
344 const forward
= (this.turn
== 'w' ? -2 : 2);
345 return this.getSlideNJumpMoves(
347 [ [forward
, 1], [forward
, -1] ],
356 getPotentialRookMoves(sq
) {
357 return this.getSlideNJumpMoves(
358 sq
, V
.steps
[V
.ROOK
], { promote: V
.P_ROOK
});
361 getPotentialBishopMoves(sq
) {
362 return this.getSlideNJumpMoves(
363 sq
, V
.steps
[V
.BISHOP
], { promote: V
.P_BISHOP
});
366 getPotentialLancerMoves(sq
) {
367 const forward
= (this.turn
== 'w' ? -1 : 1);
368 return this.getSlideNJumpMoves(
369 sq
, [[forward
, 0]], { promote: V
.P_LANCER
});
372 getPotentialDragonMoves(sq
) {
374 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
375 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
], { oneStep: true }))
379 getPotentialHorseMoves(sq
) {
381 this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
382 this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
], { oneStep: true }))
386 getPotentialKingMoves(sq
) {
387 return this.getSlideNJumpMoves(
389 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
394 isAttacked(sq
, color
) {
396 this.isAttackedByPawn(sq
, color
) ||
397 this.isAttackedByRook(sq
, color
) ||
398 this.isAttackedByDragon(sq
, color
) ||
399 this.isAttackedByKnight(sq
, color
) ||
400 this.isAttackedByBishop(sq
, color
) ||
401 this.isAttackedByHorse(sq
, color
) ||
402 this.isAttackedByLancer(sq
, color
) ||
403 this.isAttackedBySilver(sq
, color
) ||
404 this.isAttackedByGold(sq
, color
) ||
405 this.isAttackedByKing(sq
, color
)
409 isAttackedByGold([x
, y
], color
) {
410 const shift
= (color
== 'w' ? 1 : -1);
411 for (let step
of V
.steps
[V
.ROOK
].concat([[shift
, 1], [shift
, -1]])) {
412 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
415 this.board
[i
][j
] != V
.EMPTY
&&
416 this.getColor(i
, j
) == color
&&
417 [V
.GOLD_G
, V
.P_PAWN
, V
.P_SILVER
, V
.P_KNIGHT
, V
.P_LANCER
]
418 .includes(this.getPiece(i
, j
))
426 isAttackedBySilver([x
, y
], color
) {
427 const shift
= (color
== 'w' ? 1 : -1);
428 for (let step
of V
.steps
[V
.BISHOP
].concat([[shift
, 0]])) {
429 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
432 this.board
[i
][j
] != V
.EMPTY
&&
433 this.getColor(i
, j
) == color
&&
434 this.getPiece(i
, j
) == V
.SILVER_G
442 isAttackedByPawn([x
, y
], color
) {
443 const shift
= (color
== 'w' ? 1 : -1);
444 const [i
, j
] = [x
+ shift
, y
];
447 this.board
[i
][j
] != V
.EMPTY
&&
448 this.getColor(i
, j
) == color
&&
449 this.getPiece(i
, j
) == V
.PAWN
453 isAttackedByKnight(sq
, color
) {
454 const forward
= (color
== 'w' ? 2 : -2);
455 return this.isAttackedBySlideNJump(
456 sq
, color
, V
.KNIGHT
, [[forward
, 1], [forward
, -1]], "oneStep");
459 isAttackedByLancer(sq
, color
) {
460 const forward
= (color
== 'w' ? 1 : -1);
461 return this.isAttackedBySlideNJump(sq
, color
, V
.LANCER
, [[forward
, 0]]);
464 isAttackedByDragon(sq
, color
) {
466 this.isAttackedBySlideNJump(sq
, color
, V
.P_ROOK
, V
.steps
[V
.ROOK
]) ||
467 this.isAttackedBySlideNJump(
468 sq
, color
, V
.DRAGON
, V
.steps
[V
.BISHOP
], "oneStep")
472 isAttackedByHorse(sq
, color
) {
474 this.isAttackedBySlideNJump(sq
, color
, V
.P_BISHOP
, V
.steps
[V
.BISHOP
]) ||
475 this.isAttackedBySlideNJump(
476 sq
, color
, V
.DRAGON
, V
.steps
[V
.ROOK
], "oneStep")
481 let moves
= super.getAllPotentialMoves();
482 const color
= this.turn
;
483 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
484 moves
= moves
.concat(
485 this.getReserveMoves([V
.size
.x
+ (color
== "w" ? 0 : 1), i
])
488 return this.filterValid(moves
);
492 if (!super.atLeastOneMove()) {
493 // Search one reserve move
494 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
495 let moves
= this.filterValid(
496 this.getReserveMoves([V
.size
.x
+ (this.turn
== "w" ? 0 : 1), i
])
498 if (moves
.length
> 0) return true;
505 static get P_CORRESPONDANCES() {
516 static MayDecode(piece
) {
517 if (Object
.keys(V
.P_CORRESPONDANCES
).includes(piece
))
518 return V
.P_CORRESPONDANCES
[piece
];
523 super.postPlay(move);
524 const color
= move.appear
[0].c
;
525 if (move.vanish
.length
== 0)
526 // Drop unpromoted piece:
527 this.reserve
[color
][move.appear
[0].p
]--;
528 else if (move.vanish
.length
== 2)
529 // May capture a promoted piece:
530 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]++;
534 super.postUndo(move);
535 const color
= this.turn
;
536 if (move.vanish
.length
== 0)
537 this.reserve
[color
][move.appear
[0].p
]++;
538 else if (move.vanish
.length
== 2)
539 this.reserve
[color
][V
.MayDecode(move.vanish
[1].p
)]--;
542 static get SEARCH_DEPTH() {
546 static get VALUES() {
547 // TODO: very arbitrary and wrong
567 let evaluation
= super.evalPosition();
569 for (let i
= 0; i
< V
.RESERVE_PIECES
.length
; i
++) {
570 const p
= V
.RESERVE_PIECES
[i
];
571 evaluation
+= this.reserve
["w"][p
] * V
.VALUES
[p
];
572 evaluation
-= this.reserve
["b"][p
] * V
.VALUES
[p
];
578 const finalSquare
= V
.CoordsToSquare(move.end
);
579 if (move.vanish
.length
== 0) {
581 const piece
= move.appear
[0].p
.toUpperCase();
582 return (piece
!= 'P' ? piece : "") + "@" + finalSquare
;
584 const piece
= move.vanish
[0].p
.toUpperCase();
586 (piece
!= 'P' || move.vanish
.length
== 2 ? piece : "") +
587 (move.vanish
.length
== 2 ? "x" : "") +
590 move.appear
[0].p
!= move.vanish
[0].p
591 ? "=" + move.appear
[0].p
.toUpperCase()