Guide Area

Poker console game with AI in C++ (source code)

Poker console game introduction

This tutorial is a reflection of what I had to program for a school’s mandatory project. I developed a simple C++ Poker console game with adjusted rules and logic. The rules do not touch any well-known variety of the Poker game, so if you’re looking for some specific, fully working example, you should not read any further. This tutorial will be different. I will not dive deep into explanations where and why I coded that, why I did it this way, or so. I will describe the logic behind my game and some parts of the code that I consider important. At the end, I will add a full code of the game.

Logic and rules

Let’s start with the rules. The game consists of two players. One of them is going to be you, the second is artificial intelligence (CPU). Both of you get 200$ credit at the beginning of the game. Two cards at the beginning of each round are dealt to each of you, following three rounds, where one card per round is dealt. At the end of each series of rounds, you and CPU will have five cards.

This game is very simple and does not recognize card colors. That is something that you can implement into this solution yourself. The game works with following types of hands:

  • One pair
  • Two pairs
  • Three of a kind
  • Straight
  • Four of a kind
  • Full house

Unfortunately, flush, straight flush and royal flush are not implemented because of the simplicity of card colors.

Betting is allowed at the end of each round. CPU bets first and it can bet as much as it wants. Next, it’s you – you either call, fold or raise to a maximum of CPU’s budget. After that, CPU decides if it wants to call of fold.

Each series of rounds ends with a showdown of cards. Winner is decided and a new series of rounds starts. The game ends when any of you run out of money.

AI settings

Before I skip to code, it is important to note two variables of the code. Playerscore is a variable that holds information about player’s score based on his hand at the end of each round. Cpuscore is a variable that holds the same information about CPU. Variables cpu and player represent the two of you as objects. Function getrandomvalue() is just a random int generator. Below is the whole AI setting when it comes to betting. It is highly customizable and there is a plenty room to grow – all you have to do is add more and more conditions and statements.

if (cpu->getMoney() < player->getMoney()) {
    // CPU has less money than the player, so it will be more careful
    if (playerscore > cpuscore) {
        int randomnr = getrandomvalue(0, 10);
        // 30% chance that CPU will bluff
        if (randomnr < 3) {
            // If CPU bluffs, the amount will be low (his balance / 8);
            int bluff = getrandomvalue(0, cpu->getMoney()/8);
            return bluff;
        } else return 0;
    } else if (playerscore == cpuscore && !playerscore == 0 && !cpuscore == 0) {
        int randomnr = getrandomvalue(0, 10);
        // 30% chance that CPU will raise on equal score
        if (randomnr < 3) {
            // If score equals, the amount could be low or medium (his balance / 6);
            int equalscore = getrandomvalue(0, cpu->getMoney()/6);
            return equalscore;
        } else return 0;
    } else {
        int randomnr = getrandomvalue(0, 10);
        // 50% chance that CPU will raise with better cards
        if (randomnr < 5) {
            // If CPU has better cards, the amount could be low, medium and high (his balance / 4);
            int highscore = getrandomvalue(0, cpu->getMoney()/4);
            return highscore;
        } else return 0;
    }
} else if (cpu->getMoney() >= player->getMoney()) {
    // CPU has more money than the player, so it will be more daring
    if (playerscore > cpuscore) {
        int randomnr = getrandomvalue(0, 10);
        // 40% chance that CPU will bluff
        if (randomnr < 4) {
            // If CPU bluffs, the amount will be low (his balance / 8);
            int bluff = getrandomvalue(0, player->getMoney()/8);
            return bluff;
        } else return 0;
    } else if (playerscore == cpuscore && !playerscore == 0 && !cpuscore == 0) {
        int randomnr = getrandomvalue(0, 10);
        // 50% chance that CPU will raise on equal score
        if (randomnr < 5) {
            // If score equals, the amount could be low or medium (his balance / 6);
            int equalscore = getrandomvalue(0, player->getMoney()/6);
            return equalscore;
        } else return 0;
    } else {
        int randomnr = getrandomvalue(0, 10);
        // 70% chance that CPU will raise with better cards
        if (randomnr < 7) {
            // If CPU has better cards, the amount could be low, medium and high (his balance / 4);
            int highscore = getrandomvalue(0, player->getMoney()/4);
            return highscore;
        } else return 0;
    }
}

