diff --git a/README.md b/README.md index 88543e7..bcd5225 100644 --- a/README.md +++ b/README.md @@ -59,4 +59,25 @@ Pokud nevíte jak začít, zkuste se držet těchto bodů: 4. **Rozdělte si práci:** Pokud na začátku narazíte na příliš složitý problém, rozdělte si ho na menší části. Nezkoušejte naprogramovat "bojový systém" jako celek. Nejdřív naprogramujte "stisknutí mezerníku ubere nepříteli 1 HP". 5. **Dělejte si zálohy:** Často ukládejte a pokud něco začne fungovat, udělejte si kopii souboru (nebo použijte Git!), než do toho začnete vrtat dál. -Hodně štěstí při vývoji! Nezapomeňte, že primárním cílem Game Jamu je se něco nového naučit a pobavit se u toho. \ No newline at end of file +Hodně štěstí při vývoji! Nezapomeňte, že primárním cílem Game Jamu je se něco nového naučit a pobavit se u toho. + +--- + +## 🌐 Optimalizace pro Školní Webový Portál (GameGlass) + +Aby vaše hry fungovaly nejen u vás na počítači (Desktopu), ale šly bez problémů nahrát a hrát přímo ve webovém prohlížeči v našem školním systému, musíte dodržet několik důležitých technických pravidel: + +### 1. Pygame hry (Nutnost použít asynchronní kód) +Webový prohlížeč neumožňuje tzv. "nekonečné blokovací smyčky" (zamrzla by vám celá záložka v prohlížeči). Vaše hlavní herní smyčka proto **musí** být napsána asynchronně pomocí modulu `asyncio`: + +* **Pravidlo 1:** Hlavní smyčka hry musí být definována jako `async def hlavni_smycka():`. +* **Pravidlo 2:** Uvnitř `while True:` smyčky musíte na konec každého kola (typicky za `pygame.display.flip()`) přidat `await asyncio.sleep(0)`. Tento příkaz "na milisekundu" uvolní prohlížeč, aby stihl vykreslit obraz. +* **Pravidlo 3:** 🚫 **ZÁKAZ** používání `pygame.time.wait()` nebo `pygame.time.delay()`. Tyto funkce na webu totálně zamrazí celou obrazovku. Pokud potřebujete hru na vteřinu uspat (např. při chybě v Pexesu), použijte `await asyncio.sleep(1)`. +* **Pravidlo 4:** Spuštění hry na samotném konci souboru musí vypadat takto: `asyncio.run(hlavni_smycka())` + +*(Prohlédněte si naše ukázky v adresáři `pygame/`, všechny jsou už pro web plně optimalizované!)* + +### 2. Balení her pro odevzdání (ZIP) +Na portál se hry nahrávají ve formátu `.zip`. Je ale velmi důležité, co do ZIPu zabalíte: +* **Pygame a Čistý Python:** Hlavní spouštěcí soubor vaší hry by měl být umístěn přímo v kořenovém adresáři ZIPu (nezašívejte ho hluboko do složek). Náš portál se ho pokusí najít a pokud se jmenuje `main.py`, je to ideální. Spolu se zdrojovým kódem nezapomeňte do ZIPu přibalit i složku se zvuky a grafikou (např. `/assets`). +* **Ren'Py (Vizuální novely):** Nemusíte z Ren'Py enginu dělat žádný složitý "Web Export"! Stačí, když vezmete celou složku s vaším projektem (tu, která obsahuje podsložku `game/` se soubory `script.rpy` atd.) a rovnou ji zazipujete. Náš webový portál si tento ZIP automaticky rozbalí a nasadí do něj svůj vlastní webový RenPy motor. Je to naprosto bez starostí! \ No newline at end of file diff --git a/README_PRO_STUDENTY.md b/README_PRO_STUDENTY.md new file mode 100644 index 0000000..6ca67c7 --- /dev/null +++ b/README_PRO_STUDENTY.md @@ -0,0 +1,104 @@ +# 🎮 Návod pro studenty: Jak připravit hru pro GameGlass Portál + +Ahoj! Abys mohl/a svou hru nahrát na náš školní GameGlass portál a ukázat ji všem ostatním přímo ve webovém prohlížeči, stačí dodržet několik jednoduchých pravidel. + +--- + +## 1. Pravidla pro Pygame (Grafické hry) + +Aby tvoje Pygame hra mohla běžet uvnitř internetového prohlížeče, musí být strukturována tzv. "asynchronně" (aby nezamrzla prohlížeč). + +**Musíš udělat přesně tyto 3 kroky ve svém kódu:** + +1. **Importuj `asyncio`:** + Na úplný začátek souboru, hned k `import pygame`, přidej: + ```python + import pygame + import asyncio + ``` + +2. **Zabal hru do asynchronní funkce:** + Celý kód tvé hry (nebo alespoň tvoji hlavní herní smyčku) musíš zabalit do asynchronní funkce (např. `async def main():`). + A na úplný konec souboru pak hru spustit pomocí `asyncio.run(main())`. + +3. **Přidej odpočinkovou pauzu do smyčky:** + Do tvé hlavní smyčky `while True:` musíš (ideálně nakonec smyčky) přidat magické zaříkávadlo: + ```python + await asyncio.sleep(0) + ``` + *Tento příkaz řekne prohlížeči: "Teď si můžeš na milisekundu odpočinout a vykreslit obrazovku". Bez tohoto příkazu hra okamžitě zamrzne a spadne!* + +**Ukázková kostra správné Pygame hry:** +```python +import pygame +import asyncio + +# --- Zde dej své funkce a nastavení --- + +async def main(): + pygame.init() + screen = pygame.display.set_mode((800, 600)) + clock = pygame.time.Clock() + running = True + + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + # Zde se děje logika hry a vykreslování... + screen.fill((0, 0, 0)) + pygame.display.flip() + + # Omezení FPS + clock.tick(60) + + # MAGICKÝ PŘÍKAZ (NUTNÉ PRO WEB!) + await asyncio.sleep(0) + + pygame.quit() + +# Spuštění asynchronní hry +if __name__ == "__main__": + asyncio.run(main()) +``` + +--- + +## 2. Pravidla pro terminálové hry (Čistý Python textovky) + +Pokud děláš obyčejnou textovou hru (pouze příkazy `print` a `input`), je to velmi jednoduché: + +- Nemusíš používat `asyncio` vůbec. +- Můžeš normálně používat `input("Zadej volbu: ")`. Portál sám vytvoří interaktivní textové pole. +- Pokud chceš "vyčistit obrazovku" terminálu, můžeš použít klasický příkaz: + ```python + import os + os.system('cls') # nebo os.system('clear') + ``` + +--- + +## 3. Pravidla pro Ren'Py (Vizuální Novely) + +- Tvoje hra bude spuštěna přes Web Assembly engine Ren'Py. +- **Krok navíc:** Před zabalením do ZIPu jdi do složky své hry a zazipuj POUZE obsah složky `game/` (tedy soubory `.rpy` a složku s obrázky `images/`). Portál nepotřebuje zbytek enginu (soubory `.exe`, `.sh`), engine už v portálu je. + +--- + +## ⚠️ DŮLEŽITÉ UPOZORNĚNÍ K CESTÁM V KÓDU (Pro všechny hry) +**NIKDY** ve svém kódu nepoužívejte tzv. "natvrdo zadrátované cesty" k obrázkům nebo k vašemu disku! (Tzv. Absolutní cesty). + +❌ **ŠPATNĚ (Na webu to spadne):** +```python +os.chdir(r"C:\Users\Student\Plocha\MojeHra") # NIKDY! +image = pygame.image.load(r"C:\Users\Student\Plocha\MojeHra\assets\hrdina.png") +``` + +✅ **SPRÁVNĚ (Relativní cesty):** +Aplikace běží v paměti na internetu (kde disk C:\ vůbec neexistuje). Používejte relativní cesty: +```python +image = pygame.image.load("assets/hrdina.png") +``` + +Až budeš mít hotovo, všechny své soubory a složky označ a zabal přímo do archivu **.ZIP** a nahraj do formuláře na portálu! diff --git a/pygame/07_had.py b/pygame/07_had.py deleted file mode 100644 index 09ec674..0000000 --- a/pygame/07_had.py +++ /dev/null @@ -1,182 +0,0 @@ -import pygame -import asyncio # PŘIDÁNO PRO WEB: importujeme asyncio pro neblokující smyčku -import random -import sys - -# Povinná příprava knihovny Pygame -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. - async 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) - # PRIDANO PRO WEB - 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: - await hlavni_smycka() - - -# PŘIDÁNO PRO WEB: Spuštění asynchronní hry -asyncio.run(main()) \ No newline at end of file diff --git a/pygame/fix_syntax.py b/pygame/fix_syntax.py deleted file mode 100644 index 9162ddd..0000000 --- a/pygame/fix_syntax.py +++ /dev/null @@ -1,11 +0,0 @@ -import os -import glob - -os.chdir(r'C:\gitprojekty_skola\python_gamejam_examples\pygame') -for f in glob.glob('*.py'): - if f.startswith('patch'): continue - with open(f, 'r', encoding='utf-8') as file: - content = file.read() - content = content.replace('async def hlavni_smycka():', 'async def hlavni_smycka():') - with open(f, 'w', encoding='utf-8') as file: - file.write(content) diff --git a/pygame/patch.py b/pygame/patch.py deleted file mode 100644 index 9ff1a64..0000000 --- a/pygame/patch.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -import glob -import re - -os.chdir(r'C:\gitprojekty_skola\python_gamejam_examples\pygame') -for f in glob.glob('*.py'): - with open(f, 'r', encoding='utf-8') as file: - content = file.read() - - # 1. async def - content = re.sub(r'(\s*)def hlavni_smycka\(\):', r'\1async def hlavni_smycka():', content) - - # 2. Add await to hlavni_smycka() calls - content = re.sub(r'(\s+)hlavni_smycka\(\)', r'\1await hlavni_smycka()', content) - - # 3. Fix asyncio.sleep(0) - # Odstranime stary await asyncio.sleep(0) a jeho komentar - content = re.sub(r'[ \t]*# PIDNO PRO WEB: Dme prohlei anci pekreslit obrazovku\s+await asyncio\.sleep\(0\)\s*', '\n', content) - - # Pridame ho tesne za hodiny.tick(...) se stejnym odsazenim - content = re.sub(r'([ \t]*)(.*hodiny\.tick.*)', r'\1\2\n\1# PIDNO PRO WEB: Dme prohlei anci pekreslit obrazovku\n\1await asyncio.sleep(0)', content) - - with open(f, 'w', encoding='utf-8') as file: - file.write(content) - print(f'Opraveno: {f}') diff --git a/pygame/patch_safe.py b/pygame/patch_safe.py deleted file mode 100644 index 2fa80f0..0000000 --- a/pygame/patch_safe.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -import glob -import re - -os.chdir(r'C:\gitprojekty_skola\python_gamejam_examples\pygame') -for f in glob.glob('*.py'): - if f == 'patch.py': - continue - with open(f, 'r', encoding='utf-8') as file: - content = file.read() - - # 1. async def - content = re.sub(r'(\s*)def hlavni_smycka\(\):', r'\1async async def await hlavni_smycka():', content) - - # 2. Add await to await hlavni_smycka() calls - content = re.sub(r'(\s+)hlavni_smycka\(\)', r'\1await await hlavni_smycka()', content) - - # 3. Fix asyncio.sleep(0) - # Match the old comment containing WEB without needing special chars - content = re.sub(r'[ \t]*#.*WEB.*\s+await asyncio\.sleep\(0\)\s*', '\n', content) - - # Pridame ho tesne za hodiny.tick(...) se stejnym odsazenim - # PRIDANO PRO WEB - await asyncio.sleep(0) - content = re.sub(r'([ \t]*)(.*hodiny\.tick.*)', r'\1\2\n\1# PRIDANO PRO WEB\n\1await asyncio.sleep(0)', content) - - with open(f, 'w', encoding='utf-8') as file: - file.write(content) - print(f'Opraveno: {f}')