Lekce 3 - Hrací kostka - Konstruktory a náhodná čísla ve Swift

Swift OOP Hrací kostka - Konstruktory a náhodná čísla ve Swift

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, První objektová aplikace ve Swift, jsme si naprogramovali první objektovou aplikaci. Již umíme tvořit nové třídy a vkládat do nich vlastnosti a metody s parametry a návratovou hodnotou. V dnešním Swift tutoriálu začneme pracovat na slíbené aréně, ve které budou proti sobě bojovat dva bojovníci. Boj bude tahový (na přeskáčku) a bojovník vždy druhému ubere život na základě síly jeho útoku a obrany druhého bojovníka. Simulujeme v podstatě stolní hru, budeme tedy simulovat i hrací kostku, která dodá hře prvek náhodnosti. Začněme zvolna a vytvořme si dnes právě tuto hrací kostku. Zároveň se naučíme jak definovat vlastní konstruktor.

Vytvořme si novou Command Line Tool aplikaci a pojmenujme ji Arena. K projektu si přidejme novou class s názvem Kostka. Zamysleme se nad vlastnostmi, které kostce dáme. Jistě by se hodilo, kdybychom si mohli zvolit počet stěn kostky (klasicky 6 nebo 10 stěn, jak je zvykem u tohoto typu her). Dále bude kostka potřebovat tzv. generátor náhodných čísel. Ten nám samozřejmě poskytne Swift, který k těmto účelům obsahuje speciální funkci. Naše třída bude mít nyní zatím vlastnost pocetStran, který pro výchozí stav rovnou nastavíme na 6, ať nemusíme řešit Optional nebo psát konstruktor.

Minule jsme kvůli jednoduchosti nastavovali všechny vlastnosti naší třídy jako public, tedy jako veřejně přístupné. Většinou se však spíše nechce, aby se daly zvenčí modifikovat a používá se modifikátor private. Vlastnost je poté viditelná jen uvnitř třídy a zvenčí se Swift tváří, že vůbec neexistuje. Při návrhu třídy tedy nastavíme vše na private a v případě, že něco bude opravdu potřeba vystavit, smažeme private a tím se použije internal (takže pro celý modul). Naše třída nyní vypadá asi takto:

class Kostka {
        private var pocetSten = 6

}

Konstruktory

Až doposud jsme neuměli zvenčí nastavit jiné vlastnosti než public, protože např. private nejsou zvenčí viditelné. Již jsme si říkali něco málo o konstruktoru objektu. Je to metoda, která se zavolá ve chvíli vytvoření instance objektu. Slouží samozřejmě k nastavení vnitřního stavu objektu a k provedení případné inicializace. Kostku bychom nyní v main.swift vytvořili takto:

let kostka = Kostka()

Právě Kostka() je konstruktor. Protože v naší třídě žádný není, Swift si dogeneruje prázdnou metodu. My si však nyní konstruktor do třídy přidáme. Deklaruje se jako metoda, ale nemá návratový typ a musí mít název init(). V konstruktoru nastavíme počet stěn na pevnou hodnotu a a tím pádem už nemusíme mít pocetSten nastaven přímo v deklaraci, je ale třeba přidat datový typ. Třída Kostka bude vypadat následovně:

private var pocetSten : Int

init() {
        pocetSten = 6
}

Pokud kostku nyní vytvoříme, bude mít ve vlastnosti pocetSten hodnotu 6. Vypišme si počet stěn do konzole, ať vidíme, že tam hodnota opravdu je. Není dobré vlastnost nastavit na public/internal, protože nebudeme chtít, aby nám někdo mohl již u vytvořené kostky měnit počet stěn. Přidáme do třídy tedy metodu vratPocetSten(), která nám vrátí hodnotu vlastnosti pocetSten. Docílili jsme tím v podstatě toho, že je vlastnost read-only (vlastnost není viditelná a lze ji pouze číst metodou, změnit ji nelze). Swift má k tomuto účelu ještě další konstrukce, ale tím se zatím nebudeme zabývat. Nová metoda bude vypadat asi takto:

func vratPocetSten() -> Int {
        return pocetSten
}

