Tkinter で作成する簡単な 三目並べ

# 初めに
この記事は、一先ず何か、簡単なものでいいから自分で遊べるものを作成してみたいという方むけの記事です。
・・・と書きつつ、私がこれを書いたのは、暇なときに誰も遊び相手がいなかったからなんですけどね（寂しいですねw 皆さんは暇なときに遊び相手いますか？）

誰も遊び相手がいないって？ご安心ください！
三目並べは単純です。一人でできます。ハマれば二重人格作成できます。

それでは始めていきましょう！
一人で。

# 三目並べの基本的なこと
そも、三目並べとはどんなものなのでしょうか？
目を三つ並べるゲーム...面白いかもしれませんが、もう一人人間が必要ですね。
さて、Wikipedia（やっぱ英語は頭良く見えるかしら？）で調べてみましょう。
[三目並べについて](https://ja.wikipedia.org/wiki/%E4%B8%89%E7%9B%AE%E4%B8%A6%E3%81%B9)に記載されています。
説明はしないので、上記のURLより、理解してください。（既に知っとるわいという方を除く）

追記：
三目並べは、両者（今回はあなたの人格Aと人格B）が最善を尽くすと必ず引き分けとなるそうです。
ふと思いましたが、世界の情勢もそんな感じなんですかね？

# 使うもの
- python
- tkinter

モンスターエナジー（コーヒーでも可）

# 主な処理
以下は、適当に作成した処理の流れです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3674822/f7c4fce3-b668-114e-938a-0dbc68fec59c.png)

・・・適当過ぎたかな（汗

