1
1

ポーカーの実装が思いの外大変だった話

Last updated at Posted at 2024-08-11

はじめに

先日ブラックジャックを実装し,次に何を実装しようか悩んでいたら,友人とポーカーをする機会があり,ポーカーを実装することに決めました.ただルールや役に詳しくなかったこともあってかなり大変でした.
コンピュータ側の戦略は手札のカードによって機械的に決まるので,ブラフなどの戦略を取ることはありません.(交換するカードの選び方やビッドやレイズを行う条件を見てしまうと,簡単に勝ててしまうと思います.)今後機械学習などを用いたりしてもおもしろいと感じた.

  1. ロイヤルストレートフラッシュ
    同種札で数字が1番高い順位にそろったもので,10・J・Q・K・Aとなります.
  2. ストレート・フラッシュ
    同種札で数字が順番に並んでいるものです.ただし……K・A・2……とは続きません.
  3. フォア・カード
    同位札が4枚そろったものです.
  4. フルハウス
    同位札が3枚と,同位札が2枚の役です.同じ役同士では3枚組で比較し,順位の高いほうが勝ちです.
  5. フラッシュ
    同種札が5枚そろったものです.
  6. ストレート
    スートの種類に関係なく,5枚のカードの数字が続いているものです.
  7. スリーカード
    同位札が3枚そろったものです.
  8. ツウ・ペア
    2枚ずつの同位札が2組.同じ役同士では高いほうのペアを比較して,順位の高いほうが勝ち.それも同じなら低いほうのペアを比較して,順位の高いほうが勝ち.またそれも同じなら,高いほうのペアで♠を持っている人が勝ちとなります.
  9. ワン・ペア
    同位札が2枚そろったもの.同じ役同士ではペアを比較して,順位の高いほうが勝ち.それも同じなら,ペアの中で♠を持っている人が勝ちとなります.

ルール

  1. 各プレイヤーは参加チップとして1枚ずつ場に出します.
  2. 親は,よく切ったカードを左側の人から順に1枚ずつ伏せて,手持ちが5枚ずつになるように配ります。残りはストックとします.
  3. 配られた手札を見て,親の左側の人から「ビッド」または「パス」を順に宣言していきます.誰か1人がビッドしたら,それ以後の人はパスできません.最初にビッドする人は,チップの枚数を言って場に出します.(チップは最高何枚までと、決めておいたほうがよいでしょう.)
  4. 次の人からは,コールかレイズかドロップでゲームを進めます.レイズする人は、チップの枚数を言わねばなりません.
  5. 最高のビッドに対して,誰もレイズをしないままに1巡したら,1回目のビッドは終わりです.
  6. ゲームに残った人が3人以上いた場合は,親の左側に近い人から手札を交換,ドローをします.
  7. より強い役をつくるために,手札の中の不要なカードを場に伏せて出し,捨てた枚数だけ親からもらいます。親は自分で交換しますが,何枚交換したかを告げます。そして,それぞれ新しい手札を検討します.
  8. ストック・カードがなくなったときは,捨て札を集めてよくシャッフルして,使います.
  9. ドローが済むと,2回目のビッドです。新しい手札を検討し,1回目の最初にビッドした人から「ビッド」か「チェック」の宣言をしていきます。このとき,誰か1人がビッドをしたら,それ以降の人はチェックできません.
  10. そして前回同様にコール・レイズ・ドロップをしてゲームを進めます.
  11. 最高のビッドに対して誰もレイズしないまま1巡したとき,このせりは終了です。手札を公開し,ほかのプレイヤーと比べます。その中で最強のポーカー・ハンド(役)を持った人が勝ちとなり,場にあるチップを全部獲得します.
  12. また,ほかのすべてのプレイヤーがドロップをした場合も同じです.
  13. こうして何回かゲームを続け,最終的には1番多くのチップを持っている人から順に,1位、2位、3位……となります.