Přesuňme se do main.swift a vyzkoušejme si vytvořit kostku a vypsat počet stěn:

let kostka = Kostka() // v tuto chvíli se zavolá konstruktor
print(kostka.vratPocetSten())

Výstup:

6

Vidíme, že se konstruktor opravdu zavolal. My bychom ale chtěli, abychom mohli u každé kostky při vytvoření specifikovat, kolik stěn budeme potřebovat. Dáme tedy konstruktoru parametr:

init(aPocetSten: Int) {
        pocetSten = aPocetSten
}

Všimněte si, že jsme před název parametru přidali a, abychom jej odlišili od stejnojmenné vlastnosti třídy. Vraťme se k main.swift a zadejme tento parametr do konstruktoru:

let kostka = Kostka(aPocetSten: 10)
print(kostka.vratPocetSten())

Výstup:

10

Vše funguje, jak jsme očekávali. Swift nám již v tuto chvíli nevygeneruje prázdný (tzv. bezparametrický konstruktor), takže kostku bez parametru vytvořit nelze. My to však můžeme umožnit, vytvořme si další konstruktor a tentokrát bez parametru. V něm nastavíme počet stěn na 6, protože takovou hodnotu asi uživatel naší třídy u kostky očekává jako výchozí:

init() {
        pocetSten = 6
}

Zkusme si nyní vytvořit 2 instance kostky, každou jiným konstruktorem (v main.swift):

let sestistenna = Kostka()
let desetistenna = Kostka(aPocetSten: 10)
print(sestistenna.vratPocetSten())
print(desetistenna.vratPocetSten())

Výstup:

6
10

Swiftu nevadí, že máme 2 metody se stejným názvem, protože jejich parametry jsou různé. Hovoříme o tom, že metoda init() (tedy zde konstruktor) má přetížení (overload). Toho můžeme využívat i u všech dalších metod, nejen u konstruktorů. Xcode nám přetížení u metod přehledně nabízí (ve chvíli, kdy za název metody napíšeme levou závorku), variantami metody si můžeme listovat pomocí šipek. V nabídce vidíme naše 2 konstruktory:

Napovídání přetížení metod v Xcode

Mnoho metod ve Swift má hned několik přetížení, zkuste se podívat např. na metodu append() na Stringu. Je dobré si u metod projít jejich přetížení, abyste neprogramovali něco, co již někdo udělal před vámi.

Ukážeme si ještě, jak jde obejít nepraktický název atributu u parametrického konstruktoru (v našem případě aPocetSten) a potom konstruktory na chvíli opustíme. Problém je samozřejmě v tom, že když napíšeme:

init(pocetSten: Int) {
        pocetSten = pocetSten
}

Swift neví, kterou z proměnných myslíme, jestli parametr nebo atribut. V tomto případě přiřazujeme do parametru znovu ten samý parametr. Xcode nás na tuto skutečnost dokonce upozorní. Uvnitř třídy se máme možnost odkazovat na její instanci, je uložena v proměnné self. Využití si můžeme představit např. kdyby kostka měla metodu dejHraci(hrac: Hrac) a tam by volala hrac.seberKostku(self). Zde bychom hráči pomocí self předali sebe sama, tedy tu konkrétní kostku, se kterou pracujeme. My se tím zde nebudeme zatěžovat, ale využijeme odkazu na instanci při nastavování atributu:

init(pocetSten: Int) {
        self.pocetSten = pocetSten
}

Pomocí self jsme specifikovali, že levá proměnná pocetSten náleží instanci, pravou Swift chápe jako z parametru. Máme tedy 2 konstruktory, které nám umožňují tvořit různé hrací kostky. Přejděme dál.

Náhodná čísla

Definujme na kostce metodu hod(), která nám vrátí náhodné číslo od 1 do počtu stěn. Je to velmi jednoduché, metoda bude internal (půjde volat zvenčí) a nebude mít žádný parametr. Návratová hodnota bude typu Int. Náhodné číslo získáme pomocí celkem nehezké funkce arc4random_uniform(), která bere jako parametr horní hranici generovaného čísla, která do intervalu nepatří. Protože bere jako datový typ UInt32, tak musíme provést převod. K výsledku ještě přičteme jedničku, abychom se dostali do intervalu 1-6, spodní hranice je totiž nula a tu na kostce neházíme :-)

