Mesin kartu di Jawa

Ini semua dimulai ketika kami memperhatikan bahwa hanya ada sedikit aplikasi permainan kartu atau applet yang ditulis di Java. Pertama kami berpikir untuk menulis beberapa permainan, dan mulai dengan mencari tahu kode inti dan kelas yang diperlukan untuk membuat permainan kartu. Prosesnya terus berlanjut, tetapi sekarang ada kerangka kerja yang cukup stabil untuk digunakan untuk membuat berbagai solusi permainan kartu. Di sini kami menjelaskan bagaimana kerangka kerja ini dirancang, cara kerjanya, dan alat serta trik yang digunakan untuk membuatnya berguna dan stabil.

Fase desain

Dengan desain berorientasi objek, sangat penting untuk mengetahui masalah luar dalam. Jika tidak, mungkin menghabiskan banyak waktu untuk merancang kelas dan solusi yang tidak diperlukan atau tidak akan berfungsi sesuai dengan kebutuhan spesifik. Dalam kasus permainan kartu, salah satu pendekatannya adalah memvisualisasikan apa yang sedang terjadi ketika satu, dua, atau lebih orang bermain kartu.

Dek kartu biasanya berisi 52 kartu dalam empat jenis berbeda (berlian, hati, tongkat, sekop), dengan nilai mulai dari deuce hingga raja, ditambah ace. Segera muncul masalah: tergantung pada aturan permainan, kartu As bisa berupa nilai kartu terendah, tertinggi, atau keduanya.

Selanjutnya, ada pemain yang mengambil kartu dari dek ke tangan dan mengelola kartu berdasarkan aturan. Anda dapat menunjukkan kartu kepada semua orang dengan meletakkannya di atas meja atau melihatnya secara pribadi. Bergantung pada tahap permainan tertentu, Anda mungkin memiliki sejumlah N kartu di tangan Anda.

Menganalisis tahapan dengan cara ini mengungkapkan berbagai pola. Kami sekarang menggunakan pendekatan berbasis kasus, seperti dijelaskan di atas, yang didokumentasikan dalam Rekayasa Perangkat Lunak Berorientasi Objek Ivar Jacobson . Dalam buku ini, salah satu ide dasarnya adalah memodelkan kelas berdasarkan situasi kehidupan nyata. Itu membuatnya lebih mudah untuk memahami bagaimana relasi beroperasi, apa yang bergantung pada apa, dan bagaimana abstraksi beroperasi.

Kami memiliki kelas-kelas seperti CardDeck, Hand, Card, dan RuleSet. CardDeck akan berisi 52 objek Kartu di awal, dan CardDeck akan memiliki lebih sedikit objek Kartu karena ini ditarik ke objek Tangan. Objek tangan berbicara dengan objek RuleSet yang memiliki semua aturan terkait game. Pikirkan RuleSet sebagai buku pegangan game.

Kelas vektor

Dalam kasus ini, kami membutuhkan struktur data fleksibel yang menangani perubahan entri dinamis, yang menghilangkan struktur data Array. Kami juga menginginkan cara mudah untuk menambahkan elemen penyisipan dan menghindari banyak pengkodean jika memungkinkan. Ada solusi berbeda yang tersedia, seperti berbagai bentuk pohon biner. Namun, paket java.util memiliki kelas Vektor yang mengimplementasikan larik objek yang tumbuh dan menyusut ukurannya sesuai kebutuhan, yang persis seperti yang kami butuhkan. (Fungsi anggota Vektor tidak sepenuhnya dijelaskan dalam dokumentasi saat ini; artikel ini akan menjelaskan lebih lanjut bagaimana kelas Vektor dapat digunakan untuk contoh daftar objek dinamis serupa.) Kelemahan dengan kelas Vektor adalah penggunaan memori tambahan, karena banyak memori menyalin dilakukan di belakang layar. (Untuk alasan ini, Array selalu lebih baik; ukurannya statis,sehingga kompilator dapat menemukan cara untuk mengoptimalkan kode). Juga, dengan kumpulan objek yang lebih besar, kami mungkin memiliki penalti terkait waktu pencarian, tetapi Vektor terbesar yang dapat kami pikirkan adalah 52 entri. Itu masih masuk akal untuk kasus ini, dan waktu pencarian yang lama tidak menjadi masalah.

Penjelasan singkat tentang bagaimana setiap kelas dirancang dan dilaksanakan sebagai berikut.

Kelas kartu

Kelas Kartu sangat sederhana: kelas ini berisi nilai yang menandakan warna dan nilainya. Ini mungkin juga memiliki petunjuk ke gambar GIF dan entitas serupa yang mendeskripsikan kartu, termasuk kemungkinan perilaku sederhana seperti animasi (membalik kartu) dan sebagainya.

class Card mengimplementasikan CardConstants {public int color; nilai int publik; public String ImageName; }

Objek Kartu ini kemudian disimpan dalam berbagai kelas Vektor. Perhatikan bahwa nilai untuk kartu, termasuk warna, ditentukan dalam antarmuka, yang berarti bahwa setiap kelas dalam kerangka kerja dapat mengimplementasikan dan dengan cara ini menyertakan konstanta:

