見出し画像

High and Lowを作ってみよう

またある時の昼下がり、1匹の猫が日向ぼっこをしています。
どうやら、今度は一人のようです。

ミケ:む~、何を書こうかな~。あれは簡単すぎだし、かといってこれは難しすぎるしな~。

ミケ:プログラミングの知識を使って簡単なゲームを作っていくってのは決まったけど、結局何のゲームがいいかが決まらないな。

ミケ:ん~。そうだ!トランプゲームにすればいいのでは! 
簡単なやつだから、プレイヤーとCPUの一対一のゲームにしよう!
そういえば、ハイアンドローは一対一だったような気がするな~。

ミケ:簡単そうだし、ハイアンドローにしよう!

こうして、何のゲームを作っていくかが決まったようです。

ハイアンドローのルール

ということで、ハイアンドローというゲームをJavaで作っていくことにしたんだけど、そもそもハイアンドローのルールを知らない人のためにここでルールをおさらいしよう!

https://edgegram.hatenablog.jp/entry/2018/09/05/172349というサイトによると、次のように書かれていたよ。

親と子の二人に分かれる。
カード(52枚)を均等に二人に分ける。この際、どちらもカードは裏返された状態になっている。

親はカードを表にして、子はカードを裏のまま手札から一枚ずつ出す。
子は自分のカード(見えない)が親のカード(見える)より数字が大きい(high)か小さい(low)かを予想する。

子は自分のカードを表にし、予想があっていたら自分と相手のカードをもらう。外れていた場合は親がもらう。

親と子を交代する。

先に手札がなくなったほうの負け。

なお、ジョーカーが出た場合は必ず勝てる。

ほうほう、なるほどね~。なんとなくわかったかも。

まぁ試しに作ってみよう!

大まかな仕様を決めよう

ハイアンドローの基本ルールも確認できたところで、今から作るゲームの仕様を決めていこう!

GUIかCUIか

とりあえず、GUIかCUIにするかだけ決めよう。CUIのほうが楽だけど、ちょっと見た目が寂しいよね。 かといってGUIだとゲームの中身以外も考えないといけないから大変だしなぁ。

今回は初めてってこともあるし、CUIで作っていくことにしようか。

そのあとGUIにしていけばいいしね。

ゲーム内容について

内容と言っても、ハイアンドローのルールをそのまま転用すればいいんだけど、ちょっと簡単にするためにアレンジを加えていこうか。もちろん、そのままがいいって思う人はアレンジなしでもいいと思うよ。

まず、子の予想があっていたら自分と相手のカードをもらい、予想が外れていた場合は親がもらうというルールなんだけど、出したカードはそのまま捨てるようにしようかな。

そうなると、勝敗の仕方も変えないといけないよね。カードの枚数では勝負できないからね。

となると、ポイント制にしたほうがいいのかな。予想があっていたら1ポイント獲得するみたいな感じにして、最終的にポイントが高いほうが勝ちとするとかね。

あと、数字の大きさの順序なんだけど、普通なら[A<2<K<A]とかだと思うんだけど、より簡単にするために[A<2<K]にするね。※ジョーカーはなし。

数字の大きさが同じだった場合はどうしようか…。

この場合は両方ポイントなしでカードを捨てればいいかな。

…とまあこんな感じかな。考えてみたら山札をシャッフルするってどうしよう…。いや、そんな先のことは作っているときに考えればいいんだ!(複数人でやる場合はちゃんと仕様など決めたほうがいいよ。)

必要なメゾット

必要なメゾットは、だいたいこんな感じかな?

  • ゲームの内容を処理する(main)

  • 山札をシャッフルする

余り思い浮かばないけど、まぁこれも作りながら考えていけばいいか。

実際に書いてく


仕様も大まかに決まったことだし、実際に書いてみよう。

とりあえずmainメゾットを書いてっと。

public class HighAndLow{
    public static void main(String[] args) {
        
    }
}

とりあえず、山札を作っていこうか。山札はint[]の配列にしよう。

え,でもそしたらAとかJとかKは?ってなるかもしれない。でもご安心を。
AとかJとかKも数字においてしまえばいいんだ。つまりAを1,Jを11みたいにしてしまえばいいってことさ!

となると、とりあえず山札はこうなるね。

int[] deck = {1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13};

さて、じゃあ次に山札をシャッフルするメゾットを定義していこう。

https://onl.bz/e3e8kPpのサイトにあるダステンフェルドのアルゴリズムを使ってみよう。
その内容は以下のようなものだ。

要素数が n の配列 a をシャッフルする(添字は0からn-1):
i を n - 1 から 1 まで減少させながら、以下を実行する
j に 0 以上 i 以下のランダムな整数を代入する
a[j] と a[i]を交換する

計算量もO(n)らしいし、これを採用しよう。

import java.util.Arrays;

public class HighAndLow{
    public static void main(String[] args) {
        int[] deck = {1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13,1,2,3,4,5,6,7,8,9,10,11,12,13};
        shuffleDeck(deck);
        System.out.println(Arrays.toString(deck));
    }
    public static void shuffleDeck(int[] deck){
        for (int i = deck.length-1; i > 1; i--) {
            int j = new java.util.Random().nextInt(i+1);
            int tmp = deck[i];
            deck[i]=deck[j];
            deck[j]=tmp;
        }
    }
}

実行してみると、確かにシャッフルされているよね?

何度も実行してみて確かめてもいいかもね。

次に、シャッフルした山札をプレイヤーとCPUにそれぞれ分けてみようか。

それぞれの手札をplayerHand,cpuHandという配列で宣言して、山札を分割していこう。

分割にはjava.util.ArraysのcopyOfRange()を使っていこう。

