オセロ(リバーシ)で遊ぶ

# <i class='fa fa-braille' /> これはなに
Pythonでオセロ対戦してみます。

## 盤面の初期化と表示
盤面は、サイズ64の1次元配列で持つことにし、値は下記の通りとします。

- 0: 空きマス
- 1: 黒駒(*で表現)
- 2: 白駒(oで表現)

```py3:python3
import numpy as np
def create_board():
    a = np.zeros(64, dtype=int)
    a[27] = a[36] = 1
    a[28] = a[35] = 2
    return a
def print_board(a):
    print('  a b c d e f g h')
    for i in range(8):
        print(i+1, end=' ')
        print(' '.join('.*o'[j] for j in a[i*8:][:8]))

a = create_board()
print_board(a)
>>>
  a b c d e f g h
1 . . . . . . . .
2 . . . . . . . .
3 . . . . . . . .
4 . . . * o . . .
5 . . . o * . . .
6 . . . . . . . .
7 . . . . . . . .
8 . . . . . . . .
```

## 駒を置く
自駒w(1 or 2)を位置p(0～63)に置く関数を作ります。
縦横斜めの8通りは、1次元データなので、現在位置から[-9, -8, -7, -1, 1, 7, 8, 9] ずれた方向を見ます。これは、[-1, 0, 1]と[-8, 0, 8]の組合せで調べます(0と0のペアは除く)。
各方向毎に配列(b)を取り出し、相手の駒かどうか(b==3-w)の0-1を累積でかけると、とれる駒が1で表されるので、合計(sum)をとると、とれる駒数nがわかります。
位置20("e3")に置いてみましょう。

```py3:python3
def put_piece(a, p, w, puton=True, chk=True):
    t, x, y = 0, p%8, p//8
    for di, fi in zip([-1, 0, 1], [x, 7, 7-x]):
        for dj, fj in zip([-8, 0, 8], [y, 7, 7-y]):
            if not di == dj == 0:
                b = a[p+di+dj::di+dj][:min(fi, fj)]
                n = (b==3-w).cumprod().sum()
                if b.size <= n or b[n] != w: n = 0
                t += n
                if puton:
                    b[:n] = w
    if puton:
        if chk: assert(a[p] == 0 and t > 0)
        a[p] = w
    return t
put_piece(a, 20, 1)
print_board(a)
>>>
  a b c d e f g h
1 . . . . . . . .
2 . . . . . . . .
3 . . . . * . . .
4 . . . * * . . .
5 . . . o * . . .
6 . . . . . . . .
7 . . . . . . . .
8 . . . . . . . .
```

## ベストポジションを探す
良い手は、下記のポイントを適当に重みをつけて足して探しましょう。

- たくさんとれる。
- 相手の打つところが少なくなる

```py3:python3
def best(a, w):
    from math import exp
    r, b, c = [], a.copy(), 1+exp(-np.count_nonzero(a)/16)
    for i in range(64):
        if b[i] != 0: continue
        t = put_piece(b, i, w, True, False)
        if t == 0:
            b[i] = 0
            continue
        u = sum(b[j]==0 and put_piece(b, j, 3-w, False) > 0 for j in range(64))
        r.append((t-c*u+np.random.rand()*0.5, i))
        b = a.copy()
    return sorted(r)[-1][1] if r else -1
```

## 対戦してみる
pでパス、qで終了します。

```py3:python3
if __name__ == '__main__':
    a = create_board()
    w = 1
    while np.count_nonzero(a) < 64:
        print_board(a)
        s = input('> ')
        if not s or s=='q': break
        if s != 'p':
            try:
                x, y = ord(s[0])-97, int(s[1])-1
                put_piece(a, x+8*y, w)
            except:
                continue
        p = best(a, 3-w)
        if p >= 0:
            put_piece(a, p, 3-w)
    print_board(a)
    n1, n2 = (a==1).sum(), (a==2).sum()
    print('%d - %d %s' % (n1, n2,
        'You win' if n1 > n2 else
        'You lose' if n1 < n2 else 'Draw'))
>>>
  a b c d e f g h
1 . . . . . . . .
2 . . . . . . . .
3 . . . . . . . .
4 . . . * o . . .
5 . . . o * . . .
6 . . . . . . . .
7 . . . . . . . .
8 . . . . . . . .
> e3
  a b c d e f g h
1 . . . . . . . .
2 . . . . . . . .
3 . . . . * o . .
4 . . . * o . . .
5 . . . o * . . .
6 . . . . . . . .
7 . . . . . . . .
8 . . . . . . . .
> f4
  a b c d e f g h
1 . . . . . . . .
2 . . . . . . . .
3 . . . . * o . .
4 . . . * * o . .
5 . . . o o o . .
6 . . . . . . . .
7 . . . . . . . .
8 . . . . . . . .
> g3
  a b c d e f g h
1 . . . . . . . .
2 . . . . . . . .
3 . . . . * * * .
4 . . o o o o . .
5 . . . o o o . .
6 . . . . . . . .
7 . . . . . . . .
8 . . . . . . . .
> c5
  a b c d e f g h
1 . . . . . . . .
2 . . . . . . . .
3 . . . o * * * .
4 . . o o o o . .
5 . . * o o o . .
6 . . . . . . . .
7 . . . . . . . .
8 . . . . . . . .
> 
```

もう少し先まで読む方がいいですね。

## Docker用意しました

```bash:bash
docker run -it --rm tsutomu7/reversi python reversi.py
```

ブラウザでやるときは、下記のようにしてください。

```bash:bash
firefox http://localhost:8888 &
docker run -it --rm -p 8888:8888 tsutomu7/reversi
```

以上