I’ve ben involved with the development of Cgreen for a few years, so when Software Craftsmanship Linköping asked me to do a TDD session for them I obviously choose that as my basis.
Cgreen is nice for allowing modern TDDing in C (and C++) using fluent API, mocks and the rest.
I talked and we coded. I selected the Texas Hold’em kata, which is interesting because of the multitude of dimensions that need to be covered. It is also a good kata to retry to experiment with different order of the tests. (Actually, I did it from memory and got it wrong, players have 2 private cards and community cards are delt until player folds. So the tests below are inaccurate.)
Here are the tests (of course I wrote one at a time with red-green-refactor in between):
#include <cgreen/cgreen.h>
#include "thm.h"
/* First test: drives infrastructure, format of cards, hands, return value is constant */
Ensure(single_player_with_highcard_wins) {
assert_that(play_game("7D 9H AS 2H 8C 4D 5S\n"),
is_equal_to_string("\n7D 9H AS 2H 8C 4D 5S High Card Winner"));
}
/* Second test: drives identifying cards with same value */
Ensure(single_player_with_pair_wins) {
assert_that(play_game("7D 9H AS AH 8C 4D 5S\n"),
is_equal_to_string("\n7D 9H AS AH 8C 4D 5S Pair Winner"));
}
/* Third test: next step along "same value" dimension */
Ensure(single_player_with_three_of_a_kind_wins) {
assert_that(play_game("7D 8H AS AH 8C 4D 8S\n"),
is_equal_to_string("\n7D 8H AS AH 8C 4D 8S Three Of A Kind Winner"));
}
/* Fourth test: drives finding a winner */
/* Actually also drove change of input/output format to make it easier to read */
Ensure(player_with_pair_wins_over_player_with_high_card) {
assert_that(play_game("7D 9H AS 2H 8C 4D 5S\n"
"7H AH AS 2H 8C 4D 5S\n"),
is_equal_to_string("\n7D 9H AS 2H 8C 4D 5S High Card"
"\n7H AH AS 2H 8C 4D 5S Pair Winner"));
}
/* Fifth test: drives finding the actual winner even if it's not the first hand */
Ensure(player_with_high_card_loses_to_player_with_pair) {
assert_that(play_game("AS 9H AS 2H TC 4D 5S\n"
"6D 7H AS 2H TC 4D 5S\n"),
is_equal_to_string("\nAS 9H AS 2H TC 4D 5S Pair Winner"
"\n6D 7H AS 2H TC 4D 5S High Card"));
}
/* Sixth test: handling three of a kind - still "same value" dimension */
Ensure(player_with_three_of_a_kind_wins_over_pair) {
assert_that(play_game("AS 9H AS 2H 8C 4D 5S\n" /* Pair */
"2D 2S AS 2H 8C 4D 5S\n"), /* Three Of A Kind */
is_equal_to_string("\nAS 9H AS 2H 8C 4D 5S Pair"
"\n2D 2S AS 2H 8C 4D 5S Three Of A Kind Winner"));
}
/* Seventh test: handling three players - done in "number of players" dimension */
Ensure(player_with_three_of_a_kind_wins_over_high_card_and_pair) {
assert_that(play_game("AS 9H AS 2H TC 4D 5S\n" /* Pair */
"2D 2S AS 2H TC 4D 5S\n" /* Three Of A Kind */
"6D 7H AS 2H TC 4D 5S\n"), /* High Card */
is_equal_to_string("\nAS 9H AS 2H TC 4D 5S Pair"
"\n2D 2S AS 2H TC 4D 5S Three Of A Kind Winner"
"\n6D 7H AS 2H TC 4D 5S High Card"));
}
/* Eight test: handle four of a kind - done in "same value" dimension */
Ensure(player_with_four_of_a_kind_wins_over_three_of_a_kind) {
assert_that(play_game("AS AH AS 2H AC 4D 5S\n" /* Four Of A Kind */
"2D 2S AS 2H AC 4D 5S\n"), /* Three Of A Kind */
is_equal_to_string("\nAS AH AS 2H AC 4D 5S Four Of A Kind Winner"
"\n2D 2S AS 2H AC 4D 5S Three Of A Kind"));
}
/* Ninth test: handle flush */
Ensure(player_with_flush_beats_high_card) {
assert_that(play_game("QS KS AS 2S 4H 5S 9C\n" /* Flush */
"7D 8S AS 2S 4H 5S 9C\n"), /* High Card */
is_equal_to_string("\nQS KS AS 2S 4H 5S 9C Flush Winner"
"\n7D 8S AS 2S 4H 5S 9C High Card"));
}
/* Tenth test: start handling straight - match exact positions */
Ensure(player_with_straight_in_fixed_positions_beats_three_of_a_kind) {
assert_that(play_game("2S KC AS 2S 2H 5S 9C\n" /* Three Of A Kind */
"3D 4S AS 2S 2H 5S 9C\n"), /* Straight */
is_equal_to_string("\n2S KC AS 2S 2H 5S 9C Three Of A Kind"
"\n3D 4S AS 2S 2H 5S 9C Straight Winner"));
}
/* Eleventh test: straight cont. - assume starts with 'A' find others */
Ensure(player_with_straight_starting_with_ace_beats_three_of_a_kind) {
assert_that(play_game("2S KC AS 2S 2H 5S 9C\n" /* Three Of A Kind */
"4D 3S AS 2S 2H 5S 9C\n"), /* Straight */
is_equal_to_string("\n2S KC AS 2S 2H 5S 9C Three Of A Kind"
"\n4D 3S AS 2S 2H 5S 9C Straight Winner"));
}
/* Twelweth: straight cont. - starting with any value */
Ensure(player_with_straight_starting_with_three_beats_three_of_a_kind) {
assert_that(play_game("2S 2C 3S 6S 2H 7S 9C\n" /* Three Of A Kind */
"4D 5S 3S 6S 2H 7S 9C\n"), /* Straight */
is_equal_to_string("\n2S 2C 3S 6S 2H 7S 9C Three Of A Kind"
"\n4D 5S 3S 6S 2H 7S 9C Straight Winner"));
}
/* Thirteenth: straight cont. - ending with Ace */
Ensure(player_with_straight_ending_in_ace_beats_high_card) {
assert_that(play_game("AS 2C TS JS QH KS 8C\n" /* Straight */
"7H 5C TS JS QH KS 8C\n"), /* High Card */
is_equal_to_string("\nAS 2C TS JS QH KS 8C Straight Winner"
"\n7H 5C TS JS QH KS 8C High Card"));
}
/* Fourtheenth: straight cont. - starting in any position */
Ensure(player_with_straight_starting_in_any_position_beats_high_card) {
assert_that(play_game("AS 2C 8S JS QH KS TC\n" /* Straight */
"7H 5C 8S JS QH KS TC\n"), /* High Card */
is_equal_to_string("\nAS 2C 8S JS QH KS TC Straight Winner"
"\n7H 5C 8S JS QH KS TC High Card"));
}
And here is the implementation (C string handling is a bit hairy…):
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
static char *add_string_on_new_line(char *start, char *tail) {
char *string;
string = malloc(strlen(start) + 1 /*newline*/ + strlen(tail) + 1 /*null*/);
string = strcpy(string, start);
string = strcat(string, "\n");
string = strcat(string, tail);
return string;
}
static char *add_string_with_space(char *start, char *tail) {
char *string;
string = malloc(strlen(start) + 1 /*space*/ + strlen(tail) + 1 /*null*/);
string = strcpy(string, start);
string = strcat(string, " ");
string = strcat(string, tail);
return string;
}
static char color_of_card(char *hand, int card_number) {
return hand[card_number*3+1];
}
static struct {char card; int value;} card_value_table[] = {
{'A', 1},
{'T', 10},
{'J', 11},
{'Q', 12},
{'K', 13}
};
static int char2value(char card) {
for (int i=0; i<sizeof(card_value_table)/sizeof(card_value_table[0]); i++)
if (card_value_table[i].card == card)
return card_value_table[i].value;
return card-'0';
}
static char value_of_card(char *hand, int card_number) {
return char2value(hand[card_number*3]);
}
static bool same_value_of_cards(char *hand, int i, int j) {
return value_of_card(hand, i) == value_of_card(hand, j);
}
static bool has_four_of_a_kind(char *hand) {
for (int i=0; i<7; i++)
for (int j=i+1; j<7; j++)
if (same_value_of_cards(hand, i, j))
for (int k=j+1; k<7; k++)
if (same_value_of_cards(hand, i, k))
for (int l=k+1; l<7; l++)
if (same_value_of_cards(hand, i, l))
return true;
return false;
}
static bool has_flush_in_color(char *hand, char color) {
int count = 0;
for (int i=0; i<7; i++)
if (color_of_card(hand, i) == color)
count++;
return count == 5;
}
static bool has_flush(char *hand) {
static char *colors = "SCHD";
for (int color_index=0; color_index<4; color_index++) {
if (has_flush_in_color(hand, colors[color_index]))
return true;
}
return false;
}
static int card_with_value_exists(char *hand, char value) {
for (int i=0; i<7; i++) {
if (value_of_card(hand, i) == value)
return true;
if (value == 14 && value_of_card(hand, i) == 1)
return true;
}
return false;
}
static bool four_next_cards_in_sequence_exists(char *hand, int start_value) {
for (char value=start_value+1; value<start_value+5; value++)
if (!card_with_value_exists(hand, value))
return false;
return true;
}
static bool has_straight(char *hand) {
for (int card=0; card<7; card++) {
int start_value = value_of_card(hand, card);
if (four_next_cards_in_sequence_exists(hand, start_value))
return true;
}
return false;
}
static bool has_three_of_a_kind(char *hand) {
for (int i=0; i<7; i++)
for (int j=i+1; j<7; j++)
if (value_of_card(hand, i) == value_of_card(hand, j))
for (int k=j+1; k<7; k++)
if (value_of_card(hand, i) == value_of_card(hand, k))
return true;
return false;
}
static bool has_pair(char *hand) {
for (int i=0; i<7; i++)
for (int j=i+1; j<7; j++)
if (value_of_card(hand, i) == value_of_card(hand, j))
return true;
return false;
}
static char *rank_names[] = {
"High Card",
"Pair",
"Three Of A Kind",
"Straight",
"Flush",
"Four Of A Kind"
};
static int rank_of_hand(char *hand) {
if (has_four_of_a_kind(hand))
return 5;
else if (has_flush(hand))
return 4;
else if (has_straight(hand))
return 3;
else if (has_three_of_a_kind(hand))
return 2;
else if (has_pair(hand))
return 1;
else /* has_high_card() */
return 0;
}
static char *get_hand(char *hands, int hand_number) {
char *hand = strdup(&hands[hand_number*(3*7)]);
hand[20] = '\0'; /* Remove newline */
return hand;
}
static int find_winner(int rank[]) {
int max_rank = 0;
int winner = 0;
for (int i=0; rank[i] != -1; i++)
if (rank[i] >= max_rank) {
max_rank = rank[i];
winner = i;
}
return winner;
}
static int count_hands(char *hands) {
int hand_count = 0;
for (char *c = hands; *c != '\0'; hand_count += *c++ == '\n'?1:0);
return hand_count;
}
static void get_hands(char *hand[], char *hands) {
int hand_count = count_hands(hands);
for (int i=0; i<hand_count; i++) {
hand[i] = get_hand(hands, i);
}
hand[hand_count] = NULL;
}
static void rank_hands(int rank[], char *hand[]) {
for (int hand_index=0; hand[hand_index] != NULL; hand_index++) {
rank[hand_index] = rank_of_hand(hand[hand_index]);
rank[hand_index+1] = -1;
}
}
static char *result(char *hands[], int rank[], int winner) {
char *string = "";
for (int hand_index=0; hands[hand_index] != NULL; hand_index++) {
string = add_string_on_new_line(string, hands[hand_index]);
string = add_string_with_space(string, rank_names[rank[hand_index]]);
if (winner == hand_index)
string = add_string_with_space(string, "Winner");
}
return string;
}
char *play_game(char *deal) {
char **hands;
int *ranks;
int hand_count = count_hands(deal);
hands = malloc(hand_count*sizeof(char*)+1);
ranks = malloc(hand_count*sizeof(int)+1);
get_hands(hands, deal);
rank_hands(ranks, hands);
int winner = find_winner(ranks);
return result(hands, ranks, winner);
}
Note that I’m not nearly done. That’s another thing with this kata, it’s long. E.g. there are a number of smaller refactorings that I’d like to do from this point.
What’s you suggestion for next action? Next test?

One Reply to “Texas Hold’em with Cgreen”