Programiranje

Card engine v Javi

Vse se je začelo, ko smo opazili, da je v Java napisanih zelo malo aplikacij ali aplikacij s kartami. Najprej smo razmišljali o tem, da bi napisali nekaj iger, najprej pa smo ugotovili osnovno kodo in razrede, potrebne za ustvarjanje iger s kartami. Postopek se nadaljuje, zdaj pa je na voljo dokaj stabilen okvir za ustvarjanje različnih rešitev s kartami. Tukaj opisujemo, kako je bil ta okvir zasnovan, kako deluje ter orodja in trike, s katerimi je bil uporaben in stabilen.

Faza oblikovanja

Pri objektno usmerjenem oblikovanju je izredno pomembno, da poznamo težavo znotraj in zunaj. V nasprotnem primeru lahko porabite veliko časa za oblikovanje razredov in rešitev, ki niso potrebne ali ne bodo delovale glede na posebne potrebe. V primeru iger s kartami je en pristop vizualizirati, kaj se dogaja, ko ena, dve ali več oseb igra karte.

Kart s kartami običajno vsebuje 52 kart v štirih različnih oblekah (diamanti, srca, palice, pike), katerih vrednosti segajo od dvojke do kralja in asa. Takoj se pojavi težava: asi so lahko odvisno od pravil igre najnižje vrednosti karte, najvišje ali oboje.

Poleg tega obstajajo igralci, ki vzamejo karte s krova v roko in upravljajo z igro na podlagi pravil. Karte lahko pokažete vsem tako, da jih položite na mizo ali si jih ogledate zasebno. Glede na posamezno stopnjo igre boste morda imeli v roki N število kart.

Analiza faz na ta način razkriva različne vzorce. Zdaj uporabljamo pristop na podlagi primerov, kot je opisan zgoraj, ki je dokumentiran v prispevku Ivarja Jacobsona Objektno usmerjeno programsko inženirstvo. V tej knjigi je ena izmed osnovnih idej modeliranje razredov, ki temeljijo na resničnih situacijah. Tako je veliko lažje razumeti, kako delujejo odnosi, kaj je odvisno od česa in kako delujejo abstrakcije.

Imamo razrede, kot so CardDeck, Hand, Card in RuleSet. CardDeck bo na začetku vseboval 52 predmetov Card, CardDeck pa bo imel manj predmetov Card, saj so ti vrisani v predmet Hand. Ročni predmeti se pogovarjajo s predmetom RuleSet, ki ima vsa pravila v zvezi z igro. Predstavljajte si RuleSet kot priročnik za igre.

Vektorski razredi

V tem primeru smo potrebovali prilagodljivo podatkovno strukturo, ki obravnava dinamične spremembe vnosa, kar je odpravilo podatkovno strukturo Array. Želeli smo tudi preprost način za dodajanje vstavnega elementa in se izognili veliko kodiranju, če je mogoče. Na voljo so različne rešitve, na primer različne oblike binarnih dreves. Vendar pa ima paket java.util razred Vector, ki izvaja vrsto predmetov, ki rastejo in se po potrebi skrčijo, kar je bilo točno tisto, kar smo potrebovali. (Funkcije članov Vector v trenutni dokumentaciji niso v celoti razložene; ta članek bo nadalje pojasnil, kako je mogoče razred Vector uporabiti za podobne primerke seznamov dinamičnih predmetov.) Pomanjkljivost pri razredih Vector je dodatna uporaba pomnilnika zaradi veliko pomnilnika kopiranje v zakulisju. (Iz tega razloga so nizi vedno boljši; so statične velikosti, zato bi lahko prevajalnik našel načine za optimizacijo kode). Tudi pri večjih naborih predmetov bomo morda kaznovali čas iskanja, vendar je bil največji vektor, ki smo si ga lahko omislili, 52 vnosov. To je v tem primeru še vedno smiselno in dolgi časi iskanja niso bili zaskrbljujoči.

Sledi kratka razlaga, kako je bil vsak razred zasnovan in izveden.

Razred kartice

Razred Card je zelo preprost: vsebuje vrednosti, ki označujejo barvo in vrednost. Lahko ima tudi kazalce na slike GIF in podobne entitete, ki opisujejo kartico, vključno z morebitnim preprostim vedenjem, kot je animacija (obrnite kartico) itd.

razred Card izvaja CardConstants {public int color; javna int vrednost; javni String ImageName; } 

Ti predmeti Card se nato shranijo v različne vektorske razrede. Upoštevajte, da so vrednosti kartic, vključno z barvo, definirane v vmesniku, kar pomeni, da bi lahko vsak razred v okviru implementiral in na ta način vključeval konstante:

interface CardConstants {// vmesniška polja so vedno javna statična končna! int SRCA 1; int DIAMOND 2; int SPADE 3; int KLUBI 4; int JACK 11; int KRALJICA 12; int KRALJ 13; int ACE_LOW 1; int ACE_HIGH 14; } 

ClassDeck razred

Razred CardDeck bo imel notranji objekt Vector, ki bo predhodno inicializiran z 52 predmeti kartice. To se naredi z metodo, imenovano mešanje. Posledica tega je, da vsakič, ko premešate, začnete igro z določitvijo 52 kart. Odstraniti je treba vse možne stare predmete in znova zagnati iz privzetega stanja (52 predmetov kartice).

 public void shuffle () {// Vedno krovni vektor nastavite na nič in ga inicializirajte iz nič. deck.removeAllElements (); 20 // Nato vstavite 52 kartic. Ena barva naenkrat za (int i ACE_LOW; i <ACE_HIGH; i ++) {Card aCard new Card (); aCard.color SRCA; aCard.value i; deck.addElement (aCard); } // Enako naredimo za KLUBE, DIAMANTE in SPADE. } 

Ko iz CardDecka narišemo predmet Card, uporabljamo generator naključnih števil, ki pozna niz, iz katerega bo izbral naključni položaj znotraj vektorja. Z drugimi besedami, tudi če so predmeti Card urejeni, bo naključna funkcija izbrala poljuben položaj znotraj obsega elementov znotraj vektorja.

Kot del tega postopka odstranjujemo tudi dejanski objekt iz vektorja CardDeck, ko ta predmet posredujemo v razred Hand. Razred Vector preslika dejansko stanje krova in roke s podajanjem karte:

 javni izvleček kartice () {Card aCard null; položaj int (int) (Math.random () * (deck.size = ())); poskusite {aCard (Card) deck.elementAt (position); } catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } deck.removeElementAt (položaj); vrni aCard; } 

Upoštevajte, da je dobro ujeti morebitne izjeme, povezane z jemanjem predmeta iz vektorja s položaja, ki ni prisoten.

Obstaja uporabna metoda, ki ponovi vse elemente v vektorju in pokliče drugo metodo, ki bo odvrgla niz vrednosti ASCII / par barv. Ta funkcija je uporabna pri razhroščevanju razredov Deck in Hand. Značilnosti naštevanja vektorjev se v razredu Hand pogosto uporabljajo:

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

Razred roke

Razred Hand je v tem okviru pravi delovni konj. Večina zahtevanega vedenja je bilo nekaj, kar je bilo zelo naravno umestiti v ta razred. Predstavljajte si ljudi, ki imajo v rokah karte in izvajajo različne operacije, medtem ko gledajo predmete s kartice.

Najprej potrebujete tudi vektor, saj je v mnogih primerih neznano, koliko kart bo pobranih. Čeprav bi lahko implementirali matriko, je dobro, da imate tudi tu nekaj prilagodljivosti. Najbolj naravna metoda, ki jo potrebujemo, je vzeti karto:

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

CardHand je vektor, zato v ta vektor samo dodajamo predmet Card. Vendar imamo v primeru "izhodnih" operacij iz roke dva primera: enega, v katerem pokažemo kartico, in enega, v katerem oba pokažemo in potegnemo karto iz roke. Izvesti moramo oboje, vendar z dedovanjem napišemo manj kode, ker je risanje in prikazovanje kartice poseben primer samo prikaza karte:

 javna razstava kart (int položaj) {Card aCard null; poskusite {aCard (Card) cardHand.elementAt (position); } catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } vrni aCard; } 20 javnih izvlečkov kartic (int položaj) {Prikaz kartice aCard (položaj); cardHand.removeElementAt (položaj); vrni aCard; } 

Z drugimi besedami, primer risanja je vitrina, z dodatnim vedenjem odstranjevanja predmeta iz vektorja Hand.

Pri pisanju testne kode za različne razrede smo ugotovili vedno večje število primerov, v katerih je bilo treba izvedeti o različnih posebnih vrednostih v roki. Na primer, včasih smo morali vedeti, koliko kart določene vrste je v roki. Ali pa je bilo treba privzeto nizko vrednost enega spremeniti v 14 (najvišja vrednost) in nazaj. Podpora za vedenje je bila v vsakem primeru delegirana nazaj v razred Hand, saj je bilo to zelo naravno mesto za takšno vedenje. Ponovno je bilo skoraj tako, kot da za temi izračuni stojijo človeški možgani.

