Programmēšana

Karšu dzinējs Java

Tas viss sākās, kad mēs pamanījām, ka Java spēlē ir ļoti maz kāršu spēļu lietojumprogrammu vai sīklietotņu. Vispirms mēs domājām par pāris spēļu uzrakstīšanu un sākām ar to, lai noskaidrotu galveno kodu un klases, kas nepieciešamas kāršu spēļu izveidošanai. Process turpinās, taču tagad ir diezgan stabils ietvars, kas jāizmanto dažādu kāršu spēļu risinājumu veidošanai. Šeit mēs aprakstām, kā šī sistēma tika izstrādāta, kā tā darbojas, kā arī rīkus un trikus, kas tika izmantoti, lai padarītu to noderīgu un stabilu.

Projektēšanas fāze

Izmantojot objektorientētu dizainu, ir ārkārtīgi svarīgi zināt problēmu gan iekšpusē, gan ārpusē. Pretējā gadījumā ir iespējams pavadīt daudz laika, izstrādājot klases un risinājumus, kas nav vajadzīgi vai nedarbosies atbilstoši konkrētām vajadzībām. Kāršu spēļu gadījumā viena pieeja ir vizualizēt to, kas notiek, kad viena, divas vai vairākas personas spēlē kārtis.

Kāršu klājā parasti ir 52 kārtis četros dažādos uzvalkos (dimanti, sirsniņas, nūjas, lāpstas), kuru vērtības svārstās no deuce līdz karalim, kā arī ace. Tūlīt rodas problēma: atkarībā no spēles noteikumiem dūži var būt vai nu zemākā kartes vērtība, vai lielākā, vai abi.

Turklāt ir spēlētāji, kuri paņem kārtis no klāja rokā un pārvalda roku, balstoties uz noteikumiem. Varat vai nu rādīt kartes visiem, novietojot tās uz galda, vai arī privāti apskatīt. Atkarībā no konkrētā spēles posma jūsu rokā var būt N karšu skaits.

Šādi analizējot posmus, tiek atklāti dažādi modeļi. Tagad mēs izmantojam gadījuma virzītu pieeju, kā aprakstīts iepriekš, kas ir dokumentēta Ivara Jakobsona dokumentā Uz objektu orientēta programmatūras inženierija. Šajā grāmatā viena no pamatidejām ir nodarbību modelēšana, balstoties uz reālās dzīves situācijām. Tas ļauj daudz vieglāk saprast, kā darbojas attiecības, kas no kā atkarīgs un kā darbojas abstrakcijas.

Mums ir tādas klases kā CardDeck, Hand, Card un RuleSet. CardDeck sākumā būs 52 Card objekti, un CardDeck būs mazāk Card objektu, jo tie tiek ievilkti objektā Hand. Roku objekti runā ar objektu RuleSet, kurā ir visi spēles noteikumi. Padomājiet par RuleSet kā spēles rokasgrāmatu.

Vektoru klases

Šajā gadījumā mums bija nepieciešama elastīga datu struktūra, kas apstrādā dinamiskas ierakstu izmaiņas, kas izslēdza masīva datu struktūru. Mēs arī vēlējāmies vienkāršu veidu, kā pievienot ieliktņa elementu un, ja iespējams, izvairīties no daudz kodēšanas. Ir pieejami dažādi risinājumi, piemēram, dažādas bināro koku formas. Tomēr pakotnei java.util ir klase Vector, kas īsteno objektu masīvu, kas pēc vajadzības aug un samazinās pēc izmēra, kas bija tieši tas, kas mums vajadzīgs. (Vektora dalībnieka funkcijas pašreizējā dokumentācijā nav pilnībā izskaidrotas; šajā rakstā sīkāk paskaidrots, kā vektoru klasi var izmantot līdzīgiem dinamisku objektu saraksta gadījumiem.) Vector klases trūkums ir papildu atmiņas izmantošana, jo ir daudz atmiņas. kopēšana, kas veikta aiz ainas. (Šī iemesla dēļ masīvi vienmēr ir labāki; tiem ir statisks izmērs, tāpēc kompilators varētu izdomāt koda optimizācijas veidus). Turklāt, izmantojot lielākus objektu komplektus, mums varētu būt sodi par uzmeklēšanas laikiem, bet lielākais Vector, ko mēs iedomājāmies, bija 52 ieraksti. Tas joprojām ir saprātīgi šajā gadījumā, un ilgie uzmeklēšanas laiki neraizējās.

