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

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

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

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:

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

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 podmienkami

Stiahnuté 9x (25.59 kB)

 

Predchádzajúci článok
Bojovník do arény vo Swift
Všetky články v sekcii
Objektovo orientované programovanie vo Swift
Preskočiť článok
(neodporúčame)
Dedičnosť a polymorfizmus 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