public void SimpleStatement() { var ab = new ActionBucket { bar = "~`!1@2#3$4%5^6&7*8(9)0_-+={[}]:;\"\"<,<.?//*-+.a string pointed to by bar" }; Func<Int64, Int64, Int64, Int64, ActionBucket> f = (a, b, c, d) => ab; var s = new TemplateDictionary { {"foo", f} }; const string text = "$foo(1L,2L,3L,4L).func( 'a string', 0L )[1L,2L][1L,2L].func( 'adfasdfad', 92321312L )[123123L,123123L].bar"; var result1 = Template.ImmediateApply(s, text); Assert.IsFalse(result1.Errors.ContainsError()); Assert.IsFalse(result1.Errors.ContainsWarning()); Assert.AreEqual(ab.bar, result1.Output); var result2 = Template.CompileAndRun("test", text, s); Assert.IsFalse(result2.Errors.ContainsError()); Assert.IsFalse(result2.Errors.ContainsWarning()); Assert.AreEqual(ab.bar, result2.Output); }
/// <summary> /// Maps the action bucket to a real action /// the amount to call determines whether the real action will be check, respectively fold or bet, respectively raise. /// </summary> /// <param name="action">the abstracted action</param> /// <param name="amountToCall">current amount to call</param> /// <returns>real action</returns> public static ActionType MapToAction(ActionBucket action, int amountToCall) { switch (action) { case ActionBucket.Call: return(ActionType.Call); case ActionBucket.Pass: if (amountToCall == 0) { return(ActionType.Check); } else { return(ActionType.Fold); } case ActionBucket.LowBet: case ActionBucket.HighBet: case ActionBucket.MediumBet: if (amountToCall == 0) { return(ActionType.Bet); } else { return(ActionType.Raise); } } return(ActionType.Illegal); }
private Timer() { for (var index = 0; index < BucketCount; ++index) { _buckets[index] = new ActionBucket(); } }
private float getProbability(ActionBucket bucket, List <float> optimalStrategy) { bool isCallActionEnabled = (optimalStrategy.Count == 5); int passIndex = 0; int callIndex = 1; int lowBetIndex = 2; int mediumBetIndex = 3; int highBetIndex = 4; if (!isCallActionEnabled) { lowBetIndex = 1; mediumBetIndex = 2; highBetIndex = 3; } float passProbability = optimalStrategy[passIndex]; float callProbability = optimalStrategy[callIndex]; float betProbability = optimalStrategy[lowBetIndex] + optimalStrategy[mediumBetIndex] + optimalStrategy[highBetIndex]; switch (bucket) { case ActionBucket.Pass: return(optimalStrategy[passIndex]); case ActionBucket.Call: if (isCallActionEnabled) { return(optimalStrategy[callIndex]); } else { return(0); } case ActionBucket.HighBet: return(optimalStrategy[highBetIndex]); case ActionBucket.LowBet: return(optimalStrategy[lowBetIndex]); case ActionBucket.MediumBet: return(optimalStrategy[mediumBetIndex]); default: throw new NotImplementedException("ActionBucket not implemented!"); } }
/// <summary> /// Maps the action bucket to an even more abstracted feature action /// </summary> /// <param name="actionBucket"></param> /// <returns></returns> public static FeatureAction FromActionBucket(ActionBucket actionBucket) { switch (actionBucket) { case ActionBucket.Call: return(FeatureAction.Call); case ActionBucket.LowBet: case ActionBucket.MediumBet: case ActionBucket.HighBet: return(FeatureAction.Bet); case ActionBucket.Pass: return(FeatureAction.Pass); default: throw new NotImplementedException("Action bucket not supported"); } }
public override Task <GameActionEntity> GetAction(List <ActionType> possibleActions, int amountToCall) { var infoSet = new InformationSet <ActionBucket>(); infoSet.ActionHistory = actionHistory; infoSet.CardBucket = handBucket; RegretGameNode <ActionBucket> gameNode; trainedTree.TryGetValue(infoSet.GetLongHashCode(), out gameNode); if (gameNode == null) { // this should never occur randomBot.ChipStack = this.ChipStack; return(randomBot.GetAction(possibleActions, amountToCall)); } else { var optimalStrategy = gameNode.calculateAverageStrategy(); var rand = new Random(); double randomValue = rand.NextDouble(); bool isCallActionEnabled = (optimalStrategy.Count == 5); int index = 0; double sumPercent = 0; ActionBucket selectedActionBucket = ActionBucket.None; foreach (ActionBucket action in Enum.GetValues(typeof(ActionBucket))) { if (action == ActionBucket.None) { continue; } if (action == ActionBucket.Call && !isCallActionEnabled) { continue; } double newSumPercent = sumPercent + optimalStrategy[index]; if (randomValue >= sumPercent && randomValue <= newSumPercent) { selectedActionBucket = action; break; } sumPercent = newSumPercent; index++; } ActionType selectedAction = ActionAbstracter.MapToAction(selectedActionBucket, amountToCall); int betSize = 0; switch (selectedAction) { case ActionType.Bet: case ActionType.Raise: betSize = ActionAbstracter.GetBetSize(selectedActionBucket, amountToCall, currentGame.PotSize); break; case ActionType.Call: betSize = amountToCall; break; } if (!possibleActions.Contains(selectedAction)) { // in all-in scenarios actions from bot may differ from possible actions switch (selectedAction) { case ActionType.Bet: case ActionType.Raise: case ActionType.Check: if (possibleActions.Contains(ActionType.Call)) { selectedAction = ActionType.Call; } break; default: throw new Exception("Selected action is illegal!"); } } if (ChipStack < betSize) { betSize = this.ChipStack; } return(Task.FromResult <GameActionEntity>(new GameActionEntity { ActionType = selectedAction, Amount = betSize, PlayerId = this.Id })); } }
public override Task <GameActionEntity> GetAction(List <ActionType> possibleActions, int amountToCall) { var possibleFeatureActions = new List <FeatureAction>(); foreach (var possibleAction in possibleActions) { var featureAction = FeatureActionAbstracter.FromActionType(possibleAction); if (!possibleFeatureActions.Contains(featureAction)) { possibleFeatureActions.Add(featureAction); } } var currentPhasehistory = actionHistoriesPerPhase.FirstOrDefault(x => x.Phase == currentGame.Phase); Positioning opponentPositioning = Positioning.None; Aggression opponentAggression = Aggression.None; if (currentGame.Phase > GamePhase.PreFlop) { opponentPositioning = getOpponentPositioning(currentPhasehistory.ActionHistory.Count); opponentAggression = getOpponentAggression(opponentPositioning); } var counterAction = opponent.GetCounterAction(possibleFeatureActions, currentPhasehistory.ActionHistory, currentGame.Phase, opponentAggression, opponentPositioning); ActionBucket selectedActionBucket = ActionBucket.None; if (counterAction == null) { if (currentGame.Phase == GamePhase.PreFlop) { switch (opponent.PlayStyle) { case PlayStyle.LooseAggressive: case PlayStyle.LoosePassive: if (this.IsBigBlind) { selectedActionBucket = ActionBucket.LowBet; } else { if (startHandBucket >= StartHandBucket.VeryBad) { selectedActionBucket = ActionBucket.LowBet; } else { selectedActionBucket = ActionBucket.Pass; } } break; case PlayStyle.TightAggressive: case PlayStyle.TightPassive: //play hyper aggressive selectedActionBucket = ActionBucket.LowBet; break; } } else { switch (opponent.PlayStyle) { case PlayStyle.LooseAggressive: //play TightAggressive if (handStrengthBucket >= HandStrengthBucket.HighCardAce) { selectedActionBucket = ActionBucket.LowBet; } else { selectedActionBucket = ActionBucket.Pass; } break; case PlayStyle.LoosePassive: selectedActionBucket = ActionBucket.LowBet; break; case PlayStyle.TightAggressive: //play LooseAggressive if (handStrengthBucket >= HandStrengthBucket.LowPair) { selectedActionBucket = ActionBucket.LowBet; } else { selectedActionBucket = ActionBucket.Pass; } break; case PlayStyle.TightPassive: //play LooseAggressive if (handStrengthBucket >= HandStrengthBucket.HighCardElse) { selectedActionBucket = ActionBucket.LowBet; } else { selectedActionBucket = ActionBucket.Pass; } break; } } } else { if (currentGame.Phase == GamePhase.PreFlop) { switch (counterAction) { case FeatureAction.Bet: if (startHandBucket > StartHandBucket.Worst) { selectedActionBucket = ActionBucket.LowBet; } break; case FeatureAction.Pass: if (startHandBucket < StartHandBucket.Bad) { selectedActionBucket = ActionBucket.Pass; } break; } } else { switch (counterAction) { case FeatureAction.Bet: selectedActionBucket = ActionBucket.LowBet; break; case FeatureAction.Pass: if (handStrengthBucket < HandStrengthBucket.LowPair) { selectedActionBucket = ActionBucket.Pass; } break; } } } switch (selectedActionBucket) { case ActionBucket.None: //no counter move has been found. Fall back to randomness randomBot.ChipStack = this.ChipStack; return(randomBot.GetAction(possibleActions, amountToCall)); default: ActionType selectedAction = ActionAbstracter.MapToAction(selectedActionBucket, amountToCall); int betSize = 0; switch (selectedAction) { case ActionType.Bet: case ActionType.Raise: betSize = ActionAbstracter.GetBetSize(selectedActionBucket, amountToCall, currentGame.PotSize); break; case ActionType.Call: betSize = amountToCall; break; } if (!possibleActions.Contains(selectedAction)) { switch (selectedAction) { case ActionType.Bet: case ActionType.Raise: case ActionType.Check: if (possibleActions.Contains(ActionType.Call)) { selectedAction = ActionType.Call; } break; default: throw new Exception("Selected action is illegal!"); } } if (ChipStack < betSize) { betSize = this.ChipStack; } return(Task.FromResult <GameActionEntity>( new GameActionEntity { ActionType = selectedAction, Amount = betSize, PlayerId = this.Id })); } }
/// <summary> /// Minimise exploitability by mapping the bucket based on the distance to the nearest neighbours /// </summary> /// <param name="amount"></param> /// <param name="low"></param> /// <param name="high"></param> /// <param name="lowBucket"></param> /// <param name="highBucket"></param> /// <returns>action bucket</returns> private static ActionBucket getBucketByDistanceProbability(int amount, int low, int high, ActionBucket lowBucket, ActionBucket highBucket) { var random = new Random(); int distanceAmountToLow = amount - low; int distanceLowToHigh = high - low; decimal probabilityHigh = (decimal)(distanceAmountToLow / distanceLowToHigh) * 100; int probabilityHighPercentage = (int)Math.Round(probabilityHigh); int randomValue = random.Next(1, 100); if (randomValue <= probabilityHighPercentage) { return(highBucket); } else { return(lowBucket); } }
/// <summary> /// Maps the action bucket based on the current amount to call and the pot size to a suitable bet size. /// </summary> /// <param name="action">abstracted action</param> /// <param name="amountToCall">current amount to call (if this is zero, the player is betting, otherwise the player is raising)</param> /// <param name="potSize">current pot size</param> /// <returns>bet size</returns> public static int GetBetSize(ActionBucket action, int amountToCall, int potSize) { int currentMoneyInPot = (potSize - amountToCall) / 2; int betSize = 0; if (amountToCall == 0) { switch (action) { case ActionBucket.HighBet: betSize = HeadsupGame.StackSize - currentMoneyInPot; break; case ActionBucket.MediumBet: betSize = potSize; break; case ActionBucket.LowBet: betSize = potSize / 2; break; } if (betSize + currentMoneyInPot > HeadsupGame.StackSize) { betSize = HeadsupGame.StackSize - currentMoneyInPot; } } else { int betSizeFactor = 0; if (amountToCall == HeadsupGame.SmallBlindSize) { betSizeFactor = HeadsupGame.BigBlindSize; } else { betSizeFactor = amountToCall; } switch (action) { case ActionBucket.HighBet: betSize = HeadsupGame.StackSize - currentMoneyInPot; break; case ActionBucket.MediumBet: betSize = betSizeFactor * 3; break; case ActionBucket.LowBet: betSize = betSizeFactor * 2; break; } } if (potSize + betSize > HeadsupGame.StackSize * 2) { int opponentMoneyInPot = potSize - currentMoneyInPot; int maxAmountOpponentCanCall = HeadsupGame.StackSize - opponentMoneyInPot; if (maxAmountOpponentCanCall > 0) { betSize = amountToCall + maxAmountOpponentCanCall; } else { betSize = (HeadsupGame.StackSize * 2 - potSize); } if (betSize < 0) { betSize = 0; } } return(betSize); }
private Timer() { for (var index = 0; index < BucketCount; ++index) _buckets[index] = new ActionBucket(); }
/// <summary> /// Traverses the sub tree of the passed hand buckets recursively. Calculates and then backpropagates the counter factual regret for each node. /// </summary> /// <param name="gameState"></param> /// <param name="handBuckets"></param> /// <param name="actions"></param> /// <param name="probabilityPlayer1">probability of player 1 to reach this node</param> /// <param name="probabilityPlayer2">probability of player 2 to reach this node</param> /// <returns></returns> private float CalculateCounterFactualRegret(HeadsUpGameState gameState, byte[] handBuckets, List <ActionBucket> actions, float probabilityPlayer1, float probabilityPlayer2) { int plays = actions.Count; int playerIndex = plays % 2; var newState = gameState.GetCopy(); ActionBucket lastAction = ActionBucket.None; if (actions.Count > 0) { lastAction = actions[plays - 1]; } ActionBucket secondLastAction = ActionBucket.None; if (actions.Count > 1) { secondLastAction = actions[plays - 2]; } bool nextActionCallEnabled = true; bool phaseChanged = false; //the last actions determine whether the next phase has to be set or whether the game (i.e. the recursion) ends switch (lastAction) { case ActionBucket.Pass: if (secondLastAction == ActionBucket.LowBet || secondLastAction == ActionBucket.HighBet || secondLastAction == ActionBucket.MediumBet) { int payoff = (newState.PotSize - newState.AmountToCall) / 2; return((playerIndex == 0) ? payoff : -payoff); } switch (secondLastAction) { case ActionBucket.None: return((playerIndex == 0) ? HeadsupGame.SmallBlindSize : -HeadsupGame.SmallBlindSize); case ActionBucket.Call: newState.SetNextPhase(newState.Phase); phaseChanged = true; break; case ActionBucket.Pass: //check if last pass count is dividable by 2 (then it's a new phase) int lastActionPassCount = 0; for (int i = actions.Count - 1; i >= 0; i--) { if (actions[i] == ActionBucket.Pass) { lastActionPassCount++; } else { //special case: if it's first round, call pass results in ending the round if ((actions[i] == ActionBucket.Call && i == 0) && actions.Count > 2 && actions[i + 1] == ActionBucket.Pass) { lastActionPassCount--; } break; } } if (lastActionPassCount % 2 == 0) { newState.SetNextPhase(newState.Phase); phaseChanged = true; } break; } nextActionCallEnabled = false; break; case ActionBucket.Call: newState.PotSize += newState.AmountToCall; newState.AmountToCall = 0; //special case for first round: big blind needs to check or bet if (actions.Count == 1) { nextActionCallEnabled = false; } else { newState.SetNextPhase(newState.Phase); phaseChanged = true; } break; case ActionBucket.HighBet: case ActionBucket.MediumBet: case ActionBucket.LowBet: int betSize = 0; if (newState.AmountToCall > 0 && newState.AmountToCall == HeadsupGame.SmallBlindSize) { //exception: first round newState.PotSize += HeadsupGame.SmallBlindSize; } int lastActionLowBetCount = 0; for (int i = actions.Count - 1; i >= 0; i--) { if (actions[i] == ActionBucket.LowBet) { lastActionLowBetCount++; } else { break; } } betSize = ActionAbstracter.GetBetSize(lastAction, newState.AmountToCall, newState.PotSize); if (betSize > 0) { newState.AmountToCall = betSize; newState.PotSize += betSize; if (newState.PotSize >= HeadsupGame.StackSize * 2) { phaseChanged = true; newState.Phase = GamePhase.Showdown; } } else { phaseChanged = true; newState.Phase = GamePhase.Showdown; } break; } //if the phase has changed, the next event has to occur (e.g. adding a card to the current board) if (phaseChanged) { if (newState.Phase == GamePhase.Showdown) { int payoff = newState.PotSize / 2; var handComparison = HandComparer.Compare(newState.Player1HoleCards, newState.Player2HoleCards, newState.Board); switch (handComparison) { case HandComparison.None: return(0); case HandComparison.Player1Won: return((playerIndex == 0) ? payoff : -payoff); case HandComparison.Player2Won: return((playerIndex == 0) ? -payoff : payoff); } } else { nextActionCallEnabled = false; List <Card> currentBoard = null; switch (newState.Phase) { case GamePhase.Flop: currentBoard = gameState.Board.Take(3).ToList(); break; case GamePhase.Turn: currentBoard = gameState.Board.Take(4).ToList(); break; case GamePhase.River: currentBoard = gameState.Board; break; } //evaluate new hand buckets byte bucket1 = (byte)HandStrengthAbstracter.MapToBucket(currentBoard, newState.Player1HoleCards); byte bucket2 = (byte)HandStrengthAbstracter.MapToBucket(currentBoard, newState.Player2HoleCards); handBuckets = new byte[] { bucket1, bucket2 }; } } var infoSet = new InformationSet <ActionBucket>() { CardBucket = handBuckets[playerIndex], ActionHistory = actions }; int numberOfActions = Settings.NumberOfActions; if (!nextActionCallEnabled) { numberOfActions = Settings.NumberOfActions - 1; } RegretGameNode <ActionBucket> node = null; long hash = infoSet.GetLongHashCode(); //checks if the current information set already exists in O(1) if (!GameNodes.TryGetValue(hash, out node)) { node = new RegretGameNode <ActionBucket>(numberOfActions); node.InfoSet = infoSet; GameNodes.Add(hash, node); } //gets the strategy of the current player var strategy = node.calculateStrategy(playerIndex == 0 ? probabilityPlayer1 : probabilityPlayer2); // initialise utilities with zeros var utilities = new List <float>(numberOfActions); for (int i = 0; i < numberOfActions; i++) { utilities.Add(0); } float nodeUtility = 0; int index = 0; // traverse the tree further down with a breadth first search foreach (ActionBucket nextAction in Enum.GetValues(typeof(ActionBucket))) { //skip illegal actions if (nextAction == ActionBucket.None) { continue; } if (nextAction == ActionBucket.Call && !nextActionCallEnabled) { continue; } var nextHistory = new List <ActionBucket>(); nextHistory.AddRange(actions.ToArray()); nextHistory.Add(nextAction); utilities[index] = playerIndex == 0 ? -CalculateCounterFactualRegret(newState, handBuckets, nextHistory, probabilityPlayer1 * strategy[index], probabilityPlayer2) : -CalculateCounterFactualRegret(newState, handBuckets, nextHistory, probabilityPlayer1, probabilityPlayer2 * strategy[index]); //accumulate the utility of the sub branches nodeUtility += strategy[index] * utilities[index]; index++; } for (int i = 0; i < numberOfActions; i++) { //calculate the regret float regret = utilities[i] - nodeUtility; //calculate the regret sum based on the current player node.RegretSum[i] += (playerIndex == 0 ? probabilityPlayer2 : probabilityPlayer1) * regret; } return(nodeUtility); }