Vyjdeme z ukázkové hříčky s poníky z minulé hodiny, zde je ale trochu upravená. Můžete je všechny stáhnout jako jeden archiv, nebo zkopírovat kód a obrázek a mapu stáhnout odděleně. V druhém případě dejte pozor na pojmenování, soubory musíte uložit s názvy spritesheet.png a map.txt. Pythonský skript musíte buďto uložit v kódování utf-8, nebo patřičně změnit komentář coding: na prvním řádku.

Výchozí kód.
# coding: utf-8
import pygame
from random import randint

# všimněte si, že třída níže volá přímo funkci pygame.image.load
# (o něco obyčejnější by bylo volat ji v konstruktoru, ale chceme šetřit čas)
# proto je potřeba zavolat pygame.init() před samotnou definicí té třídy
pygame.init()
screenrect = pygame.Rect(0, 0, 640, 480)
screen = pygame.display.set_mode(screenrect.size)
pygame.display.set_caption('Poníci')

black = pygame.Color(0, 0, 0)
white = pygame.Color(255, 255, 255)

class Pony(pygame.sprite.Sprite):
    # rychlost pohybu v pixelech za sekundu
    speed = 50
    # všechny obrázky poníků, co budou potřeba, načteme jednou během spouštění skriptu
    # obrázek PNG s patřičným názvem musíte stáhnout a uložit do stejné složky jako tento skript
    spritesheet = pygame.image.load('spritesheet.png').convert_alpha()
    sprite_size = 32 # velikost jednoho čtverečku ve spritesheetu, v pixelech
    
    def __init__(self, maze):
        """maze: pygame.Mask; určuje, kam poník smí a kam ne"""
        pygame.sprite.Sprite.__init__(self)
        # barva. Číslo v rozsahu 0..7, protože tolik barev máme ve spritesheetu
        self.color = randint(0, 7)
        # směr chůze. 0: dolů, 1: doleva, 2: doprava, 3: nahoru, protože tak je to ve spritesheetu
        self.direction = 0
        # poloha na mapě
        self.border_right = maze.get_size()[0] - self.sprite_size
        self.border_bottom = maze.get_size()[1] - self.sprite_size
        self.x, self.y = randint(0, self.border_right), randint(0, self.border_bottom)
        self.rect = pygame.Rect(self.x, self.y, self.sprite_size, self.sprite_size)
        # na začátku cíl chůze necháme na správném místě
        self.target = self.rect.x, self.rect.y
        # načteme obrázek ze spritesheetu
        self.update_image()
        # zapamatujeme si bludiště pro pozdější kontroly nárazu
        self.maze = maze
        # do masky zaznamenáme pixely obrázku, co nejsou průhledné
        self.mask = pygame.mask.from_surface(self.image)
    
    def update(self, time):
        """Posune poníka směrem k cíli, pokud to jde
        time: float; udává čas od posledního volání"""
        distance = time * self.speed
        # pokaždé zkoušíme, jestli poník chce jít tímhle směrem a jestli může
        if self.target[0] > self.rect.x + distance and self.can_move(distance, 0):
            self.x += distance
            self.direction = 2
        elif self.target[0] < self.rect.x - distance and self.can_move(-distance, 0):
            self.x -= distance
            self.direction = 1
        elif self.target[1] > self.rect.y + distance and self.can_move(0, distance):
            self.y += distance
            self.direction = 0
        elif self.target[1] < self.rect.y - distance and self.can_move(0, -distance):
            self.y -= distance
            self.direction = 3
        # skutečnou polohu překopírujeme do celočíselného self.rect
        self.rect.x, self.rect.y = self.x, self.y
        self.update_image()
    
    def update_image(self):
        """Vybere aktuální obrázek ze spritesheetu, pro animaci"""
        # začneme v levém horním rohu tabulky obsahující poníky příslušné barvy
        cell_x, cell_y = 3 * (self.color % 4), 4 * (self.color // 4)
        # najdeme buňku podle směru chůze a aktuálního času (animace 6 snímků za vteřinu)
        cell_y += self.direction
        time = pygame.time.get_ticks() / 1000.0 # čas od spuštění hry, původně v milisekundách
        cell_x += int(6 * time) % 3
        # přepočítáme pozici v tabulce na skutečné souřadnice v pixelech a zkopírujeme obrázek
        x = cell_x * self.sprite_size
        y = cell_y * self.sprite_size
        self.image = self.spritesheet.subsurface(pygame.Rect((x, y), self.rect.size))
    
    def can_move(self, horz, vert):
        """Ověří, jestli se maska poníka nepřekrývá s maskou překážek
        horz, vert: int; posuv oproti současné pozici poníka"""
        overlap = self.maze.overlap_area(self.mask, (int(self.x + horz), int(self.y + vert)))
        return overlap == 0
    
    def set_target(self, x, y):
        """Změní cíl chůze na dané souřadnice"""
        self.target = [x, y]

def draw_map(image, filename):
    """Vykreslí na daný obrázek černě mapu z textového souboru daného názvu"""
    f = open(filename, "r")
    for line in f.readlines():
        # každý řádek souboru rozdělíme na čtyři hodnoty a ty převedeme na čísla
        x1, y1, x2, y2 = line.split()
        pygame.draw.line(image, black, (int(x1), int(y1)), (int(x2), int(y2)))
    f.close()

background = pygame.Surface(screenrect.size)
background.fill(white)
draw_map(background, "map.txt")
screen.blit(background, (0,0))
pygame.display.update()

# vyrobíme z vykreslené mapy masku: překážka je všechno, co není bílé
# (set_colorkey je malý úskok, kterým nastavíme dočasně bílou jako průhlednost)
background.set_colorkey(white)
maze = pygame.mask.from_surface(background)
background.set_colorkey(None)

ponies = pygame.sprite.RenderUpdates()
peaceful = pygame.sprite.Group()
for i in range(10):
    pony = Pony(maze)
    pony.add(ponies)
    pony.add(peaceful)
killer = pony
peaceful.remove(killer)

clock = pygame.time.Clock()

alive = True
while alive:
    for event in pygame.event.get():
        if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            alive = False
        
        elif event.type == pygame.MOUSEBUTTONDOWN:
            for pony in ponies:
                pony.set_target(event.pos[0], event.pos[1])
    
    ponies.clear(screen, background)
    ponies.update(clock.get_time() / 1000.0)
    for pony in pygame.sprite.spritecollide(killer, peaceful, dokill=False, collided=pygame.sprite.collide_mask):
        pony.kill() # poníci ve skutečnosti neumřou, jen je odebereme ze všech skupin
    dirty = ponies.draw(screen)
    pygame.display.update(dirty)
    
    clock.tick(30) # 30 FPS

pygame.quit()

První úkol: přidávání poníků stiskem klávesy

Zařiďte, aby se při každém stisknutí mezerníku přidal na obrazovku nový poník, na náhodnou pozici. Bude potřeba doplnit kód ošetřování událostí pro událost takovou, že event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE.

Zařiďte, aby klávesy A, S, D vyráběly poníky tří pevně daných barev. Budete muset změnit konstruktor poníka, aby bral číslo barvy jako parametr. Kvůli tomu budete muset taky změnit původní kód na výrobu poníků a potřebný parametr doplnit jako náhodné číslo rovnou tam. Konečně, kód klávesy A je pygame.K_A, ostatní klávesy jsou pojmenované obdobně.

Druhý úkol: mazání poníků kliknutím

Kód pro přidávání poníků kliknutím můžete smazat, na druhou stranu vám možná poslouží jako dobrá rada. Hodně se podobá tomu, co napíšete místo něj.

Zařiďte, aby poník zmizel z obrazovky, když na něj kliknete. Nejjednodušší bude použít funkci pygame.Rect.collide_point((x, y)) postupně na obdélníky odpovídající všem poníkům (je to vlastnost rect každého z nich) a pokud funkce vrátí True, poníka smazat jeho metodou kill().

Nakonec vykopírujte kód pro přidávání poníků zpět; když nekliknete na žádného poníka, měl by se nový přidat na souřadnice, kam jste klikli, s náhodnou barvou.

Editor mapy

Napište nový skript (třeba s názvem map.py), který bude sloužit ke kreslení mapy. Bude možné si naklikat úsečky na okno a ty se rovnou ve správném formátu zapíšou do souboru map.txt, aby je skript s poníky načetl při příštím spuštění.

Po prvním kliknutí myší byste si měli zapamatovat začátek úsečky. Každé druhé kliknutí by pak mělo úsečku vykreslit a zapsat příslušná čtyři čísla do souboru. Formát každé řádky souboru je Ax Ay Bx By, prostě čtyři celá čísla udávající souřadnice koncových bodů A, B.

Práce se soubory

Otevírání souboru

Textový soubor jde otevřít funkcí open(název, způsob). Funkce navrací objekt typu file, se kterým můžeme dále pracovat. První parametr je obyčejně název souboru, ale pokud ho udáváme s celým umístěním na disku, kvůli kompatibilitě se používají i na Windows obyčejná lomítka / místo zpětných. Druhý parametr je řetězec udávající, co chceme se souborem dělat. Obvykle si vystačíme s "r" pro čtení a "w+" pro vytvoření nového souboru a zápis do něj. Pokud bychom nezacházeli s textem, nýbrž s binárními daty (třeba chtěli zapsat obrázek), musíme do řetězce přidat znak "b".

Čtení a zápis

Celý obsah souboru můžeme přečíst funkcí soubor.read(), bude to řetězec. Jako parametr můžeme funkci dát maximální počet znaků, které se mají přečíst.

Jednotlivé řádky můžeme přečíst funkcí soubor.readlines(), výsledek je seznam řetězců. Když ale chceme každý řádek projít jen jednou, můžeme soubor rovnou použít v cyklu:

for radek in soubor:
    # v proměnné radek dostaneme postupně všechny řádky souboru jako řetězce

Do souboru můžeme zapisovat řádek po řádku pomocí už známé funkce print(file=soubor, cokoliv...) s pojmenovaným parametrem file. Zbytek volání se nemění, můžeme vypsat i víc hodnot za sebou. Když chceme zapsat libovolná data bez odřádkování, můžeme použít funkci soubor.write(řetězec).

Zavírání souboru

Když se souborem přestaneme pracovat, musíme ho zavřít, aby se data zapsala na disk. Jde to udělat výslovně tak, že zavoláme soubor.close(), anebo místo toho použijeme snad trochu hezčí zápis, který se o všechno postará:

with open("nazev.txt", "w+") as soubor:
    # tady může být nějaký kód, který bude pracovat se souborem
# na tomhle místě ale bude soubor už bezpečně zavřený

Dělení a spojování řetězců

Funkce str.split(oddělovač) navrací seznam vzniklý rozdělením původního řetězce podle daných oddělovačů. Oddělovačem může být libovolný řetězec, například tečka: v takovém případě bude seznam obsahovat jednotlivé věty textu. V původním pořadí a už bez teček.

Pokud zavoláme str.split() bez parametru, chová se trochu zvláštně: rozdělí řetězec podle všech mezerám podobných řetězců. Může to být jedna nebo víc mezer, tabulátor, nový řádek a podobně, nebo jejich směs. Je to praktické, ale zásadně odlišné od volání split(" ")

Naopak, druhým parametrem můžeme nastavit maximální počet nalezených oddělovačů. Délka seznamu str.split(oddělovač, maxsplit) tedy bude maxsplit + 1.

Funkce str.join(sekvence) spojí zadané řetězce do jednoho. Mírně se liší zápisem od ostatních jazyků: řetězec před tečkou bude použitý oddělovač. Když chceme spojit seznam slov, zavoláme tedy " ".join(["nazdar", "světe"]). Když chceme spojit řetězce bez oddělovače, použijeme prázdný řetězec "".