// -*- mode:C++; tab-width:4; c-basic-offset:4; indent-tabs-mode:nil -*-

///////////////////////////////////////////////////////////////////////////////
//
// Blackjack.cpp
// Version 5.0
// Copyright (C) 1999, 2001, 2002 Eric Farmer
//
// Blackjack strategy calculator.  Contains classes for computing exact
// probabilities and expected values, for outcomes of the dealer's hand and
// play options for all possible player hands.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA  02111-1307  USA
//

#include "strategy.h"
#include <iostream>
using namespace std;

///////////////////////////////////////////////////////////////////////////////
//
// BJHand
//

BJHand::BJHand() {
    reset();
}

BJHand::BJHand(const int cards[]) {
    reset(cards);
}

int BJHand::getCards(int card) const {
    return cards[card - 1];
}

int BJHand::getCards() const {
    return numCards;
}

int BJHand::getCount() const {
    return count;
}

bool BJHand::getSoft() const {
    return soft;
}

void BJHand::reset() {
    for (int card = 1; card <= 10; card++) {
        cards[card - 1] = 0;
    }
    numCards = count = 0;
    soft = false;
}

void BJHand::reset(const int cards[]) {
    numCards = count = 0;
    for (int card = 1; card <= 10; card++) {
        this->cards[card - 1] = cards[card - 1];
        numCards += cards[card - 1];
        count += card*cards[card - 1];
    }
    if (count < 12 && cards[0]) {
        count += 10;
        soft = true;
    } else {
        soft = false;
    }
}

void BJHand::deal(int card) {
    cards[card - 1]++;
    numCards++;
    count += card;
    if (card == 1 && count < 12) {
        count += 10;
        soft = true;
    } else if (count > 21 && soft) {
        count -= 10;
        soft = false;
    }
}

void BJHand::undeal(int card) {
    cards[card - 1]--;
    numCards--;
    count -= card;
    if (card == 1 && !cards[0] && soft) {
        count -= 10;
        soft = false;
    } else if (count < 12 && cards[0] && !soft) {
        count += 10;
        soft = true;
    }
}

///////////////////////////////////////////////////////////////////////////////
//
// BJShoe
//

BJShoe::BJShoe(int numDecks) {
    reset(numDecks);
}

BJShoe::BJShoe(const int cards[]) {
    reset(cards);
}

int BJShoe::getCards(int card) const {
    return cards[card - 1];
}

int BJShoe::getCards() const {
    return numCards;
}

double BJShoe::getProbability(int card) const {
    return (double)cards[card - 1]/numCards;
}

void BJShoe::reset() {
    numCards = 0;
    for (int card = 1; card <= 10; card++) {
        cards[card - 1] = totalCards[card - 1];
        numCards += cards[card - 1];
    }
}

void BJShoe::reset(const BJHand & hand) {
    numCards = 0;
    for (int card = 1; card <= 10; card++) {
        cards[card - 1] = totalCards[card - 1] - hand.cards[card - 1];
        numCards += cards[card - 1];
    }
}

void BJShoe::reset(int numDecks) {
    for (int card = 1; card < 10; card++) {
        cards[card - 1] = totalCards[card - 1] = 4*numDecks;
    }
    cards[9] = totalCards[9] = 16*numDecks;
    numCards = 52*numDecks;
}

void BJShoe::reset(const int cards[]) {
    numCards = 0;
    for (int card = 1; card <= 10; card++) {
        this->cards[card - 1] = totalCards[card - 1] = cards[card - 1];
        numCards += cards[card - 1];
    }
}

void BJShoe::deal(int card) {
    cards[card - 1]--;
    numCards--;
}

void BJShoe::undeal(int card) {
    cards[card - 1]++;
    numCards++;
}

///////////////////////////////////////////////////////////////////////////////
//
// BJDealer
//

BJDealer::BJDealer(bool hitSoft17) {
    this->hitSoft17 = hitSoft17;
    for (int count = 17; count <= 21; count++) {
        dealerHandCount[count - 17].numHands = 0;
    }

// Enumerate all possible dealer hands, counting multiplicities for each up
// card.

    currentHand.reset();
    for (upCard = 1; upCard <= 10; upCard++) {
        currentHand.deal(upCard);
        countHands();
        currentHand.undeal(upCard);
    }

    for (upCard = 1; upCard <= 10; upCard++) {
        probabilityBlackjack[upCard - 1] = 0;
    }
}

// max*Values[c] are the largest values of s and h for which we need to compute
// lookup[][c][].
//
// lookup[s][c][h] is the probability of h + 1 consecutive cards with value
// c + 1 being dealt from the shoe, given that s other cards have been removed
// from the shoe, and is equal to
// f(shoe.cards[c], h + 1)/f(shoe.numCards - s, h + 1), where f is the falling
// factorial function.

const int BJDealer::maxSvalues[10] = {0, 11, 11, 11, 12, 11, 10, 9, 8, 7};
const int BJDealer::maxHvalues[10] = {11, 8, 5, 4, 3, 2, 2, 1, 1, 1};

