Python3 を子供に教えるためにオセロを作ってみた（３）

# プログラムは動かして動きを見ながら解説を見て学ぶべし
　とにかくプログラムは動かしてナンボです。そこから色々いじってみることで理解が促進される効果もありますので、今日はオセロゲームのソースを少々長いですがまずは公開します。これを PyCharom の新しいプロジェクトとして作ってみることにしましょう。

　PyCharm を起動してファイルメニューから「新規プロジェクト」を作成します。プロジェクト名は**オセロ**とでも付けてください。
![スクリーンショット 2020-05-24 17.06.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265655/c67f0df8-5ee5-a678-7989-7b75b0840c3a.png)

　プロジェクトを作成したら、ファイルメニューから「新規...」を選択して Pythonファイルを選択して、othello_cui.py などと名前を付けて Enterキーを押すと左のプロジェクトペインの中に othello_cui.py が現れます。

![スクリーンショット 2020-05-24 17.07.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265655/e6909671-0e61-8efe-fa57-edaefe5ac73b.png)
![スクリーンショット 2020-05-24 17.08.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265655/4f1d4f84-3e7b-a0f3-e9e4-92fd5d5785e4.png)

そうしたら、othellocui.py に以下のプログラムを全てコピー＆ペーストしてください。
![スクリーンショット 2020-05-24 17.14.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265655/890e99bd-fda1-1b4d-db1d-3c3ef3edad9b.png)

ここまで出来たら CTRL+S （Macの方は command+S）でプロジェクトを保存しましょう。

## オセロゲームのソース

