6. diel - Aréna s bojovníkmi v Kotlin
V minulej lekcii, Bojovník do arény v Kotlin , 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 funkčné 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 súboru s metódou
main()
, ale vytvoríme si objekt Arena
, kde sa bude
zápas odohrávať. V main()
si potom len založíme objekty ao
zvyšok sa bude starať objekt Arena
. Pridajme k projektu teda
posledný triedu a to Arena.kt
Trieda bude viac-menej jednoduchá, ako atribúty bude obsahovať 3 potrebné inštancie: 2 bojovníkmi a hraciu kocku. V konstruktoru sa tieto atribúty naplnia z parametrov. Kód triedy bude teda nasledujúce (komentáre si listy):
class Arena(private val bojovnik1: Bojovnik, private val bojovnik2: Bojovnik, val 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:
private fun vykresli() { println("-------------- Aréna --------------\n") println("Zdraví bojovníků: \n") println("$bojovnik1 ${bojovnik1.grafickyZivot()}") println("$bojovnik2 ${bojovnik2.grafickyZivot()}") }
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 fun vypisZpravu(zprava: String) { println(zprava) Thread.sleep(500) }
Kód je zrejmý až na triedu Thread
, ktorá umožňuje prácu s
vláknami. My z nej využijeme iba metódu sleep()
, ktorá uspí
vlákno programu na daný počet milisekúnd. S vláknami budeme pracovať až
na konci kurzu.
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:
fun zapas() { println("Vítejte v aréně!") println("Dnes se utkají $bojovnik1 s $bojovnik2 \n") println("Zápas může začít...") // cyklus s bojem while (bojovnik1.nazivu() && bojovnik2.nazivu()) { bojovnik1.utoc(bojovnik2) vykresli() vypisZpravu(bojovnik1.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(bojovnik2.vratPosledniZpravu()) // zpráva o obraně bojovnik2.utoc(bojovnik1) vykresli() vypisZpravu(bojovnik2.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(bojovnik1.vratPosledniZpravu()) // zpráva o obraně println() } }
Kód vypíše jednoduché informácie a potom 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.kt
, vytvorme patričné inštancie a
zavolajte na aréne metódu zapas()
:
{KOTLIN_OOP} {KOTLIN_MAIN_BLOCK} // vytvoření objektů val kostka = Kostka(10) val zalgoren = Bojovnik("Zalgoren", 100, 20, 10, kostka) val shadow = Bojovnik("Shadow", 60, 18, 15, kostka) val arena = Arena(zalgoren, shadow, kostka) // zápas arena.zapas() {/KOTLIN_MAIN_BLOCK} {/KOTLIN_OOP}
{KOTLIN_OOP} class Kostka(pocetSten: Int) { val pocetSten: Int constructor() : this(6) init { this.pocetSten = pocetSten } fun hod(): Int { return (1..pocetSten).shuffled().first() } override fun toString(): String { return "Kostka s $pocetSten stěnami" } } {/KOTLIN_OOP}
{KOTLIN_OOP} import kotlin.math.* class Bojovnik(private val jmeno: String, private var zivot: Int, private val utok: Int, private val obrana: Int, private val kostka: Kostka) { private val maxZivot = zivot private var zprava = "" private fun nastavZpravu(zprava: String) { this.zprava = zprava } fun vratPosledniZpravu(): String { return zprava } fun nazivu(): Boolean { return (zivot > 0) } fun grafickyZivot(): String { var s = "[" val celkem = 20 var pocet = round((zivot.toDouble()/maxZivot) * celkem).toInt() if ((pocet == 0) && (nazivu())) pocet = 1 s = s.padEnd(pocet + s.length, '#') s = s.padEnd(celkem - pocet + s.length, ' ') s += "]" return s } fun utoc(souper: Bojovnik) { val uder = utok + kostka.hod() nastavZpravu("$jmeno útočí s úderem za $uder hp") souper.branSe(uder) } fun branSe(uder: Int) { val zraneni = uder - (obrana + kostka.hod()) if (zraneni > 0) { zivot -= zraneni zprava = "$jmeno utrpěl poškození $zraneni hp" if (zivot <= 0) { zivot = 0 zprava += " a zemřel" } } else nastavZpravu("$jmeno odrazil útok") nastavZpravu(zprava) } override fun toString(): String { return jmeno } } {/KOTLIN_OOP}
{KOTLIN_OOP} class Arena(private val bojovnik1: Bojovnik, private val bojovnik2: Bojovnik, val kostka: Kostka) { private fun vykresli() { println("-------------- Aréna --------------\n") println("Zdraví bojovníků: \n") println("$bojovnik1 ${bojovnik1.grafickyZivot()}") println("$bojovnik2 ${bojovnik2.grafickyZivot()}") } private fun vypisZpravu(zprava: String) { println(zprava) Thread.sleep(500) } fun zapas() { println("Vítejte v aréně!") println("Dnes se utkají $bojovnik1 s $bojovnik2! \n") println("Zápas může začít...") // cyklus s bojem while (bojovnik1.nazivu() && bojovnik2.nazivu()) { bojovnik1.utoc(bojovnik2) vykresli() vypisZpravu(bojovnik1.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(bojovnik2.vratPosledniZpravu()) // zpráva o obraně bojovnik2.utoc(bojovnik1) vykresli() vypisZpravu(bojovnik2.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(bojovnik1.vratPosledniZpravu()) // zpráva o obraně println() } } } {/KOTLIN_OOP}
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ýstup 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 5tich, 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 v Kotlinu 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
bojovnik2
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
.
Zmenená verzie vrátane podmienky, aby nemohol útočiť mŕtvy bojovník, by mohla vyzerať nejako takto:
{KOTLIN_OOP} class Arena(private val bojovnik1: Bojovnik, private val bojovnik2: Bojovnik, val kostka: Kostka) { private fun vykresli() { println("-------------- Aréna --------------\n") println("Zdraví bojovníků: \n") println("$bojovnik1 ${bojovnik1.grafickyZivot()}") println("$bojovnik2 ${bojovnik2.grafickyZivot()}") } private fun vypisZpravu(zprava: String) { println(zprava) Thread.sleep(500) } fun zapas() { // původní pořadí var b1 = bojovnik1 var b2 = bojovnik2 println("Vítejte v aréně!") println("Dnes se utkají $bojovnik1 s $bojovnik2! \n") // prohození bojovníků val zacinaBojovnik2 = kostka.hod() <= kostka.pocetSten / 2 if (zacinaBojovnik2) { b1 = bojovnik2 b2 = bojovnik1 } println("Začínat bude bojovník $b1! \n\nZápas může začít...") // cyklus s bojem while (b1.nazivu() && b2.nazivu()) { b1.utoc(b2) vykresli() vypisZpravu(b1.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(b2.vratPosledniZpravu()) // zpráva o obraně if (b2.nazivu()) { b2.utoc(b1) vykresli() vypisZpravu(b2.vratPosledniZpravu()) // zpráva o útoku vypisZpravu(b1.vratPosledniZpravu()) // zpráva o obraně } System.out.println() } } } {/KOTLIN_OOP}
{KOTLIN_OOP} {KOTLIN_MAIN_BLOCK} // vytvoření objektů val kostka = Kostka(10) val zalgoren = Bojovnik("Zalgoren", 100, 20, 10, kostka) val shadow = Bojovnik("Shadow", 60, 18, 15, kostka) val arena = Arena(zalgoren, shadow, kostka) // zápas arena.zapas() {/KOTLIN_MAIN_BLOCK} {/KOTLIN_OOP}
{KOTLIN_OOP} class Kostka(pocetSten: Int) { val pocetSten: Int constructor() : this(6) init { this.pocetSten = pocetSten } fun hod(): Int { return (1..pocetSten).shuffled().first() } override fun toString(): String { return "Kostka s $pocetSten stěnami" } } {/KOTLIN_OOP}
{KOTLIN_OOP} import kotlin.math.* class Bojovnik(private val jmeno: String, private var zivot: Int, private val utok: Int, private val obrana: Int, private val kostka: Kostka) { private val maxZivot = zivot private var zprava = "" private fun nastavZpravu(zprava: String) { this.zprava = zprava } fun vratPosledniZpravu(): String { return zprava } fun nazivu(): Boolean { return (zivot > 0) } fun grafickyZivot(): String { var s = "[" val celkem = 20 var pocet = round((zivot.toDouble()/maxZivot) * celkem).toInt() if ((pocet == 0) && (nazivu())) pocet = 1 s = s.padEnd(pocet + s.length, '#') s = s.padEnd(celkem - pocet + s.length, ' ') s += "]" return s } fun utoc(souper: Bojovnik) { val uder = utok + kostka.hod() nastavZpravu("$jmeno útočí s úderem za $uder hp") souper.branSe(uder) } fun branSe(uder: Int) { val zraneni = uder - (obrana + kostka.hod()) if (zraneni > 0) { zivot -= zraneni zprava = "$jmeno utrpěl poškození $zraneni hp" if (zivot <= 0) { zivot = 0 zprava += " a zemřel" } } else nastavZpravu("$jmeno odrazil útok") nastavZpravu(zprava) } override fun toString(): String { return jmeno } } {/KOTLIN_OOP}
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 v Kotlin , 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.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 19x (40.03 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Kotlin