sobota 27. ledna 2018

Mřížka

Svého syna jsem začal učit programovat, sotva se naučil číst a psát. Nejprve nám stačil interaktivní terminál, potom jsme dělali programy s textovým výstupem do terminálu, ale to se brzy omrzí, pro malého kluka to není nijak atraktivní. A tak jsme přešli na grafiku a rovnou animovanou.

Jako základ mi posloužila knihovna pygame, ale ta je sama o sobě příliš složitá pro někoho, kdo se teprve snaží chápat, co to jsou proměnné, cykly a podmínky. A tak jsem udělal jeden takový malý modul, který práci s grafikou usnadňuje.

Jedná se o mřížku, které lze cyklicky nastavovat barvu jednotlivých buněk. Nic víc to neumí, je to jednoduché, ale kreativně se na tom dá docela vyblbnout.

Nejjednodušší příklad použití je namalování jednoho červeného bodu do mřížky na souřadnici 1, 1:

 1 # definice barev
 2 RED    = (255,   0,   0)
 3 
 4 def maluj(self):
 5     self.bod(1, 1, RED)
 6 
 7 # zobrazeni obrazku
 8 import platno
 9 mrizka = platno.Mrizka(velikost=20, sirka=8, vyska=8)
10 mrizka.pripojMalovani(maluj)
11 mrizka.start()

Podstatný je řádek číslo 5. Vše ostatní je pevně dané a může být uloženo jako výchozí šablona.


Další příklad je složitější. Opět se vykresluje jeden bod, ale ten se tentokrát pohybuje, obíhá dokola. Je to prima příklad na procvičování výjimek.

 1 GREEN  = (  0, 255,   0) # definice barvy
 2 
 3 def nastav(self):
 4     # spousti se jen jednou, na zacatku po zavolani funkce start()
 5     self.x  = 1
 6     self.y  = 1
 7 
 8     self.dx = 1
 9     self.dy = 0
10 
11     self.minx = 1
12     self.maxx = 6
13     self.miny = 1
14     self.maxy = 6
15 
16 def maluj(self):
17     # spousti se opakovane, mrizka se vzdy cela prekresli
18     self.bod(self.x, self.y, GREEN) # namaluje bod
19 
20     self.x += self.dx # zmeni souradnici x
21     self.y += self.dy # zmeni souradnicy y
22 
23     if  self.x == self.maxx and self.y == self.miny:
24         # horni pravy roh, bod pojede dolu
25         self.dx = 0
26         self.dy = 1
27 
28     if  self.x == self.maxx and self.y == self.maxy:
29         # dolni pravy roh, bod pojede doleva
30         self.dx = -1
31         self.dy =  0
32 
33     if  self.x == self.minx and self.y == self.maxy:
34         # dolni levy roh, bod pojede nahoru
35         self.dx =  0
36         self.dy = -1
37 
38     if  self.x == self.minx and self.y == self.miny:
39         # horni levy roh, bod pojede doprava
40         self.dx = 1
41         self.dy = 0
42 
43 # zobrazeni obrazku
44 import platno
45 mrizka = platno.Mrizka(velikost=20, sirka=8, vyska=8, rychlost=5)
46 mrizka.pripojNastaveni(nastav)
47 mrizka.pripojMalovani(maluj)
48 mrizka.start()

Jak je vidět, zde se už používají dvě funkce. Funkce nastav() se spustí jen jednou na začátku a slouží k inicializaci proměnných. Funkce maluj() se spouští opakovaně. Rychlost spouštění je možno nastavit při vytvoření mřížky parametrem 'rychlost'. Číslo představuje počet spuštění za sekundu. Výchozí rychlost je 60, což je obnovovací rychlost displeje a umožňuje vytvářet plynulé animace. Ale pro pohyb bodu je to moc rychlé, lítá jako splašený, takže v příkladu je rychlost snížena na 5 obrázků za sekundu.

Obsah funkcí nastav() a maluj() si definuje uživatel sám ve svem souboru, ale připojují se k objektu mrizka  jako jeho metody. Proto mohou sdilet sve promenne pres self.


Další příklad ukazuje, jak je možno pohybovat dvěmi body, kdy se každý pohybuje jinou rychlostí.

 1 GREEN  = (  0, 255,   0) # definice barvy
 2 BLUE   = (  0,   0, 255) # definice barvy
 3 
 4 def nastav(self):
 5     # spousti se jen jednou, na zacatku po zavolani funkce start()
 6     self.xa = 1
 7     self.ya = 1
 8     self.xb = 1
 9     self.yb = 6
