IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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:

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()
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
    }
}
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
    }

}

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:

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())")
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
    }
}
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)
    }

}

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:

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())")
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
    }
}
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
    }
}
Ž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 podmienkami

Stiahnuté 9x (23.37 kB)

 

Predchádzajúci článok
Riešené úlohy k 4. lekcii OOP vo Swift
Všetky články v sekcii
Objektovo orientované programovanie vo Swift
Preskočiť článok
(neodporúčame)
Aréna s bojovníkmi vo Swift
Článok pre vás napísal Filip Němeček
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje vývoji iOS aplikací (občas macOS)
Aktivity