Skip to content

ポーカーゲーム作成Step1 ~カードと山札の設計図の作成~

今回のディレクトリ構成

まずは今回のプログラム全体を管理するディレクトリを作成しましょう。 ディレクトリ名は何でもいいですが、例としてszpp_pokerにでもしましょう。

bash
cd szpp/java
mkdir szpp_poker
cd szpp_poker

今回のプログラムはModel、View、Controllerを ディレクトリで分割します。 したがって、MVCそれぞれに対応するディレクトリを作る必要があります。実際に作っていきましょう。

bash
mkdir model
mkdir view
mkdir controller

さらに、szpp_poker直下にMain.javaを作成してください。中身は何も書かなくてよいです。 まとめると、今回のディレクトリ構成は以下のようになります。合っているか確認してください。

text
szpp_poker/
├── Main.java
├── controller/
├── model/
└── view/

今回はまずModelから作っていきましょう。

Modelを実装していこう!

ポーカーを構成する「モノ」で欠かせないのが「カード」と「山札」の存在です。山札やプレイヤーの手札はカードの集まりなわけですから、プログラム的にはカードのリスト(配列)として扱うことが可能です。そういう意味でカードは特に重要そうです。ということでまずはカードの設計図から実装していきましょう。szpp_poker/modelの中にCard.javaを作成してください。

Cardクラスの作成

まずCardクラスの一行目に以下のコードを書いてください。

java
package model;

いきなりわけわからんキーワードが出てきました。説明していきます。

パッケージ化

過去作成したポケモンゲームでは、すべてのファイルを同じディレクトリに書いていましたが、あれは好ましい手法とは言えません。本当は今回のmodelviewcontrollerディレクトリのように、役割ごとにファイルだけでなくディレクトリも分割するべきです。

しかし、Javaは同一ディレクトリのクラスは何もしなくても勝手に呼び出すことができますが、ディレクトリが違った場合は、「このクラスはあなたとは違うディレクトリにいるよ」と教えてあげなければいけません。

そこで使うのがパッケージという仕組みです。

ファイルの1行目にpackage model;と書くことで、「このファイルはmodelというパッケージ(ディレクトリ)に属していますよ」と明確に宣言することになります。 こうすることで、後でcontrollerviewのような別のディレクトリからCardクラスを使いたい時に、プログラムが迷子にならずに正しく呼び出せるようになります。 なお、パッケージ名の指定方法は、実際にプログラムを実行するディレクトリ(ルートディレクトリ)から見た相対的なディレクトリ名です。今回のルートディレクトリはMain.javaがあるディレクトリのため、modelディレクトリの中に作成したクラスにはpackage modelを記述する必要があります。

基本的にはパッケージ=ディレクトリと考えてくれて構いません。


パッケージについてわかったところで、Cardクラスを作成してみてください。がんば! 注意点として、ゲッターは必ず作成してください

java
package model;

public class Card {
    // 中身を頑張って書く
}

ヒント

今回のCardクラスで実装したいのはトランプのカードです。トランプのカードが持つパラメータについて考えてみると以下のようになりそうです。

  • mark(♠、♥など)
  • rank(A,2,3,4,...)

対して、特にCardクラスに機能(振る舞い)は無さそうです。これらを踏まえて、Cardクラスを書いていきましょう。ちなみにrankStringで管理する必要はありません。int型で管理すれば十分です。(Aは1, Jは11,...といったように数字に置き換えて考えばよいです)

Cardクラスが書けた人は、最後に以下のコードを追記してください。一見意味がわからないかもしれませんが、一旦おまじないです。

java
@Override
public String toString() {
    String[] rankStrs = { "", "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" };

    return mark + rankStrs[rank];
}

Deckクラスの作成

Cardクラスが作成できたところで、Deckクラスを作成していきましょう。このクラスもみなさんに全部作らせたいところなのですが、Deckクラスは少々特殊な実装の仕方をするので一緒に作成していきましょう。

Deckクラスのパラメータ

Deckクラスが持つパラメータは山札(deck)だけです。「山札のパラメータは山札」というと何言ってるかわからないかもしれませんが、実際そうなのでそういうものです。

山札の構成要素はカードです。したがって、プログラムでは山札はカードのリストとして扱うことが可能です。それを踏まえるとまずDeckクラスのフィールドは以下のように書けます。(import文も書いておいてください。後で使います。)

java
package model; // 必ず書く!!!

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public class Deck {
    private List<Card> deck;
}

