Lekce 3 - Hrací kostka v C# - Konstruktory a náhodná čísla

C# .NET Objektově orientované programování Hrací kostka v C# - Konstruktory a náhodná čísla American English version English version

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, První objektová aplikace v C# - Hello object world, jsme si naprogramovali první objektovou aplikaci. Již umíme tvořit nové třídy a vkládat do nich atributy a metody s parametry a návratovou hodnotou. V dnešním C# .NET tutoriálu začneme pracovat na slíbené aréně, ve které budou proti sobě bojovat dva bojovníci. Boj bude tahový (na přeskáčku) a bojovník vždy druhému ubere život na základě síly jeho útoku a obrany druhého bojovníka. Simulujeme v podstatě stolní hru, budeme tedy simulovat i hrací kostku, která dodá hře prvek náhodnosti. Začněme zvolna a vytvořme si dnes právě tuto hrací kostku. Zároveň se naučíme jak definovat vlastní konstruktor.

Vytvořme si novou konzolovou aplikaci a pojmenujme ji Arena. K projektu si přidejme novou class s názvem Kostka. Zamysleme se nad atributy, které kostce dáme. Jistě by se hodilo, kdybychom si mohli zvolit počet stěn kostky (klasicky 6 nebo 10 stěn, jak je zvykem u tohoto typu her). Dále bude kostka potřebovat tzv. generátor náhodných čísel. Ten nám samozřejmě poskytne .NET framework, který k těmto účelům obsahuje třídu Random. Naše třída bude mít nyní 2 atributy:

  • pocetSten typu int
  • random typu Random, kde bude náhodný generátor.

Minule jsme kvůli jednoduchosti nastavovali všechny atributy naší třídy jako public, tedy jako veřejně přístupné. Většinou se však spíše nechce, aby se daly zvenčí modifikovat a používá se modifikátor private. Atribut je poté viditelný jen uvnitř třídy a zvenčí se C# tváří, že vůbec neexistuje. Při návrhu třídy tedy nastavíme vše na private a v případě, že něco bude opravdu potřeba vystavit, použijeme public. Naše třída nyní vypadá asi takto:

/// <summary>
/// Třída reprezentuje hrací kostku
/// </summary>
class Kostka
{
    /// <summary>
    /// Generátor náhodných čísel
    /// </summary>
    private Random random;
    /// <summary>
    /// Počet stěn kostky
    /// </summary>
    private int pocetSten;

}

Konstruktory

Až doposud jsme neuměli zvenčí nastavit jiné atributy než public, protože např. private nejsou zvenčí viditelné. Již jsme si říkali něco málo o konstruktoru objektu. Je to metoda, která se zavolá ve chvíli vytvoření instance objektu. Slouží samozřejmě k nastavení vnitřního stavu objektu a k provedení případné inicializace. Kostku bychom nyní v Program.cs vytvořili takto:

Kostka kostka = new Kostka();

Právě Kostka() je konstruktor. Protože v naší třídě žádný není, C# si dogeneruje prázdnou metodu. My si však nyní konstruktor do třídy přidáme. Deklaruje se jako metoda, ale nemá návratový typ a musí mít stejné jméno jako je jméno třídy, v našem případě tedy Kostka. V konstruktoru nastavíme počet stěn na pevnou hodnotu a vytvoříme instanci třídy Random. Konstruktor bude vypadat následovně:

public Kostka()
{
    pocetSten = 6;
    random = new Random();
}

Pokud kostku nyní vytvoříme, bude mít v atributu pocetSten 6 a v random bude vytvořená instance generátoru náhodných čísel. Vypišme si počet stěn do konzole, ať vidíme, že tam hodnota opravdu je. Není dobré atribut nastavit na public, protože nebudeme chtít, aby nám někdo mohl již u vytvořené kostky měnit počet stěn. Přidáme do třídy tedy metodu VratPocetSten(), která nám vrátí hodnotu atributu pocetSten. Docílili jsme tím v podstatě toho, že je atribut read-only (atribut není viditelný a lze ho pouze číst metodou, změnit ho nelze). C# má k tomuto účelu ještě další konstrukce, ale tím se zatím nebudeme zabývat. Nová metoda bude vypadat asi takto:

/// <summary>
/// Vrátí počet stěn hrací kostky
/// </summary>
/// <returns>počet stěn hrací kostky</returns>
public int VratPocetSten()
{
    return pocetSten;
}

Přesuňme se do Program.cs a vyzkoušejme si vytvořit kostku a vypsat počet stěn:

            Kostka kostka = new Kostka(); // v tuto chvíli se zavolá konstruktor
            Console.WriteLine(kostka.VratPocetSten());
            Console.ReadKey();
    class Kostka
    {
        private Random random;
        private int pocetSten;

        public Kostka()
        {
            pocetSten = 6;
            random = new Random();
        }

        public int VratPocetSten()
        {
            return pocetSten;
        }

    }

Výstup:

Konzolová aplikace
6

Vidíme, že se konstruktor opravdu zavolal. My bychom ale chtěli, abychom mohli u každé kostky při vytvoření specifikovat, kolik stěn budeme potřebovat. Dáme tedy kostruktoru parametr:

public Kostka(int aPocetSten)
{
    pocetSten = aPocetSten;
    random = new Random();
}

Všimněte si, že jsme před název parametru metody přidali znak "a", protože jinak by měl stejný název jako atribut a C# by to zmátlo. Vraťme se k Program.cs a zadejme tento parametr do konstruktoru:

            Kostka kostka = new Kostka(10); // v tuto chvíli se zavolá konstruktor s par. 10
            Console.WriteLine(kostka.VratPocetSten());
            Console.ReadKey();
    class Kostka
    {
        private Random random;
        private int pocetSten;

        public Kostka(int aPocetSten)
        {
            pocetSten = aPocetSten;
            random = new Random();
        }

        public int VratPocetSten()
        {
            return pocetSten;
        }

    }

Výstup:

Konzolová aplikace
10

Vše funguje, jak jsme očekávali. C# nám již v tuto chvíli nevygeneruje prázdný (tzv. bezparametrický konstruktor), takže kostku bez parametru vytvořit nelze. My to však můžeme umožnit, vytvořme si další konstruktor a tentokrát bez parametru. V něm nastavíme počet stěn na 6, protože takovou hodnotu asi uživatel naší třídy u kostky očekává jako výchozí:

public Kostka()
{
    pocetSten = 6;
    random = new Random();
}

Zkusme si nyní vytvořit 2 instance kostky, každou jiným konstruktorem (v Program.cs):

            Kostka sestistenna = new Kostka();
            Kostka desetistenna = new Kostka(10);
            Console.WriteLine(sestistenna.VratPocetSten());
            Console.WriteLine(desetistenna.VratPocetSten());
            Console.ReadKey();
    class Kostka
    {
        private Random random;
        private int pocetSten;

        public Kostka()
        {
            pocetSten = 6;
            random = new Random();
        }

        public Kostka(int aPocetSten)
        {
            pocetSten = aPocetSten;
            random = new Random();
        }

        public int VratPocetSten()
        {
            return pocetSten;
        }

    }

Výstup:

Konzolová aplikace
6
10

C# nevadí, že máme 2 metody se stejným názvem, protože jejich parametry jsou různé. Hovoříme o tom, že metoda Kostka() (tedy zde konstruktor) má přetížení (overload). Toho můžeme využívat i u všech dalších metod, nejen u konstruktorů. Visual Studio nám přetížení u metod přehledně nabízí (ve chvíli, kdy za název metody napíšeme levou závorku), variantami metody si můžeme listovat pomocí šipek. Tohoto pomocníka nazval MS IntelliSense. V nabídce vidíme naše 2 konstruktory:

Nápověda IntelliSense k přetíženým metodám v C#

Mnoho metod v .NETu má hned několik přetížení, zkuste se podívat např. na metodu Remove() na stringu. Je dobré si u metod projít jejich přetížení, abyste neprogramovali něco, co již někdo udělal před vámi. Například metoda WriteLine(), kterou znáte pro vypisování do konzole, má hned 18 variant

Ukážeme si ještě, jak jde obejít nepraktický název atributu u parametrického konstruktoru (v našem případě aPocetSten) a potom konstruktory na chvíli opustíme. Problém je samozřejmě v tom, že když napíšeme:

public Kostka(int pocetSten)
{
    pocetSten = pocetSten;
    random = new Random();
}

C# neví, kterou z proměnných myslíme, jestli parametr nebo atribut. V tomto případě přiřazujeme do parametru znovu ten samý parametr. VS nás na tuto skutečnost dokonce upozorní. Uvnitř třídy se máme možnost odkazovat na její instanci, je uložena v proměnné this. Využití si můžeme představit např. kdyby kostka měla metodu DejHraci(Hrac hrac) a tam by volala hrac.SeberKostku(this). Zde bychom hráči pomocí this předali sebe sama, tedy tu konkrétní kostku, se kterou pracujeme. My se tím zde nebudeme zatěžovat, ale využijeme odkazu na instanci při nastavování atributu:

public Kostka(int pocetSten)
{
    this.pocetSten = pocetSten;
    random = new Random();
}

Pomocí this jsme specifikovali, že levá proměnná pocetSten náleží instanci, pravou C# chápe jako z parametru. Máme tedy 2 konstruktory, které nám umožňují tvořit různé hrací kostky. Přejděme dál.

Náhodná čísla

Definujeme na kostce metodu hod(), která nám vrátí náhodné číslo od 1 do počtu stěn. Je to velmi jednoduché, metoda bude public (půjde volat zvenčí) a nebude mít žádný parametr. Návratová hodnota bude typu int. Náhodné číslo získáme tak, že na generátoru zavoláme metodu Next(). Ta má několik přetížení:

  • Next(): Varianta bez parametru vrací náhodné číslo v celém rozsahu datového typu int;
  • Next(do): Vrací nezáporná čísla menší než mez do. Např. random.Next(100) tedy vrátí číslo od 0 do 99.
  • Next(od, do): Vrátí náhodné číslo v zadané mezi, přičemž od do intervalu patří a do již ne. Tedy náhodné číslo od 1 do 100 by vrátilo random.Next(1, 101);

Pro naše účely se nejlépe hodí třetí přetížení, píšeme tedy:

/// <summary>
/// Vykoná hod kostkou
/// </summary>
/// <returns>Číslo od 1 do počtu stěn</returns>
public int hod()
{
    return random.Next(1, pocetSten + 1);
}

Dejte si pozor, abyste netvořili generátor náhodných čísel v metodě, která má náhodné číslo vracet, tedy že by se pro každé náhodné číslo vytvořil nový generátor. Výsledná čísla pak nejsou téměř náhodná nebo dokonce vůbec. Vždy si vytvořte jednu sdílenou instanci generátoru (např. do privátního atributu pomocí konstruktoru) a na té potom metodu Next() volejte.

Překrývání metody ToString()

Kostka je téměř hotová, ukažme si ještě jednu užitečnou metodu, kterou ji přidáme a kterou budeme hojně používat i ve většině našich dalších objektů. Řeč je o metodě ToString(), o které jsme se již zmínili a kterou obsahuje každý objekt, tedy i nyní naše kostka. Metoda je určena k tomu, aby vrátila tzv. textovou reprezentaci instance. Hodí se ve všech případech, kdy si instanci potřebujeme vypsat nebo s ní pracovat jako s textem. Tuto metodu mají např. i čísla. Již víme, že v C# funguje implicitní konverze, jakmile tedy budeme chtít do konzole vypsat číslo nebo kterýkoli jiný objekt, C# na něm zavolá metodu ToString() a vypíše její výstup. Pokud si děláme vlastní třídu, měli bychom zvážit, zda se nám takováto metoda nehodí. Nikdy bychom si neměli dělat vlastní metodu, např. něco jako Vypis(), když máme v C# připravenou cestu, jak toto řešit. U kostky nemá ToString() vyšší smysl, ale u bojovníka bude jistě vracet jeho jméno. My si ji ke kostce stejně přidáme, bude vypisovat, že se jedná o kostku a vrátí i počet stěn. Nejprve si zkusme vypsat do konzole naši instanci kostky:

            Console.WriteLine(sestistenna);
    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);
        }

    }

