プログラミング上達講座5：オセロ

プログラミング上達講座の5回目です。

オセロを題材にして
プログラムを考えてみましょう。

解説動画はこちら

[![表示されなかったらすみません](https://img.youtube.com/vi/XxoOHSyyteI/0.jpg)](https://youtu.be/XxoOHSyyteI)


## オセロとは

オセロご存知でしょうか？

![othello.rule5_.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379626/a3d96c7a-2534-ff9f-0f85-54760b9bbae4.gif)

結構昔からあるゲームで
やったことがある人も多いんじゃないかと。

一応のルールは8 x 8 のマスを用いて
白黒の石を置き、相手の石をひっくり返してゆくゲーム。

相手の石を自分の石で挟めば
ひっくり返しが成立し、ひっくり返せないところには置けません。

最終的に石の多い方が勝ちというルールではあります。



### 初級編:ボードデータを作成してみよう

グローバル変数`board`として作成する。

変数`SIZE`を用意しておいて
そのサイズの分だけ サイズ x サイズ のボードを作り
ボードデータは二次元の`リスト型`とする

初期配置として
`WHITE(0)`,`BLACK(1)`を
ボードの真ん中に交互に4つ置く

それ以外は`None`で埋める

![osetth.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379626/2bd16c3e-4c96-a52d-f75d-219e697669db.png)

### 中級編:石を置いたらひっくり返せるかどうかを判定する関数を考える

石を置いたらひっくり返せるかどうかを判定する
関数`flippable`を作成しよう。

置く場所には石がなくて、石は置けるものとする。

```python
def flippable(y , x , player):
    処理
    return True or False
```

引数`player` は WHITE(0) or BLACK(1)が入る
`y` , `x` は縦 , 横方向の座標(0 - 7)が入るものとする

### 上級編:石を置く処理を行う関数を考える

石を置く処理を行う関数
`flip_stone`を作成しよう。

1.おく場所に石があるかどうかを判定する。
石があったら置けないのでFalseを返す。

2.置いたらひっくり返せるかを判定する。
返せる石がなければ置けないのでFalseを返す。

3.石を置いてひっくり返す
相手の石をひっくり返して
ボードの状態を変更してTrueを返す。

```python
def flip_stone(y ,  x , player):
    処理
    return True or False
```

ボードデータはグローバル変数の
`board`として考えてみましょう。


## 解答編

### 初級編の解答

オセロの盤面のデータ構造を考えます。
二次元のリストを作れば良いです。

```python
# 初期設定
SIZE,WHITE,BLACK = 8,0,1
 
# ボードを作成
board = [[None for i1 in range(SIZE)] for i2 in range(SIZE)] # サイズ分の大きさのボードを作成
board[3][3],board[3][4],board[4][3],board[4][4] = WHITE,BLACK,BLACK,WHITE # 初期配置
 
print(board)
```

```python:変数boardの内容
[[None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, 0, 1, None, None, None],
 [None, None, None, 1, 0, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None]]
```

こんな感じで石は0,1
石がなければNoneで格納すれば良いでしょう。


### 中級編の解答

プログラミングではよく用いられる手法ですが
ここでは「探索」というものが出てきます。

今その場所に石を置いたと考えて
(y,xを置く座標と考える)
8方向に探索に行きます。

8方向に進んだ時に
ひっくり返せる石が
有るかどうかを探します。

8方向の考え方：

今いる位置からどの方向に
進むのか数値で考えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379626/16dc60ed-4c94-aed9-cfda-1d8c5a0b4030.png)

右方向に探索する場合

自分が1の時
置いた場所から次の石が

None → 石が無いので置けない
1,0,0 → 最初が自分と同じなのでひっくり返せない
0,0,0 → 自分と同じ石が無いのでひっくり返せない
0,0,1 → 最初が自分と同じ石でなく
どこかに自分と同い石があるのでひっくり返せる

という感じになります。

あとは最初の石と、最後までの過程を
変数に記録しておけば探索できると思います。

コードはこんな感じになりました。

```python
# ボードに石を置いたら返せるかどうかを判定する関数
def flippable(y ,  x , player):
    enemy = 1 if player==0 else 0
    # 8方向を探索する
    for dy,dx in [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]:
        depth,flg = 1,0
        for i in range(SIZE):
            # 検索対象の座標を設定する
            ry,rx = y + (dy * depth) , x + (dx * depth) 
            # ボードの範囲を超えていたら抜ける
            if any([rx<=-1,ry<=-1,rx>=SIZE,ry>=SIZE]):
                break
            # 返せる石がなくてNoneだったら抜ける
            if board[ry][rx] is None:
                break
            # 返せる石がなくてplayerと同じ石だったら抜ける
            elif flg==0 and board[ry][rx]==player:
                break
            # 返せる石が初めて見つかったらflg=1にする
            elif flg==0 and board[ry][rx]==enemy:
                flg = 1
            # flg=1の状態でplayerと同じ石が見つかったら処理を抜ける
            elif flg==1 and board[ry][rx]==player:
                return True
            depth += 1
    return False
```

```python
# ボードを作成
board = [[None for i1 in range(SIZE)] for i2 in range(SIZE)] # サイズ分の大きさのボードを作成
board[3][3],board[3][4],board[4][3],board[4][4] = WHITE,WHITE,BLACK,WHITE # 初期配置
flippable(2 ,  5 , 1)
```
True

　	　	　	　	　	　
### 上級編の解答

考え方としてはまずはボードが空いていないなら
Falseを返します。

置けるかどうかは中級編のflippableを再利用し

ひっくり返せる所を探索して、その座標を記録しておき
最後に石をひっくり返す処理を行います。

コードはこんな感じになりました。

```python
# 石をひっくり返す
def flip_stone(y ,  x , player):
    # ボードが空いていなければ処理を抜ける
    if board[y][x] is not None:
        return False
     
    # 置いたらひっくり返せるかを判定する
    if not flippable(y ,  x , player):
        # ひっくり返せないなら置けない
        return False
     
    # ひっくり返せるなら、石をひっくり返してボードを更新する
    else:
        enemy = 1 if player==0 else 0
        result = [(y ,  x)]
        for dy,dx in [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]:
            depth,flg = 1,0
            tmp = []
            for i in range(SIZE):
                # 検索対象の座標を設定する
                ry,rx = y + (dy * depth) , x + (dx * depth) 
                # ボードの範囲を超えていたら抜ける
                if any([rx<=-1,ry<=-1,rx>=SIZE,ry>=SIZE]):
                    break
                # 返せる石がなくてNoneだったら抜ける
                if board[ry][rx] is None:
                    break
                # 返せる石がなくてplayerと同じ石だったら抜ける
                elif flg==0 and board[ry][rx]==player:
                    break
                # 返せる石が初めて見つかったらflg=1にする
                elif flg==0 and board[ry][rx]==enemy:
                    flg = 1
                    # 返せる座標を追加する
                    tmp.append((ry,rx))
                # 返せる石が初めて見つかったら返せる座標を追加する
                elif flg==1 and board[ry][rx]==enemy:
                    tmp.append((ry,rx))
                # flg=1の状態でplayerと同じ石が見つかったらresultに追加する
                elif flg==1 and board[ry][rx]==player:
                    result += tmp
                    break
                depth += 1
         
        # 石をひっくり返す
        for y2,x2 in result:
            board[y2][x2]=player
        return True
```

さてこれでオセロゲームの用意が出来ました。
描画用の関数なども用意して
オセロゲームを試してみましょう。

```python
# ゲームを試してみる
 
# ボードを作成
board = [[None for i1 in range(SIZE)] for i2 in range(SIZE)] # サイズ分の大きさのボードを作成
board[3][3],board[3][4],board[4][3],board[4][4] = WHITE,BLACK,BLACK,WHITE # 初期配置
 
# ボードの状態を描画する
def print_board():
    print('',end='\t')
    for x0 in range(8):
        print('X:{0}'.format(x0),end='\t')
    print()
    for y1,b in enumerate(board):
        print('Y:{0}'.format(y1),end='\t')
        for x1,s in enumerate(b):
            print(' ' if s is None else s,end='\t')
        print()
 
# 初期配置を確認
print_board()
```
<img width="322" alt="スクリーンショット 2020-08-09 16.15.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379626/a9eea295-6bd2-43c0-d899-d8d6fe93b96e.png">


```python
print(flip_stone(2 , 3 , 1) )
 
# 更新後の配置
print_board()
```

<img width="321" alt="スクリーンショット 2020-08-09 16.15.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379626/d9eb0023-043a-30f7-5690-83876c42405d.png">


## まとめ

自分一人でやるのは寂しいので
このあと、コードを加えて
オセロゲームっぽく仕上げて
AIなんかも搭載してあげれば
立派なオセロゲームになると思います。

プログラミングの基本的な考え方を
身につけるにはちょうど良い題材だと思いますので
暇な方は試してみてください。


過去にはこんなオセロも
作っていましたね。

[三つ巴対決盛り上がる説を検証するためにオセロゲーム作ってみた](http://www.otupy.net/archives/31442095.html)

今回はこれまでです。



それでは。

## 作者の情報

乙pyのHP：
http://www.otupy.net/

Youtube：
https://www.youtube.com/channel/UCaT7xpeq8n1G_HcJKKSOXMw

Twitter：
https://twitter.com/otupython