Metoda hod() tedy bude vypadat následovně:

func hod() -> Int {
        return Int(arc4random_uniform(UInt32(pocetSten))) + 1
}

Výpis informací o instanci třídy Kostka

Kostka je téměř hotová, ukažme si ještě jednu užitečnou vlastnost, kterou ji přidáme a kterou budeme hojně používat i ve většině našich dalších objektů. Swift využívá vlastnost description a je určena k tomu, aby vrátila tzv. textovou reprezentaci instance. Hodí se ve všech případech, kdy si instanci potřebujeme vypsat nebo s ní pracovat jako s textem. Tuto vlastnost mají např. i čísla a využívá ji Swift, když tyto proměnné použijeme v interpolovaném Stringu nebo v metodě print(). Pokud si děláme vlastní třídu, měli bychom zvážit, zda se nám takováto vlastnost nehodí. Nikdy bychom si neměli dělat vlastní metodu, např. něco jako vypis(), když máme ve Swiftu připravenou cestu, jak toto řešit. U kostky nemá description vyšší smysl, ale u bojovníka bude jistě vracet jeho jméno. My si ji ke kostce stejně přidáme, bude vypisovat, že se jedná o kostku a vrátí i počet stěn. Nejprve si zkusme vypsat naši instanci kostky:

let sestistenna = Kostka()
let desetistenna = Kostka(pocetSten: 10)

print(sestistenna)

Do konzole se vypíše pouze cesta k naší třídě, tedy Arena.Kostka. Níže si ukážeme jak vlastnost description definovat. Kód je pro nás zatím komplexní, takže ho moc neřešte, techniky budou vysvětleny v dalších lekcích :-)

class Kostka : CustomStringConvertible {

    var description: String {
        return "Kostka s \(pocetSten) stěnami"
    }

    // Zbytek třídy Kostka

}

let sestistenna = Kostka()
let desetistenna = Kostka(pocetSten: 10)

print(sestistenna)

description jsem implementovali jako speciální vlastnost, která umožňuje pouze čtení. V definici třídy můžete vidět přidané : CustomStringConvertible - jedná se o protokol, který specifikuje, co třída umí. Protokoly si vysvětlíme v jedné z následujících lekcí.

Nyní opět zkusíme do konzole vypsat přímo instanci kostky.

Výstup:

Kostka s 6 stěnami

Ještě si naše kostky vyzkoušíme. Zkusíme si v programu s našima dvěma kostkami v cyklech házet a podíváme se, jestli fungují tak, jak se očekává:

let sestistenna = Kostka()
let desetistenna = Kostka(pocetSten: 10)

// hod šestistěnnou
print(sestistenna)
for _ in 1...10 {
    print(sestistenna.hod(), terminator: " ")
}

// hod desetistěnnou
print("\n\n \(desetistenna)")
for _ in 1...10 {
    print(desetistenna.hod(), terminator: " ")
}

Výstup může vypadat nějak takto:

Kostka s 6 stěnami
3 6 6 1 6 3 6 2 6 3

Kostka s 10 stěnami
5 9 9 2 10 4 9 3 10 5

Máme hotovou poměrně hezkou a nastavitelnou třídu, která reprezentuje hrací kostku. Bude se nám hodit v naší aréně, ale můžete ji použít i kdekoli jinde. Vidíme, jak OOP umožňuje znovupoužívat komponenty. V příští lekci, Referenční a hodnotové datové typy ve Swift, si řekneme něco o odlišnostech mezi referenčními datovými typy (objekty) a typy hodnotovými (např. Int). :)


 

 

Článek pro vás napsal Filip Němeček
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Autor se příležitostně věnuje vývoji iOS aplikací či těch webových za pomocí frameworku Django. Aktuální projekt: hrejzdarma.cz Twitter: @nemecek_f (neprogramátorské tweety)
Aktivity (2)

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!