Do konzole se vypíše pouze cesta k naší třídě, tedy Arena.Kostka. Metodu nemůžeme jen tak definovat, protože je již definována (v dalších lekcích zjistíme proč). Musíme ji tedy přepsat, resp. překrýt. Tím se opět nebudeme nyní podrobně zabývat, nicméně chci, abychom již teď uměli ToString() používat. K překrytí použijeme klíčové slovo override:

        /// <summary>
        /// Vrací textovou reprezentaci kostky
        /// </summary>
        /// <returns>Textová reprezentace kostky</returns>
        public override string ToString()
        {
            return String.Format("Kostka s {0} stěnami", pocetSten);
        }

Nyní opět zkusíme do konzole vypsat přímo instanci kostky.

Výstup:

Konzolová aplikace
Kostka s 6 stěnami

Ještě si naše kostky vyzkoušíme. Zkusíme si v programu s našima dvěma kostkama v cyklech házet a podíváme se, jestli fungují tak, jak se očekává:

            // vytvoření
            Kostka sestistenna = new Kostka();
            Kostka desetistenna = new Kostka(10);

            // hod šestistěnnou
            Console.WriteLine(sestistenna);
            for (int i = 0; i < 10; i++)
                Console.Write(sestistenna.hod() + " ");

            // hod desetistěnnou
            Console.WriteLine("\n\n" + desetistenna);
            for (int i = 0; i < 10; i++)
                Console.Write(desetistenna.hod() + " ");

            Console.ReadKey();
            // Pokud budete spouštět kód přes náš online kompiler, výsledek
            // je udržován v mezipaměti a budou padat stále ta samá čísla.
            // S jakoukoli změnou v kódu (např. i přidání komentáře) vyvoláte
            // novou kompilace a tedy i vygenerování nových čísel.
Konzolová aplikace
Kostka s 6 stěnami
3 6 6 1 6 3 6 2 6 3

Kostka s 10 stěnami
5 9 9 2 10 4 9 3 10 5

Máme hotovou poměrně hezkou a nastavitelnou třídu, která reprezentuje hrací kostku. Bude se nám hodit v naší aréně, ale můžete ji použít i kdekoli jinde. Vidíme, jak OOP umožňuje znovupoužívat komponenty. V příští lekci, Referenční a hodnotové datové typy, si řekneme něco o odlišnostech mezi referenčními datovými typy (objekty) a typy hodnotovými (např. int). :)


 

Stáhnout

Staženo 2479x (27.51 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
56 hlasů
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor sítě se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity (18)

 

 

Komentáře
Zobrazit starší komentáře (118)

Avatar
Miroslav Mazal:15. srpna 18:07

Omlouvám se, pokud se budu ptát na blbosti, ale nechápu toto: V článku je napsáno: Není dobré atribut nastavit na public, protože nebudeme chtít, aby nám někdo mohl již u vytvořené kostky měnit počet stěn. Ale přitom si počet stěn nastavujeme zvenčí (neboli v public) pomocí proměnné. Není to protimluv?

 
Odpovědět 15. srpna 18:07
Avatar
krepsy3
Redaktor
Avatar
Odpovídá na Miroslav Mazal
krepsy3:15. srpna 22:46

Není :) Chápu, o co ti jde, nicméně podívej se na to takto - při vytváření kostky pro naší hru jí dáme počet stěn - předáme tento počet jako parametr v konstruktoru - a konstruktor je pochopitelně veřejný, jinak bychom si kostku nemohli vytvořit vůbec. Tím nám vznikne kostka s počtem stěn, který jsme chtěli, a nyní již nechceme, aby bylo jakkoliv možné kostku zvenčí, tedy akcí jiného objektu, ovlivnit, tedy jí změnit počet stěn (ale cokoliv jiného). Tedy přesně tak, abychom zachovali ono zapouzdření, o kterém se již mluvilo!

Tedy - v okamžiku zahájení činnosti aplikace si vytvoříme kostku, podle naší představy, tedy se správným počtem stěn. Dále už ale s kostkou budeme jen házet, a necháme ji, aby nám dávala náhodná čísla, dle počtu stěn. Ale už jí nikde nebudeme počet stěn znovu nastavovat. Je to srozumitelné?

