{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "9703a2e2",
   "metadata": {},
   "source": [
    "# 21-09 · Архитектура: набросок класса Player\n",
    "\n",
    "Практика к разделу [«Разделяем игру на классы»](../../site/chapters/glava-21/21-09-arhitektura-proekta.html)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d2366fc",
   "metadata": {},
   "source": [
    "## Цель\n",
    "\n",
    "Собрать минимальный класс Player с Vector2-позицией и Rect, синхронизированным из неё — тот же принцип, что использует настоящий space_shooter.py."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb1073b3",
   "metadata": {},
   "source": [
    "## Про игровой цикл в этом ноутбуке\n",
    "\n",
    "В обычном `.py`-файле игровой цикл — это `while rabotaet:`, зависящий от событий пользователя (закрытие окна, нажатия клавиш). В автоматически выполняемом ноутбуке некому создавать такие события, поэтому здесь мы прогоняем фиксированное число кадров через `for kadr in range(N):` — логика каждого отдельного кадра (движение, столкновения, отрисовка) при этом точно такая же, как в настоящей игре из `projects/pygame/space-shooter/space_shooter.py`. Скорость по-прежнему задана в пикселях в секунду (px/s), а не в пикселях за кадр — но чтобы результат ноутбука не зависел от того, насколько быстро выполняется код на конкретном компьютере, `dt` здесь берётся не из `clock.tick()`, а фиксируется заранее как `1 / FPS`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35e501e5",
   "metadata": {},
   "source": [
    "## Рабочий пример"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ca372973",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pygame\n",
    "\n",
    "pygame.init()\n",
    "screen = pygame.display.set_mode((480, 720))\n",
    "\n",
    "\n",
    "class Player:\n",
    "    def __init__(self, image, center):\n",
    "        self.image = image\n",
    "        self.rect = self.image.get_rect()\n",
    "        self.position = pygame.Vector2(center)\n",
    "        self.rect.center = (round(self.position.x), round(self.position.y))\n",
    "\n",
    "    def move(self, direction, dt, speed=200.0):\n",
    "        if direction.length_squared() > 0:\n",
    "            direction = direction.normalize()\n",
    "        self.position += direction * speed * dt\n",
    "        self.rect.center = (round(self.position.x), round(self.position.y))\n",
    "\n",
    "\n",
    "image = pygame.Surface((44, 44), pygame.SRCALPHA)\n",
    "pygame.draw.polygon(image, (60, 170, 255), [(22, 2), (40, 40), (4, 40)])\n",
    "\n",
    "player = Player(image, (240.0, 600.0))\n",
    "for _ in range(30):\n",
    "    player.move(pygame.Vector2(1, 0), 1 / 60)\n",
    "\n",
    "print(\"Позиция:\", player.position)\n",
    "print(\"Rect:\", player.rect)\n",
    "pygame.quit()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cdb6344d",
   "metadata": {},
   "source": [
    "## Проверка результата"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac05b324",
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "assert math.isclose(player.position.x, 240.0 + 200.0 * 0.5, abs_tol=1e-6)\n",
    "assert player.rect.centerx == round(player.position.x)\n",
    "print(\"Верно: rect.center пересобран из position, а не обновлён напрямую.\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Cartesian Python 3.14",
   "language": "python",
   "name": "cartesian-python314"
  },
  "language_info": {
   "name": "python",
   "version": "3.14.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
