モチベーションと目的
Javaについて少し知っておこうと思い昨日(6/21)から勉強を始めました。
6/21にprogateでJavaの基礎的なことは学んだのでアウトプットしようと思います。
作るのは何でもよかったのですが、私が愛してやまない頭脳スポーツ「テキサスホールデム」を作ってみようと思います。
就職に向けてアウトプットソースを増やす目的と、日記的な意味合いで学習の軌跡をここにメモっておくことにしました。
予定
1日目:progateでJavaについて知る(割愛)
2,3日目:とりあえず実装(できたらいいな)
4日目:オブジェクト指向に倣いコードを整える(予定)
実装機能(簡単のため相当簡略化)
プレイヤーは自分自身と相手(CPU)のみのヘッズアップ
CPUはハンドの強さではなく乱数でアクションを決定する
BTNは常に相手側でブラインドは無し
どちらかのチップがなくなったら終了
実装にあたって、
デッキの作成
デッキのシャッフル
プレイヤーに手札を配る
の処理は非常にわかりやすいサイトがありましたのでこちらをパクっ参考にさせていただきました。
Javaでなにか作りたい人必見!入門書を読み終えた人向けゲーム作成
2日目で実装できた機能は
プリフロップの一連の流れ
だけです、、値の参照渡しの理解に結構時間がかかった
コード解説
public static void main(String[] args) {
System.out.println("セッションを開始します!グッドラック!");
//デッキを作成
List <Integer> deck = new ArrayList<>(52);
//デッキをシャッフル
shuffleDeck(deck);
//プレイヤーの手札リストを生成
List <Integer> hero = new ArrayList<>();
List <Integer> enemy = new ArrayList<>();
//プレイヤーにカードを二枚ずつ配る
hero.add(deck.get(0));
enemy.add(deck.get(1));
hero.add(deck.get(2));
enemy.add(deck.get(3));
//山札の進行状況を記録する変数deckCountを定義
int deckCount = 4;
//プレイヤーの持っているチップを定義(初期値100)potも定義(初期値0)
int list[] = {100,100,0,0,0};
//[0] herochip(自分)/
//[1] enemychip(相手)/
//[2] pot/
//[3] needChiptoCall
//[4]foldFlag
//===foldFlag===
// 0 foldしてない
// 1 heroがfold
// 2 enemy がfold
Scanner scan = new Scanner(System.in);
//プリフロップの処理
preFlop(hero, list, scan);
//各プレイヤーのアクションの後にneedtocallが0の場合にそのストリートが終了したとできる
while(true) {
enemyAction(list);
if(list[4] == 2) {
System.out.println("enemyは降りましたheroの勝ちです");
break;
}else if(list[3] == 0) {
System.out.println("フロップに進みます");
break;
}
preFlop(hero, list, scan);
if(list[4] == 1) {
System.out.println("heroは降りましたenemyの勝ちです");
break;
}else if(list[3] == 0) {
System.out.println("フロップに進みます");
break;
}
}
//ターン、リバーの処理
//ショーダウン。勝敗の決定
//チップの移動
//チップがなくなったら終了
まだ全然テクニカルなことがわからないため、配列listに必要なデータをぶちこんで丸ごと渡すっていう汚いやり方です。
自分(hero)のアクション
ポーカーでの基本的なアクションは、「チェック、コール、ベット、レイズ、フォールド」の五種類
相手のベット(レイズ)が入っていない場合は「チェック、ベット」の2種類
相手のベット(レイズ)が入ってる場合は「コール、レイズ、フォールド」の3種類のアクションがある。
それぞれ「checkBet」「callRaiseFold」のメソッドとして定義した。(命名のセンスが皆無である)
checkBet
private static boolean checkBet(int list[], String str, Scanner scan){
if("c".equals(str)) {//チェックしたときの処理
//相手に回す
System.out.println("checkしました。");
return true;
}else if("b".equals(str)) {//ベットしたとき。ベット額を聞いてチップを移動、
//ベット額を聞く
while(true) {
System.out.println("いくらベットしますか?最大" + list[0] +"$です");
int bet = scan.nextInt();
if(bet <= list[0] && bet >= list[2]) {//ベット額が持ってる分を越えてる場合とポットより少ないときは
bet(list, bet);
break;
}else {
System.out.println("ベット額が不正です。正しい値を入力してください");
}
}
return true;
}else {
return false;
}
}
callRaiseFold
private static boolean callRaiseFold(int list[], String str, Scanner scan){
if("c".equals(str)) {//コールしたときの処理
//相手に回す
System.out.println("callしました。");
return true;
}else if("r".equals(str)) {//ベットしたとき。ベット額を聞いてチップを移動、
//ベット額を聞く
while(true) {
System.out.println("いくらレイズしますか?最大" + list[0] +"$です");
int raise = scan.nextInt();
if(raise <= list[0] && raise >= list[3]) {//レイス額が持ってる分を越えてる場合と前のベット(レイズ)より小さいばあい
raise(list, raise);
break;
}else {
System.out.println("レイズ額が不正です。正しい値を入力してください");
}
}
return true;
}else if("f".equals(str)){
System.out.println("フォールドします");
list[4] = 1;
return true;
}else {
return false;
}
}
boolean型で定義。コマンドラインからの入力が不正なものであった場合はfalseを返してループをするようにした。
また、foldした際はlist[4]をfoldFlagとして、heroがfoldした場合は1,enemyがfoldした場合は2とする。(デフォルトは0)
相手(enemy)のアクション
randomライブラリを用いて乱数生成、数字で割ったあまりで分類し、アクションを決定する。
switch文は見やすくてすごい好き。
private static void enemyAction(int list[]) {//敵のアクション(乱数で変動)
Random rand = new Random();
int randomValue = rand.nextInt(100);//乱数を生成
if(list[3] == 0) {//check or bet
switch(randomValue%2) {
case 0://チェックする
System.out.println("enemyはcheckしました。");
break;
case 1://ベット
int bet = list[2];//ベット額はポットサイズで固定
System.out.println("ememyは"+bet+"$ベットしました。");
//手持ちチップを減らし、ポットをふやす、needtoCallも増やす
list[1] = list[1] - bet;
list[2] = list[2] + bet;
list[3] = bet;
break;
}
}else {//call or raise or fold
switch(randomValue % 3) {
case 0://コールする
System.out.println("enemyはcallしました。");
//手持ちチップを減らしポットをふやす。needtocallをリセット
list[1] = list[1] - list[3];
list[2] = list[2] + list[3];
list[3] = 0;
break;
case 1:
int raise = list[3] * 2;
if(raise >= list[1]) {
raise = list[1];
System.out.println("ememyはAll-inです。");
}else {
System.out.println("ememyは"+raise+"$にレイズしました。");
}
//needtocall-raise = 次のneedtocall
list[1] = list[1] - raise;
list[2] = list[2] + raise;
list[3] = list[3] - raise;
break;
case 2:
System.out.println("ememyはfoldしました");
list[4] = 2;
break;
}
}
}
プリフロップの処理
list[3]が0かどうかでレイズが入っていない(フロップに進める)かどうかを判断した。ここの処理を決定するのに時間がかかった。銭湯の中で思いついた。
入力がエラーの場合は入力を再度要求する。foldFlagが立っていたら勝敗決定の処理に移動する。
private static void preFlop(List<Integer> hero, int list[], Scanner scan) {
printData(hero, list);
if(list[3] == 0) {//needchiptocallが0の時つまり、ベットレイズが入っていないとき
while(true) {
System.out.println("アクションを選択してください check : c or bet : b");
String str = scan.next();
boolean flag = checkBet(list, str, scan);
if(flag == true) {
break;
}else {
System.out.println("入力が不正です!");
}
}
}else {
while(true) {
System.out.println("アクションを選択してください call : c or raise : r or fold : f");
String str = scan.next();
boolean flag = callRaiseFold(list, str, scan);
if(flag == true) {
break;
}else {
System.out.println("入力が不正です!");
}
}
}
}
実行結果
セッションを開始します!グッドラック!
あなたのハンドは♥A♣Kです
あなたの所持チップは100です
ポットは0です
アクションを選択してください check : c or bet : b
b
いくらベットしますか?最大100$です
3
3$ベットしました
ememyは6$にレイズしました。
あなたのハンドは♥A♣Kです
あなたの所持チップは97です
ポットは9です
アクションを選択してください call : c or raise : r or fold : f
r
いくらレイズしますか?最大97$です
30
30$にレイズしました
enemyはcallしました。
フロップに進みます
明日以降
アクションのメソッドを使いまわせばターン、リバーの処理は簡単に実装できそう
ハンド(役)の勝敗の決定の仕方をどうしたらいいのかまだわからないそこに時間がかかりそう
寝違えた首が痛い(かなり)
文章を普段書かない+人に見せる用ではないので、読みにくいのは勘弁してください・・・