3. diel - Hracia kocka - Konštruktory a náhodné čísla vo Swift
V predchádzajúcom cvičení, Riešené úlohy k 1.-2. lekciu OOP vo Swift, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V minulej lekcii, Riešené úlohy k 1.-2. lekciu OOP vo Swift , sme si naprogramovali prvú objektovú aplikáciu. Už vieme tvoriť nové triedy a vkladať do nich vlastnosti a metódy s parametrami a návratovú hodnotou. V dnešnom Swift tutoriálu začneme pracovať na sľúbené aréne, v ktorej budú proti sebe bojovať dvaja bojovníci. Boj bude ťahový (na preskáčku) a bojovník vždy druhému uberie život na základe sily jeho útoku a obrany druhého bojovníka. Simulujeme v podstate stolný hru, budeme teda simulovať aj hraciu kocku, ktorá dodá hre prvok náhodnosti. Začnime zvoľna a vytvorme si dnes práve túto hraciu kocku. Zároveň sa naučíme ako definovať vlastné konštruktor.
Vytvorme si novú Command Line Tool aplikáciu a pomenujte ju
Arena
. K projektu si pridajme novú class
s názvom
Kostka
. Zamyslime sa nad vlastnosťami, ktoré kocke dáme. Iste by
sa hodilo, keby sme si mohli zvoliť počet stien kocky (klasicky 6 alebo 10
stien, ako je zvykom u tohto typu hier). Ďalej bude kocka potrebovať tzv.
Generátor náhodných čísel. Ten nám samozrejme poskytne Swift, ktorý na
tieto účely obsahuje špeciálnu funkciu. Naša trieda bude mať teraz zatiaľ
vlastnosť pocetStran
, ktorú pre východiskový stav rovno
nastavíme na 6
, nech nemusíme riešiť Optional
alebo písať konštruktor.
Minule sme kvôli jednoduchosti nastavovali všetky vlastnosti našej triedy
ako public
, teda ako verejne prístupné. Väčšinou sa však
skôr nechce, aby sa dali zvonku modifikovať a používa sa modifikátor
private
. Vlastnosť je potom viditeľná len vnútri triedy a
zvonku sa Swift tvári, že vôbec neexistuje. Pri návrhu triedy teda
nastavíme všetko na private
a v prípade, že niečo bude naozaj
potrebné vystaviť, zmažeme private
a tým sa použije
internal
(takže pre celý modul). Naša trieda teraz vyzerá asi
takto:
class Kostka { private var pocetSten = 6 }
Konštruktory
Až doteraz sme nevedeli zvonku nastaviť iné vlastnosti ako
public
, pretože napr. private
nie sú zvonku
viditeľné. Už sme si hovorili niečo málo o konstruktoru objektu. Je to
metóda, ktorá sa zavolá vo chvíli vytvorenia inštancie
objektu. Slúži samozrejme k nastavenie vnútorného stavu objektu a
na vykonanie prípadnej inicializácia. Kocku by sme teraz v
main.swift
vytvorili takto:
let kostka = Kostka()
Práve Kostka()
je konštruktor. Pretože v našej triede
žiadny nie je, Swift si dogeneruje prázdnu metódu. My si však teraz
konštruktor do triedy pridáme. Deklaruje sa ako metóda, ale nemá
návratový typ a musí mať názov
init()
. V konstruktoru nastavíme počet stien na pevnú
hodnotu aa tým pádom už nemusíme mať pocetSten
nastavený
priamo v deklarácii, je ale treba pridať dátový typ. Trieda
Kostka
bude vyzerať nasledovne:
private var pocetSten : Int init() { pocetSten = 6 }
Ak kocku teraz vytvoríme, bude mať vo vlastnosti pocetSten
hodnotu 6
. Vypíšme si počet stien do konzoly, nech vidíme, že
tam hodnota naozaj je. Nie je dobré vlastnosť nastaviť na public
/ internal
, pretože nebudeme chcieť, aby nám niekto mohol už u
vytvorenej kocky meniť počet stien.
Pridáme do triedy teda metódu vratPocetSten()
, ktorá nám
vráti hodnotu vlastnosti pocetSten
. Docielili sme tým v podstate
toho, že je vlastnosť read-only (vlastnosť nie je viditeľná a je možné ju
iba čítať metódou, zmeniť ju nemožno). Swift má na tento účel ešte
ďalšie konštrukcie, ale tým sa zatiaľ nebudeme zaoberať. Nová metóda
bude vyzerať asi takto:
func vratPocetSten() -> Int { return pocetSten }
Presuňme sa do main.swift
a vyskúšajme si vytvoriť kocku a
vypísať počet stien:
{SWIFT} let kostka = Kostka() // v tuto chvíli se zavolá konstruktor print(kostka.vratPocetSten()) {/SWIFT}
{SWIFT} class Kostka { private var pocetSten : Int init() { pocetSten = 6 } func vratPocetSten() -> Int { return pocetSten } } {/SWIFT}
výstup:
6
Vidíme, že sa konštruktor naozaj zavolal. My by sme ale chceli, aby sme mohli pri každej kocky pri vytvorení špecifikovať, koľko stien budeme potrebovať. Dáme teda konstruktoru parameter:
init(aPocetSten: Int) {
pocetSten = aPocetSten
}
Všimnite si, že sme pred názov parametra pridali a
, aby sme
ho odlíšili od rovnomennej vlastnosti triedy. Vráťme sa k
main.swift
a zadajte tento parameter do konstruktoru:
{SWIFT} let kostka = Kostka(aPocetSten: 10) print(kostka.vratPocetSten()) {/SWIFT}
{SWIFT} class Kostka { private var pocetSten : Int init(aPocetSten: Int) { pocetSten = aPocetSten } func vratPocetSten() -> Int { return pocetSten } } {/SWIFT}
výstup:
10
Všetko funguje, ako sme očakávali. Swift nám už v tejto chvíli
nevygeneruje prázdny (tzv. Bezparametrický konštruktor), takže kocku bez
parametra vytvoriť nedá. My to však môžeme umožniť, vytvorme si ďalší
konštruktor a tentoraz bez parametra. V ňom nastavíme počet stien na
6
, pretože takú hodnotu asi užívateľ našej triedy u kocky
očakáva ako predvolený:
init() { pocetSten = 6 }
Skúsme si teraz vytvoriť 2 inštancie kocky, každú iným konstruktoru (v
main.swift
):
{SWIFT} let sestistenna = Kostka() let desetistenna = Kostka(aPocetSten: 10) print(sestistenna.vratPocetSten()) print(desetistenna.vratPocetSten()) {/SWIFT}
{SWIFT} class Kostka { private var pocetSten : Int init() { pocetSten = 6 } init(aPocetSten: Int) { pocetSten = aPocetSten } func vratPocetSten() -> Int { return pocetSten } } {/SWIFT}
výstup:
6 10
Swiftu nevadí, že máme 2 metódy s rovnakým názvom, pretože ich
parametre sú rôzne. Hovoríme o tom, že metóda init()
(teda tu
konštruktor) má preťaženia (overload). Toho môžeme
využívať aj u všetkých ďalších metód, nielen u konstruktoru. Xcode nám
preťaženie pri metódach prehľadne ponúka (vo chvíli, keď za názov
metódy napíšeme ľavú zátvorku), variantmi metódy si môžeme listovať
pomocou šípok. V ponuke vidíme naše 2 konštruktory:
Mnoho metód vo Swift má hneď niekoľko preťaženie, skúste sa pozrieť
napr. Na metódu append()
na String
u. Je dobré si u
metód prejsť ich preťaženie, aby ste neprogramoval niečo, čo už niekto
urobil pred vami.
Ukážeme si ešte, ako ide obísť nepraktický názov atribútu u
parametrického konstruktoru (v našom prípade aPocetSten
) a potom
konštruktory na chvíľu opustíme. Problém je samozrejme v tom, že keď
napíšeme:
init(pocetSten: Int) {
pocetSten = pocetSten
}
Swift nevie, ktorú z premenných myslíme, či parameter alebo atribút. V
tomto prípade priraďujeme do parametra znova ten istý parameter. Xcode nás
na túto skutočnosť dokonca upozorní. Vnútri triedy sa máme možnosť
odkazovať na jej inštanciu, je uložená v premennej
self
. Využitie si môžeme predstaviť napr. Keby
kocka mala metódu dejHraci(hrac: Hrac)
a tam by volala
hrac.seberKostku(self)
. Tu by sme hráči pomocou
self
odovzdali seba samého, teda tú konkrétnu
kocku, s ktorou pracujeme. My sa tým tu nebudeme zaťažovať, ale využijeme
odkazu na inštanciu pri nastavovaní atribútu:
init(pocetSten: Int) { self.pocetSten = pocetSten }
Pomocou self
sme špecifikovali, že ľavá premenná
pocetSten
náleží inštanciu, pravú Swift chápe ako z
parametra. Máme teda 2 konštruktory, ktoré nám umožňujú tvoriť rôzne
hracie kocky. Prejdime ďalej.
Náhodné čísla
Definujme na kocke metódu hod()
, ktorá nám vráti náhodné
číslo od 1
do počtu stien. Je to veľmi jednoduché, metóda
bude internal
(pôjde volať zvonku) a nebude mať žiadny
parameter. Návratová hodnota bude typu Int
. Swift nám ponúka
pekné metódy random()
na číslach a dokonca aj na type
bool
(kde dostaneme náhodne true
alebo
false
). My chceme celé čísla, takže zavoláme metódu
random()
na type Int
nasledovne:
Int.random(in: 1...pocetSten)
.
Metóda hod()
teda bude vyzerať nasledovne:
func hod() -> Int { return Int.random(in: 1...pocetSten) }
Výpis informácií o inštanciu triedy Kostka
Kocka je takmer hotová, ukážme si ešte jednu užitočnú vlastnosť,
ktorú ju pridáme a ktorú budeme hojne používať aj vo väčšine našich
ďalších objektov. Swift využíva vlastnosť description
a je
určená na to, aby vrátila tzv. Textovú reprezentáciu
inštancie. Hodí sa vo všetkých prípadoch, kedy si inštanciu
potrebujeme vypísať alebo s ňou pracovať ako s textom. Túto vlastnosť
majú napr. Aj čísla a využíva ju Swift, keď tieto premenné použijeme v
interpolovanom String
u alebo v metóde print()
. Ak si
robíme vlastný triedu, mali by sme zvážiť, či sa nám takáto vlastnosť
nehodí. Nikdy by sme si nemali robiť vlastnú metódu, napr. Niečo ako
vypis()
, keď máme vo Swiftu pripravenú cestu, ako toto riešiť.
U kocky nemá description
vyšší zmysel, ale u bojovníka bude
určite vracať jeho meno. My si ju ku kocke rovnako pridáme, bude vypisovať,
že sa jedná o kocku a vráti aj počet stien. Najprv si skúsme vypísať
našu inštanciu kocky:
{SWIFT} let sestistenna = Kostka() let desetistenna = Kostka(pocetSten: 10) print(sestistenna) {/SWIFT}
{SWIFT} class Kostka { private var pocetSten : Int init() { pocetSten = 6 } init(pocetSten: Int) { self.pocetSten = pocetSten } func vratPocetSten() -> Int { return pocetSten } func hod() -> Int { return Int(arc4random_uniform(UInt32(pocetSten))) + 1 } } {/SWIFT}
Do konzoly sa vypíše iba cesta k našej triede, teda
Arena.Kostka
. Nižšie si ukážeme ako vlastnosť description
definovať. Kód je pre nás zatiaľ komplexné, takže ho moc neriešte,
techniky budú vysvetlené v ďalších lekciách
{SWIFT} class Kostka : CustomStringConvertible { var description: String { return "Kostka s \(pocetSten) stěnami" } // ... zbytek třídy Kostka private var pocetSten : Int init() { pocetSten = 6 } init(pocetSten: Int) { self.pocetSten = pocetSten } func vratPocetSten() -> Int { return pocetSten } func hod() -> Int { return Int(arc4random_uniform(UInt32(pocetSten))) + 1 } } {/SWIFT}
{SWIFT} let sestistenna = Kostka() let desetistenna = Kostka(pocetSten: 10) print(sestistenna) {/SWIFT}
description
som implementovali ako špeciálnu vlastnosť,
ktorá umožňuje iba čítanie. V definícii triedy môžete vidieť pridané
: CustomStringConvertible
- jedná sa o protokol, ktorý
špecifikuje, čo trieda vie. Protokoly si vysvetlíme v jednej z nasledujúcich
lekcií.
Teraz opäť skúsime do konzoly vypísať priamo inštanciu kocky.
výstup:
Kostka s 6 stěnami
Ešte si naše kocky vyskúšame. Skúsime si v programe s našimi dvoma kockami v cykloch hádzať a pozrieme sa, či fungujú tak, ako sa očakáva:
{SWIFT} 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: " ") } {/SWIFT}
{SWIFT} class Kostka : CustomStringConvertible { var description: String { return "Kostka s \(pocetSten) stěnami" } private var pocetSten : Int init() { pocetSten = 6 } init(pocetSten: Int) { self.pocetSten = pocetSten } func vratPocetSten() -> Int { return pocetSten } func hod() -> Int { return Int(arc4random_uniform(UInt32(pocetSten))) + 1 } } {/SWIFT}
Výstup môže vyzerať nejako 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 hotovú pomerne peknú a nastaviteľnou triedu, ktorá reprezentuje
hraciu kocku. Bude sa nám hodiť v našej aréne, ale môžete ju použiť aj
kdekoľvek inde. Vidíme, ako OOP umožňuje znovupoužívat komponenty. V
budúcej lekcii, Riešené úlohy k 3. lekcii OOP vo Swift , si povieme niečo o odlišnostiach medzi
referenčnými dátovými typmi (objekty) a typy hodnotovými (napr.
Int
).
V nasledujúcom cvičení, Riešené úlohy k 3. lekcii OOP vo Swift, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkamiStiahnuté 17x (21.09 kB)