The way CPU decides to call or fold is described in code below:

if (roundNumber == 5) {
    if (playerscore > cpuscore) {
        int randomnr = getrandomvalue(0, 10);
        // 40% chance that CPU will call a lost game
        return randomnr < 3;
    } else {
        int randomnr = getrandomvalue(0, 10);
        // 70% chance that CPU will call a won game
        return randomnr < 7;
    }
} else {
    if (playerscore > cpuscore) {
        int randomnr = getrandomvalue(0, 10);
        // 40% chance that CPU will bluff or expect a better card to come
        return randomnr < 3;
    } else if (playerscore == cpuscore && !playerscore == 0 && !cpuscore == 0) {
        int randomnr = getrandomvalue(0, 10);
        // 50% chance that CPU will call on equal score
        return randomnr < 5;
    } else {
        int randomnr = getrandomvalue(0, 7);
        // 80% chance that CPU will call with better cards
        return randomnr < 7;
    }
}

Last but not least, playerscore and cpuscore are calculated very simply (read more in next chapter):

if (isonepair(input)) return 1;
else if (istwopairs(input)) return 2;
else if (isthreeofakind(input)) return 3;
else if (isstraight(input)) return 4;
else if (isfullhouse(input)) return 5;
else if (isfourofakind(input)) return 6;
else return 0;

Hands and their values

Each of the six supported hands has its own logic. Each part of the code has a bunch of comments which describe the workflow, but let’s take full house as an example, so you have a better understanding of the way it works.

Full house is a combination of cards in which you get one pair and one triplet, while holding maximum of 5 cards (which makes sense, but it’s important to realize that this number will never grow higher – at least with these rules). Let’s say we have a hand consisting of cards A A A J J . Our array of cards will be called cards and we will calculate the score by counting the number of appearances of each position in the array:

  • cards[0] = A, which occurs 3 times in the array, therefore the value of cards[0] = 3
  • cards[1] = A, which occurs 3 times in the array, therefore the value of cards[0] = 3
  • cards[2] = A, which occurs 3 times in the array, therefore the value of cards[0] = 3
  • cards[3] = J, which occurs 2 times in the array, therefore the value of cards[0] = 2
  • cards[4] = J, which occurs 2 times in the array, therefore the value of cards[0] = 2

3 + 3 + 3 + 2 + 2 = 13, so this number will from now on represent the full house. In the code below, you can read about the other hands:

bool isfourofakind(vector<string> input) {
    bool result = false;
    int counter = 0;
    for (int i = 0; i < input.size(); i++) {
        counter += count(input.begin(), input.end(), input[i]);
    }
    // Four of a kind consists of one single and four of the same, which means that the sum of occurences
    // for this condition must be 17, f.e. JKKKK is a four of a kind:
    // input[0] = J and it occurs there one, so counter += 1
    // input[1] = K and it occurs four times, so counter += 4
    // input[2] = K and it again occurs four times, so counter += 4
    // ...
    // and at the end, counter must equal to 17 in case of four of a kind
    if (counter == 17) {
        result = true;
    }
    return result;
}
bool isthreeofakind(vector<string> input) {
    bool result = false;
    int counter = 0;
    for (int i = 0; i < input.size(); i++) {
        counter += count(input.begin(), input.end(), input[i]);
    }
    // Three of a kind consists of two singles and three of the same, which means that the sum of occurences
    // for this condition must be 11, f.e. 96JJJ is a three of a kind:
    // input[0] = 9 and it occurs there once, so counter += 1
    // input[1] = 6 and it occurs also once, so counter += 1
    // input[2] = J and it again occurs three times, so counter += 3
    // ...
    // and at the end, counter must equal to 11 in case of three of a kind
    if (counter == 11) {
        result = true;
    }
    return result;
}
bool isfullhouse(vector<string> input) {
    bool result = false;
    int counter = 0;
    for (int i = 0; i < input.size(); i++) {
        counter += count(input.begin(), input.end(), input[i]);
    }
    // Full house consists of one pair and one triplet on hand, which means that the sum of occurences
    // for this condition must be 13, f.e. 33555 is a fullhouse:
    // input[0] = 3 and it occurs there twice, so counter += 2
    // input[1] = 3 and it occurs again twice, so counter += 2
    // input[2] = 5 and it occurs thrice, so counter += 3
    // ...
    // and at the end, counter must equal to 13 in case of full house
    if (counter == 13) {
        result = true;
    }
    return result;
}
bool isstraight(vector<string> input) {
    bool result = false;
    int counter = 0;
    for (int i = 0; i < input.size(); i++) {
        counter += count(input.begin(), input.end(), input[i]);
    }
    // Straight consists of five singles, which means that the sum of occurences
    // for this condition must be 5, f.e. 34567 is a straight:
    // input[0] = 3 and it occurs there once, so counter += 1
    // input[1] = 4 and it occurs again once, so counter += 1
    // input[2] = 5 and it occurs again once, so counter += 1
    // ...
    // and at the end, counter must equal to 5 in case of straight
    if (counter == 5) {
        // The first condition is fulfilled, now we have to check if elements match straight pattern
        string combined =
                string(input[0]) + string(input[1]) + string(input[2]) + string(input[3]) + string(input[4]);
        for (string x : straightpatterns) {
            if (x == combined) {
                result = true;
            }
        }
    }
    return result;
}
bool istwopairs(vector<string> input) {
    bool result = false;
    int counter = 0;
    for (int i = 0; i < input.size(); i++) {
        counter += count(input.begin(), input.end(), input[i]);
    }
    // Two pairs consists of two doubles, which means that the sum of occurences
    // for this condition must be 9, f.e. 22778 is a two-pair:
    // input[0] = 2 and it occurs there once, so counter += 1
    // input[1] = 2 and it occurs again once, so counter += 1
    // input[2] = 7 and it occurs again once, so counter += 1
    // input[3] = 7 and it occurs again once, so counter += 1
    // input[4] = 8 and it occurs again once, so counter += 1
    // and at the end, counter must equal to 9 in case of two-pair hand
    if (counter == 9) {
        result = true;
    }
    return result;
}
bool isonepair(vector<string> input) {
    bool result = false;
    int counter = 0;
    for (int i = 0; i < input.size(); i++) {
        counter += count(input.begin(), input.end(), input[i]);
    }
    // Two pairs consists of two doubles, which means that the sum of occurences
    // for this condition must be 7, f.e. 227J8 is a two-pair:
    // input[0] = 2 and it occurs there once, so counter += 2
    // input[1] = 2 and it occurs again once, so counter += 2
    // input[2] = 7 and it occurs again once, so counter += 1
    // input[3] = J and it occurs again once, so counter += 1
    // input[4] = 8 and it occurs again once, so counter += 1
    // and at the end, counter must equal to 7 in case of two-pair hand
    if (counter == 7) {
        result = true;
    }
    return result;
}

Logging

The code has a simple logger which outputs information into PokerCPP.log file located right next to the built .exe file. Activities that are logged are:

  • Application start
  • A new game start
  • A new player entered the game
  • When CPU wins round
  • When player wins round
  • The result of each game
  • Application exit

Logger in really a simple function which writes lines into file.

void writetolog(string text) {
  ofstream myfile;
  myfile.open("PokerCPP.log", std::ios_base::app);
  myfile << text << endl;
  myfile.flush();
  myfile.close();
}

 Source code

The source code is attached to this article in form of a zip archive file. I will not attach a zipped .exe file due to security reasons, so you will have to build the exe yourself, or run the program from an IDE. Visual Studio and CLion build the code without any problem, although system(“CLS”) will make you a bit of frustrated in CLion console, so I highly recommend Visual Studio for running. Enjoy!

Download source code here

Vladimir Marton

DevOps Engineer focused on cloud infrastructure, automation, CI/CD and programming in Javascript, Python, PHP and SQL. Guidearea is my oldest project where I write articles about programming, marketing, SEO and others.