Testi

Testi: testi-objektni-minoboti.py

Naloga

Napiši razred Minobot. Ta sicer ne bo imel več nobene zveze z minami, imel pa bo zvezo z nalogo Minobot, ki smo jo reševali pred časom.

Minobot se v začetku nahaja na koordinatah (0, 0) in je obrnjen na desno. Koordinatni sistem je takšen kot pri matematiki: koordinata y narašča navzgor.

Razred Minobot ima naslednje metode.

  • naprej(d) gre za d naprej v podani smeri;
  • desno() se obrne za 90 stopinj v desno;
  • levo() se obrne za 90 stopinj levo;
  • koordinate() vrne trenutne koordinate (x in y)
  • razdalja() vrne pravokotno razdaljo do koordinat (0, 0): če se robot nahaja na (5, -3), je razdalja do (0, 0) enaka 8.

Če, recimo izvedemo

a = Minobot()
a.levo()
a.naprej(4)
a.desno()
a.naprej(3)
print(a.koordinate())

se izpiše (3, 4).

Rešitev

Robot bo imel tri atribute self.x in self.y bosta koordinati, self.smer pa smer. Kako bomo shranili koordinate, naloga bolj ali manj določa: pravi, naj y narašča navzgor, zato bo najbolj praktično, če ga tudi v razredu shranjujemo tako.

Glede tega, kako naj shranimo smer, naloga ne zahteva ničesar. Lahko shranimo kot v stopinjah, kot v radianih, smer neba s črko ali besedo v angleščini ali slovenščini, lahko shranjujemo z znaki v, ^, < in >... Kakor želimo. Storili bomo takle: 0 bo sever, 1 vzhod, 2 jug in 3 zahod. To nam zelo poenostavi metodi desno in levo.

class Minobot:
    def __init__(self):
        self.x = self.y = 0
        self.smer = 1

    def desno(self):
        self.smer = (self.smer + 1) % 4

    def levo(self):
        self.smer = (self.smer - 1) % 4

    def naprej(self, d):
        if self.smer == 0:
            self.y += d
        elif self.smer == 1:
            self.x += d
        elif self.smer == 2:
            self.y -= d
        else:
            self.x -= d

    def koordinate(self):
        return self.x, self.y

    def razdalja(self):
        return abs(self.x) + abs(self.y)

Za primer krajše rešitve povejmo, da pozna Python tudi kompleksna števila. Po inženirski navadi za i je uporabljamo i temveč j. Tako bi 1 + 2i zapisali z 1 + 2j. Če želimo le i, moramo napisati 1j, saj bi sam j pomenil spremenljivko j.

class Minobot:
    def __init__(self):
        self.pozicija = 0
        self.smer = 1

    def desno(self):
        self.smer *= -1j

    def levo(self):
        self.smer *= 1j

    def naprej(self, d):
        self.pozicija += d * self.smer

    def koordinate(self):
        return int(self.pozicija.real), int(self.pozicija.imag)

    def razdalja(self):
        x, y = self.koordinate()
        return abs(x) + abs(y)

Zdaj je self.smer kar "vektor" v kompleksnem - enaka bo 1, -i, -1 in 1 (desno, dol, levo in gor). Obe koordinati bosta shranjeni v enem samem kompleksnem številu self.pozicija; metoda naprej ga spremeni tako, da se prestavi za podano razdaljo (d) v trenutni smeri.

Zapleteta (pa ne preveč) se le koordinate in razdalja, ki morata iz pozicije pobrati realno in imaginarno komponento.

Toliko, da veste, zakaj se splača poslušati pri matematiki.

Dodatna naloga

Dodaj metodo razveljavi(), ki razveljavi zadnji ukaz naprej, levo ali desno. Lahko jo pokličemo tudi večkrat. (Če jo pokličemo tolikokrat, da ni več česa razveljavljati, ne naredi ničesar.)

Če gornji program nadaljujemo z

a.razveljavi()
a.razveljavi()
a.naprej(2)
print(a.koordinate())

se izpiše (0, 6), saj smo razveljavili zadnja ukaza naprej in desno.

Če nadaljujemo z

a.razveljavi()
print(a.koordinate())

se izpiše (0, 4), saj smo razveljavili naprej(2).

Rešitev

Undo najlažje naredimo tako, da si zapomnimo vsa pretekla stanja. (Pravi undo pa je pogosto narejen tako, da si ob izvedbi akcije zapomnimo nasprotno akcijo. Mnogi ste dejansko reševali tako - to je sicer pohvalno, vendar je pri tej nalogi težje.)

Pretekla stanja bomo shranjevali v self.stanja: to bo seznam trojk (self.x, self.y, self.smer). Metoda, ki jo zahteva naloga, razveljavi, bo le nastavila self.x, self.y in self.smer na zadnje shranjeno stanje. Poleg tega pa bomo napisali še metodo shrani_stanje, ki bo dodala trojko v seznam. Metodo bomo poklicali pred izvedbo vsake akcije, torej na začetku metod desno, levo in naprej.

class Minobot:
    def __init__(self):
        self.x = self.y = 0
        self.smer = 1
        self.stanja = []

    def shrani_stanje(self):
        self.stanja.append((self.x, self.y, self.smer))

    def razveljavi(self):
        if self.stanja:
            self.x, self.y, self.smer = self.stanja.pop()

    def desno(self):
        self.shrani_stanje()
        self.smer = (self.smer + 1) % 4

    def levo(self):
        self.shrani_stanje()
        self.smer = (self.smer - 1) % 4

    def naprej(self, d):
        self.shrani_stanje()
        if self.smer == 0:
            self.y += d
        elif self.smer == 1:
            self.x += d
        elif self.smer == 2:
            self.y -= d
        else:
            self.x -= d

    def koordinate(self):
        return self.x, self.y

    def razdalja(self):
        return abs(self.x) + abs(self.y)

Najpomembnejši nauk te zgodbe je, da mora imeti vsak robot svoj undo, zato ga je potrebno shraniti v self in ne v globalno spremenljivko, kot ste počeli nekateri. Globalne spremenljivke so slaba ideja.

Zadnja sprememba: četrtek, 25. marec 2021, 21.27