![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
V naší první případové studii vytvoříme malou videohru s použitím nástrojů z balíku GASP. Hra bude střílet míč přes plochu okna zleva doprava a hráč bude na pravé straně okna míč chytat
while k pohybu míčeV Gaspu lze použít příkaz while k vytvoření pohybu. Následující program přesune černý míč přes grafický náčrtník o velikosti 800x600 pixelů.
Zapiš jej do souboru jménem pitch.py:
from gasp import * begin_graphics(800, 600, title="Catch", background=color.YELLOW) set_speed(120) ball_x = 10 ball_y = 300 ball = Circle((ball_x, ball_y), 10, filled=True) dx = 4 dy = 1 while ball_x < 810: ball_x += dx ball_y += dy move_to(ball, (ball_x, ball_y)) update_when('next_tick') end_graphics()
Při pohybu míče přes plochu uvidíme takovéto zobrazení:
Zaznamenej si několik prvních iterací tohoto programu aby ses ujistil, že máš jasno v tom, co se děje s proměnnými x a y.
Na tomto příkladě se naučíme několik nových věcí o Gaspu:
begin_graphics přijímá argumenty pro šířku, výšku, název a barvu pozadí náčrtníku.
set_speed přijímá četnost rámců (frames) za vteřinu.filled=True do funkce Circle(...) vytvoříme vyplněnou kružnici.ball = Circle přiřadí kružnici (we will talk later
about what a circle actually is) k proměnné jménem ball, aby bylo možné na ni později odkazovat.move_to v Gaspu přemístí zadaný tvar (v tomto případě míč) do zadané polohy.update_when se v Gaspu používá k odložení akce do výskytu specifikované události. Událost 'next_tick' zajistí zobrazení rámců v zadané rychlosti (set_speed). Jiné platné argumenty pro
update_when jsou 'key_pressed'
a 'mouse_clicked'. Aby naše hra byla ještě zajímavější, chceme mít možnost měnit rychlost a směr míče. Gasp má funkci
random_between(low, high), která vrací náhodné číslo (random) mezi low a high hodnotou. Práci funkce poznáme spuštěním následujícího programu:
from gasp import * i = 0 while i < 10: print random_between(->5, 5) i +=1
Při každém volání této funkce je víceméně náhodně vybráno číslo mezi -5 a 5. Po našem spuštění programu jsme dostali:
-2 -1 -4 1 -2 3 -5 -3 4 -5
Vy dostanete patrně jiné pořadí čísel.
Použijme random_between ke změně směru míče.
Ve skriptu pitch.py změníme přiřazení :
dy = 1
přiřazením náhodného čísla mezi -4 a 4:
dy = random_between(-4, 4)
odrážel
Když spustíme tuto novou verzi programu, uvidíme, že míč často opustí plochu nahoře nebo dole před tím, než ukončí svou dráhu. Abychom tomu zabránili, zajistíme, aby se míč od okrajů odrážel. A sice tak, že změnou znaménka dy pošleme míč zpět ve směru kolmém k původní dráze.
Následující kód zapiš jako první řádek těla smyčky while do pitch.py:
if ball_y >= 590 or ball_y <= 10: dy *= -1
Program si pouštěj opakovaně a pozoruj jeho chování.
breakPříkaz break se používá k okamžitému opuštění těla smyčky. Následující program zavádí jednoduchou hru s hádáním:
from gasp import * number = random_between(1, 1000) guesses = 1 guess = input("Guess the number between 1 and 1000: ") while guess != number: if guess > number: print "Too high!" else: print "Too low!" guess = input("Guess the number between 1 and 1000: ") guesses += 1 print "\n\nCongratulations, you got it in %d guesses!\n\n" % guesses
Použitím příkazu break přepíšeme program bez duplicitního použití příkazu input:
from gasp import * number = random_between(1, 1000) guesses = 0 while True: guess = input("Guess the number between 1 and 1000: ") guesses += 1 if guess > number: print >"Too high!" elif guess < number: print "Too low!" else: print"\n\nCongratulations, you got it in %d guesses!\n\n" % guesses break
Tento program využívá matematického zákona o (trichotomní) jednoznačnosti - pro dvě reálná číísla a,b platí jeden z těchto tří vztahů:
a > b, a < b nebo a = b. I když jsou obě verze programu stejně dlouhé,
lze mít zato, že logika druhé verze je jasnější.
Vlož tento program do souboru guess.py.
Následující program vytvoří kružnici (naši lapačku), která reaguje na vstup
z klávesnice. Stisk klávesy j nebo k posune lapačku nahoru nebo dolů.
Vlož následující kód do souboru mitt.py:
from gasp import * begin_graphics(800, 600, title="Catch", background=color.YELLOW) set_speed(120) mitt_x = 780 mitt_y = 300 mitt = Circle((mitt_x, mitt_y), 20) while True: if key_pressed('k') and mitt_y <= 580: mitt_y += 5 elif key_pressed('j') and mitt_y >= 20: mitt_y -= 5 if key_pressed('escape'): break move_to(mitt, (mitt_x, mitt_y)) update_when('next_tick') end_graphics()
Spusť mitt.py; stisky j a k se
pohybuj nahoru a dolů.
Následující program posouvá dva míče k sobě z opačných stran plochy. Když se míče srazí, oba zmizí a program končí:
from gasp import * def distance(x1, y1, x2, y2): return ((x2 - x1)**2 + (y2 - y1)**2)**0.5 begin_graphics(800, 600, title="Catch", background=color.YELLOW) set_speed(120) ball1_x = 10 ball1_y = 300 ball1 = Circle((ball1_x, ball1_y), 10, filled=True) ball1_dx = 4 ball2_x = 790 ball2_y = 300 ball2 = Circle((ball2_x, ball2_y), 10) ball2_dx = -4 while ball1_x < 810: ball1_x += ball1_dx ball2_x += ball2_dx move_to(ball1, (ball1_x, ball1_y)) move_to(ball2, (ball2_x, ball2_y)) if distance(ball1_x, ball1_y, ball2_x, ball2_y) <= 20: remove_from_screen(ball1) remove_from_screen(ball2) break update_when('next_tick') sleep(1) end_graphics()
Vlož tento program do souboru collide.py a spusť jej.
Abychom mohli zkombinovat pohybující se míč, pohybující se lapačku a ošetření kolize, potřebujeme jednu smyčku loop, která všechny tyto činnosti provede popořadě:
from gasp import * def distance(x1, y1, x2, y2): return ((x2 - x1)**2 + (y2 - y1)**2)**0.5 begin_graphics(800, 600, title="Catch", background=color.YELLOW) set_speed(120) ball_x =10 ball_y = 300 ball = Circle((ball_x, ball_y), >10, filled=True) dx = 4 dy = random_between(-4, 4<) mitt_x =780 mitt_y = 300 mitt = Circle((mitt_x, mitt_y), 20) while True: # move the ball if ball_y >= >590> or ball_y <= >10: dy *= -1 ball_x += dx if ball_x > 810: # the ball has gone off the screen break ball_y += dy move_to(ball, (ball_x, ball_y)) # check on the mitt if key_pressed('k') and mitt_y <= 580: mitt_y += 5 elif key_pressed('j') and mitt_y >= 20: mitt_y -= 5 if key_pressed('escape'): break move_to(mitt, (mitt_x, mitt_y)) if distance(ball_x, ball_y, mitt_x, mitt_y) <= 30: # balon je chycen remove_from_screen(ball) break update_when('next_tick') end_graphics()
Vlož program do souboru catch.py a několikrát jej spusť. Vyzkoušej situace, kdy míč chytíš
i kdy jej mineš.
Tento program zobrazí na obrazovce skóre pro hráče i počítač. Generuje náhodné číslo 0 nebo 1 (jako když házíme mincí) a přídá bod hráči - padne-li 1 nebo počítači - padne-li 0. Aktuální stav je průběžně zobrazován.
from gasp import * begin_graphics(800, 600, title=>"Catch", background=color.YELLOW) set_speed(120) player_score = 0 comp_score = 0 player = Text("Player: %d Points" % player_score, (10, 570), size=24) computer = Text("Computer: %d Points" % comp_score, (640, 570), size=24) while player_score < 5 and comp_score < 5: sleep(1) winner = random_between(0, 1) if winner: player_score += 1 remove_from_screen(player) player = Text("Player: %d Points" % player_score, (10, 570), size=>24) else: comp_score += 1 remove_from_screen(computer) computer = Text("Computer: %d Points" % comp_score, (640, 570), size=24) if player_score == 5: Text("Player Wins!", (340, 290), size=32) else: Text("Computer Wins!", (340, 290), size=32) sleep(4) end_graphics()
Vlož tento program do souboru scores.py a spusť jej.
Můžeme ještě upravit skript catch.py tak, aby byl zobrazován vítěz. Za podmínku if ball_x > 810 : přidáme následující řádky:
Text("Computer Wins!", (340, 290), size=32)
sleep(2)
Jako cvičení si vyzkoušej zobrazení vítěze.
Náš program se nám poněkud rozšiřuje. Abychom jej ještě více zkomplikovali, chystáme se zvýšit jeho složitost. Další stupeň rozvoje programu vyžaduje vloženou smyčku. Vnější smyčka ošetří opakované sehrávky dokud hráč nebo počítač nedosáhne vítězného skóre. Vnitřní smyčkou bude ta, kterou již máme, jež ošetřuje jednotlivou sehrávku - posouvajíc míč a lapačku a určujíc zda došlo či nedošlo k chycení míče.
Výzkum naznačuje, že jsou jasné hranice pro naši poznávací schopnost (viz George A. Miller: The Magical Number Seven, Plus or Minus Two: Some Limits on our Capacity for Processing Information). Čím složitější se program stává, tím je i pro zkušeného programátora obtížnější jej rozvíjet a udržovat.
Abychom zvládli zvyšující se složitost, můžeme zabalit skupiny příbuzných příkazů do funkcí, kterýmžto zobecněním (abstrakcí) zakryjeme některé detaily programu. To nám umožní mentálně zacházet se skupinou příkazů jako s jednou entitou, čímž se nám zvětší šířka pásma (bandwidth) pro řešení dalších úkolů. Schopnost používat zobecnění je nejmocnějším nástrojem při tvorbě programů.
Zde je úplná verze catch.py:
from gasp import * COMPUTER_WINS = 1 PLAYER_WINS = 0 QUIT = -1 def distance(x1, y1, x2, y2): return ((x2 - x1)**2 + (y2 - y1)**2)**0.5 def play_round(): ball_x = 10 ball_y = random_between(20, 280) ball = Circle((ball_x, ball_y), 10, filled=True) dx = 4 dy = random_between(-5, 5) mitt_x = 780 mitt_y = random_between(20, 280) mitt = Circle((mitt_x, mitt_y), 20) while True: if ball_y >= 590 or ball_y <= 10: dy *= -1 ball_x += dx ball_y += dy if ball_x >= 810: remove_from_screen(ball) remove_from_screen(mitt) return COMPUTER_WINS move_to(ball, (ball_x, ball_y)) if key_pressed('k') and mitt_y <= 580: mitt_y +=5 elif key_pressed('j') and mitt_y >= 20: mitt_y -= 5 if key_pressed('escape'): return QUIT move_to(mitt, (mitt_x, mitt_y)) if distance(ball_x, ball_y, mitt_x, mitt_y) <= 30: remove_from_screen(ball) remove_from_screen(mitt) return PLAYER_WINS update_when('next_tick') def play_game(): player_score = 0 comp_score = 0 while True: pmsg = Text("Player: %d Points" % player_score, (10, 570), size=24) cmsg = Text("Computer: %d Points" % comp_score, (640, 570), size=24) sleep(3) remove_from_screen(pmsg) remove_from_screen(cmsg) result = play_round() if result == PLAYER_WINS: player_score += 1 elif result == COMPUTER_WINS: comp_score += 1 else: return QUIT if player_score == 5: return PLAYER_WINS elif comp_score == 5: return COMPUTER_WINS begin_graphics(800, 600, title="Catch", background=color.YELLOW) set_speed(120) result = play_game() if result == PLAYER_WINS: Text("Player Wins!", (340, 290), size=32) elif result == COMPUTER_WINS: Text("Computer Wins!", (340, 290), size=32) sleep(4) end_graphics()
Z uvedeného příkladu si vezmeme některá poučení:
COMPUTER_WINS,
PLAYER_WINS, a QUIT zvyšují přehlednost programu. Je zavedenou praxí psát jména konstant velkými písmeny. V Pythonu se nedoporučuje přiřazovat konstantám nové hodnoty.
play_round(). Tato funkce využívá konstanty, definované na počátku programu. Je mnohem snadnější pamatovat si COMPUTER_WINS než hodnotu, která je této konstantě přiřazena.
play_game(), vytváří proměnné pro
player_score a comp_score. Smyčka while opakovaně volá fci play_round, zkoumá výsledek každého volání a náležitě upravuje skóre. Posléze, když hráč nebo počítač dosáhnou pěti bodů, vrátí play_game jméno vítěze hlavnímu tělu (main body) programu, který je zobrazí a ukončí hru.
result. Jedna je ve funkci play_game a druhá je v hlavním těle programu. Kromě stejného jména nemají spolu nic společného, protože se nacházejí v různých jmenných prostorech (namespaces). Každá funkce vytváří svůj vlastní jmenný prostor a jména, vytvořená uvnitř funkce nejsou "viditelná" vně těla funkce. Jmenné prostory budeme podrobněji probírat v příští kapitole.
mitt.py? Vyhledej příslušné dva řádky programu, které produkují toto chování a popiš jejich působení.
guess.py? Při vhodné strategii by počet pokusů a omylů pro dosažení správné odpovědi neměl být větší než 11. Jaká je tato strategie?
mitt.py dostane lapačkak hornímu nebo dolnímu okraji okna? Vyhledej příslušné řádky v programu a vysvětli podrobně jejich působení.
collide.py změň hodnotu ball1_dx na 2. Jak odlišně se program chová? Nyní změň ball1_dx zpět na 4 a změň ball2_dx na -2. Vysvětli podrobně jak tyto změny ovlivňují chování programu.
# před ...) příkaz break ve skriptu collide.py. Pozoruješ nějakou změnu v chování programu? Nyní také předkomentuj příkaz remove_from_screen(ball1). Co se stane nyní? Experimentuj s předkomentováním a odkomentováním příkazů remove_from_screen a break tak dlouho, až budeš schopen podrobně vysvětlit jak tyto příkazy spolupracují k zajištění kýženého chování programu.
catch.py v kapitole 8.8 přidat řádky
Text("Player Wins!", (340, 290), size=32)
sleep(2)
tak, aby se "Player Wins" zobrazilo, je-li míč lapen?
catch.py, stiskneš-li <Escape> během provádění play_round. Co se stane při stisknutí této klávesnice? Proč?
catch.py. Popiš detailně co každá řádka kódu provádí. Který příkaz volá funkci, která spustí hru?
pong.pyPong byla jedna z prvních komerčních videoher. S velkým písmenem P je to registrovaná ochranná známka, ale označení pong
se užívá pro rozličné hry připomínající stolní tenis.
Program catch.py již obsahuje všechny potřebné programové nástroje pro vývoj vlastní verze pongu. Postupné (inkrementální) přetvoření catch.py na pong.py je cílem tohoto projektu, jehož lze dosíci vypracováním následující řady cvičení:
catch.py na pong1.py a změň míč na pálku použitím Box místo Circle.
Podrobnější informaci o objektu Box nalezneš v příloze B. Proveď potřebné úpravy kódu pro existenci pálky ve hře.
pong1.py na pong2.py. Nahraď funkci distance booleovskou funkcí hit(bx, by, r, px, py, h), která vrátí True, je-li vertikální souřadnice míče (by) mezi spodkem a vrškem pálky a jeho horizontální poloha (bx) je ve vzdálenosti menší nebo rovné poloměru (r) od čela pálky. Funkce hit má zjistit dotek míče s pálkou a odrazit míč v opačném směru když hit vrátí True. Tvoje dokončená funkce by měla vyhovět těmto doctestům:
def hit(bx, by, r, px, py, h): """ >>> hit(760, 100, 10, 780, 100, 100) False >>> hit(770, 100, 10, 780, 100, 100) True >>> hit(770, 200, 10, 780, 100, 100) True >>> hit(770, 210, 10, 780, 100, 100) False """Nakonec změň bodovací logiku tak aby hráč dostal bod, když míč opustí plochu na levém okraji.
pong2.py na pong3.py. Na levý okraj plochy přidej další pálku, která se pohybuje nahoru při stisku klávesnice 'a' a dolů při stisku klávesnice 's'. Zadej startovní polohu míče do středu plochy (400, 300) s náhodným pohybem vlevo či vpravo na začátku každé sehrávky.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |