Programování

Kartový modul v Javě

Všechno to začalo, když jsme si všimli, že v Javě je napsáno jen velmi málo aplikací nebo appletů pro karetní hry. Nejprve jsme přemýšleli o psaní několika her a začali jsme zjišťováním základního kódu a tříd potřebných pro vytváření karetních her. Proces pokračuje, ale nyní existuje poměrně stabilní rámec, který lze použít pro vytváření různých řešení karetních her. Zde popisujeme, jak byl tento rámec navržen, jak funguje, a nástroje a triky, které byly použity, aby byl užitečný a stabilní.

Fáze návrhu

U objektově orientovaného designu je nesmírně důležité znát problém uvnitř i vně. Jinak je možné strávit spoustu času navrhováním tříd a řešení, která nejsou nutná nebo nebudou fungovat podle konkrétních potřeb. V případě karetních her je jedním z přístupů vizualizace toho, co se děje, když jedna, dvě nebo více osob hraje karty.

Balíček karet obvykle obsahuje 52 karet ve čtyřech různých barvách (diamanty, srdce, kluby, piky) s hodnotami od dvojky po krále plus eso. Okamžitě nastane problém: v závislosti na pravidlech hry mohou být esa buď nejnižší hodnota karty, nejvyšší nebo obě.

Kromě toho existují hráči, kteří berou karty z balíčku do ruky a spravují ruku na základě pravidel. Karty můžete všem ukázat tak, že je položíte na stůl, nebo si je prohlédnout soukromě. V závislosti na konkrétní fázi hry můžete mít v ruce N počet karet.

Analýza fází tímto způsobem odhalí různé vzory. Nyní používáme přístup založený na velkých a malých písmenech, jak je popsáno výše, který je dokumentován v Ivar Jacobson Objektově orientované softwarové inženýrství. V této knize je jednou ze základních myšlenek modelování tříd na základě skutečných situací. Díky tomu je mnohem snazší pochopit, jak vztahy fungují, co na čem závisí a jak fungují abstrakce.

Máme kurzy jako CardDeck, Hand, Card a RuleSet. CardDeck bude na začátku obsahovat 52 objektů Card a CardDeck bude mít méně Card objektů, protože jsou nakresleny do Hand objektu. Ruční objekty mluví s objektem RuleSet, který má všechna pravidla týkající se hry. Představte si RuleSet jako příručku ke hře.

Vektorové třídy

V tomto případě jsme potřebovali flexibilní datovou strukturu, která zpracovává dynamické změny vstupu, což eliminovalo datovou strukturu Array. Chtěli jsme také snadný způsob, jak přidat vložený prvek a vyhnout se spoustě kódování, pokud je to možné. K dispozici jsou různá řešení, například různé formy binárních stromů. Balíček java.util však má třídu Vector, která implementuje řadu objektů, které podle potřeby rostou a zmenšují se, což bylo přesně to, co jsme potřebovali. (Členské funkce Vector nejsou v aktuální dokumentaci plně vysvětleny; tento článek dále vysvětlí, jak lze třídu Vector použít pro podobné instance seznamů dynamických objektů.) Nevýhodou u tříd Vector je další využití paměti kvůli velké paměti kopírování provedené v zákulisí. (Z tohoto důvodu jsou pole vždy lepší; mají statickou velikost, takže kompilátor mohl zjistit způsoby, jak optimalizovat kód). Také s většími sadami objektů bychom mohli mít tresty týkající se doby vyhledávání, ale největší Vector, na který jsme si mohli vzpomenout, bylo 52 záznamů. To je pro tento případ stále rozumné a dlouhé doby vyhledávání nebyly problémem.

Následuje stručné vysvětlení, jak byla každá třída navržena a implementována.

Třída karet

Třída Card je velmi jednoduchá: obsahuje hodnoty signalizující barvu a hodnotu. Může mít také ukazatele na obrázky GIF a podobné entity, které kartu popisují, včetně možného jednoduchého chování, jako je animace (otočení karty) atd.

třída Card implementuje CardConstants {public int color; public int value; public String ImageName; } 

Tyto objekty karty jsou poté uloženy v různých vektorových třídách. Všimněte si, že hodnoty pro karty, včetně barev, jsou definovány v rozhraní, což znamená, že každá třída v rámci může implementovat a tímto způsobem zahrnují konstanty:

rozhraní CardConstants {// pole rozhraní jsou vždy veřejná statická konečná! int SRDCE 1; int DIAMOND 2; int SPADE 3; int KLUBY 4; int JACK 11; int QUEEN 12; int KING 13; int ACE_LOW 1; int ACE_HIGH 14; } 

CardDeck třída

Třída CardDeck bude mít interní objekt Vector, který bude předem inicializován 52 objekty karty. To se provádí pomocí metody zvané shuffle. Důsledkem je, že pokaždé, když zamícháte, opravdu začnete hru definováním 52 karet. Je nutné odstranit všechny možné staré objekty a začít znovu od výchozího stavu (52 karetních objektů).

 public void shuffle () {// Vždy vynulujte vektor balíčku a inicializujte jej úplně od začátku. deck.removeAllElements (); 20 // Poté vložte 52 karet. Jedna barva najednou pro (int i ACE_LOW; i <ACE_HIGH; i ++) {Card aCard new Card (); aCard.color SRDCE; aCard.value i; deck.addElement (aCard); } // Totéž proveďte pro KLUBY, DIAMANTY a RYCHLOSTI. } 

Když nakreslíme objekt Card z CardDeck, používáme generátor náhodných čísel, který zná množinu, ze které vybere náhodnou pozici uvnitř vektoru. Jinými slovy, i když jsou objednané objekty Card, náhodná funkce vybere libovolnou pozici v rozsahu prvků uvnitř Vectoru.

V rámci tohoto procesu také odstraňujeme skutečný objekt z vektoru CardDeck, když tento objekt předáváme třídě Hand. Třída Vector mapuje skutečnou situaci balíčku karet a ruky předáním karty:

 public Card draw () {Card aCard null; int pozice (int) (Math.random () * (deck.size = ())); zkuste {aCard (Card) deck.elementAt (position); } catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } deck.removeElementAt (pozice); vrátit kartu; } 

Všimněte si, že je dobré zachytit všechny možné výjimky související s převzetím objektu z Vektoru z polohy, která není přítomna.

Existuje metoda obsluhy, která iteruje všemi prvky ve vektoru a volá jinou metodu, která vypíše řetězec ASCII hodnota / pár barev. Tato funkce je užitečná při ladění tříd Deck a Hand. Funkce výčtu vektorů se hodně používají ve třídě Hand:

 public void dump () {Enumeration enum deck.elements (); while (enum.hasMoreElements ()) {Card card (Card) enum.nextElement (); RuleSet.printValue (karta); }} 

Ruční třída

Třída Hand je v tomto rámci skutečným tahounem. Většina požadovaného chování bylo něco, co bylo velmi přirozené umístit do této třídy. Představte si, že lidé drží karty v ruce a dělají různé operace, zatímco se dívají na objekty karty.

Nejprve také potřebujete vektor, protože v mnoha případech není známo, kolik karet bude vyzvednuto. I když byste mohli implementovat pole, je dobré mít i zde určitou flexibilitu. Nejpřirozenější metodou, kterou potřebujeme, je vzít si kartu:

 public void take (Card theCard) {cardHand.addElement (theCard); } 

CardHand je vektor, takže do tohoto vektoru jen přidáváme objekt Card. V případě operací „výstupu“ z ruky však máme dva případy: jeden, ve kterém ukážeme kartu, a druhý, ve kterém oba ukážeme a nakreslíme kartu z ruky. Musíme implementovat obojí, ale pomocí dědičnosti píšeme méně kódu, protože kreslení a předvádění karty je speciální případ z pouhého předvedení karty:

 public Card show (int pozice) {Card aCard null; try {aCard (Card) cardHand.elementAt (position); } catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } vrátit aCard; } 20 veřejných losování karet (int pozice) {Card aCard show (pozice); cardHand.removeElementAt (pozice); vrátit kartu; } 

Jinými slovy, případ kreslení je ukázkovým příkladem s dalším chováním odebrání objektu z Hand vektoru.

Při psaní testovacího kódu pro různé třídy jsme zjistili rostoucí počet případů, ve kterých bylo nutné zjistit různé speciální hodnoty v ruce. Například jsme někdy potřebovali vědět, kolik karet konkrétního typu bylo v ruce. Nebo musela být výchozí nízká hodnota esa jednoho změněna na 14 (nejvyšší hodnota) a zpět. V každém případě byla podpora chování delegována zpět do třídy Hand, protože to bylo velmi přirozené místo pro takové chování. Opět to bylo téměř jako by za těmito výpočty byl lidský mozek.

