{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "39238af3",
   "metadata": {},
   "source": [
    "# 20-05 · Прыгающий мяч\n",
    "\n",
    "Практика к разделу [«Мини-проект — прыгающий мяч»](../../site/chapters/glava-20/20-05-mini-proekt-myach-itogi.html). Тот же код, что и в `projects/pygame/bouncing-ball/bouncing_ball.py`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf48c733",
   "metadata": {},
   "source": [
    "## Цель\n",
    "\n",
    "Собрать и протестировать мяч, отскакивающий от стен."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "275c0b59",
   "metadata": {},
   "source": [
    "## Про игровой цикл в этом ноутбуке\n",
    "\n",
    "В обычном `.py`-файле игровой цикл — это `while rabotaet:`, где `rabotaet` становится `False`, когда пользователь закрывает окно. В автоматически выполняемом ноутбуке некому закрыть окно, поэтому здесь мы прогоняем фиксированное число кадров через `for _ in range(N):` — логика каждого отдельного кадра (движение, отрисовка, `clock.tick()`) при этом точно такая же, как в настоящей игре."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05741c40",
   "metadata": {},
   "source": [
    "## Полный мини-проект"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "26e3bbf0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-14T20:59:25.504312Z",
     "iopub.status.busy": "2026-08-14T20:59:25.504205Z",
     "iopub.status.idle": "2026-08-14T20:59:28.885101Z",
     "shell.execute_reply": "2026-08-14T20:59:28.884143Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pygame-ce 2.5.8 (SDL 2.32.10, Python 3.14.6)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Кадров прогнано: 200\n",
      "Отскоков от стен: 3\n",
      "Финальная позиция: (68, 68)\n"
     ]
    }
   ],
   "source": [
    "import pygame\n",
    "\n",
    "SHIRINA, VYSOTA = 600, 400\n",
    "RADIUS = 20\n",
    "\n",
    "pygame.init()\n",
    "screen = pygame.display.set_mode((SHIRINA, VYSOTA))\n",
    "clock = pygame.time.Clock()\n",
    "\n",
    "x, y = SHIRINA // 2, VYSOTA // 2\n",
    "dx, dy = 4, 3\n",
    "\n",
    "otskokov = 0\n",
    "\n",
    "for kadr in range(200):\n",
    "    for event in pygame.event.get():\n",
    "        pass\n",
    "\n",
    "    x += dx\n",
    "    y += dy\n",
    "\n",
    "    if x - RADIUS < 0 or x + RADIUS > SHIRINA:\n",
    "        dx = -dx\n",
    "        otskokov += 1\n",
    "    if y - RADIUS < 0 or y + RADIUS > VYSOTA:\n",
    "        dy = -dy\n",
    "        otskokov += 1\n",
    "\n",
    "    screen.fill((20, 20, 40))\n",
    "    pygame.draw.circle(screen, (255, 100, 100), (int(x), int(y)), RADIUS)\n",
    "    pygame.display.flip()\n",
    "    clock.tick(60)\n",
    "\n",
    "print(\"Кадров прогнано: 200\")\n",
    "print(\"Отскоков от стен:\", otskokov)\n",
    "print(\"Финальная позиция:\", (x, y))\n",
    "pygame.quit()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "383f4b62",
   "metadata": {},
   "source": [
    "## Проверка результата"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "67df4932",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-14T20:59:28.887022Z",
     "iopub.status.busy": "2026-08-14T20:59:28.886829Z",
     "iopub.status.idle": "2026-08-14T20:59:28.889624Z",
     "shell.execute_reply": "2026-08-14T20:59:28.889155Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Верно: мяч отскочил от стен 3 раз(а) за 200 кадров.\n"
     ]
    }
   ],
   "source": [
    "assert otskokov > 0\n",
    "print(f\"Верно: мяч отскочил от стен {otskokov} раз(а) за 200 кадров.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56e1066c",
   "metadata": {},
   "source": [
    "## Задание ★★ Самостоятельная задача\n",
    "\n",
    "Проверьте, что мяч всегда остаётся в границах экрана на каждом кадре."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "71a77148",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-14T20:59:28.890820Z",
     "iopub.status.busy": "2026-08-14T20:59:28.890699Z",
     "iopub.status.idle": "2026-08-14T20:59:29.030281Z",
     "shell.execute_reply": "2026-08-14T20:59:29.029769Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Мяч когда-либо вышел за границы: False\n",
      "Верно: мяч всегда оставался в пределах экрана.\n"
     ]
    }
   ],
   "source": [
    "pygame.init()\n",
    "screen = pygame.display.set_mode((SHIRINA, VYSOTA))\n",
    "clock = pygame.time.Clock()\n",
    "\n",
    "x, y = SHIRINA // 2, VYSOTA // 2\n",
    "dx, dy = 7, 5  # быстрее — легче случайно \"пробить\" стену при ошибке в коде\n",
    "\n",
    "vyshel_za_granicy = False\n",
    "for kadr in range(300):\n",
    "    x += dx\n",
    "    y += dy\n",
    "    if x - RADIUS < 0 or x + RADIUS > SHIRINA:\n",
    "        dx = -dx\n",
    "        x = max(RADIUS, min(x, SHIRINA - RADIUS))\n",
    "    if y - RADIUS < 0 or y + RADIUS > VYSOTA:\n",
    "        dy = -dy\n",
    "        y = max(RADIUS, min(y, VYSOTA - RADIUS))\n",
    "\n",
    "    if not (RADIUS <= x <= SHIRINA - RADIUS and RADIUS <= y <= VYSOTA - RADIUS):\n",
    "        vyshel_za_granicy = True\n",
    "\n",
    "print(\"Мяч когда-либо вышел за границы:\", vyshel_za_granicy)\n",
    "assert vyshel_za_granicy is False\n",
    "print(\"Верно: мяч всегда оставался в пределах экрана.\")\n",
    "pygame.quit()"
   ]
  }
 ],
 "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
}
