指し手表現(Move / Move32 / AperyMove / Ki2Notation)
rsshogi は、用途に応じて 16 ビット指し手(Move)、移動後の駒情報を含む 32 ビット指し手(Move32)、Apery 互換の指し手(AperyMove / AperyMove32)、および KI2 棋譜表記用の 32 ビット構造体(Ki2Notation)を使い分ける。
Move(u16) と AperyMove(u16) のビットレイアウト比較
16 ビット指し手には、YaneuraOu 系で標準的な Move(u16) と、Apery(WCSC26)互換の教師データや外部フォーマット入出力で用いられる AperyMove(u16) の 2 種類がある。
Move (u16) - YaneuraOu 互換フォーマット:
Bit: 15 14 13..7 (7 bits) 6..0 (7 bits)
+-------+------+-------------------------+--------------------+
|Promote| Drop | From (0..80) / | To (0..80) |
| Flag | Flag | Dropped PieceType(1..7) | |
+-------+------+-------------------------+--------------------+
AperyMove (u16) - Apery (WCSC26) 互換フォーマット:
Bit: 15 14 13..7 (7 bits) 6..0 (7 bits)
+-------+------+-------------------------+--------------------+
| 0 |Promot| From (0..80) / | To (0..80) |
|Unused | Flag | Drop Source (81..87) | |
+-------+------+-------------------------+--------------------+
両者のビット割り当ての違いは以下の通りである。
| 項目 | Move(u16) | AperyMove(u16) |
|---|---|---|
ビット 0..=6(7 bits) | 移動先マス to(0..=80) | 移動先マス to(0..=80) |
ビット 7..=13(7 bits) | 盤上移動時は移動元マス from(0..=80)、駒打ち時は打つ PieceType(1..=7) | 盤上移動時は移動元マス from(0..=80)、駒打ち時は 81 + PieceType - 1(81..=87) |
ビット 14(1 bit) | 駒打ちフラグ MOVE_DROP(1 << 14) | 成りフラグ PROMOTE_FLAG(1 << 14) |
ビット 15(1 bit) | 成りフラグ MOVE_PROMOTE(1 << 15) | 未使用(常に 0) |
Move は駒打ち判定をビット 14 の単一ビットテスト(self.0 & MOVE_DROP != 0)で行い、ビット 7..=13 から PieceType(1..=7)をオフセット計算なしで直接取り出せるため、内部の局面更新や合法手生成で最速となる。
一方、AperyMove はビット 7..=13 の数値が 81 以上かどうかで駒打ちを判定する設計であり、既存の Apery 形式バイナリ資産との相互変換のために提供されている。
特殊指し手定数の衝突回避設計
探索エンジンやプロトコル処理では、「指し手なし」「パス(Null Move)」「投了」「入玉宣言勝ち」などの特殊状態を Move(u16) の同じ 16 ビット空間内で表現できると都合がよい。
rsshogi では、これらの特殊定数を (k << 7) + k(すなわち from == to かつ打フラグ 0、成フラグ 0)のビットパターンとして定義している。
| 定数名 | 計算式 | 内部値(10 進) | from / to フィールド | 用途 |
|---|---|---|---|---|
Move::MOVE_NONE | (0 << 7) + 0 | 0 | from = 0, to = 0 | 未初期化または該当手なし。 |
Move::MOVE_NULL | (1 << 7) + 1 | 129 | from = 1, to = 1 | Null Move Pruning 等のパス手。 |
Move::MOVE_RESIGN | (2 << 7) + 2 | 258 | from = 2, to = 2 | 投了。 |
Move::MOVE_WIN | (3 << 7) + 3 | 387 | from = 3, to = 3 | 入玉宣言勝ち。 |
Move::MOVE_END | (4 << 7) + 4 | 516 | from = 4, to = 4 | 終端マーカー。 |
将棋の通常の盤上移動では移動元と移動先が必ず異なる(from != to)ため、ビット 14(駒打ちフラグ)が 0 でかつ from == to となる指し手は合法手および擬似合法手として絶対に生成されない。
したがって、これらの特殊定数は通常の有効な指し手と一切衝突せず、Move::is_normal(self) は from != to または駒打ちフラグが立っているかを検査することで安全に有効手を識別できる。
Move32(u32) と AperyMove32(u32) の拡張ビットレイアウト
32 ビット指し手は、下位 16 ビットに 16 ビット指し手をそのまま保持し、上位 16 ビットに駒情報を付加した形式である。
Move32 (u32):
Bit: 31..21 (11 bits) 20..16 (5 bits) 15..0 (16 bits)
+------------------+------------------------+------------------------+
| 0 (Unused) | Piece After Move (Piece| Move (u16) |
| | 1..=30, or 0 if unset) | |
+------------------+------------------------+------------------------+
AperyMove32 (u32):
Bit: 31..24 (8 bits) 23..20 (4 bits) 19..16 (4 bits) 15..0 (16 bits)
+------------------+------------------------+------------------------+------------------------+
| 0 (Unused) | Captured PieceType | PieceType Before Move | AperyMove (u16) |
| | (0..=14) | (0=Drop, 1..=14=Board) | |
+------------------+------------------------+------------------------+------------------------+
| 項目 | Move32(u32) | AperyMove32(u32) |
|---|---|---|
ビット 0..=15 | Move::raw() | AperyMove::raw() |
ビット 16..=20 | 移動後の盤上駒 Piece(1..=30、未設定時は 0) | ビット 16..=19:移動前の PieceType(駒打ち時は 0)、ビット 20..=23:捕獲した PieceType(0..=14) |
| 上位未使用ビット | ビット 21..=31(常に 0) | ビット 24..=31(常に 0) |
Move32 はビット 16..=20 に移動後の Piece(先後と成り状態を含む 1..=30)を保持する。
これにより、History Heuristic などの指し手オーダリングにおいて 盤面配列[from] を引き直すことなく (piece_after_move, to_sq) のバケットインデックスを即座に算出できる。
complete_move32 と apply_move32 における上位ビットの補完と検証
Move32 は、Move32::from(mv) によって上位ビットが 0(!mv.has_piece_info())のまま構築される場合と、Move32::from_move_with_piece(mv, piece) や Move32List 生成器によって上位ビットが設定済み(mv.has_piece_info())で渡される場合の両方がある。
Position の指し手適用メソッド(apply_move32、try_apply_move32)と内部の補完処理 complete_move32 は、この上位ビットの有無に応じて以下のように動作する。
- 上位ビットが未設定(
!mv.has_piece_info())の場合:Position::complete_move32がself.move32_from_move(mv.to_move())を呼び出し、現在の盤面(self.piece_on(from))と手番から移動後のPieceを算出して上位ビットを埋める。 - 上位ビットが設定済み(
mv.has_piece_info())の場合:complete_move32は与えられたMove32をそのまま維持し、続いてapply_move32_with_gives_check_forの内部で盤上の実際の駒および手番と上位ビットの整合性が厳密に検査される。- 駒打ちのとき:打つ駒種と現在の手番から決まる
Pieceがmv.piece_after_move()と一致するか、持ち駒を所持しているか、移動先が空マスかを検証する。 - 盤上移動のとき:移動元マスに自分の駒が存在するか、移動先に自分の駒や玉がないか、成フラグと成り可能駒種の整合性、および成や不成を適用した結果の
new_pieceがmv.piece_after_move()と完全に一致するかを検証する。
- 駒打ちのとき:打つ駒種と現在の手番から決まる
不整合が検出された場合、try_apply_move32(および try_apply_move32_with_facts)は盤面を変更せずに Err(MoveError::InvalidMove) を返し、apply_move32 は .expect(...) によりパニックする。
これにより、別の局面で作られた不正な Move32 が誤って適用されて内部のビットボードや Zobrist ハッシュが破損することを防いでいる。
指し手の先後反転と左右反転
Move::flip() と Move32::flip() は、移動元と移動先を 180 度回転した指し手を返す。
7g7f は 3c3d となり、Move32 では移動後の駒色も入れ替わる。
Move は色を持たないため、呼び出し側が反転後の手番を管理する。
mirror_file() は段と駒色を保って筋だけを反転し、7g7f を 3g3f に変換する。
駒打ちは駒種を保ち、打ち先を変換する。
成りフラグを保ち、通常手でない値は変更しない。
いずれも元の指し手を書き換えず、2 回適用すると元に戻る。
Python では先後反転を flip_side()、左右反転を mirror_file() として公開する。
使用例は Python の指し手 API を参照する。
Ki2Notation(u32) のビットレイアウト
KI2 形式(「▲7六歩」「△同銀右不成」など)の指し手は、盤面上の他駒との位置関係に依存する相対表記を含む。
rsshogi では、文字列アロケーションを伴わずに KI2 表記情報を保持および比較できるよう、Ki2Notation(u32) に全フィールドをパックしている。
Ki2Notation (u32):
Bit: 31..19 (13b) 18..17 (2b) 16..13 (4b) 12..9 (4b) 8..2 (7b) 1 0
+-------------+-------------+-------------+-------------+---------------+------+------+
| 0 (Unused) |Ki2Promotion | Ki2Suffix | PieceType | Destination | Same | Side |
| | (0..=2) | (0..=11) | (1..=14) | (0 or 1..=81) | Flag | (0/1)|
+-------------+-------------+-------------+-------------+---------------+------+------+
各ビットフィールドの定義は以下の通りである。
| ビット範囲 | フィールド名 | 内容 |
|---|---|---|
Bit 0 | side | 手番(0 = BLACK 先手 ▲、1 = WHITE 後手 △)。 |
Bit 1 | same | 同 表記フラグ(直前の応手と同一マスのとき 1)。 |
Bits 2..=8(7 bits) | destination | same == 1 のときは 0、same == 0 のときは to.to_index() + 1(1..=81)。 |
Bits 9..=12(4 bits) | piece_type | 表記対象の移動前 PieceType(1..=14)。 |
Bits 13..=16(4 bits) | suffix | Ki2Suffix(0: なし、1: 上、2: 引、3: 寄、4: 直、5: 右、6: 左、7: 右上、8: 右引、9: 左上、10: 左引、11: 打)。 |
Bits 17..=18(2 bits) | promotion | Ki2Promotion(0: なし、1: 成、2: 不成)。 |
Bits 19..=31(13 bits) | 未使用 | 常に 0。 |