LoginSignup
2

More than 3 years have passed since last update.

Reactのチュートリアルの三目並べをTkinterでやってみる

Posted at

今度はPythonで・・・

またまたくどいですが三目並べチュートリアルを勉強も兼ねてPythonでやってみます。
GUIのアプリを作成する場合に、Tkinterを使用するのが一般的らしいです。こちらを使用していきます。

開発環境はVSCodeで、Pythonのインストールは以前の記事で行いました。

Tkinterのインストール

ターミナル上で以下を実行します。

python -m tkinter

マス目に数値を表示させる

こちらのHello worldを参考に、メイン処理を記述します。

マス目にはラベルWidget?を使用しました。
ttkのウィジェットが主流らしいです多分。
今回はtkの方のウィジェット使ってます。

tictactoe.py
import tkinter as tk
import tkinter.font as font


class Application(tk.Frame):

    def __init__(self, master=None):
        super().__init__(master)
        master.title('三目並べ')
        master.geometry('600x400')
        self.master = master
        self.pack(anchor=tk.W)
        self.create_squares()

    def create_squares(self):
        labelFont = font.Font(size=30, weight='bold')
        for i in range(0, 3):
            for j in range(0, 3):
                tblTxt = i * 3 + j
                square = tk.Label(self, text=tblTxt, bg='white', bd=2,
                                  font=labelFont, relief='groove', width=5, height=2)
                square.grid(column=j, row=i)


root = tk.Tk()
Application(master=root)
root.mainloop()

gridのcolumn,rowの指定でマス目を9つ配置しています。

デバッグ
デバッグしたかったので、以下のVSCodeのデバッグマークからlaunch.jsonを作成しました。
image.png

launch.json
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        }
    ]
}

デフォルトのままです。

実行結果
image.png

マス目にX/Oを表示させる

tictactoe.py
import tkinter as tk
import tkinter.font as font


class Application(tk.Frame):

    def __init__(self, master=None):
        super().__init__(master)
        master.title('三目並べ')
        master.geometry('600x400')
        self.master = master
        self.pack(anchor=tk.W)
        self.xIsNext = True
        self.create_squares()

    def create_squares(self):
        labelFont = font.Font(size=30, weight='bold')
        for i in range(0, 3):
            for j in range(0, 3):
                square = tk.Label(self, text='', bg='white', bd=2,
                                  font=labelFont, relief='groove', width=5, height=2)
                square.grid(column=j, row=i)
                square.bind("<1>", self.btnClick)

    def btnClick(self, event):
        label = event.widget
        if label['text'] == '':
            label['text'] = 'X' if self.xIsNext else 'O'
            self.xIsNext = False if self.xIsNext else True


root = tk.Tk()
Application(master=root)
root.mainloop()

作成したsquarebindを使用してイベントを設定しました。
第1引数と第2引数はお決まりのようです。
selfがおそらくApplicationのインスタンス。(自身)
eventがクリックイベント発生時のイベントオブジェクト。

履歴ありの完成までもっていく

なぜこう冗長になるのか・・・

tictactoe.py
import tkinter as tk
import tkinter.font as font


class Application(tk.Frame):

    def __init__(self, master=None):
        super().__init__(master)
        master.title('三目並べ')
        master.geometry('600x400')
        self.master = master
        self.pack()

        self.lFrame = tk.Frame(self)
        self.lFrame.grid(column=0, row=0)
        self.rFrame = tk.Frame(self)
        self.rFrame.grid(column=1, row=0)

        self.squares = []
        self.stepNumber = 0
        self.history = [{'squares': [''] * 9, 'message':'次の手番 :X'}]

        # メッセージ
        self.message = tk.Label(self.rFrame, text='次の手番 :X')
        self.message.pack(anchor=tk.N)

        # 履歴ボタン
        button = tk.Button(self.rFrame, text='Go to game start')
        button.bind('<1>', lambda e, args={'index': 0}: self.histBtnClick(e, args))
        button.pack()
        self.buttons = [button]

        # マス目
        self.create_squares()

        # 勝ちライン
        self.winnerLines = [
            [0, 1, 2],
            [3, 4, 5],
            [6, 7, 8],
            [0, 3, 6],
            [1, 4, 7],
            [2, 5, 8],
            [0, 4, 8],
            [2, 4, 6]
        ]

    def create_squares(self):
        labelFont = font.Font(size=30, weight='bold')
        for i in range(0, 3):
            for j in range(0, 3):
                square = tk.Label(self.lFrame, text='', bg='white', bd=2,
                                  font=labelFont, relief='groove', width=5, height=2)
                square.grid(column=j, row=i)
                square.bind("<1>", lambda e, args={'index': i * 3 + j}: self.sqClick(e, args))
                self.squares.append(square)

    def sqClick(self, event, args):

        # カレント
        current = self.history[self.stepNumber]

        label = event.widget

        if label['text'] == '' and not self.calculateWinner(current['squares']):
            label['text'] = 'X' if self.stepNumber % 2 == 0 else 'O'

            # シャローコピー
            squares = current['squares'].copy()
            squares[args['index']] = label['text']

            # 手番
            message = '次の手番 :' + ('O' if self.stepNumber % 2 == 0 else 'X')
            if self.calculateWinner(squares):
                message = '勝者 :' + label['text']

            for i in range(len(self.history) - (self.stepNumber + 1)):
                self.history.pop()
                button = self.buttons.pop()
                button.destroy()

            self.stepNumber += 1
            self.message['text'] = message
            self.history.append({'squares': squares, 'message': f'{message}'})
            self.createButton(self.stepNumber)

    def createButton(self, index):
        button = tk.Button(self.rFrame, text=f'Go to move #{index}')
        button.bind('<1>', lambda e, args={'index': index}: self.histBtnClick(e, args))
        self.buttons.append(button)
        button.pack()

    def histBtnClick(self, event, args):
        self.stepNumber = args['index']

        history = self.history[self.stepNumber]
        self.message['text'] = history['message']

        # マス目を上書き
        for i, square in enumerate(history['squares']):
            self.squares[i]['text'] = square

    # reactのチュートリアルから拝借
    def calculateWinner(self, squares):
        for line in self.winnerLines:
            if squares[line[0]] != '' \
                    and squares[line[0]] == squares[line[1]] \
                    and squares[line[0]] == squares[line[2]]:
                return squares[line[0]]
        return None


root = tk.Tk()
Application(master=root)
root.mainloop()

マス目の右にボタンと手番のメッセージを配置するために、一番外枠にframeを用意し、その中にさらに左と右でframeを用意しました。

結果
image.png

感想

Python独特のインデントで区別する感じはまだ違和感があります・・・。
レイアウト配置に少し手間取ったので、VisualStudioみたいにデザイナーでGUIを使って配置したいんですけど、Pythonにあるのでしょうか。

機械学習等もやっていきたいので、これからもPythonは触っていこうと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2