Memorandum

普段の生活の覚え書き。主に技術録

BlackJackプログラム

以前、Qiitaというサイトで以下の記事を見かけました。 https://qiita.com/hirossyi73/items/cf8648c31898216312e5

上記の記事で紹介していたBlackJackのプログラムを作って見ようと思い作ってみました。

設計ができない

記事内では設計やクラスを考え、保守性や拡張性を保持しながら書くことの重要性を説いてました。

私も設計から始めようとしたところ、0からソフトウェアの設計をやるという経験がほとんどなかったので、設計の方法というのが分かりませんでした。 学生の頃から十年近くプログラムを書いてきましたが、プログラムをある程度書けても設計手法というのがわかっていないということに気づくことができました。

授業でソフトウェア工学を学んで、設計手法やソフトウェアライフサイクルなどは知っているつもりでしたが、いざ実践となると何から始めるべきなのか戸惑ってしまいます。

なのでいい機会と思い、ソフトウェア設計について学ぶことにしました。

仕様

今後の設計のために作成するプログラムの仕様を決めていきます。

  • 初期カード52枚。引くカードの重複はないようにする。
  • プレイヤーとディーラーの二人対戦。
  • ゲーム開始時にカードを2枚ずつ配ります。引いたカードは画面上でわかるようにします。しかしディーラーの2枚目のカードをわからないようにします。
  • 先にプレイヤーがカードを引くかどうか決めます。カードを引く度に次のカードを引くかどうか決められます。プレイヤーが21を超えていたらバーストでゲーム終了です。
  • プレイヤーが引き終えたら、次にディーラーが引きます。ディーラーは自分の手札が17以上になるまで引き続けます。
  • プレイヤー、ディーラーともに引き終えたら勝負です。より21に近いほうが勝ちです。
  • 絵札(J、Q、K)は10として扱います。
  • Aはとりあえず1として扱います。
  • その他のルール(ダブルダウン、スプリット、サレンダー)はなしです。

一応作った

手始めに、戒めとして設計せずにフィーリングで作成したプログラムを載せてみます。 とりあえず得意言語のJavaで書きました。

クリックで開きます

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.io.*;

class BlackJack{
    static final int allCards = 52;
    //create card
    static String[] marks = new String[] {"ハート","スペード","クラブ","ダイヤ"};
    static String[] nums = new String[] {"A","2","3","4","5","6","7","8","9","10","J","Q","K"};
    //不要
    //static String[][] cards = new String[4][13];

    static List<Integer> useCards = new ArrayList<>();

   public static void main(String args[]){

        int playerCards = 0;
        int dielerCards = 0;
        System.out.println("★☆★☆★☆★☆★☆ BLACK JACKへようこそ ★☆★☆★☆★☆★☆");
        System.out.println("ゲームを開始します");

        //Playerにカードを2枚配る
        int twice = 2;
        while(twice>0){
            int cardNum = passCard();
            if(checkDuplication(cardNum)){
                continue;
            }
            System.out.println("あなたの引いたカードは"+checkCardMark(cardNum)+"の"+checkCardNumMark(cardNum)+"です。");
            playerCards += checkCardNum(cardNum);
            twice--;
        }

        //Dilerにカードを2枚配る
        twice = 2;
        while(twice>0){
            int cardNum = passCard();
            if(checkDuplication(cardNum)){
                continue;
            }
            if(twice==2)System.out.println("ディーラーの引いたカードは"+checkCardMark(cardNum)+"の"+checkCardNumMark(cardNum)+"です。");
            else System.out.println("ディーラーの2枚目のカードは分かりません。");
            dielerCards += checkCardNum(cardNum);
            twice--;
        }

        System.out.println("あなたの現在の得点は"+playerCards+"です。");

        System.out.println("カードを引きますか?引く場合はYを、引かない場合はNを入力してください。");

        BufferedReader reader = new BufferedReader (new InputStreamReader (System.in));
        try {
            while(true){
                String line = reader.readLine();
                if(line.equals("Y") || line.equals("y")){
                    int cardNum = passCard();
                    if(checkDuplication(cardNum)){
                        continue;
                    }
                    System.out.println("あなたの引いたカードは"+checkCardMark(cardNum)+"の"+checkCardNumMark(cardNum)+"です。");
                    playerCards += checkCardNum(cardNum);
                    if(playerCards == 21){
                        System.out.println("★☆★☆★☆★☆ブラックジャック!!!★☆★☆★☆★☆");
                        System.out.println("あなたの勝ちです!");
                        System.out.println("ブラックジャック終了!また遊んでね!");
                        return;
                    }
                    if(playerCards > 21){
                        System.out.println("バースト!あなたの負けです。。。");
                        return;
                    }
                    System.out.println("あなたの現在の得点は"+playerCards+"です。");
                    System.out.println("もう一度引きますか?引く場合はYを、引かない場合はNを入力して下さい。");
                }else if(line.equals("N") || line.equals("n")){
                    System.out.println("勝負を開始します!");
                    break;
                }else{
                    System.out.println("入力エラーです。もう一度入力してください。");
                }
            }
        } catch (IOException e) {
            System.out.println (e);
        }

        System.out.println("ディーラーの現在の得点は"+dielerCards+"です。");
        while(dielerCards < 17){
            int cardNum = passCard();
            if(checkDuplication(cardNum)){
                continue;
            }
            System.out.println("ディーラーはカードを引きました。");
            dielerCards += checkCardNum(cardNum);
            if(dielerCards > 21){
                System.out.println("ディーラーがバーストしました。あなたの勝ちです。");
                System.out.println("ブラックジャック終了!また遊んでね!");
                return;
            }
        }

        System.out.println("ディーラーの用意が整いました。");
        System.out.println("勝負開始!");
        System.out.println("あなたの得点:"+playerCards+"VSディーラーの得点:"+dielerCards);
        if(playerCards > dielerCards){
            System.out.println("あなたの勝ちです!Conguratulations!!");
        }else{
            System.out.println("あなたの負けです。");
        }
        System.out.println("ブラックジャック終了!また遊んでね!");
    }

