5. diel - Bojovník do arény vo Swift
V predchádzajúcom cvičení, Riešené úlohy k 4. lekcii OOP vo Swift, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V minulej lekcii, Riešené úlohy k 4. lekcii OOP vo Swift , sme si vysvetlili rozdiely medzi referenčnými a hodnotovými dátovými typy. Už teda vieme, ako fungujú referencie a ako môžeme s objektmi zaobchádzať. Bude sa nám to hodiť dnes aj nabudúce. Tento a budúci Swift tutoriál budú totiž venované dokončenie našej arény. Hracie kocku už máme, ešte nám chýba ďalšie 2 objekty: bojovník a samotná aréna. Dnes sa budeme venovať bojovníkovi. Najprv si popíšme, čo má bojovník vedieť, potom sa pustíme do písania kódu.
Vlastnosti
Bojovník sa bude nejako menovať a bude mať určitý
počet hp (teda života, napr. 80hp). Budeme uchovávať jeho
maximálnej život (bude sa líšiť u každej inštancie) a
jeho súčasný život, teda napr. Zranený bojovník bude mať
40HP z 80. Bojovník má určitý útok a
obranu, oboje vyjadrené opäť v hp. Keď bojovník útočí s
útokom 20HP na druhého bojovníka s obranou 10hp, uberie mu 10hp života.
Bojovník bude mať referenciu na inštanciu triedy
Kostka
. Pri útoku či obrane si vždy hodí kockou a k
útoku / obrane pripočíta padlých číslo. (Samozrejme by mohol mať každý
bojovník svoju kocku, ale chcel som sa priblížiť stolové podobe hry a
ukázať, ako OOP naozaj simuluje realitu. Bojovníci teda budú zdieľať jednu
inštanciu kocky.) Kockou dodáme hre prvok náhody, v realite sa jedná vlastne
o šťastie, ako sa útok alebo obrana vydarí. Konečne budeme chcieť, aby
bojovníci podávali správy o tom, čo sa deje, pretože inak
by z toho užívateľ nič nemal. Správa bude vyzerať napr. "Zalgoren útočí
s úderom za 25HP.". Správami sa zatiaľ nebudeme zaťažovať a vrátime sa k
nim až nakoniec.
Už vieme, čo budeme robiť, poďme na to! K projektu Arena
si
pridajme triedu Bojovnik
a dodajme ju patričné vlastnosti.
Všetky budú privátne:
class Bojovnik { private var jmeno : String private var zivot : Double private var maxZivot : Double private var utok : Int private var obrana : Int private var kostka : Kostka }
Trieda Kostka
musí samozrejme byť v našom projekte.
Metódy
Poďme pre vlastnosti vytvoriť konštruktor, aby nám fungoval build, nebude to nič ťažké.
init(jmeno: String, zivot: Int, utok: Int, obrana: Int, kostka: Kostka) { self.jmeno = jmeno self.zivot = Double(zivot) self.maxZivot = self.zivot self.utok = utok self.obrana = obrana self.kostka = kostka }
Všimnite si, že maximálna zdravie si v konstruktoru odvodíme a nemáme na
neho parameter v hlavičke metódy. Predpokladáme, že bojovník je pri
vytvorení plne zdravý, stačí nám teda poznať iba jeho život a maximálny
život bude rovnaký. Premenné zivot
a maxZivot
si
síce v konstruktoru vyžiadame ako Int
, ale interne prevedieme na
Double
, aby sme mali neskôr ľahšie delenie.
Prejdime k metódam, opäť sa najprv zamyslime nad tým, čo by mal
bojovník vedieť. Začnime tým jednoduchším, budeme chcieť nejakú textovú
reprezentáciu, aby sme mohli bojovníka vypísať. Vytvoríme si teda
vlastnosť description
, ktorú už poznáme (nezabudnite za triedu
pridať : CustomStringConvertible
, ako pri triede
Kostka
), ktorá vráti meno bojovníka. Určite sa nám bude hodiť
metóda, vracajúci či je bojovník nažive (teda typu Bool
). Aby
to bolo trochu zaujímavejšie, budeme chcieť kresliť život bojovníka do
konzoly, nebudeme teda písať, koľko má života, ale "vykreslíme" ho
takto:
[######### ]
Vyššie uvedený život by zodpovedal asi 70%. Doteraz spomínané metódy
nepotrebovali žiadne parametre. Samotný útok a obranu nechajme na neskôr a
poďme si implementovať description
, nazivu()
a
grafickyZivot()
. Začnime s description
, tam nie je
čo vymýšľať:
var description: String { return jmeno }
Teraz Implementujte metódu nazivu()
, opäť to nebude nič
ťažké. Stačí skontrolovať, či je život väčšia ako 0
a
podľa toho sa zachovať. Mohli by sme ju napísať napríklad takto:
func nazivu() -> Bool { if zivot > 0 { return true } else { return false } }
Keďže aj samotný výraz zivot > 0
je vlastne logická
hodnota, môžeme vrátiť tú a kód sa značne zjednoduší:
func nazivu() -> Bool { return zivot > 0 }
Grafický život
Ako som sa už zmienil, metóda grafickyZivot()
bude
umožňovať vykresliť ukazovateľ života v grafickej podobe. Už vieme, že z
hľadiska objektového návrhu nie je vhodné, aby metóda objektu priamo
vypisovala do konzoly (ak nie je k výpisu objekt určený), preto si znaky
uložíme do reťazca a ten vrátime pre neskoršie vypísanie. Ukážeme si
kód metódy a následne podrobne popíšeme:
func grafickyZivot() -> String { var s = "[" let celkem : Double = 20 var pocet : Double = round((zivot / maxZivot) * celkem) if (pocet == 0) && (nazivu()) { pocet = 1 } for _ in 0..<Int(pocet) { s += "#" } s = s.padding(toLength: Int(celkem) + 1, withPad: " ", startingAt: 0) s += "]" return s }
Pripravíme si reťazec s
a vložíme do neho úvodný znak
[
. Určíme si celkovú dĺžku ukazovateľa života do premennej
celkem
(napr. 20
). Teraz v podstate nepotrebujeme nič
iné, než trojčlenka. Ak maxZivot
zodpovedá celkem
dielikov, zivot
bude zodpovedať pocet
dielkam.
Premenná pocet
obsahuje počet dielikov aktuálneho zdravie.
Matematicky platí, že
pocet = (zivot / maxZivot) * celkem
. My ešte
doplníme zaokrúhlení na celé dieliky.
Mali by sme ošetriť prípad, kedy je život taký nízky, že nám vyjde na
0
dielikov, ale bojovník je stále nažive. V tom prípade
vykreslíme 1 dielik, inak by to vyzeralo, že je už mŕtvy.
Ďalej stačí jednoducho for
cyklom pripojiť k reťazcu
s
patričný počet znakov a doplniť ich medzerami do celkovej
dĺžky. Doplnenie medzerami prevedieme pomocou padding()
na
dĺžku celkem + 1
(a musíme previesť späť na typ
Int
), kde ten znak naviac je úvodná znak [
. Pridáme
koncový znak a reťazec vrátime.
Týmto sme sa dostali k metóde padding()
, ktorú sme v
základnom kurze vynechali. Swift má totiž iba túto jednu metódu, ktorá
funguje celkom jednoducho, ale vie doplniť znaky iba z pravej strany. Ak by sme
chceli dopĺňať znaky z ľavej strany, tak si musíme napísať vlastnú
logiku.
Všetko si vyskúšame, prejdime do main.swift
a vytvorme si
bojovníka (a kocku, pretože tu musíme konstruktoru bojovníka odovzdať).
Následne výpisy, či je nažive a jeho život graficky:
{SWIFT} let kostka = Kostka(pocetSten: 10) let bojovnik = Bojovnik(jmeno: "Zalgoren", zivot: 100, utok: 20, obrana: 10, kostka: kostka) print("Bojovník: \(bojovnik)") // test description print("Naživu: \(bojovnik.nazivu())") // test nazivu() print("Život: \(bojovnik.grafickyZivot())") // test grafickyZivot() {/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}
{SWIFT} class Bojovnik: CustomStringConvertible { private var jmeno : String private var zivot : Double private var maxZivot : Double private var utok : Int private var obrana : Int private var kostka : Kostka init(jmeno: String, zivot: Int, utok: Int, obrana: Int, kostka: Kostka) { self.jmeno = jmeno self.zivot = Double(zivot) self.maxZivot = self.zivot self.utok = utok self.obrana = obrana self.kostka = kostka } var description: String { return jmeno } func nazivu() -> Bool { return zivot > 0 } func grafickyZivot() -> String { var s = "[" let celkem : Double = 20 var pocet : Double = round((zivot / maxZivot) * celkem) if (pocet == 0) && (nazivu()) { pocet = 1 } for _ in 0..<Int(pocet) { s += "#" } s = s.padding(toLength: Int(celkem) + 1, withPad: " ", startingAt: 0) s += "]" return s } } {/SWIFT}
výstup:
Bojovník: Zalgoren Naživu: true Život: [####################]
Boj
Dostávame sa k samotnému boju. Implementujeme metódy pre útok a obranu.
Obrana
Začnime obranou. Metóda branSe()
bude umožňovať brániť sa
úderu, ktorého sila bude odovzdaná metóde ako parameter. Metódu si opäť
ukážeme a potom popíšeme:
func branSe(uder: Int) { let zraneni = Double(uder - (obrana + kostka.hod())) if (zraneni > 0) { zivot -= zraneni if (zivot <= 0) { zivot = 0 } } }
Najprv spočítame skutočné zranenia a to tak, že z útoku nepriateľa
odpočítame našu obranu zvýšenú o číslo, ktoré padlo na hracej kocke. Ak
sme zranenia celej neodrazil (zraneni > 0
), budeme znižovať
náš život. Táto podmienka je dôležitá, keby sme zranenia odrazili a bolo
napr. -2, bez podmienky by sa život zvýšil. Po znížení života
skontrolujeme, či nie je v zápornej hodnote a prípadne ho dorovnáme na
nulu.
Útok
Metóda utoc()
bude brať ako parameter inštanciu bojovníka,
na ktorého sa útočí. To preto, aby sme na ňom mohli zavolať metódu
branSe()
, ktorá na náš útok zareaguje a zmenší protivníkov
život. Tu vidíme výhody referencií vo Swift, môžeme si inštancie
jednoducho odovzdávať a volať na nich metódy, bez toho aby došlo k ich
skopírovanie. Ako prvý vypočítame úder, podobne ako pri obrane, úder bude
náš útok + hodnota z hracej kocky. Na súperovi následne zavoláme metódu
branSe()
s hodnotou úderu:
func utoc(souper: Bojovnik) { let uder = utok + kostka.hod() souper.branSe(uder: uder) }
To by sme mali, poďme si skúsiť v našom ukážkovom programe zaútočiť a potom znova vykresliť život. Pre jednoduchosť nemusíme zakladať ďalšieho bojovníka, ale môžeme zaútočiť sami na seba:
{SWIFT} let kostka = Kostka(pocetSten: 10) let bojovnik = Bojovnik(jmeno: "Zalgoren", zivot: 100, utok: 20, obrana: 10, kostka: kostka) print("Bojovník: \(bojovnik)") // test description print("Naživu: \(bojovnik.nazivu())") // test nazivu() print("Život: \(bojovnik.grafickyZivot())") // test grafickyZivot() bojovnik.utoc(souper: bojovnik) // test útoku print("Život po útoku: \(bojovnik.grafickyZivot())") {/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}
{SWIFT} class Bojovnik: CustomStringConvertible { private var jmeno : String private var zivot : Double private var maxZivot : Double private var utok : Int private var obrana : Int private var kostka : Kostka init(jmeno: String, zivot: Int, utok: Int, obrana: Int, kostka: Kostka) { self.jmeno = jmeno self.zivot = Double(zivot) self.maxZivot = self.zivot self.utok = utok self.obrana = obrana self.kostka = kostka } var description: String { return jmeno } func nazivu() -> Bool { return zivot > 0 } func grafickyZivot() -> String { var s = "[" let celkem : Double = 20 var pocet : Double = round((zivot / maxZivot) * celkem) if (pocet == 0) && (nazivu()) { pocet = 1 } for _ in 0..<Int(pocet) { s += "#" } s = s.padding(toLength: Int(celkem) + 1, withPad: " ", startingAt: 0) s += "]" return s } func branSe(uder: Int) { let zraneni = Double(uder - (obrana + kostka.hod())) if (zraneni > 0) { zivot -= zraneni if (zivot <= 0) { zivot = 0 } } } func utoc(souper: Bojovnik) { let uder = utok + kostka.hod() souper.branSe(uder: uder) } } {/SWIFT}
výstup:
Bojovník: Zalgoren Naživu: True Život: [####################] Život po útoku: [################## ]
Zdá sa, že všetko funguje, ako má. Prejdime k poslednému bodu dnešného tutoriálu a to k správam.
Správy
Ako už bolo povedané, o útokoch a obrane budeme užívateľa informovať
výpisom na konzolu. Výpis nebude vykonávať samotná trieda
Bojovnik
, tá bude len vracať správy ako textové reťazce. Jedna
možnosť by bola nastaviť návratový typ metód utoc()
a
branSe()
na String
a pri ich volanie vrátiť aj
správu. Problém by však nastal v prípade, keď by sme chceli získať
správu od metódy, ktorá už niečo vracia. Metóda samozrejme nemôže
jednoducho vrátiť 2 veci.
Poďme na vec univerzálnejšie, správu budeme ukladať do privátnej
vlastnosti zprava
a urobíme metódy pre jej uloženie a vrátenie.
Samozrejme by sme mohli urobiť vlastnosť verejnú, ale nie je tu dôvod,
prečo umožniť zvonku zápis do správy a tiež by skladanie zložitejšie
správy vnútri triedy mohlo byť niekedy problematické.
K vlastnostiam triedy teda pridáme:
private var zprava : String = ""
Teraz si vytvoríme dve metódy. Privátne nastavZpravu()
,
ktorá berie ako parameter text správy a slúži na interné účely triedy,
kde nastaví správu do privátnej vlastnosti:
private func nastavZpravu(_ zprava: String) { self.zprava = zprava }
Nič zložité. Podobne jednoduchá bude verejná metóda pre navrátenie správy:
func vratPosledniZpravu() -> String { return zprava }
O prácu so správami obohatíme naše metódy utoc()
a
branSe()
, teraz budú vyzerať takto:
func utoc(souper: Bojovnik) { let uder = utok + kostka.hod() nastavZpravu("\(jmeno) útočí s úderem za \(uder) hp") souper.branSe(uder: uder) } func branSe(uder: Int) { let zraneni = Double(uder - (obrana + kostka.hod())) var zprava = "" if (zraneni > 0) { zivot -= zraneni zprava = "\(jmeno) utrpěl poškození \(Int(zraneni)) hp" if (zivot <= 0) { zivot = 0 } } else { zprava = "\(jmeno) odrazil útok" } nastavZpravu(zprava) }
Všetko si opäť vyskúšame, tentoraz už vytvoríme druhého bojovníka:
{SWIFT} let kostka = Kostka(pocetSten: 10) let bojovnik = Bojovnik(jmeno: "Zalgoren", zivot: 100, utok: 20, obrana: 10, kostka: kostka) print("Život: \(bojovnik.grafickyZivot())") // test grafickyZivot() // útok na našeho bojovníka let souper = Bojovnik(jmeno: "Shadow", zivot: 60, utok: 18, obrana: 15, kostka: kostka) souper.utoc(souper: bojovnik) print(souper.vratPosledniZpravu()) print(bojovnik.vratPosledniZpravu()) print("Život: \(bojovnik.grafickyZivot())") {/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}
{SWIFT} class Bojovnik: CustomStringConvertible { private var jmeno : String private var zivot : Double private var maxZivot : Double private var utok : Int private var obrana : Int private var kostka : Kostka private var zprava : String = "" init(jmeno: String, zivot: Int, utok: Int, obrana: Int, kostka: Kostka) { self.jmeno = jmeno self.zivot = Double(zivot) self.maxZivot = self.zivot self.utok = utok self.obrana = obrana self.kostka = kostka } var description: String { return jmeno } func nazivu() -> Bool { return zivot > 0 } func grafickyZivot() -> String { var s = "[" let celkem : Double = 20 var pocet : Double = round((zivot / maxZivot) * celkem) if (pocet == 0) && (nazivu()) { pocet = 1 } for _ in 0..<Int(pocet) { s += "#" } s = s.padding(toLength: Int(celkem) + 1, withPad: " ", startingAt: 0) s += "]" return s } func utoc(souper: Bojovnik) { let uder = utok + kostka.hod() nastavZpravu("\(jmeno) útočí s úderem za \(uder) hp") souper.branSe(uder: uder) } func branSe(uder: Int) { let zraneni = Double(uder - (obrana + kostka.hod())) var zprava = "" if (zraneni > 0) { zivot -= zraneni zprava = "\(jmeno) utrpěl poškození \(Int(zraneni)) hp" if (zivot <= 0) { zivot = 0 } } else { zprava = "\(jmeno) odrazil útok" } nastavZpravu(zprava) } private func nastavZpravu(_ zprava: String) { self.zprava = zprava } func vratPosledniZpravu() -> String { return zprava } } {/SWIFT}
Život: [####################] Shadow útočí s úderem za 24 hp Zalgoren utrpěl poškození 10 hp Život: [################## ]
Máme kocku i bojovníka, teraz už chýba len aréna. Tú si vytvoríme hneď v nasledujúcej lekcii, Aréna s bojovníkmi vo Swift .
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkamiStiahnuté 9x (23.37 kB)