6. diel - Aréna s bojovníkmi vo Swift
V minulej lekcii, Bojovník do arény vo Swift , sme si vytvorili triedu bojovníka. Hracie kocku máme hotovú z prvých lekcií objektovo orientovaného programovania. Dnes teda dáme všetko dokopy a vytvoríme vo Swift funkčnej arénu. Tutoriál bude skôr oddychový a pomôže nám zopakovať si prácu s objektmi.
Potrebujeme napísať nejaký kód pre obsluhu bojovníkov a výpis správ
užívateľmi. Samozrejme ho nebudeme búšiť rovno do main.swift
,
ale vytvoríme si objekt Arena
, kde sa bude zápas odohrávať.
Súbor main.swift
potom len založia objekty ao zvyšok sa bude
starať objekt Arena
. Pridajme k projektu teda posledný triedu a
to Arena.swift
.
Trieda bude viac-menej jednoduchá, ako vlastnosti bude obsahovať 3 potrebné inštancie: 2 bojovníkmi a hraciu kocku. V konstruktoru sa tieto vlastnosti naplnia z parametrov. Kód triedy bude teda nasledujúce:
class Arena { private var bojovnik1 : Bojovnik private var bojovnik2 : Bojovnik private var kostka : Kostka init(bojovnik1: Bojovnik, bojovnik2: Bojovnik, kostka: Kostka) { self.bojovnik1 = bojovnik1 self.bojovnik2 = bojovnik2 self.kostka = kostka } }
Zamyslime sa nad metódami. Z verejných metód bude určite potrebné len
tá na simuláciu zápasu. Výstup programu na konzolu urobíme trochu na
úrovni a tiež umožníme triede Arena
, aby priamo ku konzole
pristupovala. Rozhodli sme sa, že výpis bude v kompetencii triedy, keďže sa
nám to tu oplatí. Naopak keby výpis vykonávali aj bojovníci, bolo by to na
škodu (neboli by univerzálna). Potrebujeme teda metódu, ktorá vykreslí
obrazovku s aktuálnymi údajmi o bicykli a životy bojovníkov. Správy o
útoku a obrane budeme chcieť vypisovať s dramatickou pauzou, aby bol
výsledný efekt lepšie, urobíme si pre takýto typ správ ešte pomocnú
metódu. Začnime s vykreslením informačnej obrazovky:
func vykresli() { print("\n \n \n \n \n \n \n \n") print("-------------- Aréna -------------- \n") print("Zdraví bojovníků: \n") print("\(bojovnik1) \(bojovnik1.grafickyZivot())") print("\(bojovnik2) \(bojovnik2.grafickyZivot())") }
Tu asi nie je čo riešiť. Keďže nejde rozumne zmazať výstup, tak si všetko dostatočne odřádkujeme, aby sme videli vždy len aktuálny výstup. Metóda je privátne, budeme ju používať len vnútri triedy.
Ďalšie privátne metódou bude výpis správy s dramatickou pauzou:
private func vypisZpravu(_ zprava: String) { print(zprava) sleep(1) }
Kód je zrejmý až na metódu sleep()
, ktorá uspí vlákno
programu na daný počet sekúnd.
Obe metódy vlastne len vypisujú na konzolu, pripadá mi zbytočné je
skúšať, presunieme sa teda už k samotnému zápasu. Metóda
zapas()
nebude mať žiadne parametre a nebude ani nič vracať.
Vnútri bude cyklus, ktorý bude na striedačku volať útoky bojovníkov
navzájom a vypisovať informačnú obrazovku a správy. Metóda by mohla
vyzerať takto:
func zapas() { print("Vítejte v aréně!") print("Dnes se utkají \(bojovnik1) s \(bojovnik2)! \n") print("Zápas může začít...") readLine() // cyklus s bojem while bojovnik1.nazivu() && bojovnik2.nazivu() { bojovnik1.utoc(souper: bojovnik2) vykresli() vypisZpravu(bojovnik1.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(bojovnik2.vratPosledniZpravu()) // zpráva o obraně bojovnik2.utoc(souper: bojovnik1) vykresli() vypisZpravu(bojovnik2.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(bojovnik1.vratPosledniZpravu()) // zpráva o obraně print(" ") } }
Kód vypíše jednoduché informácie a po stlačení Enter (aby sme
potvrdili vstup pre readLine()
) prejde do cyklu s bojom. Jedná sa
o while
cyklus, ktorý sa opakuje, kým sú obaja bojovníci
nažive. Prvý bojovník zaútočí na druhého, jeho útok vnútorne zavolá na
druhom bojovníkovi obranu. Po útoku vykreslíme obrazovku s informáciami a
ďalej správy o útoku a obrane pomocou našej metódy
vypisZpravu()
, ktorá po výpise urobí dramatickú pauzu. To isté
vykonáme aj pre druhého bojovníka.
Presuňme sa do main.swift
, vytvorme patričné inštancie a
zavolajte na aréne metódu zapas()
:
{SWIFT} // vytvoření objektů let kostka = Kostka(pocetSten: 10) let zalgoren = Bojovnik(jmeno: "Zalgoren", zivot: 100, utok: 20, obrana: 10, kostka: kostka) let shadow = Bojovnik(jmeno: "Shadow", zivot: 60, utok: 18, obrana: 15, kostka: kostka) let arena = Arena(bojovnik1: zalgoren, bojovnik2: shadow, kostka: kostka) // zápas arena.zapas() {/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}
{SWIFT} class Arena { private var bojovnik1 : Bojovnik private var bojovnik2 : Bojovnik private var kostka : Kostka init(bojovnik1: Bojovnik, bojovnik2: Bojovnik, kostka: Kostka) { self.bojovnik1 = bojovnik1 self.bojovnik2 = bojovnik2 self.kostka = kostka } func vykresli() { print("\n \n \n \n \n \n \n \n") print("-------------- Aréna -------------- \n") print("Zdraví bojovníků: \n") print("\(bojovnik1) \(bojovnik1.grafickyZivot())") print("\(bojovnik2) \(bojovnik2.grafickyZivot())") } private func vypisZpravu(_ zprava: String) { print(zprava) sleep(1) } func zapas() { print("Vítejte v aréně!") print("Dnes se utkají \(bojovnik1) s \(bojovnik2)! \n") print("Zápas může začít...") readLine() // cyklus s bojem while bojovnik1.nazivu() && bojovnik2.nazivu() { bojovnik1.utoc(souper: bojovnik2) vykresli() vypisZpravu(bojovnik1.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(bojovnik2.vratPosledniZpravu()) // zpráva o obraně bojovnik2.utoc(souper: bojovnik1) vykresli() vypisZpravu(bojovnik2.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(bojovnik1.vratPosledniZpravu()) // zpráva o obraně print(" ") } } } {/SWIFT}
Charakteristiky hrdinov si môžete upraviť podľa ľubovôle. Program spustíme:
-------------- Aréna -------------- Zdraví bojovníků: Zalgoren [###### ] Shadow [ ] Shadow útočí úderem za 20 hp Zalgoren utrpěl poškození 4 hp
Výsledok je docela pôsobivý. Objekty spolu komunikujú, grafický život ubúda ako má, zážitok umocňuje dramatická pauza. Aréna má však 2 nedostatky.
- V cykle s bojom útočí prvý bojovník na druhého. Potom však vždy
útočia aj druhý bojovník, nehľadiac na to, či ho prvý nezabil. Môže
teda útočiť už ako mŕtvy. Pozrite sa na výpis vyššie, Shadow útočil
ako posledný, aj keď bol mŕtvy. Až potom sa vystúpilo z
while
cyklu. U prvého bojovníka tento problém nie je, u druhého musíme pred útokom kontrolovať, či je nažive. - Druhým nedostatkom je, že bojovníci vždy bojujú v rovnakom poradí, čiže tu "Zalgoren" má vždy výhodu. Poďme vniesť ďalší prvok náhody a pomocou kocky rozhodnime, ktorý z bojovníkov bude začínať. Keďže sú bojovníci vždy dvaja, stačí hodiť kockou a pozrieť sa, či padlo číslo menšie alebo rovné polovici počtu stien kocky. Teda napr. Ak padne na desetistěnné kocke číslo do 5, začína 2. bojovník, inak začína prvý.
Zostáva zamyslieť sa nad tým, ako do kódu zaniesť prehadzovania
bojovníkov. Iste by bolo veľmi neprehľadné opodmínkovat príkazy vo
while
cyklu. Keďže už vieme, že vo Swift fungujú referencie,
nie je pre nás problém urobiť si 2 premenné, v ktorých budú inštancie
bojovníkov, nazvime ich jednoducho b1
a b2
. Do
týchto premenných si na začiatku dosadíme bojovníkmi bojovnik1
a bojovnik2
tak, ako potrebujeme. Môžeme teda pri pozitívnom
hodu kockou dosadiť do b1
bojovník2
a naopak,
výsledkom bude, že začínať bude ten druhý. Kód cyklu sa takto vôbec
nezmení a zostane stále prehľadný a jednoduchý, len miesto
bojovnik
bude b
.
Ešte sa zbavíme varovanie u readLine()
. Metóda totiž vracia
String?
(Optional
), ale nás vrátená hodnota
nezaujíma. Preto ju priradíme do "_", čím sa práve táto situácia vo Swift
označuje. Výhodné to je aj pre nás či ďalšieho programátora, pretože
hneď vieme, že túto hodnotu ďalej nepotrebujeme a kód je tak
čitateľnejší.
Zmenená verzie vrátane podmienky, aby nemohol útočiť mŕtvy bojovník, by mohla vyzerať nejako takto:
{SWIFT} class Arena { private var bojovnik1 : Bojovnik private var bojovnik2 : Bojovnik private var kostka : Kostka init(bojovnik1: Bojovnik, bojovnik2: Bojovnik, kostka: Kostka) { self.bojovnik1 = bojovnik1 self.bojovnik2 = bojovnik2 self.kostka = kostka } func vykresli() { print("\n \n \n \n \n \n \n \n") print("-------------- Aréna -------------- \n") print("Zdraví bojovníků: \n") print("\(bojovnik1) \(bojovnik1.grafickyZivot())") print("\(bojovnik2) \(bojovnik2.grafickyZivot())") } private func vypisZpravu(_ zprava: String) { print(zprava) sleep(1) } func zapas() { // původní pořadí var b1 = bojovnik1 var b2 = bojovnik2 print("Vítejte v aréně!") print("Dnes se utkají \(bojovnik1) s \(bojovnik2)! \n") // prohození bojovníků let zacinaBojovnik2 = kostka.hod() <= kostka.vratPocetSten() / 2 if (zacinaBojovnik2) { b1 = bojovnik2 b2 = bojovnik1 } print("Začínat bude bojovník \(b1)! \nZápas může začít...") _ = readLine() // cyklus s bojem while b1.nazivu() && b2.nazivu() { b1.utoc(souper: b2) vykresli() vypisZpravu(b1.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(b2.vratPosledniZpravu()) // zpráva o obraně if (b2.nazivu()) { b2.utoc(souper: b1) vykresli() vypisZpravu(b2.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(b1.vratPosledniZpravu()) // zpráva o obraně } print(" ") } } } {/SWIFT}
{SWIFT} // vytvoření objektů let kostka = Kostka(pocetSten: 10) let zalgoren = Bojovnik(jmeno: "Zalgoren", zivot: 100, utok: 20, obrana: 10, kostka: kostka) let shadow = Bojovnik(jmeno: "Shadow", zivot: 60, utok: 18, obrana: 15, kostka: kostka) let arena = Arena(bojovnik1: zalgoren, bojovnik2: shadow, kostka: kostka) // zápas arena.zapas() {/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}
Program vyskúšajme.
-------------- Aréna -------------- Zdraví bojovníků: Zalgoren [######### ] Shadow [ ] Zalgoren útočí úderem za 27 hp Shadow utrpěl poškození 11 hp a zemřel
Vidíme, že je všetko už v poriadku. Gratulujem vám, ak ste sa dostali až sem a tutoriály naozaj čítali a pochopili, máte základy objektového programovania a dokážete tvoriť rozumné aplikácie
V budúcej lekcii, Dedičnosť a polymorfizmus vo Swift , sa pozrieme na objektovo orientované programovanie podrobnejšie. V úvode sme si hovorili, že OOP stojí na pilieroch: zapuzdrenie, dedičnosť a polymorfizmus. Prvý vieme už veľmi dobre a modifikátor private je nám známy. Ďalšie dva nás čakajú nabudúce.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkamiStiahnuté 9x (25.59 kB)