Mesin kad di Java

Ini semua bermula apabila kita menyedari bahawa terdapat sangat sedikit aplikasi permainan kad atau applet yang ditulis di Java. Mula-mula kami berfikir untuk menulis beberapa permainan, dan bermula dengan mencari kod teras dan kelas yang diperlukan untuk membuat permainan kad. Prosesnya berterusan, tetapi sekarang ada kerangka kerja yang cukup stabil untuk digunakan untuk membuat pelbagai penyelesaian permainan kad. Di sini kami menerangkan bagaimana kerangka ini dirancang, bagaimana ia beroperasi, dan alat dan trik yang digunakan untuk menjadikannya berguna dan stabil.

Fasa reka bentuk

Dengan reka bentuk berorientasikan objek, sangat penting untuk mengetahui masalah di dalam dan luar. Jika tidak, mungkin menghabiskan banyak masa merancang kelas dan penyelesaian yang tidak diperlukan atau tidak akan berfungsi mengikut keperluan tertentu. Dalam kes permainan kad, satu pendekatan adalah untuk memvisualisasikan apa yang sedang berlaku ketika satu, dua, atau lebih orang bermain kad.

Dek kad biasanya mengandungi 52 kad dalam empat pakaian yang berlainan (berlian, hati, kelab, sekop), dengan nilai mulai dari deuce hingga raja, ditambah ace. Segera timbul masalah: bergantung pada peraturan permainan, ace boleh menjadi nilai kad terendah, tertinggi, atau kedua-duanya.

Tambahan pula, ada pemain yang mengambil kad dari geladak ke tangan dan menguruskan tangan berdasarkan peraturan. Anda boleh menunjukkan kad kepada semua orang dengan meletakkannya di atas meja atau melihatnya secara tertutup. Bergantung pada tahap permainan tertentu, anda mungkin mempunyai jumlah kad N di tangan anda.

Menganalisis tahap dengan cara ini menunjukkan pelbagai corak. Kami sekarang menggunakan pendekatan berdasarkan kes, seperti yang dijelaskan di atas, yang didokumentasikan dalam Kejuruteraan Perisian Berorientasi Objek Ivar Jacobson . Dalam buku ini, salah satu idea asas adalah memodelkan kelas berdasarkan situasi kehidupan sebenar. Itu menjadikan lebih mudah untuk memahami bagaimana hubungan beroperasi, apa yang bergantung pada apa, dan bagaimana abstraksi beroperasi.

Kami mempunyai kelas seperti CardDeck, Hand, Card, dan RuleSet. CardDeck akan mengandungi 52 objek Kad pada permulaannya, dan CardDeck akan mempunyai lebih sedikit objek Kad kerana objek ini dilukis menjadi objek Tangan. Objek tangan bercakap dengan objek RuleSet yang mempunyai semua peraturan mengenai permainan. Fikirkan RuleSet sebagai buku panduan permainan.

Kelas vektor

Dalam kes ini, kami memerlukan struktur data yang fleksibel yang menangani perubahan kemasukan dinamik, yang menghilangkan struktur data Array. Kami juga mahukan cara mudah untuk menambahkan elemen sisipan dan mengelakkan banyak pengekodan jika boleh. Terdapat pelbagai penyelesaian yang tersedia, seperti pelbagai bentuk pokok binari. Namun, paket java.util mempunyai kelas Vektor yang menerapkan sekumpulan objek yang tumbuh dan menyusut dalam ukuran yang diperlukan, itulah yang kami perlukan. (Fungsi anggota Vektor tidak dijelaskan sepenuhnya dalam dokumentasi semasa; artikel ini akan menjelaskan lebih lanjut bagaimana kelas Vektor dapat digunakan untuk contoh senarai objek dinamik yang serupa.) Kekurangan dengan kelas Vektor adalah penggunaan memori tambahan, kerana banyak memori penyalinan dilakukan di belakang tabir. (Atas sebab ini, Susunan selalu lebih baik; ukurannya statik,jadi penyusun dapat mengetahui cara mengoptimumkan kod) Juga, dengan set objek yang lebih besar, kami mungkin mempunyai hukuman mengenai waktu pencarian, tetapi Vektor terbesar yang dapat kami fikirkan adalah 52 entri. Itu masih wajar untuk kes ini, dan masa pencarian yang panjang tidak menjadi perhatian.

Penjelasan ringkas mengenai bagaimana setiap kelas dirancang dan dilaksanakan.

Kelas kad

Kelas Kad sangat mudah: ia mengandungi nilai yang menandakan warna dan nilainya. Mungkin juga menunjukkan petunjuk kepada gambar GIF dan entiti serupa yang menerangkan kad, termasuk kemungkinan tingkah laku sederhana seperti animasi (flip a card) dan sebagainya.

kad kelas melaksanakan CardConstants {public int color; nilai int awam; String ImageName awam; }

Objek Kad ini kemudian disimpan dalam pelbagai kelas Vektor. Perhatikan bahawa nilai untuk kad, termasuk warna, ditentukan dalam antara muka, yang bermaksud bahawa setiap kelas dalam rangka dapat diterapkan dan dengan cara ini termasuk pemalar:

antara muka CardConstants {// medan antara muka selalu menjadi akhir statik awam! int HATI 1; int DIAMOND 2; int SPADE 3; int KELAB 4; int JACK 11; int QUEEN 12; int KING 13; int ACE_LOW 1; int ACE_HIGH 14; }

Kelas CardDeck

Kelas CardDeck akan mempunyai objek Vektor dalaman, yang akan diawali dengan 52 objek kad. Ini dilakukan dengan menggunakan kaedah yang disebut shuffle. Implikasinya adalah bahawa setiap kali anda melakukan rombakan, anda memang memulakan permainan dengan menentukan 52 kad. Anda perlu mengeluarkan semua objek lama yang mungkin dan bermula dari keadaan lalai lagi (52 objek kad).

rombakan kekosongan awam () {// Sentiasa sifar vektor dek dan mulakan dari awal. deck.removeAllElements (); 20 // Kemudian masukkan 52 kad. Satu warna pada satu masa untuk (int i ACE_LOW; i <ACE_HIGH; i ++) {Card aCard new Card (); aCard.color HATI; aCard.value i; deck.addElement (aCard); } // Lakukan perkara yang sama untuk CLUBS, DIAMONDS dan SPADES. }

Apabila kami menarik objek Kad dari CardDeck, kami menggunakan penjana nombor rawak yang mengetahui set dari mana ia akan memilih kedudukan rawak di dalam vektor. Dengan kata lain, walaupun objek Kad disusun, fungsi rawak akan memilih kedudukan sewenang-wenang dalam ruang lingkup elemen di dalam Vektor.

Sebagai sebahagian daripada proses ini, kami juga mengeluarkan objek sebenar dari vektor CardDeck semasa kami menyampaikan objek ini ke kelas Tangan. Kelas Vektor memetakan situasi kehidupan sebenar kad dan tangan dengan menyebarkan kad:

cabutan Kad awam () {Kad aCard null; kedudukan int (int) (Math.random () * (deck.size = ())); cuba {aCard (Card) deck.elementAt (kedudukan); } tangkapan (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } deck.removeElementAt (kedudukan); kembalikan aCard; }

Perhatikan bahawa ada baiknya menangkap pengecualian yang mungkin berkaitan dengan mengambil objek dari Vektor dari kedudukan yang tidak ada.

Terdapat kaedah utiliti yang berulang melalui semua elemen dalam vektor dan memanggil kaedah lain yang akan membuang rentetan nilai / warna ASCII. Ciri ini berguna semasa menyahpepijat kelas Deck dan Hand. Ciri penghitungan vektor banyak digunakan dalam kelas Tangan:

lambakan kekosongan awam () {Enumeration enum deck.elements (); manakala (enum.hasMoreElements ()) {Kad kad (Kad) enum.nextElement (); RuleSet.printValue (kad); }}

Kelas tangan

Kelas Tangan adalah tenaga kerja sebenar dalam rangka ini. Sebilangan besar tingkah laku yang diperlukan adalah sesuatu yang sangat wajar dilakukan di kelas ini. Bayangkan orang memegang kad di tangan mereka dan melakukan pelbagai operasi sambil melihat objek Kad.

Pertama, anda juga memerlukan vektor, kerana dalam banyak kes tidak diketahui berapa banyak kad yang akan diambil. Walaupun anda dapat menerapkan array, ada baiknya Anda memiliki kelenturan di sini juga. Kaedah yang paling semula jadi yang kita perlukan adalah mengambil kad:

pengambilan awam tidak sah (Card theCard) {cardHand.addElement (theCard); }

CardHandadalah vektor, jadi kami hanya menambahkan objek Kad ke dalam vektor ini. Walau bagaimanapun, dalam kes operasi "output" dari tangan, kita mempunyai dua kes: satu di mana kita menunjukkan kad, dan satu di mana kita berdua menunjukkan dan menarik kad dari tangan. Kita perlu melaksanakan kedua-duanya, tetapi menggunakan warisan kita menulis lebih sedikit kod kerana melukis dan menunjukkan kad adalah kes khas dari hanya menunjukkan kad:

persembahan Kad awam (kedudukan int) {Kad aCard null; cuba {aCard (Card) cardHand.elementAt (kedudukan); } tangkapan (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } kembalikan aCard; } 20 undian Kad awam (kedudukan int) {Kad aCard menunjukkan (kedudukan); cardHand.removeElementAt (kedudukan); kembalikan aCard; }

Dengan kata lain, kotak gambar adalah kes pertunjukan, dengan tingkah laku tambahan untuk mengeluarkan objek dari vektor Tangan.

Dalam menulis kod ujian untuk pelbagai kelas, kami mendapati semakin banyak kes di mana perlu untuk mengetahui tentang pelbagai nilai khas di tangan. Sebagai contoh, kadang-kadang kita perlu mengetahui berapa banyak kad jenis tertentu yang ada di tangan. Atau nilai ace rendah satu harus diubah menjadi 14 (nilai tertinggi) dan kembali lagi. Dalam setiap kes, sokongan tingkah laku didelegasikan kembali ke kelas Tangan, kerana itu adalah tempat yang sangat wajar untuk tingkah laku tersebut. Sekali lagi, seolah-olah otak manusia berada di belakang tangan melakukan pengiraan ini.

Ciri penghitungan vektor boleh digunakan untuk mengetahui berapa banyak kad dengan nilai tertentu yang terdapat di kelas Tangan:

 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

Adalah sangat penting untuk menulis kod ujian dan contoh semasa melaksanakan kerangka sebenar. Dengan cara ini, anda selalu tahu seberapa baik kod pelaksanaan berfungsi; anda menyedari fakta mengenai ciri dan perincian mengenai pelaksanaan. Memandangkan lebih banyak masa, kami akan menerapkan poker - kes ujian seperti itu akan memberikan lebih banyak wawasan mengenai masalah ini dan akan menunjukkan bagaimana untuk mentakrifkan semula kerangka.