（初心者向け）Pythonでオセロを作ってみよう

こんにちは！かっきーです！！

今回はPythonでオセロを作りたいと思います。

（追記）(1)randomは必要ありませんでした。申し訳ございません。
必要なライブラリは以下の通りです。
(1) random
(2) numpy

(1)は標準ライブラリと呼ばれ元々Pythonに組み込まれているので、pip installする必要はありません。

(2)のnumpyはインストールしていなければ、

```pip install numpy
```
とコマンドプロンプトに打ち込み、インストールしてみてください。


ライブラリの準備が出来たらメインのオセロプログラムに入りたいと思います。

```python
from numpy import zeros, where, float32

class othello:
    #定数 (-1をかけることで黒から白、白から黒へのひっくり返しを表現できる)
    white = 1.
    black = -1.
    empty = 0.
    edge = 8          # 1辺の個数
    
    #初期化
    def __init__(self):
        self.board = zeros(self.edge**2, dtype=float32)     # 盤面を0(=self.empty)で初期化
        self.board[27] = self.board[36] = self.white           # 初期コマ配置
        self.board[28] = self.board[35] = self.black            # 初期コマ配置
        self.turn = True                             # white:True  black:False
        self.available = []                          # ひっくり返せる場所の座標を保持する配列 

    #ひっくり返す s:置く番号   ex:Trueならひっくり返しを実行, Falseならひっくり返さないで置けるか否か確認するだけ
    def over(self, s, ex=True):
        s = int(s)
        start = s
        my_color = self.white if self.turn else self.black      
        flag = False               # sに置けるか否かフラグ
        for i in range(-1, 2):
            for j in range(-1, 2):
                v = self.edge * i + j       # vは8方向(の中のどれか)への移動を表す
                if v == 0:
                    continue
                s = start
                for k in range(self.edge - 1):
                    s += v                         # 移動量を足す
                    if (s < 0 or s > self.edge**2 - 1 or self.board[s] == self.empty):   # 盤面外 or 空きマスならその方向へはひっくり返せない
                        break
                    if self.board[s] == my_color:                        # 自分の色と一致したら
                        if k > 0:                                               # k=0のときは, 置いた場所(start)と接してるマスなのでひっくり返しは発生しない
                            if ex:          
                                self.board[start + v: s : v] *= -1      # ひっくり返しを実行
                            flag = True                                    # 1枚でもひっくり返せればその場所に置くことができる
                        break
                    if (((s % self.edge == 0 or s % self.edge == self.edge - 1) and abs(v) != self.edge)
                        or ((s // self.edge == 0 or s // self.edge == self.edge - 1) and abs(v) != 1)):
                        break
        if flag and ex:                   # 指定した場所にコマを置けるなら置く
            self.board[start] = my_color
        return flag

    #勝敗判定
    def judge(self):
        n_white = len(where(self.board == self.white)[0])     # 白石の個数
        n_black = len(where(self.board == self.black)[0])      # 黒石の個数
        print("\n", "黒 >> ", n_black, "   白 >> ", n_white, "\n")     # 石の個数を出力
        if n_white < n_black:                   
            print("黒の勝ち")
        elif n_black < n_white:
            print("白の勝ち")
        else:
            print("引き分け")

    #アップデート関数
    def update(self, end=False):
        self.turn ^= True
        self.available = []                                             # self.availableに置くことのできる座標を入れておく
        for i in range(self.edge**2):
            if self.board[i] == self.empty and self.over(i, ex=False):
                self.available.append(i)
        if len(self.available) == 0:           
                return False if end else self.master(end=True)           # 2回連続置けないならゲーム終了
        self._input()
        return True
    
    #入力(CUI)
    def _input(self):
        while True:
            msg = "白のターン" if self.turn else "黒のターン"         
            x, y = map(int, input(msg + "  行列指定 >> ").split())
            if 8 * x + y in self.available:                     # 石が置ける場所が入力されたら
                self.over(8 * x + y)                            # ひっくり返す処理
                break
            print("---Invalid Position---")                    # 石が置けない場所が入力されたら警告メッセージ

    #盤面描画(CUI)
    def draw(self):
        edge = self.edge
        print("\n    0  1  2  3  4  5  6  7")
        for i in range(edge):
            temp = []
            temp.append(str(int(i)))
            for el in self.board[edge * i:edge * (i + 1)]:
                if el == self.white:
                    temp.append("○")
                elif el == self.black:
                    temp.append("●")
                else:
                    temp.append("＋")
            print(" ".join(temp))            

#メイン関数
def main():
    s = othello()                # オセロインスタンス生成
    flag = True                  # ゲームが続いてるか否かのフラグ (True:続行中, False:終了)
    while flag:
        s.draw()                  # 盤面描画
        flag = s.update()     # アップデート関数
    s.judge()                     #ゲームが終了したら勝敗判定

if __name__ == "__main__":
    main()
```

（注意点）
(1) overメソッドにおいてキーワード引数exによって、実際にコマをひっくり返すのか否かを判断しています。ひっくり返さなくていいときというのは、事前にどこにコマを置けるのかを探索するときです。

(2) overメソッドにおいて盤面の座標は行列形式ではなく、self.board上のインデックスで表現しています。なので行列とインデックスの変換に注意してください。例えば、i行j列のインデックスは、i * self.edge + j となります。

このプログラムをコピペして実行してみてください。
何か不具合等ありましたらお気軽にご連絡ください。