個人的なプロジェクトのポートフォリオを拡大したいので、カードゲームを作ることにしました。より正確には、マカオと呼ばれています。 StackOverflowでこの answer を読み、最初の回答から手順を実行しようとしました。私は、ゲームの一般的なルールとその特殊性を説明する物語を作りました。
マカオのゲーム。プレーヤーの最小数は2です。カードのデッキには、最初に52枚のカードが含まれています。各カードには次の属性があります。
- スーツ(ダイヤモンド、クラブ、ハート、スペード)
- ランク(2、3、4、5、6、7、8、9、A、J、Q、K、ジョーカー)
ゲームを開始すると、デッキがシャッフルされ、すべてのプレイヤーが5枚のカードを受け取ります。カードに残った最初のプレイヤーが勝者です。その後、カードがデッキから取り出され、表を上にしてテーブルに置かれます。
現在のプレイヤーは、テーブルに置かれたカードを見て、自分のパケットからカードをテーブルに置くことができるかどうかを決定します。カードが他のカードと互換性がある場合、プレイヤーはカードをテーブルに置くことができます。
2枚のカードは、次の条件のいずれかを達成する場合に互換性があります。同じランクまたは同じスーツを持っている。カードのドメインを拡張すると、いくつかの特別なカードが存在します。これらも指定されたルールに従う必要がありますが、特別な能力という追加の属性があります。彼らはどんなスーツを持っているかもしれませんが、これらのランクはカードを特別であるとみなします:2、3、4、7、A、そしてジョーカー。ジョーカーは、以前に指定されたルールに従う必要がないため、ワイルドカードです。つまり、プレイヤーはいつでもテーブルに置くことができます。
次のセクションでは、前述のカードの特殊能力について詳しく説明します。
ランク:7.このカードは、テーブルに置かれたとき、現在のプレイヤーにスーツを指定するように要求します。次のプレーヤーは、以前に指定されたスートのカードを置く義務があります。そうしないと、デッキから新しいカードを受け取ります。
ランク:2および3。このカードは、次のプレーヤーにデッキから2/3(ランクが2の場合は2枚のカード、同じルールは3枚)のカードを受け取るように強制します。これで、次のプレーヤーも同じスーツの2または3、またはジョーカーを持っている場合、テーブルに置くことができ、次のプレーヤーはカードの合計数を受け取る必要があります。同じ規則が循環的に適用されます。次のプレーヤーが同じスートの4を持っている場合、前のプレーヤーが投入した義務を停止できます。
ランク:4。特別な能力:それは前のプレーヤーによって注入された義務を停止することができます。それが通常のカード(すなわち、特別な能力を持たないカード)の上に置かれた場合、それはそのカードのように振る舞います。
- ランク:A(エース)。特殊能力:次のプレイヤーのターンをスキップします。
- ランク:ジョーカー。これはワイルドカードなので、ほとんどすべてのインスタンスでテーブルに配置できます。例外は、テーブルの一番上にあるカードがA(エース)である場合に発生し、現在のプレイヤーのターンはスキップされます。特殊能力:2/3カードと同じですが、次のプレーヤーは5枚のカード(ブラックジョーカーの場合)または10枚(レッドの場合)を受け取る必要があります。
ルールを明確にして、ゲームを続行します。現在のプレイヤーがそのような互換性のあるカード(またはそれ以上)を所有している場合、それをテーブルに置くか、デッキから新しいカードを受け取るかは彼の選択です。また、ゲーム中に、デッキが空になる状況が発生する可能性があります。その場合、一番上のカードの下にあるテーブルのすべてのカードが取り出され、シャッフルされ、デッキの補充に使用されます。上のカードはそのままです。
今度は次のプレイヤーの番です。プレイヤーの1人がカードからなくなるまで、前述の手順が繰り返されます。そのプレイヤーが勝者です。
この物語から、私は次のクラスを抽出します:
ここに私の問題があります。
これは私の最初の中規模のオブジェクト指向プロジェクトであり、正しい方向に進んでいることを確認したいと思います。
適切な抽象化を行ったかどうかはわかりません。
まあ、それは始まりですが、それを思いやりすぎてコードを書き始めた場合にのみわかるでしょうsingこれらの抽象化。このコードは、テスト、実際のゲームロジック、またはその両方です。
特別なカードをデザインする方法がわかりません。特別なカードごとに個別の(カードから継承された)クラスが存在する必要がありますか?それとも作曲を使うべきですか?
どちらでもない。私がこれを正しく理解した場合、「特別」であることは、ランクから導き出せる特性です。したがって、CardクラスがRank
(およびSuit
)の属性を持っている場合、「特殊能力」について決定を下すためにゲームロジックが必要とするすべての情報が含まれます。
また、どのようにジョーカーカードを実装する必要がありますか?彼らは本当にスーツを持っているのではなく、ただランクを持っています。
シンプル:ジョーカーの5番目のスーツ「なし」を紹介します。カードのコンストラクターは、誰かが非ジョーカーのスーツ「なし」でカードを初期化しようとした場合、またはランク「ジョーカー」で別のスーツのカードを初期化しようとした場合に例外をスローできます。
ゲームクラスは、プレーヤーのターンと例外的なケース(トップカードが特別なカードになるケース)を管理する必要がありますか?
ゲームのロジックを実装する必要がある場所。最初にそれをGameクラスに入れるのは賢明なスタートです。そのクラスが進化し、そのクラスが大きくなりすぎたり、責任が多すぎたりする場合は、そのような責任を特定し、名前を付けて、別々のクラスにリファクタリングしてください。これを事前に過度に分析する必要はありません。
Playerは単なるインターフェースであるべきですか?これにより、AIプレーヤー(Playerを実装する)などの追加機能のためのスペースが残ります。
繰り返しますが、この決定を行う必要はありませんnow。 1つのPlayerクラスから始めて、HumanPlayer
とAIPlayer
が必要になるところまで来たら、リファクタリングするのに十分早いです。
あなたはまともなヘッドスタートを手に入れましたが、コーディングを始める前にすべての可能なクラスを絶対に持っている必要があると感じてはいけません。コードは自然に進化して、機能を追加したり、既存のクラスを拡張したりします。
私が推奨する場合は、デッキからカードを配る、プレーヤーのターン、破棄、ドローなど、基本的にやりたいと思うあらゆるものなど、一般的な「カードゲーム」ロジックを扱うプログラムの部分を分離してみてくださいカードゲームで。
次に、Game
クラスを、カードゲームを処理するための抽象クラスに変換します。基本的なメソッドを使用して、ドロー、破棄、ターンの変更などの基本的なカード機能を実行できます。
次に、Game
をMacaoGame
に拡張します。MacaoGame
は、ゲームロジックを処理する「頭脳」です。この特定のタイプのゲームを処理するためにMacaoGame
が特に必要とするものはすべてMacaoGame
クラスに直接追加できますが、プログラムの別の部分(実装に役立つ部分)として扱う必要がありますMacaoGame
で、基本的なカードゲームエンジンを扱う部分ではありません)。
たとえば、特別な意味を持つカードを識別する列挙型を作成するとします(Javaでは、カードをコンストラクタに直接渡すことができるため、たとえば、カードがSuitChangeCard
かどうかを簡単に確認できます)。このようにすると、後で簡単に変更でき、また、基盤となるカードゲームエンジンも変更されないため、非常に柔軟な設計決定が可能になります。
現時点では、Player
は、プレーヤーについて知る必要があるすべてのものを保持する実装クラスにすることができます。ただし、プレイヤーがAIであるかAIであるかによってプレイヤーの動作が異なる場合、はい、これは抽象化して拡張する最初のクラスになります。プレイヤーの重要な部分は実際のプレーであると思うので、あなたは入力を求めています。入力は人間のプレーヤーまたはAIから来る可能性があり、これはそれほど大きな違いはありませんが、一方は同期しているが他方は同期していないことに注意する必要があります。
したがって、コールバックを使用して入力を受け取ることは、価値があるかもしれません(実行中のスレッドがユーザー入力の待機でハングすることを気にしない限り)。
ジョーカーカードはそれ自身のカードです。プログラムのカードゲームエンジンの部分では、ランクを扱うことができますが、ランクはゲームに基づいて変化する可能性があるため、あまり強調しないでください。たとえば、一部のゲームでは、エースは2よりも低いと見なされます。MacaoGame
を除くすべての場所で他のカードと同じように扱って、問題ありません。
あなたは間違いなく正しい軌道に乗っています。 MacaoGame
ロジックを他のロジックと組み合わせても、深刻な問題が発生することはないと思いますが、他のカードゲームを実装したい場合は、やや柔軟性の低い設計になります。また、カードゲームエンジンの機能とマカオの特定のゲームを扱うプログラムの一部として、開発を頭の中で整理するのにも役立ちます。幸運を!
このようなモデリングゲームではOOを忘れます。 UIとしては問題ありませんが、ゲームの簡単なモデルを作成できるようになるまでは、誰が行くのか、有効な動きは何かを伝えるだけで、問題が発生します。
すべてのターンベースのゲームは同じパターンに従います。プレイヤーがコントロールを持ち、コントロールが別のプレイヤーに切り替わるまで、またはゲームが終了するまでプレイを行います。
あなたの場合、次のスーツを指定するなどのオプション付きのスペシャルのためにいくつかの追加で、動きはデッキのすべてのカードとして列挙可能です。 (2つの動きと1つの動きのどちらで表現するかを決める必要があります)
1h、2h、3h .... 1c、2c ... Jr、Jbなど
プレーヤーはプレイが進行する順序を持ち、その動きは手札のカードに限定されます。
これで、いくつかの文字列配列でゲームを非常に簡単にモデル化できます。ランダムなカードを選ぶだけの簡単なボットを作ることもできます。
それがうまくいったらThen抽象化が意味を成すものについて心配し始めます。しかし、彼らは人間が遊ぶのに必要な物理的装置とは何の関係もないことになるかもしれません
デザインがオブジェクト指向の原則に準拠しているかどうかを確認するには、2つの簡単なルールがあります。
All(オブジェクトとメソッド)の名前は、ドメイン内の何かを意味します。これは、Service、Managerオブジェクト名、いいえexecute()メソッドなど。
オブジェクトから漏洩するデータはありません。つまり、すでに利用可能なデータを返すだけのメソッドはありません。つまりgetterメソッドはありません。
今、これは妥協することなくすべてのプロジェクトで常に行われるべきだと言っているのではありません。しかし、あなたの場合のために、そしてあなたが本当にそれを正しくしたいなら、あなたはこの道から外れるべきではありません。
これは最初は簡単ではありませんが、遅かれ早かれ、正しい考え方に身を置くでしょう。 デメテルの法則 、 単一の責任の原則 などの原則に準拠することもほぼ自動的に保証されます。
最初にカードを見てみましょう:
4スーツ、13ランク。カラーウィッシュカードを容易にするために、2番目のスーツを追加するか、そのランクをスーツごとに1つに分割できます。前者は、他の多くのカードゲームでより簡単に使用できます。
つまり、2ビットのスーツ、2ビットの次のスーツ、4ビットのランクでは、1オクテットになります。ランク0は予約する必要があるため、すべてゼロは「カードではない」ことを意味し、他の考慮事項のためにまだ多くの値が残っています。
各プレイヤーの手にベクターを作成し、パイルを破棄してスタックを再描画します。デッキにベクターは必要ありません。まったく新しいものを取得する関数を追加するだけです。今、あなたは実際に物事を行うためにいくつかの追加の機能が必要であり、あなたは設定されています。