Tālāk sniegts īss paskaidrojums par katras klases izveidi un ieviešanu.

Karšu klase

Kartes klase ir ļoti vienkārša: tajā ir vērtības, kas norāda krāsu un vērtību. Tam var būt arī norādes uz GIF attēliem un līdzīgām entītijām, kas apraksta karti, ieskaitot iespējamu vienkāršu rīcību, piemēram, animāciju (kartes uzsvēršanu) un tā tālāk.

klase Card ievieš CardConstants {public int color; public int vērtība; public String ImageName; } 

Pēc tam šie kartes objekti tiek glabāti dažādās Vector klasēs. Ņemiet vērā, ka karšu vērtības, ieskaitot krāsu, ir noteiktas saskarnē, kas nozīmē, ka katra ietvara klase varētu ieviest un tādā veidā iekļaut konstantes:

interfeiss CardConstants {// saskarnes lauki vienmēr ir publiski statiski galīgi! int SIRDS 1; int 2. DIAMONDA; int SPADE 3; int 4. KLUBI; int JACK 11; int 12. KARALIENE; int KARALIS 13; int ACE_LOW 1; int ACE_HIGH 14; } 

CardDeck klase

CardDeck klasē būs iekšējs Vector objekts, kas tiks sākotnēji inicializēts ar 52 kartes objektiem. Tas tiek darīts, izmantojot metodi, ko sauc par shuffle. Tas nozīmē, ka katru reizi, kad jūs sajaucaties, jūs patiešām sākat spēli, definējot 52 kārtis. Ir nepieciešams noņemt visus iespējamos vecos objektus un atkal sākt no noklusējuma stāvokļa (52 kartes objekti).

 public void shuffle () {// Vienmēr nulle klāja vektora un inicializējiet to no nulles. deck.removeAllElements (); 20 // Tad ievietojiet 52 kartes. Viena krāsa vienā reizē (int i ACE_LOW; i <ACE_HIGH; i ++) {Card aCard new Card (); aCard.color SIRDS; aCard.value i; deck.addElement (aCard); } // Dariet to pašu ar KLUBIEM, DIAMONDIEM un Lāpstām. } 

Kad no CardDeck izvelkam objektu Card, mēs izmantojam nejaušo skaitļu ģeneratoru, kas zina kopu, no kuras tas izvēlēsies nejaušu pozīciju vektora iekšienē. Citiem vārdiem sakot, pat ja Kartes objekti ir sakārtoti, nejaušības funkcija izvēlas patvaļīgu pozīciju vektora iekšienē esošo elementu ietvaros.

Šī procesa ietvaros mēs arī noņemam faktisko objektu no CardDeck vektora, kad mēs nododam šo objektu Hand klasei. Vektora klase kartē reālo karšu klāja un rokas situāciju, nododot karti:

 publiska karšu izloze () {Card aCard null; int pozīcija (int) (Math.random () * (klāja.izmērs = ())); izmēģiniet {aCard (Card) deck.elementAt (position); } catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } klājs.removeElementAt (pozīcija); atgriezt aCard; } 

Ņemiet vērā, ka ir labi noķert visus iespējamos izņēmumus, kas saistīti ar objekta izņemšanu no vektora no vietas, kuras nav.

Ir lietderības metode, kas atkārto visus vektora elementus un izsauc citu metodi, kas atcels ASCII vērtības / krāsu pāra virkni. Šī funkcija ir noderīga, atkļūdojot gan klases Deck, gan Hand. Roku klasē vektoru uzskaitījuma pazīmes tiek izmantotas daudz:

 public void dump () {Uzskaitījuma enum deck.elements (); while (enum.hasMoreElements ()) {Kartes karte (Karte) enum.nextElement (); RuleSet.printValue (karte); }} 

Roku klase

Šajā klasē Roku klase ir īsts darba zirgs. Lielākā daļa no prasītās uzvedības bija kaut kas tāds, ko bija ļoti dabiski ievietot šajā klasē. Iedomājieties, ka cilvēki, turot rokās kartes un veicot dažādas darbības, skatoties uz Kartes objektiem.

Pirmkārt, jums ir nepieciešams arī vektors, jo daudzos gadījumos nav zināms, cik kartes tiks paņemtas. Lai gan jūs varētu ieviest masīvu, ir labi, ka arī šeit ir zināma elastība. Dabiskākā mums vajadzīgā metode ir kartes ņemšana:

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

CardHand ir vektors, tāpēc mēs šajā vektorā vienkārši pievienojam objektu Karte. Tomēr "izejas" operāciju gadījumā no rokas mums ir divi gadījumi: viens, kurā mēs parādām karti, un viens, kurā mēs gan parādām, gan izvelkam karti no rokas. Mums ir jāievieš abi, taču, izmantojot mantojumu, mēs rakstām mazāk kodu, jo kartes zīmēšana un parādīšana ir īpašs gadījums, ja vienkārši parādāt karti:

 public Card show (int position) {Card aCard null; izmēģiniet {aCard (Card) cardHand.elementAt (position); } catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } atgriezt aCard; } 20 publiska karšu izloze (int pozīcija) {Card aCard show (pozīcija); cardHand.removeElementAt (pozīcija); atgriezt aCard; } 

