{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "40a02dce",
   "metadata": {},
   "source": [
    "# 17-20 · Архитектура TicTacToeApp\n",
    "\n",
    "Практика к разделу [«Архитектура TicTacToeApp»](../../site/chapters/glava-17/17-20-arhitektura-app.html)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7a924160",
   "metadata": {},
   "source": [
    "## Цель\n",
    "\n",
    "Собрать минимальный объектный граф app.root / app.state и убедиться, что домен и UI разделены."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "deca78a9",
   "metadata": {},
   "source": [
    "## Про mainloop() в этом ноутбуке\n",
    "\n",
    "Вместо `root.mainloop()` здесь используется `root.update()` + `root.destroy()` — приложение строится точно так же, просто без бесконечного ожидания в конце (см. объяснение в ноутбуках главы 16)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f488775",
   "metadata": {},
   "source": [
    "## Рабочий пример"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c40315a3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-03T10:13:05.858528Z",
     "iopub.status.busy": "2026-09-03T10:13:05.858352Z",
     "iopub.status.idle": "2026-09-03T10:13:05.961499Z",
     "shell.execute_reply": "2026-09-03T10:13:05.960983Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "app.root — это Tk: True\n",
      "app.state — это GameState: True\n"
     ]
    }
   ],
   "source": [
    "import tkinter as tk\n",
    "from dataclasses import dataclass, field\n",
    "\n",
    "@dataclass\n",
    "class GameState:\n",
    "    board: list[str] = field(default_factory=lambda: [\"\"] * 9)\n",
    "    current_player: str = \"X\"\n",
    "\n",
    "class TicTacToeApp:\n",
    "    def __init__(self, root):\n",
    "        self.root = root          # app HAS-A root — не наследование от tk.Tk\n",
    "        self.state = GameState()  # app HAS-A state\n",
    "        self.buttons = []\n",
    "\n",
    "root = tk.Tk()\n",
    "app = TicTacToeApp(root)\n",
    "print(\"app.root — это Tk:\", isinstance(app.root, tk.Tk))\n",
    "print(\"app.state — это GameState:\", isinstance(app.state, GameState))\n",
    "root.update()\n",
    "root.destroy()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8552d51c",
   "metadata": {},
   "source": [
    "## Задание ★★ Самостоятельная задача\n",
    "\n",
    "Добавьте методу build_board(self) создание девяти tk.Button и убедитесь, что len(self.buttons) == 9 после его вызова."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "9db4a0f7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-03T10:13:05.963270Z",
     "iopub.status.busy": "2026-09-03T10:13:05.963131Z",
     "iopub.status.idle": "2026-09-03T10:13:06.052896Z",
     "shell.execute_reply": "2026-09-03T10:13:06.052045Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Клеток в app.buttons: 9\n",
      "app.buttons — виджеты, app.state — состояние. Это разные вещи.\n"
     ]
    }
   ],
   "source": [
    "class TicTacToeApp:\n",
    "    def __init__(self, root):\n",
    "        self.root = root\n",
    "        self.state = GameState()\n",
    "        self.buttons = []\n",
    "\n",
    "    def build_board(self):\n",
    "        for index in range(9):\n",
    "            btn = tk.Button(self.root, text=\"\", width=3, height=1)\n",
    "            btn.grid(row=index // 3, column=index % 3)\n",
    "            self.buttons.append(btn)\n",
    "\n",
    "root = tk.Tk()\n",
    "app = TicTacToeApp(root)\n",
    "app.build_board()\n",
    "root.update()\n",
    "\n",
    "print(\"Клеток в app.buttons:\", len(app.buttons))\n",
    "assert len(app.buttons) == 9\n",
    "\n",
    "# Виджеты созданы — но модель по-прежнему пустая. Это и есть разделение:\n",
    "# app.buttons — то, что видно; app.state — то, что игра на самом деле помнит.\n",
    "assert app.state.board == [\"\"] * 9\n",
    "print(\"app.buttons — виджеты, app.state — состояние. Это разные вещи.\")\n",
    "root.destroy()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Cartesian Python 3.14",
   "language": "python",
   "name": "cartesian-python314"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.14.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
