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

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

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

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

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

```cpp: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);
```

```cpp: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);
            }
        }
    }
}
```

```cpp: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;
}
```