(参考:https://www.nintendo.com/jp/others/playing_cards/howtoplay/poker/index.html)

header.h
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <algorithm>
#define N 4
#define MAX 5
using namespace std;

class card {
public:
    char c;
    int num;
    card(int num1, char ch) {
        c = ch;
        num = num1;
    }
};

class player {
public:
    vector<card> cards;
    int chip;
    player() {
        chip = 10;
    }
};

extern vector<card> deck;
extern int player_score[N]; //プレイヤーとCOMのスコア(score関数参照)
extern vector<card> discard;

void initialization(void);
int score(vector<card> v);
vector<int> strategy(vector<card> v);
void show_card(int n, vector<card> cards);
void show_chip(int n, int chip);
void bid_or_pass(int& bet, int& flag, int &flag_2, int& n, player *players);
void call_or_raise(int& bet, int& flag, int& flag_2, int& n, player* players);
function.cpp
#include "header.h"

void initialization() { //山札生成
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    srand((unsigned int)time(NULL));
    char set_card[4] = { 's','h','d','c' };
    for (int i = 1;i <= 13; i++) {
        for (char j : set_card) {
            card c(i, j);
            deck.push_back(c);
        }
    }
}

int score(vector<card> v) { //スコア計算
    vector<int> set, sorted;
    vector<char> set_c;
    for (card i : v) {
        if (i.num == 1) {
            set.push_back(14);
            sorted.push_back(14);
        }
        else {
            set.push_back(i.num);
            sorted.push_back(i.num);
        }
        set_c.push_back(i.c);
    }
    sort(sorted.begin(), sorted.end());

    sort(set.begin(), set.end());
    auto result1 = unique(set.begin(), set.end());
    set.erase(result1, set.end());

    sort(set_c.begin(), set_c.end());
    auto result2 = unique(set_c.begin(), set_c.end());
    set_c.erase(result2, set_c.end());

    if (set.size() == 5 && sorted[0] == 10 && set_c.size() == 1) //ロイヤルストレートフラッシュ
        return 9;
    else if (set.size() == 5 && sorted[0] + 4 == sorted[4] && set_c.size() == 1) //ストレート・フラッシュ
        return 8;
    else if (sorted[0] == sorted[3] || sorted[1] == sorted[4]) //フォア・カード
        return 7;
    else if (set.size() == 2) //フルハウス
        return 6;
    else if (set_c.size() == 1) //フラッシュ
        return 5;
    else if (set.size() == 5 && sorted[0] + 4 == sorted[4]) //ストレート
        return 4;
    else if (sorted[0] == sorted[2] || sorted[1] == sorted[3] || sorted[2] == sorted[4]) //スリーカード
        return 3;
    else if (set.size() == 3) //ツウ・ペア
        return 2;
    else if (set.size() == 4) //ワン・ペア
        return 1;
    else
        return 0;
}

vector<int> strategy(vector<card> v) { //COMのカード交換戦略
    vector<int> ans, set_num, sorted;
    vector<char> e, set_c;
    for (card i : v) {
        if (i.num == 1) {
            set_num.push_back(14);
            sorted.push_back(14);
        }
        else {
            set_num.push_back(i.num);
            sorted.push_back(i.num);
        }
        e.push_back(i.c);
        set_c.push_back(i.c);
    }
    unique(set_c.begin(), set_c.end());
    sort(sorted.begin(), sorted.end());

    if (set_c.size() == 1 || set_num.size() == 2) {
        return ans;
    }
    else if (set_num.size() < 5) {
        for (int i = 0;i < 5;i++) {
            int cnt = count(sorted.begin(), sorted.end(), sorted[i]);
            if (cnt == 1) {
                ans.push_back(i);
            }
        }
        return ans;
    }
    else if ((set_c.size() == 2) && (count(e.begin(),e.end(),set_c[0]) == 1 || count(e.begin(), e.end(), set_c[0]) == 4)) {
        for (char c : set_c) {
            int cnt = count(e.begin(), e.end(), c);
            if (cnt == 1) {
                for (int i = 0;i < 5;i++) {
                    if (v[i].c == c) ans.push_back(i);
                    break;
                }
            }
        }
        return ans;
    }
    else if (set_num.size() == 5) {
        if (sorted[0] + 4 == sorted[4]) {
            return ans;
        }
        else if (sorted[0] + 3 == sorted[3]) {
            for (int i = 0;i < 5;i++) {
                if (v[i].num == sorted[4])
                    ans.push_back(i);
            }
        }
        else if (sorted[1] + 3 == sorted[4]) {
            for (int i = 0;i < 5;i++) {
                if (v[i].num == sorted[0])
                    ans.push_back(i);
            }
        }
        if (ans.size() > 0) {
            return ans;
        }
    }
    for (int i = 0;i < 5;i++) {
        ans.push_back(i);
    }
    for (int i = 0; i < ans.size(); i++) {
        discard.push_back(v[i]);
    }
    return ans;
}

void show_card(int n, vector<card> cards) { //カード出力
    if (n == 0) {
        cout << "Your ";
    }
    else {
        cout << "COM" << n << "'s ";
    }
    cout << "cards is ";
    for (int i = 0;i < 5;i++) {
        if (cards[i].c == 's') cout << "spade";
        if (cards[i].c == 'h') cout << "heart";
        if (cards[i].c == 'd') cout << "diamond";
        if (cards[i].c == 'c') cout << "club";
        cout << cards[i].num;
        if (i < 4) {
            cout << ", ";
        }
    }
    cout << ".\n";
}

void show_chip(int n, int chip) { //チップの残り枚数出力
    if (n == 0) {
        cout << "You have ";
    }
    else {
        cout << "COM" << n << " has ";
    }
    cout << chip << " chip(s) left.\n";
}

void bid_or_pass(int &bet, int &flag, int &flag_2, int &n, player *players) {
    int tmp = bet;
    for (int i = 1;i < N;i++) {
        n = i;
        if (player_score[i] > 1) { //COMはツーペアより良い役のときはビッド
            if (bet >= players[i].chip) {
                cout << "COM" << i << ": all in\n";
                flag++;
                continue;
            }
            cout << "COM" << i << ": bid,";
            int r = rand() % (MAX - bet) + 1;
            r = min(r, players[i].chip);
            cout << "add " << r << " more chip(s).\n";
            bet += r;
            flag = 0;
            break;
        }
        else {
            cout << "COM" << i << ": pass\n";
            flag++;
        }
    }

    if (tmp == bet && flag_2 == 0) {
        cout << "pass or bid or fold(0: pass,1: bid, 2: fold)";
        int input;
        cin >> input;
        flag++;
        n = 0;
        if (input == 1) {
            cout << "How many would you like to add?(Up to " << min(MAX, players[0].chip) - bet << " can be added.)";
            cin >> input;
            bet += input;
            flag = 0;
        }
        else if (input == 2) {
            flag_2 = 1;
            players[0].chip -= min(bet, players[0].chip);
        }
    }
}

void call_or_raise(int& bet, int& flag, int& flag_2, int& n, player* players) {
    if (n > 0 && n < N - 1) {
        for (int i = n + 1;i < N;i++) {
            n = i;
            if (player_score[i] > 4) { //COMはフラッシュより良い役のときはレイズ
                if (bet >= players[i].chip) {
                    cout << "COM" << i << ": all in\n";
                    flag++;
                    continue;
                }
                cout << "COM" << i << ": raise, ";
                int r = rand() % 4 + 1;
                r = min(r, players[i].chip);
                cout << "add" << r << " more chip(s).\n";
                bet += r;
                flag = 0;
            }
            else {
                cout << "COM" << i << ": call\n";
                flag++;
            }
        }
    }

    while (flag < N - 1 && bet < MAX) {
        n = (n + 1) % N;
        if (player_score[n] > 5 && n != 0) {
            if (bet >= players[n].chip) {
                cout << "COM" << n << ": all in\n";
                flag++;
                continue;
            }
            cout << "COM" << n << ": raise, ";
            int r = rand() % (MAX - bet) + 1;
            r = min(r, players[n].chip);
            cout << "add" << r << " more chip(s).\n";
            bet += r;
            flag = 0;
        }
        else if (n != 0) {
            cout << "COM" << n << ": call\n";
            flag++;
        }
        else if (flag_2 == 0) {
            cout << "call or raise or fold(0: call, 1: raise, 2: fold)";
            int input;
            cin >> input;
            flag++;
            if (input == 1) {
                cout << "How many chips would you like to add?(Up to " << min(MAX, players[0].chip) - bet << " can be added.)";
                cin >> input;
                bet += input;
                flag = 0;
            }
            else if (input == 2) {
                flag_2 = 1;
                players[0].chip -= min(bet, players[0].chip);
            }
        }
    }
}
main.cpp
#include "header.h"

vector<card> deck; //山札
int player_score[N]; //プレイヤーとCOMのスコア(score関数参照)
vector<card> discard;

int main(void) {
    int judge = 1; //ループ判定用変数
    player players[N]; //プレイヤーとCOMのカード,チップ記憶
    
    while (judge > 0) { //誰かのチップがなくなるまで無限ループ
        initialization(); //山札生成
        int bet = 1; //場に一人が賭けているチップ数
        for (int i = 0;i < N;i++) { //一人に五枚ずつ配る
            vector<card> c = {};
            for (int j = 0;j < 5;j++) {
                int r = rand() % (52 - 5 * i - j);
                c.push_back(deck[r]);
                deck.erase(deck.begin() + r);
            }
            players[i].cards = c;
        }

        show_card(0, players[0].cards); //プレイヤーのカード出力

        for (int i = 0;i < N;i++) {
            player_score[i] = score(players[i].cards); //全員のスコア計算
        }

        int n = 0;
        int flag = 0;
        int flag_2 = 0;

        bid_or_pass(bet, flag, flag_2, n, players);
        call_or_raise(bet, flag, flag_2, n, players);

        if (flag_2 == 0) {
            cout << "How many cards would you like to exchange?";
            cin >> n;
            if (n == 5) { //5枚全て交換するとき
                for (int i = 0;i < 5;i++) {
                    int r = rand() % (32 - i);
                    players[0].cards[i] = deck[r];
                    deck.erase(deck.begin() + r);
                }
            }
            else if (n > 0) {
                cout << "Which card(s) would you like to replace? (Enter separated by spaces)";
                for (int i = 0;i < n;i++) {
                    int rep;
                    cin >> rep;
                    discard.push_back(players[0].cards[rep - 1]);
                    int r = rand() % (52 - 5*N - i);
                    players[0].cards[rep - 1] = deck[r];
                    deck.erase(deck.begin() + r);
                }
            }
            show_card(0, players[0].cards); //交換後のプレイヤーのカード出力
        }

        for (int i = 1;i < N;i++) {
            vector<int> rep = strategy(players[i].cards); //COMが交換するカード
            for (int j : rep) {
                int r = rand() % (deck.size());
                players[i].cards[j] = deck[r];
                deck.erase(deck.begin() + r);
                if (deck.size() == 0) {
                    copy(discard.begin(), discard.end(), deck.begin());
                    discard.clear();
                }
            }
            cout << "COM" << i << ": exchanged " << rep.size() << " cards.\n";
        }

        n = 0;
        flag = 0;
        bid_or_pass(bet, flag, flag_2, n, players);
        int sum = 0;
        for (int i = 0;i < N;i++) {
            if (i == 0 && flag_2 == 1) {
                continue;
            }
            int a = min(bet, players[i].chip);
            sum += a;
            players[i].chip -= a;
        }

        vector<int> card_sum;
        for (int i = 0;i < N;i++) {
            if (i == 0 && flag_2 == 1) {
                card_sum.push_back(0);
                player_score[0] = 0;
                continue;
            }
            player_score[i] = score(players[i].cards);
            int a = 0;
            for (int j = 0;j < 5;j++) {
                if (players[i].cards[j].num == 1) {
                    a += 14;
                }
                else {
                    a += players[i].cards[j].num;
                }
            }
            card_sum.push_back(a);
            if (i > 0) {
                show_card(i, players[i].cards); //交換後のCOMのカード出力
            }
        }
        int winner = *max_element(begin(player_score), end(player_score));

        if (count(begin(player_score), end(player_score), winner) == 1) { //スコアで一人勝ちの場合はそのまま場のチップ総取り
            for (int i = 0;i < N;i++) {
                if (player_score[i] == winner) {
                    players[i].chip += sum;
                    if (i == 0) {
                        cout << "player win\n";
                    }
                    else {
                        cout << "COM" << i << " wins\n";
                    }
                }
            }
        }
        else { //スコアで同点の人がいる場合,カードの総和で勝負(ただしポーカーでは1が一番強いので,1は14としてカウント)
            vector<int> winners,tmp;
            for (int i = 0;i < N;i++) {
                if (player_score[i] == winner) {
                    winners.push_back(i);
                }
            }
            for (int i : winners) {
                tmp.push_back(card_sum[i]);
            }
            int max = *max_element(tmp.begin(), tmp.end());
            if (count(tmp.begin(), tmp.end(), max) == 1) { //カードの総和で一人勝ちの場合はそのまま場のチップ総取り
                for (int i : winners) {
                    if (card_sum[i] == max) {
                        players[i].chip += sum;
                        if (i == 0) {
                            cout << "player win\n";
                        }
                        else {
                            cout << "COM" << i << " wins\n";
                        }
                    }
                }
            }
            else { //カードの総和で同点の人がいる場合はそのまま場のチップを山分け
                int n = count(tmp.begin(), tmp.end(), max);
                for (int i : winners) {
                    if (card_sum[i] == max) {
                        players[i].chip += sum / n;
                        if (i == 0) {
                            cout << "player and";
                        }
                        else {
                            cout << "COM" << i << " and";
                        }
                    }
                }
                cout << " win\n";
            }
        }
        for (int i = 0;i < N;i++) {
            show_chip(i, players[i].chip);
        }
        cout << "\n";
        judge = 1;
        for(int i=0;i<N;i++){
            judge *= players[i].chip;
        }
    }
    return 0;
}
1
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1