3. diel - Hracia kocka v C# - Zapuzdrenie, konštruktor a Random
V predchádzajúcom cvičení, Riešené úlohy k 1.-2. lekcii OOP v C# .NET, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
Už vieme tvoriť nové triedy a vkladať do nich atribúty a metódy s parametrami a návratovou hodnotou. V dnešnom C# .NET tutoriále začneme pracovať na sľúbenej aréne, v ktorej budú proti sebe bojovať dvaja bojovníci. Boj bude ťahový (na preskáčku) a bojovník vždy druhému uberie život na základe sily jeho útoku a obrany druhého bojovníka. Simulujeme v podstate stolnú hru, budeme teda simulovať aj hraciu kocku, ktorá dodá hre prvok náhodnosti. Začnime zvoľna a vytvorme si dnes práve túto hraciu kocku. Zároveň sa naučíme, ako definovať vlastný konštruktor.
Vytvorenie projektu
Vytvorme si novú konzolovú aplikáciu a pomenujme ju Arena
. K
projektu si pridajme novú class
s názvom RollingDie
.
Zamyslime sa nad atribútmi, ktoré kocke dáme. Iste by sa hodilo, keby sme si
mohli zvoliť počet stien kocky (klasicky 6 alebo 10 stien, ako je zvykom pri
tomto type hier). Ďalej bude kocka potrebovať tzv. generátor náhodných
čísel. Ten nám samozrejme poskytne .NET framework, ktorý na tieto účely
obsahuje triedu Random
. Naša trieda bude mať teraz 2
atribúty:
sidesCount
typuint
,random
typuRandom
, kde bude náhodný generátor.
Zapuzdrenie
Minule sme kvôli jednoduchosti nastavovali všetky atribúty našej triedy
ako public
, teda ako verejne prístupné. Väčšinou sa však
skôr nechce, aby sa dali zvonku modifikovať a používa sa modifikátor
private
. Atribút je potom viditeľný len vo vnútri triedy a
zvonku sa C# tvári, že vôbec neexistuje. Pri návrhu triedy teda nastavíme
všetko na private
a v prípade, že niečo bude naozaj potrebné
vystaviť, použijeme public
. Naša trieda teraz vyzerá takto:
/// <summary> /// Class representing a die for a board game /// </summary> class RollingDie { /// <summary> /// Random number generator /// </summary> private Random random; /// <summary> /// Number of sides that the die has /// </summary> private int sidesCount; }
Konštruktory
Až doposiaľ sme nevedeli zvonku nastaviť iné atribúty ako
public
, pretože napr. private
nie sú zvonku
viditeľné. Už sme si hovorili niečo málo o konštruktore objektu. Je to
metóda, ktorá sa zavolá vo chvíli vytvorenia inštancie
objektu. Slúži samozrejme na nastavenie vnútorného stavu objektu a
na vykonanie prípadnej inicializácie. Kocku by sme teraz v
Program.cs
vytvorili takto:
RollingDie die = new RollingDie();
Práve RollingDie()
je konštruktor. Pretože v našej triede
žiadny nie je, C# si dogeneruje prázdnu metódu. My si však teraz
konštruktor do triedy pridáme. Deklaruje sa ako metóda, ale nemá
návratový typ a musí mať rovnaké meno ako je meno
triedy, v našom prípade teda RollingDie
. V konštruktore
nastavíme počet stien na pevnú hodnotu a vytvoríme inštanciu triedy
Random
. Konštruktor bude vyzerať nasledovne:
public RollingDie() { sidesCount = 6; random = new Random(); }
Ak kocku teraz vytvoríme, bude mať v atribúte sidesCount
6
a v random
bude vytvorená inštancia generátora
náhodných čísel. Vypíšme si počet stien do konzoly, nech vidíme, že tam
hodnota naozaj je. Nie je dobré atribút nastaviť na public
,
pretože nebudeme chcieť, aby nám niekto mohol už pri vytvorenej kocke meniť
počet stien. Pridáme do triedy teda metódu GetSidesCount()
,
ktorá nám vráti hodnotu atribútu sidesCount
. Docielili sme tým
v podstate to, že je atribút read-only (atribút nie je viditeľný a možno
ho iba čítať metódou, zmeniť ho nemožno). C# má na tento účel ešte
ďalšie konštrukcie, ale tým sa zatiaľ nebudeme zaoberať. Nová metóda
bude vyzerať asi takto:
/// <summary> /// Returns the number of sides the die has /// </summary> /// <returns>Number of sides the die has</returns> public int GetSidesCount() { return sidesCount; }
Presuňme sa do Program.cs
a vyskúšajme si vytvoriť kocku a
vypísať počet stien:
{CSHARP_CONSOLE} RollingDie die = new RollingDie(); // At this moment the constructor is called. Console.WriteLine(die.GetSidesCount()); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public int GetSidesCount() { return sidesCount; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikácia
6
Vidíme, že sa konštruktor naozaj zavolal. My by sme ale chceli, aby sme mohli pri každej kocke pri vytvorení špecifikovať, koľko stien budeme potrebovať. Dáme teda kostruktoru parameter:
public RollingDie(int aSidesCount) { sidesCount = aSidesCount; random = new Random(); }
Všimnite si, že sme pred názov parametra metódy pridali znak "a",
pretože inak by mal rovnaký názov ako atribút a C# by to zmiatlo. Vráťme
sa k Program.cs
a zadajme tento parameter do konštruktora:
{CSHARP_CONSOLE} RollingDie die = new RollingDie(10); // At this moment the constructor is called with the parameter 10. Console.WriteLine(die.GetSidesCount()); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} class RollingDie { private Random random; private int sidesCount; public RollingDie(int aSidesCount) { sidesCount = aSidesCount; random = new Random(); } public int GetSidesCount() { return sidesCount; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikácia
10
Všetko funguje, ako sme očakávali. C# nám už v tejto chvíli
nevygeneruje prázdny (tzv. bezparametrický konštruktor), takže kocku bez
parametra vytvoriť nemožno. My to však môžeme umožniť, vytvorme si
ďalší konštruktor a tentoraz bez parametra. V ňom nastavíme počet stien
na 6
, pretože takú hodnotu asi užívateľ našej triedy pri
kocke očakáva ako východiskový:
public RollingDie() { sidesCount = 6; random = new Random(); }
Skúsme si teraz vytvoriť 2 inštancie kocky, každú iným konštruktorom
(v Program.cs
):
{CSHARP_CONSOLE} RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); Console.WriteLine(sixSided.GetSidesCount()); Console.WriteLine(tenSided.GetSidesCount()); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public RollingDie(int aSidesCount) { sidesCount = aSidesCount; random = new Random(); } public int GetSidesCount() { return sidesCount; } } {/CSHARP_OOP}
Výstup:
Konzolová aplikácia
6
10
Jazyku C# nevadí, že máme 2 metódy s rovnakým názvom, pretože ich
parametre sú rôzne. Hovoríme o tom, že metóda RollingDie()
(teda tu konštruktor) má preťaženie (overload). To môžeme
využívať aj pri všetkých ďalších metódach, nielen pri konštruktoroch.
Visual Studio nám preťaženie pri metódach
prehľadne ponúka (vo chvíli, keď za názov metódy napíšeme ľavú
zátvorku), variantmi metódy si môžeme listovať pomocou šípok. Tohto
pomocníka nazval MS IntelliSense. V ponuke vidíme naše 2 konštruktory:
Veľa metód v .NET má hneď niekoľko preťažení. Skúsme sa pozrieť
napr. na metódu Remove()
na reťazci string
. Prejsť
si jednotlivé preťaženia pri metódach je dobré preto, aby sme
neprogramovali niečo, čo už niekto urobil pred nami. Napríklad metóda
WriteLine()
, ktorú poznáme pre vypisovanie do konzoly, má hneď
18 variantov.
Než konštruktory na chvíľu opustíme, ešte si ukážeme, ako je možné
obísť nepraktický názov atribútu u parametrického konštruktora (v našom
prípade aSidesCount
). Pozrime sa na nasledujúci kód:
public RollingDie(int sidesCount) { sidesCount = sidesCount; random = new Random(); }
Problém samozrejme spočíva v tom, že z vyššie napísaného C#
nespozná, ktorú z premenných myslíme. Či parameter, alebo atribút. V tomto
prípade priraďujeme do parametra znovu ten istý parameter. VS nás na túto
skutočnosť dokonca upozorní. Vnútri triedy však máme možnosť odkazovať
sa na jej inštanciu, ktorá je uložená v premennej this
.
Využitie si môžeme predstaviť napr. nasledovne: kocka by mala metódu
GiveToPlayer(Player player)
a tam by volala
player.PickUpDie(this)
. Tu by sme hráči pomocou this
odovzdali seba samého, teda tú konkrétnu kocku, s ktorou pracujeme. My sa
týmto tu nebudeme zaťažovať, ale využijeme odkaz na inštanciu pri
nastavovaní atribútu:
public RollingDie(int sidesCount) { this.sidesCount = sidesCount; random = new Random(); }
Pomocou this
sme špecifikovali, že ľavá premenná
sidesCount
patrí inštancii, pravú C# chápe ako z parametra.
Máme teda 2 konštruktory, ktoré nám umožňujú tvoriť rôzne hracie kocky.
Prejdime ďalej.
Náhodné čísla
Definujme na kocke metódu Roll()
, ktorá nám vráti náhodné
číslo od 1
do počtu stien. Je to veľmi jednoduché, metóda
bude public
(pôjde volať zvonku) a nebude mať žiadny parameter.
Návratová hodnota bude typu int
. Náhodné číslo získame tak,
že na generátore zavoláme metódu Next()
. Tá má niekoľko
preťažení:
Next()
– Variant bez parametra vracia náhodné číslo v celom rozsahu dátového typuint
.Next(To)
– Vráti nezáporné čísla menšie ako hranicaTo
. Napr.random.Next(100)
teda vráti číslo od0
do99
.Next(From, To)
– Vráti náhodné číslo v zadanej hranici, pričomFrom
do intervalu patrí aTo
už nie. Teda náhodné číslo od1
do100
by vrátilorandom.Next(1, 101)
.
Na naše účely sa najlepšie hodí tretie preťaženie, píšeme teda:
/// <summary> /// Rolls a die /// </summary> /// <returns>A number from 1 to sides count</returns> public int Roll() { return random.Next(1, sidesCount + 1); }
Treba si dať pozor na to, aby sme netvorili generátor náhodných čísel v
metóde, ktorá má náhodné číslo vracať, teda aby sa pre každé náhodné
číslo nevytvoril nový generátor. Výsledné čísla potom takmer nie sú
náhodné, alebo dokonca vôbec. Vždy si treba vytvoriť jednu zdieľanú
inštanciu generátora (napr. do privátneho atribútu pomocou konštruktora) a
na tej potom metódu Next()
volať.
Prekrývanie metódy
ToString()
Kocka je takmer hotová, ukážme si ešte jednu užitočnú metódu, ktorú
ju pridáme a ktorú budeme hojne používať aj vo väčšine našich
ďalších objektov. Reč je o metóde ToString()
, o ktorej sme sa
už zmienili a ktorú obsahuje každý objekt, teda aj teraz
naša kocka. Metóda je určená na to, aby vrátila tzv. textovú
reprezentáciu inštancie. Hodí sa vo všetkých prípadoch, keď si
inštanciu potrebujeme vypísať alebo s ňou pracovať ako s textom. Túto
metódu majú napr. aj čísla. Už vieme, že v C# funguje implicitná
konverzia, akonáhle teda budeme chcieť do konzoly vypísať číslo alebo
ktorýkoľvek iný objekt, C# na ňom zavolá metódu ToString()
a
vypíše jej výstup. Ak si robíme vlastnú triedu, mali by sme zvážiť, či
sa nám takáto metóda nehodí. Nikdy by sme si nemali robiť vlastnú metódu,
napr. niečo ako Print()
, keď máme v C# pripravenú cestu, ako
toto riešiť. U kocky nemá ToString()
vyšší zmysel, ale u
bojovníka bude určite vracať jeho meno. My si ju ku kocke aj tak pridáme,
bude vypisovať, že sa jedná o kocku a vráti aj počet stien. Najprv si
skúsme vypísať do konzoly našu inštanciu kocky:
{CSHARP_CONSOLE} RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); Console.WriteLine(sixSided); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public RollingDie(int sidesCount) { this.sidesCount = sidesCount; random = new Random(); } public int GetSidesCount() { return sidesCount; } public int Roll() { return random.Next(1, sidesCount + 1); } } {/CSHARP_OOP}
Do konzoly sa vypíše iba cesta k našej triede, teda
Arena.RollingDie
. Metódu nemôžeme len tak definovať, pretože
je už definovaná (v ďalších lekciách zistíme prečo). Musíme ju teda
prepísať, resp. prekryť. Tým sa opäť nebudeme teraz
podrobne zaoberať, však je potreba, aby sme už teraz vedeli
ToString()
používať. Na prekrytie použijeme kľúčové slovo
override
:
{CSHARP_OOP} class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public RollingDie(int sidesCount) { this.sidesCount = sidesCount; random = new Random(); } public int GetSidesCount() { return sidesCount; } public int Roll() { return random.Next(1, sidesCount + 1); } /// <summary> /// Returns a textual representation of our die /// </summary> /// <returns>Textual representation of the die</returns> public override string ToString() { return String.Format("Rolling a die with {0} sides", sidesCount); } } {/CSHARP_OOP}
{CSHARP_CONSOLE} RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); Console.WriteLine(sixSided); Console.ReadKey(); {/CSHARP_CONSOLE}
Teraz opäť skúsime do konzoly vypísať priamo inštanciu kocky.
Výstup:
Konzolová aplikácia
Rolling a die with 6 sides
Ešte si naše kocky vyskúšame. Skúsime si v programe s našimi dvoma kockami v cykloch hádzať a pozrieme sa, či fungujú tak, ako sa očakáva:
{CSHARP_CONSOLE} // Create instances RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); // Rolls the 6-sided die Console.WriteLine(sixSided); for (int i = 0; i < 10; i++) Console.Write(sixSided.Roll() + " "); // Rolls the 10-sided die Console.WriteLine("\n\n" + tenSided); for (int i = 0; i < 10; i++) Console.Write(tenSided.Roll() + " "); Console.ReadKey(); // If you run the code through our online compiler, the result // is cached, and the same numbers will keep appearing. // Any change in the code (e.g., even adding a comment) will trigger // a new compilation and thus the generation of new numbers. {/CSHARP_CONSOLE}
{CSHARP_OOP} class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public RollingDie(int sidesCount) { this.sidesCount = sidesCount; random = new Random(); } public int GetSidesCount() { return sidesCount; } public int Roll() { return random.Next(1, sidesCount + 1); } public override string ToString() { return String.Format("Rolling a die with {0} sides", sidesCount); } } {/CSHARP_OOP}
Výstup:
Konzolová aplikácia
Rolling a die with 6 sides
3 6 6 1 6 3 6 2 6 3
Rolling a die with 10 sides
5 9 9 2 10 4 9 3 10 5
Máme hotovú pomerne peknú a nastaviteľnú triedu, ktorá reprezentuje hraciu kocku. Bude sa nám hodiť v našej aréne, ale môžete ju použiť aj kdekoľvek inde. Vidíme, ako OOP umožňuje znovupoužívať komponenty.
V nasledujúcom cvičení, Riešené úlohy k 3. lekcii OOP v C# .NET, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.
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é 6x (54.73 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#