```python
"""
Othello Game
Copyright 2020 (C) by tsFox
"""

class OthelloCls:
    def __init__(self):
        self.ot_bit = list()
        # 初期設定＝盤面にオセロの駒を4つ置く
        for i in range(64):
            if i == 27 or i == 36:
                self.ot_bit.append('○')
            elif i == 28 or i == 35:
                self.ot_bit.append('●')
            else:
                self.ot_bit.append('・')
        self.ot_offdef = '●'                       # どっちのターンか
        self.ot_search = '○'                       # その逆の手
        # 八方向の隣のコマ計算のためにディクショナリを使って
        # 計算テーブルを作っておく（方向がキー、計算値は Tuppleで定義）
        self.ot_direction = dict()                  # 八方向の検索テーブル
        self.ot_direction = { 'ul' : ( -1 , -1 ) ,  # 左斜め上
                              'up' : (  0 , -1 ) ,  # 真上
                              'ur' : ( +1 , -1 ) ,  # 右斜め上
                              'lt' : ( -1 ,  0 ) ,  # 左
                              'rt' : ( +1 ,  0 ) ,  # 右
                              'dl' : ( -1 , +1 ) ,  # 左斜め下
                              'dn' : (  0 , +1 ) ,  # 真下
                              'dr' : ( +1 , +1 ) }  # 右斜め下
        # ひっくり返せると判定した場合の終端位置
        self.ot_lastposX = 0                        # ひっくり返す終端Ｘ
        self.ot_lastposY = 0                        # ひっくり返す終端Ｙ

    # 入力
    def ot_inputXY(self,otstr):
        while True:
            myXY = input(otstr)
            if myXY == "":
                continue
            # コメント欄にチェックの仕方をいくつか追記しました！ '20/5/30
            if myXY.isdigit() and (1 <= int(myXY) <= 8): 
                return int(myXY)
            print("ぶっぶ〜！1〜8を入力してね！")

    # あなたの番です表示
    def ot_yourturn(self):
        print("{} の手です！".format(self.ot_offdef))

    # あなたの番です
    def ot_changeturn(self):
        self.ot_offdef = '●' if self.ot_offdef == '○' else '○'
        self.ot_search = '●' if self.ot_offdef == '○' else '○'

    # 終了かどうか判定する（自分の手が置けないだけの場合はターンを変える）
    def ot_checkendofgame(self):
        # 自分の手を置ける場所がなく、相手の手も置けない
        if 0 > self.ot_canplacemypeace():
            self.ot_changeturn()
            if 0 > self.ot_canplacemypeace():
                bc = myot.ot_bit.count('●')
                wc = myot.ot_bit.count('○')
                if ( bc - wc ) > 0:
                    bws = "●の勝利"
                elif ( bc - wc ) < 0:
                    bws = "○の勝利"
                else:
                    bws = "引き分け"
                print("{}です。お疲れ様でした！".format(bws))
                return 1
            else:
                print("{}を置ける場所がありません。相手のターンに変わります！！".format(self.ot_search))
                return -1
        else:
            return 0

    # 自分の手を置ける場所があるかどうか探す
    def ot_canplacemypeace(self):
        for n in range(64):
            if self.ot_bit[n] != '・':
                continue
            for d in self.ot_direction:
                if 1 == self.ot_next_onepeace(int(n%8)+1,int(n/8)+1, d):
                    return 0
        # 一個も置ける場所がないとここに来る
        return -1

    # オセロの盤面に手を置く
    def ot_place(self, ot_x, ot_y):
        ot_change = False
        for d in self.ot_direction:
            if 1 == self.ot_next_onepeace(ot_x,ot_y,d):
                self.ot_peace(ot_x,ot_y,self.ot_offdef)
                self.ot_changepeace(ot_x,ot_y,d)
                ot_change = True
        # １個も有効な方向が無かった場合
        if not ot_change:
            print("{}を置く事ができませんでした".format(self.ot_offdef))
            return -1
        # 画面を表示して手を相手のターンに変更
        self.ot_display()
        self.ot_changeturn()
        return 0

    # 次なる一手を置く処理（置けない処理も）
    def ot_next_onepeace(self,ot_x,ot_y,ot_dir):
        # 自分の位置から全方位の次の手が逆手で且つ自手の場合
        nx = self.ot_direction[ot_dir][0]
        ny = self.ot_direction[ot_dir][1]

        # そもそも次の一手は置けない判定
        if ( nx < 0 and ot_x == 1 ) or ( ny < 0 and ot_y == 1 ) or ( nx == 1 and ot_x == 8 ) or ( ny == 1 and ot_y == 8 ):
            return -1

        # 隣の1個をゲットする（自分の手から見て左と上はマイナス方向になる）
        nextpeace = self.ot_peace(ot_x+nx,ot_y+ny)

        # 隣の手が・か自手だったら置けないと判定
        if nextpeace == '・' or nextpeace == self.ot_offdef:
            return -1

        # 隣の隣があるか判定
        cx = ot_x+(nx*2)
        cy = ot_y+(ny*2)

        # 隣の隣がなければ置けないと判定（方向が左か上だったら左端、上端を判断）
        if ( nx < 0 and cx == 0 ) or ( nx > 0 and cx == 9 ) or ( ny < 0 and cy == 0 ) or ( ny > 0 and cy == 9 ):
            return -1

        # 次の次がとれるのでそれを探す
        nextnextpeace = self.ot_peace(cx,cy)

        if nextnextpeace == '・' :
            return -1         # ひっくり返せませんね通知

        if nextnextpeace == self.ot_offdef:
            # ひっくり返せる終端位置を記録する
            self.ot_lastposX = cx
            self.ot_lastposY = cy
            return 1         # ひっくり返せるよ通知

        # 自分の手と自分の手が続いたら、自分の関数をもう一回呼ぶ（再帰）
        return self.ot_next_onepeace(ot_x+nx, ot_y+ny, ot_dir)

    # 手をひっくり返す処理
    def ot_changepeace(self,ot_x,ot_y,ot_dir):
        # 自分の位置から全方位の次の手が逆手で且つ自手の場合
        nx = self.ot_direction[ot_dir][0]
        ny = self.ot_direction[ot_dir][1]
        # 隣の1個を書き換える
        nextpeace = self.ot_peace(ot_x+nx,ot_y+ny,self.ot_offdef)
        # 隣の隣の座標
        cx = ot_x+(nx*2)
        cy = ot_y+(ny*2)
        # ひっくり返す位置が最後の座標だったら終了
        if cx == self.ot_lastposX and cy == self.ot_lastposY:
            return
        # まだひっくり返せるようならもう一度自分を呼び出す（再帰）
        return self.ot_changepeace(ot_x+nx, ot_y+ny, ot_dir)

    # 指定位置の手を取得する（ついでに書き換えもする）
    def ot_peace(self,ot_x,ot_y,ot_chr=None):
        if ot_chr != None:
            self.ot_bit[(ot_y - 1) * 8 + (ot_x - 1)] = ot_chr
        return self.ot_bit[(ot_y-1)*8+(ot_x-1)]

    # オセロの盤面を表示する
    def ot_display(self):
        print("Ｘ① ② ③ ④ ⑤ ⑥ ⑦ ⑧")
        for l in range(1,9):
            print("{}".format(l), end='' )
            for c in range(1,9):
                print(" {}".format(self.ot_bit[(l-1)*8+(c-1)]), end='')
            print()
        print("            ○：{}　●：{}".format(self.ot_bit.count('○'),self.ot_bit.count('●')))

if __name__ == '__main__':

    myot = OthelloCls()
    myot.ot_display()

    while True:
        # ゲームが終わったか、自分の手を置く場所があるか判断する
        sts = myot.ot_checkendofgame()
        if sts == 1:            # ゲーム終了
            break
        elif sts < 0:           # 自分の手は置けない
            continue

        # どちらのターンかを表示する
        myot.ot_yourturn()
        # X座標とＹ座標を入力する
        myx = myot.ot_inputXY("X = ")
        myy = myot.ot_inputXY("Y = ")
        # 指定座標に手を置く
        myot.ot_place(myx,myy)
```
## まずは一回遊んでみましょう！！

　PyCharm の実行メニューから「実行 othello_cui」を選択するか、ソース内の `if __name__ == '__main__':` の左端にある緑色の三角の実行ボタンを押すことでプログラムを開始できます。
![スクリーンショット 2020-05-24 17.18.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265655/4f0dcd04-7a0d-0524-3615-55a9953e4501.png)