10 
11     self.da = 1
12     self.db = 1
13 
14     self.mina = 1
15     self.maxa = 6
16     self.minb = 1
17     self.maxb = 6
18 
19 def maluj(self):
20     # spousti se opakovane, mrizka se vzdy cela prekresli
21     self.bod(self.xa, self.ya, GREEN) # namaluje bod
22     self.bod(self.xb, self.yb, BLUE)  # namaluje bod
23     if  self.cyklus % 10 == 0:
24         self.xa += self.da
25         if  self.xa == self.maxa:
26             self.da = -1
27         if  self.xa == self.mina:
28             self.da = 1
29     if  self.cyklus % 20 == 0:
30         self.xb += self.db
31         if  self.xb == self.maxb:
32             self.db = -1
33         if  self.xb == self.minb:
34             self.db = 1
35 
36 # zobrazeni obrazku
37 import platno
38 mrizka = platno.Mrizka(velikost=20, sirka=8, vyska=8, rychlost=60)
39 mrizka.pripojNastaveni(nastav)
40 mrizka.pripojMalovani(maluj)
41 mrizka.start()

Rozdílu rychlostí je dosaženo různě častým spouštěním výpočtu pro pohyb bodu. Pro bod 'a' se výpočet spouští každý desátý cyklus, pro bod 'b' každý dvacátý, má proto poloviční rychlost. Cyklů je přibližně 60 za sekundu, takže bod 'a' se za sekundu posune o 6 míst a bod 'b' se posune o 3 místa za sekundu.


Jde vymyslet a vymysleli jsme si spoustu dalších věcí. Syna např. bavilo si ve mřížce 'malovat' a namaloval si mimo jiné obrázek uvedený vlevo.

Hráli jsme si s ním a nakonec jsme ho zanimovali. Lodička začala plavat zprava doleva. Udělali jsme to jednoduše, začali jsme posouvat třetí až osmý řádek, což vyvolává dojem pohybu lodičky. Kdyby obrázek měl složitější pozadí, nešlo by to tak snadno, a to už je jen krůček ke sprajtům, tedy nejprve vykreslit pozadí a na něj teprve nějaké objekty. Není to nijak efektivní, ale zato je to jednoduché na pochopení.

A to je hlavní smysl mřížky. Atraktivní a zábavnou formou zprostředkovat základy programování. Ukázka kódu:

 1 RED    = (255,   0,   0)
 2 BLUE   = (  0,   0, 255)
 3 GREEN  = (  0, 255,   0)
 4 YELLOW = (255, 255,   0)
 5 BLACK  = (0,     0,   0)
 6 PURPLE = (255,   0, 255)
 7 CYAN   = (  0, 255, 255)
 8 BROWN  = (143,  89,   2)
 9 
10 def nastav(self):
11     self.obrazek = [
12         'BBBBBBBBBBBBBBBB',
13         'CCCCCCCCCCCCCCCC',
14         'BBBBBBBBYBBBBBBB',
15         'CCCCCCC YCCCCCCC',
16         'BBBBBB  YBBBBBBB',
17         'CCCVCCCCYCCCVCCC',
18         'BBBBVBBBYBBVBBBB',
19         'CCCCCVVVVVVCCCCC',
20         'BBBBBBBBBBBBBBBB',
21         'CCCCCCCCCCCCCCCC',
22         'LLLLLLLLGLLLLLLL',
23         'LLLLLLLGVGLLLLLL',
24         'LLLLLLGLVLGLLLLL',
25         'LLLLLLLLVLLLLLLL',
26         'LLLLLLLLVLLLLLLL',
27         'LLLLLLLLVLLLLLLL',
28         'GGGGGGGGGGGGGGGG',
29         'GGGGGGGGGGGGGGGG'
30     ]
31 
32     self.posun   = -1
33     self.sloupcu = len(self.obrazek[0])
34 
35 def maluj(self):
36     if  self.cyklus % 5 == 0:
37         self.posun += 1
38         if  self.posun == self.sloupcu:
39             self.posun = 0
40 
41     for sloupec in xrange(16):
42         posunuty_sloupec = sloupec + self.posun
43         if  posunuty_sloupec >= self.sloupcu:
44             posunuty_sloupec -= self.sloupcu
45 
46         for radek in xrange(16):
47             # vyber znaku
48             if  radek > 1 and radek < 8:
49                 znak = self.obrazek[radek][posunuty_sloupec]
50             else:
51                 znak = self.obrazek[radek][sloupec]
52 
53             # namalovani znaku
54             if  znak == ' ':
55                 continue
56             if  znak == 'R':
57                 self.bod(sloupec, radek, RED)
58             if  znak == 'G':
59                 self.bod(sloupec, radek, GREEN)
60             if  znak == 'B':
61                 self.bod(sloupec, radek, BLUE)
62             if  znak == 'Y':
63                 self.bod(sloupec, radek, BLACK)
64             if  znak == 'L':
65                 self.bod(sloupec, radek, YELLOW)
66             if  znak == 'C':
67                 self.bod(sloupec, radek, CYAN)
68             if  znak == 'p':
69                 self.bod(sloupec, radek, PURPLE)
70             if  znak == 'V':
71                 self.bod(sloupec, radek, BROWN)
72 
73 # zobrazeni obrazku
74 import platno
75 mrizka = platno.Mrizka(velikost=20, sirka=16, vyska=16)
76 mrizka.pripojNastaveni(nastav)
77 mrizka.pripojMalovani(maluj)
78 mrizka.start()


