Zarábaj až 6 000 € mesačne! Akreditované rekvalifikačné kurzy od 0 €. Viac informácií.

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

Klikni pre editáciu
  • App
    • main.swift
    • Kostka.swift
    • Bojovnik.swift
    • Arena.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()
    
  • 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(" ")
            }
        }
    }
    
    
    • Skontroluj, či výstupy programu zodpovedajú predlohe. S inými textami testy neprejdú.

    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:

    Klikni pre editáciu
    • App
      • Arena.swift
      • main.swift
      • Kostka.swift
      • Bojovnik.swift
    •     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
          }
      }
      
      • Skontroluj, či výstupy programu zodpovedajú predlohe. S inými textami testy neprejdú.

      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)

       

      Ako sa ti páči článok?
      Pred uložením hodnotenia, popíš prosím autorovi, čo je zleZnakov 0 z 50-500
      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