24. Pohybujúce sa obrázky


Obrázkom v nejakej väčšej scéne, ktoré sa animujú, hýbu, spolupracujú navzájom, resp. sa dajú modifikovať prostredníctvom myši alebo klávesnice, sa zvykne hovoriť sprite. V predchádzajúcej prednáške sme do grafickej plochy umiestňovali nejaké animované objekty a už toto boli najjednoduchšie verzie sprite (škriatkov). Pripomeňme si aplikáciu s animovanými obrázkami z minulej prednášky:

import tkinter
import random
from PIL import Image, ImageTk

class Anim:
    canvas = None
    def __init__(self, x, y, zoz):
        self.id = self.canvas.create_image(x, y)
        self.zoz = zoz
        self.faza = 0

    def dalsia_faza(self):
        self.faza = (self.faza + 1) % len(self.zoz)
        self.canvas.itemconfig(self.id, image=self.zoz[self.faza])

class Plocha:
    def __init__(self, meno_pozadia, *obrazky):
        self.pozadie = tkinter.PhotoImage(file=meno_pozadia)
        sir, vys = self.pozadie.width(), self.pozadie.height()
        self.canvas = Anim.canvas = tkinter.Canvas(width=sir, height=vys)
        self.canvas.pack()
        self.canvas.create_image(0, 0, image=self.pozadie, anchor='nw')
        self.zoz = obrazky
        self.azoz = []
        self.timer()
        self.canvas.bind('<ButtonPress>', self.klik)

    def timer(self):
        for a in self.azoz:
            a.dalsia_faza()
        self.canvas.after(100, self.timer)

    def klik(self, event):
        self.azoz.append(Anim(event.x, event.y, random.choice(self.zoz)))

class Program:
    def __init__(self):

        def strihaj(meno_suboru, n):
            obr = Image.open(meno_suboru)
            sir, vys = obr.width//n, obr.height
            zoz = []
            for x in range(0, obr.width, sir):
                zoz.append(ImageTk.PhotoImage(obr.crop((x, 0, x+sir, vys))))
            return zoz

        win = tkinter.Tk()
        win.title('zvieratka v lese')
        zoz1 = strihaj('vtak.png', 8)
        zoz2 = strihaj('zajo.png', 8)
        obr = Image.open('pyton.png')
        zoz3 = [ImageTk.PhotoImage(obr.rotate(uhol, expand=True)) for uhol in range(0, 360, 10)]
        obr = Image.open('kacicka.png')
        zoz4 = [ImageTk.PhotoImage(obr.resize(int(v*r) for v in obr.size))
                                   for r in (.6, .55, .5, .45, .4, .35, .3, .35, .4, .45, .5, .55)]
        Plocha('les.png', zoz1, zoz2, zoz3, zoz4)

Program()

V tejto aplikácii sa po každom kliknutí objavil ďalší animovaný obrázok, pričom sa náhodne vyberal z

  • zoz1 - vtáčik mávajúci krídlami

  • zoz2 - skákajúci zajac

  • zoz3 - otáčajúci sa pytón

  • zoz4 - kačička s pulzujúcou veľkosťou

Postupne budeme túto aplikáciu modifikovať a dopĺňať o ďalšie správanie.


1. Ďalší animovaný objekt

Aplikáciu upravíme tak, aby sa namiesto otáčajúceho sa pytóna a pulzujúcej kačičky objavila krútiaca sa zemeguľa. Využijeme týchto 21 obrázkov zemegule:

_images/24_1.png