Pokud si to chce někdo vyzkoušet, potřebuje onen tajemný modul plátno, kde je definována třída Mrizka. Zde je jeho kód a jak je vidět, je velmi jednoduchý až primitivní. Jediná zajímavost je způsob připojení uživatelem definované funkce jako metodu třídy. To jsem nevěděl jak na to a musel si to vygooglovat.

Omluvte kombinaci českých a anglickych názvů proměnných. Jsem zvyklý používat anglické názvy, ale pro veřejné rozhraní pro uživatele jsem použil české, aby to děti měli jednodušší a tak vznikl kočkopes.

Zákaz vstupu je upozornění pro děti, že tohle není ten správný soubor, kde by mohly něco zkoušet a měnit. Modul je uložen na stejném místě, kde mají své prográmky, jenž ho používají. Už to jsou desítky souborů, a tak ani neví kde co mají a čas od času si otevřou i tento soubor a šťourají se v něm. Pak se diví, že to najednou, táto, přestalo fungovat úplně, ale úplně všechno :-). Holt chybami se člověk učí.

Při vytváření objektu mřížka lze nastavit počet sloupců a řádek i velikost buňek. Lze nastavit i velikost 1, pak lze ovládat každý pixel plátna jednotlivě. Je to pěkné na barevné přechody. V takovém režimu jsou už na překážku ohraničení buněk a vypnou se. Ručně lze ohraničení zapnout a vypnout parametrem lines.

 1 ################################################################################
 2 #                       Z A K A Z   V S T U P U                                #
 3 ################################################################################
 4 try:
 5     import pygame as pg
 6     from pygame.locals import *
 7 except:
 8     import pygame_sdl2 as pg
 9     from   pygame_sdl2 import *
10 
11 pg.init()
12 
13 BACKGROUND = (255,255,255)
14 FOREGROUND = (0,0,0)
15 
16 class Mrizka(object):
17     def __init__(self, sirka=16, vyska=16, velikost=8, lines='auto'):
18         self.w = sirka
19         self.h = vyska
20         self.s = velikost
21 
22         self.cyklus = 0
23 
24         if  lines == 'auto':
25             self.lines = 1 if velikost > 8 else 0
26         else:
27             self.lines = lines
28 
29         self.wPix = sirka * velikost
30         self.hPix = vyska * velikost
31         if  self.lines:
32             self.wPix += 1
33             self.hPix += 1
34 
35         self.o = pg.display.set_mode((self.wPix, self.hPix))
36         self.c = pg.time.Clock()
37 
38     def start(self):
39         self.nastav()
40         while True:
41             self.cyklus = self.cyklus + 1
42             for event in pg.event.get():
43                 if event.type == QUIT:
44                     pg.quit()
45                     return
46             self.redraw()
47 
48     def smazat(self, color=BACKGROUND):
49         self.o.fill(color)
50 
51     def drawGrid(self, color=FOREGROUND):
52         for i in xrange(0, self.hPix+1, self.s):
53             self.o.fill(color, (0,i, self.wPix,1))
54         for i in xrange(0, self.wPix+1, self.s):
55             self.o.fill(color, (i,0, 1,self.hPix))
56 
57     def redraw(self):
58         self.smazat()
59         self.maluj()
60         if  self.lines:
61             self.drawGrid()
62         pg.display.update()
63         self.c.tick_busy_loop(60)
64 
65     def nastav(self):
66         pass # user draw
67 
68     def maluj(self):
69         pass # user draw
70 
71     def pripojNastaveni(self, method):
72         def binding_scope(*args, **kwargs):
73             return method(self, *args, **kwargs)
74         self.nastav = binding_scope
75 
76     def pripojMalovani(self, method):
77         def binding_scope(*args, **kwargs):
78             return method(self, *args, **kwargs)
79         self.maluj = binding_scope
80 
81     def bbod(self, bod, color=FOREGROUND):
82         self.o.fill(color, (bod[0]*self.s, bod[1]*self.s, self.s, self.s))
83 
84     def bod(self, x, y, color=FOREGROUND):
85         self.o.fill(color, (x*self.s, y*self.s, self.s, self.s))
86 
87 
88 if  __name__ == '__main__':
89     #  test
90     def maluj(self):
91         self.bod(3,3,(222,0,0))
92 
93     obr = Mrizka(velikost=20)
94     obr.pripojMalovani(maluj)
95     obr.start()
96     print 'HOTOVO'
--

Žádné komentáře:

Okomentovat