<gh_stars>0
import tkinter as tk
import tkinter as ttk
# from tkinter import font as tkfont
from PIL import Image, ImageTk
import random
import copy
import math

# 定数

first_col = "#EE4444"
first_col_light = "#EE9999"
second_col = "#4444EE"
second_col_light = "#9999EE"

C_DELAY = 400


# ユーティリティ {{{

def from_rgb(col):
    return "#%02x%02x%02x" % col


def random_color():
    return (
            random.randrange(256),
            random.randrange(256),
            random.randrange(256)
            )


def my_polygon(canvas, pos, pos_list, *args, rotate=0, **kwargs):
    n = len(pos_list)

    pos_list = copy.deepcopy(pos_list)

    pos_list = list(map(lambda el: (
        pos[0] + (el[0] * math.cos(rotate) - el[1] * math.sin(rotate)),
        pos[1] + (el[0] * math.sin(rotate) + el[1] * math.cos(rotate))
        ), pos_list))
    for i in range(n):
        canvas.create_line(pos_list[i], pos_list[(i + 1) % n], *args, **kwargs)


def writePlayer(canvas, who, x, y, *, small=False):
    names = ["first", "second"]
    marks = ["o", "x"]
    cols = [first_col, second_col]
    if small:
        canvas.create_text(
                x, y,
                text=names[who], font=("Molot", 18))
        canvas.create_text(
                x + 45 if who == 0 else x + 53, y-2,
                text=marks[who], fill=cols[who], font=("Molot", 24))
    else:
        canvas.create_text(
                x, y,
                text=names[who], font=("Molot", 26))
        canvas.create_text(
                x + 65 if who == 0 else x + 77, y,
                text=marks[who], fill=cols[who], font=("Molot", 35))

# }}}


# クラス {{{

class Game:
    def __init__(self):
        self.board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

    def get(self, i, j):
        assert(0 <= i < 3)
        assert(0 <= j < 3)
        return self.board[i][j]

    def set(self, i, j, k):
        assert(0 <= i < 3)
        assert(0 <= j < 3)
        assert(0 <= k <= 2)
        self.board[i][j] = k
        return self.check()

    def check(self):
        board = self.board
        for i in range(3):
            if board[i][0] == board[i][1] == board[i][2] != 0:
                return board[i][0]
        for i in range(3):
            if board[0][i] == board[1][i] == board[2][i] != 0:
                return board[0][i]
        if board[0][0] == board[1][1] == board[2][2] != 0:
            return board[0][0]
        if board[0][2] == board[1][1] == board[2][0] != 0:
            return board[0][2]
        if self.countNonZero() == 9:
            return -1
        return 0

    def countNonZero(self):
        cnt = 0
        for i in range(3):
            for j in range(3):
                if self.board[i][j]:
                    cnt += 1
        return cnt

    # こういうのはデバッグ用に残しておく
    def output(self):
        print("-------")
        for i in range(3):
            print("|", end="")
            for j in range(3):
                if self.get(i, j) == 0:
                    print(" ", end="")
                elif self.get(i, j) == 1:
                    print("o", end="")
                elif self.get(i, j) == 2:
                    print("x", end="")
                print("|", end="")
            print()
            print("-------")

    def outputWithNumber(self):
        num = 1
        fromNum = []
        print()
        print("-------")
        for i in range(3):
            print("|", end="")
            for j in range(3):
                if game.get(i, j) == 0:
                    print(num, end="")
                    num += 1
                    fromNum.append((i, j))
                elif game.get(i, j) == 1:
                    print("o", end="")
                elif game.get(i, j) == 2:
                    print("x", end="")
                print("|", end="")
            print()
            print("-------")
        return fromNum

    def __hash__(self):
        hs = 0
        for i in range(3):
            for j in range(3):
                hs += self.board[i][j] * (3 ** (i * 3 + j))
        return hs


class GameAI:
    def __init__(self, func, name):
        self.func = func
        self.name = name

    def think(self, me, game: Game):
        return self.func(me, game)


game = Game()


def think1(me, game):
    while(True):
        select = (random.randrange(3), random.randrange(3))
        if game.get(select[0], select[1]) == 0:
            return select

# }}}


# 計算 {{{

# メモ化再帰 : 本質的な計算量改善

memo = {}


def think_dfs(me, you, game):
    if hash(game) in memo:
        return memo[hash(game)]
    if game.check() != 0:
        memo[hash(game)] = (game.check(), [])
        return memo[hash(game)]

    canWin = []
    canReachDraw = []
    some = []

    for i in range(3):
        for j in range(3):
            if game.get(i, j) == 0:
                game2 = copy.deepcopy(game)
                game2.set(i, j, me)
                winner = think_dfs(you, me, game2)[0]
                if me == winner:
                    canWin.append((i, j))
                if winner == -1:
                    canReachDraw.append((i, j))
                some.append((i, j))
    if canWin:
        memo[hash(game)] = (me, canWin)
    elif canReachDraw:
        memo[hash(game)] = (-1, canReachDraw)
    else:
        memo[hash(game)] = (you, some)
    return memo[hash(game)]


# メモを予め埋めておく
think_dfs(1, 2, Game())
think_dfs(2, 1, Game())


def think2(me, game):
    if random.randrange(9) < 3:
        return think3(me, game)
    else:
        return think1(me, game)


def think3(me, game, *, noResign=False):
    (winner, candi) = think_dfs(me, 3 - me, copy.deepcopy(game))
    if winner == 3 - me and not noResign:
        return None
    assert(len(candi) != 0)
    return random.choice(candi)


# }}}


# アプリメイン {{{