コンストラクタ

コンストラクタも少し特殊です。これまでの経験からはこんな感じに書きたくなるかもしれません。

java
public Deck(List<Card> deck) {
    this.deck = deck;
}

しかし、これではインスタンス化を行う側のクラスでカードのリストを作らなければいけません。コンストラクタを呼んだら、自動でDeckクラスが山札を初期化してくれるような実装をしたほうが好ましいです。

それを踏まえると、コンストラクタはこんな感じで書いたほうが良いです。

java
public Deck() {
    deck = new ArrayList<>();
    reset();
}

reset()はその名の通りdeckをリセットするメソッドです。早速作成しましょう。

reset()メソッド

デッキの内容を初期化するような処理をこのメソッドに書きたいです。

初期化は以下の手順で行えそうです。

  1. deckの中身を全て消去する。
  2. 全種類のカードをdeckに追加する。
  3. デッキをシャッフルする。

今回、この手順の1と3は実装しておきました。3はshuffle()メソッドで行っています。2を頑張って実装してみてください!

ここで注意点として、 カードの柄は半角の記号1文字で表すようにしてください。 例えば「スペードの5」を実装したいときには、new Card("スペード", 5);と書くのではなく、new Card("♠", 5);と書くようにしてください。

java
public void reset() {
    deck.clear() // 手順1. deckの中身を全て消去

    // 手順2を頑張って書く

    shuffle() // 手順3. デッキをシャッフル
}

public void shuffle() {
    Collections.shuffle(deck);
}

ヒント

  • deckCard型のリストなのですから、add()メソッドでCard型のインスタンスを追加することで山札にカードを追加することができます。例えば、「♠のA」を山札に追加する処理は、以下のように書くことができます。
java
Card card = new Card("♠", 1);
deck.add(card);

draw()メソッド

山札の機能として、「山札からカードをドローされる」というものがあります。このdraw()メソッドを作成してみましょう。

このメソッドの仕様は以下の通りです。

  • 引数は無し
  • 返す値はドローしたカード自身
  • ドローするカードの数は1枚だけを想定してよい(カードを2枚ドローする場合などは考えなくてよい)

ヒント

  • ArrayListには要素の削除を行うメソッドが用意されています。それを使えば山札からカードを削除する処理が書けそうですね。
  • このメソッドでは、山札から削除されたカード自身を返り値とする必要があります。どのように書けば実装できるかな⋯

getRemainingCount()メソッド

山札の残り枚数を返すメソッドです。デバッグ用に使います。

java
public int getRemainingCount() {
    return deck.size();
}

テスト実行

ここまででCardクラスとDeckクラスの実装ができました。実際に正しく実装できているか試してみましょう。 szpp_pokerディレクトリ直下にあるMain.javaに以下のコードを書いてください。

java
import model.Card; // パッケージ化されたクラスを使用する際にはインポートをする必要がある
import model.Deck;

public class Main {
    public static void main(String[] args) {
        Deck deck = new Deck();

        System.out.println("山札の枚数: " + deck.getRemainingCount());

        System.out.println("5枚ドローします");
        for (int i = 0; i < 5; i++) {
            Card card = deck.draw();
            System.out.println(card.toString());
        }

        System.out.println("ドロー後の山札の枚数: " + deck.getRemainingCount());
    }
}

実装例

Card.java

java
package model;

public class Card {
    private String mark;
    private int rank;

    public Card(String mark, int rank) {
        this.mark = mark;
        this.rank = rank;
    }

    public String getMark() {
        return mark;
    }

    public int getRank() {
        return rank;
    }

    @Override
    public String toString() {
        String[] rankStrs = { "", "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" };

        return mark + rankStrs[rank];
    }
}

Deck.java

java
package model;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public class Deck {
    List<Card> deck;

    public Deck() {
        deck = new ArrayList<>();
        reset();
    }

    public void reset() {
        deck.clear();

        for (int i = 1; i <= 13; i++) {
            Card card1 = new Card("♠", i);
            Card card2 = new Card("♣", i);
            Card card3 = new Card("♦", i);
            Card card4 = new Card("♥", i);

            deck.add(card1);
            deck.add(card2);
            deck.add(card3);
            deck.add(card4);
        }

        shuffle();
    }

    public void shuffle() {
        Collections.shuffle(deck);
    }

    public Card draw() {
        return deck.remove(deck.size() - 1);
    }

    public int getRemainingCount() {
        return deck.size();
    }
}