6. diel - C # - 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 teda dáme všetko dokopy a vytvoríme v C # .NET 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 Program.cs
, ale vytvoríme si objekt Arena
, kde sa bude zápas odohrávať.
Program.cs
potom len založia objekty ao zvyšok sa bude starať
objekt Arena
. Pridajme k projektu teda posledný triedu a to
Arena.cs
.
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 Bojovnik bojovnik1; private Bojovnik bojovnik2; private Kostka kostka; public Arena(Bojovnik bojovnik1, Bojovnik bojovnik2, Kostka kostka) { this.bojovnik1 = bojovnik1; this.bojovnik2 = bojovnik2; this.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 void Vykresli() { Console.Clear(); Console.WriteLine("-------------- Aréna -------------- \n"); Console.WriteLine("Zdravie bojovníkov: \n"); Console.WriteLine("{0} {1}", bojovnik1, bojovnik1.GrafickyZivot()); Console.WriteLine("{0} {1}", bojovnik2, bojovnik2.GrafickyZivot()); }
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 void VypisZpravu(string zprava) { Console.WriteLine(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. Aby všetko fungovalo, musíme pridať
using System.Threading;
na začiatok súboru Arena.cs
.
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 void Zapas() { Console.WriteLine("Vitajte v aréne!"); Console.WriteLine("Dnes sa stretnú {0} s {1}! \n", bojovnik1, bojovnik2); Console.WriteLine("Zápas môže začať..."); Console.ReadKey(); // cyklus s bojem while (bojovnik1.Nazivu() && bojovnik2.Nazivu()) { bojovnik1.Utoc(bojovnik2); Vykresli(); VypisZpravu(bojovnik1.VratPosledniZpravu()); // správa o útoku VypisZpravu(bojovnik2.VratPosledniZpravu()); // správa o obrane bojovnik2.Utoc(bojovnik1); Vykresli(); VypisZpravu(bojovnik2.VratPosledniZpravu()); // správa o útoku VypisZpravu(bojovnik1.VratPosledniZpravu()); // správa o obrane Console.WriteLine(); } }
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 Program.cs
, vytvorme patričné inštancie a
zavolajte na aréne metódu Zapas()
:
{CSHARP_OOP} {CSHARP_MAIN_BLOCK} // vytvorenie objektov Kostka kostka = new Kostka(10); Bojovnik zalgoren = new Bojovnik("Zalgoren", 100, 20, 10, kostka); Bojovnik shadow = new Bojovnik("Shadow", 60, 18, 15, kostka); Arena arena = new Arena(zalgoren, shadow, kostka); // zápas arena.Zapas(); Console.ReadKey(); {/CSHARP_MAIN_BLOCK} {/CSHARP_OOP}
{CSHARP_OOP} class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int VratPocetSten() { return pocetSten; } public int hod() { return random.Next(1, pocetSten + 1); } public override string ToString() { return String.Format("Kocka s {0} stenami", pocetSten); } } {/CSHARP_OOP}
{CSHARP_OOP} class Bojovnik { private string jmeno; private int zivot; private int maxZivot; private int utok; private int obrana; private Kostka kostka; private string zprava; public Bojovnik(string jmeno, int zivot, int utok, int obrana, Kostka kostka) { this.jmeno = jmeno; this.zivot = zivot; this.maxZivot = zivot; this.utok = utok; this.obrana = obrana; this.kostka = kostka; } public bool Nazivu() { return (zivot > 0); } public string GrafickyZivot() { string s = "["; int celkem = 20; double pocet = Math.Round(((double)zivot / maxZivot) * celkem); if ((pocet == 0) && (Nazivu())) pocet = 1; for (int i = 0; i < pocet; i++) s += "#"; s = s.PadRight(celkem + 1); s += "]"; return s; } public void Utoc(Bojovnik souper) { int uder = utok + kostka.hod(); NastavZpravu(String.Format("{0} útočia s úderom za {1} hp", jmeno, uder)); souper.BranSe(uder); } public void BranSe(int uder) { int zraneni = uder - (obrana + kostka.hod()); if (zraneni > 0) { zivot -= zraneni; zprava = String.Format("{0} utrpel poškodenie {1} hp", jmeno, zraneni); if (zivot <= 0) { zivot = 0; zprava += " a zomrel"; } } else zprava = String.Format("{0} odrazil útok", jmeno); NastavZpravu(zprava); } private void NastavZpravu(string zprava) { this.zprava = zprava; } public string VratPosledniZpravu() { return zprava; } public override string ToString() { return jmeno; } } {/CSHARP_OOP}
using System.Threading; {CSHARP_OOP} class Arena { private Bojovnik bojovnik1; private Bojovnik bojovnik2; private Kostka kostka; public Arena(Bojovnik bojovnik1, Bojovnik bojovnik2, Kostka kostka) { this.bojovnik1 = bojovnik1; this.bojovnik2 = bojovnik2; this.kostka = kostka; } private void Vykresli() { Console.Clear(); Console.WriteLine("-------------- Aréna -------------- \n"); Console.WriteLine("Zdravie bojovníkov: \n"); Console.WriteLine("{0} {1}", bojovnik1, bojovnik1.GrafickyZivot()); Console.WriteLine("{0} {1}", bojovnik2, bojovnik2.GrafickyZivot()); } private void VypisZpravu(string zprava) { Console.WriteLine(zprava); Thread.Sleep(500); } public void Zapas() { Console.WriteLine("Vitajte v aréne!"); Console.WriteLine("Dnes sa stretnú {0} s {1}! \n", bojovnik1, bojovnik2); Console.WriteLine("Zápas môže začať..."); Console.ReadKey(); // cyklus s bojom while (bojovnik1.Nazivu() && bojovnik2.Nazivu()) { bojovnik1.Utoc(bojovnik2); Vykresli(); VypisZpravu(bojovnik1.VratPosledniZpravu()); // správa o útoku VypisZpravu(bojovnik2.VratPosledniZpravu()); // správa o obrane bojovnik2.Utoc(bojovnik1); Vykresli(); VypisZpravu(bojovnik2.VratPosledniZpravu()); // správa o útoku VypisZpravu(bojovnik1.VratPosledniZpravu()); // správa o obrane Console.WriteLine(); } } } {/CSHARP_OOP}
Charakteristiky hrdinov si môžete upraviť podľa ľubovôle. Program spustíme:
Konzolová aplikácia
-------------- Aréna --------------
Zdravie bojovníkov:
Zalgoren [###### ]
Shadow [ ]
Shadow útočia úderom za 20 hp
Zalgoren utrpel poškodenie 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 v C # fungujú referencie, nie je pre nás problém urobiť si 2 premenné, v ktorých budú inštancie bojovníkov, nazvime ich jednoduchob1
ab2
. Do týchto premenných si na začiatku dosadíme bojovníkmibojovnik1
abojovnik2
tak, ako potrebujeme. Môžeme teda pri pozitívnom hodu kockou dosadiť dob1
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 miestobojovnik
budeb
.Zmenená verzie vrátane podmienky, aby nemohol útočiť mŕtvy bojovník, by mohla vyzerať nejako takto:
using System.Threading; {CSHARP_OOP} class Arena { private Bojovnik bojovnik1; private Bojovnik bojovnik2; private Kostka kostka; public Arena(Bojovnik bojovnik1, Bojovnik bojovnik2, Kostka kostka) { this.bojovnik1 = bojovnik1; this.bojovnik2 = bojovnik2; this.kostka = kostka; } private void Vykresli() { Console.Clear(); Console.WriteLine("-------------- Aréna -------------- \n"); Console.WriteLine("Zdravie bojovníkov: \n"); Console.WriteLine("{0} {1}", bojovnik1, bojovnik1.GrafickyZivot()); Console.WriteLine("{0} {1}", bojovnik2, bojovnik2.GrafickyZivot()); } private void VypisZpravu(string zprava) { Console.WriteLine(zprava); Thread.Sleep(500); } public void Zapas() { // pôvodné poradie Bojovnik b1 = bojovnik1; Bojovnik b2 = bojovnik2; Console.WriteLine("Vitajte v aréne!"); Console.WriteLine("Dnes sa stretnú {0} s {1}! \n", bojovnik1, bojovnik2); // prehodenie bojovníkov bool zacinaBojovnik2 = (kostka.hod() <= kostka.VratPocetSten() / 2); if (zacinaBojovnik2) { b1 = bojovnik2; b2 = bojovnik1; } Console.WriteLine("Začínať bude bojovník {0}! \nZápas môže začať...", b1); Console.ReadKey(); // cyklus s bojom while (b1.Nazivu() && b2.Nazivu()) { b1.Utoc(b2); Vykresli(); VypisZpravu(b1.VratPosledniZpravu()); // správa o útoku VypisZpravu(b2.VratPosledniZpravu()); // správa o obrane if (b2.Nazivu()) { b2.Utoc(b1); Vykresli(); VypisZpravu(b2.VratPosledniZpravu()); // správa o útoku VypisZpravu(b1.VratPosledniZpravu()); // správa o obrane } Console.WriteLine(); } } } {/CSHARP_OOP}
{CSHARP_OOP} {CSHARP_MAIN_BLOCK} // vytvorenie objektov Kostka kostka = new Kostka(10); Bojovnik zalgoren = new Bojovnik("Zalgoren", 100, 20, 10, kostka); Bojovnik shadow = new Bojovnik("Shadow", 60, 18, 15, kostka); Arena arena = new Arena(zalgoren, shadow, kostka); // zápas arena.Zapas(); Console.ReadKey(); {/CSHARP_MAIN_BLOCK} {/CSHARP_OOP}
{CSHARP_OOP} class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int VratPocetSten() { return pocetSten; } public int hod() { return random.Next(1, pocetSten + 1); } public override string ToString() { return String.Format("Kocka s {0} stenami", pocetSten); } } {/CSHARP_OOP}
{CSHARP_OOP} class Bojovnik { private string jmeno; private int zivot; private int maxZivot; private int utok; private int obrana; private Kostka kostka; private string zprava; public Bojovnik(string jmeno, int zivot, int utok, int obrana, Kostka kostka) { this.jmeno = jmeno; this.zivot = zivot; this.maxZivot = zivot; this.utok = utok; this.obrana = obrana; this.kostka = kostka; } public bool Nazivu() { return (zivot > 0); } public string GrafickyZivot() { string s = "["; int celkem = 20; double pocet = Math.Round(((double)zivot / maxZivot) * celkem); if ((pocet == 0) && (Nazivu())) pocet = 1; for (int i = 0; i < pocet; i++) s += "#"; s = s.PadRight(celkem + 1); s += "]"; return s; } public void Utoc(Bojovnik souper) { int uder = utok + kostka.hod(); NastavZpravu(String.Format("{0} útočia s úderom za {1} hp", jmeno, uder)); souper.BranSe(uder); } public void BranSe(int uder) { int zraneni = uder - (obrana + kostka.hod()); if (zraneni > 0) { zivot -= zraneni; zprava = String.Format("{0} utrpel poškodenie {1} hp", jmeno, zraneni); if (zivot <= 0) { zivot = 0; zprava += "a zomrel"; } } else zprava = String.Format("{0} odrazil útok", jmeno); NastavZpravu(zprava); } private void NastavZpravu(string zprava) { this.zprava = zprava; } public string VratPosledniZpravu() { return zprava; } public override string ToString() { return jmeno; } } {/CSHARP_OOP}
Program vyskúšajme.
Konzolová aplikácia
-------------- Aréna --------------
Zdraví bojovníků:
Zalgoren [######### ]
Shadow [ ]
Zalgoren útočia úderom za 27 hp
Shadow utrpel poškodenie 11 hp a zomrel
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.
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é 1702x (145.25 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#