座標系(Color / File / Rank / Square)
rsshogi の盤面座標系は、Bitboard のビット配置や利きテーブルの参照と直結している。
本ページでは、Color、File、Rank、Square のビット表現と、整数除算を排除した座標変換の実装を解説する。
手番(Color)
Color は #[repr(transparent)] pub struct Color(u8) として定義され、先手(BLACK = 0)と後手(WHITE = 1)の 2 値のみをとる。
配列サイズ定数として Color::COUNT = 2 を持ち、by_color: [Bitboard; Color::COUNT] や hands: [Hand; Color::COUNT] のインデックスにそのまま用いられる。
手番の反転 Color::flip(self) は Color(self.0 ^ 1) の 1 命令(XOR)で実装されている。
分岐を伴わずに相手番のインデックスを計算できるため、局面更新や王手判定のホットパスで多用される。
筋(File)と段(Rank)
File と Rank はともに #[repr(transparent)] の i8 ラッパーであり、0..=8 の 9 値(COUNT = 9)をとる。
将棋の標準表記との対応は以下の通りである。
内部値(raw) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
File(筋) | 1 筋 | 2 筋 | 3 筋 | 4 筋 | 5 筋 | 6 筋 | 7 筋 | 8 筋 | 9 筋 |
Rank(段) | 一段 | 二段 | 三段 | 四段 | 五段 | 六段 | 七段 | 八段 | 九段 |
| USI 文字 | '1' / 'a' | '2' / 'b' | '3' / 'c' | '4' / 'd' | '5' / 'e' | '6' / 'f' | '7' / 'g' | '8' / 'h' | '9' / 'i' |
内部表現に符号付き整数 i8 を採用しているのは、座標の差分計算(df = to_file.raw() - from_file.raw() など)や盤外判定において、アンダークローを気にせず符号付き減算を行えるようにするためである。
また Rank::relative(self, color: Color) は、先手なら self、後手なら Rank(8 - self.0) を返し、敵陣 3 段目以内(relative_rank.raw() <= 2)の成判定や行き所のない駒の判定を先後共通のロジックに統一する。
マス(Square)と File-major 順序
Square は #[repr(transparent)] pub struct Square(i8) として定義され、盤上の 81 マスを 0..=80、無効マス(センチネル)を SQ_NONE = 81 で表す。
定数として Square::COUNT = 81 およびセンチネルを含む配列用の Square::COUNT_WITH_NONE = 82 を提供する。
File と Rank から Square への変換は、以下の File-major(筋優先)順序 で定義される。
#![allow(unused)]
fn main() {
let raw = file.raw() * 9 + rank.raw();
}
盤面上の各マスと内部インデックス 0..=80 の対応は以下のようになる。
9筋 8筋 7筋 6筋 5筋 4筋 3筋 2筋 1筋
(F8) (F7) (F6) (F5) (F4) (F3) (F2) (F1) (F0)
+----+----+----+----+----+----+----+----+----+
一 | 72 | 63 | 54 | 45 | 36 | 27 | 18 | 9 | 0 | (R0)
二 | 73 | 64 | 55 | 46 | 37 | 28 | 19 | 10 | 1 | (R1)
三 | 74 | 65 | 56 | 47 | 38 | 29 | 20 | 11 | 2 | (R2)
四 | 75 | 66 | 57 | 48 | 39 | 30 | 21 | 12 | 3 | (R3)
五 | 76 | 67 | 58 | 49 | 40 | 31 | 22 | 13 | 4 | (R4)
六 | 77 | 68 | 59 | 50 | 41 | 32 | 23 | 14 | 5 | (R5)
七 | 78 | 69 | 60 | 51 | 42 | 33 | 24 | 15 | 6 | (R6)
八 | 79 | 70 | 61 | 52 | 43 | 34 | 25 | 16 | 7 | (R7)
九 | 80 | 71 | 62 | 53 | 44 | 35 | 26 | 17 | 8 | (R8)
+----+----+----+----+----+----+----+----+----+
この配置により、同一の筋に属する 9 マス(一段目〜九段目)は必ず連続した 9 個の整数インデックス(file * 9 .. file * 9 + 9)を占める。
将棋では歩や香車の前進、および二歩の検査がすべて「筋」単位で行われるため、File-major 順序を採用することで縦方向のビット操作と筋単位のマスク処理が最小の命令数で実行できる。
また、盤面の 180 度回転(後手視点への変換)は Square::inverse(self) によって Square(80 - self.0) の減算 1 回で計算され、左右反転は Square::horizontal_flip(self) によって (8 - file) * 9 + rank で計算される。
盤面上の視覚的なマッピング
以下の将棋盤は、基準となる 1 一(0、緑丸)、5 五(40、青丸)、9 九(80、赤丸)の 3 マスをハイライト表示したものである(上部の数字が筋 9..1、右側の漢数字が段 一..九 を表す)。
RAW_TO_FILE / RAW_TO_RANK による除算と剰余の排除
Square から File(sq / 9)や Rank(sq % 9)を逆算する処理は、指し手生成や成判定、利き計算の至る所で呼ばれる。
整数除算や剰余(またはコンパイラが生成する乗算シフト展開)のレイテンシと境界チェックの分岐を完全に排除するため、crates/rsshogi/src/types/square.rs では 256 要素の定数ルックアップテーブルを保持している。
#![allow(unused)]
fn main() {
static RAW_TO_FILE: [i8; 256] = {
let mut table = [0i8; 256];
let mut sq = 0usize;
while sq < Square::COUNT {
table[sq] = (sq / Rank::COUNT) as i8;
sq += 1;
}
table
};
static RAW_TO_RANK: [i8; 256] = {
let mut table = [0i8; 256];
let mut sq = 0usize;
while sq < Square::COUNT {
table[sq] = (sq % Rank::COUNT) as i8;
sq += 1;
}
table
};
}
Square::file(self) と Square::rank(self) は、self.0 as u8 as usize をインデックスとしてこの 256 バイト配列を引く。
u8 からの拡張であるためインデックスは常に 0..=255 に収まり、Rust コンパイラは境界チェック(bounds check)命令を一切出力せずに 1 回のメモリアクセス命令(movsx)へとコンパイルする。
SquareTable<T, N> による型付きテーブル参照
Square をインデックスとする配列(盤面配列や各種の利きテーブル)に対しては、SquareTable<T, const N: usize>([T; N]) が提供されている。
N には通常 Square::COUNT(81)または Square::COUNT_WITH_NONE(82)を指定する。
Index<Square> トレイト実装により、table[sq] の形式で直接アクセスできるほか、SquareTable::get_valid(sq) を用いることで「有効な盤上マス(0..81)であることが呼び出し元で保証されている」場合の get_unchecked 相当の高速参照を安全に記述できる。
これにより、生の usize インデックスを直接扱うことによる筋や段との取り違えバグを防いでいる。