/* 一人で遊ぶポーカーのプログラム(ジョーカー入り) ジョーカーを一枚使う。最初にコインを何枚か賭ける。五枚のカードが手札と して配られ、そのうちの零枚から五枚を選んで、一度だけ交換すると、交換後 の手札の役に応じた枚数のコインがもらえる。これのくり返し。 トランプカードは、0 から 51 の数で表す。0 から 12 がスペードの A から K を、13 から 25 がハートの A から K を、26 から 38 がクラブの A から K を、39 から 51 がダイヤモンドの A から K を意味する。(こう決めると、数 を 13 で割った商がスートを、余りがランクを表すことになる。これは、昔 ながらの決まったやり方の一つらしい。) 内部では、ジョーカーは 52 で表している。また、ジョーカーはその時点での 手札に含まれないどんなカードの代わりにもなる、というルールを採用した。 役の判定に当っては、ジョーカーを 52 枚のカードすべてで順に置き換えて、 できた役のうち最高の役になったものを役としている。だから、ジョーカー を手札に含まれているカードで置き換えてできる役も含めている。(これでも 同じ結果になるはずである。) ここまでで習ったことだけを使って書いてある。C言語の全貌を学べばもっと うまく書ける部分を含んでいる。 (画面制御エスケープシーケンスは使っていない。) poker.c からの、その他の改変箇所は以下の通り。 最初にカードが配られたときにも役を表示するようにした。 乱数の種に、現在時刻だけでなく持っているコインの枚数も関係するように した。(前のままでは、二人で同時に動かして一人が全カードを交換し、それ を見てもう一人が交換するカードを選ぶという裏技ができたので。) 当ったコインの枚数が、行頭でない位置に表示されるようにした。次に行頭 から出力される、持っているコインの枚数と間違える場合があったため。 交換するカードを入力し、Enter を押した時点で、再度、乱数が初期化される ようにした。そのほうが、自分で運を切りひらいた感じが強まると思うので。 コイン枚数がオーバーフローして負になった場合も破産メッセージが出るよう にした。 */ #include #include /* rand() */ #include /* time() */ #define YES 1 #define NO 0 #define EMPTY (-1) /* カッコでくくるのは、もしも a = 1 + EMPTY * b などの式 */ /* を書いてもエラーにならないため。念のため。 */ #define JOKER 52 #define COINS 100 /* 最初に持っているコインの枚数。変えてよい */ int card[53]; /* ジョーカーを使うので 53 枚 */ int hand[6]; /* 手札。hand[0] は使わない */ int a[6]; /* hand[i] を新たにひくなら a[i] には YES を、そうでなければ */ /* NO を入れる。a[0] は使わない */ void initrand(int coins); void initcard(void); void draw(void); void printhands(void); int analyse0(void); int analyse(void); void printresult(int get); main() { int i, x, y; int coins, bet, get; for (coins = COINS; coins > 0; ) { printf("--------------------\n"); /* 新しいゲームの始まりを示す線 */ for (bet = -1; bet < 0; ) { printf("%d 枚のコインを持っています.\n", coins); printf("何枚賭けますか? (0 を入力すれば終了)>"); scanf("%d", &bet); if (bet < 0) { printf("負の枚数は賭けられません.\n"); } if (bet > coins) { printf("そんなに持っていませんよ.\n"); bet = -1; /* これでやり直すことになる */ } } if (bet == 0) { coins = 0; /* これでプログラムの終了へ */ } else { initrand(coins); /* 乱数の初期化 */ initcard(); /* カードの初期化 */ for (i = 1; i <= 5; i++) { /* 最初は五枚ひくので全部 YES */ a[i] = YES; } draw(); /* カードをひく */ /* 次の五行はデバッグの跡。復活させればインチキも可能! */ /* hand[1] = 0; hand[2] = 13; hand[3] = 26; hand[4] = 39; hand[5] = JOKER; */ printhands(); /* 手札を画面表示 */ printresult(analyse0()); /* 役を画面表示 */ printf("どれを交換しますか? --- 例:1, 2, 5 枚目"); printf("なら 125 と入力。一枚も変えないなら 0 と入力\n"); scanf("%d", &x); initrand(coins); /* ここで再度、乱数を初期化 */ /* ユーザが x に入力した値を分析し、i 枚目を交換するなら a[i] に */ /* YES が、交換しないなら NO がはいるようにする。次とその次の */ /* ループでそうなるんだけど、どうしてかは自分で考えて。 */ for (i = 1; i <= 5; i++) { a[i] = NO; } for ( ; x != 0; x = x / 10) { y = x % 10; if (y >= 1 && y <= 5) { a[y] = YES; } } draw(); /* カードを交換する(実際はひく)*/ printhands(); /* 手札を画面表示 */ get = analyse0(); /* 手札を分析。倍率が返ってくる */ printresult(get); /* 結果を出力 */ if (get > 0) { printf("%d 枚のコインが当たりました.\n", get * bet); } else { printf("残念でした.\n"); } coins = coins + (get - 1) * bet; if (coins <= 0) { printf("あなたは破産しました....\n"); } } } } /* 乱数を初期化する。(この関数はわからなくてもよい。)*/ void initrand(int coins) { unsigned seed = (unsigned)time(NULL); /* 現在時刻を取得して */ seed = seed + coins; /* それにコインの枚数を足す */ printf("乱数の種は %u です.\n", seed); srand(seed); /* それを乱数の種に */ } /* card[ ](カード)を初期化する */ void initcard(void) { int i; for (i = 0; i <= 52; i++) { /* YES はまだそのカードが引かれていない */ card[i] = YES; /* ことを示す印 */ } } /* カードをひく。a[i](i は 1 から 5)が YES であるような i に対し、hand[i] */ /* を選ぶ。カードはスペードの A からダイヤモンドの K まで順番に並んでおり、 */ /* そのあとにジョーカーが置かれている。乱数を使ってその中から一枚を選ぶ。 */ /* すでにひかれたカードを再度選ぶことを防ぐため、ひいたカードに対する */ /* card[n] は YES から NO に変える。NO のカードを選んでしまったら、再度 */ /* ひく。このゲームでは最大で 10 枚のカードしか使わないので、このやり方で */ /* まず大丈夫であろう。 */ void draw(void) { int i, n; for (i = 1; i <= 5; i++) { if (a[i] == YES) { for (hand[i] = EMPTY; hand[i] == EMPTY; ) { n = rand() % 53; /* これが選ばれたカード。 */ if (card[n] == YES) { /* 「残っている」だったら */ hand[i] = n; /* そのカードを hand[i] に代入、*/ card[n] = NO; /* 「残っていない」に変える */ } } } } } /* 手札の出力。非常にシンプルにしてある。ここだけを変えることも可能。hand[i] */ /* の値は i 番目のカードを意味する。値とカードの関係は冒頭のコメントを参照。 */ void printhands(void) { int i, y; for (i = 1; i <= 5; i++) { /* カードの数(ランク)を表示 */ if (hand[i] == JOKER) { printf(" ? "); /* ジョーカーはランクの位置に「?」を書く */ } else { y = hand[i] % 13; /* これがカードの数(ランク)を示す */ if (y == 0) { /* 数(ランク)の表示 */ printf(" A "); } else if (y == 10) { printf(" J "); } else if (y == 11) { printf(" Q "); } else if (y == 12) { printf(" K "); } else { printf("%2d ", y+1); } } } printf("\n"); for (i = 1; i <= 5; i++) { /* カードのマーク(スート)を表示 */ if (hand[i] == JOKER) { /* ジョーカーはスートの位置も「?」を書く */ printf(" ? "); } else { y = hand[i] / 13; /* これがカードのマーク(スート)を示す */ if (y == 0) { printf(" ! "); /* スペード(のつもり)*/ } else if (y == 1) { printf(" & "); /* ハート(のつもり)*/ } else if (y == 2) { printf(" %% "); /* クラブ(のつもり)*/ } else { /*(「%」を出力させるときは「%%」)*/ printf(" @ "); /* ダイヤモンド(のつもり) */ } } } printf("\n"); } /* 手札を解析し、倍率を返す。具体的には、ジョーカーがあれば、それを 52 枚の */ /* 普通のカードで順に置き換えつつ analyse() を呼んで、返ってきた倍率のうちで */ /* 最高のものを返す。ジョーカーがなければ、analyse() を呼んで、その返して */ /* きた値(倍率)を返すだけ。 */ int analyse0(void) { int i, j, x, get, joker; joker = NO; for (i = 1; i <= 5; i++) { if (hand[i] == JOKER) { joker = YES; get = 0; for (j = 0; j < 52; j++) { hand[i] = j; x = analyse(); if (x > get) { get = x; } } hand[i] = JOKER; /* 置き換えたのをジョーカーに戻しておく */ } } if (joker == YES) { return get; } else { return analyse(); } } /* 手札の役の解析を行ない、役に応じた倍率を返す。ジョーカーは手札に含まれて */ /* いないと仮定しているが、ジョーカーを普通のカードで置き換えた場合も扱うの */ /* で、同じカードが二枚ある場合にも対応している! */ int analyse(void) { int i, j, x, y; int samerank, sequence, sequence2, flush, get; /* 数(ランク)が一致する組(ペア)の数を数える。この数でワンペア、 */ /* ツーペア、スリーカード、フルハウスなどが判定できるのは有名な事実 */ samerank = 0; for (i = 1; i <= 5; i++) { for (j = i+1; j <= 5; j++) { if (hand[i] % 13 == hand[j] % 13) { samerank++; } } } /* ランクが一致するペアがない場合に限り、隣り合ったランクのペアの数を */ /* 数える。これはストレートかどうかを判定するためなので、ランクが一致 */ /* するペアがある場合は 0 とする。 */ sequence = 0; sequence2 = 0; if (samerank == 0) { for (i = 1; i <= 5; i++) { for (j = i+1; j <= 5; j++) { x = hand[i] % 13 - hand[j] % 13; if (x == 1 || x == -1) { sequence++; } } } /* ここで sequence が 4 ならストレート(10-J-Q-K-A 以外)*/ for (i = 1; i <= 5; i++) { for (j = i+1; j <= 5; j++) { if (hand[i] % 13 == 0) { /* A は 13 と置き換えてから */ x = 13; } else { x = hand[i] % 13; } if (hand[j] % 13 == 0) { y = 13; } else { y = hand[j] % 13; } if (x == y + 1 || x + 1 == y) { /* 上と同じことを行なう */ sequence2++; /* (細部は違うけど) */ } } } /* ここで sequence2 が 4 ならストレート(A-2-3-4-5 以外) */ } /* 次に、マーク(スート)がすべて同じかどうかの判定を行なう */ /* (素直に && で四つの条件をつなげたほうがよかったかも) */ flush = YES; for (i = 1; i <= 4; i++) { if (hand[i] / 13 != hand[i+1] / 13) { flush = NO; } } /* 以上で、解析の材料はそろった。あとは判定するのみ。get は倍率。*/ if (samerank == 1) { get = 1; /* ワンペア */ } else if (samerank == 2) { get = 2; /* ツーペア */ } else if (samerank == 3) { get = 3; /* スリーカード */ } else if ((sequence == 4 || sequence2 == 4) && flush == NO) { get = 4; /* ストレート */ } else if (sequence != 4 && sequence2 != 4 && flush == YES) { get = 5; /* フラッシュ */ } else if (samerank == 4) { get = 10; /* フルハウスです */ } else if (samerank == 6) { get = 20; /* フォーカード */ } else if (sequence == 4 && flush == YES) { get = 50; /* ストレートフラッシュ */ } else if (samerank == 10) { get = 100; /* ファイブカード */ } else if (sequence != 4 && sequence2 == 4 && flush == YES) { get = 500; /* ロイヤルストレートフラッシュ */ } else { get = 0; } return get; } /* 結果の出力。ここと analyse() の終わり近くとで、数値が合っていないと */ /* いけないのが、めんどうな、というか、うまく書けていないところ。 */ void printresult(int get) { if (get == 1) { printf("ワンペアです. "); } else if (get == 2) { printf("ツーペアです. "); } else if (get == 3) { printf("スリーカードです. "); } else if (get == 4) { printf("ストレートです! "); } else if (get == 5) { printf("フラッシュです! "); } else if (get == 10) { printf("フルハウスです! "); } else if (get == 20) { printf("フォーカードです!! "); } else if (get == 50) { printf("ストレートフラッシュです!!! "); } else if (get == 100) { printf("ファイブカードです!!!! "); } else if (get == 500) { printf("ロイヤルストレートフラッシュです!!!!! "); } }