Citiem vārdiem sakot, izlozes gadījums ir parādīšanās gadījums ar papildu uzvedību, noņemot objektu no vektora Roku.

Rakstot testa kodu dažādām klasēm, mēs atradām arvien lielāku skaitu gadījumu, kad bija nepieciešams uzzināt par dažādām īpašām vērtībām rokā. Piemēram, dažreiz mums vajadzēja zināt, cik konkrēta veida karšu ir rokā. Vai arī noklusējuma ace zemā vērtība bija jāmaina uz 14 (augstākā vērtība) un atkal atpakaļ. Katrā gadījumā uzvedības atbalsts tika deleģēts atpakaļ klasē Rokas, jo tā bija ļoti dabiska vieta šādai uzvedībai. Atkal bija gandrīz tā, it kā cilvēka smadzenes būtu aiz rokas, veicot šos aprēķinus.

Vektoru uzskaitīšanas pazīmi var izmantot, lai uzzinātu, cik daudz noteiktu vērtību karšu bija klasē Rokas:

 public int NCards (int value) {int n 0; Uzskaites uzskaites karteHand.elements (); while (enum.hasMoreElements ()) {tempCard (Card) enum.nextElement (); // = tempCard definēts, ja (tempCard.value = vērtība) n ++; } atgriešanās n; } 

Līdzīgi jūs varētu atkārtot kartes objektus un aprēķināt kopējo karšu summu (kā testā 21) vai mainīt kartes vērtību. Ņemiet vērā, ka pēc noklusējuma visi objekti ir Java atsauces. Ja jūs izgūstat, jūsuprāt, pagaidu objektu un to modificējat, faktiskā vērtība tiek mainīta arī vektora saglabātā objekta iekšpusē. Tas ir svarīgs jautājums, kas jāpatur prātā.

RuleSet klase

RuleSet klase ir kā noteikumu grāmata, kuru šad un tad pārbaudāt, spēlējot spēli; tajā ir visa uzvedība attiecībā uz noteikumiem. Ņemiet vērā, ka iespējamās stratēģijas, ko spēļu spēlētājs var izmantot, ir balstītas vai nu uz lietotāja interfeisa atsauksmēm, vai arī uz vienkāršu vai sarežģītāku mākslīgā intelekta (AI) kodu. RuleSet uztrauc tikai to, ka tiek ievēroti noteikumi.

Šajā klasē tika iekļauta arī cita uzvedība, kas saistīta ar kartītēm. Piemēram, mēs izveidojām statisku funkciju, kas izdrukā kartes vērtības informāciju. Vēlāk to varēja ievietot arī karšu klasē kā statisku funkciju. Pašreizējā formā RuleSet klasei ir tikai viens pamatnoteikums. Tas aizņem divas kartes un nosūta atpakaļ informāciju par to, kura karte bija augstākā:

 public int augstāks (pirmā karte, otrā karte) {int kura 0; ja (one.value = ACE_LOW) one.value ACE_HIGH; ja (divi.vērtība = ACE_LOW) divi. vērtība ACE_HIGH; // Šajā kārtulā, kurā uzvar visaugstākā vērtība, mēs neņemam vērā // krāsu. ja (viena.vērtība> divas.vērtība) kurš 1; ja (viena.vērtība <divas.vērtība) kuršviens 2; ja (viena.vērtība = divas.vērtība) kurš viens 0; // Normalizējiet ACE vērtības, tāpēc ievadītajam ir tādas pašas vērtības. ja (one.value = ACE_HIGH) one.value ACE_LOW; ja (divi.vērtība = ACE_HIGH) divi. vērtība ACE_LOW; atgriezt kuru; } 

Veicot pārbaudi, jums jāmaina dūžu vērtības, kuru dabiskā vērtība ir no 1 līdz 14. Ir svarīgi pēc tam mainīt vērtības uz vienu, lai izvairītos no iespējamām problēmām, jo ​​šajā kontekstā mēs pieņemam, ka dūži vienmēr ir viens.

21 gadījumā mēs apakškategoriju RuleSet izveidojām, lai izveidotu TwentyOneRuleSet klasi, kas zina, kā saprast, vai roka ir zem 21, tieši 21 vai virs 21. Tajā ņemtas vērā arī ace vērtības, kas varētu būt vai nu viens, vai 14, un mēģina noskaidrot labāko iespējamo vērtību. (Lai iegūtu vairāk piemēru, skatiet pirmkodu.) Tomēr spēlētājam ir jānosaka stratēģijas; šajā gadījumā mēs uzrakstījām vienkāršu domājošu AI sistēmu, kur, ja pēc divām kārtīm jūsu roka ir zem 21, jūs paņemat vēl vienu karti un apstājaties.

Kā izmantot nodarbības

Ir diezgan vienkārši izmantot šo sistēmu:

 myCardDeck new CardDeck (); myRules new RuleSet (); rokaJauna roka (); handB new Hand (); DebugClass.DebugStr ("Izvelciet pa piecām kartītēm, lai pasniegtu A un B roku"); for (int i 0; i <NCARDS; i ++) {handA.take (myCardDeck.draw ()); handB.take (myCardDeck.draw ()); } // Pārbaudes programmas, atspējojiet, komentējot vai izmantojot DEBUG karodziņus. testHandValues ​​(); testCardDeckOperations (); testCardValues ​​(); testHighestCardValues ​​(); tests21 (); 

Dažādās testa programmas ir izolētas atsevišķās statisko vai nestacionāro dalībnieku funkcijās. Izveidojiet tik daudz roku, cik vēlaties, paņemiet kartes un ļaujiet atkritumu savākšanai atbrīvoties no neizmantotajām rokām un kartēm.

Jūs izsaucat RuleSet, norādot rokas vai kartes objektu, un, pamatojoties uz atgriezto vērtību, jūs zināt rezultātu:

 DebugClass.DebugStr ("Salīdziniet otro karti A un B rokā"); int uzvarētājs myRules.higher (handA.show (1), = handB.show (1)); if (uzvarētājs = 1) o.println ("Rokam A bija augstākā karte."); else if (uzvarētājs = 2) o.println ("B rokai bija visaugstākā karte."); else o.println ("Tas bija neizšķirts."); 

Vai arī 21 gadījumā:

 int rezultāts myTwentyOneGame.isTwentyOne (handC); if (rezultāts = 21) o.println ("Mēs saņēmām Divdesmit Vienu!"); else if (rezultāts> 21) o.println ("Mēs zaudējām" + rezultāts); else {o.println ("Mēs ņemam citu karti"); // ...} 

Testēšana un atkļūdošana

Īstenojot faktisko sistēmu, ir ļoti svarīgi uzrakstīt testa kodu un piemērus. Tādā veidā jūs vienmēr zināt, cik labi darbojas ieviešanas kods; jūs saprotat faktus par funkcijām un detalizētu informāciju par ieviešanu. Ņemot vērā vairāk laika, mēs būtu ieviesuši pokeru - šāds pārbaudes gadījums būtu sniedzis vēl lielāku ieskatu problēmā un parādījis, kā no jauna definēt ietvaru.

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