antarmuka CardConstants {// bidang antarmuka selalu final statis publik! int HATI 1; int DIAMOND 2; int SPADE 3; int CLUBS 4; int JACK 11; int QUEEN 12; int RAJA 13; int ACE_LOW 1; int ACE_HIGH 14; }

Kelas CardDeck

Kelas CardDeck akan memiliki objek Vektor internal, yang akan diinisialisasi sebelumnya dengan 52 objek kartu. Ini dilakukan dengan menggunakan metode yang disebut shuffle. Implikasinya adalah setiap kali mengocok, Anda memang memulai permainan dengan menentukan 52 kartu. Anda perlu menghapus semua objek lama yang mungkin dan mulai dari keadaan default lagi (52 objek kartu).

public void shuffle () {// Selalu nol vektor dek dan inisialisasi dari awal. deck.removeAllElements (); 20 // Kemudian masukkan 52 kartu. Satu warna pada satu waktu untuk (int i ACE_LOW; i <ACE_HIGH; i ++) {Card aCard new Card (); aCard.color HATI; aCard.value i; deck.addElement (aCard); } // Lakukan hal yang sama untuk CLUBS, DIAMONDS dan SPADES. }

Saat kita menggambar objek Card dari CardDeck, kita menggunakan generator nomor acak yang mengetahui himpunan dari mana ia akan memilih posisi acak di dalam vektor. Dengan kata lain, meskipun objek Kartu diurutkan, fungsi acak akan memilih posisi arbitrer dalam cakupan elemen di dalam Vektor.

Sebagai bagian dari proses ini, kami juga menghapus objek sebenarnya dari vektor CardDeck saat kami meneruskan objek ini ke kelas Hand. Kelas Vector memetakan situasi kehidupan nyata dari setumpuk kartu dan tangan dengan memberikan kartu:

public Card draw () {Card aCard null; posisi int (int) (Math.random () * (deck.size = ())); coba {aCard (Card) deck.elementAt (position); } menangkap (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } deck.removeElementAt (posisi); mengembalikan kartu; }

Perhatikan bahwa sebaiknya menangkap semua kemungkinan pengecualian terkait pengambilan objek dari Vektor dari posisi yang tidak ada.

Ada metode utilitas yang mengulangi semua elemen dalam vektor dan memanggil metode lain yang akan membuang string pasangan nilai / warna ASCII. Fitur ini berguna saat men-debug kelas Deck dan Hand. Fitur enumerasi vektor banyak digunakan di kelas Hand:

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

Kelas tangan

Kelas Hand adalah pekerja keras nyata dalam kerangka ini. Sebagian besar perilaku yang diperlukan adalah sesuatu yang sangat alami untuk ditempatkan di kelas ini. Bayangkan orang-orang memegang kartu di tangan mereka dan melakukan berbagai operasi sambil melihat objek Kartu.

Pertama, Anda juga memerlukan vektor, karena dalam banyak kasus tidak diketahui berapa banyak kartu yang akan diambil. Meskipun Anda dapat mengimplementasikan larik, ada baiknya untuk memiliki fleksibilitas di sini juga. Metode paling alami yang kita butuhkan adalah mengambil kartu:

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

CardHandadalah sebuah vektor, jadi kami hanya menambahkan objek Card ke dalam vektor ini. Namun, dalam kasus operasi "keluaran" dari tangan, kami memiliki dua kasus: satu di mana kami menunjukkan kartu, dan satu di mana kami berdua menunjukkan dan menarik kartu dari tangan. Kita perlu menerapkan keduanya, tetapi dengan menggunakan pewarisan kita menulis lebih sedikit kode karena menggambar dan menunjukkan kartu adalah kasus khusus dari hanya menunjukkan kartu:

public Card show (int position) {Card aCard null; coba {aCard (Card) cardHand.elementAt (position); } menangkap (ArrayIndexOutOfBoundsException e) {e.printStackTrace (); } kembalikan aCard; } 20 penarikan kartu publik (posisi int) {kartu aCard show (posisi); cardHand.removeElementAt (posisi); mengembalikan kartu; }

Dengan kata lain, kasus gambar adalah kasus pertunjukan, dengan perilaku tambahan untuk menghapus objek dari vektor Tangan.

Dalam menulis kode pengujian untuk berbagai kelas, kami menemukan semakin banyak kasus di mana perlu untuk mencari tahu tentang berbagai nilai khusus di tangan. Misalnya, terkadang kami perlu mengetahui berapa banyak kartu dari jenis tertentu yang ada di tangan. Atau nilai default ace low satu harus diubah menjadi 14 (nilai tertinggi) dan kembali lagi. Dalam setiap kasus, dukungan perilaku didelegasikan kembali ke kelas Hand, karena ini adalah tempat yang wajar untuk perilaku tersebut. Sekali lagi, seolah-olah otak manusia berada di belakang tangan yang melakukan perhitungan ini.

Fitur enumerasi vektor dapat digunakan untuk mengetahui berapa banyak kartu dengan nilai tertentu yang ada di kelas 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

Sangat penting untuk menulis kode dan contoh pengujian sambil menerapkan kerangka kerja yang sebenarnya. Dengan cara ini, Anda selalu tahu seberapa baik kode implementasi bekerja; Anda menyadari fakta tentang fitur dan detail tentang implementasi. Mengingat lebih banyak waktu, kami akan menerapkan poker - kasus uji seperti itu akan memberikan lebih banyak wawasan tentang masalah dan akan menunjukkan cara mendefinisikan ulang kerangka kerja.