S funkcijo naštevanja vektorjev lahko ugotovimo, koliko kart določene vrednosti je bilo prisotnih v razredu Hand:

 public int NCards (int value) {int n 0; Enumeration enum cardHand.elements (); while (enum.hasMoreElements ()) {tempCard (Card) enum.nextElement (); // = tempCard definirano, če (tempCard.value = vrednost) n ++; } vrnitev n; } 

Podobno lahko pregledujete predmete kartice in izračunate skupno vsoto kart (kot v testu 21) ali spremenite vrednost kartice. Upoštevajte, da so privzeto vsi predmeti sklici v Javi. Če poiščete začasni objekt in ga spremenite, se spremeni tudi dejanska vrednost znotraj predmeta, ki ga shrani vektor. To je pomembno vprašanje, ki ga je treba upoštevati.

Razred RuleSet

Razred RuleSet je kot knjiga s pravili, ki jo občasno preverite, ko igrate igro; vsebuje vse vedenja v zvezi s pravili. Upoštevajte, da možne strategije, ki jih igralec igre lahko uporabi, temeljijo na povratnih informacijah uporabniškega vmesnika ali na preprosti ali bolj zapleteni kodi umetne inteligence (AI). Vse, kar skrbi RuleSet, je, da se pravila upoštevajo.

V ta razred so bila uvrščena tudi druga vedenja, povezana s kartami. Na primer, ustvarili smo statično funkcijo, ki natisne podatke o vrednosti kartice. Kasneje bi to lahko tudi uvrstili v razred Card kot statično funkcijo. V trenutni obliki ima razred RuleSet samo eno osnovno pravilo. Vzame dve karti in vrne informacije o tem, katera karta je bila najvišja:

 javni int višji (kartica ena, kartica druga) {int whichone 0; if (one.value = ACE_LOW) one.value ACE_HIGH; if (two.value = ACE_LOW) two.value ACE_HIGH; // V tem pravilu nastavimo najvišje vrednosti zmag, ne upoštevamo // barve. if (one.value> two.value) kateri 1; if (one.value <two.value) whichone 2; if (one.value = two.value) whichone 0; // Normaliziramo vrednosti ACE, tako da je bilo tisto, kar je bilo posredovano, enake vrednosti. if (one.value = ACE_HIGH) one.value ACE_LOW; if (two.value = ACE_HIGH) two.value ACE_LOW; vrnitev katera; } 

Med izvajanjem testa morate spremeniti vrednosti ace, ki imajo naravno vrednost od ena do 14. Pomembno je, da vrednosti nato spremenite nazaj na eno, da se izognete morebitnim težavam, saj v tem okviru predvidevamo, da so asi vedno eno.

V primeru 21 smo podrazred RuleSet ustvarili razred TwentyOneRuleSet, ki ve, kako ugotoviti, ali je kombinacija pod 21, natanko 21 ali nad 21. Upošteva tudi vrednosti asa, ki bi lahko bile ena ali 14, in poskuša ugotoviti najboljšo možno vrednost. (Za več primerov glejte izvorno kodo.) Vendar pa mora igralec določiti strategije; v tem primeru smo napisali preprost sistem AI, kjer, če je tvoja roka pod dvema kartama pod 21, vzameš še eno karto in se ustaviš.

Kako uporabljati razrede

Uporaba tega okvira je dokaj enostavna:

 myCardDeck novo CardDeck (); myRules new RuleSet (); handA nov Hand (); handB nov Hand (); DebugClass.DebugStr ("Nariši po pet kart v roko A in roko B"); for (int i 0; i <NCARDS; i ++) {handA.take (myCardDeck.draw ()); handB.take (myCardDeck.draw ()); } // Preizkusite programe, onemogočite jih tako, da jih komentirate ali uporabite zastavice DEBUG. testHandValues ​​(); testCardDeckOperations (); testCardValues ​​(); testHighestCardValues ​​(); test21 (); 

Različni testni programi so ločeni v ločene statične ali nestatične funkcije članov. Ustvarite toliko rok, kolikor želite, vzemite karte in pustite, da se smetar znebi neuporabljenih rok in kart.

RuleSet pokličete tako, da navedete predmet roke ali kartice in na podlagi vrnjene vrednosti poznate rezultat:

 DebugClass.DebugStr ("Primerjaj drugo karto v roki A in Roki B"); int zmagovalec myRules.higher (handA.show (1), = handB.show (1)); if (zmagovalec = 1) o.println ("kombinacija A je imela najvišjo karto."); sicer če (zmagovalec = 2) o.println ("kombinacija B je imela najvišjo karto."); else o.println ("Bilo je neodločeno."); 

Ali v primeru 21:

 int rezultat myTwentyOneGame.isTwentyOne (handC); if (result = 21) o.println ("Dobili smo enaindvajset!"); sicer if (rezultat> 21) o.println ("Izgubili smo" + rezultat); else {o.println ("Vzamemo drugo kartico"); // ...} 

Testiranje in odpravljanje napak

Zelo pomembno je, da med izvajanjem dejanskega okvira napišete testno kodo in primere. Na ta način ves čas veste, kako dobro deluje izvedbena koda; spoznate dejstva o značilnostih in podrobnostih o izvedbi. Če bi imeli več časa, bi uvedli poker - takšen testni primer bi omogočil še večji vpogled v težavo in pokazal, kako na novo opredeliti ogrodje.

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