【図解】オセロのビットボード実装、合法手検出について

巷に出ているビットボードでオセロの実装をするソースコードがマジで理解できず大変な思いをしているので、自分の理解のために図解してみます。今回は特に**合法手の検出**を解説します。
計算は[こちらの記事](https://qiita.com/sensuikan1973/items/459b3e11d91f3cb37e43)のソースコードに準拠しています。
また、ボードとビットの対応については以下の画像を参照してください。
![Bitboard (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/c368b91c-8efe-0746-1dc1-75ac376ba312.png)
この配置にすることで、シフトの方向が直感的になります。
## 合法手の計算
以下のような盤面を考えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/5622e226-4929-0290-dc47-d5efa8fb4086.png)
ここでの手番は黒なので、この盤面から黒が置ける場所を探していきます。
### 番兵を用意する
ここでは、石の右側を見ていくことにします。右側なので、[元記事](https://qiita.com/sensuikan1973/items/459b3e11d91f3cb37e43)での「左右端の番兵」にあたる値を用います。
相手のボードと`0x7e7e7e7e7e7e7e7e`をAND演算した値を用いるのですが、この`0x7e7e7e7e7e7e7e7e`はボード上で見ると、以下のように左右端を除いた全てのマスにビットが立っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/aefee074-a199-3a8d-9152-0e5548bda740.png)

相手のボード(ここでは白)とAND演算をすると、以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/e2e3ad4a-91fa-c73c-712c-825c90fdd8a3.png)
基本的に、AND演算をして残った部分を足すという操作の繰り返しになります。
そのままではシフトしていくと反対側の左右から出てきてしまうことになりますが、左右の端が0であるこのボードを用意することで、端にたどり着いた値は必ず0とAND演算されて消滅することになり、結果としてループすることを防いでいます。
ここでは簡単のため『番兵ボード』と呼ぶことにします。
### 自分のボードをシフトする
冒頭で述べたボードのレイアウトの通りであれば、石の右側を探す場合には右シフトしていきます。
以下の画像では1ビット右にシフトしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/a0c5c48c-06a2-35d8-a4d6-ccceff904df9.png)

先程用意した番兵ボードと重ねるとこのようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/513eb254-f203-cfbe-6944-e26ddc7fcfb3.png)

AND演算を行ったあとのボードを一時変数に入れておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/5d4f85bf-8c22-8d80-3c72-ec499e7592d7.png)

以上の工程を、この1行で行っています。
```python
tmp = 番兵ボード & (自分のボード >> 1)
```

同様の操作を5回繰り返し、一時変数にOR演算で加えていきます。
今回の盤面では、3回で一時変数の値が変化しなくなりました。
![ダウンロード (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/f3164b98-2dd0-a14e-36ae-2fd448fa6206.gif)

```python
tmp |= 番兵ボード & (tmp >> 1)
```
### 空ボードと重ね合わせる
駒を置いていない部分のビットが立っている空ボードを用意します(実際のコードではシフト操作を行う前に用意します)。白と黒のボードをOR演算し、それを反転させることで作ることができます。
![Bitboard (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/5f6ccc95-9412-3de3-642f-957d1b64219d.png)
```python
空ボード = ~(自分のボード | 相手のボード)
```
もう一回一時変数を右シフトして、空ボードとのAND演算を行い、合法手ボード変数に代入します。
![Bitboard (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/e19687bb-4bd2-91e0-e318-11c47be96416.png)
画像右のように、現在の盤面と重ねて見ると、右側の合法手が全て現れていることがわかります。
```python
合法手ボード = 空ボード & (tmp >> 1)
```
※既に他の方向を調べ、合法手ボードに何か値が入っている場合は、`|=`演算子でOR演算をしつつ代入します。
### 全ての方向に対して同じことを行う
当然合法手はすべての方向に現れる可能性があるので、あとの7方向に対しても同じようにシフトによる合法手検出を行います。各方向には当然別の番人がいます。
![Bitboard (13).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310556/34ec55e2-0e47-6a23-0314-0975974ba6d4.png)

各方向においては、以下のようにシフトします。
- **左**: 左に1ビット
- **右**: 右に1ビット
- **上**: 左に8ビット
- **下**: 右に8ビット
- **右上**: 左に7ビット
- **左上**: 左に9ビット
- **右下**: 右に9ビット
- **左下**: 右に7ビット

## まとめ
きちんと調べるまではイメージが湧かなかったのですが、実際に画像化することでどのような動作を行っているのかがなんとなくわかるようになりました。難しいからこそ、ただコピペせず何をやっているのかをきちんと理解した上で実装したいと思います。

## 参考資料
[オセロをビットボードで実装する (Qiita)](https://qiita.com/sensuikan1973/items/459b3e11d91f3cb37e43)
[ビットボード解説 (Speaker Deck)](https://speakerdeck.com/antenna_three/bitutobodojie-shuo)