Funkce výčtu vektorů může být použita ke zjištění, kolik karet konkrétní hodnoty bylo ve třídě Hand přítomno:

 public int NCards (int value) {int n 0; Výčet enum cardHand.elements (); while (enum.hasMoreElements ()) {tempCard (Card) enum.nextElement (); // = tempCard definováno if (tempCard.value = value) n ++; } návrat n; } 

Podobně můžete iterovat objekty karet a vypočítat celkový součet karet (jako v testu 21), nebo změnit hodnotu karty. Všimněte si, že ve výchozím nastavení jsou všechny objekty odkazy v Javě. Pokud načtete dočasný objekt, který považujete za dočasný, a upravíte jej, změní se skutečná hodnota také uvnitř objektu uloženého vektorem. Toto je důležitá otázka, kterou je třeba mít na paměti.

Třída RuleSet

Třída RuleSet je jako kniha pravidel, kterou občas zkontrolujete při hraní hry; obsahuje veškeré chování týkající se pravidel. Upozorňujeme, že možné strategie, které hráč může použít, jsou založeny buď na zpětné vazbě uživatelského rozhraní, nebo na jednoduchém nebo složitějším kódu umělé inteligence (AI). RuleSet si dělá starosti jen s dodržováním pravidel.

Do této třídy bylo také zařazeno další chování související s kartami. Například jsme vytvořili statickou funkci, která vytiskne informace o hodnotě karty. Později by to mohlo být také umístěno do třídy Card jako statická funkce. V aktuální podobě má třída RuleSet pouze jedno základní pravidlo. Vezme dvě karty a odešle zpět informace o tom, která karta byla nejvyšší:

 public int vyšší (karta jedna, karta dvě) {int whoone 0; if (one.value = ACE_LOW) one.value ACE_HIGH; if (two.value = ACE_LOW) two.value ACE_HIGH; // V tomto pravidle nastavíme nejvyšší hodnotu vyhrává, nezohledňujeme // barvu. if (one.value> two.value) whichone 1; if (one.value <two.value) whichone 2; if (one.value = two.value) whichone 0; // Normalizujte hodnoty ACE, takže to, co bylo předáno, má stejné hodnoty. if (one.value = ACE_HIGH) one.value ACE_LOW; if (two.value = ACE_HIGH) two.value ACE_LOW; vrátit kdokoli; } 

Při provádění testu musíte změnit hodnoty es, které mají přirozenou hodnotu od 1 do 14. Je důležité změnit hodnoty zpět na jednu později, abyste se vyhnuli možným problémům, protože v tomto rámci předpokládáme, že esa jsou vždy jedna.

V případě 21 jsme podtřídili RuleSet, abychom vytvořili třídu TwentyOneRuleSet, která ví, jak zjistit, zda je handa pod 21, přesně 21 nebo nad 21. Rovněž bere v úvahu hodnoty esa, které mohou být buď jedna, nebo 14, a snaží se zjistit nejlepší možnou hodnotu. (Další příklady najdete ve zdrojovém kódu.) Je však na hráči, aby strategie definoval; v tomto případě jsme napsali jednoduchý systém AI, kde pokud je vaše ruka po dvou kartách pod 21, vezmete ještě jednu kartu a zastavíte.

Jak používat třídy

Je poměrně jednoduché použít tento rámec:

 myCardDeck nový CardDeck (); myRules new RuleSet (); handA new Hand (); handB new Hand (); DebugClass.DebugStr ("Nakreslete po pěti kartách do ruky A a ruky B"); for (int i 0; i <NCARDS; i ++) {handA.take (myCardDeck.draw ()); handB.take (myCardDeck.draw ()); } // Testovací programy, deaktivujte buď komentářem, nebo použitím příznaků DEBUG. testHandValues ​​(); testCardDeckOperations (); testCardValues ​​(); testHighestCardValues ​​(); test21 (); 

Různé testovací programy jsou izolovány do samostatných statických nebo nestatických členských funkcí. Vytvořte tolik rukou, kolik chcete, vezměte karty a nechte sběr odpadků zbavit se nepoužívaných rukou a karet.

Voláte RuleSet poskytnutím objektu ruky nebo karty a na základě vrácené hodnoty znáte výsledek:

 DebugClass.DebugStr ("Porovnejte druhou kartu v ruce A a ruce B"); int vítěz myRules.higher (handA.show (1), = handB.show (1)); if (vítěz = 1) o.println ("Hand A měl nejvyšší kartu."); jinak if (vítěz = 2) o.println ("Hand B měl nejvyšší kartu."); else o.println ("Byla to remíza."); 

Nebo v případě 21:

 int výsledek myTwentyOneGame.isTwentyOne (handC); if (result = 21) o.println ("Máme dvacet jedna!"); else if (výsledek> 21) o.println ("Ztratili jsme" + výsledek); else {o.println ("Vezmeme jinou kartu"); // ...} 

Testování a ladění

Při implementaci skutečného rámce je velmi důležité psát testovací kód a příklady. Tímto způsobem vždy víte, jak dobře implementační kód funguje; uvědomíte si fakta o funkcích a podrobnosti o implementaci. Vzhledem k více času bychom implementovali poker - takový testovací případ by poskytl ještě lepší vhled do problému a ukázal by, jak předefinovat rámec.

$config[zx-auto] not found$config[zx-overlay] not found