7 changed files with 1292 additions and 1236 deletions
@ -1,216 +1,224 @@ |
|||||
import pygame |
import pygame |
||||
|
import asyncio # PŘIDÁNO PRO WEB: importujeme asyncio pro neblokující smyčku |
||||
import random |
import random |
||||
import sys |
import sys |
||||
|
|
||||
# pygame.init() nastartuje všechny vnitřní moduly Pygame. |
|
||||
# Je to naprosto povinný první krok před tím, než můžeme v Pygame cokoliv udělat. |
|
||||
pygame.init() |
|
||||
|
|
||||
# --- Konstanty okna a rozvržení mřížky --- |
|
||||
# Konstanty (velkými písmeny) se během hry nemění. Určují základní vlastnosti naší hry. |
|
||||
SIRKA_OKNA = 600 |
|
||||
VYSKA_OKNA = 600 |
|
||||
FPS = 30 # Počet snímků za sekundu (jak rychle se hra překresluje). 30 bohatě stačí pro Pexeso. |
|
||||
|
|
||||
# Kolik karet chceme mít na šířku a na výšku? |
|
||||
RADKY = 4 |
|
||||
SLOUPCE = 4 |
|
||||
# Velikost jedné karty v pixelech (obrazových bodech) |
|
||||
KARTICKA_VELIKOST = 100 |
|
||||
# Mezera mezi kartičkami |
|
||||
MEZERA = 20 |
|
||||
|
|
||||
# Výpočet celkové šířky a výšky celé mřížky karet, abychom ji pak mohli na obrazovce vycentrovat |
|
||||
Mrizka_sirka = (SLOUPCE * KARTICKA_VELIKOST) + ((SLOUPCE - 1) * MEZERA) |
|
||||
Mrizka_vyska = (RADKY * KARTICKA_VELIKOST) + ((RADKY - 1) * MEZERA) |
|
||||
|
|
||||
# Vypočítáme, kde mřížka začíná na osách X (vodorovně) a Y (svisle), aby byla přesně uprostřed okna |
|
||||
Odsazeni_x = (SIRKA_OKNA - Mrizka_sirka) // 2 |
|
||||
Odsazeni_y = (VYSKA_OKNA - Mrizka_vyska) // 2 |
|
||||
|
|
||||
# Barvy v Pygame se zadávají ve formátu (R, G, B) = Red, Green, Blue. |
|
||||
# Každá hodnota je od 0 (nic) do 255 (maximum dané barvy). |
|
||||
BILA = (255, 255, 255) |
|
||||
SEDA = (150, 150, 150) |
|
||||
TMAVE_MODRA = (20, 20, 80) |
|
||||
ZELENA_TEXT = (50, 255, 50) |
|
||||
|
|
||||
# Budeme mít celkem 16 karet (4x4), což znamená 8 různých párů (8 unikátních barev). |
|
||||
BARVY_PARU = [ |
|
||||
(255, 0, 0), # Červená |
|
||||
(0, 255, 0), # Světle Zelená |
|
||||
(0, 0, 255), # Modrá |
|
||||
(255, 255, 0), # Žlutá |
|
||||
(255, 0, 255), # Fialová |
|
||||
(0, 255, 255), # Azurová |
|
||||
(255, 165, 0), # Oranžová |
|
||||
(139, 69, 19) # Hnědá |
|
||||
] |
|
||||
|
|
||||
# Vytvoříme samotné okno (obrazovku), kam se bude vše vykreslovat |
|
||||
okno = pygame.display.set_mode((SIRKA_OKNA, VYSKA_OKNA)) |
|
||||
# Nastavíme text, který uvidíme nahoře v liště okna |
|
||||
pygame.display.set_caption("Pexeso (Hledání barevných párů)") |
|
||||
|
|
||||
# Založíme si herní "hodiny" - pomohou nám udržovat hru v rychlosti stanovené přes FPS |
|
||||
hodiny = pygame.time.Clock() |
|
||||
|
|
||||
# Fonty slouží pro vykreslování textu. Načteme si dva různé. |
|
||||
font = pygame.font.SysFont("arial", 40, bold=True) |
|
||||
font_maly = pygame.font.SysFont("arial", 20) |
|
||||
|
|
||||
# --- Třída objektu - objektově orientované programování (OOP) --- |
|
||||
# Třída je jako "plánek" na vytvoření konkrétních věcí. Zde je to plánek na vytvoření "Kartičky". |
|
||||
# Každá kartička si bude sama pamatovat, jakou má barvu a jestli je už otočená. |
|
||||
class Karticka: |
|
||||
def __init__(self, radek, sloupec, barva_vnitrku): |
|
||||
self.barva = barva_vnitrku # Tato barva bude vidět, když se karta otočí |
|
||||
|
|
||||
# Stavové proměnné (True = ano, False = ne) |
|
||||
self.odkryta = False # Zda ji hráč v tomto tahu právě teď otočil |
|
||||
self.nalezena = False # Zda už hráč našel její pár a karta je tak z kola venku |
|
||||
|
|
||||
# Matematika! Vypočítáme přesnou pozici X a Y (v pixelech), kam se tato karta nakreslí. |
|
||||
self.x = Odsazeni_x + sloupec * (KARTICKA_VELIKOST + MEZERA) |
|
||||
self.y = Odsazeni_y + radek * (KARTICKA_VELIKOST + MEZERA) |
|
||||
|
|
||||
# pygame.Rect vytvoří "neviditelný obdélník" kolem naší kartičky. |
|
||||
# Díky němu se mnohem snadněji zjišťuje, jestli na kartičku hráč kliknul myší. |
|
||||
self.rect = pygame.Rect(self.x, self.y, KARTICKA_VELIKOST, KARTICKA_VELIKOST) |
|
||||
|
|
||||
def vykresli(self): |
|
||||
# Funkce (metoda), která zajistí, že se karta správně namaluje na obrazovku |
|
||||
|
|
||||
if self.odkryta or self.nalezena: |
|
||||
# Pokud je karta odkrytá (otočená), namalujeme její tajnou barvu |
|
||||
pygame.draw.rect(okno, self.barva, self.rect) |
|
||||
else: |
|
||||
# Pokud karta leží rubem nahoru, namalujeme ji šedou (nebo jakýkoliv jiný vzor chceme) |
|
||||
pygame.draw.rect(okno, SEDA, self.rect) |
|
||||
|
|
||||
# Kromě výplně nakreslíme ještě bílý obrys (rámeček) o tloušťce 3 pixely, ať to lépe vypadá |
|
||||
pygame.draw.rect(okno, BILA, self.rect, 3) |
|
||||
|
|
||||
def hlavni_smycka(): |
|
||||
# --- PŘÍPRAVA HRY --- |
|
||||
# 1. Připravíme si seznam barev. Chceme 8 párů, takže každou barvu z BARVY_PARU potřebujeme dvakrát. |
|
||||
barvy_do_hry = BARVY_PARU * 2 |
|
||||
# Modul random má funkci shuffle, která pořadí položek v seznamu krásně zamíchá. |
|
||||
random.shuffle(barvy_do_hry) |
|
||||
|
|
||||
# 2. Vytvoření "mřížky" kartiček (2D pole, tedy seznam v seznamu) |
|
||||
karticky = [] |
|
||||
index_barvy = 0 # Slouží k tomu, abychom z listu barvy_do_hry postupně brali barvy |
|
||||
|
|
||||
for radek in range(RADKY): |
|
||||
rada = [] # Vytvoříme prázdný seznam pro jeden konkrétní řádek |
|
||||
for sloupec in range(SLOUPCE): |
|
||||
# Pro každé políčko vytvoříme nový objekt podle našeho "plánku" Karticka |
|
||||
karta = Karticka(radek, sloupec, barvy_do_hry[index_barvy]) |
|
||||
rada.append(karta) # Kartu přidáme do řádku |
|
||||
index_barvy += 1 |
|
||||
karticky.append(rada) # Celý naplněný řádek přidáme do hlavní mřížky |
|
||||
|
|
||||
# Proměnné pro systém hry - pamatujeme si, co hráč naklikal v aktuálním tahu |
|
||||
prvni_vybrana = None |
|
||||
druha_vybrana = None |
|
||||
|
|
||||
pocet_pokusu = 0 |
|
||||
nalezeno_paru = 0 |
|
||||
|
|
||||
# --- HLAVNÍ HERNÍ SMYČKA --- |
|
||||
# Tento cyklus 'while True' běží pořád dokola, stokrát za sekundu a tvoří samotnou hru |
|
||||
while True: |
|
||||
|
|
||||
# 1. ZPRACOVÁNÍ UDÁLOSTÍ (Event handling) |
|
||||
# Události jsou vstupy od hráče: stisknutí klávesy, kliknutí myší, zavření okna křížkem... |
|
||||
for udalost in pygame.event.get(): |
|
||||
# Pokud hráč klikl na červený křížek okna, ukončíme Pygame a celou aplikaci |
|
||||
if udalost.type == pygame.QUIT: |
|
||||
pygame.quit() |
|
||||
sys.exit() |
|
||||
|
|
||||
# Kontrola kliknutí myší (MOUSEBUTTONDOWN = tlačítko zmáčknuto dolů, button 1 = levé tlačítko) |
|
||||
if udalost.type == pygame.MOUSEBUTTONDOWN and udalost.button == 1: |
|
||||
|
|
||||
# Pokud už hráč v tomto tahu otočil dvě karty a čekáme, zabráníme mu klikat dál! |
|
||||
if prvni_vybrana is not None and druha_vybrana is not None: |
|
||||
continue # "continue" přeskočí zbytek kódu v této smyčce a nedovolí mu kartu otočit |
|
||||
|
|
||||
# Kde přesně se myš nachází? Souřadnice X,Y na obrazovce. |
|
||||
mys_x, mys_y = pygame.mouse.get_pos() |
|
||||
|
|
||||
# Projdeme všechny naše karty jednu po druhé a zkontrolujeme, zda hráč neklikl na některou z nich |
|
||||
for radek in range(RADKY): |
|
||||
for sloupec in range(SLOUPCE): |
|
||||
karta = karticky[radek][sloupec] |
|
||||
|
|
||||
# Metoda collidepoint zjistí, zda se bod (myš) nachází uvnitř obdélníku (karty) |
|
||||
if karta.rect.collidepoint(mys_x, mys_y): |
|
||||
# Otočit můžeme jen kartu, která ještě NENÍ otočená |
|
||||
if not karta.odkryta and not karta.nalezena: |
|
||||
karta.odkryta = True # Hráč ji otočil! |
|
||||
|
|
||||
# Systém logiky: Byla tohle první karta v tahu? |
|
||||
if prvni_vybrana is None: |
|
||||
prvni_vybrana = karta |
|
||||
else: |
|
||||
# Nebyla, takže to už musí být druhá karta v tahu. |
|
||||
druha_vybrana = karta |
|
||||
pocet_pokusu += 1 # Tah končí, započítáme pokus |
|
||||
|
|
||||
# 2. VYKRESLOVÁNÍ (Kreslíme nový "snímek" - frame) |
|
||||
# Nejdřív vždy smažeme starý obraz tím, že celé okno přebarvíme jednolitou barvou |
|
||||
okno.fill(TMAVE_MODRA) |
|
||||
|
|
||||
# Projdeme mřížku a řekneme každé kartičce, aby se nakreslila na okno |
|
||||
|
async def main(): # PŘIDÁNO PRO WEB: Zabalíme celou hru do asynchronní funkce |
||||
|
# pygame.init() nastartuje všechny vnitřní moduly Pygame. |
||||
|
# Je to naprosto povinný první krok před tím, než můžeme v Pygame cokoliv udělat. |
||||
|
pygame.init() |
||||
|
|
||||
|
# --- Konstanty okna a rozvržení mřížky --- |
||||
|
# Konstanty (velkými písmeny) se během hry nemění. Určují základní vlastnosti naší hry. |
||||
|
SIRKA_OKNA = 600 |
||||
|
VYSKA_OKNA = 600 |
||||
|
FPS = 30 # Počet snímků za sekundu (jak rychle se hra překresluje). 30 bohatě stačí pro Pexeso. |
||||
|
|
||||
|
# Kolik karet chceme mít na šířku a na výšku? |
||||
|
RADKY = 4 |
||||
|
SLOUPCE = 4 |
||||
|
# Velikost jedné karty v pixelech (obrazových bodech) |
||||
|
KARTICKA_VELIKOST = 100 |
||||
|
# Mezera mezi kartičkami |
||||
|
MEZERA = 20 |
||||
|
|
||||
|
# Výpočet celkové šířky a výšky celé mřížky karet, abychom ji pak mohli na obrazovce vycentrovat |
||||
|
Mrizka_sirka = (SLOUPCE * KARTICKA_VELIKOST) + ((SLOUPCE - 1) * MEZERA) |
||||
|
Mrizka_vyska = (RADKY * KARTICKA_VELIKOST) + ((RADKY - 1) * MEZERA) |
||||
|
|
||||
|
# Vypočítáme, kde mřížka začíná na osách X (vodorovně) a Y (svisle), aby byla přesně uprostřed okna |
||||
|
Odsazeni_x = (SIRKA_OKNA - Mrizka_sirka) // 2 |
||||
|
Odsazeni_y = (VYSKA_OKNA - Mrizka_vyska) // 2 |
||||
|
|
||||
|
# Barvy v Pygame se zadávají ve formátu (R, G, B) = Red, Green, Blue. |
||||
|
# Každá hodnota je od 0 (nic) do 255 (maximum dané barvy). |
||||
|
BILA = (255, 255, 255) |
||||
|
SEDA = (150, 150, 150) |
||||
|
TMAVE_MODRA = (20, 20, 80) |
||||
|
ZELENA_TEXT = (50, 255, 50) |
||||
|
|
||||
|
# Budeme mít celkem 16 karet (4x4), což znamená 8 různých párů (8 unikátních barev). |
||||
|
BARVY_PARU = [ |
||||
|
(255, 0, 0), # Červená |
||||
|
(0, 255, 0), # Světle Zelená |
||||
|
(0, 0, 255), # Modrá |
||||
|
(255, 255, 0), # Žlutá |
||||
|
(255, 0, 255), # Fialová |
||||
|
(0, 255, 255), # Azurová |
||||
|
(255, 165, 0), # Oranžová |
||||
|
(139, 69, 19) # Hnědá |
||||
|
] |
||||
|
|
||||
|
# Vytvoříme samotné okno (obrazovku), kam se bude vše vykreslovat |
||||
|
okno = pygame.display.set_mode((SIRKA_OKNA, VYSKA_OKNA)) |
||||
|
# Nastavíme text, který uvidíme nahoře v liště okna |
||||
|
pygame.display.set_caption("Pexeso (Hledání barevných párů)") |
||||
|
|
||||
|
# Založíme si herní "hodiny" - pomohou nám udržovat hru v rychlosti stanovené přes FPS |
||||
|
hodiny = pygame.time.Clock() |
||||
|
|
||||
|
# Fonty slouží pro vykreslování textu. Načteme si dva různé. |
||||
|
font = pygame.font.SysFont("arial", 40, bold=True) |
||||
|
font_maly = pygame.font.SysFont("arial", 20) |
||||
|
|
||||
|
# --- Třída objektu - objektově orientované programování (OOP) --- |
||||
|
# Třída je jako "plánek" na vytvoření konkrétních věcí. Zde je to plánek na vytvoření "Kartičky". |
||||
|
# Každá kartička si bude sama pamatovat, jakou má barvu a jestli je už otočená. |
||||
|
class Karticka: |
||||
|
def __init__(self, radek, sloupec, barva_vnitrku): |
||||
|
self.barva = barva_vnitrku # Tato barva bude vidět, když se karta otočí |
||||
|
|
||||
|
# Stavové proměnné (True = ano, False = ne) |
||||
|
self.odkryta = False # Zda ji hráč v tomto tahu právě teď otočil |
||||
|
self.nalezena = False # Zda už hráč našel její pár a karta je tak z kola venku |
||||
|
|
||||
|
# Matematika! Vypočítáme přesnou pozici X a Y (v pixelech), kam se tato karta nakreslí. |
||||
|
self.x = Odsazeni_x + sloupec * (KARTICKA_VELIKOST + MEZERA) |
||||
|
self.y = Odsazeni_y + radek * (KARTICKA_VELIKOST + MEZERA) |
||||
|
|
||||
|
# pygame.Rect vytvoří "neviditelný obdélník" kolem naší kartičky. |
||||
|
# Díky němu se mnohem snadněji zjišťuje, jestli na kartičku hráč kliknul myší. |
||||
|
self.rect = pygame.Rect(self.x, self.y, KARTICKA_VELIKOST, KARTICKA_VELIKOST) |
||||
|
|
||||
|
def vykresli(self): |
||||
|
# Funkce (metoda), která zajistí, že se karta správně namaluje na obrazovku |
||||
|
|
||||
|
if self.odkryta or self.nalezena: |
||||
|
# Pokud je karta odkrytá (otočená), namalujeme její tajnou barvu |
||||
|
pygame.draw.rect(okno, self.barva, self.rect) |
||||
|
else: |
||||
|
# Pokud karta leží rubem nahoru, namalujeme ji šedou (nebo jakýkoliv jiný vzor chceme) |
||||
|
pygame.draw.rect(okno, SEDA, self.rect) |
||||
|
|
||||
|
# Kromě výplně nakreslíme ještě bílý obrys (rámeček) o tloušťce 3 pixely, ať to lépe vypadá |
||||
|
pygame.draw.rect(okno, BILA, self.rect, 3) |
||||
|
|
||||
|
def hlavni_smycka(): |
||||
|
# --- PŘÍPRAVA HRY --- |
||||
|
# 1. Připravíme si seznam barev. Chceme 8 párů, takže každou barvu z BARVY_PARU potřebujeme dvakrát. |
||||
|
barvy_do_hry = BARVY_PARU * 2 |
||||
|
# Modul random má funkci shuffle, která pořadí položek v seznamu krásně zamíchá. |
||||
|
random.shuffle(barvy_do_hry) |
||||
|
|
||||
|
# 2. Vytvoření "mřížky" kartiček (2D pole, tedy seznam v seznamu) |
||||
|
karticky = [] |
||||
|
index_barvy = 0 # Slouží k tomu, abychom z listu barvy_do_hry postupně brali barvy |
||||
|
|
||||
for radek in range(RADKY): |
for radek in range(RADKY): |
||||
|
rada = [] # Vytvoříme prázdný seznam pro jeden konkrétní řádek |
||||
for sloupec in range(SLOUPCE): |
for sloupec in range(SLOUPCE): |
||||
karticky[radek][sloupec].vykresli() |
|
||||
|
|
||||
# Vykreslíme text s počtem pokusů. Funkce render vytvoří z textu "obrázek", který pak můžeme vykreslit |
|
||||
text_tahy = font_maly.render(f"Počet pokusů: {pocet_pokusu}", True, BILA) |
|
||||
# blit je příkaz pro "nalep tento obrázek na dané souřadnice" |
|
||||
okno.blit(text_tahy, (10, 10)) |
|
||||
|
|
||||
# Zkontrolujeme vítězství |
|
||||
if nalezeno_paru == 8: # Maximum možných párů je 8 |
|
||||
text_konec = font.render("Vítězství! Skvělá paměť.", True, ZELENA_TEXT) |
|
||||
# Center zarovná text hezky doprostřed požadované X pozice |
|
||||
rect = text_konec.get_rect(center=(SIRKA_OKNA // 2, VYSKA_OKNA - 30)) |
|
||||
okno.blit(text_konec, rect) |
|
||||
|
|
||||
# ZÁSADNÍ VĚC: Po tom co jsme do paměti Pygame naskládali příkazy kreslení, |
|
||||
# musíme to všechno najednou zviditelnit na obrazovce pomocí příkazu flip() nebo update()! |
|
||||
# Děláme to už teď, abychom (pokud budeme dole hru pauzovat), viděli otočenou druhou kartu. |
|
||||
pygame.display.flip() |
|
||||
|
|
||||
# 3. HERNÍ LOGIKA: Vyhodnocení tahu |
|
||||
# Pokud už hráč vybral dvě karty, musíme zjistit, jestli udělal pár |
|
||||
if prvni_vybrana is not None and druha_vybrana is not None: |
|
||||
if prvni_vybrana.barva == druha_vybrana.barva: |
|
||||
# SUPER! Barvy se shodují, je to PÁR! |
|
||||
prvni_vybrana.nalezena = True |
|
||||
druha_vybrana.nalezena = True |
|
||||
nalezeno_paru += 1 |
|
||||
else: |
|
||||
# ŠPATNĚ! Barvy se liší. |
|
||||
# Aby hráč vůbec stihl zaregistrovat barvu druhé karty, musíme na chvíli zmrazit hru |
|
||||
# wait(1000) hru zastaví přesně na 1000 milisekund (1 sekundu) |
|
||||
pygame.time.wait(1000) |
|
||||
|
|
||||
# A pak obě karty otočíme zase lícem dolů (šedá strana nahoru) |
|
||||
prvni_vybrana.odkryta = False |
|
||||
druha_vybrana.odkryta = False |
|
||||
|
|
||||
# Tah je u konce, ať už to byl pár nebo ne. Vyčistíme výběr pro další tah! |
|
||||
prvni_vybrana = None |
|
||||
druha_vybrana = None |
|
||||
|
|
||||
# Rychlost smyčky: Řekneme hodinám, že chceme, aby smyčka běžela maximálně tolikrát za vteřinu, jak určuje FPS |
|
||||
hodiny.tick(FPS) |
|
||||
|
|
||||
# Tohle říká Pythonu: Pokud tento soubor spouštíš jako hlavní program (ne jen importuješ), |
|
||||
# tak teprve tehdy spusť hlani_smycka() |
|
||||
if __name__ == "__main__": |
|
||||
hlavni_smycka() |
|
||||
|
# Pro každé políčko vytvoříme nový objekt podle našeho "plánku" Karticka |
||||
|
karta = Karticka(radek, sloupec, barvy_do_hry[index_barvy]) |
||||
|
rada.append(karta) # Kartu přidáme do řádku |
||||
|
index_barvy += 1 |
||||
|
karticky.append(rada) # Celý naplněný řádek přidáme do hlavní mřížky |
||||
|
|
||||
|
# Proměnné pro systém hry - pamatujeme si, co hráč naklikal v aktuálním tahu |
||||
|
prvni_vybrana = None |
||||
|
druha_vybrana = None |
||||
|
|
||||
|
pocet_pokusu = 0 |
||||
|
nalezeno_paru = 0 |
||||
|
|
||||
|
# --- HLAVNÍ HERNÍ SMYČKA --- |
||||
|
# Tento cyklus 'while True' běží pořád dokola, stokrát za sekundu a tvoří samotnou hru |
||||
|
while True: |
||||
|
|
||||
|
# 1. ZPRACOVÁNÍ UDÁLOSTÍ (Event handling) |
||||
|
# Události jsou vstupy od hráče: stisknutí klávesy, kliknutí myší, zavření okna křížkem... |
||||
|
for udalost in pygame.event.get(): |
||||
|
# Pokud hráč klikl na červený křížek okna, ukončíme Pygame a celou aplikaci |
||||
|
if udalost.type == pygame.QUIT: |
||||
|
pygame.quit() |
||||
|
sys.exit() |
||||
|
|
||||
|
# Kontrola kliknutí myší (MOUSEBUTTONDOWN = tlačítko zmáčknuto dolů, button 1 = levé tlačítko) |
||||
|
if udalost.type == pygame.MOUSEBUTTONDOWN and udalost.button == 1: |
||||
|
|
||||
|
# Pokud už hráč v tomto tahu otočil dvě karty a čekáme, zabráníme mu klikat dál! |
||||
|
if prvni_vybrana is not None and druha_vybrana is not None: |
||||
|
continue # "continue" přeskočí zbytek kódu v této smyčce a nedovolí mu kartu otočit |
||||
|
|
||||
|
# Kde přesně se myš nachází? Souřadnice X,Y na obrazovce. |
||||
|
mys_x, mys_y = pygame.mouse.get_pos() |
||||
|
|
||||
|
# Projdeme všechny naše karty jednu po druhé a zkontrolujeme, zda hráč neklikl na některou z nich |
||||
|
for radek in range(RADKY): |
||||
|
for sloupec in range(SLOUPCE): |
||||
|
karta = karticky[radek][sloupec] |
||||
|
|
||||
|
# Metoda collidepoint zjistí, zda se bod (myš) nachází uvnitř obdélníku (karty) |
||||
|
if karta.rect.collidepoint(mys_x, mys_y): |
||||
|
# Otočit můžeme jen kartu, která ještě NENÍ otočená |
||||
|
if not karta.odkryta and not karta.nalezena: |
||||
|
karta.odkryta = True # Hráč ji otočil! |
||||
|
|
||||
|
# Systém logiky: Byla tohle první karta v tahu? |
||||
|
if prvni_vybrana is None: |
||||
|
prvni_vybrana = karta |
||||
|
else: |
||||
|
# Nebyla, takže to už musí být druhá karta v tahu. |
||||
|
druha_vybrana = karta |
||||
|
pocet_pokusu += 1 # Tah končí, započítáme pokus |
||||
|
|
||||
|
# 2. VYKRESLOVÁNÍ (Kreslíme nový "snímek" - frame) |
||||
|
# Nejdřív vždy smažeme starý obraz tím, že celé okno přebarvíme jednolitou barvou |
||||
|
okno.fill(TMAVE_MODRA) |
||||
|
|
||||
|
# Projdeme mřížku a řekneme každé kartičce, aby se nakreslila na okno |
||||
|
for radek in range(RADKY): |
||||
|
for sloupec in range(SLOUPCE): |
||||
|
karticky[radek][sloupec].vykresli() |
||||
|
|
||||
|
# Vykreslíme text s počtem pokusů. Funkce render vytvoří z textu "obrázek", který pak můžeme vykreslit |
||||
|
text_tahy = font_maly.render(f"Počet pokusů: {pocet_pokusu}", True, BILA) |
||||
|
# blit je příkaz pro "nalep tento obrázek na dané souřadnice" |
||||
|
okno.blit(text_tahy, (10, 10)) |
||||
|
|
||||
|
# Zkontrolujeme vítězství |
||||
|
if nalezeno_paru == 8: # Maximum možných párů je 8 |
||||
|
text_konec = font.render("Vítězství! Skvělá paměť.", True, ZELENA_TEXT) |
||||
|
# Center zarovná text hezky doprostřed požadované X pozice |
||||
|
rect = text_konec.get_rect(center=(SIRKA_OKNA // 2, VYSKA_OKNA - 30)) |
||||
|
okno.blit(text_konec, rect) |
||||
|
|
||||
|
# ZÁSADNÍ VĚC: Po tom co jsme do paměti Pygame naskládali příkazy kreslení, |
||||
|
# musíme to všechno najednou zviditelnit na obrazovce pomocí příkazu flip() nebo update()! |
||||
|
# Děláme to už teď, abychom (pokud budeme dole hru pauzovat), viděli otočenou druhou kartu. |
||||
|
pygame.display.flip() |
||||
|
|
||||
|
# 3. HERNÍ LOGIKA: Vyhodnocení tahu |
||||
|
# Pokud už hráč vybral dvě karty, musíme zjistit, jestli udělal pár |
||||
|
if prvni_vybrana is not None and druha_vybrana is not None: |
||||
|
if prvni_vybrana.barva == druha_vybrana.barva: |
||||
|
# SUPER! Barvy se shodují, je to PÁR! |
||||
|
prvni_vybrana.nalezena = True |
||||
|
druha_vybrana.nalezena = True |
||||
|
nalezeno_paru += 1 |
||||
|
else: |
||||
|
# ŠPATNĚ! Barvy se liší. |
||||
|
# Aby hráč vůbec stihl zaregistrovat barvu druhé karty, musíme na chvíli zmrazit hru |
||||
|
# wait(1000) hru zastaví přesně na 1000 milisekund (1 sekundu) |
||||
|
pygame.time.wait(1000) |
||||
|
|
||||
|
# A pak obě karty otočíme zase lícem dolů (šedá strana nahoru) |
||||
|
prvni_vybrana.odkryta = False |
||||
|
druha_vybrana.odkryta = False |
||||
|
|
||||
|
# Tah je u konce, ať už to byl pár nebo ne. Vyčistíme výběr pro další tah! |
||||
|
prvni_vybrana = None |
||||
|
druha_vybrana = None |
||||
|
|
||||
|
# Rychlost smyčky: Řekneme hodinám, že chceme, aby smyčka běžela maximálně tolikrát za vteřinu, jak určuje FPS |
||||
|
hodiny.tick(FPS) |
||||
|
# PŘIDÁNO PRO WEB: Dáme prohlížeči šanci překreslit obrazovku |
||||
|
await asyncio.sleep(0) |
||||
|
|
||||
|
# Tohle říká Pythonu: Pokud tento soubor spouštíš jako hlavní program (ne jen importuješ), |
||||
|
# tak teprve tehdy spusť hlani_smycka() |
||||
|
if __name__ == "__main__": |
||||
|
hlavni_smycka() |
||||
|
|
||||
|
|
||||
|
# PŘIDÁNO PRO WEB: Spuštění asynchronní hry |
||||
|
asyncio.run(main()) |
||||
@ -1,194 +1,202 @@ |
|||||
import pygame |
import pygame |
||||
|
import asyncio # PŘIDÁNO PRO WEB: importujeme asyncio pro neblokující smyčku |
||||
import random |
import random |
||||
import sys |
import sys |
||||
|
|
||||
# pygame.init() připraví knihovnu Pygame k použití. Volat to musíme vždy jako první věc! |
|
||||
pygame.init() |
|
||||
|
|
||||
# --- Nastavení okna a základních konstant --- |
|
||||
# Konstanty nepíšeme malými písmeny, ale VELKÝMI, abychom zdůraznili, že se jejich hodnota za běhu nemění. |
|
||||
SIRKA_OKNA = 400 |
|
||||
VYSKA_OKNA = 600 |
|
||||
FPS = 60 # FPS (Frames Per Second) = kolikrát se celá obrazovka překreslí za vteřinu. 60 je hezky plynulé. |
|
||||
|
|
||||
# Barvy zapisujeme jako mix tří kanálů: (Červená, Zelená, Modrá) - každý má hodnotu 0 až 255. |
|
||||
# (0,0,0) je černá (žádná barva), (255,255,255) je bílá (plné všechny barvy). |
|
||||
CERNA = (0, 0, 0) |
|
||||
BILA = (255, 255, 255) |
|
||||
MODRA_OBLOHA = (135, 206, 235) |
|
||||
ZELENA = (34, 139, 34) |
|
||||
ZLUTYP_PTAK = (255, 215, 0) |
|
||||
CERVENA = (255, 0, 0) |
|
||||
|
|
||||
# Vytvoření okna (plocha, kam se bude vše vykreslovat) |
|
||||
okno = pygame.display.set_mode((SIRKA_OKNA, VYSKA_OKNA)) |
|
||||
pygame.display.set_caption("Flappy Bird") # Nápis v horní liště programu |
|
||||
|
|
||||
# Herní hodiny pro řízení rychlosti smyčky |
|
||||
hodiny = pygame.time.Clock() |
|
||||
|
|
||||
# Tvorba písem. font_velky použijeme na herní nápisy (Konec hry apod.), font_maly na menší text. |
|
||||
font_velky = pygame.font.SysFont("arial", 48, bold=True) |
|
||||
font_maly = pygame.font.SysFont("arial", 24) |
|
||||
|
|
||||
# Pomocná funkce pro snazší kreslení textu. Abychom nemuseli pokaždé opakovat stejné složité příkazy. |
|
||||
def zobraz_text(text, font, barva, x, y, zarovnat_stred=False): |
|
||||
# render() převede text ze znaků do obrázku (tzv. "Surface"), který lze následně nakreslit. |
|
||||
plocha = font.render(text, True, barva) |
|
||||
if zarovnat_stred: |
|
||||
# Vypočítáme obdélník (rect) obrázku textu a řekneme, ať je jeho STŘED (center) na dané X a Y pozici |
|
||||
rect = plocha.get_rect(center=(x, y)) |
|
||||
okno.blit(plocha, rect) # blit() nalepí obrázek plochy na 'okno' |
|
||||
else: |
|
||||
# Jinak ho plácneme normálně za jeho levý horní roh |
|
||||
okno.blit(plocha, (x, y)) |
|
||||
|
|
||||
def hlavni_smycka(): |
|
||||
# --- PROMĚNNÉ PTÁKA --- |
|
||||
# Toto jsou proměnné, které se v čase mění, proto nejsou VELKÝMI PÍSMENY |
|
||||
ptak_x = 50 # Zleva je pták relativně blízko okraji |
|
||||
ptak_y = VYSKA_OKNA // 2 # Vertikálně (Y) ho položíme doprostřed okna |
|
||||
ptak_sirka = 30 |
|
||||
ptak_vyska = 30 |
|
||||
|
|
||||
# Fyzika |
|
||||
rychlost_padu = 0 # Zpočátku stojí pták na místě |
|
||||
gravitace = 0.5 # Gravitace každým snímkem tuto 'rychlost_padu' zvyšuje směrem DOLŮ (kladné hodnoty Y) |
|
||||
sila_skoku = -8 # Skok je prostě náhlé nastavení rychlosti do záporných hodnot, tedy NAHORU! |
|
||||
|
|
||||
# --- PROMĚNNÉ PŘEKÁŽEK (ZELENÉ TRUBKY) --- |
|
||||
trubka_sirka = 60 |
|
||||
mezera_mezi_trubkami = 160 # Jak velikou skulinou bude hráč létat? (v pixelech) |
|
||||
rychlost_posunu = 3 # Překážky neustále jedou směrem doleva, aby to vypadalo, že pták letí dopředu |
|
||||
|
|
||||
# Seznam do kterého budeme ukládat trubky. Použijeme slovníky {ključ: hodnota}. |
|
||||
# Např: {"x": 400, "y_stred": 300} ... 'y_stred' určuje, kde přesně je střed oné mezery kudy se letí |
|
||||
trubky = [] |
|
||||
|
|
||||
# Funkce k přidání nové trubky na určené X ose |
|
||||
def pridej_trubku(x_pozice): |
|
||||
# Nechceme mezeru úplně na kraji (nemožné projet), omezíme ji od 150px nahoře po 150px dole |
|
||||
stred_mezery = random.randint(150, VYSKA_OKNA - 150) |
|
||||
trubky.append({"x": x_pozice, "y_stred": stred_mezery}) |
|
||||
|
|
||||
# Přidáme první dvě trubky už před startem hry, nacházejí se zprvu mimo okno vpravo. |
|
||||
pridej_trubku(SIRKA_OKNA + 100) |
|
||||
pridej_trubku(SIRKA_OKNA + 400) # Další trubka má odstup 300 pixelů |
|
||||
|
|
||||
skore = 0 |
|
||||
|
|
||||
# STAVOVÝ AUTOMAT - skvělý trik v programování. Znalost toho "ve kterém jsme stavu" |
|
||||
# nám pomáhá měnit logiku. Hra se chová úplně jinak, když jsme v MENU než když právě HRAJEME. |
|
||||
stav_hry = "START" # Možnosti: "START", "HRA", "KONEC" |
|
||||
|
|
||||
# --- HERNÍ SMYČKA --- |
|
||||
while True: |
|
||||
|
|
||||
# 1. ČTENÍ VSTUPŮ (UDÁLOSTI) |
|
||||
for udalost in pygame.event.get(): |
|
||||
if udalost.type == pygame.QUIT: |
|
||||
pygame.quit() # Korektně ukončí knihovnu Pygame |
|
||||
sys.exit() # Ukončí celý Python skript |
|
||||
|
|
||||
# Pokud někdo zmáčkl klávesu na klávesnici (KEYDOWN) |
|
||||
if udalost.type == pygame.KEYDOWN: |
|
||||
if udalost.key == pygame.K_SPACE: # Speciálně zkoumáme MEZERNÍK |
|
||||
# Co dělá mezerník záleží na tom, v jakém STAVU je hra: |
|
||||
if stav_hry == "START": |
|
||||
stav_hry = "HRA" # Přepneme hru na aktivní |
|
||||
rychlost_padu = sila_skoku # Hned v první sekundě mu dáme skokový impuls |
|
||||
elif stav_hry == "HRA": |
|
||||
rychlost_padu = sila_skoku # Hráč skočil! (Pták vzletí nahoru) |
|
||||
elif stav_hry == "KONEC": |
|
||||
return # Vyskočíme z této funkce, což vyústí ve spuštění znova (viz úplně dole v kódu) |
|
||||
|
|
||||
# 2. LOGIKA A POHYB |
|
||||
# Zpracovává se jen pokud jsme ve stavu HRA |
|
||||
if stav_hry == "HRA": |
|
||||
|
|
||||
# Aplikace gravitace: Rychlost pádu je stále vyšší kladné číslo |
|
||||
rychlost_padu += gravitace |
|
||||
# Přičtením této rychlosti k souřadnici Y pták pomalu (a čím dál rychleji) "padá" na zem okna |
|
||||
ptak_y += rychlost_padu |
|
||||
|
|
||||
# Pohyb trubek doleva (zmenšujeme jejich osu X) |
|
||||
|
async def main(): # PŘIDÁNO PRO WEB: Zabalíme celou hru do asynchronní funkce |
||||
|
# pygame.init() připraví knihovnu Pygame k použití. Volat to musíme vždy jako první věc! |
||||
|
pygame.init() |
||||
|
|
||||
|
# --- Nastavení okna a základních konstant --- |
||||
|
# Konstanty nepíšeme malými písmeny, ale VELKÝMI, abychom zdůraznili, že se jejich hodnota za běhu nemění. |
||||
|
SIRKA_OKNA = 400 |
||||
|
VYSKA_OKNA = 600 |
||||
|
FPS = 60 # FPS (Frames Per Second) = kolikrát se celá obrazovka překreslí za vteřinu. 60 je hezky plynulé. |
||||
|
|
||||
|
# Barvy zapisujeme jako mix tří kanálů: (Červená, Zelená, Modrá) - každý má hodnotu 0 až 255. |
||||
|
# (0,0,0) je černá (žádná barva), (255,255,255) je bílá (plné všechny barvy). |
||||
|
CERNA = (0, 0, 0) |
||||
|
BILA = (255, 255, 255) |
||||
|
MODRA_OBLOHA = (135, 206, 235) |
||||
|
ZELENA = (34, 139, 34) |
||||
|
ZLUTYP_PTAK = (255, 215, 0) |
||||
|
CERVENA = (255, 0, 0) |
||||
|
|
||||
|
# Vytvoření okna (plocha, kam se bude vše vykreslovat) |
||||
|
okno = pygame.display.set_mode((SIRKA_OKNA, VYSKA_OKNA)) |
||||
|
pygame.display.set_caption("Flappy Bird") # Nápis v horní liště programu |
||||
|
|
||||
|
# Herní hodiny pro řízení rychlosti smyčky |
||||
|
hodiny = pygame.time.Clock() |
||||
|
|
||||
|
# Tvorba písem. font_velky použijeme na herní nápisy (Konec hry apod.), font_maly na menší text. |
||||
|
font_velky = pygame.font.SysFont("arial", 48, bold=True) |
||||
|
font_maly = pygame.font.SysFont("arial", 24) |
||||
|
|
||||
|
# Pomocná funkce pro snazší kreslení textu. Abychom nemuseli pokaždé opakovat stejné složité příkazy. |
||||
|
def zobraz_text(text, font, barva, x, y, zarovnat_stred=False): |
||||
|
# render() převede text ze znaků do obrázku (tzv. "Surface"), který lze následně nakreslit. |
||||
|
plocha = font.render(text, True, barva) |
||||
|
if zarovnat_stred: |
||||
|
# Vypočítáme obdélník (rect) obrázku textu a řekneme, ať je jeho STŘED (center) na dané X a Y pozici |
||||
|
rect = plocha.get_rect(center=(x, y)) |
||||
|
okno.blit(plocha, rect) # blit() nalepí obrázek plochy na 'okno' |
||||
|
else: |
||||
|
# Jinak ho plácneme normálně za jeho levý horní roh |
||||
|
okno.blit(plocha, (x, y)) |
||||
|
|
||||
|
def hlavni_smycka(): |
||||
|
# --- PROMĚNNÉ PTÁKA --- |
||||
|
# Toto jsou proměnné, které se v čase mění, proto nejsou VELKÝMI PÍSMENY |
||||
|
ptak_x = 50 # Zleva je pták relativně blízko okraji |
||||
|
ptak_y = VYSKA_OKNA // 2 # Vertikálně (Y) ho položíme doprostřed okna |
||||
|
ptak_sirka = 30 |
||||
|
ptak_vyska = 30 |
||||
|
|
||||
|
# Fyzika |
||||
|
rychlost_padu = 0 # Zpočátku stojí pták na místě |
||||
|
gravitace = 0.5 # Gravitace každým snímkem tuto 'rychlost_padu' zvyšuje směrem DOLŮ (kladné hodnoty Y) |
||||
|
sila_skoku = -8 # Skok je prostě náhlé nastavení rychlosti do záporných hodnot, tedy NAHORU! |
||||
|
|
||||
|
# --- PROMĚNNÉ PŘEKÁŽEK (ZELENÉ TRUBKY) --- |
||||
|
trubka_sirka = 60 |
||||
|
mezera_mezi_trubkami = 160 # Jak velikou skulinou bude hráč létat? (v pixelech) |
||||
|
rychlost_posunu = 3 # Překážky neustále jedou směrem doleva, aby to vypadalo, že pták letí dopředu |
||||
|
|
||||
|
# Seznam do kterého budeme ukládat trubky. Použijeme slovníky {ključ: hodnota}. |
||||
|
# Např: {"x": 400, "y_stred": 300} ... 'y_stred' určuje, kde přesně je střed oné mezery kudy se letí |
||||
|
trubky = [] |
||||
|
|
||||
|
# Funkce k přidání nové trubky na určené X ose |
||||
|
def pridej_trubku(x_pozice): |
||||
|
# Nechceme mezeru úplně na kraji (nemožné projet), omezíme ji od 150px nahoře po 150px dole |
||||
|
stred_mezery = random.randint(150, VYSKA_OKNA - 150) |
||||
|
trubky.append({"x": x_pozice, "y_stred": stred_mezery}) |
||||
|
|
||||
|
# Přidáme první dvě trubky už před startem hry, nacházejí se zprvu mimo okno vpravo. |
||||
|
pridej_trubku(SIRKA_OKNA + 100) |
||||
|
pridej_trubku(SIRKA_OKNA + 400) # Další trubka má odstup 300 pixelů |
||||
|
|
||||
|
skore = 0 |
||||
|
|
||||
|
# STAVOVÝ AUTOMAT - skvělý trik v programování. Znalost toho "ve kterém jsme stavu" |
||||
|
# nám pomáhá měnit logiku. Hra se chová úplně jinak, když jsme v MENU než když právě HRAJEME. |
||||
|
stav_hry = "START" # Možnosti: "START", "HRA", "KONEC" |
||||
|
|
||||
|
# --- HERNÍ SMYČKA --- |
||||
|
while True: |
||||
|
|
||||
|
# 1. ČTENÍ VSTUPŮ (UDÁLOSTI) |
||||
|
for udalost in pygame.event.get(): |
||||
|
if udalost.type == pygame.QUIT: |
||||
|
pygame.quit() # Korektně ukončí knihovnu Pygame |
||||
|
sys.exit() # Ukončí celý Python skript |
||||
|
|
||||
|
# Pokud někdo zmáčkl klávesu na klávesnici (KEYDOWN) |
||||
|
if udalost.type == pygame.KEYDOWN: |
||||
|
if udalost.key == pygame.K_SPACE: # Speciálně zkoumáme MEZERNÍK |
||||
|
# Co dělá mezerník záleží na tom, v jakém STAVU je hra: |
||||
|
if stav_hry == "START": |
||||
|
stav_hry = "HRA" # Přepneme hru na aktivní |
||||
|
rychlost_padu = sila_skoku # Hned v první sekundě mu dáme skokový impuls |
||||
|
elif stav_hry == "HRA": |
||||
|
rychlost_padu = sila_skoku # Hráč skočil! (Pták vzletí nahoru) |
||||
|
elif stav_hry == "KONEC": |
||||
|
return # Vyskočíme z této funkce, což vyústí ve spuštění znova (viz úplně dole v kódu) |
||||
|
|
||||
|
# 2. LOGIKA A POHYB |
||||
|
# Zpracovává se jen pokud jsme ve stavu HRA |
||||
|
if stav_hry == "HRA": |
||||
|
|
||||
|
# Aplikace gravitace: Rychlost pádu je stále vyšší kladné číslo |
||||
|
rychlost_padu += gravitace |
||||
|
# Přičtením této rychlosti k souřadnici Y pták pomalu (a čím dál rychleji) "padá" na zem okna |
||||
|
ptak_y += rychlost_padu |
||||
|
|
||||
|
# Pohyb trubek doleva (zmenšujeme jejich osu X) |
||||
|
for t in trubky: |
||||
|
t["x"] -= rychlost_posunu |
||||
|
|
||||
|
# Kontrola první trubky v seznamu: Nevyjela nám náhodou úplně ven z okna doleva? |
||||
|
if trubky[0]["x"] < -trubka_sirka: |
||||
|
trubky.pop(0) # Odstraníme první trubku ze seznamu, už ji nikdy neuvidíme |
||||
|
|
||||
|
# Zjistíme, kde byla zhruba naposled vygenerovaná trubka, a přidáme novou ZA ni |
||||
|
posledni_trubka_x = trubky[-1]["x"] |
||||
|
pridej_trubku(posledni_trubka_x + 300) |
||||
|
|
||||
|
# Pokud trubka odjede ven, znamená to, že ji hráč musel úspěšně přeletět! |
||||
|
skore += 1 |
||||
|
|
||||
|
# --- Detekce nárazů (KOLIZE) --- |
||||
|
# Pro kontrolu jestli pták do něčeho vrazil používáme pygame.Rect (neviditelné hranice objektů) |
||||
|
ptak_rect = pygame.Rect(ptak_x, ptak_y, ptak_sirka, ptak_vyska) |
||||
|
|
||||
|
# 1) Náraz do stropu nebo do podlahy okna |
||||
|
if ptak_y < 0 or ptak_y + ptak_vyska > VYSKA_OKNA: |
||||
|
stav_hry = "KONEC" # Bum! Umřel. |
||||
|
|
||||
|
# 2) Náraz do jakékoliv překážky |
||||
|
for t in trubky: |
||||
|
# Vypočteme, kde končí 'horní' překážka |
||||
|
horni_vyska = t["y_stred"] - (mezera_mezi_trubkami // 2) |
||||
|
horni_rect = pygame.Rect(t["x"], 0, trubka_sirka, horni_vyska) |
||||
|
|
||||
|
# Vypočteme, kde začíná 'spodní' překážka (od středu + polovina mezery) |
||||
|
dolni_y = t["y_stred"] + (mezera_mezi_trubkami // 2) |
||||
|
# Výška spodní části je VYSKA_OKNA mínus počáteční Y souřadnice spodní trubky |
||||
|
dolni_rect = pygame.Rect(t["x"], dolni_y, trubka_sirka, VYSKA_OKNA - dolni_y) |
||||
|
|
||||
|
# Pokud se "obdélník ptáka" dotýká / prolíná (colliderect) s horní nebo dolní trubkou |
||||
|
if ptak_rect.colliderect(horni_rect) or ptak_rect.colliderect(dolni_rect): |
||||
|
stav_hry = "KONEC" |
||||
|
|
||||
|
# 3. KRESLENÍ (na 'platno') |
||||
|
# Smažeme stopu ze starého snímku překrytím čistou oblohou |
||||
|
okno.fill(MODRA_OBLOHA) |
||||
|
|
||||
|
# Nakreslení obou překážek |
||||
for t in trubky: |
for t in trubky: |
||||
t["x"] -= rychlost_posunu |
|
||||
|
|
||||
# Kontrola první trubky v seznamu: Nevyjela nám náhodou úplně ven z okna doleva? |
|
||||
if trubky[0]["x"] < -trubka_sirka: |
|
||||
trubky.pop(0) # Odstraníme první trubku ze seznamu, už ji nikdy neuvidíme |
|
||||
|
|
||||
# Zjistíme, kde byla zhruba naposled vygenerovaná trubka, a přidáme novou ZA ni |
|
||||
posledni_trubka_x = trubky[-1]["x"] |
|
||||
pridej_trubku(posledni_trubka_x + 300) |
|
||||
|
|
||||
# Pokud trubka odjede ven, znamená to, že ji hráč musel úspěšně přeletět! |
|
||||
skore += 1 |
|
||||
|
|
||||
# --- Detekce nárazů (KOLIZE) --- |
|
||||
# Pro kontrolu jestli pták do něčeho vrazil používáme pygame.Rect (neviditelné hranice objektů) |
|
||||
ptak_rect = pygame.Rect(ptak_x, ptak_y, ptak_sirka, ptak_vyska) |
|
||||
|
|
||||
# 1) Náraz do stropu nebo do podlahy okna |
|
||||
if ptak_y < 0 or ptak_y + ptak_vyska > VYSKA_OKNA: |
|
||||
stav_hry = "KONEC" # Bum! Umřel. |
|
||||
|
|
||||
# 2) Náraz do jakékoliv překážky |
|
||||
for t in trubky: |
|
||||
# Vypočteme, kde končí 'horní' překážka |
|
||||
|
# Horní trubka vykreslena od Y=0 (strop) |
||||
horni_vyska = t["y_stred"] - (mezera_mezi_trubkami // 2) |
horni_vyska = t["y_stred"] - (mezera_mezi_trubkami // 2) |
||||
horni_rect = pygame.Rect(t["x"], 0, trubka_sirka, horni_vyska) |
|
||||
|
|
||||
# Vypočteme, kde začíná 'spodní' překážka (od středu + polovina mezery) |
|
||||
|
pygame.draw.rect(okno, ZELENA, (t["x"], 0, trubka_sirka, horni_vyska)) |
||||
|
|
||||
|
# Dolní trubka vykreslena od konce mezery (dolni_y) až dolu k podlaze |
||||
dolni_y = t["y_stred"] + (mezera_mezi_trubkami // 2) |
dolni_y = t["y_stred"] + (mezera_mezi_trubkami // 2) |
||||
# Výška spodní části je VYSKA_OKNA mínus počáteční Y souřadnice spodní trubky |
|
||||
dolni_rect = pygame.Rect(t["x"], dolni_y, trubka_sirka, VYSKA_OKNA - dolni_y) |
|
||||
|
|
||||
# Pokud se "obdélník ptáka" dotýká / prolíná (colliderect) s horní nebo dolní trubkou |
|
||||
if ptak_rect.colliderect(horni_rect) or ptak_rect.colliderect(dolni_rect): |
|
||||
stav_hry = "KONEC" |
|
||||
|
|
||||
# 3. KRESLENÍ (na 'platno') |
|
||||
# Smažeme stopu ze starého snímku překrytím čistou oblohou |
|
||||
okno.fill(MODRA_OBLOHA) |
|
||||
|
|
||||
# Nakreslení obou překážek |
|
||||
for t in trubky: |
|
||||
# Horní trubka vykreslena od Y=0 (strop) |
|
||||
horni_vyska = t["y_stred"] - (mezera_mezi_trubkami // 2) |
|
||||
pygame.draw.rect(okno, ZELENA, (t["x"], 0, trubka_sirka, horni_vyska)) |
|
||||
|
|
||||
# Dolní trubka vykreslena od konce mezery (dolni_y) až dolu k podlaze |
|
||||
dolni_y = t["y_stred"] + (mezera_mezi_trubkami // 2) |
|
||||
pygame.draw.rect(okno, ZELENA, (t["x"], dolni_y, trubka_sirka, VYSKA_OKNA - dolni_y)) |
|
||||
|
|
||||
# Nakreslení postavy (Ptáka) |
|
||||
# int() ořezává desetinná čísla z fyziky do celých čísel pixelů, aby to šlo nakreslit |
|
||||
pygame.draw.rect(okno, ZLUTYP_PTAK, (ptak_x, int(ptak_y), ptak_sirka, ptak_vyska)) |
|
||||
|
|
||||
# --- ZOBRAZOVÁNÍ NÁPISŮ (UI) --- |
|
||||
if stav_hry == "START": |
|
||||
zobraz_text("Stiskni MEZERNÍK", font_maly, BILA, SIRKA_OKNA//2, VYSKA_OKNA//2 - 50, zarovnat_stred=True) |
|
||||
zobraz_text("pro začátek létání", font_maly, BILA, SIRKA_OKNA//2, VYSKA_OKNA//2, zarovnat_stred=True) |
|
||||
elif stav_hry == "HRA": |
|
||||
# Skóre nahoře |
|
||||
zobraz_text(str(skore), font_velky, BILA, SIRKA_OKNA//2, 50, zarovnat_stred=True) |
|
||||
elif stav_hry == "KONEC": |
|
||||
# GAME OVER statistiky po smrti |
|
||||
zobraz_text("GAME OVER", font_velky, CERVENA, SIRKA_OKNA//2, VYSKA_OKNA//2 - 50, zarovnat_stred=True) |
|
||||
zobraz_text(f"Skóre: {skore}", font_maly, BILA, SIRKA_OKNA//2, VYSKA_OKNA//2 + 10, zarovnat_stred=True) |
|
||||
zobraz_text("Stiskni MEZERNÍK pro restart", font_maly, CERNA, SIRKA_OKNA//2, VYSKA_OKNA//2 + 50, zarovnat_stred=True) |
|
||||
|
|
||||
# "Otoč" buffer z paměti na reálný monitor - tady se stane kouzlo a vše se zobrazí! |
|
||||
pygame.display.flip() |
|
||||
|
|
||||
# Pojistka pro správnou rychlost. Počká se milisekundu tak, abychom dodrželi stanovené FPS. |
|
||||
hodiny.tick(FPS) |
|
||||
|
|
||||
# Toto funguje tak, že hru spouštíme donekonečna. Když hráč zemře a dá mezerník, |
|
||||
# funkce 'hlavni_smycka()' skončí (return), ale díky tomuto cyklu 'while True' |
|
||||
# se obratem zavolá od znova z čistého stolu a se skórem nula. |
|
||||
if __name__ == "__main__": |
|
||||
while True: |
|
||||
hlavni_smycka() |
|
||||
|
pygame.draw.rect(okno, ZELENA, (t["x"], dolni_y, trubka_sirka, VYSKA_OKNA - dolni_y)) |
||||
|
|
||||
|
# Nakreslení postavy (Ptáka) |
||||
|
# int() ořezává desetinná čísla z fyziky do celých čísel pixelů, aby to šlo nakreslit |
||||
|
pygame.draw.rect(okno, ZLUTYP_PTAK, (ptak_x, int(ptak_y), ptak_sirka, ptak_vyska)) |
||||
|
|
||||
|
# --- ZOBRAZOVÁNÍ NÁPISŮ (UI) --- |
||||
|
if stav_hry == "START": |
||||
|
zobraz_text("Stiskni MEZERNÍK", font_maly, BILA, SIRKA_OKNA//2, VYSKA_OKNA//2 - 50, zarovnat_stred=True) |
||||
|
zobraz_text("pro začátek létání", font_maly, BILA, SIRKA_OKNA//2, VYSKA_OKNA//2, zarovnat_stred=True) |
||||
|
elif stav_hry == "HRA": |
||||
|
# Skóre nahoře |
||||
|
zobraz_text(str(skore), font_velky, BILA, SIRKA_OKNA//2, 50, zarovnat_stred=True) |
||||
|
elif stav_hry == "KONEC": |
||||
|
# GAME OVER statistiky po smrti |
||||
|
zobraz_text("GAME OVER", font_velky, CERVENA, SIRKA_OKNA//2, VYSKA_OKNA//2 - 50, zarovnat_stred=True) |
||||
|
zobraz_text(f"Skóre: {skore}", font_maly, BILA, SIRKA_OKNA//2, VYSKA_OKNA//2 + 10, zarovnat_stred=True) |
||||
|
zobraz_text("Stiskni MEZERNÍK pro restart", font_maly, CERNA, SIRKA_OKNA//2, VYSKA_OKNA//2 + 50, zarovnat_stred=True) |
||||
|
|
||||
|
# "Otoč" buffer z paměti na reálný monitor - tady se stane kouzlo a vše se zobrazí! |
||||
|
pygame.display.flip() |
||||
|
|
||||
|
# Pojistka pro správnou rychlost. Počká se milisekundu tak, abychom dodrželi stanovené FPS. |
||||
|
hodiny.tick(FPS) |
||||
|
# PŘIDÁNO PRO WEB: Dáme prohlížeči šanci překreslit obrazovku |
||||
|
await asyncio.sleep(0) |
||||
|
|
||||
|
# Toto funguje tak, že hru spouštíme donekonečna. Když hráč zemře a dá mezerník, |
||||
|
# funkce 'hlavni_smycka()' skončí (return), ale díky tomuto cyklu 'while True' |
||||
|
# se obratem zavolá od znova z čistého stolu a se skórem nula. |
||||
|
if __name__ == "__main__": |
||||
|
while True: |
||||
|
hlavni_smycka() |
||||
|
|
||||
|
|
||||
|
# PŘIDÁNO PRO WEB: Spuštění asynchronní hry |
||||
|
asyncio.run(main()) |
||||
@ -1,140 +1,148 @@ |
|||||
import pygame |
import pygame |
||||
|
import asyncio # PŘIDÁNO PRO WEB: importujeme asyncio pro neblokující smyčku |
||||
import sys |
import sys |
||||
import random |
import random |
||||
|
|
||||
# Inicializace knihovny Pygame - povinný krok. Nastaví všechny podmoduly zvuků, kreslení atd. |
# Inicializace knihovny Pygame - povinný krok. Nastaví všechny podmoduly zvuků, kreslení atd. |
||||
pygame.init() |
|
||||
|
|
||||
# --- Nastavení herního okna --- |
|
||||
SIRKA = 800 |
|
||||
VYSKA = 600 |
|
||||
# Vytvoření hlavního okna hry o dané velikosti v pixelech (obrazových bodech) |
|
||||
okno = pygame.display.set_mode((SIRKA, VYSKA)) |
|
||||
# Přidáme název hry do horní lišty softwarového okna |
|
||||
pygame.display.set_caption("Chytání jablek") |
|
||||
|
|
||||
# --- Paleta barev --- |
|
||||
# Barvy zadáváme přes číselné hodnoty Červené, Zelené a Modré - RGB (Red, Green, Blue) |
|
||||
CERNA = (0, 0, 0) # Černá je absence všech barev |
|
||||
ZELENA = (0, 255, 0) # Barva hráče (košíku) |
|
||||
CERVENA = (255, 0, 0) # Barva padajícího jablka |
|
||||
BILA = (255, 255, 255) # Použijeme pro texty |
|
||||
|
|
||||
# Vytvoření objektu 'Clock', který řídí rychlost, aby hra nejela super rychle na výkonných PC |
|
||||
hodiny = pygame.time.Clock() |
|
||||
|
|
||||
# Tvorba "fontu" neboli stylu písma pro vypisování skóre atd. |
|
||||
font = pygame.font.SysFont("Arial", 36) |
|
||||
|
|
||||
# --- Proměnné Hráče (Zelený Košík dole) --- |
|
||||
hrac_sirka = 100 |
|
||||
hrac_vyska = 20 |
|
||||
# Kde bude stát na začátku? X=vodorovně (uprostřed mínus půlka hráče), Y=svisle (úplně dole) |
|
||||
hrac_x = SIRKA // 2 - hrac_sirka // 2 |
|
||||
hrac_y = VYSKA - 40 |
|
||||
# Rychlost hráče říká, o kolik pixelů se posune, když drží šipku |
|
||||
rychlost_hrace = 8 |
|
||||
|
|
||||
# --- Proměnné Jablka (Červený čtvereček shora) --- |
|
||||
jablko_velikost = 30 |
|
||||
# Vybereme mu náhodnou souřadnici X kdekoliv od okraje do okraje |
|
||||
jablko_x = random.randint(0, SIRKA - jablko_velikost) |
|
||||
# Y mu dáme do ZÁPORNÝCH HODNOT! To znamená, že začíná nahoře schovaný mimo obrazovku! |
|
||||
jablko_y = -jablko_velikost |
|
||||
rychlost_jablka = 5 # Jak rychle jablko letí dolů? |
|
||||
|
|
||||
# Herní statistiky |
|
||||
skore = 0 |
|
||||
zivoty = 3 |
|
||||
|
|
||||
# Pravdivostní proměnná (boolean) udávající, že aplikace běží. |
|
||||
bezime = True |
|
||||
|
|
||||
# ========================================== |
|
||||
# HLAVNÍ HERNÍ SMYČKA (GAME LOOP) |
|
||||
# Vše, co se děje ve hře se opakuje zde, zhruba 60x za vteřinu |
|
||||
# ========================================== |
|
||||
while bezime: |
|
||||
|
|
||||
# 1. ČTENÍ UDÁLOSTÍ |
|
||||
# Kontrolujeme, co uživatel dělá - například zda nechce program zavřít |
|
||||
for udalost in pygame.event.get(): |
|
||||
if udalost.type == pygame.QUIT: |
|
||||
bezime = False # Když klikne na křížek, ukončíme smyčku tím, že z 'bezime' uděláme False |
|
||||
|
|
||||
# Hra se fyzicky hraje jenom dokud máme nějaké životy |
|
||||
if zivoty > 0: |
|
||||
|
|
||||
# 2. LOGIKA HRÁČE: Čteme stisknutí kláves v reálném čase |
|
||||
# pygame.key.get_pressed() vrací obrovský seznam s hodnotami True nebo False u VŠECH tlačítek na klávesnici |
|
||||
klavesy = pygame.key.get_pressed() |
|
||||
|
|
||||
# Pokud drží levou šipku a navíc ještě nenarazil do levé zdi (x > 0) |
|
||||
if klavesy[pygame.K_LEFT] and hrac_x > 0: |
|
||||
hrac_x -= rychlost_hrace # Posuneme ho doleva (odečteme x) |
|
||||
|
|
||||
# Pokud drží pravou šipku a nenarazil do pravé zdi (Zde musíme odečíst šířku postavy, abychom měřili od kraje) |
|
||||
if klavesy[pygame.K_RIGHT] and hrac_x < SIRKA - hrac_sirka: |
|
||||
hrac_x += rychlost_hrace # Posuneme ho doprava (přičteme x) |
|
||||
|
|
||||
# 3. LOGIKA PADÁNÍ JABLKA |
|
||||
# Osa Y jde v počítačové grafice "shora dolů", takže se zvyšující se Y jablko padá níže |
|
||||
jablko_y += rychlost_jablka |
|
||||
|
|
||||
# --- KOLIZE (Dotyk hráče s jablkem) --- |
|
||||
# Abychom snadno zjistili, jestli se dotýkají, "obalíme" je virtuálními obdélníky (Rect) |
|
||||
hrac_rect = pygame.Rect(hrac_x, hrac_y, hrac_sirka, hrac_vyska) |
|
||||
jablko_rect = pygame.Rect(jablko_x, jablko_y, jablko_velikost, jablko_velikost) |
|
||||
|
|
||||
# Pokud se tyto dva obdélníky protnou/srazí... Hráč ho chytil! |
|
||||
if hrac_rect.colliderect(jablko_rect): |
|
||||
skore += 1 # Dáme mu bod |
|
||||
rychlost_jablka += 0.2 # Mírně hru zrychlíme, ať je to těžší! |
|
||||
|
|
||||
# Vygenerujeme znovu to stejné jablko úplně nahoře na nové X pozici |
|
||||
jablko_y = -jablko_velikost |
|
||||
jablko_x = random.randint(0, SIRKA - jablko_velikost) |
|
||||
|
|
||||
# 4. LOGIKA PŘEHLÉDNUTÍ JABLKA |
|
||||
# Co se stane, když jablko propadne až za spodní hranu obrazovky? |
|
||||
elif jablko_y > VYSKA: |
|
||||
zivoty -= 1 # Hráč přišel o život |
|
||||
|
|
||||
# Musíme jablko i přesto respawnovat zpět nahoru, aby hra mohla pokračovat |
|
||||
jablko_y = -jablko_velikost |
|
||||
jablko_x = random.randint(0, SIRKA - jablko_velikost) |
|
||||
|
|
||||
# 5. KRESLENÍ VŠEHO NA OBRAZOVKU |
|
||||
# Každý snímek musíme celé okno zalít černou barvou, jinak by postavička dělala šmouhy, jak se hýbe |
|
||||
okno.fill(CERNA) |
|
||||
|
|
||||
# Kreslíme jenom to, co je zrovna relevantní |
|
||||
if zivoty > 0: |
|
||||
# Vykreslení obdélníku hráče |
|
||||
pygame.draw.rect(okno, ZELENA, (hrac_x, hrac_y, hrac_sirka, hrac_vyska)) |
|
||||
# Vykreslení obdélníku jablka |
|
||||
pygame.draw.rect(okno, CERVENA, (jablko_x, jablko_y, jablko_velikost, jablko_velikost)) |
|
||||
|
|
||||
# Převedeme textovou proměnnou (string) do obrázku (surface) |
|
||||
text_skore = font.render(f"Skóre: {skore}", True, BILA) |
|
||||
text_zivoty = font.render(f"Životy: {zivoty}", True, BILA) |
|
||||
|
|
||||
# 'Nalepíme' (blit) tyto obrázky textů na plátno (obrazovku) |
|
||||
okno.blit(text_skore, (10, 10)) |
|
||||
okno.blit(text_zivoty, (SIRKA - 150, 10)) |
|
||||
|
|
||||
else: |
|
||||
# Hráč nemá životy, je KONEC HRY! Kreslíme varovný text doprostřed. |
|
||||
text_konec = font.render(f"KONEC HRY! Tvé skóre: {skore}", True, CERVENA) |
|
||||
okno.blit(text_konec, (SIRKA//2 - 180, VYSKA//2)) |
|
||||
|
|
||||
# --- FINALIZACE SNÍMKU --- |
|
||||
# Toto pošle všechny ty připravené obdélníčky a texty z paměti počítače rovnou do monitoru! |
|
||||
pygame.display.flip() |
|
||||
|
|
||||
# A tohle vynutí pauzu natolik dlouhou, abychom za vteřinu nepřekročili 60 smyček (60 FPS) |
|
||||
hodiny.tick(60) |
|
||||
|
|
||||
# Pokud 'bezime' skočí na False a vyskočíme ze smyčky 'while', musíme bezpečně zhasnout! |
|
||||
pygame.quit() |
|
||||
sys.exit() |
|
||||
|
async def main(): # PŘIDÁNO PRO WEB: Zabalíme celou hru do asynchronní funkce |
||||
|
pygame.init() |
||||
|
|
||||
|
# --- Nastavení herního okna --- |
||||
|
SIRKA = 800 |
||||
|
VYSKA = 600 |
||||
|
# Vytvoření hlavního okna hry o dané velikosti v pixelech (obrazových bodech) |
||||
|
okno = pygame.display.set_mode((SIRKA, VYSKA)) |
||||
|
# Přidáme název hry do horní lišty softwarového okna |
||||
|
pygame.display.set_caption("Chytání jablek") |
||||
|
|
||||
|
# --- Paleta barev --- |
||||
|
# Barvy zadáváme přes číselné hodnoty Červené, Zelené a Modré - RGB (Red, Green, Blue) |
||||
|
CERNA = (0, 0, 0) # Černá je absence všech barev |
||||
|
ZELENA = (0, 255, 0) # Barva hráče (košíku) |
||||
|
CERVENA = (255, 0, 0) # Barva padajícího jablka |
||||
|
BILA = (255, 255, 255) # Použijeme pro texty |
||||
|
|
||||
|
# Vytvoření objektu 'Clock', který řídí rychlost, aby hra nejela super rychle na výkonných PC |
||||
|
hodiny = pygame.time.Clock() |
||||
|
|
||||
|
# Tvorba "fontu" neboli stylu písma pro vypisování skóre atd. |
||||
|
font = pygame.font.SysFont("Arial", 36) |
||||
|
|
||||
|
# --- Proměnné Hráče (Zelený Košík dole) --- |
||||
|
hrac_sirka = 100 |
||||
|
hrac_vyska = 20 |
||||
|
# Kde bude stát na začátku? X=vodorovně (uprostřed mínus půlka hráče), Y=svisle (úplně dole) |
||||
|
hrac_x = SIRKA // 2 - hrac_sirka // 2 |
||||
|
hrac_y = VYSKA - 40 |
||||
|
# Rychlost hráče říká, o kolik pixelů se posune, když drží šipku |
||||
|
rychlost_hrace = 8 |
||||
|
|
||||
|
# --- Proměnné Jablka (Červený čtvereček shora) --- |
||||
|
jablko_velikost = 30 |
||||
|
# Vybereme mu náhodnou souřadnici X kdekoliv od okraje do okraje |
||||
|
jablko_x = random.randint(0, SIRKA - jablko_velikost) |
||||
|
# Y mu dáme do ZÁPORNÝCH HODNOT! To znamená, že začíná nahoře schovaný mimo obrazovku! |
||||
|
jablko_y = -jablko_velikost |
||||
|
rychlost_jablka = 5 # Jak rychle jablko letí dolů? |
||||
|
|
||||
|
# Herní statistiky |
||||
|
skore = 0 |
||||
|
zivoty = 3 |
||||
|
|
||||
|
# Pravdivostní proměnná (boolean) udávající, že aplikace běží. |
||||
|
bezime = True |
||||
|
|
||||
|
# ========================================== |
||||
|
# HLAVNÍ HERNÍ SMYČKA (GAME LOOP) |
||||
|
# Vše, co se děje ve hře se opakuje zde, zhruba 60x za vteřinu |
||||
|
# ========================================== |
||||
|
while bezime: |
||||
|
|
||||
|
# 1. ČTENÍ UDÁLOSTÍ |
||||
|
# Kontrolujeme, co uživatel dělá - například zda nechce program zavřít |
||||
|
for udalost in pygame.event.get(): |
||||
|
if udalost.type == pygame.QUIT: |
||||
|
bezime = False # Když klikne na křížek, ukončíme smyčku tím, že z 'bezime' uděláme False |
||||
|
|
||||
|
# Hra se fyzicky hraje jenom dokud máme nějaké životy |
||||
|
if zivoty > 0: |
||||
|
|
||||
|
# 2. LOGIKA HRÁČE: Čteme stisknutí kláves v reálném čase |
||||
|
# pygame.key.get_pressed() vrací obrovský seznam s hodnotami True nebo False u VŠECH tlačítek na klávesnici |
||||
|
klavesy = pygame.key.get_pressed() |
||||
|
|
||||
|
# Pokud drží levou šipku a navíc ještě nenarazil do levé zdi (x > 0) |
||||
|
if klavesy[pygame.K_LEFT] and hrac_x > 0: |
||||
|
hrac_x -= rychlost_hrace # Posuneme ho doleva (odečteme x) |
||||
|
|
||||
|
# Pokud drží pravou šipku a nenarazil do pravé zdi (Zde musíme odečíst šířku postavy, abychom měřili od kraje) |
||||
|
if klavesy[pygame.K_RIGHT] and hrac_x < SIRKA - hrac_sirka: |
||||
|
hrac_x += rychlost_hrace # Posuneme ho doprava (přičteme x) |
||||
|
|
||||
|
# 3. LOGIKA PADÁNÍ JABLKA |
||||
|
# Osa Y jde v počítačové grafice "shora dolů", takže se zvyšující se Y jablko padá níže |
||||
|
jablko_y += rychlost_jablka |
||||
|
|
||||
|
# --- KOLIZE (Dotyk hráče s jablkem) --- |
||||
|
# Abychom snadno zjistili, jestli se dotýkají, "obalíme" je virtuálními obdélníky (Rect) |
||||
|
hrac_rect = pygame.Rect(hrac_x, hrac_y, hrac_sirka, hrac_vyska) |
||||
|
jablko_rect = pygame.Rect(jablko_x, jablko_y, jablko_velikost, jablko_velikost) |
||||
|
|
||||
|
# Pokud se tyto dva obdélníky protnou/srazí... Hráč ho chytil! |
||||
|
if hrac_rect.colliderect(jablko_rect): |
||||
|
skore += 1 # Dáme mu bod |
||||
|
rychlost_jablka += 0.2 # Mírně hru zrychlíme, ať je to těžší! |
||||
|
|
||||
|
# Vygenerujeme znovu to stejné jablko úplně nahoře na nové X pozici |
||||
|
jablko_y = -jablko_velikost |
||||
|
jablko_x = random.randint(0, SIRKA - jablko_velikost) |
||||
|
|
||||
|
# 4. LOGIKA PŘEHLÉDNUTÍ JABLKA |
||||
|
# Co se stane, když jablko propadne až za spodní hranu obrazovky? |
||||
|
elif jablko_y > VYSKA: |
||||
|
zivoty -= 1 # Hráč přišel o život |
||||
|
|
||||
|
# Musíme jablko i přesto respawnovat zpět nahoru, aby hra mohla pokračovat |
||||
|
jablko_y = -jablko_velikost |
||||
|
jablko_x = random.randint(0, SIRKA - jablko_velikost) |
||||
|
|
||||
|
# 5. KRESLENÍ VŠEHO NA OBRAZOVKU |
||||
|
# Každý snímek musíme celé okno zalít černou barvou, jinak by postavička dělala šmouhy, jak se hýbe |
||||
|
okno.fill(CERNA) |
||||
|
|
||||
|
# Kreslíme jenom to, co je zrovna relevantní |
||||
|
if zivoty > 0: |
||||
|
# Vykreslení obdélníku hráče |
||||
|
pygame.draw.rect(okno, ZELENA, (hrac_x, hrac_y, hrac_sirka, hrac_vyska)) |
||||
|
# Vykreslení obdélníku jablka |
||||
|
pygame.draw.rect(okno, CERVENA, (jablko_x, jablko_y, jablko_velikost, jablko_velikost)) |
||||
|
|
||||
|
# Převedeme textovou proměnnou (string) do obrázku (surface) |
||||
|
text_skore = font.render(f"Skóre: {skore}", True, BILA) |
||||
|
text_zivoty = font.render(f"Životy: {zivoty}", True, BILA) |
||||
|
|
||||
|
# 'Nalepíme' (blit) tyto obrázky textů na plátno (obrazovku) |
||||
|
okno.blit(text_skore, (10, 10)) |
||||
|
okno.blit(text_zivoty, (SIRKA - 150, 10)) |
||||
|
|
||||
|
else: |
||||
|
# Hráč nemá životy, je KONEC HRY! Kreslíme varovný text doprostřed. |
||||
|
text_konec = font.render(f"KONEC HRY! Tvé skóre: {skore}", True, CERVENA) |
||||
|
okno.blit(text_konec, (SIRKA//2 - 180, VYSKA//2)) |
||||
|
|
||||
|
# --- FINALIZACE SNÍMKU --- |
||||
|
# Toto pošle všechny ty připravené obdélníčky a texty z paměti počítače rovnou do monitoru! |
||||
|
pygame.display.flip() |
||||
|
|
||||
|
# A tohle vynutí pauzu natolik dlouhou, abychom za vteřinu nepřekročili 60 smyček (60 FPS) |
||||
|
hodiny.tick(60) |
||||
|
# PŘIDÁNO PRO WEB: Dáme prohlížeči šanci překreslit obrazovku |
||||
|
await asyncio.sleep(0) |
||||
|
|
||||
|
# Pokud 'bezime' skočí na False a vyskočíme ze smyčky 'while', musíme bezpečně zhasnout! |
||||
|
pygame.quit() |
||||
|
sys.exit() |
||||
|
|
||||
|
|
||||
|
# PŘIDÁNO PRO WEB: Spuštění asynchronní hry |
||||
|
asyncio.run(main()) |
||||
@ -1,192 +1,200 @@ |
|||||
import pygame |
import pygame |
||||
|
import asyncio # PŘIDÁNO PRO WEB: importujeme asyncio pro neblokující smyčku |
||||
import sys |
import sys |
||||
|
|
||||
# pygame.init() spustí vnitřní motory knihovny Pygame. Bez toho nejde ani otevřít okno. |
|
||||
pygame.init() |
|
||||
|
|
||||
# --- NASTAVENÍ OKNA --- |
|
||||
SIRKA = 800 |
|
||||
VYSKA = 600 |
|
||||
# Okno upevníme na tyto rozměry |
|
||||
okno = pygame.display.set_mode((SIRKA, VYSKA)) |
|
||||
pygame.display.set_caption("Pong - Plná Hra pro dva hráče") |
|
||||
|
|
||||
# --- BARVY --- |
|
||||
BILA = (255, 255, 255) |
|
||||
CERNA = (0, 0, 0) |
|
||||
ZELENA = (0, 255, 0) |
|
||||
|
|
||||
# Herní hodiny omezují, aby náš počítač nehrál 1000x za vteřinu, čímž by hra byla nehratelná. |
|
||||
hodiny = pygame.time.Clock() |
|
||||
|
|
||||
# Tvorba písem (pro vykreslování textu potřebujeme vždy nastavit velikost a typ písma) |
|
||||
font_velky = pygame.font.SysFont("Arial", 50) |
|
||||
font_maly = pygame.font.SysFont("Arial", 30) |
|
||||
|
|
||||
# --- STAVOVÝ AUTOMAT (State Machine) --- |
|
||||
# Trik profesionálů: Hra je vždy v nějakém "Stavu" (Menu, Hra, Konec). |
|
||||
# Podle tohoto čísla pak naše hlavní smyčka pozná, jaké má platit rozvržení obrazovky a pravidla. |
|
||||
STAV_MENU = 0 |
|
||||
STAV_HRA = 1 |
|
||||
STAV_KONEC = 2 |
|
||||
stav = STAV_MENU # Začínáme logicky v hlavním menu |
|
||||
|
|
||||
# --- PROMĚNNÉ HRÁČŮ (Pálky) A MÍČKU --- |
|
||||
palka_sirka = 15 |
|
||||
palka_vyska = 100 |
|
||||
rychlost_palky = 7 |
|
||||
micek_velikost = 15 |
|
||||
|
|
||||
# Funkce, která vrátí všechny proměnné na začátek. Použije se po gólu nebo při startu nové hry. |
|
||||
def reset_hry(): |
|
||||
# Klíčové slovo 'global' musíme použít vždycky, když uvnitř Funkce chceme MĚNIT proměnnou, |
|
||||
# která byla vytvořena venku (na nejvyšší úrovni skriptu). |
|
||||
global hrac1_y, hrac2_y, skore1, skore2, micek_x, micek_y, micek_rychlost_x, micek_rychlost_y |
|
||||
|
|
||||
# Hráče zarovnáme na střed Y osy (Výška okna / 2 - polovina velikosti hráče) |
|
||||
hrac1_y = VYSKA // 2 - palka_vyska // 2 |
|
||||
hrac2_y = VYSKA // 2 - palka_vyska // 2 |
|
||||
skore1 = 0 |
|
||||
skore2 = 0 |
|
||||
|
|
||||
# Míček přesně doprostřed obou os X a Y |
|
||||
micek_x = SIRKA // 2 - micek_velikost // 2 |
|
||||
micek_y = VYSKA // 2 - micek_velikost // 2 |
|
||||
|
|
||||
# Míček poletí rychlostí 5 bodů za snímek šikmo dolů doprava |
|
||||
micek_rychlost_x = 5 |
|
||||
micek_rychlost_y = 5 |
|
||||
|
|
||||
# Okamžitě to zavoláme, abychom proměnným výše vložili výchozí čísla hned při spuštění programu |
|
||||
reset_hry() |
|
||||
|
|
||||
bezime = True |
|
||||
# Hlavní cyklus hry, který točí snímek za snímkem donekonečna, dokud aplikaci neukončíme. |
|
||||
while bezime: |
|
||||
|
|
||||
# 1. ZPRACOVÁNÍ UDÁLOSTÍ |
|
||||
# Procházíme např. stisky tlačítek myši, křížek pro zavření okna, jedno-stisky kláves |
|
||||
for udalost in pygame.event.get(): |
|
||||
if udalost.type == pygame.QUIT: |
|
||||
bezime = False # Ukončíme while-cyklus |
|
||||
|
|
||||
# Zjišťujeme, zda se NESTISKNULA klávesa. Tohle se provede jen JEDNOU, i když ji uživatel drží! |
|
||||
if udalost.type == pygame.KEYDOWN: |
|
||||
if stav == STAV_MENU and udalost.key == pygame.K_SPACE: |
|
||||
# Jsme v menu, hráč stiskl MEZERNÍK. Přepneme hru do režimu STAV_HRA! |
|
||||
stav = STAV_HRA |
|
||||
elif stav == STAV_KONEC and udalost.key == pygame.K_SPACE: |
|
||||
# Jsme na obrazovce s vítězem, hráč chce hrát znovu. |
|
||||
reset_hry() |
|
||||
stav = STAV_HRA |
|
||||
|
|
||||
# Oproti tomu toto zjistí AKTUÁLNĚ DRŽENÉ klávesy v tomto zlomku vteřiny |
|
||||
klavesy = pygame.key.get_pressed() |
|
||||
|
|
||||
# 2. HERNÍ LOGIKA (HÝBÁNÍ SE) |
|
||||
# Tohle se provede JEN a POUZE, pokud zrovna hrajeme zápas! |
|
||||
if stav == STAV_HRA: |
|
||||
# Ovládání Hráče 1 vlevo (Klávesy W a S pro pohyb nahoru/dolů) |
|
||||
if klavesy[pygame.K_w] and hrac1_y > 0: |
|
||||
hrac1_y -= rychlost_palky |
|
||||
# Dolů nemůže víc, než je výška okna zmenšená o jeho vlastní výšku pálky |
|
||||
if klavesy[pygame.K_s] and hrac1_y < VYSKA - palka_vyska: |
|
||||
hrac1_y += rychlost_palky |
|
||||
|
|
||||
# Ovládání Hráče 2 vpravo (Šipky Nahoru a Dolů) |
|
||||
if klavesy[pygame.K_UP] and hrac2_y > 0: |
|
||||
hrac2_y -= rychlost_palky |
|
||||
if klavesy[pygame.K_DOWN] and hrac2_y < VYSKA - palka_vyska: |
|
||||
hrac2_y += rychlost_palky |
|
||||
|
|
||||
# Míček se za každou smyčku kousek posune. Tím vzniká dojem pohybu. |
|
||||
micek_x += micek_rychlost_x |
|
||||
micek_y += micek_rychlost_y |
|
||||
|
|
||||
# --- Fyzika: Odraz míčku od stropu a podlahy --- |
|
||||
# Trik: Vynásobením rychlosti číslem -1 se obrátí znaménko. |
|
||||
# Z kladné rychlosti (+5 padá dolů) se stane záporná (-5 letí nahoru). Odrazil se! |
|
||||
if micek_y <= 0 or micek_y >= VYSKA - micek_velikost: |
|
||||
micek_rychlost_y *= -1 |
|
||||
|
|
||||
# --- Fyzika: Odraz od pálek hráčů --- |
|
||||
# Abychom to jednoduše spočítali, vyrobíme si takzvané virtuální Obdélníky (Rect) |
|
||||
rect_micek = pygame.Rect(micek_x, micek_y, micek_velikost, micek_velikost) |
|
||||
|
|
||||
# Pálku Hráče 1 vykreslujeme fixně 30 pixelů od levého okraje |
|
||||
rect_hrac1 = pygame.Rect(30, hrac1_y, palka_sirka, palka_vyska) |
|
||||
# Pálku Hráče 2 fixně na pravé straně. (SIRKA - 30 od kraje - velikost pálky) |
|
||||
rect_hrac2 = pygame.Rect(SIRKA - 30 - palka_sirka, hrac2_y, palka_sirka, palka_vyska) |
|
||||
|
|
||||
# Metoda 'colliderect' doslova zkoumá, jestli se tyto obdélníky v daný moment nepřekrývají |
|
||||
if rect_micek.colliderect(rect_hrac1) or rect_micek.colliderect(rect_hrac2): |
|
||||
# Odrazil se od pálky! Obrátíme jeho směr po ose X a ještě ho mírně zrychlíme (* -1.1) |
|
||||
micek_rychlost_x *= -1.1 |
|
||||
|
|
||||
# --- Vyhodnocení Gólu --- |
|
||||
if micek_x < 0: |
|
||||
# Přeletěl za levý okraj (Hráč 1 ho nezachytil). Bod pro Hráče 2! |
|
||||
skore2 += 1 |
|
||||
micek_x, micek_y = SIRKA // 2, VYSKA // 2 # Teleportace míčku na prostředek |
|
||||
micek_rychlost_x = 5 # Reset rychlosti a ať letí k hráči co dostal gól (doprava) |
|
||||
elif micek_x > SIRKA: |
|
||||
# Přeletěl vpravo |
|
||||
skore1 += 1 |
|
||||
micek_x, micek_y = SIRKA // 2, VYSKA // 2 |
|
||||
micek_rychlost_x = -5 # Směr letu doleva |
|
||||
|
|
||||
# --- Konec Zápasu --- |
|
||||
# Zápas končí, jakmile někdo dosáhne 5 bodů |
|
||||
if skore1 >= 5 or skore2 >= 5: |
|
||||
stav = STAV_KONEC |
|
||||
|
|
||||
# 3. KRESLENÍ GRAFIKY |
|
||||
# Vždy smažeme starou stopu vyplněním okna čistě černou barvou |
|
||||
okno.fill(CERNA) |
|
||||
|
|
||||
# Co na obrazovce bude záleží zcela na tom, ve kterém STAVU náš Stavový Automat právě je! |
|
||||
if stav == STAV_MENU: |
|
||||
# Vykreslení Menu. .render převede nápis z textu do "razítka" |
|
||||
nadpis = font_velky.render("PONG", True, BILA) |
|
||||
navod = font_maly.render("Stiskni MEZERNÍK pro start", True, ZELENA) |
|
||||
|
|
||||
# Tyto razítka .blitnem (otiskneme) na plátno. Výpočet (SIRKA//2 - nadpis.get_width()//2) zarovná nápis čistě doprostřed obrazovky. |
|
||||
okno.blit(nadpis, (SIRKA//2 - nadpis.get_width()//2, 200)) |
|
||||
okno.blit(navod, (SIRKA//2 - navod.get_width()//2, 300)) |
|
||||
|
|
||||
elif stav == STAV_HRA: |
|
||||
# Jsme ve hře. Vykreslíme půlící čáru (síť v Pongu). 'aaline' je hladká čára. |
|
||||
pygame.draw.aaline(okno, BILA, (SIRKA // 2, 0), (SIRKA // 2, VYSKA)) |
|
||||
|
|
||||
# Nakreslíme pálky na místa, která předtím logika vypočítala |
|
||||
pygame.draw.rect(okno, BILA, rect_hrac1) |
|
||||
pygame.draw.rect(okno, BILA, rect_hrac2) |
|
||||
|
|
||||
# Míček vykreslíme jako 'ellipsu' napasovanou na ten neviditelný 'rect_micek' obdélník. Vznikne tak kruh. |
|
||||
pygame.draw.ellipse(okno, BILA, rect_micek) |
|
||||
|
|
||||
# Kreslení obou skóre úplně nahoru |
|
||||
text_skore = font_velky.render(f"{skore1} {skore2}", True, BILA) |
|
||||
okno.blit(text_skore, (SIRKA // 2 - text_skore.get_width() // 2, 20)) |
|
||||
|
|
||||
elif stav == STAV_KONEC: |
|
||||
# Zjištění, kdo vyhrál pomocí tzv. ternárního if (vítězem je "Hráč 1" POKUD má skore1>=5, JINAK je to "Hráč 2") |
|
||||
vitez = "Hráč 1" if skore1 >= 5 else "Hráč 2" |
|
||||
|
|
||||
# Razítka s nápisy |
|
||||
text_vitez = font_velky.render(f"{vitez} vyhrál!", True, ZELENA) |
|
||||
text_restart = font_maly.render("Stiskni MEZERNÍK pro novou hru", True, BILA) |
|
||||
|
|
||||
# Obtisknutí doprostřed okna |
|
||||
okno.blit(text_vitez, (SIRKA//2 - text_vitez.get_width()//2, 200)) |
|
||||
okno.blit(text_restart, (SIRKA//2 - text_restart.get_width()//2, 300)) |
|
||||
|
|
||||
# Obraz je složený nanečisto v paměti. .flip() ho teprve natvrdo pošle na monitor! |
|
||||
pygame.display.flip() |
|
||||
|
|
||||
# Pauza na udržení stabilních 60 FPS |
|
||||
hodiny.tick(60) |
|
||||
|
|
||||
# Konec programu |
|
||||
pygame.quit() |
|
||||
sys.exit() |
|
||||
|
async def main(): # PŘIDÁNO PRO WEB: Zabalíme celou hru do asynchronní funkce |
||||
|
# pygame.init() spustí vnitřní motory knihovny Pygame. Bez toho nejde ani otevřít okno. |
||||
|
pygame.init() |
||||
|
|
||||
|
# --- NASTAVENÍ OKNA --- |
||||
|
SIRKA = 800 |
||||
|
VYSKA = 600 |
||||
|
# Okno upevníme na tyto rozměry |
||||
|
okno = pygame.display.set_mode((SIRKA, VYSKA)) |
||||
|
pygame.display.set_caption("Pong - Plná Hra pro dva hráče") |
||||
|
|
||||
|
# --- BARVY --- |
||||
|
BILA = (255, 255, 255) |
||||
|
CERNA = (0, 0, 0) |
||||
|
ZELENA = (0, 255, 0) |
||||
|
|
||||
|
# Herní hodiny omezují, aby náš počítač nehrál 1000x za vteřinu, čímž by hra byla nehratelná. |
||||
|
hodiny = pygame.time.Clock() |
||||
|
|
||||
|
# Tvorba písem (pro vykreslování textu potřebujeme vždy nastavit velikost a typ písma) |
||||
|
font_velky = pygame.font.SysFont("Arial", 50) |
||||
|
font_maly = pygame.font.SysFont("Arial", 30) |
||||
|
|
||||
|
# --- STAVOVÝ AUTOMAT (State Machine) --- |
||||
|
# Trik profesionálů: Hra je vždy v nějakém "Stavu" (Menu, Hra, Konec). |
||||
|
# Podle tohoto čísla pak naše hlavní smyčka pozná, jaké má platit rozvržení obrazovky a pravidla. |
||||
|
STAV_MENU = 0 |
||||
|
STAV_HRA = 1 |
||||
|
STAV_KONEC = 2 |
||||
|
stav = STAV_MENU # Začínáme logicky v hlavním menu |
||||
|
|
||||
|
# --- PROMĚNNÉ HRÁČŮ (Pálky) A MÍČKU --- |
||||
|
palka_sirka = 15 |
||||
|
palka_vyska = 100 |
||||
|
rychlost_palky = 7 |
||||
|
micek_velikost = 15 |
||||
|
|
||||
|
# Funkce, která vrátí všechny proměnné na začátek. Použije se po gólu nebo při startu nové hry. |
||||
|
def reset_hry(): |
||||
|
# Klíčové slovo 'global' musíme použít vždycky, když uvnitř Funkce chceme MĚNIT proměnnou, |
||||
|
# která byla vytvořena venku (na nejvyšší úrovni skriptu). |
||||
|
global hrac1_y, hrac2_y, skore1, skore2, micek_x, micek_y, micek_rychlost_x, micek_rychlost_y |
||||
|
|
||||
|
# Hráče zarovnáme na střed Y osy (Výška okna / 2 - polovina velikosti hráče) |
||||
|
hrac1_y = VYSKA // 2 - palka_vyska // 2 |
||||
|
hrac2_y = VYSKA // 2 - palka_vyska // 2 |
||||
|
skore1 = 0 |
||||
|
skore2 = 0 |
||||
|
|
||||
|
# Míček přesně doprostřed obou os X a Y |
||||
|
micek_x = SIRKA // 2 - micek_velikost // 2 |
||||
|
micek_y = VYSKA // 2 - micek_velikost // 2 |
||||
|
|
||||
|
# Míček poletí rychlostí 5 bodů za snímek šikmo dolů doprava |
||||
|
micek_rychlost_x = 5 |
||||
|
micek_rychlost_y = 5 |
||||
|
|
||||
|
# Okamžitě to zavoláme, abychom proměnným výše vložili výchozí čísla hned při spuštění programu |
||||
|
reset_hry() |
||||
|
|
||||
|
bezime = True |
||||
|
# Hlavní cyklus hry, který točí snímek za snímkem donekonečna, dokud aplikaci neukončíme. |
||||
|
while bezime: |
||||
|
|
||||
|
# 1. ZPRACOVÁNÍ UDÁLOSTÍ |
||||
|
# Procházíme např. stisky tlačítek myši, křížek pro zavření okna, jedno-stisky kláves |
||||
|
for udalost in pygame.event.get(): |
||||
|
if udalost.type == pygame.QUIT: |
||||
|
bezime = False # Ukončíme while-cyklus |
||||
|
|
||||
|
# Zjišťujeme, zda se NESTISKNULA klávesa. Tohle se provede jen JEDNOU, i když ji uživatel drží! |
||||
|
if udalost.type == pygame.KEYDOWN: |
||||
|
if stav == STAV_MENU and udalost.key == pygame.K_SPACE: |
||||
|
# Jsme v menu, hráč stiskl MEZERNÍK. Přepneme hru do režimu STAV_HRA! |
||||
|
stav = STAV_HRA |
||||
|
elif stav == STAV_KONEC and udalost.key == pygame.K_SPACE: |
||||
|
# Jsme na obrazovce s vítězem, hráč chce hrát znovu. |
||||
|
reset_hry() |
||||
|
stav = STAV_HRA |
||||
|
|
||||
|
# Oproti tomu toto zjistí AKTUÁLNĚ DRŽENÉ klávesy v tomto zlomku vteřiny |
||||
|
klavesy = pygame.key.get_pressed() |
||||
|
|
||||
|
# 2. HERNÍ LOGIKA (HÝBÁNÍ SE) |
||||
|
# Tohle se provede JEN a POUZE, pokud zrovna hrajeme zápas! |
||||
|
if stav == STAV_HRA: |
||||
|
# Ovládání Hráče 1 vlevo (Klávesy W a S pro pohyb nahoru/dolů) |
||||
|
if klavesy[pygame.K_w] and hrac1_y > 0: |
||||
|
hrac1_y -= rychlost_palky |
||||
|
# Dolů nemůže víc, než je výška okna zmenšená o jeho vlastní výšku pálky |
||||
|
if klavesy[pygame.K_s] and hrac1_y < VYSKA - palka_vyska: |
||||
|
hrac1_y += rychlost_palky |
||||
|
|
||||
|
# Ovládání Hráče 2 vpravo (Šipky Nahoru a Dolů) |
||||
|
if klavesy[pygame.K_UP] and hrac2_y > 0: |
||||
|
hrac2_y -= rychlost_palky |
||||
|
if klavesy[pygame.K_DOWN] and hrac2_y < VYSKA - palka_vyska: |
||||
|
hrac2_y += rychlost_palky |
||||
|
|
||||
|
# Míček se za každou smyčku kousek posune. Tím vzniká dojem pohybu. |
||||
|
micek_x += micek_rychlost_x |
||||
|
micek_y += micek_rychlost_y |
||||
|
|
||||
|
# --- Fyzika: Odraz míčku od stropu a podlahy --- |
||||
|
# Trik: Vynásobením rychlosti číslem -1 se obrátí znaménko. |
||||
|
# Z kladné rychlosti (+5 padá dolů) se stane záporná (-5 letí nahoru). Odrazil se! |
||||
|
if micek_y <= 0 or micek_y >= VYSKA - micek_velikost: |
||||
|
micek_rychlost_y *= -1 |
||||
|
|
||||
|
# --- Fyzika: Odraz od pálek hráčů --- |
||||
|
# Abychom to jednoduše spočítali, vyrobíme si takzvané virtuální Obdélníky (Rect) |
||||
|
rect_micek = pygame.Rect(micek_x, micek_y, micek_velikost, micek_velikost) |
||||
|
|
||||
|
# Pálku Hráče 1 vykreslujeme fixně 30 pixelů od levého okraje |
||||
|
rect_hrac1 = pygame.Rect(30, hrac1_y, palka_sirka, palka_vyska) |
||||
|
# Pálku Hráče 2 fixně na pravé straně. (SIRKA - 30 od kraje - velikost pálky) |
||||
|
rect_hrac2 = pygame.Rect(SIRKA - 30 - palka_sirka, hrac2_y, palka_sirka, palka_vyska) |
||||
|
|
||||
|
# Metoda 'colliderect' doslova zkoumá, jestli se tyto obdélníky v daný moment nepřekrývají |
||||
|
if rect_micek.colliderect(rect_hrac1) or rect_micek.colliderect(rect_hrac2): |
||||
|
# Odrazil se od pálky! Obrátíme jeho směr po ose X a ještě ho mírně zrychlíme (* -1.1) |
||||
|
micek_rychlost_x *= -1.1 |
||||
|
|
||||
|
# --- Vyhodnocení Gólu --- |
||||
|
if micek_x < 0: |
||||
|
# Přeletěl za levý okraj (Hráč 1 ho nezachytil). Bod pro Hráče 2! |
||||
|
skore2 += 1 |
||||
|
micek_x, micek_y = SIRKA // 2, VYSKA // 2 # Teleportace míčku na prostředek |
||||
|
micek_rychlost_x = 5 # Reset rychlosti a ať letí k hráči co dostal gól (doprava) |
||||
|
elif micek_x > SIRKA: |
||||
|
# Přeletěl vpravo |
||||
|
skore1 += 1 |
||||
|
micek_x, micek_y = SIRKA // 2, VYSKA // 2 |
||||
|
micek_rychlost_x = -5 # Směr letu doleva |
||||
|
|
||||
|
# --- Konec Zápasu --- |
||||
|
# Zápas končí, jakmile někdo dosáhne 5 bodů |
||||
|
if skore1 >= 5 or skore2 >= 5: |
||||
|
stav = STAV_KONEC |
||||
|
|
||||
|
# 3. KRESLENÍ GRAFIKY |
||||
|
# Vždy smažeme starou stopu vyplněním okna čistě černou barvou |
||||
|
okno.fill(CERNA) |
||||
|
|
||||
|
# Co na obrazovce bude záleží zcela na tom, ve kterém STAVU náš Stavový Automat právě je! |
||||
|
if stav == STAV_MENU: |
||||
|
# Vykreslení Menu. .render převede nápis z textu do "razítka" |
||||
|
nadpis = font_velky.render("PONG", True, BILA) |
||||
|
navod = font_maly.render("Stiskni MEZERNÍK pro start", True, ZELENA) |
||||
|
|
||||
|
# Tyto razítka .blitnem (otiskneme) na plátno. Výpočet (SIRKA//2 - nadpis.get_width()//2) zarovná nápis čistě doprostřed obrazovky. |
||||
|
okno.blit(nadpis, (SIRKA//2 - nadpis.get_width()//2, 200)) |
||||
|
okno.blit(navod, (SIRKA//2 - navod.get_width()//2, 300)) |
||||
|
|
||||
|
elif stav == STAV_HRA: |
||||
|
# Jsme ve hře. Vykreslíme půlící čáru (síť v Pongu). 'aaline' je hladká čára. |
||||
|
pygame.draw.aaline(okno, BILA, (SIRKA // 2, 0), (SIRKA // 2, VYSKA)) |
||||
|
|
||||
|
# Nakreslíme pálky na místa, která předtím logika vypočítala |
||||
|
pygame.draw.rect(okno, BILA, rect_hrac1) |
||||
|
pygame.draw.rect(okno, BILA, rect_hrac2) |
||||
|
|
||||
|
# Míček vykreslíme jako 'ellipsu' napasovanou na ten neviditelný 'rect_micek' obdélník. Vznikne tak kruh. |
||||
|
pygame.draw.ellipse(okno, BILA, rect_micek) |
||||
|
|
||||
|
# Kreslení obou skóre úplně nahoru |
||||
|
text_skore = font_velky.render(f"{skore1} {skore2}", True, BILA) |
||||
|
okno.blit(text_skore, (SIRKA // 2 - text_skore.get_width() // 2, 20)) |
||||
|
|
||||
|
elif stav == STAV_KONEC: |
||||
|
# Zjištění, kdo vyhrál pomocí tzv. ternárního if (vítězem je "Hráč 1" POKUD má skore1>=5, JINAK je to "Hráč 2") |
||||
|
vitez = "Hráč 1" if skore1 >= 5 else "Hráč 2" |
||||
|
|
||||
|
# Razítka s nápisy |
||||
|
text_vitez = font_velky.render(f"{vitez} vyhrál!", True, ZELENA) |
||||
|
text_restart = font_maly.render("Stiskni MEZERNÍK pro novou hru", True, BILA) |
||||
|
|
||||
|
# Obtisknutí doprostřed okna |
||||
|
okno.blit(text_vitez, (SIRKA//2 - text_vitez.get_width()//2, 200)) |
||||
|
okno.blit(text_restart, (SIRKA//2 - text_restart.get_width()//2, 300)) |
||||
|
|
||||
|
# Obraz je složený nanečisto v paměti. .flip() ho teprve natvrdo pošle na monitor! |
||||
|
pygame.display.flip() |
||||
|
|
||||
|
# Pauza na udržení stabilních 60 FPS |
||||
|
hodiny.tick(60) |
||||
|
# PŘIDÁNO PRO WEB: Dáme prohlížeči šanci překreslit obrazovku |
||||
|
await asyncio.sleep(0) |
||||
|
|
||||
|
# Konec programu |
||||
|
pygame.quit() |
||||
|
sys.exit() |
||||
|
|
||||
|
|
||||
|
# PŘIDÁNO PRO WEB: Spuštění asynchronní hry |
||||
|
asyncio.run(main()) |
||||
@ -1,169 +1,177 @@ |
|||||
import pygame |
import pygame |
||||
|
import asyncio # PŘIDÁNO PRO WEB: importujeme asyncio pro neblokující smyčku |
||||
import sys |
import sys |
||||
import random |
import random |
||||
|
|
||||
pygame.init() |
|
||||
|
|
||||
# --- NASTAVENÍ OKNA A PROMĚNNÝCH PROSTŘEDÍ --- |
|
||||
SIRKA = 800 |
|
||||
VYSKA = 600 |
|
||||
okno = pygame.display.set_mode((SIRKA, VYSKA)) |
|
||||
pygame.display.set_caption("Skákačka přes překážky") |
|
||||
|
|
||||
# --- BARVY --- |
|
||||
BILA = (255, 255, 255) |
|
||||
CERNA = (0, 0, 0) |
|
||||
MODRA = (50, 150, 255) # Na modrou oblohu |
|
||||
ZELENA = (50, 200, 50) # Zelená pro plošinu trávy |
|
||||
CERVENA = (255, 0, 0) # Nebezpečné překážky v cestě |
|
||||
|
|
||||
hodiny = pygame.time.Clock() |
|
||||
font = pygame.font.SysFont("Arial", 40) |
|
||||
font_maly = pygame.font.SysFont("Arial", 25) |
|
||||
|
|
||||
# --- STAVOVÝ AUTOMAT HRY --- |
|
||||
STAV_MENU = 0 |
|
||||
STAV_HRA = 1 |
|
||||
STAV_KONEC = 2 |
|
||||
stav = STAV_MENU |
|
||||
|
|
||||
# --- FYZIKA A HERNÍ PROMĚNNÉ --- |
|
||||
podlaha_y = VYSKA - 50 # V jaké hloubce se nachází hrana země/podlahy? (od shora dolů) |
|
||||
hrac_sirka = 40 |
|
||||
hrac_vyska = 40 |
|
||||
|
|
||||
# Fyzika |
|
||||
# Gravitace způsobuje trvalé ZRYCHLOVÁNÍ pádu. Ne rychlost, zrychlení! |
|
||||
gravitace = 0.6 |
|
||||
# Skok není nic jiného, než extrémní rána do záporných hodnot na ose Y (odstřelení do stropu) |
|
||||
sila_skoku = -12 |
|
||||
|
|
||||
def reset_hry(): |
|
||||
"""Funkce pro nahození proměnných do výchozího stavu pro start čisté hry""" |
|
||||
global hrac_x, hrac_y, rychlost_y, na_zemi, prekazky, skore, rychlost_hry |
|
||||
hrac_x = 100 # Hráč stojí celkem blízko levému kraji, aby měl čas reagovat |
|
||||
|
|
||||
# Hráče položíme tak, aby jeho nohy (y + výška) přesně seděly na 'podlaha_y' |
|
||||
hrac_y = podlaha_y - hrac_vyska |
|
||||
rychlost_y = 0 |
|
||||
na_zemi = True # Nyní stojí na zemi (a smí tedy zmáčknout Skok) |
|
||||
|
|
||||
# Místo abychom dělali překážku po překážce, uchováme je jako seznam (List) |
|
||||
prekazky = [] |
|
||||
skore = 0 |
|
||||
rychlost_hry = 6 # Jak rychle ubíhá terén směrem k hráči doleva? |
|
||||
|
|
||||
reset_hry() |
|
||||
|
|
||||
# --- VLASTNÍ ČASOVAČ (Custom Event) --- |
|
||||
# Trik z programování: Pygame neustále běží ve smyčce a "naslouchá" událostem (jako stisk myši). |
|
||||
# Zde si vyrobíme událost NAŠI VLASTNÍ. Pojmenujeme ji libovolně SPAWN_PREKAZKY. |
|
||||
SPAWN_PREKAZKY = pygame.USEREVENT + 1 |
|
||||
# Nastavíme pygame časovač tak, ať tuto 'neviditelnou klávesu SPAWN_PREKAZKY' pípne |
|
||||
# do fronty událostí přesně každých 1500 milisekund (1.5 sekundy). Úplně automaticky! |
|
||||
pygame.time.set_timer(SPAWN_PREKAZKY, 1500) |
|
||||
|
|
||||
bezime = True |
|
||||
while bezime: |
|
||||
|
|
||||
# 1. ZPRACOVÁNÍ UDÁLOSTÍ |
|
||||
for udalost in pygame.event.get(): |
|
||||
if udalost.type == pygame.QUIT: |
|
||||
bezime = False |
|
||||
|
|
||||
# Zkoumání stisku kláves jako POUHÁ UDÁLOST. To znamená "zmáčklo se to" |
|
||||
# neboli neřešíme, jestli se to zrovna drží stlačené. Ideální pro Starty a Skoky. |
|
||||
if udalost.type == pygame.KEYDOWN: |
|
||||
if stav == STAV_MENU and udalost.key == pygame.K_SPACE: |
|
||||
stav = STAV_HRA |
|
||||
elif stav == STAV_KONEC and udalost.key == pygame.K_SPACE: |
|
||||
reset_hry() |
|
||||
stav = STAV_HRA |
|
||||
|
|
||||
# Jak hráč skáče? Šipka nahoru! Ale MUSÍ zároveň stát na zemi (na_zemi == True), |
|
||||
# jinak bychom mohli skákat ve vzduchu jak pták (tzv. multi-jump) |
|
||||
elif stav == STAV_HRA and udalost.key == pygame.K_UP and na_zemi: |
|
||||
rychlost_y = sila_skoku # Tímto dáváme drtivý impulz rychlosti směrem NAHORU |
|
||||
na_zemi = False # Teď už jsme ve vzduchu! |
|
||||
|
|
||||
# Tady čekáme na PÍPNUTÍ z našeho automatického Custom Časovače!!! |
|
||||
if udalost.type == SPAWN_PREKAZKY and stav == STAV_HRA: |
|
||||
# Objevil se signál, že uplynulo 1.5 sekundy. |
|
||||
# Přidáme novou překážku (čtvereček červený) na konec obrazovky (SIRKA) |
|
||||
# Tyto překážky ukládáme rovnou jako Rect, protože se to pak hodí u kolizí |
|
||||
prekazky.append(pygame.Rect(SIRKA, podlaha_y - 40, 30, 40)) |
|
||||
|
|
||||
# Aby to nebylo nudné, pokaždé hru o malilinký kousíček zrychlíme. |
|
||||
rychlost_hry += 0.1 |
|
||||
|
|
||||
# 2. LOGIKA |
|
||||
if stav == STAV_HRA: |
|
||||
# --- FYZIKA SKÁKÁNÍ --- |
|
||||
# Neúprosná gravitace v každém milisekundovém snímku přidává směrem DOLŮ! |
|
||||
rychlost_y += gravitace |
|
||||
# Změníme opravdovou Y pozici hráče o tuto aktuální rychlost. |
|
||||
# Výsledek? Hráč zpomaluje směrem nahoru... zastaví se... a začne opět zrychlovat dolů k zemi! |
|
||||
hrac_y += rychlost_y |
|
||||
|
|
||||
# Musíme ale zabránit tomu, aby hráč propadl podlahou do hlubin! |
|
||||
if hrac_y + hrac_vyska >= podlaha_y: |
|
||||
# Opravíme jeho Y, posadíme ho přesně podrážkama na okraj podlahy |
|
||||
hrac_y = podlaha_y - hrac_vyska |
|
||||
rychlost_y = 0 # Rychlost dopadu zrušíme, stál by na místě |
|
||||
na_zemi = True # Oznámíme, že zase můžeme skákat, dotýkáme se |
|
||||
|
|
||||
# Vytvoření virtuálního obalu přes našeho hráče kvůli měření kolizí |
|
||||
hrac_rect = pygame.Rect(hrac_x, hrac_y, hrac_sirka, hrac_vyska) |
|
||||
|
|
||||
# --- LOGIKA VŠECH PŘEKÁŽEK NA TRATI --- |
|
||||
# For cyklus prekazky[:] zkoumá tzv. KOPII seznamu. |
|
||||
# Je to proto, abychom z originálu mohli prvek bezpečně vymazat a nic "nepřeskočili". |
|
||||
for p in prekazky[:]: |
|
||||
p.x -= int(rychlost_hry) # Každá jedna překážka jede nezadržitelně doleva směrem na nás |
|
||||
|
|
||||
# Co když se obdélníčky Hráče a Překážky překrývají? -> BOUM! |
|
||||
if p.colliderect(hrac_rect): |
|
||||
stav = STAV_KONEC |
|
||||
|
|
||||
# Pokud na nás nenarazila, zajela na levém okraji pod 0 a zmizela (p.x < -30) |
|
||||
if p.x < -30: |
|
||||
prekazky.remove(p) # Hráč ji s úspěchem přežil a překážka zajela za monitor. Smazat z RAM! |
|
||||
skore += 1 # Přičteme za ni bod. |
|
||||
|
|
||||
# 3. KRESLENÍ (na monitor) |
|
||||
okno.fill(MODRA) # Obloha |
|
||||
# Nakreslíme zelený pruh od čáry podlahy (podlaha_y) dolů až k zemi, bude dělat vizuál trávy |
|
||||
pygame.draw.rect(okno, ZELENA, (0, podlaha_y, SIRKA, VYSKA - podlaha_y)) |
|
||||
|
|
||||
if stav == STAV_MENU: |
|
||||
text = font.render("SKÁKAČKA", True, BILA) |
|
||||
start = font_maly.render("Stiskni MEZERNÍK. Skáče se šipkou NAHORU.", True, CERNA) |
|
||||
okno.blit(text, (SIRKA//2 - text.get_width()//2, 200)) |
|
||||
okno.blit(start, (SIRKA//2 - start.get_width()//2, 300)) |
|
||||
|
|
||||
elif stav == STAV_HRA: |
|
||||
# Nakreslení postavičky hráče - využijeme ten Rect obal, co už jsme si spočítali |
|
||||
pygame.draw.rect(okno, CERNA, hrac_rect) |
|
||||
|
|
||||
# Nakreslíme úplně všechny načítané nepřátelské Rect obdélníky jako červené objekty |
|
||||
for p in prekazky: |
|
||||
pygame.draw.rect(okno, CERVENA, p) |
|
||||
|
|
||||
skore_text = font.render(f"Skóre: {skore}", True, CERNA) |
|
||||
okno.blit(skore_text, (10, 10)) |
|
||||
|
|
||||
elif stav == STAV_KONEC: |
|
||||
text = font.render("KONEC HRY", True, CERVENA) |
|
||||
skore_text = font.render(f"Dosažené skóre: {skore}", True, BILA) |
|
||||
restart = font_maly.render("Stiskni MEZERNÍK pro novou hru", True, CERNA) |
|
||||
|
|
||||
okno.blit(text, (SIRKA//2 - text.get_width()//2, 150)) |
|
||||
okno.blit(skore_text, (SIRKA//2 - skore_text.get_width()//2, 220)) |
|
||||
okno.blit(restart, (SIRKA//2 - restart.get_width()//2, 300)) |
|
||||
|
|
||||
# .flip() překlopí to, co jsme nakreslili v paměti na obrazovku k hráči |
|
||||
pygame.display.flip() |
|
||||
hodiny.tick(60) # Cílíme na hladkých 60 FPS (Frames Per Second) |
|
||||
|
|
||||
pygame.quit() |
|
||||
sys.exit() |
|
||||
|
async def main(): # PŘIDÁNO PRO WEB: Zabalíme celou hru do asynchronní funkce |
||||
|
pygame.init() |
||||
|
|
||||
|
# --- NASTAVENÍ OKNA A PROMĚNNÝCH PROSTŘEDÍ --- |
||||
|
SIRKA = 800 |
||||
|
VYSKA = 600 |
||||
|
okno = pygame.display.set_mode((SIRKA, VYSKA)) |
||||
|
pygame.display.set_caption("Skákačka přes překážky") |
||||
|
|
||||
|
# --- BARVY --- |
||||
|
BILA = (255, 255, 255) |
||||
|
CERNA = (0, 0, 0) |
||||
|
MODRA = (50, 150, 255) # Na modrou oblohu |
||||
|
ZELENA = (50, 200, 50) # Zelená pro plošinu trávy |
||||
|
CERVENA = (255, 0, 0) # Nebezpečné překážky v cestě |
||||
|
|
||||
|
hodiny = pygame.time.Clock() |
||||
|
font = pygame.font.SysFont("Arial", 40) |
||||
|
font_maly = pygame.font.SysFont("Arial", 25) |
||||
|
|
||||
|
# --- STAVOVÝ AUTOMAT HRY --- |
||||
|
STAV_MENU = 0 |
||||
|
STAV_HRA = 1 |
||||
|
STAV_KONEC = 2 |
||||
|
stav = STAV_MENU |
||||
|
|
||||
|
# --- FYZIKA A HERNÍ PROMĚNNÉ --- |
||||
|
podlaha_y = VYSKA - 50 # V jaké hloubce se nachází hrana země/podlahy? (od shora dolů) |
||||
|
hrac_sirka = 40 |
||||
|
hrac_vyska = 40 |
||||
|
|
||||
|
# Fyzika |
||||
|
# Gravitace způsobuje trvalé ZRYCHLOVÁNÍ pádu. Ne rychlost, zrychlení! |
||||
|
gravitace = 0.6 |
||||
|
# Skok není nic jiného, než extrémní rána do záporných hodnot na ose Y (odstřelení do stropu) |
||||
|
sila_skoku = -12 |
||||
|
|
||||
|
def reset_hry(): |
||||
|
"""Funkce pro nahození proměnných do výchozího stavu pro start čisté hry""" |
||||
|
global hrac_x, hrac_y, rychlost_y, na_zemi, prekazky, skore, rychlost_hry |
||||
|
hrac_x = 100 # Hráč stojí celkem blízko levému kraji, aby měl čas reagovat |
||||
|
|
||||
|
# Hráče položíme tak, aby jeho nohy (y + výška) přesně seděly na 'podlaha_y' |
||||
|
hrac_y = podlaha_y - hrac_vyska |
||||
|
rychlost_y = 0 |
||||
|
na_zemi = True # Nyní stojí na zemi (a smí tedy zmáčknout Skok) |
||||
|
|
||||
|
# Místo abychom dělali překážku po překážce, uchováme je jako seznam (List) |
||||
|
prekazky = [] |
||||
|
skore = 0 |
||||
|
rychlost_hry = 6 # Jak rychle ubíhá terén směrem k hráči doleva? |
||||
|
|
||||
|
reset_hry() |
||||
|
|
||||
|
# --- VLASTNÍ ČASOVAČ (Custom Event) --- |
||||
|
# Trik z programování: Pygame neustále běží ve smyčce a "naslouchá" událostem (jako stisk myši). |
||||
|
# Zde si vyrobíme událost NAŠI VLASTNÍ. Pojmenujeme ji libovolně SPAWN_PREKAZKY. |
||||
|
SPAWN_PREKAZKY = pygame.USEREVENT + 1 |
||||
|
# Nastavíme pygame časovač tak, ať tuto 'neviditelnou klávesu SPAWN_PREKAZKY' pípne |
||||
|
# do fronty událostí přesně každých 1500 milisekund (1.5 sekundy). Úplně automaticky! |
||||
|
pygame.time.set_timer(SPAWN_PREKAZKY, 1500) |
||||
|
|
||||
|
bezime = True |
||||
|
while bezime: |
||||
|
|
||||
|
# 1. ZPRACOVÁNÍ UDÁLOSTÍ |
||||
|
for udalost in pygame.event.get(): |
||||
|
if udalost.type == pygame.QUIT: |
||||
|
bezime = False |
||||
|
|
||||
|
# Zkoumání stisku kláves jako POUHÁ UDÁLOST. To znamená "zmáčklo se to" |
||||
|
# neboli neřešíme, jestli se to zrovna drží stlačené. Ideální pro Starty a Skoky. |
||||
|
if udalost.type == pygame.KEYDOWN: |
||||
|
if stav == STAV_MENU and udalost.key == pygame.K_SPACE: |
||||
|
stav = STAV_HRA |
||||
|
elif stav == STAV_KONEC and udalost.key == pygame.K_SPACE: |
||||
|
reset_hry() |
||||
|
stav = STAV_HRA |
||||
|
|
||||
|
# Jak hráč skáče? Šipka nahoru! Ale MUSÍ zároveň stát na zemi (na_zemi == True), |
||||
|
# jinak bychom mohli skákat ve vzduchu jak pták (tzv. multi-jump) |
||||
|
elif stav == STAV_HRA and udalost.key == pygame.K_UP and na_zemi: |
||||
|
rychlost_y = sila_skoku # Tímto dáváme drtivý impulz rychlosti směrem NAHORU |
||||
|
na_zemi = False # Teď už jsme ve vzduchu! |
||||
|
|
||||
|
# Tady čekáme na PÍPNUTÍ z našeho automatického Custom Časovače!!! |
||||
|
if udalost.type == SPAWN_PREKAZKY and stav == STAV_HRA: |
||||
|
# Objevil se signál, že uplynulo 1.5 sekundy. |
||||
|
# Přidáme novou překážku (čtvereček červený) na konec obrazovky (SIRKA) |
||||
|
# Tyto překážky ukládáme rovnou jako Rect, protože se to pak hodí u kolizí |
||||
|
prekazky.append(pygame.Rect(SIRKA, podlaha_y - 40, 30, 40)) |
||||
|
|
||||
|
# Aby to nebylo nudné, pokaždé hru o malilinký kousíček zrychlíme. |
||||
|
rychlost_hry += 0.1 |
||||
|
|
||||
|
# 2. LOGIKA |
||||
|
if stav == STAV_HRA: |
||||
|
# --- FYZIKA SKÁKÁNÍ --- |
||||
|
# Neúprosná gravitace v každém milisekundovém snímku přidává směrem DOLŮ! |
||||
|
rychlost_y += gravitace |
||||
|
# Změníme opravdovou Y pozici hráče o tuto aktuální rychlost. |
||||
|
# Výsledek? Hráč zpomaluje směrem nahoru... zastaví se... a začne opět zrychlovat dolů k zemi! |
||||
|
hrac_y += rychlost_y |
||||
|
|
||||
|
# Musíme ale zabránit tomu, aby hráč propadl podlahou do hlubin! |
||||
|
if hrac_y + hrac_vyska >= podlaha_y: |
||||
|
# Opravíme jeho Y, posadíme ho přesně podrážkama na okraj podlahy |
||||
|
hrac_y = podlaha_y - hrac_vyska |
||||
|
rychlost_y = 0 # Rychlost dopadu zrušíme, stál by na místě |
||||
|
na_zemi = True # Oznámíme, že zase můžeme skákat, dotýkáme se |
||||
|
|
||||
|
# Vytvoření virtuálního obalu přes našeho hráče kvůli měření kolizí |
||||
|
hrac_rect = pygame.Rect(hrac_x, hrac_y, hrac_sirka, hrac_vyska) |
||||
|
|
||||
|
# --- LOGIKA VŠECH PŘEKÁŽEK NA TRATI --- |
||||
|
# For cyklus prekazky[:] zkoumá tzv. KOPII seznamu. |
||||
|
# Je to proto, abychom z originálu mohli prvek bezpečně vymazat a nic "nepřeskočili". |
||||
|
for p in prekazky[:]: |
||||
|
p.x -= int(rychlost_hry) # Každá jedna překážka jede nezadržitelně doleva směrem na nás |
||||
|
|
||||
|
# Co když se obdélníčky Hráče a Překážky překrývají? -> BOUM! |
||||
|
if p.colliderect(hrac_rect): |
||||
|
stav = STAV_KONEC |
||||
|
|
||||
|
# Pokud na nás nenarazila, zajela na levém okraji pod 0 a zmizela (p.x < -30) |
||||
|
if p.x < -30: |
||||
|
prekazky.remove(p) # Hráč ji s úspěchem přežil a překážka zajela za monitor. Smazat z RAM! |
||||
|
skore += 1 # Přičteme za ni bod. |
||||
|
|
||||
|
# 3. KRESLENÍ (na monitor) |
||||
|
okno.fill(MODRA) # Obloha |
||||
|
# Nakreslíme zelený pruh od čáry podlahy (podlaha_y) dolů až k zemi, bude dělat vizuál trávy |
||||
|
pygame.draw.rect(okno, ZELENA, (0, podlaha_y, SIRKA, VYSKA - podlaha_y)) |
||||
|
|
||||
|
if stav == STAV_MENU: |
||||
|
text = font.render("SKÁKAČKA", True, BILA) |
||||
|
start = font_maly.render("Stiskni MEZERNÍK. Skáče se šipkou NAHORU.", True, CERNA) |
||||
|
okno.blit(text, (SIRKA//2 - text.get_width()//2, 200)) |
||||
|
okno.blit(start, (SIRKA//2 - start.get_width()//2, 300)) |
||||
|
|
||||
|
elif stav == STAV_HRA: |
||||
|
# Nakreslení postavičky hráče - využijeme ten Rect obal, co už jsme si spočítali |
||||
|
pygame.draw.rect(okno, CERNA, hrac_rect) |
||||
|
|
||||
|
# Nakreslíme úplně všechny načítané nepřátelské Rect obdélníky jako červené objekty |
||||
|
for p in prekazky: |
||||
|
pygame.draw.rect(okno, CERVENA, p) |
||||
|
|
||||
|
skore_text = font.render(f"Skóre: {skore}", True, CERNA) |
||||
|
okno.blit(skore_text, (10, 10)) |
||||
|
|
||||
|
elif stav == STAV_KONEC: |
||||
|
text = font.render("KONEC HRY", True, CERVENA) |
||||
|
skore_text = font.render(f"Dosažené skóre: {skore}", True, BILA) |
||||
|
restart = font_maly.render("Stiskni MEZERNÍK pro novou hru", True, CERNA) |
||||
|
|
||||
|
okno.blit(text, (SIRKA//2 - text.get_width()//2, 150)) |
||||
|
okno.blit(skore_text, (SIRKA//2 - skore_text.get_width()//2, 220)) |
||||
|
okno.blit(restart, (SIRKA//2 - restart.get_width()//2, 300)) |
||||
|
|
||||
|
# .flip() překlopí to, co jsme nakreslili v paměti na obrazovku k hráči |
||||
|
pygame.display.flip() |
||||
|
hodiny.tick(60) # Cílíme na hladkých 60 FPS (Frames Per Second) |
||||
|
# PŘIDÁNO PRO WEB: Dáme prohlížeči šanci překreslit obrazovku |
||||
|
await asyncio.sleep(0) |
||||
|
|
||||
|
pygame.quit() |
||||
|
sys.exit() |
||||
|
|
||||
|
|
||||
|
# PŘIDÁNO PRO WEB: Spuštění asynchronní hry |
||||
|
asyncio.run(main()) |
||||
@ -1,185 +1,193 @@ |
|||||
import pygame |
import pygame |
||||
|
import asyncio # PŘIDÁNO PRO WEB: importujeme asyncio pro neblokující smyčku |
||||
import sys |
import sys |
||||
import random |
import random |
||||
|
|
||||
pygame.init() |
|
||||
|
|
||||
# --- NASTAVENÍ OKNA --- |
|
||||
SIRKA = 800 |
|
||||
VYSKA = 600 |
|
||||
okno = pygame.display.set_mode((SIRKA, VYSKA)) |
|
||||
pygame.display.set_caption("Vesmírná střílečka (Space Invaders)") |
|
||||
|
|
||||
# --- BARVY --- |
|
||||
CERNA = (0, 0, 0) |
|
||||
ZELENA = (0, 255, 0) # Hráč (Vesmírná loď) |
|
||||
CERVENA = (255, 0, 0) # Nepřátelé (Mimozemšťani, Meteority) |
|
||||
ZLUCA = (255, 255, 0) # Projektily (Lasery) |
|
||||
BILA = (255, 255, 255) # Text a UI |
|
||||
|
|
||||
hodiny = pygame.time.Clock() |
|
||||
font_velky = pygame.font.SysFont("Arial", 50) |
|
||||
font_maly = pygame.font.SysFont("Arial", 30) |
|
||||
|
|
||||
# Herní stav 0,1,2 - stejná logika oddělení úvodního menu od zápasu |
|
||||
STAV_MENU = 0 |
|
||||
STAV_HRA = 1 |
|
||||
STAV_KONEC = 2 |
|
||||
stav = STAV_MENU |
|
||||
|
|
||||
# Rozměry vesmírné lodi hráče |
|
||||
hrac_sirka = 40 |
|
||||
hrac_vyska = 40 |
|
||||
rychlost_hrace = 6 |
|
||||
rychlost_projektilu = 10 # Lasery létají celkem svižně nahoru |
|
||||
|
|
||||
# Vytvoření vlastního časovače pro přidávání nepřátel, viz skákačka |
|
||||
SPAWN_NEPRITELE = pygame.USEREVENT + 1 |
|
||||
|
|
||||
def reset_hry(): |
|
||||
"""Tato funkce srovná hru zpátky do prvního dne, když spustíme reset""" |
|
||||
global hrac_x, hrac_y, projektily, nepratele, skore, rychlost_nepritele, spawn_cas |
|
||||
|
|
||||
# Hráč je dole nad hranou obrazovky |
|
||||
hrac_x = SIRKA // 2 - hrac_sirka // 2 |
|
||||
hrac_y = VYSKA - 60 |
|
||||
|
|
||||
# Budou zde létat nezávisle na sobě desítky kulek i nepřátel. |
|
||||
# Abychom se v nich vyznali, naženeme je všechny do SEZNAMŮ. |
|
||||
projektily = [] |
|
||||
nepratele = [] |
|
||||
|
|
||||
skore = 0 |
|
||||
rychlost_nepritele = 2.0 |
|
||||
spawn_cas = 1000 # Počáteční čas (v milisekundách) k objevování padajících mimozemšťanů |
|
||||
|
|
||||
# Nastartování naší události (eventu), začne tikat... |
|
||||
pygame.time.set_timer(SPAWN_NEPRITELE, spawn_cas) |
|
||||
|
|
||||
reset_hry() |
|
||||
|
|
||||
bezime = True |
|
||||
while bezime: |
|
||||
# 1. ZPRACOVÁNÍ UDÁLOSTÍ |
|
||||
for udalost in pygame.event.get(): |
|
||||
if udalost.type == pygame.QUIT: |
|
||||
bezime = False |
|
||||
|
|
||||
# Zjišťujeme JEDNORÁZOVÉ stisknutí tlačítek. Například výstřel nebo posun v menu. |
|
||||
if udalost.type == pygame.KEYDOWN: |
|
||||
if stav == STAV_MENU and udalost.key == pygame.K_SPACE: |
|
||||
stav = STAV_HRA |
|
||||
elif stav == STAV_KONEC and udalost.key == pygame.K_SPACE: |
|
||||
reset_hry() |
|
||||
stav = STAV_HRA |
|
||||
elif stav == STAV_HRA and udalost.key == pygame.K_SPACE: |
|
||||
# --- VÝSTŘEL --- |
|
||||
# Hráč právě odpálil! Vypočítáme, kde zhruba leží střed jeho lodi (špička křídel) |
|
||||
projektil_x = hrac_x + hrac_sirka // 2 - 5 |
|
||||
# A přímo na těchto souřadnicích se narodí nová "KULKA" v podobě Rect. Přidáme do listu. |
|
||||
projektily.append(pygame.Rect(projektil_x, hrac_y, 10, 20)) |
|
||||
|
|
||||
# Zde časovač zakřičel (třeba každou sekundu), že na nás padá další nepřítel |
|
||||
if udalost.type == SPAWN_NEPRITELE and stav == STAV_HRA: |
|
||||
# Narodil se nový nepřítel nahoře mimo obraz (proto je osa y záporná: -40) na náhodném místě osy X. |
|
||||
n_x = random.randint(0, SIRKA - 40) |
|
||||
nepratele.append(pygame.Rect(n_x, -40, 40, 40)) |
|
||||
|
|
||||
# STUPŇOVÁNÍ OBTÍŽNOSTI (Nepřátelé padají čím dál rychleji) |
|
||||
rychlost_nepritele += 0.05 |
|
||||
|
|
||||
# Čím déle hrajeme, tím se také zkracuje ten časovač a nepřátel je hustší roj. |
|
||||
# Omezíme to na min 300ms, aby ta hra byla alespoň fyzicky dohratelná a nepřátelé nespadli z jednolité zdi. |
|
||||
if spawn_cas > 300: |
|
||||
spawn_cas -= 20 |
|
||||
# Musíme časovač s tímto novým o 20ms kratším časem znovu "nastartovat" |
|
||||
pygame.time.set_timer(SPAWN_NEPRITELE, spawn_cas) |
|
||||
|
|
||||
if stav == STAV_HRA: |
|
||||
# 2. LOGIKA |
|
||||
|
|
||||
# Ovládání vesmírné lodi doprava a doleva držením tlačítek |
|
||||
klavesy = pygame.key.get_pressed() |
|
||||
if klavesy[pygame.K_LEFT] and hrac_x > 0: |
|
||||
hrac_x -= rychlost_hrace |
|
||||
if klavesy[pygame.K_RIGHT] and hrac_x < SIRKA - hrac_sirka: |
|
||||
hrac_x += rychlost_hrace |
|
||||
|
|
||||
# --- POHYB VŠECH LASERŮ (KULEK) --- |
|
||||
# Projektily[:] dělá kopii listu, to se musí dělat vždy, když chci za běhu for-cyklu z toho pole |
|
||||
# věci rovnou odstraňovat! Kdybychom neodstraňovali kulky, list by narostl a hra spadla na paměť. |
|
||||
for p in projektily[:]: |
|
||||
p.y -= rychlost_projektilu # Y se ZMENŠUJE, protože kulka cestuje odspodu (např. Y=500) nahoru (Y=0) |
|
||||
|
|
||||
# Když kulka odcestuje nad okraj obrazovky do hlubin kosmu, smažeme ji |
|
||||
if p.y < 0: projektily.remove(p) |
|
||||
|
|
||||
# Hráč samotný obalený do neviditelného Rect rámečku pro vyhodnocování havárií |
|
||||
hrac_rect = pygame.Rect(hrac_x, hrac_y, hrac_sirka, hrac_vyska) |
|
||||
|
|
||||
# --- POHYB VŠECH NEPŘÁTEL & KOLIZE --- |
|
||||
for n in nepratele[:]: |
|
||||
n.y += int(rychlost_nepritele) # Zvyšujeme Y -> Meteorit letí strmě dolů z nebes k podlaze |
|
||||
|
|
||||
# 1. Havárie! Pokud meteorit doletí až úplně dolů přes obrazovku, NEBO narazí do Hráče |
|
||||
if n.y > VYSKA or n.colliderect(hrac_rect): |
|
||||
stav = STAV_KONEC |
|
||||
|
|
||||
# 2. Křížová kontrola, zda SE STŘETLA KULKA S NEPŘÍTELEM (Laser do Meteoritu) |
|
||||
# Každého meteorita porovnáme znovu se všemi létajícími lasery na scéně! |
|
||||
for p in projektily[:]: |
|
||||
# .colliderect znamená "prolnuly se tyhle dva čtverce?" |
|
||||
if p.colliderect(n): |
|
||||
# BUM! Nastal obrovský výbuch. |
|
||||
# Zmizí Laser i Meteorit, tak je oba bezpečně (jen pokud už nebyli smazáni) vymažeme |
|
||||
if p in projektily: projektily.remove(p) |
|
||||
if n in nepratele: nepratele.remove(n) |
|
||||
skore += 10 # +10 bodů do kasičky! |
|
||||
|
|
||||
# SLOVO 'break' ZMRAZÍ TENTO VNITŘNÍ FOR CYKLUS! |
|
||||
# Má to logiku - Pokud tento Nepřítel 'n' byl teď vteřinu zničen, už nedává smysl dál |
|
||||
# v tomto mikrosnímku procházet zbylé kulky a ptát se, jestli ho střelily taky. Přerušíme to. |
|
||||
break |
|
||||
|
|
||||
# 3. KRESLENÍ GRAFIKY |
|
||||
okno.fill(CERNA) |
|
||||
|
|
||||
if stav == STAV_MENU: |
|
||||
text = font_velky.render("VESMÍRNÁ STŘÍLEČKA", True, ZLUCA) |
|
||||
start = font_maly.render("Stiskni MEZERNÍK. Střílíš mezerníkem, pohyb šipkami.", True, BILA) |
|
||||
okno.blit(text, (SIRKA//2 - text.get_width()//2, 200)) |
|
||||
okno.blit(start, (SIRKA//2 - start.get_width()//2, 300)) |
|
||||
|
|
||||
elif stav == STAV_HRA: |
|
||||
# Jsme ve hře! Nyní se z paměti převedou čísla Rect rámečků do reálných obrazců na monitoru. |
|
||||
|
|
||||
# Naše loď |
|
||||
pygame.draw.rect(okno, ZELENA, hrac_rect) |
|
||||
|
|
||||
# Smyčka vykreslí všechny Lasery co jsou aktuálně ve vzduchu |
|
||||
for p in projektily: |
|
||||
pygame.draw.rect(okno, ZLUCA, p) |
|
||||
|
|
||||
# Ta samá smyčka vykreslí pluky všech padajících Nepřátel |
|
||||
for n in nepratele: |
|
||||
pygame.draw.rect(okno, CERVENA, n) |
|
||||
|
|
||||
# Zobrazení skóre |
|
||||
skore_text = font_maly.render(f"Skóre: {skore}", True, BILA) |
|
||||
okno.blit(skore_text, (10, 10)) |
|
||||
|
|
||||
elif stav == STAV_KONEC: |
|
||||
text = font_velky.render("ZEMŘEL JSI!", True, CERVENA) |
|
||||
skore_text = font_velky.render(f"Tvé skóre: {skore}", True, BILA) |
|
||||
restart = font_maly.render("Stiskni MEZERNÍK pro novou hru", True, ZLUCA) |
|
||||
|
|
||||
okno.blit(text, (SIRKA//2 - text.get_width()//2, 150)) |
|
||||
okno.blit(skore_text, (SIRKA//2 - skore_text.get_width()//2, 250)) |
|
||||
okno.blit(restart, (SIRKA//2 - restart.get_width()//2, 350)) |
|
||||
|
|
||||
# Obraz je spočítán. Posíláme do HDMI portu na monitor! (.flip) |
|
||||
pygame.display.flip() |
|
||||
hodiny.tick(60) |
|
||||
|
|
||||
pygame.quit() |
|
||||
sys.exit() |
|
||||
|
async def main(): # PŘIDÁNO PRO WEB: Zabalíme celou hru do asynchronní funkce |
||||
|
pygame.init() |
||||
|
|
||||
|
# --- NASTAVENÍ OKNA --- |
||||
|
SIRKA = 800 |
||||
|
VYSKA = 600 |
||||
|
okno = pygame.display.set_mode((SIRKA, VYSKA)) |
||||
|
pygame.display.set_caption("Vesmírná střílečka (Space Invaders)") |
||||
|
|
||||
|
# --- BARVY --- |
||||
|
CERNA = (0, 0, 0) |
||||
|
ZELENA = (0, 255, 0) # Hráč (Vesmírná loď) |
||||
|
CERVENA = (255, 0, 0) # Nepřátelé (Mimozemšťani, Meteority) |
||||
|
ZLUCA = (255, 255, 0) # Projektily (Lasery) |
||||
|
BILA = (255, 255, 255) # Text a UI |
||||
|
|
||||
|
hodiny = pygame.time.Clock() |
||||
|
font_velky = pygame.font.SysFont("Arial", 50) |
||||
|
font_maly = pygame.font.SysFont("Arial", 30) |
||||
|
|
||||
|
# Herní stav 0,1,2 - stejná logika oddělení úvodního menu od zápasu |
||||
|
STAV_MENU = 0 |
||||
|
STAV_HRA = 1 |
||||
|
STAV_KONEC = 2 |
||||
|
stav = STAV_MENU |
||||
|
|
||||
|
# Rozměry vesmírné lodi hráče |
||||
|
hrac_sirka = 40 |
||||
|
hrac_vyska = 40 |
||||
|
rychlost_hrace = 6 |
||||
|
rychlost_projektilu = 10 # Lasery létají celkem svižně nahoru |
||||
|
|
||||
|
# Vytvoření vlastního časovače pro přidávání nepřátel, viz skákačka |
||||
|
SPAWN_NEPRITELE = pygame.USEREVENT + 1 |
||||
|
|
||||
|
def reset_hry(): |
||||
|
"""Tato funkce srovná hru zpátky do prvního dne, když spustíme reset""" |
||||
|
global hrac_x, hrac_y, projektily, nepratele, skore, rychlost_nepritele, spawn_cas |
||||
|
|
||||
|
# Hráč je dole nad hranou obrazovky |
||||
|
hrac_x = SIRKA // 2 - hrac_sirka // 2 |
||||
|
hrac_y = VYSKA - 60 |
||||
|
|
||||
|
# Budou zde létat nezávisle na sobě desítky kulek i nepřátel. |
||||
|
# Abychom se v nich vyznali, naženeme je všechny do SEZNAMŮ. |
||||
|
projektily = [] |
||||
|
nepratele = [] |
||||
|
|
||||
|
skore = 0 |
||||
|
rychlost_nepritele = 2.0 |
||||
|
spawn_cas = 1000 # Počáteční čas (v milisekundách) k objevování padajících mimozemšťanů |
||||
|
|
||||
|
# Nastartování naší události (eventu), začne tikat... |
||||
|
pygame.time.set_timer(SPAWN_NEPRITELE, spawn_cas) |
||||
|
|
||||
|
reset_hry() |
||||
|
|
||||
|
bezime = True |
||||
|
while bezime: |
||||
|
# 1. ZPRACOVÁNÍ UDÁLOSTÍ |
||||
|
for udalost in pygame.event.get(): |
||||
|
if udalost.type == pygame.QUIT: |
||||
|
bezime = False |
||||
|
|
||||
|
# Zjišťujeme JEDNORÁZOVÉ stisknutí tlačítek. Například výstřel nebo posun v menu. |
||||
|
if udalost.type == pygame.KEYDOWN: |
||||
|
if stav == STAV_MENU and udalost.key == pygame.K_SPACE: |
||||
|
stav = STAV_HRA |
||||
|
elif stav == STAV_KONEC and udalost.key == pygame.K_SPACE: |
||||
|
reset_hry() |
||||
|
stav = STAV_HRA |
||||
|
elif stav == STAV_HRA and udalost.key == pygame.K_SPACE: |
||||
|
# --- VÝSTŘEL --- |
||||
|
# Hráč právě odpálil! Vypočítáme, kde zhruba leží střed jeho lodi (špička křídel) |
||||
|
projektil_x = hrac_x + hrac_sirka // 2 - 5 |
||||
|
# A přímo na těchto souřadnicích se narodí nová "KULKA" v podobě Rect. Přidáme do listu. |
||||
|
projektily.append(pygame.Rect(projektil_x, hrac_y, 10, 20)) |
||||
|
|
||||
|
# Zde časovač zakřičel (třeba každou sekundu), že na nás padá další nepřítel |
||||
|
if udalost.type == SPAWN_NEPRITELE and stav == STAV_HRA: |
||||
|
# Narodil se nový nepřítel nahoře mimo obraz (proto je osa y záporná: -40) na náhodném místě osy X. |
||||
|
n_x = random.randint(0, SIRKA - 40) |
||||
|
nepratele.append(pygame.Rect(n_x, -40, 40, 40)) |
||||
|
|
||||
|
# STUPŇOVÁNÍ OBTÍŽNOSTI (Nepřátelé padají čím dál rychleji) |
||||
|
rychlost_nepritele += 0.05 |
||||
|
|
||||
|
# Čím déle hrajeme, tím se také zkracuje ten časovač a nepřátel je hustší roj. |
||||
|
# Omezíme to na min 300ms, aby ta hra byla alespoň fyzicky dohratelná a nepřátelé nespadli z jednolité zdi. |
||||
|
if spawn_cas > 300: |
||||
|
spawn_cas -= 20 |
||||
|
# Musíme časovač s tímto novým o 20ms kratším časem znovu "nastartovat" |
||||
|
pygame.time.set_timer(SPAWN_NEPRITELE, spawn_cas) |
||||
|
|
||||
|
if stav == STAV_HRA: |
||||
|
# 2. LOGIKA |
||||
|
|
||||
|
# Ovládání vesmírné lodi doprava a doleva držením tlačítek |
||||
|
klavesy = pygame.key.get_pressed() |
||||
|
if klavesy[pygame.K_LEFT] and hrac_x > 0: |
||||
|
hrac_x -= rychlost_hrace |
||||
|
if klavesy[pygame.K_RIGHT] and hrac_x < SIRKA - hrac_sirka: |
||||
|
hrac_x += rychlost_hrace |
||||
|
|
||||
|
# --- POHYB VŠECH LASERŮ (KULEK) --- |
||||
|
# Projektily[:] dělá kopii listu, to se musí dělat vždy, když chci za běhu for-cyklu z toho pole |
||||
|
# věci rovnou odstraňovat! Kdybychom neodstraňovali kulky, list by narostl a hra spadla na paměť. |
||||
|
for p in projektily[:]: |
||||
|
p.y -= rychlost_projektilu # Y se ZMENŠUJE, protože kulka cestuje odspodu (např. Y=500) nahoru (Y=0) |
||||
|
|
||||
|
# Když kulka odcestuje nad okraj obrazovky do hlubin kosmu, smažeme ji |
||||
|
if p.y < 0: projektily.remove(p) |
||||
|
|
||||
|
# Hráč samotný obalený do neviditelného Rect rámečku pro vyhodnocování havárií |
||||
|
hrac_rect = pygame.Rect(hrac_x, hrac_y, hrac_sirka, hrac_vyska) |
||||
|
|
||||
|
# --- POHYB VŠECH NEPŘÁTEL & KOLIZE --- |
||||
|
for n in nepratele[:]: |
||||
|
n.y += int(rychlost_nepritele) # Zvyšujeme Y -> Meteorit letí strmě dolů z nebes k podlaze |
||||
|
|
||||
|
# 1. Havárie! Pokud meteorit doletí až úplně dolů přes obrazovku, NEBO narazí do Hráče |
||||
|
if n.y > VYSKA or n.colliderect(hrac_rect): |
||||
|
stav = STAV_KONEC |
||||
|
|
||||
|
# 2. Křížová kontrola, zda SE STŘETLA KULKA S NEPŘÍTELEM (Laser do Meteoritu) |
||||
|
# Každého meteorita porovnáme znovu se všemi létajícími lasery na scéně! |
||||
|
for p in projektily[:]: |
||||
|
# .colliderect znamená "prolnuly se tyhle dva čtverce?" |
||||
|
if p.colliderect(n): |
||||
|
# BUM! Nastal obrovský výbuch. |
||||
|
# Zmizí Laser i Meteorit, tak je oba bezpečně (jen pokud už nebyli smazáni) vymažeme |
||||
|
if p in projektily: projektily.remove(p) |
||||
|
if n in nepratele: nepratele.remove(n) |
||||
|
skore += 10 # +10 bodů do kasičky! |
||||
|
|
||||
|
# SLOVO 'break' ZMRAZÍ TENTO VNITŘNÍ FOR CYKLUS! |
||||
|
# Má to logiku - Pokud tento Nepřítel 'n' byl teď vteřinu zničen, už nedává smysl dál |
||||
|
# v tomto mikrosnímku procházet zbylé kulky a ptát se, jestli ho střelily taky. Přerušíme to. |
||||
|
break |
||||
|
|
||||
|
# 3. KRESLENÍ GRAFIKY |
||||
|
okno.fill(CERNA) |
||||
|
|
||||
|
if stav == STAV_MENU: |
||||
|
text = font_velky.render("VESMÍRNÁ STŘÍLEČKA", True, ZLUCA) |
||||
|
start = font_maly.render("Stiskni MEZERNÍK. Střílíš mezerníkem, pohyb šipkami.", True, BILA) |
||||
|
okno.blit(text, (SIRKA//2 - text.get_width()//2, 200)) |
||||
|
okno.blit(start, (SIRKA//2 - start.get_width()//2, 300)) |
||||
|
|
||||
|
elif stav == STAV_HRA: |
||||
|
# Jsme ve hře! Nyní se z paměti převedou čísla Rect rámečků do reálných obrazců na monitoru. |
||||
|
|
||||
|
# Naše loď |
||||
|
pygame.draw.rect(okno, ZELENA, hrac_rect) |
||||
|
|
||||
|
# Smyčka vykreslí všechny Lasery co jsou aktuálně ve vzduchu |
||||
|
for p in projektily: |
||||
|
pygame.draw.rect(okno, ZLUCA, p) |
||||
|
|
||||
|
# Ta samá smyčka vykreslí pluky všech padajících Nepřátel |
||||
|
for n in nepratele: |
||||
|
pygame.draw.rect(okno, CERVENA, n) |
||||
|
|
||||
|
# Zobrazení skóre |
||||
|
skore_text = font_maly.render(f"Skóre: {skore}", True, BILA) |
||||
|
okno.blit(skore_text, (10, 10)) |
||||
|
|
||||
|
elif stav == STAV_KONEC: |
||||
|
text = font_velky.render("ZEMŘEL JSI!", True, CERVENA) |
||||
|
skore_text = font_velky.render(f"Tvé skóre: {skore}", True, BILA) |
||||
|
restart = font_maly.render("Stiskni MEZERNÍK pro novou hru", True, ZLUCA) |
||||
|
|
||||
|
okno.blit(text, (SIRKA//2 - text.get_width()//2, 150)) |
||||
|
okno.blit(skore_text, (SIRKA//2 - skore_text.get_width()//2, 250)) |
||||
|
okno.blit(restart, (SIRKA//2 - restart.get_width()//2, 350)) |
||||
|
|
||||
|
# Obraz je spočítán. Posíláme do HDMI portu na monitor! (.flip) |
||||
|
pygame.display.flip() |
||||
|
hodiny.tick(60) |
||||
|
# PŘIDÁNO PRO WEB: Dáme prohlížeči šanci překreslit obrazovku |
||||
|
await asyncio.sleep(0) |
||||
|
|
||||
|
pygame.quit() |
||||
|
sys.exit() |
||||
|
|
||||
|
|
||||
|
# PŘIDÁNO PRO WEB: Spuštění asynchronní hry |
||||
|
asyncio.run(main()) |
||||
@ -1,174 +1,182 @@ |
|||||
import pygame |
import pygame |
||||
|
import asyncio # PŘIDÁNO PRO WEB: importujeme asyncio pro neblokující smyčku |
||||
import random |
import random |
||||
import sys |
import sys |
||||
|
|
||||
# Povinná příprava knihovny Pygame |
# Povinná příprava knihovny Pygame |
||||
pygame.init() |
|
||||
|
|
||||
# --- NASTAVENÍ OKNA A MŘÍŽKY --- |
|
||||
# Hra SNAKE (Had) většinou nefunguje na pixely jako ostatní hry, ale funguje na jakési neviditelné "Mřížce". |
|
||||
SIRKA_OKNA = 800 |
|
||||
VYSKA_OKNA = 600 |
|
||||
VELIKOST_DILKU = 20 # Velikost jednoho čtverečku na mřížce (dílku hada i jídla) |
|
||||
FPS = 15 # Rychlost hada. Zde je nižší 15, protože pohyb po skocích na mřížce nevyžaduje 60 FPS. |
|
||||
|
|
||||
# Definice Palety Barev (Red, Green, Blue) |
|
||||
CERNA = (0, 0, 0) |
|
||||
BILA = (255, 255, 255) |
|
||||
ZELENA = (0, 255, 0) |
|
||||
CERVENA = (255, 0, 0) |
|
||||
|
|
||||
okno = pygame.display.set_mode((SIRKA_OKNA, VYSKA_OKNA)) |
|
||||
pygame.display.set_caption("Klasický Had (Snake)") |
|
||||
|
|
||||
hodiny = pygame.time.Clock() |
|
||||
font = pygame.font.SysFont("arial", 36) |
|
||||
|
|
||||
# Pomocná funkce pro rychlé vykreslení textu na daných x, y. |
|
||||
# Zkracuje to kód, abychom pořád neopakovali metody render() a blit(). |
|
||||
def zobraz_text(text, barva, x, y): |
|
||||
text_plocha = font.render(text, True, barva) |
|
||||
okno.blit(text_plocha, (x, y)) |
|
||||
|
|
||||
# Veškerou logiku hry teď poprvé schováme do tzv. Hlavní Funkce. |
|
||||
# Proč? Jakmile funkce skončí (kvůli výhře/prohře), velmi jednoduše se tímto trikem dá celá hra resetovat, |
|
||||
# jelikož stačí funkci zavolat odznova. |
|
||||
def hlavni_smycka(): |
|
||||
|
|
||||
# --- POČÁTEČNÍ STAV HRY --- |
|
||||
# Nejdůležitější princip této hry! Tělo hada není jeden objekt, je to SEZNAM SOUŘADNIC (pole v poli). |
|
||||
# Každý článek jeho těla má své [x, y]. Ten ÚPLNĚ PRVNÍ prvek na pozici 0 je HLAVA HADA. |
|
||||
had_telo = [ |
|
||||
[SIRKA_OKNA // 2, VYSKA_OKNA // 2], # Hlava |
|
||||
[SIRKA_OKNA // 2 - VELIKOST_DILKU, VYSKA_OKNA // 2], # Druhý článek břicha |
|
||||
[SIRKA_OKNA // 2 - 2 * VELIKOST_DILKU, VYSKA_OKNA // 2] # Ocas |
|
||||
] |
|
||||
|
|
||||
# Směr pohybu! Tohle číslo přidáme při každém snímku k souřadnicím HLAVY. |
|
||||
# Tím ji virtuálně posuneme vpřed. |
|
||||
smer_x = VELIKOST_DILKU # Z počátku letí had doprava, protože z osy X roste pozitivně |
|
||||
smer_y = 0 # Do vertikály Y mu zpočátku nepřidáváme nic, letí rovně |
|
||||
|
|
||||
# --- POZICE JÍDLA (ČERVENÝ ČTVEREČEK) --- |
|
||||
# Jídlo musíme nasměrovat přesně do nějaké buňky mřížky! |
|
||||
# Trik: random.randrange(od, do, krok) ... Jídlo padne například na X=20, 40, 60... ale NIKDY na 17, 33 atd. |
|
||||
# Kdyby nebylo zarovnané na mřížku, tak ho had miné! |
|
||||
jidlo_x = random.randrange(0, SIRKA_OKNA, VELIKOST_DILKU) |
|
||||
jidlo_y = random.randrange(0, VYSKA_OKNA, VELIKOST_DILKU) |
|
||||
|
|
||||
skore = 0 |
|
||||
konec_hry = False # Boolean kontrolka pro Game Over |
|
||||
|
|
||||
# Nekonečná smyčka tohoto jednoho konkrétního zápasu |
|
||||
while True: |
|
||||
|
|
||||
# 1. ZPRACOVÁNÍ UDÁLOSTÍ |
|
||||
for udalost in pygame.event.get(): |
|
||||
if udalost.type == pygame.QUIT: |
|
||||
pygame.quit() |
|
||||
sys.exit() |
|
||||
|
|
||||
# Detekce šipek pro změnu SMĚRU pohybu HADA |
|
||||
if udalost.type == pygame.KEYDOWN: |
|
||||
|
|
||||
# OCHRANA: Had nemůže provést fyzicky nemožný obrat o 180 stupňů a zajet si rovnou do těla! |
|
||||
# Tzn. pokud zmáčknul šipku DOLEVA, a had letí rovně/doprava (smer_x == 0), |
|
||||
# teprve pak si smí dovolit nastavit změnu směru. |
|
||||
if udalost.key == pygame.K_LEFT and smer_x == 0: |
|
||||
smer_x = -VELIKOST_DILKU # Záporná hodnota pro směr vlevo |
|
||||
smer_y = 0 # Vertikální se ruší |
|
||||
elif udalost.key == pygame.K_RIGHT and smer_x == 0: |
|
||||
smer_x = VELIKOST_DILKU # Kladná hodnota pro směr vpravo |
|
||||
smer_y = 0 |
|
||||
elif udalost.key == pygame.K_UP and smer_y == 0: |
|
||||
smer_x = 0 |
|
||||
smer_y = -VELIKOST_DILKU # Záporná hodnota směrem vzhůru k Y=0 |
|
||||
elif udalost.key == pygame.K_DOWN and smer_y == 0: |
|
||||
smer_x = 0 |
|
||||
smer_y = VELIKOST_DILKU |
|
||||
|
|
||||
# Pokud zemřel a bliká varování, čekáme na Mezerník |
|
||||
if udalost.key == pygame.K_SPACE and konec_hry: |
|
||||
# Pokud nastane RETURN, znamená to, že se celá tato 'hlavni_smycka()' okamžitě ukončí, |
|
||||
# její paměť (proměnné had_telo atd) se nenávratně vymaže. |
|
||||
# Následně ji však náš spouštěč úplně dole v souboru spustí naprosto na čisto znova. (RESTART) |
|
||||
return |
|
||||
|
|
||||
# LOGIKA HRY: Provede se jenom pokud žijeme |
|
||||
if not konec_hry: |
|
||||
|
|
||||
# --- ZÁKLADNÍ PRINCIP POHYBU HADA --- |
|
||||
# Jak hada posunout? |
|
||||
# 1. Spočítáme, kde se bude nacházet hadova hlava v PŘÍŠTÍM Snímku |
|
||||
# Vezmeme si pozici staré hlavy, neboli had_telo na nultém indexu |
|
||||
nova_hlava_x = had_telo[0][0] + smer_x |
|
||||
nova_hlava_y = had_telo[0][1] + smer_y |
|
||||
|
|
||||
# 2. Tuto ZCELA NOVOU POZICI vsuneme pomocí "insert" na ÚPLNÝ ZAČÁTEK (index 0) našeho Seznamu |
|
||||
# Náš had je najednou o kousek delší! (Natáhl hlavu tam, kam letí) |
|
||||
had_telo.insert(0, [nova_hlava_x, nova_hlava_y]) |
|
||||
|
|
||||
# --- SEŽRÁNÍ JÍDLA (Růst hada) --- |
|
||||
# Zjišťujeme, zda se souřadnice nové hlavy náhodou rovnou nerovnají pozici Jídla na mřížce. |
|
||||
# Nepoužíváme obdélníkové kolize, protože pracujeme s přesnou mřížkou! |
|
||||
if nova_hlava_x == jidlo_x and nova_hlava_y == jidlo_y: |
|
||||
skore += 1 # Bod do tabulky! |
|
||||
|
|
||||
# Zrušíme staré jídlo a najdeme mu na hracím plánu nové náhodné místo (zarovnané na mřížku) |
|
||||
jidlo_x = random.randrange(0, SIRKA_OKNA, VELIKOST_DILKU) |
|
||||
jidlo_y = random.randrange(0, VYSKA_OKNA, VELIKOST_DILKU) |
|
||||
|
|
||||
# TOHLE JE DŮLEŽITÉ: Náš hadí Seznam byl momentálně obohacen o přední novou "hlavu", tzn vyrostl, a |
|
||||
# a my mu ten starý OCAS NEZKRÁTÍME. Takže je opravdu objektivně o jedno políčko natrvalo větší! |
|
||||
else: |
|
||||
# Pokud jídlo na této pozici NEBYLO, my musíme zachovat hada stále stejně dlouhého. |
|
||||
# Nahoře jsme mu před chvílí přidali hlavičku. Tady dole mu z konce seznamu (ocásku) |
|
||||
# jeden prvek odebereme (pomocí .pop()), takže vlastně provedl POHYB A JE STÁLE STEJNĚ DLOUHÝ. |
|
||||
had_telo.pop() |
|
||||
|
|
||||
# --- SMRTELNÉ NÁRAZY (Stěna a Sebevražda) --- |
|
||||
|
|
||||
# Pokud vylétla nová hlava mimo rozměry Okna... |
|
||||
if (nova_hlava_x < 0 or nova_hlava_x >= SIRKA_OKNA or |
|
||||
nova_hlava_y < 0 or nova_hlava_y >= VYSKA_OKNA): |
|
||||
konec_hry = True # GAME OVER |
|
||||
|
|
||||
# Nebo pokud had sežral vlastního ocas! |
|
||||
# for-cyklus "for dilek in had_telo[1:]" prohlédne VŠE kromě samotné hlavy (začíná od indexu 1, čili tělo) |
|
||||
# A pokud se na jedné jediné z těchto pozic těla nachází nově nakreslená hlava, tak had do sebe kousl. |
|
||||
for dilek in had_telo[1:]: |
|
||||
if nova_hlava_x == dilek[0] and nova_hlava_y == dilek[1]: |
|
||||
konec_hry = True |
|
||||
|
|
||||
# --- VYKRESLOVÁNÍ (Obarvování monitoru) --- |
|
||||
okno.fill(CERNA) # Umytí tabule na černo |
|
||||
|
|
||||
# 1. Jídlo (Čtverec o rozměrech jedné buňky na mřížce) |
|
||||
pygame.draw.rect(okno, CERVENA, (jidlo_x, jidlo_y, VELIKOST_DILKU, VELIKOST_DILKU)) |
|
||||
|
|
||||
# 2. Celý samotný dlouhý had |
|
||||
# Projdeme seznam všech buněk a pro každičkou z nich na daném [X, Y] nakreslíme jeden zelený kvádr |
|
||||
for dilek in had_telo: |
|
||||
pygame.draw.rect(okno, ZELENA, (dilek[0], dilek[1], VELIKOST_DILKU, VELIKOST_DILKU)) |
|
||||
|
|
||||
zobraz_text(f"Skóre: {skore}", BILA, 10, 10) |
|
||||
|
|
||||
if konec_hry: |
|
||||
# Zásadní hláška uprostřed pole o tom, ať to zkusí hráč znovu. |
|
||||
zobraz_text("Konec hry! Stiskni MEZERNÍK pro novou hru.", CERVENA, 100, VYSKA_OKNA // 2) |
|
||||
|
|
||||
# Hotový frame se prohazuje s tím na monitoru |
|
||||
pygame.display.flip() |
|
||||
|
|
||||
# Omezení rychlosti |
|
||||
hodiny.tick(FPS) |
|
||||
|
|
||||
# Úplný spodek skriptu Pythonu |
|
||||
# Tento 'if' ověřuje, jestli Python zapnul tento soubor napřímo jako první. |
|
||||
if __name__ == "__main__": |
|
||||
|
|
||||
# NEKONEČNÁ SLUČKA ŽIVOTA. |
|
||||
# Jakmile uvnitř `hlavni_smycka` zavoláme 'return' kvůli Game Over + stisku Mezerníku, |
|
||||
# funkce se zničí, a tento while True ji opět vzkřísí z prachu k novému životu s čistým listem skóre. |
|
||||
while True: |
|
||||
hlavni_smycka() |
|
||||
|
async def main(): # PŘIDÁNO PRO WEB: Zabalíme celou hru do asynchronní funkce |
||||
|
pygame.init() |
||||
|
|
||||
|
# --- NASTAVENÍ OKNA A MŘÍŽKY --- |
||||
|
# Hra SNAKE (Had) většinou nefunguje na pixely jako ostatní hry, ale funguje na jakési neviditelné "Mřížce". |
||||
|
SIRKA_OKNA = 800 |
||||
|
VYSKA_OKNA = 600 |
||||
|
VELIKOST_DILKU = 20 # Velikost jednoho čtverečku na mřížce (dílku hada i jídla) |
||||
|
FPS = 15 # Rychlost hada. Zde je nižší 15, protože pohyb po skocích na mřížce nevyžaduje 60 FPS. |
||||
|
|
||||
|
# Definice Palety Barev (Red, Green, Blue) |
||||
|
CERNA = (0, 0, 0) |
||||
|
BILA = (255, 255, 255) |
||||
|
ZELENA = (0, 255, 0) |
||||
|
CERVENA = (255, 0, 0) |
||||
|
|
||||
|
okno = pygame.display.set_mode((SIRKA_OKNA, VYSKA_OKNA)) |
||||
|
pygame.display.set_caption("Klasický Had (Snake)") |
||||
|
|
||||
|
hodiny = pygame.time.Clock() |
||||
|
font = pygame.font.SysFont("arial", 36) |
||||
|
|
||||
|
# Pomocná funkce pro rychlé vykreslení textu na daných x, y. |
||||
|
# Zkracuje to kód, abychom pořád neopakovali metody render() a blit(). |
||||
|
def zobraz_text(text, barva, x, y): |
||||
|
text_plocha = font.render(text, True, barva) |
||||
|
okno.blit(text_plocha, (x, y)) |
||||
|
|
||||
|
# Veškerou logiku hry teď poprvé schováme do tzv. Hlavní Funkce. |
||||
|
# Proč? Jakmile funkce skončí (kvůli výhře/prohře), velmi jednoduše se tímto trikem dá celá hra resetovat, |
||||
|
# jelikož stačí funkci zavolat odznova. |
||||
|
def hlavni_smycka(): |
||||
|
|
||||
|
# --- POČÁTEČNÍ STAV HRY --- |
||||
|
# Nejdůležitější princip této hry! Tělo hada není jeden objekt, je to SEZNAM SOUŘADNIC (pole v poli). |
||||
|
# Každý článek jeho těla má své [x, y]. Ten ÚPLNĚ PRVNÍ prvek na pozici 0 je HLAVA HADA. |
||||
|
had_telo = [ |
||||
|
[SIRKA_OKNA // 2, VYSKA_OKNA // 2], # Hlava |
||||
|
[SIRKA_OKNA // 2 - VELIKOST_DILKU, VYSKA_OKNA // 2], # Druhý článek břicha |
||||
|
[SIRKA_OKNA // 2 - 2 * VELIKOST_DILKU, VYSKA_OKNA // 2] # Ocas |
||||
|
] |
||||
|
|
||||
|
# Směr pohybu! Tohle číslo přidáme při každém snímku k souřadnicím HLAVY. |
||||
|
# Tím ji virtuálně posuneme vpřed. |
||||
|
smer_x = VELIKOST_DILKU # Z počátku letí had doprava, protože z osy X roste pozitivně |
||||
|
smer_y = 0 # Do vertikály Y mu zpočátku nepřidáváme nic, letí rovně |
||||
|
|
||||
|
# --- POZICE JÍDLA (ČERVENÝ ČTVEREČEK) --- |
||||
|
# Jídlo musíme nasměrovat přesně do nějaké buňky mřížky! |
||||
|
# Trik: random.randrange(od, do, krok) ... Jídlo padne například na X=20, 40, 60... ale NIKDY na 17, 33 atd. |
||||
|
# Kdyby nebylo zarovnané na mřížku, tak ho had miné! |
||||
|
jidlo_x = random.randrange(0, SIRKA_OKNA, VELIKOST_DILKU) |
||||
|
jidlo_y = random.randrange(0, VYSKA_OKNA, VELIKOST_DILKU) |
||||
|
|
||||
|
skore = 0 |
||||
|
konec_hry = False # Boolean kontrolka pro Game Over |
||||
|
|
||||
|
# Nekonečná smyčka tohoto jednoho konkrétního zápasu |
||||
|
while True: |
||||
|
|
||||
|
# 1. ZPRACOVÁNÍ UDÁLOSTÍ |
||||
|
for udalost in pygame.event.get(): |
||||
|
if udalost.type == pygame.QUIT: |
||||
|
pygame.quit() |
||||
|
sys.exit() |
||||
|
|
||||
|
# Detekce šipek pro změnu SMĚRU pohybu HADA |
||||
|
if udalost.type == pygame.KEYDOWN: |
||||
|
|
||||
|
# OCHRANA: Had nemůže provést fyzicky nemožný obrat o 180 stupňů a zajet si rovnou do těla! |
||||
|
# Tzn. pokud zmáčknul šipku DOLEVA, a had letí rovně/doprava (smer_x == 0), |
||||
|
# teprve pak si smí dovolit nastavit změnu směru. |
||||
|
if udalost.key == pygame.K_LEFT and smer_x == 0: |
||||
|
smer_x = -VELIKOST_DILKU # Záporná hodnota pro směr vlevo |
||||
|
smer_y = 0 # Vertikální se ruší |
||||
|
elif udalost.key == pygame.K_RIGHT and smer_x == 0: |
||||
|
smer_x = VELIKOST_DILKU # Kladná hodnota pro směr vpravo |
||||
|
smer_y = 0 |
||||
|
elif udalost.key == pygame.K_UP and smer_y == 0: |
||||
|
smer_x = 0 |
||||
|
smer_y = -VELIKOST_DILKU # Záporná hodnota směrem vzhůru k Y=0 |
||||
|
elif udalost.key == pygame.K_DOWN and smer_y == 0: |
||||
|
smer_x = 0 |
||||
|
smer_y = VELIKOST_DILKU |
||||
|
|
||||
|
# Pokud zemřel a bliká varování, čekáme na Mezerník |
||||
|
if udalost.key == pygame.K_SPACE and konec_hry: |
||||
|
# Pokud nastane RETURN, znamená to, že se celá tato 'hlavni_smycka()' okamžitě ukončí, |
||||
|
# její paměť (proměnné had_telo atd) se nenávratně vymaže. |
||||
|
# Následně ji však náš spouštěč úplně dole v souboru spustí naprosto na čisto znova. (RESTART) |
||||
|
return |
||||
|
|
||||
|
# LOGIKA HRY: Provede se jenom pokud žijeme |
||||
|
if not konec_hry: |
||||
|
|
||||
|
# --- ZÁKLADNÍ PRINCIP POHYBU HADA --- |
||||
|
# Jak hada posunout? |
||||
|
# 1. Spočítáme, kde se bude nacházet hadova hlava v PŘÍŠTÍM Snímku |
||||
|
# Vezmeme si pozici staré hlavy, neboli had_telo na nultém indexu |
||||
|
nova_hlava_x = had_telo[0][0] + smer_x |
||||
|
nova_hlava_y = had_telo[0][1] + smer_y |
||||
|
|
||||
|
# 2. Tuto ZCELA NOVOU POZICI vsuneme pomocí "insert" na ÚPLNÝ ZAČÁTEK (index 0) našeho Seznamu |
||||
|
# Náš had je najednou o kousek delší! (Natáhl hlavu tam, kam letí) |
||||
|
had_telo.insert(0, [nova_hlava_x, nova_hlava_y]) |
||||
|
|
||||
|
# --- SEŽRÁNÍ JÍDLA (Růst hada) --- |
||||
|
# Zjišťujeme, zda se souřadnice nové hlavy náhodou rovnou nerovnají pozici Jídla na mřížce. |
||||
|
# Nepoužíváme obdélníkové kolize, protože pracujeme s přesnou mřížkou! |
||||
|
if nova_hlava_x == jidlo_x and nova_hlava_y == jidlo_y: |
||||
|
skore += 1 # Bod do tabulky! |
||||
|
|
||||
|
# Zrušíme staré jídlo a najdeme mu na hracím plánu nové náhodné místo (zarovnané na mřížku) |
||||
|
jidlo_x = random.randrange(0, SIRKA_OKNA, VELIKOST_DILKU) |
||||
|
jidlo_y = random.randrange(0, VYSKA_OKNA, VELIKOST_DILKU) |
||||
|
|
||||
|
# TOHLE JE DŮLEŽITÉ: Náš hadí Seznam byl momentálně obohacen o přední novou "hlavu", tzn vyrostl, a |
||||
|
# a my mu ten starý OCAS NEZKRÁTÍME. Takže je opravdu objektivně o jedno políčko natrvalo větší! |
||||
|
else: |
||||
|
# Pokud jídlo na této pozici NEBYLO, my musíme zachovat hada stále stejně dlouhého. |
||||
|
# Nahoře jsme mu před chvílí přidali hlavičku. Tady dole mu z konce seznamu (ocásku) |
||||
|
# jeden prvek odebereme (pomocí .pop()), takže vlastně provedl POHYB A JE STÁLE STEJNĚ DLOUHÝ. |
||||
|
had_telo.pop() |
||||
|
|
||||
|
# --- SMRTELNÉ NÁRAZY (Stěna a Sebevražda) --- |
||||
|
|
||||
|
# Pokud vylétla nová hlava mimo rozměry Okna... |
||||
|
if (nova_hlava_x < 0 or nova_hlava_x >= SIRKA_OKNA or |
||||
|
nova_hlava_y < 0 or nova_hlava_y >= VYSKA_OKNA): |
||||
|
konec_hry = True # GAME OVER |
||||
|
|
||||
|
# Nebo pokud had sežral vlastního ocas! |
||||
|
# for-cyklus "for dilek in had_telo[1:]" prohlédne VŠE kromě samotné hlavy (začíná od indexu 1, čili tělo) |
||||
|
# A pokud se na jedné jediné z těchto pozic těla nachází nově nakreslená hlava, tak had do sebe kousl. |
||||
|
for dilek in had_telo[1:]: |
||||
|
if nova_hlava_x == dilek[0] and nova_hlava_y == dilek[1]: |
||||
|
konec_hry = True |
||||
|
|
||||
|
# --- VYKRESLOVÁNÍ (Obarvování monitoru) --- |
||||
|
okno.fill(CERNA) # Umytí tabule na černo |
||||
|
|
||||
|
# 1. Jídlo (Čtverec o rozměrech jedné buňky na mřížce) |
||||
|
pygame.draw.rect(okno, CERVENA, (jidlo_x, jidlo_y, VELIKOST_DILKU, VELIKOST_DILKU)) |
||||
|
|
||||
|
# 2. Celý samotný dlouhý had |
||||
|
# Projdeme seznam všech buněk a pro každičkou z nich na daném [X, Y] nakreslíme jeden zelený kvádr |
||||
|
for dilek in had_telo: |
||||
|
pygame.draw.rect(okno, ZELENA, (dilek[0], dilek[1], VELIKOST_DILKU, VELIKOST_DILKU)) |
||||
|
|
||||
|
zobraz_text(f"Skóre: {skore}", BILA, 10, 10) |
||||
|
|
||||
|
if konec_hry: |
||||
|
# Zásadní hláška uprostřed pole o tom, ať to zkusí hráč znovu. |
||||
|
zobraz_text("Konec hry! Stiskni MEZERNÍK pro novou hru.", CERVENA, 100, VYSKA_OKNA // 2) |
||||
|
|
||||
|
# Hotový frame se prohazuje s tím na monitoru |
||||
|
pygame.display.flip() |
||||
|
|
||||
|
# Omezení rychlosti |
||||
|
hodiny.tick(FPS) |
||||
|
# PŘIDÁNO PRO WEB: Dáme prohlížeči šanci překreslit obrazovku |
||||
|
await asyncio.sleep(0) |
||||
|
|
||||
|
# Úplný spodek skriptu Pythonu |
||||
|
# Tento 'if' ověřuje, jestli Python zapnul tento soubor napřímo jako první. |
||||
|
if __name__ == "__main__": |
||||
|
|
||||
|
# NEKONEČNÁ SLUČKA ŽIVOTA. |
||||
|
# Jakmile uvnitř `hlavni_smycka` zavoláme 'return' kvůli Game Over + stisku Mezerníku, |
||||
|
# funkce se zničí, a tento while True ji opět vzkřísí z prachu k novému životu s čistým listem skóre. |
||||
|
while True: |
||||
|
hlavni_smycka() |
||||
|
|
||||
|
|
||||
|
# PŘIDÁNO PRO WEB: Spuštění asynchronní hry |
||||
|
asyncio.run(main()) |
||||
Loading…
Reference in new issue