Lekce 5 - Bojovník do arény ve Swift
V předešlém cvičení, Řešené úlohy k 4. lekci OOP ve Swift, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
Tento a příští Swift tutoriál budou věnovány dokončení naší arény. Hrací kostku již máme, ještě nám chybí další 2 objekty: bojovník a samotná aréna. Dnes se budeme věnovat bojovníkovi. Nejprve si popišme, co má bojovník umět, poté se pustíme do psaní kódu.
Vlastnosti
Bojovník se bude nějak jmenovat a bude mít určitý
počet hp (tedy života, např. 80hp). Budeme uchovávat jeho
maximální život (bude se lišit u každé instance) a jeho
současný život, tedy např. zraněný bojovník bude mít
40hp z 80. Bojovník má určitý útok a
obranu, obojí vyjádřené opět v hp. Když bojovník
útočí s útokem 20hp na druhého bojovníka s obranou 10hp, ubere mu 10hp
života. Bojovník bude mít referenci na instanci třídy
Kostka
. Při útoku či obraně si vždy hodí kostkou a k
útoku/obraně přičte padlé číslo. (Samozřejmě by mohl mít každý
bojovník svou kostku, ale chtěl jsem se přiblížit stolní podobě hry a
ukázat, jak OOP opravdu simuluje realitu. Bojovníci tedy budou sdílet jednu
instanci kostky.) Kostkou dodáme hře prvek náhody, v realitě se jedná
vlastně o štěstí, jak se útok nebo obrana vydaří. Konečně budeme
chtít, aby bojovníci podávali zprávy o tom, co se děje,
protože jinak by z toho uživatel nic neměl. Zpráva bude vypadat např.
"Zalgoren útočí s úderem za 25hp.". Zprávami se zatím nebudeme zatěžovat
a vrátíme se k nim až nakonec.
Již víme, co budeme dělat, pojďme na to! K projektu Arena
si
přidejme třídu Bojovnik
a dodejme ji patřičné vlastnosti.
Všechny budou privátní:
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 }
Třída Kostka
musí samozřejmě být v našem projektu.
Metody
Pojďme pro vlastnosti vytvořit konstruktor, aby nám fungoval build, nebude to nic těžkého.
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šimněte si, že maximální zdraví si v konstruktoru odvodíme a nemáme
na něj parametr v hlavičce metody. Předpokládáme, že bojovník je při
vytvoření plně zdravý, stačí nám tedy znát pouze jeho život a
maximální život bude stejný. Proměnné zivot
a
maxZivot
si sice v konstruktoru vyžádáme jako Int
,
ale interně převedeme na Double
, abychom měli později snazší
dělení.
Přejděme k metodám, opět se nejprve zamysleme nad tím, co by měl
bojovník umět. Začněme tím jednodušším, budeme chtít nějakou textovou
reprezentaci, abychom mohli bojovníka vypsat. Vytvoříme si tedy vlastnost
description
, kterou již známe (nezapomeňte za třídu přidat
: CustomStringConvertible
, jako u třídy Kostka
),
která vrátí jméno bojovníka. Určitě se nám bude hodit metoda,
vracející zda je bojovník naživu (tedy typu Bool
). Aby to bylo
trochu zajímavější, budeme chtít kreslit život bojovníka do konzole,
nebudeme tedy psát, kolik má života, ale "vykreslíme" ho takto:
[######### ]
Výše uvedený život by odpovídal asi 70 %. Dosud zmíněné metody
nepotřebovaly žádné parametry. Samotný útok a obranu nechme na později a
pojďme si implementovat description
, nazivu()
a
grafickyZivot()
. Začněme s description
, tam není co
vymýšlet:
var description: String { return jmeno }
Nyní implementujme metodu nazivu()
, opět to nebude nic
těžkého. Stačí zkontrolovat, zda je život větší než 0
a
podle toho se zachovat. Mohli bychom ji napsat třeba takto:
func nazivu() -> Bool { if zivot > 0 { return true } else { return false } }
Jelikož i samotný výraz zivot > 0
je vlastně logická
hodnota, můžeme vrátit tu a kód se značně zjednoduší:
func nazivu() -> Bool { return zivot > 0 }
Grafický život
Jak jsem se již zmínil, metoda grafickyZivot()
bude
umožňovat vykreslit ukazatel života v grafické podobě. Již víme, že z
hlediska objektového návrhu není vhodné, aby metoda objektu přímo
vypisovala do konzole (pokud není k výpisu objekt určený), proto si znaky
uložíme do řetězce a ten vrátíme pro pozdější vypsání. Ukážeme si
kód metody a následně podrobně 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 }
Připravíme si řetězec s
a vložíme do něj úvodní znak
[
. Určíme si celkovou délku ukazatele života do proměnné
celkem
(např. 20
). Nyní v podstatě nepotřebujeme
nic jiného, než trojčlenku. Pokud maxZivot
odpovídá
celkem
dílků, zivot
bude odpovídat
pocet
dílkům. Proměnná pocet
obsahuje počet
dílků aktuálního zdraví.
Matematicky platí, že
pocet = (zivot / maxZivot) * celkem
. My ještě
doplníme zaokrouhlení na celé dílky.
Měli bychom ošetřit případ, kdy je život tak nízký, že nám vyjde na
0
dílků, ale bojovník je stále naživu. V tom případě
vykreslíme 1 dílek, jinak by to vypadalo, že je již mrtvý.
Dále stačí jednoduše for
cyklem připojit k řetězci
s
patřičný počet znaků a doplnit je mezerami do celkové
délky. Doplnění mezerami provedeme pomocí padding()
na délku
celkem + 1
(a musíme převést zpět na typ Int
), kde
ten znak navíc je úvodní znak [
. Přidáme koncový znak a
řetězec vrátíme.
Tímto jsme se dostali k metodě padding()
, kterou jsme v
základním kurzu vynechali. Swift má totiž pouze tuto jednu metodu, která
funguje celkem jednoduše, ale umí doplnit znaky pouze z pravé strany. Pokud
bychom chtěli doplňovat znaky z levé strany, tak si musíme napsat vlastní
logiku.
Vše si vyzkoušíme, přejděme do main.swift
a vytvořme si
bojovníka (a kostku, protože tu musíme konstruktoru bojovníka předat).
Následně vypišme, zda je naživu 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áváme se k samotnému boji. Implementujeme metody pro útok a obranu.
Obrana
Začněme obranou. Metoda branSe()
bude umožňovat bránit se
úderu, jehož síla bude předána metodě jako parametr. Metodu si opět
ukážeme a poté popíšeme:
func branSe(uder: Int) { let zraneni = Double(uder - (obrana + kostka.hod())) if (zraneni > 0) { zivot -= zraneni if (zivot <= 0) { zivot = 0 } } }
Nejprve spočítáme skutečné zranění a to tak, že z útoku nepřítele
odečteme naši obranu zvýšenou o číslo, které padlo na hrací kostce.
Pokud jsme zranění celé neodrazili (zraneni > 0
), budeme
snižovat náš život. Tato podmínka je důležitá, kdybychom zranění
odrazili a bylo např. -2, bez podmínky by se život zvýšil. Po snížení
života zkontrolujeme, zda není v záporné hodnotě a případně ho
dorovnáme na nulu.
Útok
Metoda utoc()
bude brát jako parametr instanci bojovníka, na
kterého se útočí. To proto, abychom na něm mohli zavolat metodu
branSe()
, která na náš útok zareaguje a zmenší protivníkův
život. Zde vidíme výhody referencí ve Swift, můžeme si instance jednoduše
předávat a volat na nich metody, aniž by došlo k jejich zkopírování. Jako
první vypočteme úder, podobně jako při obraně, úder bude náš útok +
hodnota z hrací kostky. Na soupeři následně zavoláme metodu
branSe()
s hodnotou úderu:
func utoc(souper: Bojovnik) { let uder = utok + kostka.hod() souper.branSe(uder: uder) }
To bychom měli, pojďme si zkusit v našem ukázkovém programu zaútočit a poté znovu vykreslit život. Pro jednoduchost nemusíme zakládat dalšího bojovníka, ale můžeme zaútočit sami na sebe:
{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á se, že vše funguje, jak má. Přejděme k poslednímu bodu dnešního tutoriálu a to ke zprávám.
Zprávy
Jak již bylo řečeno, o útocích a obraně budeme uživatele informovat
výpisem na konzoli. Výpis nebude provádět samotná třída
Bojovnik
, ta bude jen vracet zprávy jako textové řetězce. Jedna
možnost by byla nastavit návratový typ metod utoc()
a
branSe()
na String
a při jejich volání vrátit i
zprávu. Problém by však nastal v případě, když bychom chtěli získat
zprávu od metody, která již něco vrací. Metoda samozřejmě nemůže
jednoduše vrátit 2 věci.
Pojďme na věc univerzálněji, zprávu budeme ukládat do privátní
vlastnosti zprava
a uděláme metody pro její uložení a
navrácení. Samozřejmě bychom mohli udělat vlastnost veřejnou, ale není
zde důvod, proč umožnit zvenčí zápis do zprávy a také by skládání
složitější zprávy uvnitř třídy mohlo být někdy problematické.
K vlastnostem třídy tedy přidáme:
private var zprava : String = ""
Nyní si vytvoříme dvě metody. Privátní nastavZpravu()
,
která bere jako parametr text zprávy a slouží pro vnitřní účely třídy,
kde nastaví zprávu do privátní vlastnosti:
private func nastavZpravu(_ zprava: String) { self.zprava = zprava }
Nic složitého. Podobně jednoduchá bude veřejná metoda pro navrácení zprávy:
func vratPosledniZpravu() -> String { return zprava }
O práci se zprávami obohatíme naše metody utoc()
a
branSe()
, nyní budou vypadat 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še si opět vyzkoušíme, tentokrát již vytvoří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 kostku i bojovníka, teď již chybí jen aréna.
Arénu si vytvoříme hned v příští lekci, Aréna s bojovníky ve Swift.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 9x (23.37 kB)
Aplikace je včetně zdrojových kódů v jazyce Swift