previous up next hi englisch index

8. Případová studie: Catch

  1. Úvodem
  2. Užití 'while' k pohybu míče
  3. Změna směru a rychlosti
  4. Aby se nám míč odrážel
  5. Příkaz 'break'
  6. Odezva na událost klávesnice
  7. Kontrola kolizí
  8. Dáme všechno dohromady
  9. Zobrazení textu
  10. Zobecnění
  11. Glosář
  12. Cvičení
  13. Projekt: pong.py

8.1 Úvodem

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 lapačkou.

8.2 Užití while k pohybu míče

V 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:

8.3 Změna směru a rychlosti

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)

8.4 Aby se nám míč 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í.

8.5 Příkaz break

Pří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.

8.6 Odezva na událost klávesnice

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ů.

8.7 Kontrola kolizí

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.

8.8 Dáme všechno dohromady

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š.

8.9 Zobrazení textu

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.

8.10 Zobecnění

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í:

8.11 Glosář

náhodný (random)
Nemající určitý vzor, nepředvídatelný. Počítače jsou navrženy tak aby byly předvídatelné a proto není možné vytvořit v počítači skutečně náhodnou hodnotu. Jisté funkce produkují sekvence hodnot, které vypadají jako náhodné veličiny ale jsou to jenom hodnoty pseudonáhodné (pseudorandom).
trichotomie (trichotomy)
Jsou-li dána dvě čísla a a b, platí pro ně pouze jeden z následujících vztahů: a < b, a > b nebo a = b. Můžeme-li tedy prokázat, že dva z těchto vztahů jsou nepravdivé, musí zbývající vztah být pravdivý.
vnořená smyčka (nested loop)
Smyčka uvnitř jiné smyčky.
zobecnění (abstraction)
Generalizace způsobená snížením rozsahu informace. Funkce v Pythonu mohou být použity k seskupení několika programových příkazů pod jedním jménem. Odhlížíme tak od detailů a číníme program přehlednější.
konstanta(constant)
Číselná hodnota, která se nemění během provádění programu. Je zavedenou konvencí používat pro zdůraznění jména zapsaná velkými písmeny, protože Python nemá jazykový nástroj, kterým by tuto neměnnost zajistil nebo vynutil.

8.12 Cvičení

  1. Co se stane po stisknutí <Escape> při běhu skriptu mitt.py? Vyhledej příslušné dva řádky programu, které produkují toto chování a popiš jejich působení.
  2. Jaké je jméno proměnné pro počítadlo (counter variable) ve skriptu 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?
  3. Co se stane, když se v programu mitt.py dostane lapačka k hornímu nebo dolnímu okraji okna? Vyhledej příslušné řádky v programu a vysvětli podrobně jejich působení.
  4. V programu 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.
  5. Předkomentuj (vlož znak # 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.
  6. Kde lze do skriptu 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?
  7. Zaznamenej průběh provádění programu ve finální verzi catch.py, stiskneš-li <Escape> během provádění play_round. Co se stane při stisknutí této klávesnice? Proč?
  8. Vyhledej hlavní tělo finální verze catch.py. Popiš detailně co každá řádka kódu provádí. Který příkaz volá funkci, která spustí hru?
  9. Urči funkci zodpovědnou za zobrazení míče a lapačky. Které další operace tato funkce provádí?
  10. Která funkce zaznamenává skóre? Je to táž funkce, která skóre zobrazuje? Odůvodni svou odpověď podrobným popisem kódu, který zavádí tyto operace.

8.13 Projekt pong.py

Pong 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í:

  1. Překopíruj 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.
  2. Překopíruj 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.
  3. Překopíruj 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.
  4. Dokonči kompletaci hry dle vlastní úvahy.
previous up next hi englisch index