　PyCharm の画面下部にこんな感じで実行されたプログラムが表示されたら、後は X（横方向）の1〜8を入力、次にY（縦方向）の1〜8を入力して、ご家族の誰かとでもオセロで対戦してみてください。

*[注意]PyCharm の環境設定で使用するフォントを等幅フォントにしないと図のように綺麗にオセロ盤が表示されません。*

![スクリーンショット 2020-05-24 17.22.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265655/a1e0ecf1-87f8-9127-d692-d9474c8ee060.png)

　手を打ったら画面がどうなりますか？そこらへんをジックリ観察しながらゲームを最後まで遊んでみたら、次の回からは詳しくプログラムの中身について解説していきたいと思います。

## 今日の Python のお勉強＝変数
　今回の章では Python の変数というものを学びたいと思います。

　変数とは何でしょうか？「変な数」じゃないんですよ。プログラムで発生する様々な値を格納する入れ物と思えば良いでしょう。例えば料理を作るときに、あらかじめ材料を切っておいてボールに入れておきますが、このボールが変数と同じ役割をします。料理をする方は、いきなり料理の鍋に塩入れて、砂糖入れて、醤油入れてとやらずに、一旦計量カップに塩を小さじ半杯、砂糖を小さじ一杯、醤油を大さじ三杯、などと入れてそこで味見してから鍋に入れたりしますよね。この計量カップを変数だと思ってくれれば間違いありません。

　何故、変数が必要なのか考えてみましょう。レシピを考えてみると解るのですが、3人前のカレーを作るだけのプログラムだったら入れるものは決まっていますし、分量も決まっていますよね？でもカレーを作るプログラムが何人分でも作れるようにしたかったらどうでしょうか？まず人数というのは毎回変わるので、指定された人数を変数に入れなければなりませんね。ジャガイモ等の材料の分量も変わるのでこれも変数に入れなければなりません。実際にはカレーだけ作るプログラムよりも、何でも作れるプログラムが良いとなれば、材料の種類の数も、入れる調味料の数も分量もどんどん変わります。プログラムにとって変数というのは無くてはならないものというのが解るでしょうか？？

　今日はいくつかの変数について説明していきたいと思います。

### 一般的な変数と演算
　一般的な変数は、当然ながら変数の値は書き換え可能です。

　`age = 12` とやったら、age という名前の変数に 12 という値を代入していることになります。この age 12 の人が来年1つ年をとると、`age += 1` と書くことで、age の中の値を＋1することができます。これを**演算**と呼びます。演算するには以下の演算子が利用できます。また演算には優先度があり、()で指定をしない限り、演算子に割り当てられた出現順位と優先順位によって演算が行われます。

|演算子|意味|計算優先度|
|:--:|:---|:-------|
|+ | 足す ＋ | 3|
|- | 引く - | 3 |
|* | かける　| 2 |
|/ |割る　| 2 |
|//|切り捨て割り算|2|
|% |割った余り| 2 | 
|**|べき乗|   1 |

　以下の計算は左から順に優先度の高い順で計算が行われています。

```python
temp = 20 * 3 / 10 + 5 * 3
60 / 10 + 15
6 + 15
21
```

　明示的に () を付けて計算優先度を指定することも出来ます。

```python
temp = 20 * 3 / (10 + 5) * 3
60 / 15 * 3
4 * 3
12
```

### 配列と言う変数
　1つの変数に1つの値だけ入れるというのでも十分足りますが、まとまった数の値を1つの変数に入れたい場合もあります。このような場合は**配列**と呼ばれる変数を使います。例えば7日分の曜日を文字で持っていたい場合は、

```python
WeekDay = [ "Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" , "Saturday" , "Sunday" ]
```

　配列の値にアクセスする場合には、WeekDay[0] などと添字やインデックスと呼ばれる数値を指定してアクセスします。インデックスは一番最初の値にアクセスするには 0 を指定します。月曜日は0、火曜日は1 ... という感じです。

　配列には多次元配列という考え方があります。先の例では文字列が7個入った一次元配列ですが、例えばカレンダーのデータを持ちたい場合は、31個の配列を、12個持った配列を作ればいいので、 `Calendar[12][31]` という二次元配列を作ることもできます。作成するプログラムに応じて多次元配列を使うのも手ですが、あまり多次元すぎても訳わからなくなるので、自分が理解できて、きれいなコードが書けることに重点を置いて利用するのがよいでしょう。

次回は、プログラムの解説をしながら、Python ならではの Tuple や Dictionary についても学んでいきたいと思います。まずはオセロゲームで遊んで見てくださいね！！

c u 

[<<Python3 を子供に教えるためにオセロを作ってみた（２）](https://qiita.com/tsFox/items/51f405e6cb0955b5a613)
　　[Python3 を子供に教えるためにオセロを作ってみた（４）>>](https://qiita.com/tsFox/items/2e683d860406c28fe3e9)