はじめに
先日ブラックジャックを実装し,次に何を実装しようか悩んでいたら,友人とポーカーをする機会があり,ポーカーを実装することに決めました.ただルールや役に詳しくなかったこともあってかなり大変でした.
コンピュータ側の戦略は手札のカードによって機械的に決まるので,ブラフなどの戦略を取ることはありません.(交換するカードの選び方やビッドやレイズを行う条件を見てしまうと,簡単に勝ててしまうと思います.)今後機械学習などを用いたりしてもおもしろいと感じた.
役
- ロイヤルストレートフラッシュ
同種札で数字が1番高い順位にそろったもので,10・J・Q・K・Aとなります. - ストレート・フラッシュ
同種札で数字が順番に並んでいるものです.ただし……K・A・2……とは続きません. - フォア・カード
同位札が4枚そろったものです. - フルハウス
同位札が3枚と,同位札が2枚の役です.同じ役同士では3枚組で比較し,順位の高いほうが勝ちです. - フラッシュ
同種札が5枚そろったものです. - ストレート
スートの種類に関係なく,5枚のカードの数字が続いているものです. - スリーカード
同位札が3枚そろったものです. - ツウ・ペア
2枚ずつの同位札が2組.同じ役同士では高いほうのペアを比較して,順位の高いほうが勝ち.それも同じなら低いほうのペアを比較して,順位の高いほうが勝ち.またそれも同じなら,高いほうのペアで♠を持っている人が勝ちとなります. - ワン・ペア
同位札が2枚そろったもの.同じ役同士ではペアを比較して,順位の高いほうが勝ち.それも同じなら,ペアの中で♠を持っている人が勝ちとなります.
ルール
- 各プレイヤーは参加チップとして1枚ずつ場に出します.
- 親は,よく切ったカードを左側の人から順に1枚ずつ伏せて,手持ちが5枚ずつになるように配ります。残りはストックとします.
- 配られた手札を見て,親の左側の人から「ビッド」または「パス」を順に宣言していきます.誰か1人がビッドしたら,それ以後の人はパスできません.最初にビッドする人は,チップの枚数を言って場に出します.(チップは最高何枚までと、決めておいたほうがよいでしょう.)
- 次の人からは,コールかレイズかドロップでゲームを進めます.レイズする人は、チップの枚数を言わねばなりません.
- 最高のビッドに対して,誰もレイズをしないままに1巡したら,1回目のビッドは終わりです.
- ゲームに残った人が3人以上いた場合は,親の左側に近い人から手札を交換,ドローをします.
- より強い役をつくるために,手札の中の不要なカードを場に伏せて出し,捨てた枚数だけ親からもらいます。親は自分で交換しますが,何枚交換したかを告げます。そして,それぞれ新しい手札を検討します.
- ストック・カードがなくなったときは,捨て札を集めてよくシャッフルして,使います.
- ドローが済むと,2回目のビッドです。新しい手札を検討し,1回目の最初にビッドした人から「ビッド」か「チェック」の宣言をしていきます。このとき,誰か1人がビッドをしたら,それ以降の人はチェックできません.
- そして前回同様にコール・レイズ・ドロップをしてゲームを進めます.
- 最高のビッドに対して誰もレイズしないまま1巡したとき,このせりは終了です。手札を公開し,ほかのプレイヤーと比べます。その中で最強のポーカー・ハンド(役)を持った人が勝ちとなり,場にあるチップを全部獲得します.
- また,ほかのすべてのプレイヤーがドロップをした場合も同じです.
- こうして何回かゲームを続け,最終的には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;
}