int[] playerHand=Arrays.copyOfRange(deck, 0, deck.length/2);
int[] cpuHand=Arrays.copyOfRange(deck, deck.length/2, deck.length);

System.out.println(Arrays.toString(playerHand));
System.out.println(playerHand.length);
System.out.println(Arrays.toString(cpuHand));
System.out.println(cpuHand.length);

こうすればシャッフルした山札を二つに分割して、それぞれの手札として配れるね。

さて、ではゲームの内容を実装していこう!

ターン数はプレイヤーとcpuの行動を1つと数えると、手札の枚数と同じ(=32回)だから、for文で手札の要素数回だけ繰り返す文を書いてみよう。

   for (int i = 0; i < playerHand.length; i++) {
            //ゲームループ
            
   }

こんな感じだね。

最初はプレイヤーが親という設定で、実際に中身を記述していくよ。

とりあえず、プレイヤーが親の場合のみ書いてみたよ。

//0ならlow,それ以外はhigh
int playerSelect=0;
int cpuSelect=0;  
    for (int i = 0; i < playerHand.length; i+=2) {
        //ゲームループ
        System.out.println("あなたが親です");
        System.out.println("あなたは"+convertToTrumpChar(playerHand[i])+"を出しました。");
        System.out.println("cpuはカードを出しました");
        if(Math.random()>0.5){
            System.out.println("cpuはhighを選択しました");
            cpuSelect=1;
        }else{
            System.out.println("cpuはlowを選択しました");
            cpuSelect=0;
        }
        System.out.println("cpuは"+convertToTrumpChar(cpuHand[i])+"でした");
        if((cpuSelect>0&&playerHand[i]>cpuHand[i])||(cpuSelect==0&&playerHand[i]<cpuHand[i])){
            System.out.println("よってcpuの勝ちです");
            System.out.println("cpuは1ポイント獲得しました");
        }else{
            System.out.println("よってあなたの勝ちです");
            System.out.println("あなたは1ポイント獲得しました");
        }
    }

ここではconvertToTrumpChar()を使っているね。これは数字が1,11,12,13のとき、A,J,Q,Kに変換するメゾットだよ。

 public static String convertToTrumpChar(int num){
        switch (num) {
            case 1:
                return "A";
            case 11:
                return "J";
            case 12:
                return "Q";
            case 13:
                return "K";
        }
        return num+"";
 }

そういえば、プレイヤーとcpuそれぞれのポイントを保持する変数を宣言し忘れていたね。それらも加えておこう。

次にプレイヤーが子の場合を付け足すとこんな感じ。

//0ならlow,それ以外はhigh
int playerSelect=0;
int cpuSelect=0;  

int playerScore=0;
int cpuScore=0;

Scanner scan = new Scanner(System.in);

for (int i = 0; i < playerHand.length; i+=2) {
    //ゲームループ
    System.out.println("あなたが親です");
    
    ~~~~
   省略
    ~~~~

    i++;
    System.out.println("-----");

    System.out.println("あなたが子です");
    System.out.println("cpuは"+convertToTrumpChar(cpuHand[i])+"を出しました。");
    System.out.println("あなたはカードを出しました");
    System.out.println("選択してください: high=0以外 low=0");
    System.out.print(">");
    playerSelect=scan.nextInt();

    System.out.println("あなたは"+(playerSelect==0?"low":"high")+"を選択しました");

    System.out.println("あなたは"+convertToTrumpChar(playerHand[i])+"でした");
    if((playerSelect>0&&playerHand[i]>cpuHand[i])||(playerSelect==0&&playerHand[i]<cpuHand[i])){
        System.out.println("よってあなたの勝ちです");
        System.out.println("あなたは1ポイント獲得しました");
        playerScore++;
    }else{
        System.out.println("よってcpuの勝ちです");
        System.out.println("cpuは1ポイント獲得しました");
        cpuScore++;
    }
  System.out.println("-----");
}

プレイヤーの選択を受け取るためにjava.util.Scannerを用いているよ(import文は省略)。

まぁほとんど親の時と一緒だね。

じゃあ最後に、勝敗を表示しよう。 for文の後に書くよ。

scan.close();
System.out.println("終了!");
System.out.println("あなた:"+playerScore+"ポイント");       
System.out.println("cpu:"+cpuScore+"ポイント");        
System.out.println("よって"+(playerScore>cpuScore?"あなた":"cpu")+"の勝利!");

一応完成?

これでゲームとして一応完成したよ!
ただ気づいているかもしれないけど、最後の結果発表の「あなた」と「cpu」で「:〇〇ポイント」の文字列がずれてたりとか、今何ターン目なのかが分かりにくいとか、まだまだ改善点はあるんだ。

だから完成ではあるんだけど、もっともっと改善してみてもいいかもね。
え?僕はしないのかって? だって、めんどくさいじゃん これを読んでいる人の力にならないからね。けっしてめんどうってわけじゃないからね!!

GitHubに公開しています!

GitHubに今回のプログラムを公開しているよ!
え?名前がミケじゃないって?

…気にしない気にしない!

レポジトリの内容は、煮るなり焼くなりどうぞ!

5/5 追記:諸事情によりレポジトリの公開をやめます。すみません🙇‍♀️

さいごに

いつの間にか陽は落ちかけて、川面 に朱色のさざ波が閃めき始めています。

ミケ:ふぅ~、やっとかきおわった~。いつの間にか5時間たってたよ….。

ミケ:もういい時間だ、おうちに戻るかにゃ~。

そういうと、ミケは自分の飼い主のおうちへと歩いていきました。

                            by ミケ

ーーーーもし間違い等ございましたらご報告くださいーーーー


この記事が気に入ったらサポートをしてみませんか?