void BJDealer::computeProbabilities(const BJShoe & shoe) {

// Compute lookup[][][].

    for (upCard = 1; upCard <= 10; upCard++) {
        int maxS = maxSvalues[upCard - 1],
            maxH = maxHvalues[upCard - 1],
            numCards = shoe.numCards;
        for (int s = 0; s <= maxS && numCards; s++, numCards--) {
            double *l = lookup[s][upCard - 1];
            int cards = shoe.cards[upCard - 1],
                n = numCards;
            double p = l[0] = (double)cards--/n--;
            for (int h = 1; h <= maxH && cards; h++) {
                p *= (double)cards--/n--;
                l[h] = p;
            }
        }
    }

// probabilityBust[] will accumulate the probability of NOT busting, so we
// don't have to actually count those hands.

    for (upCard = 1; upCard <= 10; upCard++) {
        probabilityBust[upCard - 1] = 0;
    }

// For each (non-bust, non-blackjack) possible outcome, accumulate probability
// of each hand with that count (that is actually possible in the given shoe).

    for (int count = 17; count <= 21; count++) {
        double *pcount = probabilityCount[count - 17];
        for (upCard = 1; upCard <= 10; upCard++) {
            pcount[upCard - 1] = 0;
        }
        DealerHandCount & list = dealerHandCount[count - 17];
        for (int i = 0; i < list.numHands; i++) {
            DealerHand & hand = list.dealerHands[i];
            bool possible = true;
            for (int card = 1; card <= 10; card++) {
                if (hand.cards[card - 1] > shoe.cards[card - 1]) {
                    possible = false;
                    break;
                }
            }

// If it is possible (i.e., a subset of the shoe), compute probability of the
// hand.  Note the initial value of p; we save 10 multiplications in the next
// step by doing part of the "conditioning" here.

            if (possible) {
                int s = 0;
                double p = shoe.numCards;
                for (int card = 1; card <= 10; card++) {
                    if (hand.cards[card - 1]) {
                        p *= lookup[s][card - 1][hand.cards[card - 1] - 1];
                        s += hand.cards[card - 1];
                    }
                }

// For each up card, a certain number of permutations of the cards in the hand
// are possible, each equally likely.  Count these, conditioned on each up
// card.

                for (upCard = 1; upCard <= 10; upCard++) {
                    if (hand.multiplier[upCard - 1]) {
                        pcount[upCard - 1] += p*hand.multiplier[upCard - 1]
                                /shoe.cards[upCard - 1];
                    }
                }
            }
        }
        for (upCard = 1; upCard <= 10; upCard++) {
            probabilityBust[upCard - 1] += pcount[upCard - 1];
        }
    }

// Compute P(blackjack).

    if (shoe.cards[0] && shoe.cards[9]) {
        probabilityBlackjack[0] = (double)(shoe.cards[9])/(shoe.numCards - 1);
        probabilityBust[0] += probabilityBlackjack[0];
        probabilityBlackjack[9] = (double)(shoe.cards[0])/(shoe.numCards - 1);
        probabilityBust[9] += probabilityBlackjack[9];
    } else {
        probabilityBlackjack[0] = probabilityBlackjack[9] = 0;
    }

// Now compute P(bust) easily.

    for (upCard = 1; upCard <= 10; upCard++) {
        probabilityBust[upCard - 1] = (double)1 - probabilityBust[upCard - 1];
        probabilityCard[upCard - 1] = shoe.getProbability(upCard);
    }
}

double BJDealer::getProbabilityBust(int upCard) const {
    return probabilityBust[upCard - 1];
}

double BJDealer::getProbabilityBust() const {
    double p = 0;
    for (int upCard = 1; upCard <= 10; upCard++) {
        p += probabilityBust[upCard - 1]*probabilityCard[upCard - 1];
    }
    return p;
}

double BJDealer::getProbabilityCount(int count, int upCard) const {
    return probabilityCount[count - 17][upCard - 1];
}

double BJDealer::getProbabilityCount(int count) const {
    double p = 0;
    for (int upCard = 1; upCard <= 10; upCard++) {
        p += probabilityCount[count - 17][upCard - 1]
                *probabilityCard[upCard - 1];
    }
    return p;
}

double BJDealer::getProbabilityBlackjack(int upCard) const {
    return probabilityBlackjack[upCard - 1];
}

double BJDealer::getProbabilityBlackjack() const {
    return (probabilityBlackjack[0]*probabilityCard[0] +
            probabilityBlackjack[9]*probabilityCard[9]);
}

