import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.HashSet; public class BlackJackPlayer { public static long statesGenerated = 0; public static int rewardStatesReached = 0; public static class Util { public static <K, V> Map<K, V> create(Collection<K> keys, V value) { Map<K, V> map = new LinkedHashMap<K, V>(); for (K k : keys) { map.put(k, value); } return map; } } // all the available actions public static class A implements Comparable <A> { char action; public A (char c) { this.action = c; } @Override public int compareTo(A foo) { if (foo.action == this.action) return 0; if (foo.action < this.action) return -1; if (foo.action > this.action) return 1; return 1; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + action; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } A other = (A) obj; if (action != other.action) { return false; } return true; } } public static class Mdp { private HashMap<Integer, State> states = null; private int numStates = 0; private double faceCardProbability = -1; private double otherCardsProbability = -1; public HashMap<Integer, State> getStatesHashMap() { assert(states!=null); return states; } public Mdp(double faceCardProbability) { this.faceCardProbability = faceCardProbability; this.otherCardsProbability = (1 - faceCardProbability) / 9.0; } public Collection<State> states() { // if we've already generated states, just return them if (states != null) return states.values(); // let's generate all possible states and cache them states = new HashMap<Integer, State>(220000); numStates = 0; // start with the dealer state for (int dealerSum = 2; dealerSum<=11; dealerSum++) { int dealerNumAces = 0; if (dealerSum == 11) dealerNumAces = 1; // generate starting states int numAces = 0; boolean isTwoCards = true; boolean playerTurn = true; int pairValue = 0; for (int minSum = 5; minSum <= 19; minSum++) { State s = new State(); s.minSum = minSum; s.numAces = numAces; s.dMinSum = (dealerSum - 10*dealerNumAces); s.dNumAces = dealerNumAces; s.isTwoCards = isTwoCards; s.isBlackjack = false; s.pair = pairValue; s.playerTurn = playerTurn; s.friendlyName = minSum + ""; s.generateNextStates(states, this.faceCardProbability, this.otherCardsProbability); states.put(s.hashCode(), s); } numAces = 1; for (int minSum = 3; minSum <= 10; minSum++) { State s = new State(); s.minSum = minSum; s.numAces = numAces; s.dMinSum = (dealerSum - 10*dealerNumAces); s.dNumAces = dealerNumAces; s.isTwoCards = isTwoCards; s.isBlackjack = false; s.pair = pairValue; s.playerTurn = playerTurn; s.friendlyName = "A" + minSum; s.generateNextStates(states, this.faceCardProbability, this.otherCardsProbability); states.put(s.hashCode(), s); } // add pairs numAces = 0; for (pairValue = 2; pairValue <= 10; pairValue++) { State s = new State(); s.minSum = pairValue * 2; s.numAces = numAces; s.dMinSum = (dealerSum - 10*dealerNumAces); s.dNumAces = dealerNumAces; s.isTwoCards = isTwoCards; s.isBlackjack = false; s.pair = pairValue; s.playerTurn = playerTurn; s.friendlyName = pairValue + "" + pairValue; s.generateNextStates(states, this.faceCardProbability, this.otherCardsProbability); states.put(s.hashCode(), s); } // add the pair of aces numAces = 2; for (pairValue = 1; pairValue <= 1; pairValue++) { State s = new State(); s.minSum = pairValue * 2; s.numAces = numAces; s.dMinSum = (dealerSum - 10*dealerNumAces); s.dNumAces = dealerNumAces; s.isTwoCards = isTwoCards; s.isBlackjack = false; s.pair = pairValue; s.playerTurn = playerTurn; s.friendlyName = "AA"; s.generateNextStates(states, this.faceCardProbability, this.otherCardsProbability); states.put(s.hashCode(), s); } numStates = states.size(); } System.out.println("Done (states): " + numStates); return states.values(); } public double reward(State s) { // at the start of blackjack, there is a buyin. thus the reward is -1 return s.reward; } public Set<A> actions(State s) { return s.availableActions; } public Double transitionProbability(State sDelta, State s, A a) { switch(a.action) { case 'S': return s.standStates.get(sDelta); case 'H': return s.hitStates.get(sDelta); case 'P': return s.splitStates.get(sDelta); case 'D': return s.doubleStates.get(sDelta); default: assert(false); return 0.0; } } } public static class ValueIteration { private double gamma; public ValueIteration(double gamma) { this.gamma = gamma; } public Map<State, Double> valueIteration(Mdp mdp, double epsilon) { // // local variables: U, U', vectors of utilities for states in S, // initially zero Map<State, Double> U = Util.create(mdp.states(), new Double(0)); Map<State, Double> Udelta = Util.create(mdp.states(), new Double(0)); // δ the maximum change in the utility of any state in an // iteration double delta = 0; // Note: Just calculate this once for efficiency purposes: // ε(1 - γ)/γ double minDelta = epsilon;//;0.05;//epsilon * (1 - gamma) / gamma; System.out.println("U+" + U.size()); System.out.println("UDelta:" + Udelta.size()); // repeat do { // U <- U'; δ <- 0 U.putAll(Udelta); delta = 0; // for each state s in S do for (State s : mdp.states()) { // max<sub>a ∈ A(s)</sub> Set<A> actions = mdp.actions(s); // Handle terminal states (i.e. no actions). double aMax = 0; if (actions.size() > 0) { aMax = Double.NEGATIVE_INFINITY; } for (A a : actions) { // Σ<sub>s'</sub>P(s' | s, a) U[s'] double aSum = 0; Collection<State> deltaStates = s.statesMatchingAction(a); for (State sDelta : deltaStates) { Double uget = U.get(sDelta); aSum += mdp.transitionProbability(sDelta, s, a) * uget; } // if (s.minSum == 2 && s.numAces == 2 && s.dMinSum == 1 && // s.dNumAces == 1 && s.isTwoCards && s.playerTurn) // { // System.out.println (a.action + ":" + aSum + s); // } if (aSum >= aMax) { aMax = aSum; s.bestPolicy = a.action; } } // U'[s] <- R(s) + γ // max<sub>a ∈ A(s)</sub> Udelta.put(s, mdp.reward(s) + gamma * aMax); // if |U'[s] - U[s]| > δ then δ <- |U'[s] - U[s]| double aDiff = Math.abs(Udelta.get(s) - U.get(s)); s.expectedValue = Udelta.get(s); if (aDiff > delta) { delta = aDiff; } } // until δ < ε(1 - γ)/γ if (isDebug) System.out.println("D:" + delta); } while (delta > minDelta); // return U return U; } } public static class State implements Comparable<State> { @Override public String toString() { return String .format("State [minSum=%s, numAces=%s, dMinSum=%s, dNumAces=%s, isTwoCards=%s, isBlackjack=%s, pair=%s, playerTurn=%s, doubleReward=%s, reward=%s, depth=%s]", minSum, numAces, dMinSum, dNumAces, isTwoCards, isBlackjack, pair, playerTurn, doubleReward, reward, depth); } public State() { super(); BlackJackPlayer.statesGenerated++; } int minSum = 0; int numAces = 0; int dMinSum = 0; int dNumAces = 0; boolean isTwoCards = true; boolean isBlackjack = false; /// 0 = no pair. otherwise value indicates which number the pair is of (1 = 1, 1 cards) int pair = 0; /// if playerTurn is false, then it's the dealer's turn boolean playerTurn = true; String friendlyName = null; boolean doubleReward = false; // each state has an associated reward double reward = 0; int depth = 0; // each state has available actions // if we're in an end state, then there are no available actions Set<A> availableActions = new HashSet<A>(); // expected value and best policy double expectedValue = 0; char bestPolicy = 'X'; // for each action there are associated neighbour states // list of states and associated transition probability HashMap<State, Double> hitStates = new HashMap<State,Double>(0); HashMap<State, Double> standStates = new HashMap<State,Double>(0); HashMap<State, Double> splitStates = new HashMap<State,Double>(0); HashMap<State, Double> doubleStates = new HashMap<State,Double>(0); public int playerScore() { // if the lower score more than 21, then we're in trouble // no need to figure out a better score if (minSum > 21) { return this.minSum; } int sumWithoutAces = minSum - numAces; int maxSum = minSum; int newSum = 0; int oldSum = minSum; // how many aces can we set to be 11 for (int i=1;i <= numAces; i++) { int numAcesToUseAsEleven = i; int numAcesToUseAsOne = numAces - i; newSum = sumWithoutAces + (11*numAcesToUseAsEleven) + (1*numAcesToUseAsOne); if (newSum > 21) { maxSum = oldSum; break; } else { maxSum = newSum; oldSum = newSum; } } return maxSum; } public boolean isDealerBlackjack() { if (dNumAces!=1) { return false; } if (dMinSum == 11) { return true; } else { return false; } } public int dealerScore() { // if the lower score more than 21, then we're in trouble // no need to figure out a better score if (dMinSum > 21) { return this.dMinSum; } int sumWithoutAces = dMinSum - dNumAces; int maxSum = dMinSum; int newSum = 0; int oldSum = dMinSum; // how many aces can we set to be 11 for (int i=1;i <= dNumAces; i++) { int numAcesToUseAsEleven = i; int numAcesToUseAsOne = dNumAces - i; newSum = sumWithoutAces + (11*numAcesToUseAsEleven) + (1*numAcesToUseAsOne); if (newSum > 21) { maxSum = oldSum; break; } else { maxSum = newSum; oldSum = newSum; } } return maxSum; } public Set<State> statesMatchingAction(A a) { switch(a.action) { case 'S': return this.standStates.keySet(); case 'H': return this.hitStates.keySet(); case 'P': return this.splitStates.keySet(); case 'D': return this.doubleStates.keySet(); default: System.out.println("Weird state " + a.action); assert(false); return null; } } public void generateNextStates(HashMap<Integer,State> states, double faceCardProbability, double otherCardProbability) { //System.out.println (states.size()); // let's see what actions are possible if (playerTurn) { // can I double ? doDoubleAction(states, faceCardProbability, otherCardProbability); // can I hit ? doHitAction(states, faceCardProbability, otherCardProbability); // can I stand ? doStandAction(states, faceCardProbability, otherCardProbability); // can I split ? doSplitAction(states, faceCardProbability, otherCardProbability); } // what about if it's the dealers turn ? // the next action will be to continue standing, but follow the dealer policy if (!playerTurn) { doDealerPolicy(states, faceCardProbability, otherCardProbability); } } private void generateNextStatesNoSplit(HashMap<Integer, State> states, double faceCardProbability, double otherCardProbability) { // let's see what actions are possible if (playerTurn) { // can I double ? doDoubleAction(states, faceCardProbability, otherCardProbability); // can I hit ? doHitAction(states, faceCardProbability, otherCardProbability); // can I stand ? doStandAction(states, faceCardProbability, otherCardProbability); } // what about if it's the dealers turn ? // the next action will be to continue standing, but follow the dealer policy if (!playerTurn) { doDealerPolicy(states, faceCardProbability, otherCardProbability); } } private void doStandAction(HashMap<Integer,State> states, double faceCardProbability, double otherCardProbability) { // generate states from double State s = new State(); s.minSum = this.minSum; s.numAces = this.numAces; s.dMinSum = this.dMinSum; s.dNumAces = this.dNumAces; s.isTwoCards = this.isTwoCards; // we've just done a hit s.isBlackjack = this.isBlackjack; s.pair = this.pair; s.playerTurn = false; assert (s.doubleReward == false); assert (s.reward == 0); Double transitionProb = 1.0; if (states.containsKey(s.hashCode()) && s.equals(states.get(s.hashCode()))) { this.standStates.put(states.get(s.hashCode()), transitionProb); } else { s.generateNextStates(states, faceCardProbability, otherCardProbability); this.standStates.put(s, transitionProb); states.put(s.hashCode(), s); // TODO ????? reverse lines } this.availableActions.add(new A('S')); } private void doHitAction(HashMap<Integer,State> states, double faceCardProbability, double otherCardProbability) { if (this.playerTurn && this.minSum < 21) { for (int newCard = 1; newCard <= 10; newCard++) { State s = new State(); s.minSum = this.minSum + newCard; if (newCard == 1) { s.numAces = numAces + 1; } else { s.numAces = numAces; } s.dMinSum = this.dMinSum; s.dNumAces = this.dNumAces; s.isTwoCards = false; // we've just done a hit s.isBlackjack = this.isBlackjack; s.pair = 0; if (s.minSum > 21) { // don't have to worry about double awards s.reward = -1; s.playerTurn = false; } else { s.playerTurn = playerTurn; assert (s.doubleReward == false); } Double transitionProb; if (newCard == 10) { transitionProb = faceCardProbability; } else { transitionProb = otherCardProbability; } if (states.containsKey(s.hashCode()) && s.equals(states.get(s.hashCode()))) { this.hitStates.put(states.get(s.hashCode()), transitionProb); } else { s.generateNextStates(states, faceCardProbability, otherCardProbability); this.hitStates.put(s, transitionProb); states.put(s.hashCode(), s); } this.availableActions.add(new A('H')); } } } private void doDealerPolicy(HashMap<Integer,State> states, double faceCardProbability, double otherCardProbability) { // if this state has a non-zero reward with it, we're done. no actions need to be generated if (reward != 0) { assert (hitStates.size() == 0); assert (doubleStates.size() == 0); assert (splitStates.size() == 0); assert (standStates.size() == 0); return; } // calculate currentPlayer score int playerScore = this.playerScore(); double rewardMultiplier = 1; if (doubleReward) { rewardMultiplier = 2; } if (this.dealerScore() >= 17 || this.minSum > 21) { // the dealer has stopped hitting or the player has busted return; } else { for (int newCard = 1; newCard <= 10; newCard++) { // generate states from double State s = new State(); s.depth = this.depth + 1; //System.out.print(s.depth + " "); s.minSum = this.minSum; s.numAces = this.numAces; if (newCard == 1) { s.dNumAces = this.dNumAces + 1; } else { s.dNumAces = this.dNumAces; } s.dMinSum = this.dMinSum + newCard; s.isTwoCards = this.isTwoCards; s.isBlackjack = this.isBlackjack; s.pair = this.pair; s.playerTurn = this.playerTurn; s.doubleReward = this.doubleReward; Double transitionProb; if (newCard == 10) { transitionProb = faceCardProbability; } else { transitionProb = otherCardProbability; } int newDealerScore = s.dealerScore(); if (newDealerScore >= 17) { if (newDealerScore > 21) { s.reward = 1 * rewardMultiplier; } else if (newDealerScore > playerScore) { // dealer wins s.reward = -1 * rewardMultiplier; } else if (newDealerScore < playerScore) { s.reward = 1 * rewardMultiplier; } else if (newDealerScore == playerScore) { if (s.isDealerBlackjack()) { s.reward = -1 * rewardMultiplier; } else { s.reward = 0; } } } if (states.containsKey(s.hashCode()) && s.equals(states.get(s.hashCode()))) { this.standStates.put(states.get(s.hashCode()), transitionProb); } else { this.standStates.put(s, transitionProb); if (newDealerScore < 17) { s.generateNextStates(states, faceCardProbability, otherCardProbability); } states.put(s.hashCode(), s); } this.availableActions.add(new A('S')); } return; } } private void doDoubleAction(HashMap<Integer,State> states, double faceCardProbability, double otherCardProbability) { if (isTwoCards && playerTurn) { // generate states from double for (int newCard = 1; newCard <= 10; newCard++) { State s = new State(); s.minSum = this.minSum + newCard; if (newCard == 1) { s.numAces = this.numAces + 1; } else { s.numAces = this.numAces; } s.dMinSum = this.dMinSum; s.dNumAces = this.dNumAces; s.isTwoCards = false; // isTwoCards tells us that this is a pair that is being doubled s.isBlackjack = this.isBlackjack; s.pair = this.pair; s.playerTurn = false; // we can no longer take any action s.doubleReward = true; if (s.minSum > 21) { // don't have to worry about double awards here s.reward = -2; } Double transitionProb; if (newCard == 10) { transitionProb = faceCardProbability; } else { transitionProb = otherCardProbability; } if (states.containsKey(s.hashCode()) && s.equals(states.get(s.hashCode()))) { this.doubleStates.put(states.get(s.hashCode()), transitionProb); } else { s.generateNextStates(states, faceCardProbability, otherCardProbability); this.doubleStates.put(s, transitionProb); states.put(s.hashCode(), s); } this.availableActions.add(new A('D')); } } } private void doSplitAction(HashMap<Integer,State> states, double faceCardProbability, double otherCardProbability) { if (playerTurn && pair > 0 && isTwoCards) { if (numAces!=2) // normal splitting { // generate states from double for (int newCard = 1; newCard <= 10; newCard++) { State s = new State(); s.minSum = this.pair + newCard; if (newCard == 1) { s.numAces = numAces + 1; } else { s.numAces = numAces; } s.dMinSum = this.dMinSum; s.dNumAces = this.dNumAces; s.isTwoCards = this.isTwoCards; // isTwoCards tells us that this is a pair that is being doubled s.isBlackjack = this.isBlackjack; if (newCard == pair) { s.pair = this.pair; } else { s.pair = 0; } s.playerTurn = this.playerTurn; // if the new card is an ace while pair is a 10, we have blackjack if (this.pair == 10 && newCard == 1) { s.isBlackjack = true; s.reward = 3.0; s.playerTurn = false; } else { s.doubleReward = true; } Double transitionProb; if (newCard == 10) { transitionProb = faceCardProbability; } else { transitionProb = otherCardProbability; } if (states.containsKey(s.hashCode()) && s.equals(states.get(s.hashCode()))) { this.splitStates.put(states.get(s.hashCode()), transitionProb); } else { this.splitStates.put(s, transitionProb); s.generateNextStatesNoSplit(states, faceCardProbability, otherCardProbability); states.put(s.hashCode(), s); } this.availableActions.add(new A('P')); } } else if (numAces == 2) // splitting 2 aces { assert (this.pair == 1); // (2 Aces) // generate states from double for (int newCard = 1; newCard <= 10; newCard++) { State s = new State(); s.minSum = this.pair + newCard; if (newCard != 1) // we now only have 1 aces in the newer state { s.numAces = this.numAces - 1; } else { s.numAces = this.numAces; } s.dMinSum = this.dMinSum; s.dNumAces = this.dNumAces; s.isTwoCards = this.isTwoCards; // isTwoCards tells us that this is a pair that is being doubled s.isBlackjack = this.isBlackjack; if (newCard != pair) { s.pair = 0; } // this now the dealers turn. we only get 1 additional card s.playerTurn = false; s.doubleReward = true; Double transitionProb; if (newCard == 10) { transitionProb = faceCardProbability; } else { transitionProb = otherCardProbability; } if (newCard == 10) { s.isBlackjack = false; s.reward = 0; s.playerTurn = false; } if (states.containsKey(s.hashCode()) && s.equals(states.get(s.hashCode()))) { this.splitStates.put(states.get(s.hashCode()), transitionProb); } else { this.splitStates.put(s, transitionProb); s.generateNextStates(states, faceCardProbability, otherCardProbability); states.put(s.hashCode(), s); } this.availableActions.add(new A('P')); } } } } @Override public int compareTo(State s1) { if (s1.minSum == this.minSum && s1.numAces == this.numAces && s1.dMinSum == this.dMinSum && s1.dNumAces == this.dNumAces && s1.isTwoCards == this.isTwoCards && s1.isBlackjack == this.isBlackjack && s1.pair == this.pair && s1.playerTurn == this.playerTurn) { return 0; } if (this.minSum > s1.minSum) return 1; else return -1; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + dMinSum; result = prime * result + dNumAces; result = prime * result + (doubleReward ? 1231 : 1237); result = prime * result + (isBlackjack ? 1231 : 1237); result = prime * result + (isTwoCards ? 1231 : 1237); result = prime * result + minSum; result = prime * result + numAces; result = prime * result + pair; result = prime * result + (playerTurn ? 1231 : 1237); long temp; temp = Double.doubleToLongBits(reward); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } State other = (State) obj; if (dMinSum != other.dMinSum) { return false; } if (dNumAces != other.dNumAces) { return false; } if (doubleReward != other.doubleReward) { return false; } if (isBlackjack != other.isBlackjack) { return false; } if (isTwoCards != other.isTwoCards) { return false; } if (minSum != other.minSum) { return false; } if (numAces != other.numAces) { return false; } if (pair != other.pair) { return false; } if (playerTurn != other.playerTurn) { return false; } if (Double.doubleToLongBits(reward) != Double .doubleToLongBits(other.reward)) { return false; } return true; } } private static boolean isDebug = true; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub if (args.length != 1) { System.out.println("you need to specify the probability of a face card"); return; } double faceCardProbability = Double.parseDouble(args[0]); Mdp mdp = new Mdp(faceCardProbability); double gamma = 1; double epsilon = 0.000001; ValueIteration vi = new ValueIteration(gamma); vi.valueIteration(mdp, epsilon); System.out.println("Face card probability: " + faceCardProbability); System.out.println("Num state objects created: " + BlackJackPlayer.statesGenerated); createOutputFile(mdp, "Policy.txt"); } public static void createOutputFile(Mdp mdp, String fileName) { String newLine = System.getProperty("line.separator"); String tabChar = "\t"; FileWriter outputFw; try { outputFw = new FileWriter(fileName, false /*append*/); BufferedWriter bw = new BufferedWriter(outputFw); // if we've already generated states, just return them // let's generate all possible states and cache them HashMap<Integer, State> states = mdp.getStatesHashMap(); // generate starting states int numAces = 0; boolean isTwoCards = true; boolean playerTurn = true; int pairValue = 0; for (int minSum = 5; minSum <= 19; minSum++) { bw.write(minSum + tabChar); for (int dealerSum = 2; dealerSum <=11; dealerSum++) { int dealerNumAces = 0; if (dealerSum == 11) dealerNumAces = 1; State s = new State(); s.minSum = minSum; s.numAces = numAces; s.dMinSum = (dealerSum - 10*dealerNumAces); s.dNumAces = dealerNumAces; s.isTwoCards = isTwoCards; s.isBlackjack = false; s.pair = pairValue; s.playerTurn = playerTurn; s.friendlyName = minSum + ""; bw.write(states.get(s.hashCode()).bestPolicy); if (dealerSum!=11) bw.write(" "); } bw.write(newLine); } numAces = 1; for (int minSum = 3; minSum <= 10; minSum++) { bw.write("A" + (minSum-1) + tabChar); for (int dealerSum = 2; dealerSum <=11; dealerSum++) { int dealerNumAces = 0; if (dealerSum == 11) dealerNumAces = 1; State s = new State(); s.minSum = minSum; s.numAces = numAces; s.dMinSum = (dealerSum - 10*dealerNumAces); s.dNumAces = dealerNumAces; s.isTwoCards = isTwoCards; s.isBlackjack = false; s.pair = pairValue; s.playerTurn = playerTurn; s.friendlyName = "A" + minSum; bw.write(states.get(s.hashCode()).bestPolicy); if (dealerSum!=11) bw.write(" "); } bw.write(newLine); } // add pairs numAces = 0; for (pairValue = 2; pairValue <= 10; pairValue++) { bw.write(pairValue + "" + pairValue + tabChar); for (int dealerSum = 2; dealerSum <=11; dealerSum++) { int dealerNumAces = 0; if (dealerSum == 11) dealerNumAces = 1; State s = new State(); s.minSum = pairValue * 2; s.numAces = numAces; s.dMinSum = dealerSum - 10*dealerNumAces; s.dNumAces = dealerNumAces; s.isTwoCards = isTwoCards; s.isBlackjack = false; s.pair = pairValue; s.playerTurn = playerTurn; s.friendlyName = pairValue + "" + pairValue; bw.write(states.get(s.hashCode()).bestPolicy); if (dealerSum!=11) bw.write(" "); } bw.write(newLine); } // add the pair of aces numAces = 2; for (pairValue = 1; pairValue <= 1; pairValue++) { bw.write("AA" + tabChar); for (int dealerSum = 2; dealerSum <=11; dealerSum++) { int dealerNumAces = 0; if (dealerSum == 11) dealerNumAces = 1; State s = new State(); s.minSum = pairValue * 2; s.numAces = numAces; s.dMinSum = (dealerSum - 10*dealerNumAces); s.dNumAces = dealerNumAces; s.isTwoCards = isTwoCards; s.isBlackjack = false; s.pair = pairValue; s.playerTurn = playerTurn; s.friendlyName = "AA"; bw.write(states.get(s.hashCode()).bestPolicy); if (dealerSum!=11) bw.write(" "); } } bw.close(); outputFw.close(); } catch (IOException e) { System.out.println("Unable to output file"); e.printStackTrace(); } } }