Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 6 - VB.NET - Aréna s bojovníky

V minulé lekci, Bojovník do arény, jsme si vytvořili třídu bojovníka. Hrací kostku máme hotovou z prvních lekcí objektově orientovaného programování.

Dnes ve VB.NET tutoriálu tedy dáme vše dohromady a vytvoříme funkční arénu. Tutoriál bude spíše oddechový a pomůže nám zopakovat si práci s objekty.

Potřebujeme napsat nějaký kód pro obsluhu bojovníků a výpis zpráv uživateli. Samozřejmě ho nebudeme bušit rovnou do Module1.vb, ale vytvoříme si objekt Arena, kde se bude zápas odehrávat. Module1.vb potom jen založí objekty a o zbytek se bude starat objekt Arena. Přidejme k projektu tedy poslední třídu a to Arena.vb.

Třída bude víceméně jednoduchá, jako atributy bude obsahovat 3 potřebné instance: 2 bojovníky a hrací kostku. V konstruktoru se tyto atributy naplní z parametrů. Kód třídy bude tedy následující (komentáře si dopiště):

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

Zamysleme se nad metodami. Z veřejných metod bude určitě potřeba jen ta k simulaci zápasu. Výstup programu na konzoli uděláme trochu na úrovni a také umožníme třídě Arena, aby přímo ke konzoli přistupovala. Rozhodli jsme se, že výpis bude v kompetenci třídy, jelikož se nám to zde vyplatí. Naopak kdyby výpis prováděli i bojovníci, bylo by to na škodu (nebyli by univerzální). Potřebujeme tedy metodu, která vykreslí obrazovku s aktuálními údaji o kole a životy bojovníků. Zprávy o útoku a obraně budeme chtít vypisovat s dramatickou pauzou, aby byl výsledný efekt lepší, uděláme si pro takový typ zprávy ještě pomocnou metodu. Začněme s vykreslením informační 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

Zde asi není co řešit, můžete si ještě obrazovku vyzdobit barevně, když budete chtít. Díky smazání konzole pomocí Clear() docílíme hezké informační obrazovky namísto klasického konzolového textu, kde se plná obrazovka roluje dolů. Metoda je privátní, budeme ji používat jen uvnitř třídy.

Další privátní metodou bude výpis zprávy s dramatickou pauzou:

Private Sub VypisZpravu(zprava As String)
    Console.WriteLine(zprava)
    Thread.Sleep(500)
End Sub

Kód je zřejmý až na třídu Thread, která umožňuje práci s vlákny. My z ní využijeme pouze metodu Sleep(), která uspí vlákno programu na daný počet milisekund. S vlákny budeme pracovat až na konci seriálu. Aby vše fungovalo, musíme přidat Imports System.Threading na začátek souboru Arena.vb.

Obě metody vlastně jen vypisují na konzoli, připadá mi zbytečné je zkoušet, přesuneme se tedy již k samotnému zápasu. Metoda Zapas() nebude mít žádné parametry a nebude ani nic vracet. Uvnitř bude cyklus, který bude na střídačku volat útoky bojovníků navzájem a vypisovat informační obrazovku a zprávy. Metoda by mohla vypadat 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é informace a po stisku klávesy přejde do cyklu s bojem. Jedná se o while cyklus, který se opakuje, dokud jsou oba bojovníci naživu. První bojovník zaútočí na druhého, jeho útok vnitřně zavolá na druhém bojovníkovi obranu. Po útoku vykreslíme obrazovku s informacemi a dále zprávy o útoku a obraně pomocí naší metody VypisZpravu(), která po výpisu udělá dramatickou pauzu. To samé provedeme i pro druhého bojovníka.

Přesuňme se do Module1.vb, vytvořme patřičné instance a zavolejme na aréně metodu Zapas():

' 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()
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())
        Dim zprava As String
        If zraneni > 0 Then
            zivot -= zraneni
            zprava = String.Format("{0} utrpěl poškození {1} hp", jmeno, zraneni)
            If zivot <= 0 Then
                zivot = 0
                zprava &= " a zemřel"
            End If
        Else
            zprava = String.Format("{0} odrazil útok", jmeno)
        End If
        NastavZpravu(zprava)
    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 hrdinů si můžete upravit dle libosti. Program spustíme:

Konzolová aplikace
-------------- Aréna --------------

Zdraví bojovníků:

Zalgoren [######              ]
Shadow [                    ]
Shadow útočí úderem za 20 hp
Zalgoren utrpěl poškození 4 hp

Výsledek je docela působivý. Objekty spolu komunikují, grafický život ubývá jak má, zážitek umocňuje dramatická pauza. Aréna má však 2 nedostatky.

  • V cyklu s bojem útočí první bojovník na druhého. Poté však vždy útočí i druhý bojovník, nehledě na to, zda ho první nezabil. Může tedy útočit již jako mrtvý. Podívejte se na screenshot výše, Shadow útočil jako poslední i když byl mrtvý. Až potom se vystoupilo z while cyklu. U prvního bojovníka tento problém není, u druhého musíme před útokem kontrolovat, zda je naživu.
  • Druhým nedostatkem je, že bojovníci vždy bojují ve stejném pořadí, čili zde "Zalgoren" má vždy výhodu. Pojďme vnést další prvek náhody a pomocí kostky rozhodněme, který z bojovníků bude začínat. Jelikož jsou bojovníci vždy dva, stačí hodit kostkou a podívat se, zda padlo číslo menší nebo rovné polovině počtu stěn kostky. Tedy např. pokud padne na desetistěnné kostce číslo do 5ti, začíná 2. bojovník, jinak začíná první. Zbývá zamyslet se nad tím, jak do kódu zanést prohazování bojovníků. Jistě by bylo velmi nepřehledné opodmínkovat příkazy ve while cyklu. Jelikož již víme, že ve VB.NET fungují reference, není pro nás problém udělat si 2 proměnné, ve kterých budou instance bojovníků, nazvěme je jednoduše b1 a b2. Do těchto proměnných si na začátku dosadíme bojovníky bojovnik1 a bojovnik2 tak, jak potřebujeme. Můžeme tedy při pozitivním hodu kostkou dosadit do b1 bojovník2 a naopak, výsledkem bude, že začínat bude ten druhý. Kód cyklu se takto vůbec nezmění a zůstane stále přehledný a jednoduchý, jen místo bojovnik bude b.

Změněná verze včetně podmínky, aby nemohl útočit mrtvý bojovník, by mohla vypadat nějak takto:

Public Sub Zapas()
    ' původní pořadí
    Dim b1 As Bojovnik = bojovnik1
    Dim b2 As Bojovnik = bojovnik2
    Console.WriteLine("Vítejte v aréně!")
    Console.WriteLine("Dnes se utkají {0} s {1}! " & vbCrLf, bojovnik1, bojovnik2)
    ' prohození bojovníků
    Dim zacinaBojovnik2 As Boolean = (kostka.hod() <= kostka.VratPocetSten() / 2)
    If zacinaBojovnik2 Then
        b1 = bojovnik2
        b2 = bojovnik1
    End If
    Console.WriteLine("Začínat bude bojovník {0}!" & vbCrLf & "Zápas může začít...", b1)
    Console.ReadKey()
    ' cyklus s bojem
    While b1.Nazivu() And b2.Nazivu()
        b1.Utoc(b2)
        Vykresli()
        VypisZpravu(b1.VratPosledniZpravu()) ' zpráva o útoku
        VypisZpravu(b2.VratPosledniZpravu()) ' zpráva o obraně
        If b2.Nazivu Then
            b2.Utoc(b1)
            Vykresli()
            VypisZpravu(b2.VratPosledniZpravu()) ' zpráva o útoku
            VypisZpravu(b1.VratPosledniZpravu()) ' zpráva o obraně
        End If
        Console.WriteLine()
    End While
End Sub
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())
        Dim zprava As String
        If zraneni > 0 Then
            zivot -= zraneni
            zprava = String.Format("{0} utrpěl poškození {1} hp", jmeno, zraneni)
            If zivot <= 0 Then
                zivot = 0
                zprava &= " a zemřel"
            End If
        Else
            zprava = String.Format("{0} odrazil útok", jmeno)
        End If
        NastavZpravu(zprava)
    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

Program vyzkoušejme.

Konzolová aplikace
-------------- 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še již v pořádku. Gratuluji vám, pokud jste se dostali až sem a tutoriály opravdu četli a pochopili, máte základy objektového programování a dokážete tvořit rozumné aplikace :)

V příští lekci, Dědičnost a polymorfismus, se podíváme na objektově orientované programování podrobněji. V úvodu jsme si říkali, že OOP stojí na pilířích: zapouzdření, dědičnost a polymorfismus. První umíme již velmi dobře a modifikátor Private je nám známý. Další dva nás čekají příště.


 

Předchozí článek
Bojovník do arény
Všechny články v sekci
Objektově orientované programování ve Visual Basic .NET
Přeskočit článek
(nedoporučujeme)
Dědičnost a polymorfismus
Článek pro vás napsal Michal Žůrek - misaz
Avatar
Uživatelské hodnocení:
10 hlasů
Autor se věnuje tvorbě aplikací pro počítače, mobilní telefony, mikroprocesory a tvorbě webových stránek a webových aplikací. Nejraději programuje ve Visual Basicu a TypeScript. Ovládá HTML, CSS, JavaScript, TypeScript, C# a Visual Basic.
Aktivity