├── .gitignore ├── package.json ├── LICENSE ├── src ├── EasyStrategy.js ├── BlackjackCalculation.js ├── ExactComposition.js └── Suggestion.js ├── README.md └── test ├── chart.js └── suggestion.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | src/web.config 2 | charts -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blackjack-strategy", 3 | "version": "1.4.0", 4 | "description": "provides a suggested action for a player based on Basic Strategy", 5 | "main": "src/Suggestion.js", 6 | "scripts": { 7 | "test": "node test/suggestion.spec.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/gsdriver/blackjack-strategy" 12 | }, 13 | "keywords": [ 14 | "blackjack", 15 | "basic strategy", 16 | "games" 17 | ], 18 | "directories": { 19 | "doc": ".", 20 | "test": "test", 21 | "lib": "src" 22 | }, 23 | "author": "Garrett Vargas ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/gsdriver/blackjack-strategy/issues" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 gsdriver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/EasyStrategy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | 4 | * Copyright (c) 2016 Garrett Vargas 5 | 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | module.exports = { 26 | // Recommended actions follow Basic Strategy, based on the rules currently in play 27 | EasyBasicStrategy: function(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, options) 28 | { 29 | // Can you split? 30 | if ((playerCards[0] == playerCards[1]) && (playerCards.length == 2) && (handCount < options.maxSplitHands)) 31 | { 32 | // Split unless 4s, 5s, and 10s 33 | if ((playerCards[0] != 4) && (playerCards[0] != 5) && (playerCards[0] != 10)) 34 | { 35 | return "split"; 36 | } 37 | } 38 | 39 | // Double 40 | if (((playerCards.length == 2) && ((handCount == 1) || options.doubleAfterSplit)) && 41 | ((handValue.total >= options.doubleRange[0]) && (handValue.total <= options.doubleRange[1]))) 42 | { 43 | // Only on 9-11 44 | if ((handValue.total == 9) && (dealerCard < 7) && (dealerCard != 1)) 45 | { 46 | return "double"; 47 | } 48 | if ((handValue.total == 10) && (dealerCard < 10) && (dealerCard != 1)) 49 | { 50 | return "double"; 51 | } 52 | if (handValue.total == 11) 53 | { 54 | return "double"; 55 | } 56 | } 57 | 58 | // Hit and stand 59 | if (handValue.soft) 60 | { 61 | // Hit until you get to 17, 18 against dealer 9-Ace 62 | if ((handValue.total < 18) || ((handValue.total == 18) && ((dealerCard >= 9) || (dealerCard == 1)))) 63 | { 64 | return "hit"; 65 | } 66 | } 67 | 68 | if (handValue.total <= 11) 69 | { 70 | return "hit"; 71 | } 72 | else if (handValue.total >= 17) 73 | { 74 | return "stand"; 75 | } 76 | else 77 | { 78 | // Hit on 7 or above, else stand 79 | return ((dealerCard >= 7) || (dealerCard == 1)) ? "hit" : "stand"; 80 | } 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blackjack-strategy 2 | Nodejs module that provides suggested action for a blackjack hand. It is intended to augment blackjack applications 3 | by teaching the player Basic Strategy, based on http://wizardofodds.com/games/blackjack/strategy/calculator/. You can 4 | pass in different Rule variants as well. 5 | 6 | # Available variants 7 | 8 | * numberOfDecks - 1, 2, 4, 6, or 8 9 | * doubleRange - array indicating a low and high set of player totals that can double (v1.1 or later) 10 | * double - none, any, 10or11, or 9or10or11 (note if set doubleRange overrides this value) 11 | * maxSplitHands - the maximum number of hands a player can have (1=no splits allowed) 12 | * resplitAces - whether the player can resplit Aces (true or false) 13 | * doubleAfterSplit - whether the player can double after split (true or false) 14 | * hitSoft17 - whether the dealer hits soft 17 or not 15 | * surrender - none, early, or late (early surrender means surrender is offered before the dealer checks for a blackjack) 16 | * offerInsurance - whether insurance is offered or not 17 | 18 | # Usage 19 | 20 | The exposed function from this library is `GetRecommendedPlayerAction` which will return a string suggesting an action for the player to take on their hand. 21 | 22 | ``` 23 | GetRecommendedPlayerAction(playerCards, dealerCard, handCount, 24 | dealerCheckedBlackjack, options) 25 | ``` 26 | 27 | The arguments to `GetRecommendedPlayerAction` are: 28 | 29 | * playerCards - an array of integer values of the card, from 1-10 (1=Ace, all face cards should be 10) 30 | * dealerCard - an integer representing the value of the dealer's up card 31 | * handCount - the total number of hands the player has in play, usually 1 unless the player has split 32 | * dealerCheckedBlackjack - a Boolean indicating whether the dealer has checked for Blackjack yet or not 33 | * options - an object containing information about the rules of the game and the complexity of Basic Strategy suggestion desired 34 | 35 | The options structure is composed of the following fields with the following default values: 36 | 37 | ``` 38 | { 39 | hitSoft17:true, // Does dealer hit soft 17 40 | surrender:"late", // Surrender offered - none, late, or early 41 | double:"any", // Double rules - none, 10or11, 9or10or11, any 42 | doubleRange:[0,21], // Range of values you can double, 43 | // if set supercedes double (v1.1 or higher) 44 | doubleAfterSplit:true, // Can double after split 45 | resplitAces:false, // Can you resplit aces 46 | offerInsurance:true, // Is insurance offered 47 | numberOfDecks:6, // Number of decks in play 48 | maxSplitHands:4, // Max number of hands you can have due to splits 49 | count: { // Structure defining the count (v1.3 or higher) 50 | system: null, // The count system - only "HiLo" is supported 51 | trueCount: null }; // The TrueCount (count / number of decks left) 52 | strategyComplexity:"simple" // easy (v1.2 or higher), simple, advanced, 53 | // exactComposition, bjc-supereasy (v1.4 or higher), 54 | // bjc-simple (v1.4 or higher), or bjc-great 55 | // (v1.4 or higer) - see below for details 56 | } 57 | ``` 58 | 59 | The `strategyComplexity` field determines how closely Basic Strategy is followed in making a recommendation. There are six supported values: 60 | 61 | ## Easy 62 | In the case of "easy" the strategy is an easy-to-follow set of rules designed for beginners which simulation runs show is about 0.6% less advantagous than the "advanced" strategy on a 6-deck game: 63 | 64 | * Split all pairs except 4s, 5s, and 10s 65 | * Double 9 against a dealer card of 2-6 66 | * Double 10 against a dealer card of 2-9 67 | * Always double 11 68 | * Hit totals less than 12 69 | * Stand on totals of hard 17 or above 70 | * On hard 12-16, stand against a dealer 2-6, hit on 7 or above 71 | * Hit soft total up to and including 17 72 | * Hit soft 18 if the dealer has 9, 10, or Ace showing 73 | 74 | ## Simple 75 | The "simple" strategy is a little more complex than the "easy" option and designed for more intermediate players (for example "always split 8s"). 76 | 77 | ## Advanced 78 | In the case of "advanced," Basic Stategy is followed even in more advanced edge-cases (for example, surrender a pair of 8s against a dealer ace if the dealer hits soft 17). 79 | 80 | ## Exact Composition 81 | In the case of "exactComposition," certain rules are followed based on the exact make-up of the player's hand (for example, in single deck surrender a hand with a 10 and a 7 against a dealer ace if the dealer hits soft 17, but don't surrender other types of 17-value hands). 82 | 83 | ## bjc-simple, bjc-supereasy, and bjc-great 84 | These strategies come from and, similar 85 | to "easy," "simple," and "advanced" allow players to follow a different set of rules that are 86 | easy to remember, but provide a trade-off of expected payout for an easier set of rules 87 | 88 | Some example cases: 89 | 90 | ``` 91 | const lib = require("blackjack-strategy"); 92 | 93 | // Hit a three-card 11 against a dealer 6 should return "hit" 94 | lib.GetRecommendedPlayerAction([2,3,6], 6, 1, true, null); 95 | 96 | // Pair of 8s against dealer Ace - an advanced Strategy option should "surrender" 97 | lib.GetRecommendedPlayerAction([8,8], 1, 1, true, 98 | {strategyComplexity: "advanced"}); 99 | 100 | // 10-7 against dealer Ace single deck should "surrender" 101 | lib.GetRecommendedPlayerAction([7,10], 1, 1, true, 102 | {numberOfDecks:1, strategyComplexity: "exactComposition"}); 103 | ``` 104 | 105 | # Contributing - bug fixes 106 | 107 | Contributions are welcome! Please feel free to fork this code and submit pull requests, for bug fixes or feature enhancements. 108 | 109 | 1. Fork it! 110 | 2. Create your featured branch: `git checkout -b my-feature` 111 | 3. Commit your changes: `git commit -m 'add some feature'` 112 | 4. Push to the branch: `git push origin my-feature` 113 | 5. Submit a pull request 114 | 115 | Many Thanks! 116 | -------------------------------------------------------------------------------- /test/chart.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | 4 | * Copyright (c) 2016 Garrett Vargas 5 | 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const lib = require('../src/suggestion'); 26 | 27 | function OptionsToText(options) 28 | { 29 | var text = ""; 30 | 31 | text += (options.numberOfDecks + " deck(s), "); 32 | text += (options.hitSoft17) ? "Dealer Hits ": "Dealer Stands "; 33 | text += "on soft 17 - " + options.strategyComplexity + " complexity"; 34 | 35 | return text; 36 | } 37 | 38 | function ResultToLetter(result) 39 | { 40 | var letter = "N"; 41 | 42 | switch (result) 43 | { 44 | case "surrender": 45 | letter = "R"; 46 | break; 47 | case "split": 48 | letter = "P"; 49 | break; 50 | case "stand": 51 | letter = "S"; 52 | break; 53 | case "hit": 54 | letter = "H"; 55 | break; 56 | case "double": 57 | letter = "D"; 58 | break; 59 | } 60 | 61 | return letter; 62 | } 63 | 64 | function GetOneRow(playerCards, options) 65 | { 66 | var dealerCard; 67 | var line = ""; 68 | var result; 69 | 70 | // Go thru every combination with this set of options and spit out to a CSV file 71 | // Start with the most liberal double and surrender rules, and then augment with a qualifier if you 72 | // aren't able to double or surrender 73 | options.doubleRange = [1,21]; 74 | options.surrender = "late"; 75 | options.doubleAfterSplit = true; 76 | 77 | for (dealerCard = 2; dealerCard <= 11; dealerCard++) 78 | { 79 | result = lib.GetRecommendedPlayerAction(playerCards, (dealerCard == 11) ? 1 : dealerCard, 1, true, options); 80 | line += ResultToLetter(result); 81 | 82 | if ((result == "double") || (result == "surrender")) 83 | { 84 | // Do it again with no double or surrender 85 | options.doubleRange = [0,0]; 86 | options.surrender = "none"; 87 | 88 | result = lib.GetRecommendedPlayerAction(playerCards, dealerCard, 1, true, options); 89 | line += ResultToLetter(result); 90 | 91 | // Reset to initial liberal options 92 | options.doubleRange = [1,21]; 93 | options.surrender = "late"; 94 | } 95 | else if (result == "split") 96 | { 97 | // Try it again if you can't double after split 98 | options.doubleAfterSplit = false; 99 | result = lib.GetRecommendedPlayerAction(playerCards, dealerCard, 1, true, options); 100 | if (result != "split") 101 | { 102 | line += ResultToLetter(result); 103 | } 104 | 105 | // Reset to initial liberal options 106 | options.doubleAfterSplit = true; 107 | } 108 | 109 | if (dealerCard < 11) 110 | { 111 | line += "," 112 | } 113 | } 114 | 115 | return line; 116 | } 117 | 118 | // Creates a chart for the given playing options - for double and surrender, it creates an entry that tells 119 | // you what the alternate action should be if you cannot double or surrender 120 | function CreateChart(options) 121 | { 122 | var playerTotal; 123 | var line = ""; 124 | var playerCards = []; 125 | 126 | // First the hard totals (8-17) 127 | console.log(OptionsToText(options)); 128 | console.log(" ,2,3,4,5,6,7,8,9,10,A"); 129 | for (playerTotal = 8; playerTotal <= 17; playerTotal++) 130 | { 131 | // Turn this into a hard hand - make it 10+ (or 2+), as this avoids splits 132 | // Note this assumes that we are not looking at exactComposition 133 | if (playerTotal < 12) 134 | { 135 | playerCards[0] = 2; 136 | playerCards[1] = playerTotal - 2; 137 | } 138 | else 139 | { 140 | playerCards[0] = 10; 141 | playerCards[1] = playerTotal - 10; 142 | } 143 | 144 | line = playerTotal + "," + GetOneRow(playerCards, options); 145 | 146 | // Write out this line 147 | console.log(line); 148 | } 149 | 150 | // Then the soft totals (13-20) 151 | console.log(" ,2,3,4,5,6,7,8,9,10,A"); 152 | playerCards[0] = 1; 153 | for (playerTotal = 13; playerTotal <= 20; playerTotal++) 154 | { 155 | playerCards[1] = playerTotal - 11; 156 | line = playerTotal + "," + GetOneRow(playerCards, options); 157 | 158 | // Write out this line 159 | console.log(line); 160 | } 161 | 162 | // And last the pairs 163 | console.log(" ,2,3,4,5,6,7,8,9,10,A"); 164 | for (playerTotal = 4; playerTotal <= 18; playerTotal += 2) 165 | { 166 | playerCards[0] = playerTotal / 2; 167 | playerCards[1] = playerCards[0]; 168 | line = playerCards[0] + " pair," + GetOneRow(playerCards, options); 169 | 170 | // Write out this line 171 | console.log(line); 172 | } 173 | 174 | // Aces last 175 | playerCards[0] = 1; 176 | playerCards[1] = 1; 177 | line = "A pair," + GetOneRow(playerCards, options); 178 | console.log(line); 179 | } 180 | 181 | // We will spit out basic single/double/4+ options for both hit and stand on soft 17 182 | const playerOptions = { hitSoft17: true, numberOfDecks: 1, strategyComplexity: "advanced"}; 183 | CreateChart(playerOptions); 184 | 185 | playerOptions.hitSoft17 = false; 186 | CreateChart(playerOptions); 187 | 188 | playerOptions.numberOfDecks = 2; 189 | playerOptions.hitSoft17 = true; 190 | CreateChart(playerOptions); 191 | 192 | playerOptions.hitSoft17 = false; 193 | CreateChart(playerOptions); 194 | 195 | playerOptions.numberOfDecks = 4; 196 | playerOptions.hitSoft17 = true; 197 | CreateChart(playerOptions); 198 | 199 | playerOptions.hitSoft17 = false; 200 | CreateChart(playerOptions); 201 | -------------------------------------------------------------------------------- /test/suggestion.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | 4 | * Copyright (c) 2016 Garrett Vargas 5 | 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const lib = require('../src/suggestion'); 26 | 27 | var succeeded = 0; 28 | var failed = 0; 29 | 30 | function RunTest(testName, playerCards, dealerCard, handCount, dealerCheckedBlackjack, options, expectedResult) 31 | { 32 | const result = lib.GetRecommendedPlayerAction(playerCards, dealerCard, handCount, dealerCheckedBlackjack, options); 33 | 34 | if (result == expectedResult) 35 | { 36 | console.log("SUCCESS: " + testName + " returned " + result); 37 | succeeded++; 38 | } 39 | else 40 | { 41 | console.log("FAIL: " + testName + " returned " + result + " rather than " + expectedResult); 42 | failed++; 43 | } 44 | } 45 | 46 | // Use the default options 47 | RunTest("Stand on 16 against dealer 3", [9,7], 3, 1, true, null, "stand"); 48 | RunTest("Split 9s against dealer 5", [9,9], 5, 1, true, null, "split"); 49 | RunTest("Hit 16 against 10 after split", [9,7], 10, 2, true, null, "hit"); 50 | RunTest("Split pair of 8s against dealer Ace - basic", [8,8], 1, 1, true, null, "split"); 51 | RunTest("Surrender 15 against dealer 10",[10,5], 10, 1, true, null, "surrender"); 52 | RunTest("No insurance ever", [10,1], 1, 1, false, null, "noinsurance"); 53 | RunTest("Double soft 17 against 6", [1,6], 6, 2, true, null, "double"); 54 | RunTest("Three-card 11 against 6", [2,3,6], 6, 1, true, null, "hit"); 55 | 56 | // Some single deck cases 57 | RunTest("Split 6s against dealer 2", [6,6], 2, 1, true, {numberOfDecks:1, doubleAfterSplit:false}, "split"); 58 | RunTest("Split 9s against Ace single deck", [9,9], 1, 1, true, {strategyComplexity:"advanced", numberOfDecks:1, doubleAfterSplit:true}, "split"); 59 | RunTest("Double 8 against dealer 5 single deck", [3,5], 5, 1, true, {numberOfDecks:1, doubleAfterSplit:false}, "double"); 60 | RunTest("Hit 8 against dealer 5 single deck if can't double", [3,5], 5, 1, true, {numberOfDecks:1, doubleRange:[9,11]}, "hit"); 61 | RunTest("Stand soft 18 against Ace if stand on soft 17", [1,7], 1, 1, true, {numberOfDecks:1, hitSoft17:false}, "stand"); 62 | 63 | // blackjackcalculation.com Super-easy strategy 64 | RunTest("Split pair of 8s against dealer 9", [8,8], 9, 1, true, {numberOfDecks:1, strategyComplexity: "bjc-supereasy"}, "split"); 65 | RunTest("Double 10 against dealer low card", [7,3], 4, 1, true, {numberOfDecks:4, strategyComplexity: "bjc-supereasy"}, "double"); 66 | RunTest("Hit soft 17 or less", [1,4], 4, 1, true, {numberOfDecks:2, strategyComplexity: "bjc-supereasy"}, "hit"); 67 | RunTest("Stand on 12+ if dealer low card", [10,3], 6, 1, true, {numberOfDecks:4, strategyComplexity: "bjc-supereasy"}, "stand"); 68 | RunTest("Hit until 17+ if dealer high card", [10,3], 1, 1, true, {numberOfDecks:6, strategyComplexity: "bjc-supereasy"}, "hit"); 69 | 70 | // blackjackcalculation.com Simple strategy 71 | RunTest("Surrender hard 16 against dealer 10",[10,6], 10, 1, true, {numberOfDecks:4, surrender: "early", strategyComplexity: "bjc-simple"}, "surrender"); 72 | RunTest("Split pair of 3s against dealer low card", [3,3], 6, 1, true, {numberOfDecks:2, strategyComplexity: "bjc-simple"}, "split"); 73 | RunTest("Double 10 against dealer 2-9", [2,8], 8, 1, true, {numberOfDecks:2, strategyComplexity: "bjc-great"}, "double"); 74 | RunTest("Double A6 or A7 against dealer low card", [1,7], 4, 1, true, {numberOfDecks:6, strategyComplexity: "bjc-simple"}, "double"); 75 | 76 | // blackjackcalculation.com Great strategy 77 | RunTest("Hit soft 18 against dealer 9-A", [1,7], 10, 1, true, {numberOfDecks:4, strategyComplexity: "bjc-great"}, "hit"); 78 | RunTest("Hit on 12 if dealer 2 or 3", [5,7], 3, 1, true, {numberOfDecks:4, strategyComplexity: "bjc-great"}, "hit"); 79 | RunTest("Double A2-A5 against dealer 5,6", [1,4], 6, 1, true, {numberOfDecks:6, strategyComplexity: "bjc-great"}, "double"); 80 | 81 | // Easy strategy 82 | RunTest("Split pair of 7s against dealer 10 single deck", [7,7], 10, 1, true, {numberOfDecks:1, strategyComplexity: "easy"}, "split"); 83 | RunTest("Double 11 against Ace multi-deck stand soft 17", [7,4], 1, 1, true, {numberOfDecks:6, hitSoft17: false, strategyComplexity: "easy"}, "double"); 84 | 85 | // Advanced strategy 86 | RunTest("Surrender pair of 8s against dealer Ace", [8,8], 1, 1, true, {strategyComplexity: "advanced"}, "surrender"); 87 | RunTest("Early Surrender pair of 8s against dealer 10 single deck", [8,8], 10, 1, false, {numberOfDecks:1, surrender:"early", strategyComplexity: "advanced"}, "surrender"); 88 | 89 | // Exact Composition tests 90 | RunTest("Hit pair of 7s against dealer 10 single deck", [7,7], 10, 1, true, {numberOfDecks:1}, "hit"); 91 | RunTest("Surrender pair of 7s against dealer 10 single deck with exact composition", [7,7], 10, 1, true, {numberOfDecks:1, strategyComplexity:"exactComposition"}, "surrender"); 92 | RunTest("Surrender 10-7 against dealer Ace single deck", [7,10], 1, 1, true, {numberOfDecks:1, strategyComplexity: "exactComposition"}, "surrender"); 93 | RunTest("Specific four-card hand in double deck stand soft 17", [8,1,6,1], 10, 1, true, {numberOfDecks: 2, strategyComplexity: "exactComposition", hitSoft17: false}, "hit"); 94 | RunTest("Specific six-card hand in double deck stand soft 17", [2,3,3,2,3,3], 9, 1, true, {numberOfDecks: 2, strategyComplexity: "exactComposition", hitSoft17: false}, "stand") 95 | RunTest("Three card 16 against 10 in double deck stand soft 17", [4,4,10], 10, 1, true, {numberOfDecks: 2, strategyComplexity: "exactComposition", hitSoft17: false}, "stand"); 96 | RunTest("8/7 against Ace in double deck hit soft 17 doesn't surrender", [8,7], 1, 1, true, {numberOfDecks:2, strategyComplexity: "exactComposition", hitSoft17: true}, "hit"); 97 | 98 | // Some counting strategies 99 | RunTest("Insurance with counting", [4,9], 1, 1, false, {count:{system: "HiLo", trueCount: 4}}, "insurance"); 100 | RunTest("Split 10s against 5", [10,10], 5, 1, false, {count:{system: "HiLo", trueCount: 5.5}}, "split") 101 | RunTest("Surrender 14 against 10", [4,10], 10, 1, false, {count:{system: "HiLo", trueCount: 3}}, "surrender") 102 | 103 | // Test legacy "double" option 104 | RunTest("Hit with no double", [2,8], 6, 1, false, {double: "none"}, "hit"); 105 | 106 | // Final summary 107 | console.log("\r\nRan " + (succeeded + failed) + " tests; " + succeeded + " passed and " + failed + " failed"); -------------------------------------------------------------------------------- /src/BlackjackCalculation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | 4 | * Copyright (c) 2016 Garrett Vargas 5 | 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | module.exports = { 26 | // Recommended actions follow simple guidelines: 27 | // * always split aces and eights 28 | // * double 9 and 10 vs. dealer low card, and double 11 always 29 | // * hit on a soft 17 or less, stand on a soft 18 or more 30 | // * stand on any hand 12+ on a dealer low card (never bust) 31 | // * on a dealer high card, hit until 17+ (mimic the dealer) 32 | SuperEasyStrategy: function(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, options) 33 | { 34 | // Can you split? 35 | if ((playerCards[0] == playerCards[1]) && (playerCards.length == 2) && (handCount < options.maxSplitHands)) 36 | { 37 | // only split As and 8s 38 | if ((playerCards[0] == 1) || (playerCards[0] == 8)) 39 | { 40 | return "split"; 41 | } 42 | } 43 | 44 | // Double 45 | if (((playerCards.length == 2) && ((handCount == 1) || options.doubleAfterSplit)) && 46 | ((handValue.total >= options.doubleRange[0]) && (handValue.total <= options.doubleRange[1]))) 47 | { 48 | // Only on 9-10 against a dealer low card 49 | if ((handValue.total == 9) && (dealerCard < 7) && (dealerCard != 1)) 50 | { 51 | return "double"; 52 | } 53 | if ((handValue.total == 10) && (dealerCard < 7) && (dealerCard != 1)) 54 | { 55 | return "double"; 56 | } 57 | if (handValue.total == 11) 58 | { 59 | return "double"; 60 | } 61 | } 62 | 63 | // Hit and stand 64 | if (handValue.soft) 65 | { 66 | // Hit on a soft 17 or less, stand on a soft 18 or more 67 | return (handValue.total < 18) ? "hit" : "stand"; 68 | } 69 | 70 | // On a dealer low card, stand on any hand 12+ (never bust) 71 | if (handValue.total <= 11) 72 | { 73 | return "hit"; 74 | } 75 | else if ((dealerCard < 7) && (dealerCard != 1)) 76 | { 77 | return "stand"; 78 | } 79 | else 80 | { 81 | // On a dealer high card, hit until 17+ (mimic the dealer) 82 | // Hit on 7 or above, else stand 83 | return (handValue.total < 17) ? "hit" : "stand"; 84 | } 85 | }, 86 | 87 | // Recommended actions follow super-easy strategy, plus: 88 | // * Split 2s, 3s, 6s, 7s, and 9s vs. a dealer low card 89 | // * Double on 10 vs. a dealer 7, 8, or 9 (in addition to dealer 2 through 6) 90 | // * Double A6, A7 vs. a dealer low card 91 | SimpleStrategy: function(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, options) 92 | { 93 | // If early surrender is allowed, check that now (that's what early surrender means - before dealer checks for blackjack 94 | if ((options.surrender == "early") && (!handValue.soft && (handValue.total == 16) && (dealerCard == 10))) 95 | { 96 | return "surrender"; 97 | } 98 | 99 | // Can you split? 100 | if ((playerCards[0] == playerCards[1]) && (playerCards.length == 2) && (handCount < options.maxSplitHands)) 101 | { 102 | // Always split As and 8s 103 | if ((playerCards[0] == 1) || (playerCards[0] == 8)) 104 | { 105 | return "split"; 106 | } 107 | // Split unless 4s, 5s, and 10s against a dealer low card 108 | if ((playerCards[0] != 4) && (playerCards[0] != 5) && (playerCards[0] != 10) && (dealerCard < 7) && (dealerCard != 1)) 109 | { 110 | return "split"; 111 | } 112 | } 113 | 114 | // Double 115 | if (((playerCards.length == 2) && ((handCount == 1) || options.doubleAfterSplit)) && 116 | ((handValue.total >= options.doubleRange[0]) && (handValue.total <= options.doubleRange[1]))) 117 | { 118 | // Only on 9 against a dealer low card 119 | if ((handValue.total == 9) && (dealerCard < 7) && (dealerCard != 1)) 120 | { 121 | return "double"; 122 | } 123 | // Double on 10 vs. a dealer 7, 8, or 9 (in addition to dealer 2 through 6) 124 | if ((handValue.total == 10) && (dealerCard < 10) && (dealerCard != 1)) 125 | { 126 | return "double"; 127 | } 128 | // Double A6, A7 vs. a dealer low card 129 | if (playerCards.includes(1) && (playerCards.includes(6) || playerCards.includes(7)) && (dealerCard < 7) && (dealerCard != 1)) { 130 | return "double"; 131 | } 132 | if (handValue.total == 11) 133 | { 134 | return "double"; 135 | } 136 | } 137 | 138 | // Hit and stand 139 | if (handValue.soft) 140 | { 141 | // Hit on a soft 17 or less, stand on a soft 18 or more 142 | return (handValue.total < 18) ? "hit" : "stand"; 143 | } 144 | 145 | // On a dealer low card, stand on any hand 12+ (never bust) 146 | if (handValue.total <= 11) 147 | { 148 | return "hit"; 149 | } 150 | else if ((dealerCard < 7) && (dealerCard != 1)) 151 | { 152 | return "stand"; 153 | } 154 | else 155 | { 156 | // On a dealer high card, hit until 17+ (mimic the dealer) 157 | // Hit on 7 or above, else stand 158 | return (handValue.total < 17) ? "hit" : "stand"; 159 | } 160 | }, 161 | 162 | // Recommended actions follow super-easy and simple strategy, plus: 163 | // * Double A2-A5 vs. a dealer 5,6 164 | // * Stand on soft 18 or more Exception: Hit S18 vs 9, 10, Ace 165 | // * Dealer low card: Never Bust Stand on 12 or more Exception: Hit 12 vs 2, 3 166 | GreatStrategy: function(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, options) 167 | { 168 | // If early surrender is allowed, check that now (that's what early surrender means - before dealer checks for blackjack 169 | if ((options.surrender == "early") && (!handValue.soft && (handValue.total == 16) && (dealerCard == 10))) 170 | { 171 | return "surrender"; 172 | } 173 | 174 | // Can you split? 175 | if ((playerCards[0] == playerCards[1]) && (playerCards.length == 2) && (handCount < options.maxSplitHands)) 176 | { 177 | // Always split As and 8s 178 | if ((playerCards[0] == 1) || (playerCards[0] == 8)) 179 | { 180 | return "split"; 181 | } 182 | // Split unless 4s, 5s, and 10s against a dealer low card 183 | if ((playerCards[0] != 4) && (playerCards[0] != 5) && (playerCards[0] != 10) && (dealerCard < 7) && (dealerCard != 1)) 184 | { 185 | return "split"; 186 | } 187 | } 188 | 189 | // Double 190 | if (((playerCards.length == 2) && ((handCount == 1) || options.doubleAfterSplit)) && 191 | ((handValue.total >= options.doubleRange[0]) && (handValue.total <= options.doubleRange[1]))) 192 | { 193 | // Only on 9 against a dealer low card 194 | if ((handValue.total == 9) && (dealerCard < 7) && (dealerCard != 1)) 195 | { 196 | return "double"; 197 | } 198 | // Double on 10 vs. a dealer 7, 8, or 9 (in addition to dealer 2 through 6) 199 | if ((handValue.total == 10) && (dealerCard < 10) && (dealerCard != 1)) 200 | { 201 | return "double"; 202 | } 203 | if (playerCards.includes(1)) { 204 | // Double A6, A7 vs. a dealer low card 205 | if ((playerCards.includes(6) || playerCards.includes(7)) && (dealerCard < 7) && (dealerCard != 1)) { 206 | return "double"; 207 | } 208 | // Double A2-A5 vs. a dealer 5,6 209 | if ((playerCards.includes(2) || playerCards.includes(3) || playerCards.includes(4) || playerCards.includes(5)) && ((dealerCard == 5) || (dealerCard == 6))) { 210 | return "double"; 211 | } 212 | } 213 | if (handValue.total == 11) 214 | { 215 | return "double"; 216 | } 217 | } 218 | 219 | // Hit and stand 220 | if (handValue.soft) 221 | { 222 | // Hit on a soft 17 or less 223 | if (handValue.total < 18) 224 | { 225 | return "hit"; 226 | } 227 | // Stand on soft 18 or more 228 | // Exception: Hit S18 vs 9, 10, Ace 229 | return (handValue.total == 18 && ((dealerCard > 8) || (dealerCard == 1))) ? "hit" : "stand"; 230 | } 231 | 232 | // On a dealer low card, stand on any hand 12+ (never bust) 233 | // Exception: Hit 12 vs 2, 3 234 | if (handValue.total <= 11) 235 | { 236 | return "hit"; 237 | } 238 | else if ((handValue.total == 12) && ((dealerCard == 2) || (dealerCard == 3))) { 239 | return "hit"; 240 | } 241 | else if ((dealerCard < 7) && (dealerCard != 1)) 242 | { 243 | return "stand"; 244 | } 245 | else 246 | { 247 | // On a dealer high card, hit until 17+ (mimic the dealer) 248 | // Hit on 7 or above, else stand 249 | return (handValue.total < 17) ? "hit" : "stand"; 250 | } 251 | } 252 | }; 253 | -------------------------------------------------------------------------------- /src/ExactComposition.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | 4 | * Copyright (c) 2016 Garrett Vargas 5 | 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | module.exports = { 26 | // Special-case overrides based on exact composition of cards 27 | GetExactCompositionOverride: function (playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, options) 28 | { 29 | // If exactComposition isn't set, no override 30 | if (options.strategyComplexity != "exactComposition") 31 | { 32 | return null; 33 | } 34 | 35 | // Now look at strategies based on game options 36 | if ((options.numberOfDecks == 2) && (!options.hitSoft17)) 37 | { 38 | return TwoDeckStandSoft17(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, options); 39 | } 40 | }, 41 | // Should you surrender based on the exact composition of your cards? 42 | // return value of true means you should surrender (based on the exact cards in your hand) 43 | // return value of false means you should NOT surrender 44 | // return value of null means there is no opinion based on exact composition (continue processing via normal rules) 45 | GetSurrenderOverride : function (playerCards, dealerCard, handValue, handCount, options) 46 | { 47 | var shouldSurrender = null; 48 | 49 | if (options.surrender == "early") 50 | { 51 | // Don't surrender 10/4 or 5/9 in single deck, or 4/10 in double deck against dealer 10 52 | if ((handValue.total == 14) && (dealerCard == 10)) 53 | { 54 | if ((options.numberOfDecks == 1) && 55 | ((playerCards[0] == 4) || (playerCards[0] == 5) || (playerCards[0] == 9) || (playerCards[0] == 10))) 56 | { 57 | shouldSurrender = false; 58 | } 59 | if ((options.numberOfDecks == 2) && ((playerCards[0] == 4) || (playerCards[0] == 10))) 60 | { 61 | shouldSurrender = false; 62 | } 63 | } 64 | } 65 | // Late surrender against an Ace when dealer hits on soft 17 66 | else if ((options.hitSoft17) && (dealerCard == 1)) 67 | { 68 | // Single or double deck 8/7 doesn't surrender 69 | if ((handValue.total == 15) && (options.numberOfDecks <= 2) && ((playerCards[0] == 8) || (playerCards[0] == 7))) 70 | { 71 | shouldSurrender = false; 72 | } 73 | else if ((handValue.total == 17) && (options.numberOfDecks == 1) && ((playerCards[0] == 10) || (playerCards[0] == 7))) 74 | { 75 | shouldSurrender = true; 76 | } 77 | } 78 | // Late surrender against an Ace, dealer doesn't hit soft 17 79 | else if (dealerCard == 1) 80 | { 81 | // If it is a 9/7 single deck, we don't surrender 82 | if ((handValue.total == 16) && (options.numberOfDecks == 1) && ((playerCards[0] == 9) || (playerCards[0] == 7))) 83 | { 84 | shouldSurrender = false; 85 | } 86 | } 87 | // Late surrender against a non-Ace 88 | else 89 | { 90 | // 8/7 against 10 doesn't surrender 91 | if ((handValue.total == 15) && (dealerCard == 10) && ((playerCards[0] == 8) || (playerCards[0] == 7))) 92 | { 93 | shouldSurrender = false; 94 | } 95 | } 96 | 97 | return shouldSurrender; 98 | } 99 | 100 | }; 101 | 102 | /* 103 | * Internal functions 104 | */ 105 | 106 | function FindExceptionInList(playerCards, dealerCard, overrides) 107 | { 108 | // First create a sorted version of this list 109 | var sortedHand = playerCards.slice(0).sort((a, b) => (a-b)); 110 | var action = null; 111 | 112 | // Now you can compare 113 | for (var i = 0; i < overrides.length; i++) 114 | { 115 | if ((dealerCard == overrides[i].dealer) && (sortedHand.length == overrides[i].hand.length)) 116 | { 117 | for (var card = 0; card < sortedHand.length; card++) 118 | { 119 | if (sortedHand[card] != overrides[i].hand[card]) 120 | { 121 | // Not a match 122 | break; 123 | } 124 | } 125 | 126 | // If we got all the way through, it's a match! 127 | if (card == sortedHand.length) 128 | { 129 | // We did it! 130 | action = overrides[i].action; 131 | break; 132 | } 133 | } 134 | } 135 | 136 | return action; 137 | } 138 | 139 | // Taken from http://wizardofodds.com/games/blackjack/appendix/3b/ 140 | function TwoDeckStandSoft17(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, options) 141 | { 142 | var result; 143 | const overrides = [ 144 | {hand:[4,6,6], dealer: 10, action: "hit"}, 145 | {hand:[3,6,7], dealer: 10, action: "hit"}, 146 | {hand:[2,6,8], dealer: 10, action: "hit"}, 147 | {hand:[1,6,9], dealer: 10, action: "hit"}, 148 | {hand:[3,3,10], dealer: 10, action: "hit"}, 149 | {hand:[2,2,6,6], dealer: 10, action: "hit"}, 150 | {hand:[1,3,6,6], dealer: 10, action: "hit"}, 151 | {hand:[1,2,6,7], dealer: 10, action: "hit"}, 152 | {hand:[1,1,6,8], dealer: 10, action: "hit"}, 153 | {hand:[2,2,2,10], dealer: 10, action: "hit"}, 154 | {hand:[1,2,3,10], dealer: 10, action: "hit"}, 155 | {hand:[4,4,4,4], dealer: 9, action: "stand"}, 156 | {hand:[3,4,4,5], dealer: 9, action: "stand"}, 157 | {hand:[3,3,5,5], dealer: 9, action: "stand"}, 158 | {hand:[2,4,5,5], dealer: 9, action: "stand"}, 159 | {hand:[1,5,5,5], dealer: 9, action: "stand"}, 160 | {hand:[2,2,3,3,6], dealer: 10, action: "hit"}, 161 | {hand:[1,1,1,6,7], dealer: 10, action: "hit"}, 162 | {hand:[1,1,2,2,10], dealer: 10, action: "hit"}, 163 | {hand:[1,1,2,6,6], dealer: 10, action: "hit"}, 164 | {hand:[1,3,4,4,4], dealer: 9, action: "stand"}, 165 | {hand:[2,2,4,4,4], dealer: 9, action: "stand"}, 166 | {hand:[2,3,3,4,4], dealer: 9, action: "stand"}, 167 | {hand:[3,3,3,3,4], dealer: 9, action: "stand"}, 168 | {hand:[1,2,3,5,5], dealer: 9, action: "stand"}, 169 | {hand:[1,2,4,4,5], dealer: 9, action: "stand"}, 170 | {hand:[1,3,3,4,5], dealer: 9, action: "stand"}, 171 | {hand:[2,2,2,5,5], dealer: 9, action: "stand"}, 172 | {hand:[2,2,3,4,5], dealer: 9, action: "stand"}, 173 | {hand:[2,3,3,3,5], dealer: 9, action: "stand"}, 174 | {hand:[1,1,1,1,4,4], dealer: 3, action: "stand"}, 175 | {hand:[1,1,2,4,4,4], dealer: 7, action: "stand"}, 176 | {hand:[1,1,3,3,4,4], dealer: 7, action: "stand"}, 177 | {hand:[1,2,2,3,4,4], dealer: 7, action: "stand"}, 178 | {hand:[1,2,3,3,3,4], dealer: 7, action: "stand"}, 179 | {hand:[1,3,3,3,3,3], dealer: 7, action: "stand"}, 180 | {hand:[2,2,2,3,3,4], dealer: 7, action: "stand"}, 181 | {hand:[2,2,3,3,3,3], dealer: 7, action: "stand"}, 182 | {hand:[1,3,3,3,3,3], dealer: 8, action: "stand"}, 183 | {hand:[2,2,3,3,3,3], dealer: 8, action: "stand"}, 184 | {hand:[1,3,3,3,3,3], dealer: 9, action: "stand"}, 185 | {hand:[2,2,3,3,3,3], dealer: 9, action: "stand"}, 186 | {hand:[1,1,2,4,4,4], dealer: 9, action: "stand"}, 187 | {hand:[1,1,3,3,4,4], dealer: 9, action: "stand"}, 188 | {hand:[1,2,2,3,4,4], dealer: 9, action: "stand"}, 189 | {hand:[1,2,3,3,3,4], dealer: 9, action: "stand"}, 190 | {hand:[2,2,2,2,4,4], dealer: 9, action: "stand"}, 191 | {hand:[2,2,2,3,3,4], dealer: 9, action: "stand"}, 192 | {hand:[1,1,1,3,5,5], dealer: 9, action: "stand"}, 193 | {hand:[1,1,1,4,4,5], dealer: 9, action: "stand"}, 194 | {hand:[1,1,2,2,5,5], dealer: 9, action: "stand"}, 195 | {hand:[1,1,2,3,4,5], dealer: 9, action: "stand"}, 196 | {hand:[1,1,3,3,3,5], dealer: 9, action: "stand"}, 197 | {hand:[1,2,2,2,4,5], dealer: 9, action: "stand"}, 198 | {hand:[1,2,2,3,3,5], dealer: 9, action: "stand"}, 199 | {hand:[2,2,2,2,3,5], dealer: 9, action: "stand"}, 200 | {hand:[1,1,1,1,6,6], dealer: 10, action: "hit"}, 201 | {hand:[1,2,2,2,3,6], dealer: 10, action: "hit"}, 202 | {hand:[2,2,2,2,2,6], dealer: 10, action: "hit"}, 203 | ]; 204 | 205 | // Special case if there are two cards 206 | if (playerCards.length == 2) 207 | { 208 | if ((handValue.total == 11) && (dealerCard == 1) && 209 | ((playerCards[0] == 2) || (playerCards[0] == 3) || (playerCards[0] == 8) || (playerCards[0] == 9))) 210 | { 211 | return "double"; 212 | } 213 | else if ((handValue.total == 12) && (dealerCard == 4) && 214 | ((playerCards[0] == 2) || (playerCards[0] == 10))) 215 | { 216 | return "hit"; 217 | } 218 | } 219 | else // 3 or more cards 220 | { 221 | // Ace, Ace, 6 hits against dealer Ace 222 | if ((playerCards.length == 3) && (handValue.total == 18) && handValue.soft && (dealerCard == 1) && 223 | ((playerCards[0] == 6) || (playerCards[1] == 6) || (playerCards[2] == 6))) 224 | { 225 | return "hit"; 226 | } 227 | else if ((playerCards.length == 6) || (handValue.total == 16)) 228 | { 229 | result = FindExceptionInList(playerCards, dealerCard, overrides); 230 | if (result) 231 | { 232 | return result; 233 | } 234 | } 235 | 236 | // Three or more cards, 16 vs 10 stands and soft 18 vs Ace stands (except as noted in table which was already checked) 237 | if ((handValue.total == 16) && (dealerCard == 10)) 238 | { 239 | return "stand"; 240 | } 241 | if ((handValue.total == 18) && (handValue.soft) && (dealerCard == 1)) 242 | { 243 | return "stand"; 244 | } 245 | } 246 | 247 | return null; 248 | } 249 | 250 | 251 | -------------------------------------------------------------------------------- /src/Suggestion.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | 4 | * Copyright (c) 2016 Garrett Vargas 5 | 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const ec = require('../src/ExactComposition'); 26 | const easy = require('../src/EasyStrategy'); 27 | const BlackjackCalculation = require('./BlackjackCalculation'); 28 | 29 | module.exports = { 30 | // Recommended actions follow Basic Strategy, based on the rules currently in play 31 | GetRecommendedPlayerAction: function(playerCards, dealerCard, handCount, dealerCheckedBlackjack, options) 32 | { 33 | const playerOptions = ExtractOptions(options); 34 | var exactCompositionOverride = null; 35 | var countResult = null; 36 | var handValue = HandTotal(playerCards); 37 | 38 | // If early surrender is allowed, check that now (that's what early surrender means - before dealer checks for blackjack 39 | if ((playerOptions.surrender == "early") && (ShouldPlayerSurrender(playerCards, dealerCard, handValue, handCount, playerOptions))) 40 | { 41 | return "surrender"; 42 | } 43 | 44 | // OK, let's see if the count will change anything 45 | if (playerOptions.count.system == "HiLo") 46 | { 47 | countResult = AdjustPlayForHiLoCount(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, playerOptions); 48 | if (countResult) 49 | { 50 | return countResult; 51 | } 52 | } 53 | 54 | // OK, if an ace is showing it's easy - never take insurance 55 | if (((dealerCard == 1) && !dealerCheckedBlackjack && playerOptions.offerInsurance)) 56 | { 57 | return "noinsurance"; 58 | } 59 | 60 | if ((playerOptions.strategyComplexity == "easy")) 61 | { 62 | return easy.EasyBasicStrategy(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, playerOptions); 63 | } 64 | 65 | // blackjackcalculation.com basic strategies 66 | if ((playerOptions.strategyComplexity == "bjc-supereasy")) 67 | { 68 | return BlackjackCalculation.SuperEasyStrategy(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, playerOptions); 69 | } 70 | 71 | if ((playerOptions.strategyComplexity == "bjc-simple")) 72 | { 73 | return BlackjackCalculation.SimpleStrategy(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, playerOptions); 74 | } 75 | 76 | if ((playerOptions.strategyComplexity == "bjc-great")) 77 | { 78 | return BlackjackCalculation.GreatStrategy(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, playerOptions); 79 | } 80 | 81 | // OK, first, if there is an exact composition override use that 82 | exactCompositionOverride = ec.GetExactCompositionOverride(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, playerOptions); 83 | if (exactCompositionOverride) 84 | { 85 | return exactCompositionOverride; 86 | } 87 | 88 | // Check each situation 89 | if (ShouldPlayerSplit(playerCards, dealerCard, handValue, handCount, playerOptions)) 90 | { 91 | return "split"; 92 | } 93 | else if (ShouldPlayerDouble(playerCards, dealerCard, handValue, handCount, playerOptions)) 94 | { 95 | return "double"; 96 | } 97 | // Note if early surrender is allowed we already checked, so no need to check again 98 | else if ((playerOptions.surrender != "early") && ShouldPlayerSurrender(playerCards, dealerCard, handValue, handCount, playerOptions)) 99 | { 100 | return "surrender"; 101 | } 102 | else if (ShouldPlayerStand(playerCards, dealerCard, handValue, handCount, playerOptions)) 103 | { 104 | return "stand"; 105 | } 106 | else if (ShouldPlayerHit(playerCards, dealerCard, handValue, handCount, playerOptions)) 107 | { 108 | return "hit"; 109 | } 110 | 111 | // I got nothing 112 | return "none"; 113 | } 114 | }; 115 | 116 | /* 117 | * Internal functions 118 | */ 119 | 120 | function HandTotal(cards) 121 | { 122 | var retval = { total: 0, soft: false }; 123 | var hasAces = false; 124 | 125 | for (var i = 0; i < cards.length; i++) 126 | { 127 | retval.total += cards[i]; 128 | 129 | // Note if there's an ace 130 | if (cards[i] == 1) 131 | { 132 | hasAces = true; 133 | } 134 | } 135 | 136 | // If there are aces, add 10 to the total (unless it would go over 21) 137 | // Note that in this case the hand is soft 138 | if ((retval.total <= 11) && hasAces) 139 | { 140 | retval.total += 10; 141 | retval.soft = true; 142 | } 143 | 144 | return retval; 145 | } 146 | 147 | function ExtractOptions(options) 148 | { 149 | const playerOptions = { hitSoft17: true, surrender: "late", doubleRange:[0,21], doubleAfterSplit: true, 150 | resplitAces: false, offerInsurance: true, numberOfDecks: 6, maxSplitHands: 4, 151 | count: {system: null, trueCount: null}, strategyComplexity: "simple"}; 152 | 153 | // Override defaults where set 154 | if (options) 155 | { 156 | if (options.hasOwnProperty("hitSoft17")) 157 | { 158 | playerOptions.hitSoft17 = options.hitSoft17; 159 | } 160 | if (options.hasOwnProperty("surrender")) 161 | { 162 | playerOptions.surrender = options.surrender; 163 | } 164 | if (options.hasOwnProperty("doubleAfterSplit")) 165 | { 166 | playerOptions.doubleAfterSplit = options.doubleAfterSplit; 167 | } 168 | if (options.hasOwnProperty("resplitAces")) 169 | { 170 | playerOptions.resplitAces = options.resplitAces; 171 | } 172 | if (options.hasOwnProperty("offerInsurance")) 173 | { 174 | playerOptions.offerInsurance = options.offerInsurance; 175 | } 176 | if (options.hasOwnProperty("numberOfDecks")) 177 | { 178 | playerOptions.numberOfDecks = options.numberOfDecks; 179 | } 180 | if (options.hasOwnProperty("maxSplitHands")) 181 | { 182 | playerOptions.maxSplitHands = options.maxSplitHands; 183 | } 184 | if (options.hasOwnProperty("count")) 185 | { 186 | playerOptions.count = options.count; 187 | } 188 | if (options.hasOwnProperty("strategyComplexity")) 189 | { 190 | // Note that prior to version 1.1.2, "basic" was used instead of "simple" 191 | playerOptions.strategyComplexity = options.strategyComplexity; 192 | if (playerOptions.strategyComplexity == "basic") 193 | { 194 | playerOptions.strategyComplexity = "simple"; 195 | } 196 | } 197 | 198 | // Double rules - make sure doubleRange is set as that is all we use here 199 | if (options.hasOwnProperty("doubleRange")) 200 | { 201 | playerOptions.doubleRange = options.doubleRange; 202 | } 203 | else if (options.hasOwnProperty("double")) 204 | { 205 | // Translate to doubleRange 206 | if (options.double == "none") 207 | { 208 | playerOptions.doubleRange[0] = 0; 209 | playerOptions.doubleRange[1] = 0; 210 | } 211 | else if (options.double === "10or11") 212 | { 213 | playerOptions.doubleRange[0] = 10; 214 | playerOptions.doubleRange[1] = 11; 215 | } 216 | else if (options.double === "9or10or11") 217 | { 218 | playerOptions.doubleRange[0] = 9; 219 | playerOptions.doubleRange[1] = 11; 220 | } 221 | } 222 | } 223 | 224 | return playerOptions; 225 | } 226 | 227 | // 228 | // Split strategy 229 | // 230 | 231 | function ShouldPlayerSplit(playerCards, dealerCard, handValue, handCount, options) 232 | { 233 | var shouldSplit = false; 234 | 235 | // It needs to be a possible action 236 | if ((playerCards.length != 2) || (handCount == options.maxSplitHands) || (playerCards[0] != playerCards[1])) 237 | { 238 | return false; 239 | } 240 | 241 | // OK, it's a possibility 242 | switch (playerCards[0]) 243 | { 244 | case 1: 245 | // Always split aces 246 | shouldSplit = true; 247 | break; 248 | case 2: 249 | // Against 4-7, or 2 and 3 if you can double after split; also against a dealer 3 in single deck 250 | shouldSplit = ((dealerCard > 3) && (dealerCard < 8)) || (((dealerCard == 2) || (dealerCard == 3)) && (options.doubleAfterSplit)); 251 | if ((dealerCard == 3) && (options.numberOfDecks == 1)) 252 | { 253 | shouldSplit = true; 254 | } 255 | break; 256 | case 3: 257 | // Against 4-7, or 2 and 3 if you can double after split 258 | // Also in single deck against an 8 if you can double after split 259 | shouldSplit = ((dealerCard > 3) && (dealerCard < 8)) || (((dealerCard == 2) || (dealerCard == 3)) && (options.doubleAfterSplit)); 260 | if ((dealerCard == 8) && (options.numberOfDecks == 1) && (options.doubleAfterSplit)) 261 | { 262 | shouldSplit = true; 263 | } 264 | break; 265 | case 4: 266 | // Against 5 or 6, and only if you can double after split 267 | // Or against a 4 in single deck, if you can double after split 268 | shouldSplit = ((dealerCard == 5) || (dealerCard == 6) || ((dealerCard == 4) && (options.numberOfDecks == 1))) && (options.doubleAfterSplit); 269 | break; 270 | case 6: 271 | // Split 3-6, or against a 2 if double after split is allowed (for four or more decks, always for one or two decks) 272 | // Or in single or double deck, against a 7 if double after split is allowed 273 | shouldSplit = ((dealerCard > 2) && (dealerCard < 7)) || 274 | ((dealerCard == 2) && (((options.numberOfDecks >= 4) && options.doubleAfterSplit) || (options.numberOfDecks <= 2))); 275 | if ((options.numberOfDecks <= 2) && (dealerCard == 7) && options.doubleAfterSplit) 276 | { 277 | shouldSplit = true; 278 | } 279 | break; 280 | case 7: 281 | // Split on 2-7, and on single or double deck split against an 8 only if you can double after split 282 | shouldSplit = ((dealerCard > 1) && (dealerCard < 8)); 283 | if ((dealerCard == 8) && (options.numberOfDecks <= 2)) 284 | { 285 | shouldSplit = options.doubleAfterSplit; 286 | } 287 | break; 288 | case 8: 289 | // The simple rule is always split 8s - there are exceptions where we will surrender instead, which are covered in 290 | // the case where the player should surrender. Check if they should surrender, if not they should split 291 | shouldSplit = !ShouldPlayerSurrender(playerCards, dealerCard, handValue, handCount, options); 292 | break; 293 | case 9: 294 | // Split against 2-9 except 7 295 | // An advanced exception - 9s should split against an ace in single deck if dealer has an ace and you can double after split 296 | // and the dealer hits soft 17 (that's a mouthful!) 297 | shouldSplit = ((dealerCard > 1) && (dealerCard < 10) && (dealerCard != 7)); 298 | if ((options.strategyComplexity != "simple") && (dealerCard == 1) && (options.numberOfDecks == 1) && (options.doubleAfterSplit) && options.hitSoft17) 299 | { 300 | shouldSplit = true; 301 | } 302 | break; 303 | case 5: 304 | case 10: 305 | default: 306 | // Don't split 5s or 10s ... or cards I don't know 307 | break; 308 | } 309 | 310 | return shouldSplit; 311 | } 312 | 313 | // 314 | // Double strategy 315 | // 316 | 317 | function ShouldPlayerDouble(playerCards, dealerCard, handValue, handCount, options) 318 | { 319 | var shouldDouble = false; 320 | 321 | // It needs to be a possible action 322 | if ((playerCards.length != 2) || ((handCount > 1) && !options.doubleAfterSplit)) 323 | { 324 | return false; 325 | } 326 | if ((handValue.total < options.doubleRange[0]) || (handValue.total > options.doubleRange[1])) 327 | { 328 | return false; 329 | } 330 | 331 | // OK, looks like you can double 332 | if (handValue.soft) 333 | { 334 | // Let's look at the non-ace card to determine what to do (get this by the total) 335 | switch (handValue.total) 336 | { 337 | case 13: 338 | case 14: 339 | // Double against dealer 5 or 6; on single deck you should also double against 4 340 | // On double deck a soft 14 should double against 4 if the dealer hits soft 17 341 | shouldDouble = (dealerCard == 5) || (dealerCard == 6); 342 | if ((dealerCard == 4) && ((options.numberOfDecks == 1) || ((options.numberOfDecks == 2) && (handValue.total == 14) && options.hitSoft17))) 343 | { 344 | shouldDouble = true; 345 | } 346 | break; 347 | case 15: 348 | case 16: 349 | // Double against dealer 4-6 350 | shouldDouble = (dealerCard >= 4) && (dealerCard <= 6); 351 | break; 352 | case 17: 353 | // Double against 3-6, and against 2 in single deck 354 | shouldDouble = ((dealerCard >= 3) && (dealerCard <= 6)) || ((dealerCard == 2) && (options.numberOfDecks == 1)); 355 | break; 356 | case 18: 357 | // Double against 3-6 - also 2 if the dealer hits soft 17 and there is more than one deck 358 | shouldDouble = ((dealerCard >= 3) && (dealerCard <= 6)) || ((dealerCard == 2) && options.hitSoft17 && (options.numberOfDecks > 1)); 359 | break; 360 | case 19: 361 | // Double against 6 if the dealer hits soft 17, or always in single deck 362 | shouldDouble = (dealerCard == 6) && (options.hitSoft17 || (options.numberOfDecks == 1)); 363 | break; 364 | default: 365 | // Don't double 366 | break; 367 | } 368 | } 369 | else 370 | { 371 | // Double on 9, 10, or 11 only (8 in single deck) 372 | switch (handValue.total) 373 | { 374 | case 8: 375 | // Double 5-6 in single deck 376 | shouldDouble = ((dealerCard >= 5) && (dealerCard <= 6) && (options.numberOfDecks == 1)); 377 | break; 378 | case 9: 379 | // Double 3-6, and 2 in single or double deck 380 | shouldDouble = ((dealerCard >= 3) && (dealerCard <= 6)) || ((dealerCard == 2) && (options.numberOfDecks <= 2)); 381 | break; 382 | case 10: 383 | // Double 2-9 384 | shouldDouble = (dealerCard >= 2) && (dealerCard <= 9); 385 | break; 386 | case 11: 387 | // Double anything except an ace (and then only if the dealer doesn't hit soft 17 or there are more than two decks) 388 | shouldDouble = !((dealerCard == 1) && !options.hitSoft17 && (options.numberOfDecks > 2)); 389 | break; 390 | default: 391 | break; 392 | } 393 | } 394 | 395 | return shouldDouble; 396 | } 397 | 398 | // 399 | // Surrender strategy 400 | // 401 | // This is fairly complex and handles different strategies for early or late surrender 402 | // It also looks at various combinations of player hands for more advanced strategy complexities 403 | // 404 | 405 | function ShouldPlayerSurrender(playerCards, dealerCard, handValue, handCount, options) 406 | { 407 | var shouldSurrender = false; 408 | 409 | // You can only surrender on your first two cards, and it has to be an option 410 | if (((options.surrender != "early") && (options.surrender != "late")) || (playerCards.length != 2) || (handCount != 1)) 411 | { 412 | return false; 413 | } 414 | 415 | // See if there is a special suggestion based on the exact composition of the player's hand 416 | var exactCompositionSurrender = ec.GetSurrenderOverride(playerCards, dealerCard, handValue, handCount, options); 417 | if (exactCompositionSurrender != null) 418 | { 419 | return exactCompositionSurrender; 420 | } 421 | 422 | // Don't surrender a soft hand 423 | if (handValue.soft) 424 | { 425 | return false; 426 | } 427 | 428 | if (options.surrender == "early") 429 | { 430 | if (dealerCard == 1) 431 | { 432 | // Surrender Dealer Ace vs. hard 5-7, hard 12-17, including pair of 3's, 6's, 7's or 8's 433 | // Also surender against pair of 2's if the dealer hits soft 17 434 | if (((handValue.total >= 5) && (handValue.total <= 7)) || ((handValue.total >= 12) && (handValue.total <= 17))) 435 | { 436 | shouldSurrender = true; 437 | } 438 | if ((playerCards[0] == 2) && (playerCards[1] == 2) && options.hitSoft17) 439 | { 440 | shouldSurrender = true; 441 | } 442 | } 443 | else if (dealerCard == 10) 444 | { 445 | // Surrender dealer 10 against a hard 14-16, including pair of 7's or 8's 446 | if ((handValue.total >= 14) && (handValue.total <= 16)) 447 | { 448 | // UNLESS it's a pair of 8's in single deck and double after split is allowed 449 | // This is an advanced option (for simple, we will "always split 8s") 450 | if ((playerCards[0] == 8) && (playerCards[1] == 8)) 451 | { 452 | shouldSurrender = (options.strategyComplexity != "simple") && (options.numberOfDecks == 1) && (options.doubleAfterSplit); 453 | } 454 | else 455 | { 456 | shouldSurrender = true; 457 | } 458 | } 459 | } 460 | else if (dealerCard == 9) 461 | { 462 | // Surrender if we have 16, but no including a pair of 8's 463 | if ((handValue.total == 16) && (playerCards[0] != 8)) 464 | { 465 | shouldSurrender = true; 466 | } 467 | } 468 | } 469 | // Late surrender against an Ace when dealer hits on soft 17 470 | else if ((options.hitSoft17) && (dealerCard == 1)) 471 | { 472 | switch (handValue.total) 473 | { 474 | case 14: 475 | // If looking at advanced, then surrender a pair of 7s in a single deck game only 476 | shouldSurrender = ((options.strategyComplexity != "simple") && (options.numberOfDecks == 1) && (playerCards[0] == 7)); 477 | break; 478 | case 15: 479 | // Surrender 480 | shouldSurrender = true; 481 | break; 482 | case 16: 483 | // Surrender unless it's a pair of 8s in a single deck game or a double deck game with no double after split 484 | // This is an advanced option (simple is "always split 8s") 485 | shouldSurrender = (playerCards[0] != 8); 486 | if ((options.strategyComplexity != "simple") && (playerCards[0] == 8)) 487 | { 488 | // Surrender pair of 8s if four or more decks or in a double-deck game where double after split isn't allowed 489 | if ((options.numberOfDecks >= 4) || ((options.numberOfDecks == 2) && !options.doubleAfterSplit)) 490 | { 491 | shouldSurrender = true; 492 | } 493 | } 494 | break; 495 | case 17: 496 | // Surrender (that's new by me) 497 | shouldSurrender = true; 498 | break; 499 | default: 500 | // Don't surender 501 | break; 502 | } 503 | } 504 | // Late surrender against an Ace, dealer doesn't hit soft 17 505 | else if (dealerCard == 1) 506 | { 507 | // We only surrender a 16 in this case (not a pair of 8s). 508 | shouldSurrender = (handValue.total == 16) && (playerCards[0] != 8); 509 | } 510 | // Late surrender against a non-Ace 511 | else 512 | { 513 | // The simple rule is 15 against 10 if more than one deck, and 16 (non-8s) against 10 always or against 9 if 4 or more decks 514 | // If looking at advanced, 7s surrender against 10 in single deck 515 | if (handValue.total == 14) 516 | { 517 | shouldSurrender = ((options.strategyComplexity != "simple") && (playerCards[0] == 7) && (dealerCard == 10) && (options.numberOfDecks == 1)); 518 | } 519 | else if (handValue.total == 15) 520 | { 521 | // Surrender against 10 unless it's a single deck game 522 | shouldSurrender = ((dealerCard == 10) && (options.numberOfDecks > 1)); 523 | } 524 | else if (handValue.total == 16) 525 | { 526 | // Surrender against 10 or Ace, and against 9 if there are more than 4 decks 527 | shouldSurrender = (playerCards[0] != 8) && ((dealerCard == 10) || ((dealerCard == 9) && (options.numberOfDecks >= 4))); 528 | } 529 | } 530 | 531 | return shouldSurrender; 532 | } 533 | 534 | // 535 | // Stand Strategy 536 | // 537 | // Note that we've already checkd other actions such as double, split, and surrender 538 | // So at this point we're only assessing whether you should stand as opposed to hitting 539 | // 540 | 541 | function ShouldPlayerStand(playerCards, dealerCard, handValue, handCount, options) 542 | { 543 | var shouldStand = false; 544 | 545 | if (handValue.soft) 546 | { 547 | // Don't stand until you hit 18 548 | if (handValue.total > 18) 549 | { 550 | shouldStand = true; 551 | } 552 | else if (handValue.total == 18) 553 | { 554 | // Stand against dealer 2-8, and against a dealer Ace in single deck if they dealer will stand on soft 17 555 | shouldStand = ((dealerCard >= 2) && (dealerCard <= 8)) || 556 | ((dealerCard == 1) && (options.numberOfDecks == 1) && !options.hitSoft17); 557 | } 558 | } 559 | else 560 | { 561 | // Stand on 17 or above 562 | if (handValue.total > 16) 563 | { 564 | shouldStand = true; 565 | } 566 | else if (handValue.total > 12) 567 | { 568 | // 13-16 you should stand against dealer 2-6 569 | shouldStand = (dealerCard >= 2) && (dealerCard <= 6); 570 | } 571 | else if (handValue.total == 12) 572 | { 573 | // Stand on dealer 4-6 574 | shouldStand = (dealerCard >= 4) && (dealerCard <= 6); 575 | } 576 | 577 | // Advanced option - in single deck a pair of 7s should stand against a dealer 10 578 | if ((options.strategyComplexity != "simple") && (handValue.total == 14) && (playerCards[0] == 7) && (dealerCard == 10) && (options.numberOfDecks == 1)) 579 | { 580 | shouldStand = true; 581 | } 582 | } 583 | 584 | return shouldStand; 585 | } 586 | 587 | // 588 | // Hit strategy 589 | // 590 | // Note this is the last action we check (we told them not to do anything else), so by default you should hit 591 | // Since we don't have the full game state, it's assumed that the caller made sure not to call if the player 592 | // took an action where the player has no choice of play (e.g. doubled or split aces) 593 | // 594 | 595 | function ShouldPlayerHit(playerCards, dealerCard, handValue, handCount, options) 596 | { 597 | // The only sanity check we'll do is that you haven't already busted 598 | return (handValue.total < 21); 599 | } 600 | 601 | // 602 | // Hi-Lo strategy 603 | // 604 | // Adjussts the recommendation based on the Hi-Lo count as defined at 605 | // http://wizardofodds.com/games/blackjack/card-counting/high-low/ 606 | // We re 607 | // 608 | function AdjustPlayForHiLoCount(playerCards, dealerCard, handValue, handCount, dealerCheckedBlackjack, options) 609 | { 610 | var canSplit = (playerCards[0] == playerCards[1]) && (playerCards.length == 2) && (handCount < options.maxSplitHands); 611 | var canDouble = ((playerCards.length == 2) && ((handCount == 1) || options.doubleAfterSplit)) && 612 | ((handValue.total < options.doubleRange[0]) || (handValue.total > options.doubleRange[1])); 613 | var canSurrender = ((options.surrender == "early") || (options.surrender == "late")) && (playerCards.length == 2) && (handCount == 1); 614 | var surrenderMatrix = [ 615 | {player: 14, dealer: 10, count: 3}, 616 | {player: 15, dealer: 10, count: 0}, 617 | {player: 15, dealer: 9, count: 2}, 618 | {player: 15, dealer: 1, count: 1} 619 | ]; 620 | 621 | // We may take insurance! 622 | if ((dealerCard == 1) && !dealerCheckedBlackjack && options.offerInsurance && (options.count.trueCount >= 3)) 623 | { 624 | return "insurance"; 625 | } 626 | else if ((handValue.total == 16) && (dealerCard == 10) && (options.count.trueCount > 0)) 627 | { 628 | return "stand"; 629 | } 630 | else if ((handValue.total == 15) && (dealerCard == 10) && (options.count.trueCount > 4)) 631 | { 632 | return "stand"; 633 | } 634 | else if (canSplit && (handValue.total == 20)) 635 | { 636 | if ((dealerCard == 5) && (options.count.trueCount >= 5)) 637 | { 638 | return "split"; 639 | } 640 | else if ((dealerCard == 6) && (options.count.trueCount >= 4)) 641 | { 642 | return "split"; 643 | } 644 | } 645 | else if (canDouble && (handValue.total == 10) && (dealerCard == 10) && (options.count.trueCount >= 4)) 646 | { 647 | return "double"; 648 | } 649 | else if ((handValue.total == 12) && (dealerCard == 3) && (options.count.trueCount >= 2)) 650 | { 651 | return "stand"; 652 | } 653 | else if ((handValue.total == 12) && (dealerCard == 2) && (options.count.trueCount >= 3)) 654 | { 655 | return "stand"; 656 | } 657 | else if (canDouble && (handValue.total == 11) && (dealerCard == 1) && (options.count.trueCount >= 1)) 658 | { 659 | return "double"; 660 | } 661 | else if (canDouble && (handValue.total == 9) && (dealerCard == 2) && (options.count.trueCount >= 1)) 662 | { 663 | return "double"; 664 | } 665 | else if (canDouble && (handValue.total == 10) && (dealerCard == 1) && (options.count.trueCount >= 4)) 666 | { 667 | return "double"; 668 | } 669 | else if (canDouble && (handValue.total == 9) && (dealerCard == 7) && (options.count.trueCount >= 3)) 670 | { 671 | return "double"; 672 | } 673 | else if ((handValue.total == 16) && (dealerCard == 9) && (options.count.trueCount >= 5)) 674 | { 675 | return "stand"; 676 | } 677 | else if ((handValue.total == 13) && (dealerCard == 2) && (options.count.trueCount < -1)) 678 | { 679 | return "hit"; 680 | } 681 | else if ((handValue.total == 12) && (dealerCard == 4) && (options.count.trueCount < 0)) 682 | { 683 | return "hit"; 684 | } 685 | else if ((handValue.total == 12) && (dealerCard == 5) && (options.count.trueCount < -2)) 686 | { 687 | return "hit"; 688 | } 689 | else if ((handValue.total == 12) && (dealerCard == 6) && (options.count.trueCount < -1)) 690 | { 691 | return "hit"; 692 | } 693 | else if ((handValue.total == 13) && (dealerCard == 3) && (options.count.trueCount < -2)) 694 | { 695 | return "hit"; 696 | } 697 | else if (canSurrender) 698 | { 699 | for (var i = 0; i < surrenderMatrix.length; i++) 700 | { 701 | if ((handValue.total == surrenderMatrix[i].player) && (dealerCard == surrenderMatrix[i].dealer) 702 | && (options.count.trueCount >= surrenderMatrix[i].count)) 703 | { 704 | return "surrender"; 705 | } 706 | } 707 | } 708 | 709 | // Nope, no adjustment 710 | return null; 711 | } --------------------------------------------------------------------------------