6 changed files with 126 additions and 248 deletions
@ -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! |
||||
@ -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()) |
|
||||
@ -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) |
|
||||
@ -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]*# PØIDÁNO PRO WEB: Dáme prohlížeèi šanci pøekreslit 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# PØIDÁNO PRO WEB: Dáme prohlížeèi šanci pøekreslit obrazovku\n\1await asyncio.sleep(0)', content) |
|
||||
|
|
||||
with open(f, 'w', encoding='utf-8') as file: |
|
||||
file.write(content) |
|
||||
print(f'Opraveno: {f}') |
|
||||
@ -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}') |
|
||||
Loading…
Reference in new issue