Kartica u Javi

Sve je započelo kada smo primijetili da na Javi postoji vrlo malo aplikacija ili apleta s kartama. Prvo smo razmišljali o tome da napišemo nekoliko igara, a započeli smo pronalaženjem osnovnog koda i klasa potrebnih za stvaranje igara s kartama. Proces se nastavlja, ali sada postoji prilično stabilan okvir koji se koristi za stvaranje različitih rješenja za igranje karata. Ovdje opisujemo kako je ovaj okvir dizajniran, kako djeluje te alate i trikove koji su korišteni da bi bio koristan i stabilan.

Faza dizajna

Kod objektno orijentiranog dizajna izuzetno je važno znati problem iznutra i izvana. Inače je moguće potrošiti puno vremena na dizajniranje nastave i rješenja koja nisu potrebna ili neće raditi u skladu s određenim potrebama. U slučaju kartanja, jedan je pristup vizualizirati što se događa kada jedna, dvije ili više osoba igra karte.

Špil karata obično sadrži 52 karte u četiri različita odijela (dijamanti, srca, palice, pikovi), s vrijednostima u rasponu od dvojke do kralja, plus as. Odmah se pojavljuje problem: ovisno o pravilima igre, asovi mogu biti ili najniža vrijednost karte, najveća ili oboje.

Nadalje, postoje igrači koji uzimaju karte s špila u ruku i upravljaju rukom na temelju pravila. Karte možete pokazati svima stavljajući ih na stol ili ih pogledati privatno. Ovisno o određenoj fazi igre, u ruci biste mogli imati N broj karata.

Analizom faza na ovaj način otkrivaju se različiti obrasci. Sada koristimo pristup temeljen na slučajevima, kao što je gore opisano, a koji je dokumentiran u Objektno orijentiranom softverskom inženjerstvu Ivara Jacobsona . U ovoj je knjizi jedna od osnovnih ideja modeliranje nastave na temelju situacija iz stvarnog života. To mnogo olakšava razumijevanje kako funkcioniraju odnosi, što ovisi o čemu i kako funkcioniraju apstrakcije.

Imamo klase kao što su CardDeck, Hand, Card i RuleSet. CardDeck će na početku sadržavati 52 predmeta s kartice, a CardDeck će imati manje predmeta s kartice jer su uvučeni u predmet Hand. Ručni predmeti razgovaraju s RuleSet objektom koji ima sva pravila koja se odnose na igru. Zamislite RuleSet kao priručnik za igru.

Vektorske satove

U ovom nam je slučaju bila potrebna fleksibilna struktura podataka koja obrađuje dinamičke promjene unosa, što je eliminiralo strukturu podataka Array. Također smo željeli jednostavan način za dodavanje umetnutog elementa i izbjegavanje puno kodiranja ako je moguće. Dostupna su različita rješenja, poput različitih oblika binarnih stabala. Međutim, paket java.util ima klasu Vector koja implementira niz objekata koji rastu i smanjuju se po potrebi, što je bilo upravo ono što smo trebali. (Funkcije člana Vector nisu u potpunosti objašnjene u trenutnoj dokumentaciji; ovaj će članak dodatno objasniti kako se klasa Vector može koristiti za slične instance dinamičnog popisa objekata.) Nedostatak kod klasa Vector dodatna je upotreba memorije zbog puno memorije kopiranje izvršeno iza kulisa. (Iz tog su razloga nizovi uvijek bolji; oni su statične veličine,kako bi kompajler mogao smisliti načine za optimizaciju koda). Također, kod većih skupova objekata mogli bismo imati kazne zbog vremena pretraživanja, ali najveći Vektor kojeg smo se mogli sjetiti bio je 52 unosa. To je još uvijek razumno za ovaj slučaj, a duga vremena pretraživanja nisu bila zabrinjavajuća.

Slijedi kratko objašnjenje kako je svaki razred osmišljen i implementiran.

Razred kartice

Klasa Card vrlo je jednostavna: sadrži vrijednosti koje označavaju boju i vrijednost. Također može imati pokazivače na GIF slike i slične entitete koji opisuju karticu, uključujući moguće jednostavno ponašanje poput animacije (okretanje kartice) i tako dalje.

class Card implementira CardConstants {public int color; javna int vrijednost; javni String ImageName; }

Ovi se objekti kartice zatim pohranjuju u razne vektorske klase. Imajte na umu da su vrijednosti za kartice, uključujući boju, definirane u sučelju, što znači da bi svaka klasa u okviru mogla implementirati i na taj način uključivati ​​konstante:

interface CardConstants {// polja sučelja su uvijek javna statička konačna! int SRCA 1; int DIJAMANT 2; int SPADE 3; int KLUBOVI 4; int JACK 11; int KRALJICA 12; int KRALJ 13; int ACE_LOW 1; int ACE_HIGH 14; }

CardDeck klasa

Klasa CardDeck imat će unutarnji objekt Vector, koji će biti unaprijed inicijaliziran s 52 objekta kartice. To se radi metodom koja se naziva miješanje. Implikacija je da svaki put kad promiješate igru ​​zaista započnete definiranjem 52 karte. Potrebno je ukloniti sve moguće stare predmete i ponovno krenuti iz zadanog stanja (52 predmeta na kartici).

public void shuffle () {// Uvijek nulirajte vektor palube i inicijalizirajte ga ispočetka. deck.removeAllElements (); 20 // Zatim umetnite 52 karte. Jedna po jedna boja za (int i ACE_LOW; i <ACE_HIGH; i ++) {Card aCard new Card (); aCard.color SRCA; aCard.value i; deck.addElement (aCard); } // Učinite isto za KLUBOVE, DIJAMANTE I LAKOVE. }

Kada crtamo objekt Card iz CardDeck-a, koristimo generator slučajnih brojeva koji zna skup iz kojeg će odabrati slučajni položaj unutar vektora. Drugim riječima, čak i ako su predmeti Card poredani, slučajna funkcija odabrat će proizvoljnu poziciju unutar opsega elemenata unutar Vektora.

Kao dio ovog postupka uklanjamo i stvarni objekt iz vektora CardDeck dok prenosimo ovaj objekt u klasu Hand. Klasa Vector preslikava stvarnu situaciju špila i karata dodavanjem karte:

javno izvlačenje kartice () {Card aCard null; int položaj (int) (Math.random () * (deck.size = ())); isprobajte {aCard (Card) deck.elementAt (position); } catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } deck.removeElementAt (pozicija); vratiti aCard; }

Imajte na umu da je dobro uhvatiti sve moguće iznimke povezane s uzimanjem predmeta iz Vektora s položaja koji nije prisutan.

Postoji korisna metoda koja prelazi kroz sve elemente u vektoru i poziva drugu metodu koja će izbaciti niz ASCII vrijednosti / para boja. Ova je značajka korisna prilikom uklanjanja pogrešaka i klasa Deck i Hand. Značajke popisivanja vektora često se koriste u klasi Hand:

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

Razred ruku

Predavanje Hand je pravi radni konj u ovom okviru. Većina potrebnog ponašanja bilo je nešto što je bilo vrlo prirodno smjestiti u ovaj razred. Zamislite da ljudi drže karte u rukama i rade razne operacije dok gledaju predmete s Karte.

Prvo, potreban vam je i vektor, jer je u mnogim slučajevima nepoznato koliko će karata biti preuzeto. Iako biste mogli implementirati niz, dobro je i ovdje imati određenu fleksibilnost. Najprirodnija metoda koja nam treba je uzeti kartu:

javno poništavanje (Card theCard) {cardHand.addElement (theCard); }

CardHandje vektor, pa samo dodajemo objekt Card u ovaj vektor. Međutim, u slučaju operacija "izlaza" iz ruke, imamo dva slučaja: jedan u kojem pokazujemo kartu i jedan u kojem oboje pokazujemo i izvlačimo kartu iz ruke. Moramo implementirati oboje, ali koristeći nasljeđivanje napišemo manje koda, jer je crtanje i pokazivanje kartice poseban slučaj od samo pokazivanja kartice:

javna emisija karata (int pozicija) {Card aCard null; isprobajte {aCard (Card) cardHand.elementAt (position); } catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } vratiti aCard; } 20 javnih izvlačenja karata (int pozicija) {Pokazivanje karata aCard (pozicija); cardHand.removeElementAt (pozicija); vratiti aCard; }

Drugim riječima, slučaj izvlačenja je izložbeni slučaj, s dodatnim ponašanjem uklanjanja predmeta iz vektora Hand.

