{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cell-23-04-00",
   "metadata": {},
   "source": [
    "# 23-04 · Piłka odbicia (klasa)\n",
    "\n",
    "Praktyka do sekcji [„Projekt domowy D: odbijanie piłek od Pygame” `projects/pygame/bouncing-balls-oop/bouncing_balls.py`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "setup-23-04",
   "metadata": {},
   "source": [
    "## Reproducible local environment\n",
    "\n",
    "These appendix projects live in the course repository, not in SafeSort:\n",
    "\n",
    "```bash\n",
    "git clone https://github.com/Cartesian-School/python-from-zero.git\n",
    "cd python-from-zero\n",
    "python3.14 -m venv .venv\n",
    "source .venv/bin/activate\n",
    "# Windows PowerShell: .venv\\Scripts\\Activate.ps1\n",
    "python -m pip install -U pip\n",
    "python -m pip install pytest pygame-ce jupyter ipykernel\n",
    "python -m ipykernel install --user --name course-py314 --display-name \"Course Python 3.14\"\n",
    "jupyter lab\n",
    "```\n",
    "\n",
    "Select **Course Python 3.14**. On Linux, Tkinter may also require the OS\n",
    "package `python3-tk`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "diagnostic-23-04",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "from pathlib import Path\n",
    "\n",
    "print(sys.executable)\n",
    "print(Path.cwd())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-04-03",
   "metadata": {},
   "source": [
    "## O tym notebooku\n",
    "\n",
    "W przeciwieństwie do rozdziałów 20-21, nie potrzebujesz nawet for-v-range() zamiast grywalnej klasy while: `Myach` i jego metoda `shag(dt)` nigdy nie dotykają ekranu — okna i rozmów `pygame.display` istnieje tylko wewnątrz `main()`. Pozwala to sprawdzać fizykę piłki bez żadnego wyświetlacza."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-04-04",
   "metadata": {},
   "source": [
    "## Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-23-04-05",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "\n",
    "sys.path.insert(0, \"../../projects/pygame/bouncing-balls-oop\")\n",
    "import bouncing_balls as bb\n",
    "\n",
    "myach = bb.Myach(x=300, y=200, vx=100, vy=50, radius=15, cvet=(255, 100, 100))\n",
    "print(myach.pos, myach.velocity, myach.otskokov)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-04-06",
   "metadata": {},
   "source": [
    "## Sprawdzenie wyniku — ta sama sekunda, różna liczba klatek"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-23-04-07",
   "metadata": {},
   "outputs": [],
   "source": [
    "def proigrat_sekundu(obrazec, kolichestvo_kadrov):\n",
    "    myach = bb.Myach(\n",
    "        obrazec.pos.x, obrazec.pos.y, obrazec.velocity.x, obrazec.velocity.y,\n",
    "        obrazec.radius, obrazec.cvet,\n",
    "    )\n",
    "    dt = 1 / kolichestvo_kadrov\n",
    "    for _ in range(kolichestvo_kadrov):\n",
    "        myach.shag(dt)\n",
    "    return myach.pos\n",
    "\n",
    "\n",
    "pozicii = {n: proigrat_sekundu(myach, n) for n in (30, 60, 120)}\n",
    "for n, poz in pozicii.items():\n",
    "    print(n, \"кадров/сек ->\", poz)\n",
    "\n",
    "assert abs(pozicii[30].x - pozicii[60].x) < 1e-6\n",
    "assert abs(pozicii[30].y - pozicii[60].y) < 1e-6\n",
    "assert abs(pozicii[30].x - pozicii[120].x) < 1e-6\n",
    "assert abs(pozicii[30].y - pozicii[120].y) < 1e-6\n",
    "print(\"Верно для свободного равномерного движения: позиции совпали в пределах численной \"\n",
    "      \"точности. При дискретных столкновениях результат всё ещё может зависеть от dt.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-04-08",
   "metadata": {},
   "source": [
    "## Eksperyment — odbicie od ściany również liczy się przez dt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-23-04-09",
   "metadata": {},
   "outputs": [],
   "source": [
    "myach_u_steny = bb.Myach(x=bb.SHIRINA - 20, y=200, vx=200, vy=0, radius=15, cvet=(100, 200, 255))\n",
    "for _ in range(60):\n",
    "    myach_u_steny.shag(1 / 60)\n",
    "\n",
    "print(\"Позиция:\", myach_u_steny.pos, \"отскоков:\", myach_u_steny.otskokov)\n",
    "assert myach_u_steny.otskokov >= 1\n",
    "assert myach_u_steny.radius <= myach_u_steny.pos.x <= bb.SHIRINA - myach_u_steny.radius\n",
    "print(\"Верно: мяч отразился от правой стены и остался внутри границ экрана.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-04-10",
   "metadata": {},
   "source": [
    "## Starter\n",
    "\n",
    "Wypełnij zaznaczone miejsce. Niezmieniony starter nie przechodzi tests."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "task-23-04",
   "metadata": {
    "tags": [
     "exercise",
     "starter"
    ]
   },
   "outputs": [],
   "source": [
    "myachi = bb.sozdat_myachi(3)\n",
    "\n",
    "# TODO: advance every ball for 300 time steps of 1/60 second.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-04-12",
   "metadata": {},
   "source": [
    "## Task\n",
    "\n",
    "Stwórz trzy kule i wykonaj 300 kroków po 1/60 sekundy. Zapisz lista `myachi`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-04-13",
   "metadata": {},
   "source": [
    "## Tests\n",
    "\n",
    "Run After task cell: jest podstawowy przykład i przynajmniej jeden skrajny przypadek."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "tests-23-04",
   "metadata": {
    "tags": [
     "exercise-tests"
    ]
   },
   "outputs": [],
   "source": [
    "assert len(myachi) == 3\n",
    "for m in myachi:\n",
    "    assert m.radius <= m.pos.x <= bb.SHIRINA - m.radius\n",
    "    assert m.radius <= m.pos.y <= bb.VYSOTA - m.radius\n",
    "    assert m.otskokov > 0\n",
    "print(\"Tests passed\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-04-15",
   "metadata": {},
   "source": [
    "## Hint\n",
    "\n",
    "Potrzebne są dwa zagnieżdżone cykle: czas krok na zewnątrz, kule w środku."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23-04-16",
   "metadata": {},
   "source": [
    "## Solution\n",
    "\n",
    "Pokaż rozwiązanie po własnej próbie</summary>\n",
    "\n",
    "```python\n",
    "myachi = bb.sozdat_myachi(3)\n",
    "for _ in range(300):\n",
    "    for m in myachi:\n",
    "        m.shag(1 / 60)\n",
    "\n",
    "for m in myachi:\n",
    "    assert m.radius <= m.pos.x <= bb.SHIRINA - m.radius\n",
    "    assert m.radius <= m.pos.y <= bb.VYSOTA - m.radius\n",
    "    assert m.otskokov > 0\n",
    "\n",
    "print(\"Отскоков у каждого мяча:\", [m.otskokov for m in myachi])\n",
    "print(\"Верно: после 300 кадров все мячи внутри экрана и хотя бы раз отскочили.\")\n",
    "```\n",
    "\n",
    "</details>"
   ]
  }
 ],
 "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
}
