ビットボード
ビットボードは 9 × 9 の盤面にあるマス集合を 81 個のビットで表す Bitboard である。
駒種ごとの配置、先後の占有、利き、ピンの候補を同じ集合演算として扱えるため、合法手生成と局面更新の共通の基盤になる。
Square::raw() の 0..81 がビット位置に対応し、SQ_11 は bit 0、SQ_99 は bit 80 である。
表現の二層
公開される Bitboard の実体は #[repr(C, align(16))] の [u64; 2] であり、サイズは常に 16 バイトである。
前半のワードには bit 0..62、後半のワードには bit 63..80 を詰め、残りのビットは盤外として扱う。
from_parts、from_packed_bits、否定演算は盤外ビットをマスクするため、通常の API を通る値は 81 マスの範囲に保たれる。
packed_bits は連続した bit 0..80 の u128 表現を返す。
内部の raw_bits は後半ワードを bit 64 から置く演算用の表現であり、飛び利きの u128 演算以外に意味を持つ保存形式ではない。
この区別により、盤面座標と連続した 81 ビットの契約を保ったまま、二つの u64 レーンをそのまま演算できる。
使い分け
単一マスは Bitboard::from_square、筋・段・敵陣は file_mask、rank_mask、promotion_zone で生成する。
複数集合の結合、交差、差分はそれぞれ |、&、and_not を使う。
集合を列挙するときは for sq in &bb または pop_lsb を使い、空集合は EMPTY と is_empty で表す。
ビットボードは駒そのものや手番を含まないため、どの集合を表すかは利用側の変数名と Position の不変条件で決まる。
なぜ集合として持つのか
盤面配列は「あるマスに何の駒があるか」を一点ずつ答える表現である。
対してビットボードは「条件を満たす全マス」を一度に答える表現である。
例えば自駒、敵駒、空きマスを集合で持てば、移動先候補は駒の利きと空きマスまたは敵駒の和集合との AND で得られる。
王手判定、遮蔽、ピン、回避手の候補も、まずマス集合を絞ることで局所的な走査に落とせる。
この利点は 81 マスを毎回全走査しないことだけではない。
複数の条件を一つの式へ合成できるため、候補の意味と除外条件を近くに保てることが重要である。
敵玉への利き ∩ 自駒以外の移動先 = 王手候補の移動先
候補の利き ∩ 敵玉の周辺 = 玉を攻撃する候補
配列との分担
Position は各マスの駒を調べるための盤面配列も保持する。
ビットボードだけでは、立っているビットが歩なのか銀なのかを判別できないからである。
局面更新では盤面配列で駒を読み、駒種・色・占有のビットボードを差分更新する。
手生成では駒種別ビットボードから候補の始点を取り、盤面配列で必要な駒情報を補う。
この二重表現は同じ事実を二回持つため、更新経路が両方を同期させることが不変条件になる。
読み方の例
次の式は、ある駒種の移動先から自駒のあるマスを除く。
let destinations = attacks.and_not(position.bitboards().by_color(us));
destinations は合法手そのものではない。
成り、二歩、王手放置、打ち駒などの規則は、後続の手生成と合法性判定が加える。
ビットボードの式は候補集合を高速に作る層であり、将棋規則のすべてを単独で表す層ではない。
この章
- レイアウト は座標と二ワード表現の対応を説明する。
- 基本操作 は集合演算、走査、マスクを説明する。
- 利きの計算 は定数テーブルと占有依存の飛び利きを説明する。
- 飛び利きアルゴリズム比較 は設計選択を比較する。