{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4c02b15a",
   "metadata": {},
   "source": [
    "# 21-08 · Pełna gra\n",
    "\n",
    "Praktyka do sekcji [„Pierwsza działająca wersja gry”"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3c5088e",
   "metadata": {},
   "source": [
    "## Cel\n",
    "\n",
    "Uruchamiaj grę wiele klatek pod rząd i upewnij się, że ruch statku, wygląd wrogów, renderowanie i ukończenie gry działają razem jako jedna całość."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac707f78",
   "metadata": {},
   "source": [
    "## O pętli gier w tym laptopie\n",
    "\n",
    "W normalnym życiu `.py`pliku -plik, pętla gry to `while rabotaet:`to zależy od zdarzeń użytkownika (zamykanie okna, naciśnięcia klawiszy). W automatycznie wykonywanym laptopie nie ma nikogo, kto mógłby je tworzyć, więc tutaj uruchamiamy stałą liczbę klatek przez `for kadr in range(N):` — logika każdej pojedynczej klatki (ruch, kolizje, rysowanie) przy tym dokładnie taka sama, jak w prawdziwej grze z `projects/pygame/space-shooter/space_shooter.py`. Prędkość wciąż jest podana w pikselach na sekundę (px/s), a nie w pikselach na klatkę — ale aby wynik notatnika nie zależał od tego, jak szybko kod działa na konkretnym komputerze, `dt` tutaj nie jest zaczerpnięte z `clock.tick()`, ale jest ustalone z góry jako `1 / FPS`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b53b27ff",
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "\n",
    "import pygame\n",
    "\n",
    "SHIRINA, VYSOTA = 480, 720\n",
    "FPS = 60\n",
    "\n",
    "KORABL_SHIRINA, KORABL_VYSOTA = 44, 44\n",
    "KORABL_SKOROST = 260.0   # px/s\n",
    "\n",
    "PULYA_SHIRINA, PULYA_VYSOTA = 6, 18\n",
    "PULYA_SKOROST = 560.0   # px/s\n",
    "\n",
    "VRAG_SHIRINA, VRAG_VYSOTA = 32, 28\n",
    "VRAG_SKOROST = 150.0                # px/s\n",
    "INTERVAL_POYAVLENIYA_VRAGA = 0.75   # секунд между новыми врагами\n",
    "\n",
    "BELYJ = (255, 255, 255)\n",
    "CHERNYJ = (10, 10, 20)\n",
    "ZELYONYJ = (80, 220, 120)\n",
    "KRASNYJ = (230, 60, 60)\n",
    "ZHYOLTYJ = (240, 220, 80)\n",
    "\n",
    "pygame.init()\n",
    "screen = pygame.display.set_mode((SHIRINA, VYSOTA))\n",
    "pygame.display.set_caption(\"Космический шутер\")\n",
    "clock = pygame.time.Clock()\n",
    "shrift = pygame.font.SysFont(None, 32)\n",
    "shrift_bolshoj = pygame.font.SysFont(None, 64)\n",
    "\n",
    "\n",
    "def novaya_igra():\n",
    "    korabl = pygame.Rect(\n",
    "        SHIRINA // 2 - KORABL_SHIRINA // 2,\n",
    "        VYSOTA - KORABL_VYSOTA - 20,\n",
    "        KORABL_SHIRINA,\n",
    "        KORABL_VYSOTA,\n",
    "    )\n",
    "    return {\n",
    "        \"korabl\": korabl,\n",
    "        \"korabl_x\": float(korabl.x),\n",
    "        \"puli\": [],\n",
    "        \"vragi\": [],\n",
    "        \"schet\": 0,\n",
    "        \"vremya_do_vraga\": INTERVAL_POYAVLENIYA_VRAGA,\n",
    "        \"igra_okonchena\": False,\n",
    "    }\n",
    "\n",
    "\n",
    "def obrabotat_klavishi(state, klavishi, dt):\n",
    "    napravlenie = klavishi[pygame.K_RIGHT] - klavishi[pygame.K_LEFT]\n",
    "    korabl_x = state[\"korabl_x\"] + napravlenie * KORABL_SKOROST * dt\n",
    "    state[\"korabl_x\"] = max(0.0, min(korabl_x, SHIRINA - KORABL_SHIRINA))\n",
    "    state[\"korabl\"].x = round(state[\"korabl_x\"])\n",
    "\n",
    "\n",
    "def vystrelit(state):\n",
    "    korabl = state[\"korabl\"]\n",
    "    pulya_rect = pygame.Rect(\n",
    "        korabl.centerx - PULYA_SHIRINA // 2,\n",
    "        korabl.top,\n",
    "        PULYA_SHIRINA,\n",
    "        PULYA_VYSOTA,\n",
    "    )\n",
    "    state[\"puli\"].append({\"rect\": pulya_rect, \"y\": float(pulya_rect.y)})\n",
    "\n",
    "\n",
    "def sozdat_vraga():\n",
    "    x = random.randint(0, SHIRINA - VRAG_SHIRINA)\n",
    "    return {\"rect\": pygame.Rect(x, -VRAG_VYSOTA, VRAG_SHIRINA, VRAG_VYSOTA), \"y\": float(-VRAG_VYSOTA)}\n",
    "\n",
    "\n",
    "def obnovit_igru(state, dt):\n",
    "    if state[\"igra_okonchena\"]:\n",
    "        return\n",
    "\n",
    "    for pulya in state[\"puli\"]:\n",
    "        pulya[\"y\"] -= PULYA_SKOROST * dt\n",
    "        pulya[\"rect\"].y = round(pulya[\"y\"])\n",
    "    state[\"puli\"] = [p for p in state[\"puli\"] if p[\"rect\"].bottom > 0]\n",
    "\n",
    "    state[\"vremya_do_vraga\"] -= dt\n",
    "    if state[\"vremya_do_vraga\"] <= 0.0:\n",
    "        state[\"vragi\"].append(sozdat_vraga())\n",
    "        state[\"vremya_do_vraga\"] += INTERVAL_POYAVLENIYA_VRAGA\n",
    "\n",
    "    for vrag in state[\"vragi\"]:\n",
    "        vrag[\"y\"] += VRAG_SKOROST * dt\n",
    "        vrag[\"rect\"].y = round(vrag[\"y\"])\n",
    "\n",
    "    novye_puli = []\n",
    "    novye_vragi = list(state[\"vragi\"])\n",
    "    for pulya in state[\"puli\"]:\n",
    "        popala = False\n",
    "        for vrag in list(novye_vragi):\n",
    "            if pulya[\"rect\"].colliderect(vrag[\"rect\"]):\n",
    "                novye_vragi.remove(vrag)\n",
    "                state[\"schet\"] += 10\n",
    "                popala = True\n",
    "                break\n",
    "        if not popala:\n",
    "            novye_puli.append(pulya)\n",
    "    state[\"puli\"] = novye_puli\n",
    "    state[\"vragi\"] = novye_vragi\n",
    "\n",
    "    for vrag in state[\"vragi\"]:\n",
    "        if vrag[\"rect\"].bottom >= VYSOTA or vrag[\"rect\"].colliderect(state[\"korabl\"]):\n",
    "            state[\"igra_okonchena\"] = True\n",
    "            break\n",
    "\n",
    "\n",
    "def narisovat(state):\n",
    "    screen.fill(CHERNYJ)\n",
    "    pygame.draw.rect(screen, ZELYONYJ, state[\"korabl\"])\n",
    "    for pulya in state[\"puli\"]:\n",
    "        pygame.draw.rect(screen, ZHYOLTYJ, pulya[\"rect\"])\n",
    "    for vrag in state[\"vragi\"]:\n",
    "        pygame.draw.rect(screen, KRASNYJ, vrag[\"rect\"])\n",
    "\n",
    "    tablo = shrift.render(f\"Счёт: {state['schet']}\", True, BELYJ)\n",
    "    screen.blit(tablo, (10, 10))\n",
    "\n",
    "    if state[\"igra_okonchena\"]:\n",
    "        nadpis = shrift_bolshoj.render(\"ИГРА ОКОНЧЕНА\", True, BELYJ)\n",
    "        rect = nadpis.get_rect(center=(SHIRINA // 2, VYSOTA // 2))\n",
    "        screen.blit(nadpis, rect)\n",
    "\n",
    "    pygame.display.flip()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa7e1fc1",
   "metadata": {},
   "source": [
    "## Pełna symulacja – ruch i pojawianie się wrogów bez strzelania\n",
    "\n",
    "Aby przewidywalny wynik był (wrogowie nie są niszczeni losowymi trafieniami), w tej rozgrywce statek po prostu się porusza, a jeden z przeciwników leci na dół ekranu i kończy grę, dokładnie tak, jak opisano w sekcji „Gdy wróg niszczy statek”"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dadee6bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "state = novaya_igra()\n",
    "random.seed(3)\n",
    "dt = 1 / FPS\n",
    "\n",
    "# Przy VRAG_SKOROST = 150 px/s i dt = 1/60 przeciwnik przechodzi około 2.5 px na klatkę,\n",
    "# to znaczy, wszystkie 720 px wysokości ekranu – w około 288 klatkach po pojawieniu się.\n",
    "# Pierwszy wróg pojawia się w 45 klatce (interwał 0,75 s = 45 klatek przy 60 FPS),\n",
    "# więc 500 klatek daje pewną margines.\n",
    "for kadr in range(500):\n",
    "    klavishi = {\n",
    "        pygame.K_LEFT: kadr % 20 < 10,\n",
    "        pygame.K_RIGHT: kadr % 20 >= 10,\n",
    "    }\n",
    "    obrabotat_klavishi(state, klavishi, dt)\n",
    "    obnovit_igru(state, dt)\n",
    "    narisovat(state)\n",
    "    clock.tick(FPS)\n",
    "    if state[\"igra_okonchena\"]:\n",
    "        break\n",
    "\n",
    "print(\"Кадров прогнано:\", kadr + 1)\n",
    "print(\"Финальный счёт:\", state[\"schet\"])\n",
    "print(\"Игра окончена:\", state[\"igra_okonchena\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81a9519c",
   "metadata": {},
   "source": [
    "## Sprawdzenie wyniku"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "106d38b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert state[\"igra_okonchena\"] is True, \"за 500 кадров хотя бы один враг должен долететь до низа экрана\"\n",
    "assert kadr + 1 <= 500\n",
    "print(f\"Верно: игра завершилась на кадре {kadr + 1} со счётом {state['schet']}.\")\n",
    "pygame.quit()"
   ]
  }
 ],
 "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
}