class TicTacToe(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.width = 500
        self.height = 500

        self.title("Tic Tac Toe")
        self.geometry(f"{self.width}x{self.height}")

        # メインのフレーム
        container = ttk.Frame(self)
        container.pack(side="top", fill="both", expand=True)

        # grid_[row/column]configure(index, ...)
        # これは, weightによる重み付けを行ったグリッド表示をする
        # https://effbot.org/tkinterbook/grid.htm#Tkinter.Grid.grid_rowconfigure-method
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        pages = [TitleScene, LevelSelect, Main]  # TODO
        self.frames = {}
        for page in pages:
            page_name = page.__name__
            frame = page(parent=container, controller=self)
            self.frames[page_name] = frame

            # グリッドマネージャーにより, フレームを中央に置く
            # https://effbot.org/tkinterbook/grid.htm
            frame.grid(row=0, column=0, sticky="nsew")
            # nsew と指定することで中央寄せができる

        self.raise_frame("TitleScene")

    def raise_frame(self, page_name):
        assert(page_name in self.frames.keys())

        # フレームの重なりを一番上に持ってくるようなことをする
        # tkraise : https://kite.com/python/docs/Tkinter.Frame.tkraise
        frame = self.frames[page_name]
        frame.refresh()
        frame.tkraise()

# }}}


# シーンの抽象化 {{{

class Scene(ttk.Frame):
    def __init__(self, *args, parent, controller, **kwargs):
        ttk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent
        self.controller = controller
        self.width = controller.width
        self.height = controller.height

    def refresh(self):
        pass

# }}}


# シーン : タイトル {{{

class TitleScene(Scene):
    def __init__(self, parent, controller):
        Scene.__init__(self, parent=parent, controller=controller)

        canvas = self.canvas = ttk.Canvas(
                self,
                width=500, height=500, bg="black")
        canvas.pack()

        # https://stackoverflow.com/questions/29401873/tkinter-pil-image-not-displaying-inside-of-a-function
        # ものすごい罠. 画像は変数に束縛しておかないと, ガベコレで勝手に消えてしまう
        self.title0_img = ImageTk.PhotoImage(Image.open("title1.png"))
        canvas.create_image(0, 0, image=self.title0_img, anchor="nw")

        self.left_logo = MiniLogo(canvas, "left_logo")

        self.right_logo = MiniLogo(canvas, "right_logo")

        self.counter = 0
        self.after(1000//60, self.update)

        canvas.bind("<1>", lambda ev: controller.raise_frame("LevelSelect"))

    def update(self):

        M = 300
        self.counter += 1
        self.counter %= M

        canvas = self.canvas
        y = self.height - 80 + math.sin(math.pi * 2 * self.counter / M) * 20
        canvas.delete("mes")
        canvas.create_text(
                self.width / 2, y,
                text="touch to start",
                fill="white", tags="mes", font=("Molot", 30))

        self.left_logo.pos = (45, y)
        self.right_logo.pos = (self.width - 45, y)

        self.after(1000//60, self.update)

    def refresh(self):
        pass


# }}}


# 小さな回るロゴ {{{

class MiniLogo:
    def __init__(self, canvas, tag):
        self.canvas = canvas
        self.counter = 0
        self.tag = tag
        self.delay = 1000 // 60
        self.pos = (0, 0)
        canvas.after(self.delay, self.rotateOuter)

    def my_after(self, M, M2, now, nxt):
        canvas = self.canvas
        if self.counter < M:
            canvas.after(self.delay, now)
        elif self.counter < M2:
            canvas.after(self.delay, now)
        else:
            self.counter = 0
            canvas.after(self.delay, nxt)

    # 外側で一回転
    def rotateOuter(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        tag = self.tag
        c = min(self.counter / M, 1)

        canvas.delete(tag)

        for i in range(-1, 2):
            for j in range(-1, 2):
                my_polygon(
                        canvas,
                        (self.pos[0] + i * 12, self.pos[1] + j * 12),
                        [(-6, -6), (-6, 6), (6, 6), (6, -6)],
                        rotate=2*math.pi*3/4*c,
                        fill="white",
                        width=2,
                        capstyle="projecting",  # もはやどこにドキュメントが存在するかわからない
                        tags=tag)

        self.my_after(M, M * 3, self.rotateOuter, self.shrink)

    # 小さくなる
    def shrink(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        tag = self.tag
        c = min(self.counter / M, 1)

        canvas.delete(tag)

        for i in range(-1, 2):
            for j in range(-1, 2):
                r = 6 * (1 - c * .4)
                my_polygon(
                        canvas,
                        (self.pos[0] + i * 12, self.pos[1] + j * 12),
                        [(-r, -r), (-r, r), (r, r), (r, -r)],
                        fill="white",
                        width=2,
                        capstyle="projecting",
                        tags=tag)

        self.my_after(M, M * 3, self.shrink, self.gather)

    # 集まる
    def gather(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        tag = self.tag
        c = min(self.counter / M, 1)

        canvas.delete(tag)

        for i in range(-1, 2):
            for j in range(-1, 2):
                r = 6 * .6
                my_polygon(
                        canvas,
                        (
                            self.pos[0] + i * 12 * (1 - c),
                            self.pos[1] + j * 12 * (1 - c)),
                        [(-r, -r), (-r, r), (r, r), (r, -r)],
                        fill="white",
                        width=2,
                        capstyle="projecting",
                        tags=tag)

        self.my_after(M, M * 3, self.gather, self.rotateInner)

    # 内側で回転
    def rotateInner(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        tag = self.tag
        c = min(self.counter / M, 1)

        canvas.delete(tag)

        r = 6 * .6
        my_polygon(
                canvas,
                self.pos,
                [(-r, -r), (-r, r), (r, r), (r, -r)],
                fill="white",
                rotate=-math.pi*2/4*3*c,
                width=2,
                capstyle="projecting",
                tags=tag)

        self.my_after(M, M * 3, self.rotateInner, self.spread)

    # 広がる
    def spread(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        tag = self.tag
        c = min(self.counter / M, 1)

        canvas.delete(tag)

        for i in range(-1, 2):
            for j in range(-1, 2):
                r = 6 * .6
                my_polygon(
                        canvas,
                        (self.pos[0] + i * 12 * c, self.pos[1] + j * 12 * c),
                        [(-r, -r), (-r, r), (r, r), (r, -r)],
                        fill="white",
                        width=2,
                        capstyle="projecting",
                        tags=tag)

        self.my_after(M, M * 3, self.spread, self.expand)

    # 大きくなる
    def expand(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        c = min(self.counter / M, 1)
        tag = self.tag

        canvas.delete(tag)

        for i in range(-1, 2):
            for j in range(-1, 2):
                r = 6 * (.4 + c * .6)
                my_polygon(
                        canvas,
                        (self.pos[0] + i * 12, self.pos[1] + j * 12),
                        [(-r, -r), (-r, r), (r, r), (r, -r)],
                        fill="white",
                        width=2,
                        capstyle="projecting",
                        tags=tag)

        self.my_after(M, M * 3, self.expand, self.rotateOuter)

# }}}


# シーン : レベルセレクト {{{

class LevelSelect(Scene):
    def __init__(self, *args, **kwargs):
        Scene.__init__(self, *args, **kwargs)

        self.lv1 = LevelSelector(selection, self)
        self.lv1.place(x=30, y=200)
        self.lv2 = LevelSelector(selection, self)
        self.lv2.place(x=260, y=200)

        start_btn = MyButton(
                self,
                text="start",
                width=240, height=60,
                command=self.start)
        start_btn.place(x=120, y=420)

        card1 = Player(self, text="First", mark="o", col=first_col)
        card1.place(x=30, y=100)

        card2 = Player(self, text="Second", mark="x", col=second_col)
        card2.place(x=260, y=100)

        self.lv1.set(0)
        self.lv2.set(1)

    def start(self, ev):
        self.controller.AI1 = self.lv1.getAI()
        self.controller.AI2 = self.lv2.getAI()
        self.controller.Card1 = self.lv1.getCard()
        self.controller.Card2 = self.lv2.getCard()

        self.controller.raise_frame("Main")

    def refresh(self):
        pass

# }}}


# シーン : ゲーム {{{

class Main(Scene):
    def __init__(self, *args, **kwargs):
        Scene.__init__(self, *args, **kwargs)

        vs = ttk.Canvas(self, width=200, height=100)
        vs.create_text(40, 40, text="v", font=("Molot", 23))
        vs.create_text(60, 50, text="s", font=("Molot", 23))
        vs.place(x=197, y=10)

        cells = self.cells = [[None for _ in range(3)] for _ in range(3)]

        for i in range(3):
            for j in range(3):
                # クロージャを作る
                cell = cells[i][j] = Cell(
                        self,
                        width=100, height=100,
                        command=(lambda i, j: lambda ev: self.clicked(i, j))
                        (i, j))
                cell.place(x=90 + 104*i, y=100 + 104*j)
                cell.setMark("o", first_col)

        self.card = [None, None]

        self.resign_btn = MyButton(
                self, text="resign",
                width=140, height=60,
                command=self.resign)
        self.help_btn = MyButton(
                self, text="help",
                width=140, height=60,
                command=self.help)
        self.quit_btn = MyButton(
                self, text="quit",
                width=140, height=60,
                command=self.quit)

        self.message = None

    def human_interface(self, show):
        if show:
            self.resign_btn.place(x=10, y=420)
            self.help_btn.place(x=170, y=420)
            self.quit_btn.place(x=330, y=420)
        else:
            self.resign_btn.place_forget()
            self.help_btn.place_forget()
            self.quit_btn.place_forget()

    def over(self):
        self.message = ttk.Canvas(self, width=300, height=100)
        self.message.place(x=20, y=410)
        self.gameover = True
        self.refrectGame()
        self.human_interface(False)
        self.quit_btn.place(x=330, y=420)

    def draw(self):
        self.over()
        self.message.create_text(140, 40, text="draw", font=("Molot", 30))

    def resigned(self):
        self.over()
        writePlayer(self.message, self.turn, 60, 25, small=True)
        self.message.create_text(190, 25, text="resigned.", font=("Molot", 18))
        writePlayer(self.message, 1 - self.turn, 130, 60)
        self.message.create_text(260, 60, text="win", font=("Molot", 25))

    def playerWin(self):
        self.over()
        writePlayer(self.message, self.turn, 70, 40)
        self.message.create_text(200, 40, text="win", font=("Molot", 25))

    def refresh(self):
        self.game = Game()

        self.AI = [self.controller.AI1, self.controller.AI2]

        for c in self.card:
            if c:
                c.place_forget()

        if self.message:
            self.message.place_forget()

        Card1 = self.controller.Card1
        Card2 = self.controller.Card2

        self.card = [Card1(self), Card2(self)]

        self.card[0].place(x=20, y=10)
        self.card[1].place(x=270, y=25)

        self.is_human = False
        self.gameover = False
        self.turn = 0
        self.turnSetup()

    def refrectGame(self):
        for i in range(3):
            for j in range(3):
                w = self.game.get(i, j)
                self.cells[i][j].disabled = not self.is_human or self.gameover
                if w == 0:
                    self.cells[i][j].setMark(
                            "o" if self.turn == 0 else "x",
                            first_col_light if self.turn == 0
                            else second_col_light)
                    self.cells[i][j].setDetermined(False)
                elif w == 1:
                    self.cells[i][j].setMark("o", first_col)
                    self.cells[i][j].setDetermined(True)
                elif w == 2:
                    self.cells[i][j].setMark("x", second_col)
                    self.cells[i][j].setDetermined(True)

    def turnSetup(self):
        self.is_human = self.AI[self.turn].name == "human"
        self.opps = 1 - self.turn

        self.refrectGame()

        self.card[self.turn].setActive(True)
        self.card[self.opps].setActive(False)

        self.human_interface(self.is_human)

        if not self.is_human:
            self.after(C_DELAY, self.AI_choose)

    def AI_choose(self):
        sel = self.AI[self.turn].think(self.turn + 1, self.game)
        if sel is None:
            self.resigned()
            return
        self.game.set(sel[0], sel[1], self.turn + 1)
        self.next()

    def clicked(self, i, j):
        if self.game.get(i, j) != 0:
            return
        if not self.is_human or self.gameover:
            return
        self.game.set(i, j, self.turn + 1)
        self.next()

    def resign(self, ev):
        if not self.is_human or self.gameover:
            return
        self.resigned()

    def help(self, ev):
        if not self.is_human or self.gameover:
            return
        sel = think3(self.turn, self.game, noResign=True)
        self.game.set(sel[0], sel[1], self.turn + 1)
        self.next()

    def quit(self, ev):
        if not self.is_human and not self.gameover:
            return
        self.controller.raise_frame("TitleScene")

    def next(self):
        ch = self.game.check()
        if ch == 0:
            self.turn = 1 - self.turn
            self.turnSetup()
        elif ch == -1:
            self.draw()
        elif ch == 1 or ch == 2:
            self.playerWin()

# }}}


# ボタン {{{

class MyButton(ttk.Canvas):
    def __init__(
            self, *args, width, height,
            command=None, text="",
            col1=from_rgb((240, 200, 200)),
            col2=from_rgb((240, 160, 160)),
            col3=from_rgb((240, 60, 60)),
            after_moved=None,
            after_leaved=None,
            fontsize=18, **kwargs):
        ttk.Canvas.__init__(self, *args, width=width, height=height, **kwargs)

        self.disabled = False

        self.width = width
        self.height = height

        self.col1 = col1
        self.col2 = col2
        self.col3 = col3

        self.after_moved = after_moved
        self.after_leaved = after_leaved

        if command:
            self.bind("<1>", command)

        self.bind("<1>", self.pressing, add="+")
        self.bind("<ButtonRelease-1>", self.moved)
        self.bind("<Enter>", self.moved)
        self.bind("<Motion>", self.moved)
        self.bind("<Leave>", self.leaved)

        self.leaved(None)

        self.text = self.create_text(
                width/2, height/2,
                text=text, font=("Molot", fontsize))

    def pressing(self, ev, force=False):
        if self.disabled and not force:
            return
        width, height = self.width, self.height
        self.delete("face")
        self.create_rectangle(
                4, 4, width, height,
                fill=self.col3,
                outline=from_rgb((120, 120, 120)), width=4, tags="face"
                )
        self.lower("face")

    def moved(self, ev, force=False):
        if self.disabled and not force:
            return
        width, height = self.width, self.height
        self.delete("face")
        self.create_rectangle(
                4, 4, width, height,
                fill=self.col2,
                outline=from_rgb((120, 120, 120)), width=4, tags="face"
                )
        self.lower("face")
        if self.after_moved:
            self.after_moved()

    def leaved(self, ev, force=False):
        if self.disabled and not force:
            return
        width, height = self.width, self.height
        self.delete("face")
        self.create_rectangle(
                4, 4, width, height,
                fill=self.col1,
                outline=from_rgb((120, 120, 120)), width=4, tags="face"
                )
        self.lower("face")
        if self.after_leaved:
            self.after_leaved()

# }}}


# セル {{{

class Cell(MyButton):
    def __init__(self, *args, **kwargs):
        self.determined = False
        self.mark = ""
        self.col = "black"

        MyButton.__init__(
                self, *args,
                col1="#EEEEEE",
                col2="#DDDDDD",
                col3="#888888",
                after_moved=self.after_moved,
                after_leaved=self.after_leaved,
                **kwargs)

        self.setDetermined(False)

    def setDetermined(self, determined):
        self.determined = determined

        if determined:
            self.delete("mark")
            self.create_text(
                    50, 50,
                    text=self.mark, font=("Molot", 50),
                    tags="mark",
                    fill=self.col)

            self.col1 = "#EEEEEE"
            self.col2 = "#EEEEEE"
            self.col3 = "#EEEEEE"
            self.leaved(None, force=True)
        else:
            self.col1 = "#EEEEEE"
            self.col2 = "#DDDDDD"
            self.col3 = "#888888"
            self.leaved(None, force=True)

    def setMark(self, mark, col):
        self.mark = mark
        self.col = col

    def after_moved(self):
        if self.determined:
            return
        self.delete("mark")
        self.create_text(
                50, 50,
                text=self.mark, font=("Molot", 50),
                tags="mark",
                fill=self.col)

    def after_leaved(self):
        if self.determined:
            return
        self.delete("mark")

# }}}


# レベルカード {{{

class Lv1(ttk.Canvas):
    def __init__(
            self, *args, **kwargs):
        self.width = width = 200
        self.height = height = 60
        ttk.Canvas.__init__(self, *args, width=width, height=height, **kwargs)

        self.setActive(True)

    def setActive(self, active):
        width = self.width
        height = self.height

        self.delete("all")

        self.create_rectangle(
                4, 4, width, height,
                fill=from_rgb((240, 240, 200)) if active
                else "#AAAAAA",
                outline="black",
                width=4 if active else 1,
                tags="face"
                )
        self.text = self.create_text(
                20 + width/2, height/2,
                text="Lv.1", font=("Molot", 22))
        r = 4
        square = [(-r, -r), (-r, r), (r, r), (r, -r)]

        my_polygon(self, (width/2 - 40, height/2), square)


class Lv2(ttk.Canvas):
    def __init__(
            self, *args, **kwargs):
        self.width = width = 200
        self.height = height = 60
        ttk.Canvas.__init__(self, *args, width=width, height=height, **kwargs)

        self.setActive(True)

    def setActive(self, active):
        width = self.width
        height = self.height

        self.delete("all")

        self.create_rectangle(
                4, 4, width, height,
                fill=from_rgb((240, 200, 160)) if active
                else "#AAAAAA",
                outline="black",
                width=4 if active else 1,
                tags="face"
                )
        self.text = self.create_text(
                20 + width/2, height/2,
                text="Lv.2", font=("Molot", 22))
        r = 4
        square = [(-r, -r), (-r, r), (r, r), (r, -r)]

        my_polygon(self, (width/2 - 40, height/2-6), square)
        my_polygon(self, (width/2 - 40 - 8, height/2+8), square)
        my_polygon(self, (width/2 - 40 + 8, height/2+8), square)


class Lv3(ttk.Canvas):
    def __init__(
            self, *args, **kwargs):
        self.width = width = 200
        self.height = height = 60
        ttk.Canvas.__init__(self, *args, width=width, height=height, **kwargs)

        self.setActive(True)

    def setActive(self, active):
        width = self.width
        height = self.height

        self.delete("all")

        self.create_rectangle(
                4, 4, width, height,
                fill=from_rgb((240, 160, 150)) if active
                else "#AAAAAA",
                outline="black",
                width=4 if active else 1,
                tags="face"
                )
        self.text = self.create_text(
                20 + width/2, height/2,
                text="Lv.3", font=("Molot", 22))

        r = 2
        square = [(-r, -r), (-r, r), (r, r), (r, -r)]
        my_polygon(self, (width/2 - 40, height/2+2), square)

        r = 4
        square = [(-r, -r), (-r, r), (r, r), (r, -r)]
        my_polygon(self, (width/2 - 40, height/2-6), square)
        my_polygon(self, (width/2 - 40 - 8, height/2+8), square)
        my_polygon(self, (width/2 - 40 + 8, height/2+8), square)
        self.create_arc(
                width/2 - 45, height/2 - 3,
                width/2 - 35, height/2 + 7,
                extent=359, style="chord")


class HumanLavel(ttk.Canvas):
    def __init__(
            self, *args, **kwargs):
        self.width = width = 200
        self.height = height = 60
        ttk.Canvas.__init__(self, *args, width=width, height=height, **kwargs)

        self.setActive(True)

    def setActive(self, active):
        width = self.width
        height = self.height

        self.delete("all")

        self.create_rectangle(
                4, 4, width, height,
                fill=from_rgb((200, 240, 240)) if active
                else "#AAAAAA",
                outline="black",
                width=4 if active else 1,
                tags="face"
                )
        self.text = self.create_text(
                20 + width/2, height/2,
                text="human", font=("Molot", 22))
        r = 4
        square = [(-r, -r), (-r, r), (r, r), (r, -r)]

        my_polygon(self, (width/2 - 60, height/2 - 10), square)
        self.create_line(
                (width/2 - 60 - 8, height/2 + 8),
                (width/2 - 60, height/2 - 6))
        self.create_line(
                (width/2 - 60 + 8, height/2 + 8),
                (width/2 - 60, height/2 - 6))
        self.create_line(
                (width/2 - 60, height/2 - 6),
                (width/2 - 60, height/2 + 16))
        self.create_line(
                (width/2 - 60, height/2 + 16),
                (width/2 - 60 - 8, height/2 + 16 + 8))
        self.create_line(
                (width/2 - 60, height/2 + 16),
                (width/2 - 60 + 8, height/2 + 16 + 8))

# }}}


# レベルセレクタ {{{

class LevelSelector(ttk.Frame):
    def __init__(self, selection, *args, **kwargs):
        ttk.Frame.__init__(self, *args, **kwargs)
        self.selection = selection
        self.n = len(selection)

        # prev ボタン
        prev_btn = self.btn = MyButton(
                self, width=100, height=60,
                text="prev", command=lambda ev: self.prev())
        prev_btn.grid(row=0, column=0)

        # next ボタン
        next_btn = self.btn = MyButton(
                self,
                width=100, height=60,
                text="next", command=lambda ev: self.next())
        next_btn.grid(row=2, column=0)

        self.cards = []
        for (AI, Card) in selection:
            frame = ttk.Frame(self)
            frame.grid(row=1, column=0, sticky="nsew")
            card = Card(frame)
            card.grid(row=1, column=0, sticky="nsew")
            self.cards.append(frame)

        self.index = 0
        self.raiseCard(0)

    def raiseCard(self, num):
        assert(0 <= num < self.n)
        self.cards[num].tkraise()

    def next(self):
        self.index += 1
        self.index %= self.n
        self.raiseCard(self.index)

    def prev(self):
        self.index -= 1
        self.index %= self.n
        self.raiseCard(self.index)

    def getAI(self):
        return self.selection[self.index][0]

    def getCard(self):
        return self.selection[self.index][1]

    def set(self, index):
        self.index = index
        self.raiseCard(self.index)

# }}}


# AIのセットアップ {{{

human = (GameAI(None, "human"), HumanLavel)
AI1 = (GameAI(think1, "computer (level1)"), Lv1)
AI2 = (GameAI(think2, "computer (level2)"), Lv2)
AI3 = (GameAI(think3, "computer (level3)"), Lv3)  # TODO

selection = [human, AI1, AI2, AI3]

# }}}


# プレイヤーカード {{{

# 先手/後手 流用
class Player(ttk.Frame):
    def __init__(self, *args, text, mark, col, **kwargs):
        ttk.Frame.__init__(self, *args, **kwargs)
        self.width = width = 200
        self.height = height = 80

        canvas = ttk.Canvas(self, width=width, height=height)
        canvas.grid()

        canvas.create_rectangle(
                4, 4, width, height,
                fill=from_rgb((240, 240, 240)),
                outline="black", width=4, tags="face"
                )
        canvas.create_text(
                width/2 - 20, height/2,
                text=text, font=("Molot", 22))
        canvas.create_text(
                width/2 + 50, height/2,
                text=mark, font=("Molot", 40),
                fill=col)

# }}}


# メインループ
if __name__ == "__main__":
    app = TicTacToe()
    app.mainloop()

import tkinter as tk
import tkinter as ttk
# from tkinter import font as tkfont
from PIL import Image, ImageTk
import random
import copy
import math

# 定数

first_col = "#EE4444"
first_col_light = "#EE9999"
second_col = "#4444EE"
second_col_light = "#9999EE"

C_DELAY = 400


# ユーティリティ {{{

def from_rgb(col):
    return "#%02x%02x%02x" % col


def random_color():
    return (
            random.randrange(256),
            random.randrange(256),
            random.randrange(256)
            )


def my_polygon(canvas, pos, pos_list, *args, rotate=0, **kwargs):
    n = len(pos_list)

    pos_list = copy.deepcopy(pos_list)

    pos_list = list(map(lambda el: (
        pos[0] + (el[0] * math.cos(rotate) - el[1] * math.sin(rotate)),
        pos[1] + (el[0] * math.sin(rotate) + el[1] * math.cos(rotate))
        ), pos_list))
    for i in range(n):
        canvas.create_line(pos_list[i], pos_list[(i + 1) % n], *args, **kwargs)


def writePlayer(canvas, who, x, y, *, small=False):
    names = ["first", "second"]
    marks = ["o", "x"]
    cols = [first_col, second_col]
    if small:
        canvas.create_text(
                x, y,
                text=names[who], font=("Molot", 18))
        canvas.create_text(
                x + 45 if who == 0 else x + 53, y-2,
                text=marks[who], fill=cols[who], font=("Molot", 24))
    else:
        canvas.create_text(
                x, y,
                text=names[who], font=("Molot", 26))
        canvas.create_text(
                x + 65 if who == 0 else x + 77, y,
                text=marks[who], fill=cols[who], font=("Molot", 35))

# }}}


# クラス {{{

class Game:
    def __init__(self):
        self.board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

    def get(self, i, j):
        assert(0 <= i < 3)
        assert(0 <= j < 3)
        return self.board[i][j]

    def set(self, i, j, k):
        assert(0 <= i < 3)
        assert(0 <= j < 3)
        assert(0 <= k <= 2)
        self.board[i][j] = k
        return self.check()

    def check(self):
        board = self.board
        for i in range(3):
            if board[i][0] == board[i][1] == board[i][2] != 0:
                return board[i][0]
        for i in range(3):
            if board[0][i] == board[1][i] == board[2][i] != 0:
                return board[0][i]
        if board[0][0] == board[1][1] == board[2][2] != 0:
            return board[0][0]
        if board[0][2] == board[1][1] == board[2][0] != 0:
            return board[0][2]
        if self.countNonZero() == 9:
            return -1
        return 0

    def countNonZero(self):
        cnt = 0
        for i in range(3):
            for j in range(3):
                if self.board[i][j]:
                    cnt += 1
        return cnt

    # こういうのはデバッグ用に残しておく
    def output(self):
        print("-------")
        for i in range(3):
            print("|", end="")
            for j in range(3):
                if self.get(i, j) == 0:
                    print(" ", end="")
                elif self.get(i, j) == 1:
                    print("o", end="")
                elif self.get(i, j) == 2:
                    print("x", end="")
                print("|", end="")
            print()
            print("-------")

    def outputWithNumber(self):
        num = 1
        fromNum = []
        print()
        print("-------")
        for i in range(3):
            print("|", end="")
            for j in range(3):
                if game.get(i, j) == 0:
                    print(num, end="")
                    num += 1
                    fromNum.append((i, j))
                elif game.get(i, j) == 1:
                    print("o", end="")
                elif game.get(i, j) == 2:
                    print("x", end="")
                print("|", end="")
            print()
            print("-------")
        return fromNum

    def __hash__(self):
        hs = 0
        for i in range(3):
            for j in range(3):
                hs += self.board[i][j] * (3 ** (i * 3 + j))
        return hs


class GameAI:
    def __init__(self, func, name):
        self.func = func
        self.name = name

    def think(self, me, game: Game):
        return self.func(me, game)


game = Game()


def think1(me, game):
    while(True):
        select = (random.randrange(3), random.randrange(3))
        if game.get(select[0], select[1]) == 0:
            return select

# }}}


# 計算 {{{

# メモ化再帰 : 本質的な計算量改善

memo = {}


def think_dfs(me, you, game):
    if hash(game) in memo:
        return memo[hash(game)]
    if game.check() != 0:
        memo[hash(game)] = (game.check(), [])
        return memo[hash(game)]

    canWin = []
    canReachDraw = []
    some = []

    for i in range(3):
        for j in range(3):
            if game.get(i, j) == 0:
                game2 = copy.deepcopy(game)
                game2.set(i, j, me)
                winner = think_dfs(you, me, game2)[0]
                if me == winner:
                    canWin.append((i, j))
                if winner == -1:
                    canReachDraw.append((i, j))
                some.append((i, j))
    if canWin:
        memo[hash(game)] = (me, canWin)
    elif canReachDraw:
        memo[hash(game)] = (-1, canReachDraw)
    else:
        memo[hash(game)] = (you, some)
    return memo[hash(game)]


# メモを予め埋めておく
think_dfs(1, 2, Game())
think_dfs(2, 1, Game())


def think2(me, game):
    if random.randrange(9) < 3:
        return think3(me, game)
    else:
        return think1(me, game)


def think3(me, game, *, noResign=False):
    (winner, candi) = think_dfs(me, 3 - me, copy.deepcopy(game))
    if winner == 3 - me and not noResign:
        return None
    assert(len(candi) != 0)
    return random.choice(candi)


# }}}


# アプリメイン {{{

class TicTacToe(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.width = 500
        self.height = 500

        self.title("Tic Tac Toe")
        self.geometry(f"{self.width}x{self.height}")

        # メインのフレーム
        container = ttk.Frame(self)
        container.pack(side="top", fill="both", expand=True)

        # grid_[row/column]configure(index, ...)
        # これは, weightによる重み付けを行ったグリッド表示をする
        # https://effbot.org/tkinterbook/grid.htm#Tkinter.Grid.grid_rowconfigure-method
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        pages = [TitleScene, LevelSelect, Main]  # TODO
        self.frames = {}
        for page in pages:
            page_name = page.__name__
            frame = page(parent=container, controller=self)
            self.frames[page_name] = frame

            # グリッドマネージャーにより, フレームを中央に置く
            # https://effbot.org/tkinterbook/grid.htm
            frame.grid(row=0, column=0, sticky="nsew")
            # nsew と指定することで中央寄せができる

        self.raise_frame("TitleScene")

    def raise_frame(self, page_name):
        assert(page_name in self.frames.keys())

        # フレームの重なりを一番上に持ってくるようなことをする
        # tkraise : https://kite.com/python/docs/Tkinter.Frame.tkraise
        frame = self.frames[page_name]
        frame.refresh()
        frame.tkraise()

# }}}


# シーンの抽象化 {{{

class Scene(ttk.Frame):
    def __init__(self, *args, parent, controller, **kwargs):
        ttk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent
        self.controller = controller
        self.width = controller.width
        self.height = controller.height

    def refresh(self):
        pass

# }}}


# シーン : タイトル {{{

class TitleScene(Scene):
    def __init__(self, parent, controller):
        Scene.__init__(self, parent=parent, controller=controller)

        canvas = self.canvas = ttk.Canvas(
                self,
                width=500, height=500, bg="black")
        canvas.pack()

        # https://stackoverflow.com/questions/29401873/tkinter-pil-image-not-displaying-inside-of-a-function
        # ものすごい罠. 画像は変数に束縛しておかないと, ガベコレで勝手に消えてしまう
        self.title0_img = ImageTk.PhotoImage(Image.open("title1.png"))
        canvas.create_image(0, 0, image=self.title0_img, anchor="nw")

        self.left_logo = MiniLogo(canvas, "left_logo")

        self.right_logo = MiniLogo(canvas, "right_logo")

        self.counter = 0
        self.after(1000//60, self.update)

        canvas.bind("<1>", lambda ev: controller.raise_frame("LevelSelect"))

    def update(self):

        M = 300
        self.counter += 1
        self.counter %= M

        canvas = self.canvas
        y = self.height - 80 + math.sin(math.pi * 2 * self.counter / M) * 20
        canvas.delete("mes")
        canvas.create_text(
                self.width / 2, y,
                text="touch to start",
                fill="white", tags="mes", font=("Molot", 30))

        self.left_logo.pos = (45, y)
        self.right_logo.pos = (self.width - 45, y)

        self.after(1000//60, self.update)

    def refresh(self):
        pass


# }}}


# 小さな回るロゴ {{{

class MiniLogo:
    def __init__(self, canvas, tag):
        self.canvas = canvas
        self.counter = 0
        self.tag = tag
        self.delay = 1000 // 60
        self.pos = (0, 0)
        canvas.after(self.delay, self.rotateOuter)

    def my_after(self, M, M2, now, nxt):
        canvas = self.canvas
        if self.counter < M:
            canvas.after(self.delay, now)
        elif self.counter < M2:
            canvas.after(self.delay, now)
        else:
            self.counter = 0
            canvas.after(self.delay, nxt)

    # 外側で一回転
    def rotateOuter(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        tag = self.tag
        c = min(self.counter / M, 1)

        canvas.delete(tag)

        for i in range(-1, 2):
            for j in range(-1, 2):
                my_polygon(
                        canvas,
                        (self.pos[0] + i * 12, self.pos[1] + j * 12),
                        [(-6, -6), (-6, 6), (6, 6), (6, -6)],
                        rotate=2*math.pi*3/4*c,
                        fill="white",
                        width=2,
                        capstyle="projecting",  # もはやどこにドキュメントが存在するかわからない
                        tags=tag)

        self.my_after(M, M * 3, self.rotateOuter, self.shrink)

    # 小さくなる
    def shrink(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        tag = self.tag
        c = min(self.counter / M, 1)

        canvas.delete(tag)

        for i in range(-1, 2):
            for j in range(-1, 2):
                r = 6 * (1 - c * .4)
                my_polygon(
                        canvas,
                        (self.pos[0] + i * 12, self.pos[1] + j * 12),
                        [(-r, -r), (-r, r), (r, r), (r, -r)],
                        fill="white",
                        width=2,
                        capstyle="projecting",
                        tags=tag)

        self.my_after(M, M * 3, self.shrink, self.gather)

    # 集まる
    def gather(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        tag = self.tag
        c = min(self.counter / M, 1)

        canvas.delete(tag)

        for i in range(-1, 2):
            for j in range(-1, 2):
                r = 6 * .6
                my_polygon(
                        canvas,
                        (
                            self.pos[0] + i * 12 * (1 - c),
                            self.pos[1] + j * 12 * (1 - c)),
                        [(-r, -r), (-r, r), (r, r), (r, -r)],
                        fill="white",
                        width=2,
                        capstyle="projecting",
                        tags=tag)

        self.my_after(M, M * 3, self.gather, self.rotateInner)

    # 内側で回転
    def rotateInner(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        tag = self.tag
        c = min(self.counter / M, 1)

        canvas.delete(tag)

        r = 6 * .6
        my_polygon(
                canvas,
                self.pos,
                [(-r, -r), (-r, r), (r, r), (r, -r)],
                fill="white",
                rotate=-math.pi*2/4*3*c,
                width=2,
                capstyle="projecting",
                tags=tag)

        self.my_after(M, M * 3, self.rotateInner, self.spread)

    # 広がる
    def spread(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        tag = self.tag
        c = min(self.counter / M, 1)

        canvas.delete(tag)

        for i in range(-1, 2):
            for j in range(-1, 2):
                r = 6 * .6
                my_polygon(
                        canvas,
                        (self.pos[0] + i * 12 * c, self.pos[1] + j * 12 * c),
                        [(-r, -r), (-r, r), (r, r), (r, -r)],
                        fill="white",
                        width=2,
                        capstyle="projecting",
                        tags=tag)

        self.my_after(M, M * 3, self.spread, self.expand)

    # 大きくなる
    def expand(self):
        M = 20
        self.counter += 1
        canvas = self.canvas
        c = min(self.counter / M, 1)
        tag = self.tag

        canvas.delete(tag)

        for i in range(-1, 2):
            for j in range(-1, 2):
                r = 6 * (.4 + c * .6)
                my_polygon(
                        canvas,
                        (self.pos[0] + i * 12, self.pos[1] + j * 12),
                        [(-r, -r), (-r, r), (r, r), (r, -r)],
                        fill="white",
                        width=2,
                        capstyle="projecting",
                        tags=tag)

        self.my_after(M, M * 3, self.expand, self.rotateOuter)

# }}}


# シーン : レベルセレクト {{{

class LevelSelect(Scene):
    def __init__(self, *args, **kwargs):
        Scene.__init__(self, *args, **kwargs)

        self.lv1 = LevelSelector(selection, self)
        self.lv1.place(x=30, y=200)
        self.lv2 = LevelSelector(selection, self)
        self.lv2.place(x=260, y=200)

        start_btn = MyButton(
                self,
                text="start",
                width=240, height=60,
                command=self.start)
        start_btn.place(x=120, y=420)

        card1 = Player(self, text="First", mark="o", col=first_col)
        card1.place(x=30, y=100)

        card2 = Player(self, text="Second", mark="x", col=second_col)
        card2.place(x=260, y=100)

        self.lv1.set(0)
        self.lv2.set(1)

    def start(self, ev):
        self.controller.AI1 = self.lv1.getAI()
        self.controller.AI2 = self.lv2.getAI()
        self.controller.Card1 = self.lv1.getCard()
        self.controller.Card2 = self.lv2.getCard()

        self.controller.raise_frame("Main")

    def refresh(self):
        pass

# }}}


# シーン : ゲーム {{{

class Main(Scene):
    def __init__(self, *args, **kwargs):
        Scene.__init__(self, *args, **kwargs)

        vs = ttk.Canvas(self, width=200, height=100)
        vs.create_text(40, 40, text="v", font=("Molot", 23))
        vs.create_text(60, 50, text="s", font=("Molot", 23))
        vs.place(x=197, y=10)

        cells = self.cells = [[None for _ in range(3)] for _ in range(3)]

        for i in range(3):
            for j in range(3):
                # クロージャを作る
                cell = cells[i][j] = Cell(
                        self,
                        width=100, height=100,
                        command=(lambda i, j: lambda ev: self.clicked(i, j))
                        (i, j))
                cell.place(x=90 + 104*i, y=100 + 104*j)
                cell.setMark("o", first_col)

        self.card = [None, None]

        self.resign_btn = MyButton(
                self, text="resign",
                width=140, height=60,
                command=self.resign)
        self.help_btn = MyButton(
                self, text="help",
                width=140, height=60,
                command=self.help)
        self.quit_btn = MyButton(
                self, text="quit",
                width=140, height=60,
                command=self.quit)

        self.message = None

    def human_interface(self, show):
        if show:
            self.resign_btn.place(x=10, y=420)
            self.help_btn.place(x=170, y=420)
            self.quit_btn.place(x=330, y=420)
        else:
            self.resign_btn.place_forget()
            self.help_btn.place_forget()
            self.quit_btn.place_forget()

    def over(self):
        self.message = ttk.Canvas(self, width=300, height=100)
        self.message.place(x=20, y=410)
        self.gameover = True
        self.refrectGame()
        self.human_interface(False)
        self.quit_btn.place(x=330, y=420)

    def draw(self):
        self.over()
        self.message.create_text(140, 40, text="draw", font=("Molot", 30))

    def resigned(self):
        self.over()
        writePlayer(self.message, self.turn, 60, 25, small=True)
        self.message.create_text(190, 25, text="resigned.", font=("Molot", 18))
        writePlayer(self.message, 1 - self.turn, 130, 60)
        self.message.create_text(260, 60, text="win", font=("Molot", 25))

    def playerWin(self):
        self.over()
        writePlayer(self.message, self.turn, 70, 40)
        self.message.create_text(200, 40, text="win", font=("Molot", 25))

    def refresh(self):
        self.game = Game()

        self.AI = [self.controller.AI1, self.controller.AI2]

        for c in self.card:
            if c:
                c.place_forget()

        if self.message:
            self.message.place_forget()

        Card1 = self.controller.Card1
        Card2 = self.controller.Card2

        self.card = [Card1(self), Card2(self)]

        self.card[0].place(x=20, y=10)
        self.card[1].place(x=270, y=25)

        self.is_human = False
        self.gameover = False
        self.turn = 0
        self.turnSetup()

    def refrectGame(self):
        for i in range(3):
            for j in range(3):
                w = self.game.get(i, j)
                self.cells[i][j].disabled = not self.is_human or self.gameover
                if w == 0:
                    self.cells[i][j].setMark(
                            "o" if self.turn == 0 else "x",
                            first_col_light if self.turn == 0
                            else second_col_light)
                    self.cells[i][j].setDetermined(False)
                elif w == 1:
                    self.cells[i][j].setMark("o", first_col)
                    self.cells[i][j].setDetermined(True)
                elif w == 2:
                    self.cells[i][j].setMark("x", second_col)
                    self.cells[i][j].setDetermined(True)

    def turnSetup(self):
        self.is_human = self.AI[self.turn].name == "human"
        self.opps = 1 - self.turn

        self.refrectGame()

        self.card[self.turn].setActive(True)
        self.card[self.opps].setActive(False)

        self.human_interface(self.is_human)

        if not self.is_human:
            self.after(C_DELAY, self.AI_choose)

    def AI_choose(self):
        sel = self.AI[self.turn].think(self.turn + 1, self.game)
        if sel is None:
            self.resigned()
            return
        self.game.set(sel[0], sel[1], self.turn + 1)
        self.next()

    def clicked(self, i, j):
        if self.game.get(i, j) != 0:
            return
        if not self.is_human or self.gameover:
            return
        self.game.set(i, j, self.turn + 1)
        self.next()

    def resign(self, ev):
        if not self.is_human or self.gameover:
            return
        self.resigned()

    def help(self, ev):
        if not self.is_human or self.gameover:
            return
        sel = think3(self.turn, self.game, noResign=True)
        self.game.set(sel[0], sel[1], self.turn + 1)
        self.next()

    def quit(self, ev):
        if not self.is_human and not self.gameover:
            return
        self.controller.raise_frame("TitleScene")

    def next(self):
        ch = self.game.check()
        if ch == 0:
            self.turn = 1 - self.turn
            self.turnSetup()
        elif ch == -1:
            self.draw()
        elif ch == 1 or ch == 2:
            self.playerWin()

# }}}


# ボタン {{{

class MyButton(ttk.Canvas):
    def __init__(
            self, *args, width, height,
            command=None, text="",
            col1=from_rgb((240, 200, 200)),
            col2=from_rgb((240, 160, 160)),
            col3=from_rgb((240, 60, 60)),
            after_moved=None,
            after_leaved=None,
            fontsize=18, **kwargs):
        ttk.Canvas.__init__(self, *args, width=width, height=height, **kwargs)

        self.disabled = False

        self.width = width
        self.height = height

        self.col1 = col1
        self.col2 = col2
        self.col3 = col3

        self.after_moved = after_moved
        self.after_leaved = after_leaved

        if command:
            self.bind("<1>", command)

        self.bind("<1>", self.pressing, add="+")
        self.bind("<ButtonRelease-1>", self.moved)
        self.bind("<Enter>", self.moved)
        self.bind("<Motion>", self.moved)
        self.bind("<Leave>", self.leaved)

        self.leaved(None)

        self.text = self.create_text(
                width/2, height/2,
                text=text, font=("Molot", fontsize))

    def pressing(self, ev, force=False):
        if self.disabled and not force:
            return
        width, height = self.width, self.height
        self.delete("face")
        self.create_rectangle(
                4, 4, width, height,
                fill=self.col3,
                outline=from_rgb((120, 120, 120)), width=4, tags="face"
                )
        self.lower("face")

    def moved(self, ev, force=False):
        if self.disabled and not force:
            return
        width, height = self.width, self.height
        self.delete("face")
        self.create_rectangle(
                4, 4, width, height,
                fill=self.col2,
                outline=from_rgb((120, 120, 120)), width=4, tags="face"
                )
        self.lower("face")
        if self.after_moved:
            self.after_moved()

    def leaved(self, ev, force=False):
        if self.disabled and not force:
            return
        width, height = self.width, self.height
        self.delete("face")
        self.create_rectangle(
                4, 4, width, height,
                fill=self.col1,
                outline=from_rgb((120, 120, 120)), width=4, tags="face"
                )
        self.lower("face")
        if self.after_leaved:
            self.after_leaved()

# }}}


# セル {{{

class Cell(MyButton):
    def __init__(self, *args, **kwargs):
        self.determined = False
        self.mark = ""
        self.col = "black"

        MyButton.__init__(
                self, *args,
                col1="#EEEEEE",
                col2="#DDDDDD",
                col3="#888888",
                after_moved=self.after_moved,
                after_leaved=self.after_leaved,
                **kwargs)

        self.setDetermined(False)

    def setDetermined(self, determined):
        self.determined = determined

        if determined:
            self.delete("mark")
            self.create_text(
                    50, 50,
                    text=self.mark, font=("Molot", 50),
                    tags="mark",
                    fill=self.col)

            self.col1 = "#EEEEEE"
            self.col2 = "#EEEEEE"
            self.col3 = "#EEEEEE"
            self.leaved(None, force=True)
        else:
            self.col1 = "#EEEEEE"
            self.col2 = "#DDDDDD"
            self.col3 = "#888888"
            self.leaved(None, force=True)

    def setMark(self, mark, col):
        self.mark = mark
        self.col = col

    def after_moved(self):
        if self.determined:
            return
        self.delete("mark")
        self.create_text(
                50, 50,
                text=self.mark, font=("Molot", 50),
                tags="mark",
                fill=self.col)

    def after_leaved(self):
        if self.determined:
            return
        self.delete("mark")

# }}}


# レベルカード {{{

class Lv1(ttk.Canvas):
    def __init__(
            self, *args, **kwargs):
        self.width = width = 200
        self.height = height = 60
        ttk.Canvas.__init__(self, *args, width=width, height=height, **kwargs)

        self.setActive(True)

    def setActive(self, active):
        width = self.width
        height = self.height

        self.delete("all")

        self.create_rectangle(
                4, 4, width, height,
                fill=from_rgb((240, 240, 200)) if active
                else "#AAAAAA",
                outline="black",
                width=4 if active else 1,
                tags="face"
                )
        self.text = self.create_text(
                20 + width/2, height/2,
                text="Lv.1", font=("Molot", 22))
        r = 4
        square = [(-r, -r), (-r, r), (r, r), (r, -r)]

        my_polygon(self, (width/2 - 40, height/2), square)


class Lv2(ttk.Canvas):
    def __init__(
            self, *args, **kwargs):
        self.width = width = 200
        self.height = height = 60
        ttk.Canvas.__init__(self, *args, width=width, height=height, **kwargs)

        self.setActive(True)

    def setActive(self, active):
        width = self.width
        height = self.height

        self.delete("all")

        self.create_rectangle(
                4, 4, width, height,
                fill=from_rgb((240, 200, 160)) if active
                else "#AAAAAA",
                outline="black",
                width=4 if active else 1,
                tags="face"
                )
        self.text = self.create_text(
                20 + width/2, height/2,
                text="Lv.2", font=("Molot", 22))
        r = 4
        square = [(-r, -r), (-r, r), (r, r), (r, -r)]

        my_polygon(self, (width/2 - 40, height/2-6), square)
        my_polygon(self, (width/2 - 40 - 8, height/2+8), square)
        my_polygon(self, (width/2 - 40 + 8, height/2+8), square)


class Lv3(ttk.Canvas):
    def __init__(
            self, *args, **kwargs):
        self.width = width = 200
        self.height = height = 60
        ttk.Canvas.__init__(self, *args, width=width, height=height, **kwargs)

        self.setActive(True)

    def setActive(self, active):
        width = self.width
        height = self.height

        self.delete("all")

        self.create_rectangle(
                4, 4, width, height,
                fill=from_rgb((240, 160, 150)) if active
                else "#AAAAAA",
                outline="black",
                width=4 if active else 1,
                tags="face"
                )
        self.text = self.create_text(
                20 + width/2, height/2,
                text="Lv.3", font=("Molot", 22))

        r = 2
        square = [(-r, -r), (-r, r), (r, r), (r, -r)]
        my_polygon(self, (width/2 - 40, height/2+2), square)

        r = 4
        square = [(-r, -r), (-r, r), (r, r), (r, -r)]
        my_polygon(self, (width/2 - 40, height/2-6), square)
        my_polygon(self, (width/2 - 40 - 8, height/2+8), square)
        my_polygon(self, (width/2 - 40 + 8, height/2+8), square)
        self.create_arc(
                width/2 - 45, height/2 - 3,
                width/2 - 35, height/2 + 7,
                extent=359, style="chord")


class HumanLavel(ttk.Canvas):
    def __init__(
            self, *args, **kwargs):
        self.width = width = 200
        self.height = height = 60
        ttk.Canvas.__init__(self, *args, width=width, height=height, **kwargs)

        self.setActive(True)

    def setActive(self, active):
        width = self.width
        height = self.height

        self.delete("all")

        self.create_rectangle(
                4, 4, width, height,
                fill=from_rgb((200, 240, 240)) if active
                else "#AAAAAA",
                outline="black",
                width=4 if active else 1,
                tags="face"
                )
        self.text = self.create_text(
                20 + width/2, height/2,
                text="human", font=("Molot", 22))
        r = 4
        square = [(-r, -r), (-r, r), (r, r), (r, -r)]

        my_polygon(self, (width/2 - 60, height/2 - 10), square)
        self.create_line(
                (width/2 - 60 - 8, height/2 + 8),
                (width/2 - 60, height/2 - 6))
        self.create_line(
                (width/2 - 60 + 8, height/2 + 8),
                (width/2 - 60, height/2 - 6))
        self.create_line(
                (width/2 - 60, height/2 - 6),
                (width/2 - 60, height/2 + 16))
        self.create_line(
                (width/2 - 60, height/2 + 16),
                (width/2 - 60 - 8, height/2 + 16 + 8))
        self.create_line(
                (width/2 - 60, height/2 + 16),
                (width/2 - 60 + 8, height/2 + 16 + 8))

# }}}


# レベルセレクタ {{{

class LevelSelector(ttk.Frame):
    def __init__(self, selection, *args, **kwargs):
        ttk.Frame.__init__(self, *args, **kwargs)
        self.selection = selection
        self.n = len(selection)

        # prev ボタン
        prev_btn = self.btn = MyButton(
                self, width=100, height=60,
                text="prev", command=lambda ev: self.prev())
        prev_btn.grid(row=0, column=0)

        # next ボタン
        next_btn = self.btn = MyButton(
                self,
                width=100, height=60,
                text="next", command=lambda ev: self.next())
        next_btn.grid(row=2, column=0)

        self.cards = []
        for (AI, Card) in selection:
            frame = ttk.Frame(self)
            frame.grid(row=1, column=0, sticky="nsew")
            card = Card(frame)
            card.grid(row=1, column=0, sticky="nsew")
            self.cards.append(frame)

        self.index = 0
        self.raiseCard(0)

    def raiseCard(self, num):
        assert(0 <= num < self.n)
        self.cards[num].tkraise()

    def next(self):
        self.index += 1
        self.index %= self.n
        self.raiseCard(self.index)

    def prev(self):
        self.index -= 1
        self.index %= self.n
        self.raiseCard(self.index)

    def getAI(self):
        return self.selection[self.index][0]

    def getCard(self):
        return self.selection[self.index][1]

    def set(self, index):
        self.index = index
        self.raiseCard(self.index)

# }}}


# AIのセットアップ {{{

human = (GameAI(None, "human"), HumanLavel)
AI1 = (GameAI(think1, "computer (level1)"), Lv1)
AI2 = (GameAI(think2, "computer (level2)"), Lv2)
AI3 = (GameAI(think3, "computer (level3)"), Lv3)  # TODO

selection = [human, AI1, AI2, AI3]

# }}}


# プレイヤーカード {{{

# 先手/後手 流用
class Player(ttk.Frame):
    def __init__(self, *args, text, mark, col, **kwargs):
        ttk.Frame.__init__(self, *args, **kwargs)
        self.width = width = 200
        self.height = height = 80

        canvas = ttk.Canvas(self, width=width, height=height)
        canvas.grid()

        canvas.create_rectangle(
                4, 4, width, height,
                fill=from_rgb((240, 240, 240)),
                outline="black", width=4, tags="face"
                )
        canvas.create_text(
                width/2 - 20, height/2,
                text=text, font=("Molot", 22))
        canvas.create_text(
                width/2 + 50, height/2,
                text=mark, font=("Molot", 40),
                fill=col)

# }}}


# メインループ
if __name__ == "__main__":
    app = TicTacToe()
    app.mainloop()
# from tkinter import font as tkfont # 定数 # ユーティリティ {{{ # }}} # クラス {{{ # こういうのはデバッグ用に残しておく # }}} # 計算 {{{ # メモ化再帰 : 本質的な計算量改善 # メモを予め埋めておく # }}} # アプリメイン {{{ # メインのフレーム # grid_[row/column]configure(index, ...) # これは, weightによる重み付けを行ったグリッド表示をする # https://effbot.org/tkinterbook/grid.htm#Tkinter.Grid.grid_rowconfigure-method # TODO # グリッドマネージャーにより, フレームを中央に置く # https://effbot.org/tkinterbook/grid.htm # nsew と指定することで中央寄せができる # フレームの重なりを一番上に持ってくるようなことをする # tkraise : https://kite.com/python/docs/Tkinter.Frame.tkraise # }}} # シーンの抽象化 {{{ # }}} # シーン : タイトル {{{ # https://stackoverflow.com/questions/29401873/tkinter-pil-image-not-displaying-inside-of-a-function # ものすごい罠. 画像は変数に束縛しておかないと, ガベコレで勝手に消えてしまう # }}} # 小さな回るロゴ {{{ # 外側で一回転 # もはやどこにドキュメントが存在するかわからない # 小さくなる # 集まる # 内側で回転 # 広がる # 大きくなる # }}} # シーン : レベルセレクト {{{ # }}} # シーン : ゲーム {{{ # クロージャを作る # }}} # ボタン {{{ # }}} # セル {{{ # }}} # レベルカード {{{ # }}} # レベルセレクタ {{{ # prev ボタン # next ボタン # }}} # AIのセットアップ {{{ # TODO # }}} # プレイヤーカード {{{ # 先手/後手 流用 # }}} # メインループ