# コードの記述
今回のコードは、github.com 上にあげていますので、一先ずコードを見てみたい、コードを書いても上手く動かない等々という方は、[こちら](https://github.com/chi1180/tic-tac-toe/blob/master/code.py)でコードを確認してみてください。

それでは、コードエディタ、カフェイン、.pyファイルを準備しまして、三目並べの作成に取り掛かりましょう！

### 必要なモジュールのインポート
これは非常に簡単。２行で済みますでしょう。
必要なのは、tkinterと、勝敗判定などの結果をユーザに表示するためのメッセージボックスです。
```python:code.py
from tkinter import *
from tkinter.messagebox import *
````
※from "module name" import の後に *（アスタリスク）を付けることで、そのモジュール内で定義されている変数やメソッドをまとめてインポートできます（わ～い）。

### メインクラスの作成
以下な感じ
```python:code.py
class TicTacToe(Frame):
    # code here
````

#### init
def __init__はコンストラクタつまり初期化メソッドですね。
ここに初期設定（例えば変数の作成だったり、UI（ユーザインターフェイス）の作成）を記述します。
```python:code.py
    def __init__(self, master=None):
        super().__init__(master)

        # tkinterウィンドウのタイトルを設定
        self.master.title("tic tac toe game")

        # 先手後手（先手=1、後手=2）
        self.turn = 1
        # 先手後手のマーク（self.turn をもとに割り当てるので、index0に空文字）
        self.turn_mark = [ "", "!", "?" ]
        # 勝者がどちらかのときにself.turn - 1として使う
        self.turn_name = [ "first turn", "second turn" ]

        # ボードのデータを作成する
        self.setBoardData()
        # ボードを作成する
        self.createBoardView()
````

#### setBoardData
ボードの中のマス目のデータを作成します。
クリックされるたびにこのデータを更新して、これをもとにゲームの判定をします。
```python:code.py
    def setBoardData(self):
        # マス目ごとのデータを入れる配列
        self.board = []
        # マスのカウント用変数
        square_num = 0

        for c in range(3):
            self.board.append([])

            for r in range(3):
                square_num += 1

                # マス目の選別用にインクリメントしたsquare_numを入れる
                square = [ square_num, 0 ]
                self.board[c].append(square)
````

self.board は以下のような感じ
[
[ [1, 0], [2, 0], [3, 0] ],
[ [4, 0], [5, 0], [6, 0] ],
[ [7, 0], [8, 0], [9, 0] ]
]
マス目が見えてくるでしょう？

#### createBoardView
ボードを作成します。
といっても、実際にはグリッドスタイルのフレームに、3 x 3 でボタンを表示するようにしています。

```python:code.py
    def createBoardView(self):
        for c in range(3):
            for r in range(3):
                # ボードデータのindex1の値をターンのマークの配列のindexにして
                # ボタンに表示させるテキストにします。
                inner_text = self.turn_mark[self.board[c][r][1]]

                # ボタンがクリックされた時の引数としてボードデータのindex0
                # （個々のマスの選別のための番号）をとっておきます。
                square_num = self.board[c][r][0]

                # ボタンを作成します。
                square = Button(
                    self.master,
                    text=inner_text,
                    command=lambda arug=square_num: self.squareClicked(arug),
                    width=5,
                    height=2,
                    font=", 24")

                # グリッドでの位置を、for分のc, r をもとに設定します。
                square.grid(row = c, column = r)
````

#### squareClicked
引数として、クリックされたボタンを選別するためのsquare_numをとります。
```python:code.py
    def squareClicked(self, square_num):
        for c in range(3):
            for r in range(3):
                # for文のc, rをもとにボードデータを参照し、
                # index0の番号が引数と一致する（クリックされた）ものかを確認します。
                is_arug_square = self.board[c][r][0] == square_num

                if is_arug_square:
                    # クリックされたマスが既に選択されていたものかを確認します。
                    selected = self.board[c][r][1]

                    if selected:
                        # ルール違反であること、その内容をポップアップで表示します。
                        showinfo("against the rules",
                                 "This square has selected!\nYou must select to square that wasn't selected.")
                    else:
                        # for文のc, rで参照したボードデータのindex1にクリックされたときのターンを入れます。
                        self.board[c][r][1] = self.turn

                        # ボードを表示し、先手後手の順番をいじって、勝者がイルカを確認します。
                        self.createBoardView()
                        self.turnToggleChange()
                        self.winerCheck()
````

#### turnToggleChange

ターンの交互をするために、この関数で先手後手をやりくりします。

```python:code.py
    def turnToggleChange(self):
        turn = self.turn

        if turn == 1:
            self.turn = 2
        else:
            self.turn = 1

        # 1 なら 2, 2 なら 1 にする
````

#### winerCheck
勝者が存在するのか、もし存在するならその勝者は誰なのか、を確認します。
（ちょっとミステリアスですね。いいですね。でも今回はあなたの人格Aか人格Bかって話です。）

```python:code.py
    def winerCheck(self):
        # 勝者がいた場合、そのターンの番号（1 or 2）を入れます。
        winer = 0

        # 縦と横のマス目一列が先手のみもしくは後手のみによってすべて選択されているかを確認します。
        # 初期値はみなさん0なので、0ではないということも確認します。
        for c in range(3):
            is_row_selected_by_only_turn =(
                    self.board[c][0][1] == self.board[c][1][1] == self.board[c][2][1] and self.board[c][2][1])
            is_column_selected_by_only_turn = (
                    self.board[0][c][1] == self.board[1][c][1] == self.board[2][c][1] and self.board[2][c][1])

            if is_row_selected_by_only_turn:
                winer = self.board[c][2][1]
                # 勝者が見つかった場合、すべてのチェックは不要なので、処理を終了します。
                break
            elif is_column_selected_by_only_turn:
                winer = self.board[2][c][1]
                break

        # 勝者が見つかれば
        if winer:
            # 誰が勝者かを表示する
            self.showWiner(winer_num=winer)
        else:
            # 斜め方向を確認する
            is_x_right_selected_by_only_turn = (
                    self.board[0][2][1] == self.board[1][1][1] == self.board[2][0][1] and self.board[2][0][1])
            is_x_left_selected_by_only_turn = (
                    self.board[0][0][1] == self.board[1][1][1] == self.board[2][2][1] and self.board[2][2][1])

            if is_x_right_selected_by_only_turn:
                winer = self.board[2][0][1]
            elif is_x_left_selected_by_only_turn:
                winer = self.board[2][2][1]

            if winer:
                self.showWiner(winer_num=winer)
            else:
                # 勝者が存在せず、すべてのマス目が埋まっていることを確認する
                selected_count = 0

                for c in range(3):
                    for r in range(3):
                        is_selected = self.board[c][r][1]

                        # マス目が選択済みであったらインクリメント
                        if is_selected:
                            selected_count += 1

                # すべてのマス目が選択済みでありましたら
                if selected_count == 9:
                    # 引き分けをポップアップにて宣言
                    showinfo("game end", "This game is draw!")
                    # ゲームをリプレイするか聞く関数を実行
                    self.askPlayAgain()
````

#### showWiner
ゲームの書勝者をポップアップで表示します。
```python:code.py
    def showWiner(self, winer_num):
        # 表示する勝者はwiner_num（もといself.turn）をもとに
        message = "This game's winer is " + self.turn_name[winer_num - 1] + "!"
        showinfo("game end", message)
        self.askPlayAgain()
````

#### askPlayAgain
もう一度ゲームをプレイするかをユーザに聞き、その後の処理を行います。
```python:code.py
    def askPlayAgain(self):
        message = "Are you want to play again?"
        # 「はい」か「いいえ」をユーザに選択させます。（trueもしくはfalseが返されます）
        ask_replay = askyesno("replay or exit", message)

        # 「はい」だった場合、初期化メソッドを実行します。
        if ask_replay:
            self.__init__(root)
        else: # やりたくねーよとなっちゃったら、閉じて終了とします。
            self.master.quit()
````

### プログラムの呼び出し
これが最後だ！
※クラスの外に記述してくださいね
```python:code.py
# プログラムを実行していきます。
root = Tk()
frame = TicTacToe(root)
frame.grid()

root.mainloop()
````

# 最後に
以上が、Tkinter で作成する「簡単な三目並べ」でした～。

どうでしたか？
なにともあれこれからですね～二重人格形成は♪

作成したゲームはデスクトップに置きましょう！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3674822/d345ae71-79f1-703d-2b3b-024dca639e7f.png)

これで人生がより楽しくなると素晴らしいですね！
（やけくそですね！w）

最後まで読んでくれてありがとう！
二重人格によって人生が楽しくなるでしょう！