Pisanjem testnog koda za različite razrede, otkrili smo sve veći broj slučajeva u kojima je bilo potrebno saznati o raznim posebnim vrijednostima u ruci. Na primjer, ponekad smo morali znati koliko je karata određenog tipa u ruci. Ili se zadana ace niska vrijednost jedne morala promijeniti u 14 (najviša vrijednost) i vratiti. U svakom slučaju podrška za ponašanje vraćena je natrag u klasu Hand, jer je to bilo sasvim prirodno mjesto za takvo ponašanje. Opet, to je bilo gotovo kao da ljudski mozak stoji iza ruke i vrši ove izračune.

Značajka popisivanja vektora može se koristiti za otkrivanje koliko je karata određene vrijednosti bilo prisutno u klasi Hand:

 public int NCards (int value) { int n 0; Enumeration enum cardHand.elements (); while (enum.hasMoreElements ()) { tempCard (Card) enum.nextElement (); // = tempCard defined if (tempCard.value= value) n++; } return n; } 

Similarly, you could iterate through the card objects and calculate the total sum of cards (as in the 21 test), or change the value of a card. Note that, by default, all objects are references in Java. If you retrieve what you think is a temporary object and modify it, the actual value is also changed inside the object stored by the vector. This is an important issue to keep in mind.

RuleSet class

The RuleSet class is like a rule book that you check now and then when you play a game; it contains all the behavior concerning the rules. Note that the possible strategies a game player may use are based either on user interface feedback or on simple or more complex artificial intelligence (AI) code. All the RuleSet worries about is that the rules are followed.

Other behaviors related to cards were also placed into this class. For example, we created a static function that prints the card value information. Later, this could also be placed into the Card class as a static function. In the current form, the RuleSet class has just one basic rule. It takes two cards and sends back information about which card was the highest one:

 public int higher (Card one, Card two) { int whichone 0; if (one.value= ACE_LOW) one.value ACE_HIGH; if (two.value= ACE_LOW) two.value ACE_HIGH; // In this rule set the highest value wins, we don't take into // account the color. if (one.value > two.value) whichone 1; if (one.value < two.value) whichone 2; if (one.value= two.value) whichone 0; // Normalize the ACE values, so what was passed in has the same values. if (one.value= ACE_HIGH) one.value ACE_LOW; if (two.value= ACE_HIGH) two.value ACE_LOW; return whichone; } 

You need to change the ace values that have the natural value of one to 14 while doing the test. It's important to change the values back to one afterward to avoid any possible problems as we assume in this framework that aces are always one.

In the case of 21, we subclassed RuleSet to create a TwentyOneRuleSet class that knows how to figure out if the hand is below 21, exactly 21, or above 21. It also takes into account the ace values that could be either one or 14, and tries to figure out the best possible value. (For more examples, consult the source code.) However, it's up to the player to define the strategies; in this case, we wrote a simple-minded AI system where if your hand is below 21 after two cards, you take one more card and stop.

How to use the classes

It is fairly straightforward to use this framework:

 myCardDeck new CardDeck (); myRules new RuleSet (); handA new Hand (); handB new Hand (); DebugClass.DebugStr ("Draw five cards each to hand A and hand B"); for (int i 0; i < NCARDS; i++) { handA.take (myCardDeck.draw ()); handB.take (myCardDeck.draw ()); } // Test programs, disable by either commenting out or using DEBUG flags. testHandValues (); testCardDeckOperations(); testCardValues(); testHighestCardValues(); test21(); 

The various test programs are isolated into separate static or non-static member functions. Create as many hands as you want, take cards, and let the garbage collection get rid of unused hands and cards.

You call the RuleSet by providing the hand or card object, and, based on the returned value, you know the outcome:

 DebugClass.DebugStr ("Compare the second card in hand A and Hand B"); int winner myRules.higher (handA.show (1), = handB.show (1)); if (winner= 1) o.println ("Hand A had the highest card."); else if (winner= 2) o.println ("Hand B had the highest card."); else o.println ("It was a draw."); 

Or, in the case of 21:

 int result myTwentyOneGame.isTwentyOne (handC); if (result= 21) o.println ("We got Twenty-One!"); else if (result > 21) o.println ("We lost " + result); else { o.println ("We take another card"); // ... } 

Testing and debugging

Vrlo je važno napisati testni kôd i primjere dok implementirate stvarni okvir. Na taj način u svakom trenutku znate koliko dobro funkcionira implementacijski kod; shvatite činjenice o značajkama i pojedinostima o provedbi. Dali bismo više vremena, implementirali bismo poker - takav testni slučaj pružio bi još bolji uvid u problem i pokazao bi kako redefinirati okvir.