6. diel - VB.NET - Aréna s bojovníkmi
V minulej lekcii, Bojovník do arény , sme si vytvorili triedu bojovníka. Hracie kocku máme hotovú z prvých lekcií objektovo orientovaného programovania. Dnes vo VB.NET tutoriálu 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 Module1.vb, ale vytvoríme si objekt Arena, kde sa bude zápas odohrávať. Module1.vb potom len založia objekty ao zvyšok sa bude starať objekt Arena. Pridajme k projektu teda posledný triedu a to Arena.vb.
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 bojovnik1 As Bojovnik Private bojovnik2 As Bojovnik Private kostka As Kostka Public Sub New(bojovnik1 As Bojovnik, bojovnik2 As Bojovnik, kostka As Kostka) Me.bojovnik1 = bojovnik1 Me.bojovnik2 = bojovnik2 Me.kostka = kostka End Sub End Class
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 Sub Vykresli() Console.Clear() Console.WriteLine("-------------- Aréna -------------- " & vbCrLf) Console.WriteLine("Zdraví bojovníků: " & vbCrLf) Console.WriteLine("{0} {1}", bojovnik1, bojovnik1.GrafickyZivot()) Console.WriteLine("{0} {1}", bojovnik2, bojovnik2.GrafickyZivot()) End Sub
Tu asi nie je čo riešiť, môžete si ešte obrazovku vyzdobiť farebne, keď budete chcieť. Vďaka zmazanie konzole pomocou Clear () docielime pekné informačné obrazovky namiesto klasického konzolového texte, kde sa plná obrazovka roluje dole. 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 Sub VypisZpravu(zprava As String) Console.WriteLine(zprava) Thread.Sleep(500) End Sub
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 seriálu. Aby všetko fungovalo, musíme pridať Imports System.Threading na začiatok súboru Arena.vb.
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:
Public Sub Zapas() Console.WriteLine("Vítejte v aréně!") Console.WriteLine("Dnes se utkají {0} s {1}! " & vbCrLf, bojovnik1, bojovnik2) Console.WriteLine("Zápas může začít...") Console.ReadKey() ' cyklus s bojem While bojovnik1.Nazivu() And 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ě Console.WriteLine() End While End Sub
Kód vypíše jednoduché informácie a po stlačení klávesy 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 Module1.vb, vytvorme patričné inštancie a zavolajte na aréne metódu Zapas ():
{VBNET_CONSOLE} ' vytvoření objektů Dim kostka As New Kostka(10) Dim zalgoren As New Bojovnik("Zalgoren", 100, 20, 10, kostka) Dim shadow As New Bojovnik("Shadow", 60, 18, 15, kostka) Dim arena As New Arena(zalgoren, shadow, kostka) ' zápas arena.Zapas() Console.ReadKey() {/VBNET_CONSOLE}
Imports System.Threading Public Class Kostka Private random As Random Private pocetSten As Integer Public Sub New() pocetSten = 6 random = New Random() End Sub Public Sub New(PocetSten As Integer) Me.pocetSten = PocetSten random = New Random() End Sub Public Function Hod() As Integer Return random.Next(1, pocetSten + 1) End Function Public Function VratPocetSten() As Integer Return pocetSten End Function Public Overrides Function ToString() As String Return String.Format("Kostka s {0} stěnami", pocetSten) End Function End Class
Class Bojovnik Private jmeno As String Private zivot As Integer Private maxZivot As Integer Private utok As Integer Private obrana As Integer Private kostka As Kostka Private zprava As String Public Sub New(jmeno As String, zivot As Integer, utok As Integer, obrana As Integer, kostka As Kostka) Me.jmeno = jmeno Me.zivot = zivot Me.maxZivot = zivot Me.utok = utok Me.obrana = obrana Me.kostka = kostka End Sub Public Function Nazivu() As Boolean Return (zivot > 0) End Function Public Function GrafickyZivot() As String Dim s As String = "[" Dim celkem As Integer = 20 Dim pocet As Double = Math.Round((zivot / maxZivot) * celkem) If (pocet = 0) AndAlso (Nazivu()) Then pocet = 1 End If For i As Integer = 0 To pocet - 1 s += "#" Next s = s.PadRight(celkem + 1) s += "]" Return s End Function Public Sub Utoc(souper As Bojovnik) Dim uder As Integer = utok + kostka.hod() NastavZpravu(String.Format("{0} útočí s úderem za {1} hp", jmeno, uder)) souper.BranSe(uder) End Sub Public Sub BranSe(uder As Integer) Dim zraneni As Integer = uder - (obrana + kostka.hod()) If zraneni > 0 Then zivot -= zraneni Dim zprava = String.Format("{0} utrpěl poškození {1} hp", jmeno, zraneni) If zivot <= 0 Then zivot = 0 zprava &= " a zemřel" Else zprava = String.Format("{0} odrazil útok", jmeno) End If NastavZpravu(zprava) End If End Sub Private Sub NastavZpravu(zprava As String) Me.zprava = zprava End Sub Public Function VratPosledniZpravu() As String Return Me.zprava End Function Public Overrides Function ToString() As String Return jmeno End Function End Class
Class Arena Private bojovnik1 As Bojovnik Private bojovnik2 As Bojovnik Private kostka As Kostka Public Sub New(bojovnik1 As Bojovnik, bojovnik2 As Bojovnik, kostka As Kostka) Me.bojovnik1 = bojovnik1 Me.bojovnik2 = bojovnik2 Me.kostka = kostka End Sub Private Sub Vykresli() Console.Clear() Console.WriteLine("-------------- Aréna -------------- " & vbCrLf) Console.WriteLine("Zdraví bojovníků: " & vbCrLf) Console.WriteLine("{0} {1}", bojovnik1, bojovnik1.GrafickyZivot()) Console.WriteLine("{0} {1}", bojovnik2, bojovnik2.GrafickyZivot()) End Sub Private Sub VypisZpravu(zprava As String) Console.WriteLine(zprava) Thread.Sleep(500) End Sub Public Sub Zapas() Console.WriteLine("Vítejte v aréně!") Console.WriteLine("Dnes se utkají {0} s {1}! " & vbCrLf, bojovnik1, bojovnik2) Console.WriteLine("Zápas může začít...") Console.ReadKey() ' cyklus s bojem While bojovnik1.Nazivu() And 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ě Console.WriteLine() End While End Sub End Class
Charakteristiky hrdinov si môžete upraviť podľa ľubovôle. Program spustíme:
Konzolová aplikácia
-------------- 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 screenshot 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 vo VB.NET 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.
Zmenená verzie vrátane podmienky, aby nemohol útočiť mŕtvy bojovník, by mohla vyzerať nejako takto:
Class Arena Private bojovnik1 As Bojovnik Private bojovnik2 As Bojovnik Private kostka As Kostka Public Sub New(bojovnik1 As Bojovnik, bojovnik2 As Bojovnik, kostka As Kostka) Me.bojovnik1 = bojovnik1 Me.bojovnik2 = bojovnik2 Me.kostka = kostka End Sub Private Sub Vykresli() Console.Clear() Console.WriteLine("-------------- Aréna -------------- " & vbCrLf) Console.WriteLine("Zdraví bojovníků: " & vbCrLf) Console.WriteLine("{0} {1}", bojovnik1, bojovnik1.GrafickyZivot()) Console.WriteLine("{0} {1}", bojovnik2, bojovnik2.GrafickyZivot()) End Sub Private Sub VypisZpravu(zprava As String) Console.WriteLine(zprava) Thread.Sleep(500) End Sub Public Sub Zapas() Console.WriteLine("Vítejte v aréně!") Console.WriteLine("Dnes se utkají {0} s {1}! " & vbCrLf, bojovnik1, bojovnik2) Console.WriteLine("Zápas může začít...") Console.ReadKey() ' cyklus s bojem While bojovnik1.Nazivu() And 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ě Console.WriteLine() End While End Sub End Class
Imports System.Threading Public Class Kostka Private random As Random Private pocetSten As Integer Public Sub New() pocetSten = 6 random = New Random() End Sub Public Sub New(PocetSten As Integer) Me.pocetSten = PocetSten random = New Random() End Sub Public Function Hod() As Integer Return random.Next(1, pocetSten + 1) End Function Public Function VratPocetSten() As Integer Return pocetSten End Function Public Overrides Function ToString() As String Return String.Format("Kostka s {0} stěnami", pocetSten) End Function End Class
Class Bojovnik Private jmeno As String Private zivot As Integer Private maxZivot As Integer Private utok As Integer Private obrana As Integer Private kostka As Kostka Private zprava As String Public Sub New(jmeno As String, zivot As Integer, utok As Integer, obrana As Integer, kostka As Kostka) Me.jmeno = jmeno Me.zivot = zivot Me.maxZivot = zivot Me.utok = utok Me.obrana = obrana Me.kostka = kostka End Sub Public Function Nazivu() As Boolean Return (zivot > 0) End Function Public Function GrafickyZivot() As String Dim s As String = "[" Dim celkem As Integer = 20 Dim pocet As Double = Math.Round((zivot / maxZivot) * celkem) If (pocet = 0) AndAlso (Nazivu()) Then pocet = 1 End If For i As Integer = 0 To pocet - 1 s += "#" Next s = s.PadRight(celkem + 1) s += "]" Return s End Function Public Sub Utoc(souper As Bojovnik) Dim uder As Integer = utok + kostka.hod() NastavZpravu(String.Format("{0} útočí s úderem za {1} hp", jmeno, uder)) souper.BranSe(uder) End Sub Public Sub BranSe(uder As Integer) Dim zraneni As Integer = uder - (obrana + kostka.hod()) If zraneni > 0 Then zivot -= zraneni Dim zprava = String.Format("{0} utrpěl poškození {1} hp", jmeno, zraneni) If zivot <= 0 Then zivot = 0 zprava &= " a zemřel" Else zprava = String.Format("{0} odrazil útok", jmeno) End If NastavZpravu(zprava) End If End Sub Private Sub NastavZpravu(zprava As String) Me.zprava = zprava End Sub Public Function VratPosledniZpravu() As String Return Me.zprava End Function Public Overrides Function ToString() As String Return jmeno End Function End Class
{VBNET_CONSOLE} ' vytvoření objektů Dim kostka As New Kostka(10) Dim zalgoren As New Bojovnik("Zalgoren", 100, 20, 10, kostka) Dim shadow As New Bojovnik("Shadow", 60, 18, 15, kostka) Dim arena As New Arena(zalgoren, shadow, kostka) ' zápas arena.Zapas() Console.ReadKey() {/VBNET_CONSOLE}
Program vyskúšajme.
Konzolová aplikácia
-------------- 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 , 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.