Túto zemeguľu musíme rozstrihať trochu inak ako vtáka a zajaca. Upravíme funkciu strihaj a aj inicializáciu triedy Program`:

class Program:
    def __init__(self):

        def strihaj(meno_suboru, ps, pr=1):
            obr = Image.open(meno_suboru)
            sir, vys = obr.width//ps, obr.height//pr
            zoz = []
            for y in range(0, obr.height, vys):
                for x in range(0, obr.width, sir):
                    zoz.append(ImageTk.PhotoImage(obr.crop((x, y, x+sir, y+vys))))
            return zoz

        win = tkinter.Tk()
        win.title('zvieratka v lese')
        zoz1 = strihaj('vtak.png', 8)
        zoz2 = strihaj('zajo.png', 8)
        zoz3 = strihaj('zemegula.png', 7, 3)
        Plocha('les.png', zoz1, zoz2, zoz3)

Funkcia strihaj teraz zvláda rozstrihať prečítaný obrázkový súbor nielen na rovnaké obrázky v jednom rade, ale aj ak je takýchto radov niekoľko pod sebou: parameter ps označuje počet stĺpcov a pr počet riadkov s obrázkami.


2. Posúvanie objektov myšou

Do triedy Plocha pridáme 3 nové udalosti a tiež upravíme klikanie, pomocou ktorého pridávame nové objekty:

  • '<ButtonPress-3>' - metóda klik pridáva nový objekt (kliknutie pravým tlačidlom myši)

  • '<ButtonPress-1>' - metóda mouse_down zistí, či sa kliklo do objektu a umožní ďalšie ťahanie

  • '<B1-Motion>' - metóda mouse_move robí samotné ťahanie

  • '<ButtonRelease-1>' - metóda mouse_up zruší ťahanie

Všetky tri ťahacie metódy využívajú premennú (atribút) tahany, ktorá ma buď hodnotu None (nič sa neťahá), alebo referenciu na jeden z objektov (inštancie triedy Anim). Trieda PLocha teraz vyzera takto:

class Plocha:
    def __init__(self, meno_pozadia, *obrazky):
        ...
        self.tahany = None
        self.canvas.bind('<ButtonPress-3>', self.klik)
        self.canvas.bind('<ButtonPress-1>', self.mouse_down)
        self.canvas.bind('<B1-Motion>', self.mouse_move)
        self.canvas.bind('<ButtonRelease-1>', self.mouse_up)

    ...

    def mouse_down(self, event):
        for a in reversed(self.azoz):
            if a.vnutri(event.x, event.y):
                self.tahany = a
                self.dx, self.dy = event.x - a.x, event.y - a.y
                return
        self.tahany = None

    def mouse_move(self, event):
        if self.tahany is not None:
            self.tahany.presun(event.x-self.dx, event.y-self.dy)

    def mouse_up(self, event):
        self.tahany = None

Metóda mouse_down využíva to, že objektu typu Anim sa vieme opýtať, či sme klikli do jeho vnútra vnutri(x, y) a tiež potom pri hýbaní myšou vieme posúvať aj samotný objekt metódou presun(x, y). Všimnite si pomocné premenné self.dx a self.dy, v ktorych sa ukladá posun kliknutia od stredu objektu (jeho (x, y)). Tiež si všimnite, že v metóde mouse_down postupne prechádzame všetky animované objekty v azoz v opačnom poradí, ako sa vytvorili (použili sme reversed), teda skôr sa testujú objekty, ktoré su navrchu (ak sa prekrývajú).

Do triedy Anim pridáme dve nové metódy vnutri a presun a tiež nové premenné x, y a vel, ktorá označuje približnú veľkosť objektu:

class Anim:
    canvas = None
    def __init__(self, x, y, zoz):
        self.x, self.y = x, y
        self.id = self.canvas.create_image(x, y)
        self.zoz = zoz
        self.faza = 0
        self.vel = min(zoz[0].width(), zoz[0].height()) / 2

    def dalsia_faza(self):
        self.faza = (self.faza + 1) % len(self.zoz)
        self.canvas.itemconfig(self.id, image=self.zoz[self.faza])

    def presun(self, x, y):
        self.canvas.move(self.id, x-self.x, y-self.y)
        self.x, self.y = x, y

    def vnutri(self, x, y):
        return (self.x-x)**2 + (self.y-y)**2 <= self.vel**2

3. Rôzna rýchlosť animácií

V momentálnej verzii aplikácie sa všetky objekty animujú rovnakou rýchlosťou: pri každom tiknutí časovača (po 100 milisekundách) si každý objekt nastaví ďalšiu fázu animácie. Lenže teraz by sme chceli každému animovanému objektu nastaviť jeho individuálnu rýchlosť animácie, teda hodnotu jeho premennej tik, ktorá bude označovať jeho rýchlosť v milisekundách. Do inicializácie Anim pridáme nový parameter:

class Anim:
    canvas = None
    def __init__(self, x, y, zoz, tik=100):
        self.tik = tik
        ...

Hodnotou atribútu tik by sme radi nastavili jeho individuálny čas zmeny fázy animácie. Toto ale musí zabezpečiť časovač timer v triede Plocha. Zatiaľ vyzerá takto:

def timer(self):
    for a in self.azoz:
        a.dalsia_faza()
    self.canvas.after(100, self.timer)

V časovači sa pravidelne pri každom tiknutí časovača postupne prechádzajú všetky objekty a každému sa nastaví ďalšia fáza. Jedna z možností, ako to vyriešiť, by bola taká, že by sme pre každý objekt pripravili jeho súkromný časovač s jeho vlastnou frekvenciou tikania. Toto by asi fungovalo, len by to priveľmi (a zbytočne) zaťažilo nielen Python ale aj operačný systém. Budeme to riešiť inak: časovač bude hýbať len s tými objektami, ktorým už uplynul ich čas (od poslednej zmeny fázy).

Preto prerobíme zoznam azoz všetkých animovaných zoznamov: okrem referencie na objekt bude obsahovať aj čas, kedy chceme, aby timer zmenil ďalšiu fázu. Spomeňme si, že funkcia time.time() (z modulu time) nám nejako vráti momentálny čas v sekundách. Preto time.time() + tik/1000 označuje čas, ktorý bude o tik milisekúnd. Do azoz budeme vkladať (tuple) dvojice: (čas, referencia). Tieto dvojice v azoz budeme udržiavať usporiadané podľa časov, teda na začiatku zoznamu budú tie dvojice, ktoré majú najmenší čas a bude ich treba čo najskôr animovať.

Ako bude teraz fungovať časovač. Zapíšme si to pseudokódom:

def timer(self):
    while 'na začiatku azoz je čas, ktorý treba už vykonať':
        a = 'vyber z azoz prvý objekt'
        a.dalsia_faza()
        'vlož objekt a do azoz s novým časom na správne miesto'
    self.canvas.after(10, self.timer)        # pre istotu tikaj častejšie ako 100

Uvedomte si, že ak azoz obsahuje dvojice (čas, objekt), tak azoz[0][0] znamená čas prvého prvku azoz, azoz.pop(0) vráti tento prvý prvok (a zo zoznamu ho odstráni). Potom azoz.pop(0)[1] odstráni z azoz prvý prvok a vráti jeho druhú časť, teda referenciu na objekt. Teraz vieme zapísať časovač:

def timer(self):
    while self.azoz != [] and self.azoz[0][0] <= time.time():
        a = self.azoz.pop(0)[1]
        a.dalsia_faza()
        self.vloz_do_azoz(a)
    self.canvas.after(10, self.timer)

kde metóda vloz_do_azoz(a) zrejme vloží objekt a do zoznamu dvojíc azoz na správne miesto, teda zistí jeho nasledovný čas time.time() + a.tik/1000 a takúto dvojicu vloží (zrejme pomocou metódy insert) do zoznamu (list) azoz:

def vloz_do_azoz(self, a):                 # nová metóda v triede Plocha
    cas = time.time() + a.tik/1000
    for i in range(len(self.azoz)):
        if self.azoz[i][0] > cas:
            self.azoz.insert(i, (cas, a))
            return
    self.azoz.append((cas, a))

Teraz ešte treba upraviť všetky metódy v Plocha, ktoré pracovali so zoznamom azoz tak, aby korektne pracovali so zoznamom dvojíc:

def klik(self, event):
    a = Anim(event.x, event.y, random.choice(self.zoz), random.randint(20, 200))
    self.azoz.insert(0, (time.time(), a))

def mouse_down(self, event):
    for t, a in self.azoz:
        if a.vnutri(event.x, event.y):
            self.tahany = a
            self.dx, self.dy = event.x - a.x, event.y - a.y
            return
    self.tahany = None

Všimnite si, že metóda klik, ktorá do plochy pridáva nový náhodný objekt s náhodnou rýchlosťou animácie (posledný parameter tik bude náhodné číslo od 20 do 200) a zároveň do časovača naplánuje čo najskoršie vykreslenie tohto objektu a tým aj naštartovanie jeho budúcej animácie.

Metóda mouse_down musí vo for-cykle prechádzať zoznam dvojíc a preto ani nevyužijeme reversed.

Ešte nezabudnite na začiatok aplikácie pridať import time. Teraz by mohla fungovať aplikácia takto:

  • po kliknutí (pravým tlačidlom myši) pridá nový náhodný objekt s náhodnou rýchlosťou animácie

  • ťahaním (ľavým tlačidlom myši) môžeme ľubovoľný animovaný objekt presúvať na nové miesto

  • počas presúvania stále beží jeho animácia

  • ak sa viac animovaných objektov navzájom prekrýva, tak kliknutie a ťahanie nemusí vybrať vrchný objekt, ale náhodný podľa momentálneho stavu azoz


4. Automatický pohyb objektov

Ďalším krokom pri vylepšovaní aplikácie bude automatický pohyb všetkých animovaných objektov. Pri pohybe objektu v grafickej ploche sa často využíva idea vektora: objekt má určený svoj smer pohybu ako dvojicu čísel (dx, dy), pričom v každom kroku (časovača) sa zmení poloha objektu v ploche: x = x + dx a y = y + dy. Asi by sa zišlo strážiť, aby nám objekt neodišiel z plochy preč a už sa nikdy nevrátil späť. Ak si niekde zapamätáme veľkosť grafickej plochy (šírku a výšku), jednoducho sa dá robiť takúto kontrolu:

x = x + dx
if x < 0:
    x = x + sirka
if x >= sirka:
    x = x - sirka

podobne by to bolo aj so súradnicou y. V skutočnosti sa to dá skrátiť takto:

x = (x + dx) % sirka
y = (y + dy) % vyska

Vďaka tejto úprave, animovaný objekt pri pohybe z plochy nevypadne, ale objaví sa na opačnom konci a pokračuje v pohybe stále svojim smerom.

Upravovať budeme najprv metódy triedy Anim:

class Anim:
    canvas = None
    def __init__(self, x, y, zoz, tik=100, dx=0, dy=0):
        self.tik = tik
        self.x, self.y = x, y
        self.dx, self.dy = dx, dy
        ...

Metódu dalsia_faza premenujeme na pohyb:

class Anim:
    ...

    def pohyb(self):
        self.faza = (self.faza + 1) % len(self.zoz)
        self.canvas.itemconfig(self.id, image=self.zoz[self.faza])
        if self.dx or self.dy:
            self.presun((self.x + self.dx) % self.sirka,
                        (self.y + self.dy) % self.vyska)

Zrejme ešte vygenerujeme nejaké dx a dy pri vytváraní animovaného objektu (pri pravom kliknutí myši do plochy). Asi bude najlepšie, keď vtáčiky budú lietať len smerom zľava doprava, zajačiky budú skákať len sprava doľava a zemegule sa budú hýbať ľubovoľným smerom. Opravíme metódy klik a timer v triede Plocha, v inicializácii pridáme šírku a výšku ako triedne atribúty v Anim:

class Plocha:
    def __init__(self, meno_pozadia, *obrazky):
        self.pozadie = tkinter.PhotoImage(file=meno_pozadia)
        sir, vys = self.pozadie.width(), self.pozadie.height()
        Anim.sirka, Anim.vyska = sir, vys
        ...

    def timer(self):
        while self.azoz != [] and self.azoz[0][0] <= time.time():
            a = self.azoz.pop(0)[1]
            a.pohyb()
            self.vloz_do_azoz(a)
        self.canvas.after(10, self.timer)

    ...

    def klik(self, event):
        ix = random.randrange(len(self.zoz))
        if ix == 0:
            dx, dy = random.randint(1, 5), random.randint(-2, 2)
        elif ix == 1:
            dx, dy = random.randint(-7, -4), random.randint(-2, 2)
        elif ix == 2:
            dx, dy = random.randint(-5, 5), random.randint(-5, 5)
        a = Anim(event.x, event.y, self.zoz[ix], random.randint(20, 200), dx, dy)
        self.azoz.insert(0, (time.time(), a))

    ...

Teraz otestujte, ako sa animované objekty hneď po vytvorení rozpŕchnu všetkými smermi.


5. Iný pohyb pre niektorý objekt

V momentálnej verzii sa všetky tri typy animovaných objektov správajú na okraji úplne rovnako: keď objekt na nejakej hrane vypadne, objaví sa na opačnom konci plochy.

Teraz by sme chceli zmeniť správanie animovanej zemegule: keď sa priblíži k okraju plochy, odrazí sa presne tak, ako sa odrážajú gulečníkové gule na biliardovom stole. Teraz sa bude počítať (x, y) a meniť (dx, dy) takto:

if x + dx < vel:
    dx = abs(dx)
if x + dx > sirka - vel:
    dx = -abs(dx)
if y + dy < vel:
    dy = abs(dy)
if y + dy > vyska - vel:
    dy = -abs(dy)
x, y = x + dx, y + dy

Ďalej si ukážeme dva postupy, ako to môžeme zakomponovať do našej aplikácie. Prvá verzia pridá do triedy Anim atribút odraz, ktorý, ak bude mať hodnotu True, bude sa odrážať od okrajov, hodnota False ponechá pôvodné správanie.

Opravme triedu Anim:

class Anim:
    canvas = None
    odraz = False
    def __init__(self, x, y, zoz, tik=100, dx=0, dy=0):
        ...

    def pohyb(self):
        self.faza = (self.faza + 1) % len(self.zoz)
        self.canvas.itemconfig(self.id, image=self.zoz[self.faza])
        if self.dx or self.dy:
            if self.odraz:
                if self.x+self.dx < self.vel: self.dx = abs(self.dx)
                if self.y+self.dy < self.vel: self.dy = abs(self.dy)
                if self.x+self.dx > self.sirka - self.vel: self.dx = -abs(self.dx)
                if self.y+self.dy > self.vyska - self.vel: self.dy = -abs(self.dy)
                self.presun(self.x + self.dx, self.y + self.dy)
            else:
                self.presun((self.x + self.dx) % self.sirka,
                            (self.y + self.dy) % self.vyska)

    ...

Ešte musíme opraviť vytvorenie takéhoto animovaného objektu v metóde klik v triede Plocha:

class Plocha:
    ...

    def klik(self, event):
        ix = random.randrange(len(self.zoz))
        if ix == 0:
            dx, dy = random.randint(1, 5), random.randint(-2, 2)
        elif ix == 1:
            dx, dy = random.randint(-7, -4), random.randint(-2, 2)
        elif ix == 2:
            dx, dy = random.randint(-5, 5), random.randint(-5, 5)
        a = Anim(event.x, event.y, self.zoz[ix], random.randint(20, 200), dx, dy)
        if ix == 2:
            a.odraz = True
        self.azoz.insert(0, (time.time(), a))

    ...

Teraz by aplikácia mohla fungovať s novým správaním animovanej zemegule.


To isté môžeme dosiahnuť aj inak: namiesto nového atribútu odraz v triede Anim, vytvoríme novú odvodenú triedu, v ktorej pozmeníme len metódu pohyb. Trieda Anim je teraz pôvodná verzia a odvodená trieda bude AnimSOdrazom:

class Anim:
    canvas = None
    def __init__(self, x, y, zoz, tik=100, dx=0, dy=0):
        ...

    def pohyb(self):
        self.faza = (self.faza + 1) % len(self.zoz)
        self.canvas.itemconfig(self.id, image=self.zoz[self.faza])
        if self.dx or self.dy:
            self.presun((self.x + self.dx) % self.sirka,
                        (self.y + self.dy) % self.vyska)

    ...

class AnimSOdrazom(Anim):
    def pohyb(self):
        self.faza = (self.faza + 1) % len(self.zoz)
        self.canvas.itemconfig(self.id, image=self.zoz[self.faza])
        if self.dx or self.dy:
            if self.x+self.dx < self.vel: self.dx = abs(self.dx)
            if self.y+self.dy < self.vel: self.dy = abs(self.dy)
            if self.x+self.dx > self.sirka - self.vel: self.dx = -abs(self.dx)
            if self.y+self.dy > self.vyska - self.vel: self.dy = -abs(self.dy)
            self.presun(self.x + self.dx, self.y + self.dy)

Malá oprava ešte aj v metóde klik:

class Plocha:
    ...

    def klik(self, event):
        ix = random.randrange(len(self.zoz))
        if ix == 0:
            dx, dy = random.randint(1, 5), random.randint(-2, 2)
        elif ix == 1:
            dx, dy = random.randint(-7, -4), random.randint(-2, 2)
        elif ix == 2:
            dx, dy = random.randint(-5, 5), random.randint(-5, 5)
        if ix == 2:
            a = AnimSOdrazom(event.x, event.y, self.zoz[ix], random.randint(20, 200), dx, dy)
        else:
            a = Anim(event.x, event.y, self.zoz[ix], random.randint(20, 200), dx, dy)
        self.azoz.insert(0, (time.time(), a))

    ...

13. Týždenný projekt

L.I.S.T.


Robot Karel

Robot Karel sa pohybuje po štvorcovej sieti, v ktorej sa na niektorých políčkach nachádzajú kartičky s nejakými symbolmi. Robot prechádza ponad tieto políčka, pričom na niektorých môže kartičku pod sebou zdvihnúť (vloží si ju do svojho batoha), resp. karičku z batoha vybrať a položiť na políčko pod seba. Robot reaguje na povely 'vlavo', 'vpravo', 'krok', 'zdvihni', 'poloz':

  • robot je natočený v jednom zo štyroch smerov, označovať ich budeme takto: 0 na východ, 1 na juh, 2 na západ, 3 na sever

  • príkazy 'vlavo', resp. 'vpravo' otočia robota v danom smere

  • príkazom 'krok' robot prejde v momentálnom smere na susedné políčko, ak je už na okraji siete, z plochy nevypadne, ale v danom smere nevykoná nič

  • príkazom 'zdvihni' zoberie kartičku z políčka pod sebou a vloží ju do batoha; ak na danom políčku nebola žiadna kartička, príkaz nevykoná nič; ak na danom políčku bolo na sebe viac kartičiek, robot zdvihne najvrchnejšiu z nich; kartičky vkladá do batoha na seba v poradí ako ich zdvíhal z plochy (naspodku je prvá, na vrchu je naposledy zdvihnutá)

  • príkazom 'poloz' vyberie najvrchnejšiu kartičku z batoha a vloží ju na políčko pod seba; ak bol batoh prázdny, príkaz neurobí nič; ak na políčku už boli nejaké kartičky pred tým, novú kartičku položí na vrch týchto kartičiek.

Zadanie štvorcovej siete s počiatočným rozložením kartičiek je v textovom súbore. V prvom riadku je dvojica celých čísel, ktorá popisuje veľkosť štvorcovej siete: počet riadkov a počet stĺpcov. Za tým nasleduje informácia o kartičkách v ploche - v každom riadku je symbol na kartičke a dvojica celých čísel, ktoré označujú riadok a stĺpec pozície kartičky (číslujeme od 0). Na jednom políčku sa môže nachádzať aj viac kartičiek.

Naprogramuj triedu RobotKarel:

class RobotKarel:
    def __init__(self, meno_suboru):
        ...

    def __str__(self):
        return ''

    def robot(self, riadok, stlpec, smer):
        ...

    def rob(self, prikaz):
        return 0

    def batoh(self):
        return []

kde

  • init prečíta súbor - robot tam zatiaľ nie je

  • __str__ vráti znakovú reprezentáciu plochy: pozíciu robota zapíš (podľa momentálneho natočenia) jedným zo znakov '>', 'v', '<', '^', ak je na políčku viac kartičiek, zobrazí sa iba najvrchnejšia z nich, prázdne políčko zobraz znakom '.'; ak je robot na políčku s kartičkami, zobrazí sa iba robot

  • robot položí robota na zadaný riadok a stĺpec s daným otočením (číslo od 0 do 3)

  • rob dostáva jeden povel, alebo postupnosť za sebou nasledujúcich povelov, pričom povel je jeden z reťazcov 'vlavo', 'vpravo', 'krok', 'zdvihni', 'poloz', ktorý môže mať na začiatku aj celé číslo, vtedy to označuje počet opakovaní; napríklad '3 krok' označuje tri kroky za sebou; robot sa postupne pohybuje v danom smere, pričom zbiera, resp. kladie kartičky; povely, ktoré sa nedajú vykonať, ignoruje; funkcia vráti počet tu vykonaných ale neignorovaných povelov

  • metóda batoh vráti momentálny zoznam kartičiek so symbolmi v batohu (prvým prvkom je najspodnejšia kartička, posledným je najvrchnejšia)

  • odporúčame štvorcovú sieť reprezentovať ako dvojrozmernú tabuľku (zoznam zoznamov), v ktorej každý prvok je buď reťazec (postupnosť znakov) alebo zoznam znakov, políčka bez kartičiek reprezentuj prázdnym reťazcom, resp. zoznamom

Napríklad, pre súbor 'subor1.txt':

3 4
N 1 3
O 1 2
H 1 1
P 0 1
Y 0 2
T 0 3

takýto test:

if __name__ == '__main__':
    k = RobotKarel('subor1.txt')
    k.robot(0, 0, 0)
    print(k)
    print(k.rob('krok'))
    print(k.rob('2 zdvihni'))
    k.rob('krok')
    k.rob('vpravo')
    k.rob('krok')
    k.rob('2 zdvihni')
    k.rob('2 krok')
    print(k)
    print('batoh =', k.batoh())
    k.rob('poloz vlavo')
    k.rob('krok 6 vlavo')
    print(k)
    print('batoh =', k.batoh())

vypíše:

>PYT
.HON
....
1
1
..YT
.H.N
..v.
batoh = ['P', 'O']
..YT
.H.N
..O<
batoh = ['P']

Z úlohového servera L.I.S.T. si stiahni kostru programu riesenie.py. Pozri si testovacie dáta v súboroch 'subor1.txt', 'subor2.txt', 'subor3.txt', …, ktoré bude používať testovač.

Tvoj odovzdaný program s menom riesenie.py musí začínať tromi riadkami komentárov:

# 13. zadanie: karel
# autor: Janko Hraško
# datum: 17.12.2020

Riešenie (bez dátových súborov) odovzdaj na úlohový server https://list.fmph.uniba.sk/ najneskôr do 30. decembra, kde ho môžeš nechať otestovať. Testovač bude spúšťať metódy triedy s rôznymi vstupmi. Môžeš zaň získať 5 bodov.