{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "85228ab4",
   "metadata": {},
   "source": [
    "# 21-03 · Ruch statków i wrogowie\n",
    "\n",
    "Praktyka do sekcji [„Ruch statku i pojawianie się wrogów”](/pl/chapters/rozdzial-21/21-03-dvizhenie-vragi.html)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6200a566",
   "metadata": {},
   "source": [
    "## Cel\n",
    "\n",
    "Ogranicz ruch statku do krawędzi ekranu i zmuszaj wrogów do pojawiania się i opadania."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6fb7ce85",
   "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": "markdown",
   "id": "3c40c66d",
   "metadata": {},
   "source": [
    "## Sprawa robocza"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ddc8dc1a",
   "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": "code",
   "execution_count": null,
   "id": "ffff3f73",
   "metadata": {},
   "outputs": [],
   "source": [
    "state = novaya_igra()\n",
    "dt = 1 / FPS\n",
    "\n",
    "# symulacji: strzałka w prawo jest przytrzymywana przez 60 klatek z rzędu (60 * dt = 1,0 sekundy)\n",
    "for kadr in range(60):\n",
    "    klavishi = {pygame.K_LEFT: False, pygame.K_RIGHT: True}\n",
    "    obrabotat_klavishi(state, klavishi, dt)\n",
    "    obnovit_igru(state, dt)\n",
    "    narisovat(state)\n",
    "    clock.tick(FPS)\n",
    "\n",
    "print(\"Корабль после 1 секунды движения вправо:\", state[\"korabl\"])\n",
    "print(\"Врагов появилось:\", len(state[\"vragi\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eff83b25",
   "metadata": {},
   "source": [
    "## Sprawdzenie wyniku"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12c90f92",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert state[\"korabl\"].x == SHIRINA - KORABL_SHIRINA, \"корабль должен упереться в правый край\"\n",
    "print(\"Верно: корабль остановился у правого края экрана, не выйдя за его пределы.\")\n",
    "\n",
    "assert len(state[\"vragi\"]) >= 1, \"за 1 секунду должен появиться хотя бы один враг (интервал 0.75 с)\"\n",
    "print(f\"Найдено врагов: {len(state['vragi'])}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9fa0c5a8",
   "metadata": {},
   "source": [
    "## Podstawowa praktyka zadań ★\n",
    "\n",
    "Uruchom kolejne 45 klatek (0,75 sekundy) bez ruchu statku i upewnij się, że pojawi się drugi wróg."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8a84c0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "for kadr in range(45):\n",
    "    klavishi = {pygame.K_LEFT: False, pygame.K_RIGHT: False}\n",
    "    obrabotat_klavishi(state, klavishi, dt)\n",
    "    obnovit_igru(state, dt)\n",
    "    narisovat(state)\n",
    "\n",
    "print(\"Врагов теперь:\", len(state[\"vragi\"]))\n",
    "assert len(state[\"vragi\"]) >= 2\n",
    "print(\"Верно: появился второй враг.\")"
   ]
  }
 ],
 "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
}
