
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 ミケ
ーーーーもし間違い等ございましたらご報告くださいーーーー