void BJDealer::countHands() {

// If necessary, draw another card.

    if (currentHand.count < 17 || (hitSoft17
            && currentHand.count == 17 && currentHand.soft)) {
        for (int card = 1; card <= 10; card++) {
            currentHand.deal(card);
            countHands();
            currentHand.undeal(card);
        }

// Otherwise, record all non-bust, non-blackjack hands.

    } else if (currentHand.count <= 21 && (currentHand.numCards != 2
            || currentHand.count != 21)) {
        DealerHandCount & list = dealerHandCount[currentHand.count - 17];
        bool found = false;
        for (int i = 0; i < list.numHands; i++) {
            DealerHand & hand = list.dealerHands[i];
            bool match = true;
            for (int card = 1; card <= 10; card++) {
                if (currentHand.cards[card - 1] != hand.cards[card - 1]) {
                    match = false;
                    break;
                }
            }
            if (match) {
                hand.multiplier[upCard - 1]++;
                found = true;
                break;
            }
        }
        if (!found) {
            DealerHand & hand = list.dealerHands[list.numHands];
            for (int card = 1; card <= 10; card++) {
                hand.cards[card - 1] = currentHand.cards[card - 1];
                hand.multiplier[card - 1] = 0;
            }
            hand.multiplier[upCard - 1]++;
            list.numHands++;
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
//
// BJRules
//

BJRules::BJRules() {
    hitSoft17 = false;
    doubleAnyTotal = true;
    double9 = true;
    doubleSoft = true;
    doubleAfterHit = false;
    doubleAfterSplit = true;
    resplit = true;
    resplitAces = false;
    lateSurrender = false;
}

BJRules::BJRules(bool hitSoft17, bool doubleAnyTotal, bool double9,
                 bool doubleSoft, bool doubleAfterHit, bool doubleAfterSplit,
                 bool resplit, bool resplitAces, bool lateSurrender) {
    this->hitSoft17 = hitSoft17;
    this->doubleAnyTotal = doubleAnyTotal;
    this->double9 = double9;
    this->doubleSoft = doubleSoft;
    this->doubleAfterHit = doubleAfterHit;
    this->doubleAfterSplit = doubleAfterSplit;
    this->resplit = resplit;
    this->resplitAces = resplitAces;
    this->lateSurrender = lateSurrender;
}

BJRules::~BJRules() {
}

bool BJRules::getHitSoft17() {
    return hitSoft17;
}

bool BJRules::getDoubleDown(const BJHand & hand) {
    return (
        (doubleAnyTotal || hand.getCount() == 10 || hand.getCount() == 11 ||
            (double9 && hand.getCount() == 9)) &&
        (doubleSoft || !hand.getSoft()) &&
        (doubleAfterHit || hand.getCards() == 2));
}

bool BJRules::getDoubleAfterSplit(const BJHand & hand) {
    return (doubleAfterSplit && getDoubleDown(hand));
}

int BJRules::getResplit(int pairCard) {
    return ((resplit && (resplitAces || pairCard != 1)) ? 4 : 2);
}

bool BJRules::getLateSurrender() {
    return lateSurrender;
}

bool
BJRules::getDoubleAnyTotal ()
{
  return doubleAnyTotal;
}

bool
BJRules::getDouble9 ()
{
  return double9;
}

bool
BJRules::getDoubleSoft ()
{
  return doubleSoft;
}

bool
BJRules::getDoubleAfterHit ()
{
  return doubleAfterHit;
}

bool
BJRules::getDoubleAfterSplit ()
{
  return doubleAfterSplit;
}

bool
BJRules::getResplit ()
{
  return resplit;
}

bool
BJRules::getResplitAces ()
{
  return resplitAces;
}

///////////////////////////////////////////////////////////////////////////////
//
// BJStrategy
//

BJStrategy::~BJStrategy() {
}

int BJStrategy::getOption(const BJHand & hand, int upCard, bool doubleDown,
                          bool split, bool surrender) {
    return BJ_MAX_VALUE;
}

///////////////////////////////////////////////////////////////////////////////
//
// BJProgress
//

BJProgress::~BJProgress() {
}

void BJProgress::indicate(int percentComplete) {
}

///////////////////////////////////////////////////////////////////////////////
//
// BJPlayer
//

BJPlayer::BJPlayer(const BJShoe & shoe, BJRules & rules, BJStrategy & strategy,
                   BJProgress & progress) {
    reset(shoe, rules, strategy, progress);
}

void BJPlayer::reset(const BJShoe & shoe, BJRules & rules,
                     BJStrategy & strategy, BJProgress & progress) {

// Forget about any cards already dealt from the shoe, so shoe.reset(hand) will
// work.

    this->shoe = shoe;
    numHands = 0;
    for (int card = 1; card <= 10; card++) {
        playerHands[numHands].cards[card - 1] =
        playerHands[numHands].hitHand[card - 1] = 0;
        this->shoe.totalCards[card - 1] = shoe.cards[card - 1];
    }

// Remember resplit rules when enumerating player hands.

    for (int pairCard = 1; pairCard <= 10; pairCard++) {
        resplit[pairCard - 1] = rules.getResplit(pairCard);
    }

// Enumerate all possible player hands.

    currentHand.reset();
    countHands(numHands++, 1);
    linkHands();

// Compute dealer probabilities for each hand.  This takes the most time, so
// keep the caller updated on the progress.

    computeDealer(rules, progress);

// Compute expected values for standing, doubling down, and hitting (in that
// order, so all required values will be available when needed).

    linkHandCounts();
    computeStand();
    computeDoubleDown();
    computeHit(rules, strategy);

// Compute expected values for splitting pairs.  Re-link original hands by
// count for future use.

    computeSplit(rules, strategy);
    linkHandCounts();

// Blackjack pays 3:2, so correct the value for standing on this hand.  We wait
// to do this until after computing E(split) since a blackjack after splitting
// a pair only pays even money.

    correctStandBlackjack();

// Compute overall expected values, condition individual hands on no dealer
// blackjack, and finalize progress indicator.

    computeOverall(rules, strategy);
    conditionNoBlackjack();
    progress.indicate(100);
}

double BJPlayer::getValueStand(const BJHand & hand, int upCard) const {
    return playerHands[findHand(hand)].valueStand[false][upCard - 1];
}

double BJPlayer::getValueHit(const BJHand & hand, int upCard) const {
    return playerHands[findHand(hand)].valueHit[false][upCard - 1];
}

double BJPlayer::getValueDoubleDown(const BJHand & hand, int upCard) const {
    return playerHands[findHand(hand)].valueDoubleDown[false][upCard - 1];
}

double BJPlayer::getValueSplit(int pairCard, int upCard) const {
    return valueSplit[pairCard - 1][upCard - 1];
}

double BJPlayer::getValue(int upCard) const {
    return overallValues[upCard - 1];
}

double BJPlayer::getValue() const {
    return overallValue;
}

int BJPlayer::getOption(const BJHand & hand, int upCard, bool doubleDown,
                        bool split, bool surrender) {
    PlayerHand & testHand = playerHands[findHand(hand)];
    double value = testHand.valueStand[false][upCard - 1];
    int option = BJ_STAND;
    if (value < testHand.valueHit[false][upCard - 1]) {
        value = testHand.valueHit[false][upCard - 1];
        option = BJ_HIT;
    }
    if (doubleDown) {
        if (value < testHand.valueDoubleDown[false][upCard - 1]) {
            value = testHand.valueDoubleDown[false][upCard - 1];
            option = BJ_DOUBLE_DOWN;
        }
    }
    if (split) {
        int pairCard = 1;
        while (!hand.cards[pairCard - 1]) {
            pairCard++;
        }
        if (value < valueSplit[pairCard - 1][upCard - 1]) {
            value = valueSplit[pairCard - 1][upCard - 1];
            option = BJ_SPLIT;
        }
    }
    if (surrender) {
        if (value < -0.5) {
            value = -0.5;
            option = BJ_SURRENDER;
        }
    }
    return option;
}

int BJPlayer::findHand(const BJHand & hand) const {
    int i = 0;
    for (int card = 1; card <= 10; card++) {
        for (int c = 0; c < hand.cards[card - 1]; c++) {
            i = playerHands[i].hitHand[card - 1];
        }
    }
    return i;
}

bool BJPlayer::record(const BJHand & hand) {
    bool result = false;

// A hand is saved if it is not a bust hand...

    if (hand.count <= 21) {
        result = true;
    } else {

// Or if it may be a split hand; note that we don't need extra hands for split
// aces, since hitting split aces is not allowed.

        for (int card = 2; card <= 10; card++) {
            int s = resplit[card - 1];
            if (hand.cards[card - 1] < s) {
                s = hand.cards[card - 1];
            }
            if (hand.count - card*(s - 1) <= 21) {
                result = true;
                break;
            }
        }
    }
    return result;
}

void BJPlayer::countHands(int i, int maxCard) {

// To only count each hand (subset) once, only draw cards higher than any in
// the hand.

    for (int card = maxCard; card <= 10; card++) {
        if (shoe.cards[card - 1]) {
            shoe.deal(card);
            currentHand.deal(card);

// If the hand is not busted (or could be a split hand), record it and
// partially link it up; we'll finish with linkHands() later.

            if (record(currentHand)) {
                playerHands[i].hitHand[card - 1] = numHands;
                for (int c = 1; c <= 10; c++) {
                    playerHands[numHands].cards[c - 1] = currentHand.
                            cards[c - 1];
                    playerHands[numHands].hitHand[c - 1] = 0;
                }
                countHands(numHands++, card);
            }
            currentHand.undeal(card);
            shoe.undeal(card);
        }
    }
}

void BJPlayer::linkHands() {
    for (int i = 0; i < numHands; i++) {
        PlayerHand & hand = playerHands[i];
        for (int card = 1; card <= 10; card++) {
            if (!hand.hitHand[card - 1]
                    && hand.cards[card - 1] < shoe.cards[card - 1]) {
                currentHand.reset(hand.cards);
                currentHand.deal(card);
                if (record(currentHand)) {
                    hand.hitHand[card - 1] = findHand(currentHand);
                }
            }
        }
    }
}

void BJPlayer::computeDealer(BJRules & rules, BJProgress & progress) {
    BJDealer dealer(rules.getHitSoft17());
    for (int i = 0; i < numHands; i++) {
        progress.indicate(100*i/numHands);
        PlayerHand & hand = playerHands[i];
        currentHand.reset(hand.cards);
        shoe.reset(currentHand);
        dealer.computeProbabilities(shoe);
        for (int upCard = 1; upCard <= 10; upCard++) {
            hand.probabilityBust[upCard - 1] = dealer.
                    probabilityBust[upCard - 1];
            for (int count = 17; count <= 21; count++) {
                hand.probabilityCount[count - 17][upCard - 1] = dealer.
                        probabilityCount[count - 17][upCard - 1];
            }
            hand.probabilityBlackjack[upCard - 1] = dealer.
                    probabilityBlackjack[upCard - 1];
        }
    }
}

void BJPlayer::linkHandCounts(bool split, int pairCard, int splitHands) {
    for (int count = 4; count <= 21; count++) {
        playerHandCount[count][false] = playerHandCount[count][true] = 0;
    }
    for (int i = 0; i < numHands; i++) {
        currentHand.reset(playerHands[i].cards);
        bool link;

// This would be easier if not for the fact that hitting split aces is not
// allowed.

        if (split) {
            int numCards = currentHand.numCards - (splitHands - 1);
            link = (currentHand.cards[pairCard - 1] >= splitHands)
                    && (currentHand.count - pairCard*(splitHands - 1) <= 21)
                    && (numCards >= 2 && (pairCard != 1 || numCards == 2));
        } else {
            link = (currentHand.count <= 21 && currentHand.numCards >= 2);
        }
        if (link) {
            for (int hands = 1; hands < splitHands; hands++) {
                currentHand.undeal(pairCard);
            }
            int j = playerHandCount[currentHand.count][currentHand.soft];
            playerHandCount[currentHand.count][currentHand.soft] = i;
            playerHands[i].nextHand = j;
        }
    }
}

void BJPlayer::computeStand(bool split, int pairCard, int splitHands) {
    int count;

// This particular traversal of hands isn't really necessary for standing and
// doubling down; it is simply useful to use the already-identified list of
// valid hands (depending on whether we are splitting, how many hands, etc.).

    for (count = 21; count >= 11; count--) {
        computeStandCount(count, false, split, pairCard, splitHands);
    }
    for (count = 21; count >= 12; count--) {
        computeStandCount(count, true, split, pairCard, splitHands);
    }
    for (count = 10; count >= 4; count--) {
        computeStandCount(count, false, split, pairCard, splitHands);
    }
}

void BJPlayer::computeStandCount(int count, bool soft, bool split,
                                 int pairCard, int splitHands) {
    for (int i = playerHandCount[count][soft]; i;
            i = playerHands[i].nextHand) {
        PlayerHand & hand = playerHands[i];
        currentHand.reset(hand.cards);
        shoe.reset(currentHand);
        for (int hands = 1; hands < splitHands; hands++) {
            currentHand.undeal(pairCard);
        }
        for (int upCard = 1; upCard <= 10; upCard++) {
            if (shoe.cards[upCard - 1]) {
                hand.valueStand[split][upCard - 1] =
                        hand.probabilityBust[upCard - 1]
                        - hand.probabilityBlackjack[upCard - 1];
                for (int count = 17; count <= 21; count++) {
                    if (currentHand.count > count) {
                        hand.valueStand[split][upCard - 1] +=
                            hand.probabilityCount[count - 17][upCard - 1];
                    } else if (currentHand.count < count) {
                        hand.valueStand[split][upCard - 1] -=
                            hand.probabilityCount[count - 17][upCard - 1];
                    }
                }
            }
        }
    }
}

void BJPlayer::computeDoubleDown(bool split, int pairCard, int splitHands) {
    int count;

    for (count = 21; count >= 11; count--) {
        computeDoubleDownCount(count, false, split, pairCard, splitHands);
    }
    for (count = 21; count >= 12; count--) {
        computeDoubleDownCount(count, true, split, pairCard, splitHands);
    }
    for (count = 10; count >= 4; count--) {
        computeDoubleDownCount(count, false, split, pairCard, splitHands);
    }
}

void BJPlayer::computeDoubleDownCount(int count, bool soft, bool split,
                                      int pairCard, int splitHands) {
    for (int i = playerHandCount[count][soft]; i;
            i = playerHands[i].nextHand) {
        PlayerHand & hand = playerHands[i];
        currentHand.reset(hand.cards);
        shoe.reset(currentHand);
        for (int hands = 1; hands < splitHands; hands++) {
            currentHand.undeal(pairCard);
        }
        for (int upCard = 1; upCard <= 10; upCard++) {
            if (shoe.cards[upCard - 1]) {
                shoe.deal(upCard);

// We only lose our initial wager if the dealer has blackjack.

                if (upCard == 1) {
                    hand.valueDoubleDown[split][upCard - 1] = shoe.
                            getProbability(10);
                } else if (upCard == 10) {
                    hand.valueDoubleDown[split][upCard - 1] = shoe.
                            getProbability(1);
                } else {
                    hand.valueDoubleDown[split][upCard - 1] = 0;
                }

                for (int card = 1; card <= 10; card++) {
                    if (shoe.cards[card - 1]) {
                        currentHand.deal(card);
                        int j = hand.hitHand[card - 1];
                        double value;
                        if (currentHand.count <= 21) {
                            value = playerHands[j].
                                    valueStand[split][upCard - 1]*2;
                        } else {
                            value = -2;
                        }
                        currentHand.undeal(card);
                        hand.valueDoubleDown[split][upCard - 1] += value
                                *shoe.getProbability(card);
                    }
                }
                shoe.undeal(upCard);
            }
        }
    }
}

void BJPlayer::computeHit(BJRules & rules, BJStrategy & strategy, bool split,
                          int pairCard, int splitHands) {
    int count;

// By computing values for E(hit) in the proper order, we guarantee that any
// values needed for computing the value of a given hand will be available.
// We start with the hard hands, but stop at hard 11, since we could draw an
// ace to a 10, making a soft 21.

    for (count = 21; count >= 11; count--) {
        computeHitCount(count, false, rules, strategy, split, pairCard,
                splitHands);
    }
    for (count = 21; count >= 12; count--) {
        computeHitCount(count, true, rules, strategy, split, pairCard,
                splitHands);
    }
    for (count = 10; count >= 4; count--) {
        computeHitCount(count, false, rules, strategy, split, pairCard,
                splitHands);
    }
}

void BJPlayer::computeHitCount(int count, bool soft, BJRules & rules,
                               BJStrategy & strategy, bool split, int pairCard,
                               int splitHands) {
    for (int i = playerHandCount[count][soft]; i;
            i = playerHands[i].nextHand) {
        PlayerHand & hand = playerHands[i];
        currentHand.reset(hand.cards);
        shoe.reset(currentHand);
        for (int hands = 1; hands < splitHands; hands++) {
            currentHand.undeal(pairCard);
        }
        for (int upCard = 1; upCard <= 10; upCard++) {
            if (shoe.cards[upCard - 1]) {
                shoe.deal(upCard);
                hand.valueHit[split][upCard - 1] = 0;
                for (int card = 1; card <= 10; card++) {
                    if (shoe.cards[card - 1]) {
                        currentHand.deal(card);
                        int j = hand.hitHand[card - 1];
                        double value, testValue;
                        if (currentHand.count <= 21) {
                            PlayerHand & hitHand = playerHands[j];
                            bool doubleDown;
                            if (split) {
                                doubleDown = rules.
                                        getDoubleAfterSplit(currentHand);
                            } else {
                                doubleDown = rules.getDoubleDown(currentHand);
                            }
                            switch (strategy.getOption(currentHand, upCard,
                                    doubleDown, false, false)) {

// To be consistent with a "fixed" playing strategy, use the option maximizing
// expected value for the non-split hand.

                            case BJ_MAX_VALUE :
                                j = findHand(currentHand);
                                testValue = playerHands[j].
                                        valueStand[false][upCard - 1];
                                value = hitHand.valueStand[split][upCard - 1];
                                if (testValue < playerHands[j].
                                        valueHit[false][upCard - 1]) {
                                    testValue = playerHands[j].
                                            valueHit[false][upCard - 1];
                                    value = hitHand.
                                            valueHit[split][upCard - 1];
                                }
                                if (doubleDown) {
                                    if (testValue < playerHands[j].
                                          valueDoubleDown[false][upCard - 1]) {
                                        value = hitHand.
                                            valueDoubleDown[split][upCard - 1];
                                    }
                                }
                                break;
                            case BJ_STAND :
                                value = hitHand.valueStand[split][upCard - 1];
                                break;
                            case BJ_HIT :
                                value = hitHand.valueHit[split][upCard - 1];
                                break;
                            case BJ_DOUBLE_DOWN :
                                value = hitHand.
                                        valueDoubleDown[split][upCard - 1];
                                break;
                            default :
                                value = 0;
                            }
                        } else {
                            value = -1;
                        }
                        currentHand.undeal(card);
                        hand.valueHit[split][upCard - 1] += value
                                *shoe.getProbability(card);
                    }
                }
                shoe.undeal(upCard);
            }
        }
    }
}

void BJPlayer::computeSplit(BJRules & rules, BJStrategy & strategy) {
    for (int pairCard = 1; pairCard <= 10; pairCard++) {
        if (resplit[pairCard - 1] >= 2 && shoe.totalCards[pairCard - 1] >= 2) {

// Compute maximum number of split hands.

            int maxSplitHands = resplit[pairCard - 1];
            if (shoe.totalCards[pairCard - 1] < maxSplitHands) {
                maxSplitHands = shoe.totalCards[pairCard - 1];
            }

// Compute probability of splitting exactly 2, 3, and 4 hands.

            double pSplit[5][10];
            shoe.reset();
            shoe.deal(pairCard); shoe.deal(pairCard);
            for (int upCard = 1; upCard <= 10; upCard++) {
                if (shoe.cards[upCard - 1]) {
                    shoe.deal(upCard);
                    double n = shoe.numCards,
                        p = shoe.cards[pairCard - 1];
                    if (maxSplitHands > 2) {
                        pSplit[2][upCard - 1] = (n - p)/n*(n - 1 - p)/(n - 1);
                        if (maxSplitHands > 3) {
                            pSplit[3][upCard - 1] = pSplit[2][upCard - 1]*2
                                    *p/(n - 2)*(n - 2 - p)/(n - 3);
                            pSplit[4][upCard - 1] = (double)1
                                    - pSplit[2][upCard - 1]
                                    - pSplit[3][upCard - 1];
                        } else {
                            pSplit[3][upCard - 1] = (double)1
                                    - pSplit[2][upCard - 1];
                            pSplit[4][upCard - 1] = 0;
                        }
                    } else {
                        pSplit[2][upCard - 1] = 1;
                        pSplit[3][upCard - 1] = pSplit[4][upCard - 1] = 0;
                    }

// We only lose our initial wager if the dealer has blackjack.

                    if (upCard == 1) {
                        valueSplit[pairCard - 1][upCard - 1] = shoe.
                                getProbability(10);
                    } else if (upCard == 10) {
                        valueSplit[pairCard - 1][upCard - 1] = shoe.
                                getProbability(1);
                    } else {
                        valueSplit[pairCard - 1][upCard - 1] = 0;
                    }
                    valueSplit[pairCard - 1][upCard - 1] *=
                            pSplit[2][upCard - 1] + pSplit[3][upCard - 1]*2
                                                  + pSplit[4][upCard - 1]*3;
                    shoe.undeal(upCard);
                }
            }

// For each possible number of split hands, re-compute expected values with the
// appropriate number of pair cards removed.

            for (int splitHands = 2; splitHands <= maxSplitHands;
                    splitHands++) {
                linkHandCounts(true, pairCard, splitHands);
                computeStand(true, pairCard, splitHands);
                if (pairCard != 1) {
                    computeDoubleDown(true, pairCard, splitHands);
                    computeHit(rules, strategy, true, pairCard, splitHands);
                }
                currentHand.reset();
                currentHand.deal(pairCard);

// Remove split pair cards for weighting expected values of possible hands.

                int i = 0, j;
                shoe.reset();
                for (int split = 0; split < splitHands; split++) {
                    shoe.deal(pairCard);
                    i = playerHands[i].hitHand[pairCard - 1];
                }
                for (int upCard = 1; upCard <= 10; upCard++) {
                    if (shoe.cards[upCard - 1]) {
                        shoe.deal(upCard);
                        double valueUpCard = 0,
                            pNoPair = (double)1 - shoe.
                                    getProbability(pairCard);

// Evaluate each possible two-card split hand.

                        for (int card = 1; card <= 10; card++) {
                            if (shoe.cards[card - 1]) {
                                currentHand.deal(card);
                                PlayerHand & hand = playerHands[
                                        playerHands[i].hitHand[card - 1]];
                                double value, testValue;
                                if (pairCard == 1) {
                                    value = hand.valueStand[true][upCard - 1];
                                } else {
                                    bool doubleDown = rules.
                                            getDoubleAfterSplit(currentHand);
                                    switch (strategy.getOption(currentHand,
                                            upCard, doubleDown, false, false)){

// Again, use the playing option that maximizes expected value for the
// non-split hand.

                                    case BJ_MAX_VALUE :
                                        j = findHand(currentHand);
                                        testValue = playerHands[j].
                                                valueStand[false][upCard - 1];
                                        value = hand.
                                                valueStand[true][upCard - 1];
                                        if (testValue < playerHands[j].
                                                valueHit[false][upCard - 1]) {
                                            testValue = playerHands[j].
                                                   valueHit[false][upCard - 1];
                                            value = hand.
                                                    valueHit[true][upCard - 1];
                                        }
                                        if (doubleDown) {
                                            if (testValue < playerHands[j].
                                                valueDoubleDown[false]
                                                        [upCard - 1]) {
                                                value = hand.
                                                    valueDoubleDown[true]
                                                        [upCard - 1];
                                            }
                                        }
                                        break;
                                    case BJ_STAND :
                                        value = hand.
                                                valueStand[true][upCard - 1];
                                        break;
                                    case BJ_HIT :
                                        value = hand.
                                                valueHit[true][upCard - 1];
                                        break;
                                    case BJ_DOUBLE_DOWN :
                                        value = hand.
                                                valueDoubleDown[true]
                                                    [upCard - 1];
                                        break;
                                    default :
                                        value = 0;
                                    }
                                }

// If further resplitting is allowed, condition on NOT drawing an additional
// pair.

                                double p = shoe.getProbability(card);
                                if (splitHands < maxSplitHands) {
                                    p /= pNoPair;
                                }
                                if (card != pairCard
                                        || splitHands == maxSplitHands) {
                                    valueUpCard += value*p;
                                }
                                currentHand.undeal(card);
                            }
                        }
                        valueSplit[pairCard - 1][upCard - 1] += valueUpCard
                                *pSplit[splitHands][upCard - 1]*splitHands;
                        shoe.undeal(upCard);
                    }
                }
            }
        }
    }
}

void BJPlayer::correctStandBlackjack() {
    if (shoe.totalCards[0] && shoe.totalCards[9]) {
        currentHand.reset();
        currentHand.deal(1); currentHand.deal(10);
        PlayerHand & hand = playerHands[findHand(currentHand)];
        shoe.reset(currentHand);
        if (shoe.cards[0]) {
            shoe.deal(1);
            hand.valueStand[false][0] = (double)3/2
                    *((double)1 - shoe.getProbability(10));
            shoe.undeal(1);
        }
        for (int upCard = 2; upCard < 10; upCard++) {
            if (shoe.cards[upCard - 1]) {
                hand.valueStand[false][upCard - 1] = (double)3/2;
            }
        }
        if (shoe.cards[9]) {
            shoe.deal(10);
            hand.valueStand[false][9] = (double)3/2
                    *((double)1 - shoe.getProbability(1));
            shoe.undeal(10);
        }
    }
}

void BJPlayer::computeOverall(BJRules & rules, BJStrategy & strategy) {
    overallValue = 0;
    bool surrender = rules.getLateSurrender();
    shoe.reset();
    for (int upCard = 1; upCard <= 10; upCard++) {
        overallValues[upCard - 1] = 0;
        if (shoe.cards[upCard - 1]) {
            shoe.deal(upCard);
            for (int card1 = 1; card1 <= 10; card1++) {
                for (int card2 = 1; card2 <= 10; card2++) {
                    if (shoe.cards[card1 - 1] && shoe.cards[card2 - 1] &&
                            (card1 != card2 || shoe.cards[card1 - 1] >= 2)) {
                        currentHand.reset();
                        double p = shoe.getProbability(card1);
                        shoe.deal(card1); currentHand.deal(card1);
                        p *= shoe.getProbability(card2);
                        shoe.deal(card2); currentHand.deal(card2);
                        PlayerHand & hand = playerHands[findHand(currentHand)];
                        double value = 0;
                        BJHand testHand(hand.cards);
                        bool doubleDown = rules.getDoubleDown(testHand),
                            split = (card1 == card2
                                    && resplit[card1 - 1] >= 2);
                        double valueSurrender;
                        switch (strategy.getOption(testHand, upCard,
                                doubleDown, split, surrender)) {
                        case BJ_MAX_VALUE :
                            value = hand.valueStand[false][upCard - 1];
                            if (value < hand.valueHit[false][upCard - 1]) {
                                value = hand.valueHit[false][upCard - 1];
                            }
                            if (doubleDown) {
                                if (value < hand.
                                        valueDoubleDown[false][upCard - 1]) {
                                    value = hand.
                                        valueDoubleDown[false][upCard - 1];
                                }
                            }
                            if (split) {
                                if (value < valueSplit[card1 - 1]
                                        [upCard - 1]) {
                                    value = valueSplit[card1 - 1][upCard - 1];
                                }
                            }
                            if (surrender) {
                                valueSurrender = computeSurrender(upCard);
                                if (value < valueSurrender) {
                                    value = valueSurrender;
                                }
                            }
                            break;
                        case BJ_STAND :
                            value = hand.valueStand[false][upCard - 1];
                            break;
                        case BJ_HIT :
                            value = hand.valueHit[false][upCard - 1];
                            break;
                        case BJ_DOUBLE_DOWN :
                            value = hand.valueDoubleDown[false][upCard - 1];
                            break;
                        case BJ_SPLIT :
                            value = valueSplit[card1 - 1][upCard - 1];
                            break;
                        case BJ_SURRENDER :
                            value = computeSurrender(upCard);
                            break;
                        }
                        overallValues[upCard - 1] += value*p;
                        shoe.undeal(card2);
                        shoe.undeal(card1);
                    }
                }
            }
            shoe.undeal(upCard);
            overallValue += overallValues[upCard - 1]
                    *shoe.getProbability(upCard);
        }
    }
}

double BJPlayer::computeSurrender(int upCard) {
    double valueSurrender;

    if (upCard == 1) {
        valueSurrender = shoe.getProbability(10);
    } else if (upCard == 10) {
        valueSurrender = shoe.getProbability(1);
    } else {
        valueSurrender = 0;
    }
    valueSurrender = -0.5 - valueSurrender/2;
    return valueSurrender;
}

void BJPlayer::conditionNoBlackjack() {
    for (int i = 0; i < numHands; i++) {
        PlayerHand & hand = playerHands[i];
        currentHand.reset(hand.cards);
        if (currentHand.count <= 21) {
            shoe.reset(currentHand);
            if (shoe.cards[0]) {
                shoe.deal(1);
                double p = (double)1 - shoe.getProbability(10);
                if (currentHand.numCards == 2 && currentHand.count == 21) {
                    hand.valueStand[false][0] /= p;
                } else {
                    hand.valueStand[false][0] = (hand.
                            valueStand[false][0] + 1 - p)/p;
                }
                hand.valueHit[false][0] = (hand.valueHit[false][0] + 1 - p)/p;
                hand.valueDoubleDown[false][0] = (hand.
                        valueDoubleDown[false][0] + 1 - p)/p;
                shoe.undeal(1);
            }
            if (shoe.cards[9]) {
                shoe.deal(10);
                double p = (double)1 - shoe.getProbability(1);
                if (currentHand.numCards == 2 && currentHand.count == 21) {
                    hand.valueStand[false][9] /= p;
                } else {
                    hand.valueStand[false][9] = (hand.
                            valueStand[false][9] + 1 - p)/p;
                }
                hand.valueHit[false][9] = (hand.valueHit[false][9] + 1 - p)/p;
                hand.valueDoubleDown[false][9] = (hand.
                        valueDoubleDown[false][9] + 1 - p)/p;
                shoe.undeal(10);
            }
        }
    }
    for (int pairCard = 1; pairCard <= 10; pairCard++) {
        if (resplit[pairCard - 1] >= 2 && shoe.totalCards[pairCard - 1] >= 2) {
            shoe.reset();
            shoe.deal(pairCard); shoe.deal(pairCard);
            if (shoe.cards[0]) {
                shoe.deal(1);
                double p = (double)1 - shoe.getProbability(10);
                valueSplit[pairCard - 1][0] =
                        (valueSplit[pairCard - 1][0] + 1 - p)/p;
                shoe.undeal(1);
            }
            if (shoe.cards[9]) {
                shoe.deal(10);
                double p = (double)1 - shoe.getProbability(1);
                valueSplit[pairCard - 1][9] =
                        (valueSplit[pairCard - 1][9] + 1 - p)/p;
                shoe.undeal(10);
            }
        }
    }
}
