Date: Wed, 19 May 1999 14:53:04 -0400 From: xxx To: James Holliday Subject: Blackjack Strategy \* Here is the program bj-strat, which calculates blackjack strategy numbers. The program reads the point count values for cards from the command line, and prints the corresponding count strategy on the standard output. Running time for the program is approximately two minutes on the VS-2000, for the Hi-Opt I or Wong High-Low strategy, for the count range +/- 10, if the infinite deck approximation is used. For a finite number of decks, the time required is roughly 35 minutes, for the range +/- 6. Usage: The first ten arguments to the program should be the point values of the cards two through ace. After the card values, the following additional arguments are permitted, in any order: -a value value of ace in side count -n decks number of decks (infinite deck approximation if omitted) -d double-on-splits is allowed -h dealer hits soft 17 -r low high only consider true count values from low to high, inclusive -s step only consider true count values that are multiples of step Use the -a option if a side count is used for *playing* purposes. The value of the ace is is the number of points to add to the running count for each ``missing'' ace, i.e. if 26 cards are left and no aces are left, add 2 * value to the running count. Do *not* use this option if a side count is used for *betting* purposes. If the -r and -s options are omitted, the program considers the full range of count values. Examples: 1) Hi-Opt I numbers from -6 to 6; double on splits allowed, single deck: bj-strat 0 1 1 1 1 0 0 0 -1 0 -d -n 1 -r -6 6 2) High-Low with ace side count, -10 to +10 range, 2 decks: bj-strat 1 1 1 1 1 0 0 0 -1 -1 -n 2 -a 1 -r -10 10 3) Wong halves numbers from -20 to +20; dealer hits soft 17, infinite deck: bj-strat 1 2 2 3 2 1 0 -1 -2 -2 -h -r -40 40 -s 2 Output numbers must be divided by 2 in this case, because the halves count actually uses card values 1/2 to 1 1/2, not 1 to 3. This method is used because the program only accepts integer card values. Output: In the output, the symbol * next to a number indicates that the strategy changes in the opposite direction from that normally expected, e.g. if the number 7* appears in the pair split table, you should split only when the count is less than 7. The notation a/b indicates that the player should stand/double/split when the count is in the range from a to b; the notation a^b indicates that the player should stand/double/split _unless_ the count is between a and b. I have commented out the program lines that print ranges. Currently, the program only prints the strategy change number closest to zero if a range of numbers is involved. These lines are near the end of the of the program, and can be uncommented to print ranges, if desired. The more extreme values in the strategy tables (e.g. greater than +/- 20 for High-Low or Hi-Opt I) probably should not be taken too seriously. The approximation used to generate decks with specified point counts tends to break down at extreme values. How it works: The approach used in the program is fairly straightforward. First, I generate a ``typical'' deck for each true count in the range I'm interested in. Next, I assume I have an infinite deck of this composition. I calculate the player's ideal strategy for each of these decks, and look for the point count(s) at which the correct strategy for a situation changes. The assumption of an infinite deck simplifies the calculations and saves computer time, but introduces a small amount of inaccuracy. My results should be good for multiple deck play, and may also be adequate for single deck. (Last summer, during the BJ comparison contest, Matt Wilding found no measurable difference in performance between my strategy numbers for the Hi-Opt I and the published numbers). I use the following formula to construct a ``typical'' deck for a given true count. It is based on altering the frequency of cards in proportion to their point values in the counting system, until the desired true count is achieved. (Stanford Wong and Peter Griffin used similar formulas in their work). This formula assumes that the count is balanced, i.e. the point value of a complete deck is zero. Let Fo[i] = proportion of cards of rank i in a normal deck, i.e. 4/13 if i=10, 1/13 if 2 <= i <= 9 or i = Ace Let Fj[i] be the proportion of cards of rank i in a ``typical'' deck with a true count of j Let V[i] be the point value for card i in the counting system then Fj[i] = Fo[i] * (1 - (j/52)*V[i]/SS) where SS = sum over i in 2..Ace of Fo[i] * V[i]**2 The above formula is used for the infinite deck approximation. To approximate a finite number of decks, I first remove the dealer's upcard and player's cards, if known, from a finite deck. I then adjust cards' frequencies in proportion to their point count values. Finally, I assume I have an infinite deck of the resulting composition. (This is what Stanford Wong did to estimate strategy numbers for a finite deck). If an ace side count is used, I combine the main count and side count into a single point count system Vc, and use Vc instead of V in all calculations. Let Va be the value of the ace in the side count (zero if a side count is not used). then Vc[i] = V[i] - Va / 13, for i in 2..10 Vc[Ace] = V[Ace] + (12/13) * Va To determine the correct strategy for a given deck, I perform an exact probability calculation (i.e. simulation is not used at all). I calculate the player's expectation standing on a given hand vs. a given dealer hand as follows: standing_value(p, d) = if (dealer stands on d) then if (p beats d) then +1 else if (d beats p) then -1 else 0 else <--- dealer hits sum over c in 2..ace probablity[c] * standing_value(p, d + c) Each value calculated is stored in a table, to avoid calculating the same values twice. The program tries to look up the answer in the table first, and performs the calculation only if necessary. The player's expectation hitting is calculated as follows: hitting_value(p, d) = sum over c in 2..ace if (p + c is over 21) then probability[c] * -1 else probability[c] * maximum(hitting_value (p + c, d), standing_value(p + c, d)) Hitting values are also stored in a table, to avoid redundant calculations. The strategy calls for a player to hit if his hitting value is greater than his standing value. Constructing tables for doubling down and splitting is straightforward; the hit and stand tables make it easy to compute the values for the other tables. */ /* -------------------------------------------------------------------- */ /* Copyright (c) 1990, 1991 Steven C. Markowitz. All rights reserved. */ #include#define ACE 11 #define FALSE 0 #define TRUE 1 #define HARD 0 #define SOFT 1 #define OVER 2 #define DUMMY 0 #ifdef __STDC__ extern void *calloc(); #define FREE(x) free((void *) (x)) #else extern char *calloc(); #define FREE(x) free((char *) (x)) #endif float max(x, y) double x, y; { return ((x > y) ? x : y); } float min(x, y) double x, y; { return ((x < y) ? x : y); } typedef int boolean; typedef int Card; typedef float Deck[12]; /* deck[i] = proportion of cards in deck that are of type i. Deck[0] and deck[1] are not used. */ typedef float Method[12]; /* method [i] = point value of card i in counting method. Method[0] and method[1] are not used. */ /* A total represents a blackjack hand; it may be hard, soft or over 21 */ typedef struct { int tag; int value; } Total; /* A table holds real numbers. Some table elements may not have been calculated yet; this is indicated by the unknown flag. */ #define TOTALS 32 /* number of distinct blackjack totals */ #define CARDS 10 /* number different types of cards in a deck */ typedef struct { boolean unknown; float value; } Mfloat; typedef Mfloat **Table; /* A strategy describes the correct strategy for playing blackjack, for a given deck composition */ typedef struct { Deck deck; boolean dbs, soft17; Table standing_values, /* player's expectation when standing */ hitting_values, /* player's expectation hitting */ doubling_values, /* player's expectation doubling down */ splitting_values; /* player's expectation splitting */ } Strategy; /* A threshold t indicates the count at which a player should change strategy. There are six types of thresholds: POOR -- always hit/never double/never split RICH -- always stand/double/split NORMAL -- stand/double/split only if true count exceeds t.change_rich REVERSE -- stand/double/split when true count is less than t.change_poor RANGE -- stand/double/split if t.change_rich < true_count < t.change_poor REVRANGE -- stand/double/split UNLESS t.change_poor < count < t.change_rich */ #define POOR 0 #define RICH 1 #define NORMAL 2 #define REVERSE 3 #define RANGE 4 #define REVRANGE 5 typedef struct { int type, change_poor, change_rich; } Threshold; Threshold *hit_thresholds[TOTALS][CARDS], *double_thresholds[TOTALS][CARDS], *split_thresholds[CARDS][CARDS]; /* Operations on totals: */ /* make_hard: returns a hard total with a specified value */ Total make_hard(value) int value; { Total t; t.tag = HARD; t.value = value; return t; } /* make_soft: returns a soft total with a specified value */ Total make_soft(value) int value; { Total t; t.tag = SOFT; t.value = value; return t; } /* make_over: returns a total that is over 21 */ Total make_over() { Total t; t.tag = OVER; t.value = NULL; return t; } /* c2t: returns total corresponding to a given card */ Total c2t(c) Card c; { if (c == ACE) return make_soft(c); else return make_hard(c); } /* over21: returns true if total t is over 21, false otherwise */ boolean over21(t) Total t; { return (t.tag == OVER); } boolean soft21(t) Total t; { return ((t.tag == SOFT) && (t.value == 21)); } /* sum: returns the total obtained by adding a card c to a total t */ Total sum(t, c) Total t; Card c; { int new_value; Total new_total; if (t.tag == OVER) return t; new_value = t.value + c; if ((t.tag) == SOFT) new_value = new_value - 10; if (c == ACE) new_value = new_value - 10; if (new_value > 21) { new_total.tag = OVER; new_total.value = NULL; } else if ((new_value <= 11) && ((t.tag == SOFT) || (c == ACE))) { new_total.tag = SOFT; new_total.value = new_value + 10; } else { new_total.tag = HARD; new_total.value = new_value; } return new_total; } /* Operations on tables */ /* t_create: constructs and returns a new table with the specified number of rows and columns, and all elements unknown */ Table t_create(rows, columns) int rows, columns; { int i,j; Table tab = (Table) calloc((unsigned) (rows + 1), sizeof(Mfloat *)); for (i = 0; i < rows; i++) { tab[i] = (Mfloat *) calloc((unsigned) columns, sizeof(Mfloat)); for (j = 0; j < columns; j++) tab[i][j].unknown = TRUE; } tab[rows] = NULL; return tab; } Table tt_create() { return t_create(TOTALS, TOTALS); } Table ct_create() { return t_create(TOTALS, CARDS); } Table pt_create() { return t_create(CARDS, CARDS); } /* t_destroy: frees storage associated with a table */ void t_destroy(tab) Table tab; { int i; for (i = 0; tab[i]; i++) FREE(tab[i]); FREE(tab); } /* tt_fetch: returns the table element indexed by totals t1 and t2 */ Mfloat tt_fetch(tab, t1, t2) Table tab; Total t1, t2; { return tab[t2i(t1)][t2i(t2)]; } /* tt_store: stores value into a the table location specified by totals t1 and t2 */ void tt_store(tab, t1, t2, value) Table tab; Total t1, t2; double value; { int i = t2i(t1); int j = t2i(t2); tab[i][j].unknown = FALSE; tab[i][j].value = (float) value; } /* ct_fetch: returns the table element indexed by total tot and card c */ Mfloat ct_fetch(tab, tot, c) Table tab; Total tot; Card c; { return tab[t2i(tot)][c2i(c)]; } /* ct_store: stores value into the table location specified by total tot and card c */ void ct_store(tab, tot, c, value) Table tab; Total tot; Card c; double value; { int i = t2i(tot); int j = c2i(c); tab[i][j].unknown = FALSE; tab[i][j].value = (float) value; } /* pt_fetch: returns the table element indexed by cards c1 and c2 */ Mfloat pt_fetch(tab, c1, c2) Table tab; Card c1, c2; { return tab[c2i(c1)][c2i(c2)]; } /* pt_store: stores value into a the table location specified by cards c1 and c2 */ void pt_store(tab, c1, c2, value) Table tab; Card c1, c2; double value; { int i = c2i(c1); int j = c2i(c2); tab[i][j].unknown = FALSE; tab[i][j].value = (float) value; } /* t2i: returns the table index corresponding to a total */ int t2i(t) Total t; { switch (t.tag) { case OVER: /* over 21 corresponds to index 31 */ return 31; case HARD: /* hard totals 2..10 correspond to indexes 0 to 8 */ if (t.value <= 10) /* hard 11-21 corresponds to indexes 20 to 30 */ return t.value - 2; else return(t.value + 9); case SOFT: /* soft totals correspond to indexes 9 to 19 */ return t.value - 2; } } /* returns the table index corresponding to a card */ int c2i(c) Card c; { return (c - 2); /* cards 2..ace correspond to indexes 0 to 9 */ } /* Operations on strategies */ /* s_create: constructs and returns strategy object for specified deck */ Strategy *s_create(d, dbs, soft17) Deck d; boolean dbs, soft17; { Card c; Strategy *s = (Strategy *) calloc(1, sizeof(Strategy)); for (c = 2; c <= ACE; c++) s->deck[c] = d[c]; s->dbs = dbs; s->soft17 = soft17; s->standing_values = tt_create(); s->hitting_values = ct_create(); s->doubling_values = ct_create(); s->splitting_values = pt_create(); return s; } /* s_destroy: frees heap space allocated for strategy object */ void s_destroy(s) Strategy *s; { t_destroy(s->standing_values); t_destroy(s->hitting_values); t_destroy(s->doubling_values); t_destroy(s->splitting_values); FREE(s); } boolean final(s, t) Strategy *s; Total t; { return ((t.tag == OVER) || (t.value > 17) || (t.value == 17) && ((t.tag == HARD) || (! s->soft17))); } /* standing_value: returns player's expectation standing with player_hand when dealer has dealer_hand. If check_bj is true then the dealer's next card must not give him soft 21; otherwise the next card may be of any value */ float standing_value(s, player_hand, dealer_hand, check_bj) Strategy *s; Total player_hand, dealer_hand; boolean check_bj; { extern float correct_bj(); Mfloat answer; answer = tt_fetch(s->standing_values, player_hand, dealer_hand); if (answer.unknown) { float expectation; if (over21(player_hand)) expectation = -1.0; else if (final(s, dealer_hand)) if (over21(dealer_hand) || (dealer_hand.value < player_hand.value)) expectation = 1.0; else if (dealer_hand.value > player_hand.value) expectation = -1.0; else expectation = 0.0; else { Card c; expectation = 0.0; for (c = 2; c <= ACE; c++) { Total new_hand; new_hand = sum(dealer_hand, c); expectation = expectation + s->deck[c] * standing_value(s, player_hand, new_hand, FALSE); } } tt_store(s->standing_values, player_hand, dealer_hand, expectation); if (check_bj) return correct_bj(expectation, player_hand, dealer_hand, s->deck); else return expectation; } else { /* answer already calculated */ if (check_bj) return correct_bj(answer.value, player_hand, dealer_hand, s->deck); else return answer.value; } } /* correct_bj: adjust expectation to take into account the fact that the dealer cannot have soft 21 */ float correct_bj(expectation, player_hand, dealer_hand, d) double expectation; Total player_hand, dealer_hand; Deck d; { float bj_prob, adjustment; if ((dealer_hand.tag == HARD) && (dealer_hand.value == 10)) bj_prob = d[ACE]; else if ((dealer_hand.tag == SOFT) && (dealer_hand.value == ACE)) bj_prob = d[10]; else bj_prob = 0.0; if (over21(player_hand) || (player_hand.value < 21)) adjustment = 1.0; else adjustment = 0.0; return((expectation + adjustment * bj_prob) / (1.0 - bj_prob)); } /* hitting_value: returns player's expectation hitting with player_hand when dealer has upcard. */ float hitting_value(s, player_hand, upcard) Strategy *s; Total player_hand; Card upcard; { Mfloat answer; answer = ct_fetch(s->hitting_values, player_hand, upcard); if (answer.unknown) { float expectation; if (over21(player_hand)) expectation = -1.0; else { Card c; expectation = 0.0; for (c = 2; c <= ACE; c++) { Total new_hand; new_hand = sum(player_hand, c); expectation = expectation + s->deck[c] * max(standing_value(s, new_hand, c2t(upcard), TRUE), hitting_value (s, new_hand, upcard)); } } ct_store(s->hitting_values, player_hand, upcard, expectation); return expectation; } else return answer.value; } /* hit: returns TRUE if player should hit player_hand vs. upcard; otherwise returns FALSE */ boolean hit(s, player_hand, upcard) Strategy *s; Total player_hand; Card upcard; { return(hitting_value (s, player_hand, upcard) > standing_value(s, player_hand, c2t(upcard), TRUE)); } /* doubling_value: returns player's expectation doubling down with player_hand vs. upcard. */ float doubling_value(s, player_hand, upcard) Strategy *s; Total player_hand; Card upcard; { Mfloat answer; answer = ct_fetch(s->doubling_values, player_hand, upcard); if (answer.unknown) { Card c; float expectation = 0.0; for (c = 2; c <= ACE; c++) { Total new_hand; new_hand = sum(player_hand, c); expectation = expectation + 2.0 * s->deck[c] * standing_value(s, new_hand, c2t(upcard), TRUE); } ct_store(s->doubling_values, player_hand, upcard, expectation); return expectation; } else return answer.value; } /* double_down: returns TRUE if player should double down with player_hand vs. upcard; otherwise returns FALSE */ boolean double_down(s, player_hand, upcard) Strategy *s; Total player_hand; Card upcard; { return(doubling_value(s, player_hand, upcard) > max(standing_value(s, player_hand, c2t(upcard), TRUE), hitting_value (s, player_hand, upcard))); } /* splitting_value: returns player's expectation splitting a pair of rank pair when the dealer has upcard. */ float splitting_value(s, pair, upcard) Strategy *s; Card pair, upcard; { Mfloat answer; answer = pt_fetch(s->splitting_values, pair, upcard); if (answer.unknown) { Card c; float expectation = 0.0; for (c = 2; c <= ACE; c++) { Total new_hand; new_hand = sum(c2t(pair), c); if (pair == ACE) expectation = expectation + 2.0 * s->deck[c] * standing_value(s, new_hand, c2t(upcard), TRUE); else expectation = expectation + 2.0 * s->deck[c] * max(standing_value(s, new_hand, c2t(upcard), TRUE), s->dbs ? max(hitting_value (s, new_hand, upcard), doubling_value(s, new_hand, upcard)) : hitting_value(s, new_hand, upcard)); } pt_store(s->splitting_values, pair, upcard, expectation); return expectation; } else return answer.value; } /* split: returns TRUE if pair should be split vs. upcard, otherwise FALSE */ boolean split(s, pair, upcard) Strategy *s; Card pair, upcard; { Total player_hand; player_hand = sum(c2t(pair), pair); return (splitting_value(s, pair, upcard) > max(standing_value(s, player_hand, c2t(upcard), TRUE), max(hitting_value (s, player_hand, upcard), doubling_value(s, player_hand, upcard)))); } /* make_deck: constructs and returns typical deck having a true count of count, where counting_method[i] indicates the value of cards of rank i in the counting system, for 2 <= i <= ACE, decks is the number of decks in use (zero means infinite deck), p1 and p2 are the player's cards and upcard is the dealer's upcard. P1, p2 and upcard are ignored if zero. */ Deck *make_deck(counting_method, true_count, decks, p1, p2, upcard) Method counting_method; double true_count; int decks; Card p1, p2, upcard; { Card c; Deck *d; Deck base_deck; float total_cards = 52.0 * decks; int undealt_cards = total_cards; float mean = 0.0, variance = 0.0; for (c = 2; c <= ACE; c++) base_deck[c] = ((c == 10) ? 4.0 : 1.0) / 13.0; if (decks > 0) { if (p1 > 0) { base_deck[p1] = base_deck[p1] - 1.0 / total_cards; undealt_cards--; } if (p2 > 0) { base_deck[p2] = base_deck[p2] - 1.0 / total_cards; undealt_cards--; } if (upcard > 0) { base_deck[upcard] = base_deck[upcard] - 1.0 / total_cards; undealt_cards--; } for (c = 2; c <= ACE; c++) base_deck[c] = base_deck[c] * total_cards / undealt_cards; } for (c = 2; c <= ACE; c++) mean = mean + base_deck[c] * counting_method[c]; for (c = 2; c <= ACE; c++) variance = variance + base_deck[c] * counting_method[c] * counting_method[c]; variance = variance - mean * mean; d = (Deck *) calloc(1, sizeof(Deck)); for (c = 2; c <= ACE; c++) (*d)[c] = base_deck[c] * (1.0 - (true_count / 52.0 + mean) * (counting_method[c] - mean) / variance); return d; } /* main: takes a counting method description from the command line and prints the corresponding count strategy adjustments on the standard output */ main(argc, argv) int argc; char **argv; { boolean dbs = FALSE, soft17 = FALSE, found_decks = FALSE, found_range = FALSE, found_step = FALSE, found_side_count = FALSE; int main_count[12]; /* point values of cards in main count */ Method counting_method; /* point values of cards after combining main count and ace side count */ Card c, c2, pair; Strategy *s; Deck *d; int lower, upper, step, true_count; int decks = 0; int ace_side_value = 0; float sum_of_squares; extern void error(), /* prints specified error message and halt program */ error_usage(); /* prints usage error message and halts program */ extern int parse_int(); /* read counting method from command line */ if (argc < 11) error_usage(); for (c = 2; c <= ACE; c++) main_count[c] = parse_int(*++argv); /* read remaining arguments */ argc = argc - 11; while ((argc) && ((*++argv)[0] == '-')) { if (((*argv)[1] == 'd') || ((*argv)[1] == 'h')) { char next; while ((next = *++*argv) != '\0') switch (next) { case 'd': dbs = TRUE; break; case 'h': soft17 = TRUE; break; default: error_usage(); break; } argc--; } else if ((! strcmp(*argv, "-n")) && (! found_decks) && (argc >= 2)) { found_decks = TRUE; decks = parse_int(*++argv); argc = argc - 2; } else if ((! strcmp(*argv, "-a")) && (! found_side_count) && (argc >= 2)) { found_side_count = TRUE; ace_side_value = parse_int(*++argv); argc = argc - 2; } else if ((! strcmp(*argv, "-r")) && (! found_range) && (argc >= 3)) { found_range = TRUE; lower = parse_int(*++argv); upper = parse_int(*++argv); argc = argc - 3; } else if ((! strcmp(*argv, "-s")) && (! found_step) && (argc >= 2)) { found_step = TRUE; step = parse_int(*++argv); argc = argc - 2; } else error_usage(); } if (argc > 0) error_usage(); if (found_decks && decks <= 0) error("number of decks must be positive"); { /* adjust counting method for ace side count and check for illegal counting method (unbalanced or all zero) */ int adjusted_value; int total_points = 0; boolean all_zero = TRUE; for (c = 2; c <= ACE; c++) { if (c == ACE) adjusted_value = 13 * main_count[c] + ace_side_value * 12; else adjusted_value = 13 * main_count[c] - ace_side_value; if (c == 10) total_points += 4 * adjusted_value; else total_points += adjusted_value; if (adjusted_value != 0) all_zero = FALSE; counting_method[c] = adjusted_value / 13.; } if (total_points != 0) error("unbalanced count not allowed"); if (all_zero) if (ace_side_value == 0) error("all card values are zero"); else error("ace side count cancels out main count"); } if (found_range && (lower > upper)) error("lower limit greater than upper limit"); if (found_step) if (step < 1) error("step value must be positive"); else ; /* step value OK */ else step = 1; /* print title and copyright notice */ fprintf(stderr, "bj-strat version 1.2 -- May 11, 1991\n"); fprintf(stderr, "Copyright (c) 1990, 1991 Steven C. Markowitz. "); fprintf(stderr, "All rights reserved.\n"); { /* trim lower and upper limits to legal range */ float vmin = counting_method[2]; float vmax = vmin; float total_cards = 52.0 * decks; float lowest_allowed, highest_allowed; sum_of_squares = 0.0; for (c = 2; c <= ACE; c++) sum_of_squares = sum_of_squares + ((c == 10) ? 4.0 : 1.0) / 13.0 * (counting_method[c] * counting_method[c]); for (c = 3; c <= ACE; c++) { float card_value = counting_method[c]; vmin = min(vmin, card_value); vmax = max(vmax, card_value); } lowest_allowed = 52.0 * ((sum_of_squares + (decks ? 3 * vmax * (vmin - vmax) / total_cards : 0)) / (vmin + (decks ? 3 * (vmax - vmin) / total_cards : 0))) + 0.5 * step; highest_allowed = 52.0 * ((sum_of_squares + (decks ? 3 * vmin * (vmax - vmin) / total_cards : 0)) / (vmax + (decks ? 3 * (vmin - vmax) / total_cards : 0))) - 0.5 * step; if (found_range) { lower = max((double) lower, lowest_allowed); upper = min((double) upper, highest_allowed); } else { lower = lowest_allowed; upper = highest_allowed; } lower = step * (lower / step); upper = step * (upper / step); } /* initialize threshold tables using strategy for most negative deck */ fprintf(stderr, "Calculating strategy for most negative deck\n"); if (decks == 0) { d = make_deck(counting_method, lower - 0.5 * step, decks, DUMMY, DUMMY, DUMMY); s = s_create(*d, dbs, soft17); FREE(d); } for (c = 2; c <= ACE; c++) { Total player_hand; Threshold *t; int i; if (decks > 0) { d = make_deck(counting_method, lower - 0.5 * step, decks, DUMMY, DUMMY, c); s = s_create(*d, dbs, soft17); FREE(d); } for (i = 12; i <= 21; i++) { t = (Threshold *) calloc(1, sizeof(Threshold)); player_hand = make_hard(i); t->type = hit(s, player_hand, c) ? POOR : RICH; hit_thresholds[t2i(player_hand)][c2i(c)] = t; } for (i = 17; i <= 21; i++) { t = (Threshold *) calloc(1, sizeof(Threshold)); player_hand = make_soft(i); t->type = hit(s, player_hand, c) ? POOR : RICH; hit_thresholds[t2i(player_hand)][c2i(c)] = t; } for (i = 4; i <= 11; i++) { t = (Threshold *) calloc(1, sizeof(Threshold)); player_hand = make_hard(i); t->type = double_down(s, player_hand, c) ? RICH : POOR; double_thresholds[t2i(player_hand)][c2i(c)] = t; } for (c2 = 2; c2 <= c; c2++) { if (decks > 0) { s_destroy(s); d = make_deck(counting_method, lower - 0.5 * step, decks, ACE, c, c2); s = s_create(*d, dbs, soft17); FREE(d); } t = (Threshold *) calloc(1, sizeof(Threshold)); player_hand = sum(c2t(c2), ACE); t->type = double_down(s, player_hand, c) ? RICH : POOR; double_thresholds[t2i(player_hand)][c2i(c)] = t; if (c2 != c) { t = (Threshold *) calloc(1, sizeof(Threshold)); player_hand = sum(c2t(c), ACE); t->type = double_down(s, player_hand, c2) ? RICH : POOR; double_thresholds[t2i(player_hand)][c2i(c2)] = t; } if (c == ACE) { t = (Threshold *) calloc(1, sizeof(Threshold)); t->type = split(s, ACE, c2) ? RICH : POOR; split_thresholds[c2i(ACE)][c2i(c2)] = t; } else if (c == c2) { t = (Threshold *) calloc(1, sizeof(Threshold)); t->type = split(s, c, ACE) ? RICH : POOR; split_thresholds[c2i(c)][c2i(ACE)] = t; } } if (c != ACE) for (pair = 2; pair <= 10; pair++) { if (decks > 0) { s_destroy(s); d = make_deck(counting_method, lower - 0.5 * step, decks, pair, pair, c); s = s_create(*d, dbs, soft17); FREE(d); } t = (Threshold *) calloc(1, sizeof(Threshold)); t->type = split(s, pair, c) ? RICH : POOR; split_thresholds[c2i(pair)][c2i(c)] = t; } if (decks > 0) s_destroy(s); } if (decks == 0) s_destroy(s); /* calculate strategy changes for other counts */ for (true_count = lower; true_count <= upper; true_count += step) { extern void update_threshold(); fprintf(stderr, "Considering true count of %3d\n", true_count); if (decks == 0) { d = make_deck(counting_method, true_count + 0.5 * step, decks, DUMMY, DUMMY, DUMMY); s = s_create(*d, dbs, soft17); FREE(d); } for (c = 2; c <= ACE; c++) { Total player_hand; Threshold *t; int i; if (decks > 0) { d = make_deck(counting_method, true_count + 0.5 * step, decks, DUMMY, DUMMY, c); s = s_create(*d, dbs, soft17); FREE(d); } for (i = 12; i <= 21; i++) { player_hand = make_hard(i); t = hit_thresholds[t2i(player_hand)][c2i(c)]; update_threshold(t, (! hit(s, player_hand, c)), true_count); } for (i = 17; i <= 21; i++) { player_hand = make_soft(i); t = hit_thresholds[t2i(player_hand)][c2i(c)]; update_threshold(t, (! hit(s, player_hand, c)), true_count); } for (i = 4; i <= 11; i++) { player_hand = make_hard(i); t = double_thresholds[t2i(player_hand)][c2i(c)]; update_threshold(t, double_down(s, player_hand, c), true_count); } for (c2 = 2; c2 <= c; c2++) { if (decks > 0) { s_destroy(s); d = make_deck(counting_method, true_count + 0.5 * step, decks, ACE, c, c2); s = s_create(*d, dbs, soft17); FREE(d); } player_hand = sum(c2t(c2), ACE); t = double_thresholds[t2i(player_hand)][c2i(c)]; update_threshold(t, double_down(s, player_hand, c), true_count); if (c2 != c) { player_hand = sum(c2t(c), ACE); t = double_thresholds[t2i(player_hand)][c2i(c2)]; update_threshold(t, double_down(s, player_hand, c2), true_count); } if (c == ACE) { t = split_thresholds[c2i(ACE)][c2i(c2)]; update_threshold(t, split(s, ACE, c2), true_count); } else if (c == c2) { t = split_thresholds[c2i(c)][c2i(ACE)]; update_threshold(t, split(s, c, ACE), true_count); } } if (c != ACE) for (pair = 2; pair <= 10; pair++) { if (decks > 0) { s_destroy(s); d = make_deck(counting_method, true_count + 0.5 * step, decks, pair, pair, c); s = s_create(*d, dbs, soft17); FREE(d); } t = split_thresholds[c2i(pair)][c2i(c)]; update_threshold(t, split(s, pair, c), true_count); } if (decks > 0) s_destroy(s); } if (decks == 0) s_destroy(s); } /* print out the results */ /* print strategy header */ printf("%21s%s", "", "Blackjack Count Strategy Adjustments\n\n"); printf("%16s%s", "", "Cards: 2 3 4 5 6 7 8 9 10 Ace\n"); printf("%16s%s", "", "Values:"); for (c = 2; c <= ACE; c++) printf("%4d", main_count[c]); printf("\n"); if (found_side_count) printf("%27s%s%d\n", "", "Ace side count value: ", ace_side_value); printf("\n"); { int length; if (decks == 0) length = 60; else if (decks == 1) length = 53; else length = 54; if (dbs) length = length - 3; if (soft17) length = length - 5; printf("%*s", (79 - length) / 2, ""); if (decks > 0) printf("%d deck%s, ", decks, ((decks > 1) ? "s" : "")); else printf("Infinite deck, "); printf("%s%s", (dbs ? "" : "no "), "double on splits, dealer "); printf("%s%s", (soft17 ? "hits " : "stands on "), "soft 17\n\n\n"); } { extern void print_threshold(); Total player_hand; int i; /* hit/stand strategy */ printf("%30s%s", "", "Hit/Stand Strategy\n\n"); printf("%32s%s", "", "Dealer's Upcard\n\n"); printf("Player's Total%6d%6d%6d%6d%6d%6d%6d%6d%6d%7s\n\n", 2, 3, 4, 5, 6, 7, 8, 9, 10, "Ace"); for (i = 21; i >= 12; i--) { player_hand = make_hard(i); printf(" Hard %2d ", i); for (c = 2; c <= ACE; c++) print_threshold(hit_thresholds[t2i(player_hand)][c2i(c)], "Hit", "..."); printf("\n"); } printf("\n"); for (i = 21; i >= 17; i--) { player_hand = make_soft(i); printf(" Soft %2d ", i); for (c = 2; c <= ACE; c++) print_threshold(hit_thresholds[t2i(player_hand)][c2i(c)], "Hit", "..."); printf("\n"); } printf("\n\n"); /* double down strategy */ printf("%29s%s", "", "Double Down Strategy\n\n"); printf("%32s%s", "", "Dealer's Upcard\n\n"); printf("Player's Total%6d%6d%6d%6d%6d%6d%6d%6d%6d%7s\n\n", 2, 3, 4, 5, 6, 7, 8, 9, 10, "Ace"); for (i = 11; i >= 4; i--) { player_hand = make_hard(i); printf(" Hard %2d ", i); for (c = 2; c <= ACE; c++) print_threshold(double_thresholds[t2i(player_hand)][c2i(c)], "...", "dbl"); printf("\n"); } printf("\n"); for (i = 20; i >= 13; i--) { player_hand = make_soft(i); printf(" Soft %2d ", i); for (c = 2; c <= ACE; c++) print_threshold(double_thresholds[t2i(player_hand)][c2i(c)], "...", "dbl"); printf("\n"); } printf("\n\n"); /* pair split strategy */ printf("%30s%s", "", "Pair Split Strategy\n\n"); printf("%32s%s", "", "Dealer's Upcard\n\n"); printf("Player's Pair %6d%6d%6d%6d%6d%6d%6d%6d%6d%7s\n\n", 2, 3, 4, 5, 6, 7, 8, 9, 10, "Ace"); for (pair = ACE; pair >= 2; pair--) { if (pair == ACE) printf(" Ace,Ace "); else printf(" %3d,%-3d ", pair, pair); for (c = 2; c <= ACE; c++) print_threshold(split_thresholds[c2i(pair)][c2i(c)], "...", "split"); printf("\n"); } } } /* parse_int: returns the integer whose printed form is the string s. */ int parse_int(s) char *s; { int answer; char next_char = '\0'; if ((sscanf(s, "%d %c", &answer, &next_char) > 0) && (next_char == '\0')) return answer; else { fprintf(stderr, "not an integer -- %s\n", s); exit(1); } } /* update threshold: change strategy threshold value, if appropriate, based on information for current count. If strategy changes at more than two counts, remember only the changes for the two counts closest to zero. */ void update_threshold(t, new_action_rich, true_count) Threshold *t; boolean new_action_rich; /* true if player should stand/double/split */ int true_count; { if (new_action_rich) if (t->type == POOR) { t->type = NORMAL; t->change_rich = true_count; } else if ((t->type == REVERSE) || (t->type == RANGE) && (t->change_rich <= abs(true_count))) { t->type = REVRANGE; t->change_rich = true_count; } else ; /* no change in strategy */ else if (t->type == RICH) { t->type = REVERSE; t->change_poor = true_count; } else if ((t->type == NORMAL) || (t->type == REVRANGE) && (t->change_poor <= abs(true_count))) { t->type = RANGE; t->change_poor = true_count; } else ; /* no change in strategy */ } /* print threshold: prints a string representation of a threshold on the standard output */ void print_threshold(t, poor_symbol, rich_symbol) Threshold *t; char *poor_symbol, *rich_symbol; { switch (t->type) { case POOR: printf("%-6s", poor_symbol); break; case RICH: printf("%-6s", rich_symbol); break; case NORMAL: printf("%3d ", t->change_rich); break; case REVERSE: printf("%3d* ", t->change_poor); break; /* uncomment the next six lines to print data for ranges */ /* case RANGE: printf("%-d/%-d ", t->change_rich, t->change_poor); break; case REVRANGE: printf("%-d^%-d ", t->change_poor, t->change_rich); break; */ case RANGE: case REVRANGE: if ((abs(t->change_rich) < abs(t->change_poor)) || (t->change_rich = abs(t->change_poor))) printf("%3d ", t->change_rich); else printf("%3d* ", t->change_poor); break; } } void error_usage() { fprintf(stderr, "usage: bj-strat v2 v3 v4 v5 v6 v7 v8 v9 v10 vAce "); fprintf(stderr, "[-dh][-n decks][-r low high]\n"); exit(1); } void error(message) char *message; { fprintf(stderr, "error: %s\n", message); exit(1); } ![]()