Odpovědět  +1 15. srpna 22:46
Programátor je stroj k převodu kávy na kód.
Avatar
Odpovídá na krepsy3
Miroslav Mazal:15. srpna 22:52

Myslím, že ano. Zapouzdření se netýká nějakého programátora, který si otevře script programu, ale uživatelského rozhraní. Nebo tak nějak, že? No musím to stále dokola projíždět v hlavě, abych to začal "vidět" očima programátorské logiky a né logikou českého jazyka..

 
Odpovědět 15. srpna 22:52
Avatar
Odpovídá na krepsy3
Miroslav Mazal:15. srpna 23:11

Když bych teď třeba psal program dále, a snažil jsem se někde později dát kostce "desetistena" např.parametr 8, tak to již program nedovolí. Je to tak?

 
Odpovědět  +1 15. srpna 23:11
Avatar
Odpovídá na Miroslav Mazal
Michal Haňáček:16. srpna 7:03

Ahoj, je to tak.

Nejreálnější příměr je asi v reálné hrací kostce např. ze dřeva. Když někdo vyrábí kostku, ví předem že bude šestistenná a takovou jí udělá. Deseti, nebo čtyř stenou z ní potom také už neuděláš ... (jako víme že kde se vůle, tam je cesta a pilka svede ledasco, ale ... :-D ) a přesně pro stejný účel je v tomto případě konstruktor.

Odpovědět 16. srpna 7:03
Každé rozhodnutí a každý krok v životě nás někam posune. Bohužel jen některé nás posouvají dopředu.
Avatar
Odpovídá na Miroslav Mazal
Josef Horváth:16. srpna 7:07

Ano, přesně tak. Díky zapouzdření objekt ochráníš proti nechtěným změnám. Atribut se v konstruktoru nastaví pouze jednou a víc se nemění.
Tím tedy zakážeš toto:

sestistenna.pocetSten = 10

A už se ti nestane, že po hodu šestistěnnou kostkou padne třeba číslo 9.

Obdobný příklad:

Auto cerveneAuto = new Auto(„cervena“);
cerveneAuto.barva = „modrá“;

Pokud bude atribut barva „private“, pak ti kousek kódu se změnou barvy neprojde. U menších projektů takovou blbost asi sám nenapíšeš, ale u větších a složitějších aplikací, už to tak jasné být nemusí. Proto nevystavuj ven nic, co už nebude třeba měnit! Když bys přece jen potřeboval zpřístupnit atribut zvenčí, použiješ k tomu vlastnost, ale o té se dozvíš až v lekci 10 – Vlastnosti. Proto vytrvej v tutoriálech a ono už ti to do sebe pak hezky zapadne.

 
Odpovědět 16. srpna 7:07
Avatar
Miroslav Mazal:16. srpna 18:51

Next nám vygeneruje jedno z čísel v posloupnosti, kterou si zvolíme. Třeba 2-7, ale co když si chceme sami definovat jaká čísla budou na šesti stranách? Třeba kostku s čísly 2, 3, 3, 4, 4, 5 ?

 
Odpovědět 16. srpna 18:51
Avatar
Marek Uhlik
Člen
Avatar
Odpovídá na Miroslav Mazal
Marek Uhlik:16. srpna 19:07

Vytvoříš si pole hodnot co ta kostka může mít, a to co hodíš bude hodnota pole s náhodně vygenerovanym indexem

public int hod()
{
        int[] hodnoty = {2,3,3,4,4,5};
        return random.Next(0, pocetSten);
}
 
Odpovědět 16. srpna 19:07
Avatar
Marek Uhlik
Člen
Avatar
Marek Uhlik:16. srpna 19:12

Tenhle return tam teda má bejt

return hodnoty[random.Next(0, pocetSten)];
 
Odpovědět 16. srpna 19:12
Avatar
Miroslav Mazal:16. srpna 20:01

Díky. Já jsem právě přemýšlel, jak ale generátor ví, že má vybírat z daných hodnot. Teď jsi mi to sám vysvětlil.

 
Odpovědět 16. srpna 20:01
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 128. Zobrazit vše