    //1から52までのランダムな値を発生させる
    public static int passCard(){
        Random rand = new Random();
        return rand.nextInt(51);
    }

    //受け取った値からカードのマークを判定する
    public static String checkCardMark(int num){
        String cardMark = marks[num/14];
        return cardMark;
    }

    //受け取った値からカードの数字[記号]を判定する
    public static String checkCardNumMark(int num){
        String cardNum = nums[num%13];
        return cardNum;
    }

    //受け取った値からカードの数字を判定する
    public static int checkCardNum(int num){
        String card = nums[num%13];

        switch (card){
            case "A":
                card = "1";
                break;
            case "J":
                card = "10";
                break;
            case "Q":
                card = "10";
                break;
            case "K":
                card = "10";
                break;
            default:
                break;
        }
        return Integer.parseInt(card);
    }

    //カードの重複チェック
    public static boolean checkDuplication(int rand){
        boolean isDupl = false;
        for(Iterator it = useCards.iterator(); it.hasNext();) {
            int comp = (int)it.next();
           if(comp==rand){
                isDupl = true;
            }
        }
        if(!isDupl){
            useCards.add(rand);
        }
        return isDupl;
    }

一応上記のプログラムで動きますが、クラス設計なんて考えず一つのクラス内で完結しています。かろうじてメソッドを分けていますが、カード配るところもwhileループで回しているだけなので、可読性も低いし、拡張性、保守性も最悪です。

道理で、学生の頃書いていたプログラムが今読んでも思い出せくなるわけですね。

実行イメージはこんな感じです。

❯ java BlackJack
★☆★☆★☆★☆★☆ BLACK JACKへようこそ ★☆★☆★☆★☆★☆
ゲームを開始します
あなたの引いたカードはクラブの4です。
あなたの引いたカードはスペードの5です。
ディーラーの引いたカードはクラブの8です。
ディーラーの2枚目のカードは分かりません。
あなたの現在の得点は9です。
カードを引きますか?引く場合はYを、引かない場合はNを入力してください。
y
あなたの引いたカードはダイヤの7です。
あなたの現在の得点は16です。
もう一度引きますか?引く場合はYを、引かない場合はNを入力して下さい。
y
あなたの引いたカードはハートの2です。
あなたの現在の得点は18です。
もう一度引きますか?引く場合はYを、引かない場合はNを入力して下さい。
n
勝負を開始します!
ディーラーの現在の得点は12です。
ディーラーはカードを引きました。
ディーラーの用意が整いました。
勝負開始!
あなたの得点:18VSディーラーの得点:21
あなたの負けです。
ブラックジャック終了!また遊んでね!

設計手法

今後設計の手法を学んで、今回のプログラムを例に改善していこうと思います。 ただ設計は学びづらいもので、現場での場数を踏むのが一般的らしいです。

私が担当してきた案件はすでにあるものの保守やリプレースなど0から作る経験がなかったので、現場では学びづらいと思います。

ですので、現在学習しているN予備校の講師の方が下記のサイトでおすすめしていた本を学んでみようと思います。

https://www.slideshare.net/sifue/ss-40756807

[amazon asin="4822284646" kw="UMLモデリングの本質 第2版"]

UML(Unified Modeling Language)を使ってソフトウェア設計手法を学んでいくみたいです。

この本を読んでみて、ソフトウェア設計を行うとともに、本の批評などもできればと思います。

今回作成したプログラムはコンパイルする必要があり、Javaを導入している環境でしか動かないので、Web版にリプレースしたプログラムも作っていきたいです。 (※オンラインでJavaを実行できる環境もありますが、作成したプログラムが対話型なので、私が調べた中ではJavaの対話型プログラムに対応しているオンライン実行環境がありませんでした。)