📄
将棋アプリを作りたい #9 - 2歩禁止バリデーション
前回まで
前回までは、将棋のルールというよりは物理的な制約に着目して作業していました。
今回からは、少しずつ「駒の効き」「2歩判定」などの計算が多いロジックを組むことになり、少し苦手な分野に入りました。
そのため、今まで以上に関数の使い方や責務分割が難しくなっているのを感じています。
2歩禁止
const boardSize = boardConfig.boardSize;
export const violateDoublePawn = (board: Board, positionDropped: Position, side: Side): void => {
let pawnsPosList: Position[] = [];
for (let y = 0; y < boardSize; y++) {
const square: ShogiPiece | undefined = board.squares[y]![positionDropped.x];
// 持ち駒を打った側の 歩 のみカウントする
const isCountablePawn = (
square &&
square!.kind === "Pawn" &&
square!.side === side
) || (
y === positionDropped.y
);
if (isCountablePawn) {
pawnsPosList.push({ x: positionDropped.x, y });
if (pawnsPosList.length > 1) {
console.log(`2歩はこの座標で起こっています: ${pawnsPosList.map(pos => JSON.stringify(pos))}`);
throw new ShogiRulesError("DOUBLE_PAWN");
}
};
}
}
バリデーションは以下の仕組みです。
これは、既存のcanDropValidatorに統合し、動いています。
export const moveValidator = {
canDrop: (board: Board, position: Position, piece: ShogiPiece) => {
...
// ここに挿し込み
if (piece.kind === "Pawn") {
ShogiRulesValidator.assertIllegalMove.drop.violateDoublePawn(board, position, piece.side);
}
}
}
テスト
stdout | packages/core/entities/rules/__test__/shogiRules.test.ts > 将棋のルール > 2歩を禁止する
2歩はこの座標で起こっています: {"x":5,"y":4},{"x":5,"y":6}
2歩はこの座標で起こっています: {"x":4,"y":1},{"x":4,"y":6}
2歩はこの座標で起こっています: {"x":0,"y":6},{"x":0,"y":7}
✓ packages/core/entities/rules/__test__/shogiRules.test.ts (1 test) 5ms
✓ 将棋のルール (1)
✓ 2歩を禁止する 4ms
describe("将棋のルール", () => {
it("2歩を禁止する", () => {
const board = new Board(hirateSquares);
const invalidPosList: Position[] = [
{ x: 5, y: 4 },
{ x: 4, y: 1 },
{ x: 0, y: 7 }
];
invalidPosList.forEach(pos => {
expect(
() => board.dropPiece(pos, new ShogiPiece("Sente", "Pawn"))
).toThrow(ShogiRulesError);
});
});
});
まとめ
ここでのBoardはentityであり、Board自体が自律的に「その動きはできるか」を決めています。
現在のBoardを元にバリデーションを行うことで、新しいBoardインスタンスは「ただ作られるだけの存在」に徹することが出来るというのが、現在の設計です。
Boardはルールの詳細を知りませんがバリデーションを呼ぶことは可能です。
そのため、「二歩」以外に「自殺手」「詰み判定」などのルールは順次Boardクラスに追加していく予定です。
Discussion