Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Diagnostics;
- using PioViewer.Util.Debug;
- namespace PioViewer.Solver.SolverObjects
- {
- public class Game
- {
- public int Dead;
- public int[] Invested = new int[2];
- public int ToAct;
- public bool AtLeastOneRaiseHasHappened;
- public int NumberOfBetsThisStreet;
- private int _round;
- public int Round
- {
- get
- {
- return _round;
- }
- set
- {
- if (value < 3 || value > 6)
- {
- throw new DebugException("Board nedds to have 3 to 5 cards. Was " + value);
- }
- _round = value;
- }
- }
- /// <summary>
- /// Starting effective stacks
- /// </summary>
- public int EffectiveStacks { get; set; }
- private int _maxInvestedPreflop;
- public int PostflopEffectiveStacks => EffectiveStacks - _maxInvestedPreflop;
- public int PostflopCurrentInvested
- {
- get
- {
- var investedPreflop = EffectiveStacks - PostflopEffectiveStacks;
- return CurrentInvested - investedPreflop;
- }
- }
- //Who was the last aggressor on the given street. The indexes for 0,1,2 are useless :)
- //We set the aggressor for all the rounds before the game started to IP player
- //so that the first bet of the tree can be treated as a donk-bet
- public int[] StreetAggressor = new int[6];
- public void Start(SolverPlayer previousStreetAggressor)
- {
- StreetAggressor = new[] { -1, -1, -1, -1, -1, -1 };
- for (int i = 0; i < Round; i++)
- {
- StreetAggressor[i] = previousStreetAggressor?.Index ?? -1;
- }
- }
- /// <summary>
- /// This is deepness after call if there is something to call.
- /// </summary>
- public float Deepness
- {
- get
- {
- var pot = Dead + 2 * CurrentInvested;
- var stack = EffectiveStacks - CurrentInvested;
- return stack * 1.0f / pot;
- }
- }
- public int CurrentInvested
- {
- get
- {
- int max = Math.Max(Invested[0], Invested[1]);
- int other = Invested[1 - ToAct];
- if (max != other)
- {
- throw new DebugException("Incorrect money invested values.");
- }
- return max;
- }
- }
- public Game Copy()
- {
- Game result = (Game)this.MemberwiseClone();
- result.Invested = new[] { this.Invested[0], this.Invested[1] };
- result.StreetAggressor = new[] { -1, -1, -1, this.StreetAggressor[3], this.StreetAggressor[4], this.StreetAggressor[5] };
- return result;
- }
- public Game CheckCall()
- {
- if (!CanAct)
- {
- throw new DebugException("Betting forbidden after showdown");
- }
- var g = this.Copy();
- g.SetInvested(ToAct, g.Invested[1 - ToAct]);
- if (ToAct != 0 || Invested[0] != Invested[1]) //unless OOP and first check - new round
- {
- g.NumberOfBetsThisStreet = 0;
- g.Round++;
- g.ToAct = 0;
- }
- else
- {
- g.ToAct = 1 - g.ToAct;
- }
- return g;
- }
- /// <summary>
- /// Returns information on how big (in terms of the potsize) would an allin be
- /// </summary>
- public float GetAllinSizeInPercent()
- {
- int invested = Invested[1 - ToAct];
- int totalPotAfterCall = Dead + 2 * invested;
- int effectiveStack = EffectiveStacks - invested;
- return effectiveStack * 100f / totalPotAfterCall;
- }
- public bool CanAct => Round != 6;
- public Game Raise(string actionString)
- {
- if (!CanAct)
- {
- throw new TooLongLineException("Betting forbidden after showdown");
- }
- int invested = Invested[1 - ToAct];
- int totalPotAfterCall = Dead + 2 * invested;
- int effectiveStack = EffectiveStacks - invested;
- int betSize;
- if (actionString.Contains("a"))
- {
- betSize = EffectiveStacks;
- }
- else if (actionString.EndsWith("c"))
- {
- if (!int.TryParse(actionString.Substring(0, actionString.Length - 1), out int cappedSize))
- {
- throw new UserException("Incorrect betting expression '" + actionString + "'.");
- }
- betSize = cappedSize + invested;
- }
- else if (actionString.EndsWith("x"))
- {
- if (!float.TryParse(actionString.Substring(0, actionString.Length - 1), out float multiplier))
- {
- throw new UserException("Incorrect betting expression '" + actionString + "'.");
- }
- if (multiplier <= 1)
- {
- throw new UserException("Raise has to be bigger than 1x. Incorrect expression " + actionString);
- }
- if (this.Invested[0] == this.Invested[1])
- {
- throw new UserException(actionString + " only allowed for raises - not bets");
- }
- int difference = this.Invested[1 - ToAct] - this.Invested[ToAct];
- Debug.Assert(difference > 0);
- betSize = invested + (int)Math.Round(difference * (multiplier - 1));
- }
- else if (actionString.Contains("e"))
- {
- if (!int.TryParse(actionString.Substring(0, actionString.Length - 1), out int numberOfBets))
- {
- throw new UserException("Incorrect betting expression '" + actionString + "'.");
- }
- double multiplier = EvenBets(totalPotAfterCall, effectiveStack, numberOfBets);
- betSize = invested + (int)(totalPotAfterCall * multiplier);
- }
- else
- {
- if (!float.TryParse(actionString, out float percent))
- {
- throw new UserException("Incorrect betting expression '" + actionString + "'.");
- }
- betSize = invested + (int)Math.Round(totalPotAfterCall * percent / 100);
- }
- return Raise(betSize);
- }
- public Game Raise(int betSize)
- {
- if (!CanAct)
- {
- throw new TooLongLineException("Betting forbidden after showdown");
- }
- var g = this.Copy();
- g.StreetAggressor[g.Round] = g.ToAct;
- var minraise = 1;
- if (this.Invested[0] != this.Invested[1])
- {
- g.AtLeastOneRaiseHasHappened = true;
- minraise = Math.Abs(this.Invested[0] - this.Invested[1]);
- }
- g.NumberOfBetsThisStreet += 1;
- if (betSize >= EffectiveStacks)
- {
- g.Round = 6;
- betSize = EffectiveStacks;
- }
- else if (betSize < g.Invested[1 - ToAct] + minraise)
- {
- betSize = Math.Min(g.Invested[1 - ToAct] + minraise, EffectiveStacks);
- }
- g.SetInvested(ToAct, betSize);
- g.ToAct = 1 - g.ToAct;
- return g;
- }
- private void SetInvested(int toAct, int investment)
- {
- this.Invested[toAct] = investment;
- if (Round == 0)
- {
- _maxInvestedPreflop = Math.Max(_maxInvestedPreflop, investment);
- }
- }
- private static double EvenBets(int pot, int stack, int number_of_bets)
- {
- double depth = (2.0 * stack + pot) / pot;
- double mult = Math.Pow(depth, 1.0 / number_of_bets);
- return (mult - 1) / 2;
- }
- }
- public class TooLongLineException : UserException
- {
- public TooLongLineException(string message)
- : base(message)
- {
- }
- }
- }
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using PioViewer.Solver.SolverObjects;
- using PioViewer.Util.Debug;
- using PioViewer.Util.TreeBuilding;
- namespace PioViewer.Browser.TreeBuilding.V2
- {
- public class ActionBuildingUtilV2
- {
- private TreeBuildingDataV2LinesGenerationPart TreeData { get; set; }
- private List<ActionLine> _lines = new List<ActionLine>();
- private HashSet<ActionLine> _removeLines = new HashSet<ActionLine>();
- private List<ActionLine> _forceLines = new List<ActionLine>();
- private Game StartingGame { get; set; }
- public static TreeGenerationLines GenerateLinesForPreflop(TreeBuildingDataV2LinesGenerationPart treeData, int[] pot,
- int effectiveStacks, SolverPlayer preflopAggressor)
- {
- Game g = new Game()
- {
- Dead = pot[2],
- Invested = new[] { pot[0], pot[1] },
- EffectiveStacks = effectiveStacks,
- Round = 3,
- };
- return GenerateLines(treeData, g, preflopAggressor);
- }
- public static TreeGenerationLines GenerateLines(TreeBuildingDataV2LinesGenerationPart treeData)
- {
- Game g = new Game()
- {
- Dead = treeData.Pot,
- EffectiveStacks = treeData.EffectiveStacks,
- Round = treeData.Board.Length,
- };
- return GenerateLines(treeData, g, null);
- }
- private static TreeGenerationLines GenerateLines(TreeBuildingDataV2LinesGenerationPart treeData, Game g,
- SolverPlayer preflopAggressor)
- {
- if (treeData == null)
- {
- return new TreeGenerationLines()
- {
- AddLines = new List<ActionLine>(),
- RemoveLines = new List<ActionLine>(),
- ForceLines = new List<ActionLine>()
- };
- }
- var util = new ActionBuildingUtilV2()
- {
- TreeData = treeData
- };
- g.Start(preflopAggressor);
- util.StartingGame = g;
- util.GenerateLines(ActionLine.CreateRoot(g.CurrentInvested), g);
- util.GenerateForcingBet();
- util.AddExtraLines();
- util.RemoveLinesProvidedByUser();
- util.AssertActionsMakeSense();
- util.ClearDuplicatedLines();
- return new TreeGenerationLines()
- {
- AddLines = util._lines,
- RemoveLines = util._removeLines.ToList(),
- ForceLines = util._forceLines
- };
- }
- private void ClearDuplicatedLines()
- {
- var lines = _lines.ToArray();
- _lines = null;
- Array.Sort(lines);
- var result = new List<ActionLine>();
- for (int i = 0; i < lines.Length - 1; i++)
- {
- if (lines[i + 1].StartsWith(lines[i]))
- {
- //nothing
- }
- else
- {
- result.Add(lines[i]);
- }
- }
- result.Add(lines[lines.Length - 1]);
- _lines = result;
- }
- private void AssertActionsMakeSense()
- {
- if (TreeData.ForceOOPBet && TreeData.ForceIPBet)
- {
- throw new UserException("Cannot force both IP and OOP bets.");
- }
- if (_lines.Count == 0)
- {
- if (TreeData.ForceOOPBet)
- {
- throw new UserException(
- "Specified tree would be empty. You might have selected 'force OOP bet', but there are no bets for OOP.");
- }
- if (TreeData.ForceIPBet)
- {
- throw new UserException(
- "Specified tree would be empty. You might have selected 'force IP bet', but there are no bets for IP.");
- }
- throw new UserException("Specified tree would be empty.");
- }
- }
- private void GenerateForcingBet()
- {
- if (StartingGame.Invested[0] != StartingGame.Invested[1])
- {
- throw new UserException("This only works if both players have some money in the pot to begin with.");
- }
- var oopCheck = ActionLine.Create(ActionLine.CreateRoot(StartingGame.Invested[0]), StartingGame.Invested[0], "CheckCall");
- ActionLine removedLine;
- if (TreeData.ForceOOPBet)
- {
- removedLine = oopCheck;
- }
- else if (TreeData.ForceIPBet)
- {
- _forceLines.Add(oopCheck);
- _lines.RemoveAll(a => !a.StartsWith(oopCheck));
- //oop check, ip check
- removedLine = ActionLine.Create(oopCheck, StartingGame.Invested[1], "CheckCall");
- }
- else
- {
- return;
- }
- _removeLines.Add(removedLine);
- _lines.RemoveAll(a => a.StartsWith(removedLine));
- }
- public void RemoveLinesProvidedByUser()
- {
- var parsedLines = new List<RemoveLineByUser>();
- if (TreeData.RemovedLines == null) return;
- foreach (var r in TreeData.RemovedLines)
- {
- if (!string.IsNullOrWhiteSpace(r))
- {
- parsedLines.Add(RemoveLineByUser.Parse(r));
- }
- }
- foreach (var parsedLine in parsedLines.OrderBy(pl => pl.Expressions.Count))
- {
- var linesToBeRemovedFromAllLines = new List<ActionLine>();
- var linesToBeAdded = new List<ActionLine>();
- foreach (var line in _lines)
- {
- var result = parsedLine.ShouldRemove(line);
- if (result != null)
- {
- if (result.RemoveThisLine)
- {
- linesToBeRemovedFromAllLines.Add(line);
- }
- if (result.AdditionalLineToRemove != null)
- {
- result = parsedLine.ShouldRemove(line);
- _removeLines.Add(result.AdditionalLineToRemove);
- }
- if (result.ParentLineToStay != null)
- {
- linesToBeAdded.Add(result.ParentLineToStay);
- }
- }
- }
- foreach (var line in linesToBeAdded)
- {
- _lines.Add(line);
- }
- foreach (var line in linesToBeRemovedFromAllLines)
- {
- _lines.Remove(line);
- }
- }
- }
- private void AddExtraLinesHelper(ActionLine path, string[] actions, int actionIndex, Game g)
- {
- if (actions.Length == actionIndex || g.CurrentInvested >= g.EffectiveStacks)
- {
- GenerateLines(path, g);
- return;
- }
- Action<Game, string> next =
- (newGame, actionNames) =>
- {
- AddExtraLinesHelper(ActionLine.Create(path, newGame.CurrentInvested, actionNames), actions, actionIndex + 1,
- newGame);
- };
- Action<Game, string[]> nextMany =
- (newGame, actionNames) =>
- {
- AddExtraLinesHelper(ActionLine.Create(path, newGame.CurrentInvested, actionNames), actions, actionIndex + 1,
- newGame);
- };
- {
- var action = actions[actionIndex];
- var items = action.ToLower().Trim().Split();
- if (string.IsNullOrWhiteSpace(items[0]))
- {
- return;
- }
- if (items[0].StartsWith("c")) //check, call
- {
- if (items.Length != 1)
- {
- throw new UserException("Invalid action expression: " + action);
- }
- next(g.CheckCall(), "CheckCall");
- }
- else if (items[0].StartsWith("f"))
- {
- throw new UserException("Can't add extra lines with explicit fold");
- }
- else if (items[0].StartsWith("b") || items[0].StartsWith("r")) //bet or raise
- {
- if (items.Length == 1)
- {
- //find all sizes already used in the tree
- var knownBetsizes = new Dictionary<int, HashSet<string>>();
- foreach (var line in this._lines)
- {
- if (line.StartsWith(path))
- {
- var potentialBet = line.FirstActionAfter(path);
- var currentBetsize = path == null ? 0 : path.Bet;
- if (potentialBet != null && potentialBet.Bet > currentBetsize)
- {
- if (!knownBetsizes.ContainsKey(potentialBet.Bet))
- {
- knownBetsizes[potentialBet.Bet] = new HashSet<string>();
- }
- knownBetsizes[potentialBet.Bet].UnionWith(potentialBet.BettingExpressions);
- }
- }
- }
- foreach (var kvp in knownBetsizes)
- {
- int raiseSize = kvp.Key;
- var expressions = kvp.Value.ToArray();
- if (g.CanAct)
- {
- nextMany(g.Raise(raiseSize), expressions);
- }
- }
- }
- else
- {
- if (g.CanAct)
- {
- next(g.Raise(items[1]), items[1]);
- }
- }
- }
- else if (items[0].StartsWith("a"))
- {
- if (items.Length != 1)
- {
- throw new UserException("Invalid action expression: " + action);
- }
- next(g.Raise("allin"), "Allin");
- }
- else
- {
- throw new UserException("Invalid expression: " + action);
- }
- }
- }
- private void AddExtraLines()
- {
- if (TreeData.ExtraLines == null) return;
- foreach (var extraLine in TreeData.ExtraLines)
- {
- if (string.IsNullOrWhiteSpace(extraLine)) continue;
- var actions = extraLine.Split(',');
- try
- {
- AddExtraLinesHelper(ActionLine.CreateRoot(this.StartingGame.Invested.Min()), actions, 0,
- this.StartingGame);
- }
- catch (TooLongLineException)
- {
- throw new UserException($"Couldn't add extra line \"{extraLine}\" to the tree. Line is too long.");
- }
- catch (Exception x)
- {
- Console.WriteLine(x);
- throw new UserException($"Couldn't add extra line \"{extraLine}\" to the tree.\n{x.Message}");
- }
- }
- }
- /// <summary>
- /// returns a validation string - true if all bet action eventually lead to allin
- /// </summary>
- /// <param name="path"></param>
- /// <param name="g"></param>
- /// <returns></returns>
- private bool GenerateLines(ActionLine path, Game g)
- {
- var atLeastOneLineToAllin = false;
- if (g.CurrentInvested >= g.EffectiveStacks)
- {
- _lines.Add(path);
- return true;
- }
- if (g.Round == 6) return false;
- if (GenerateLines(ActionLine.Create(path, g.CurrentInvested, "CheckCall"), g.CheckCall()))
- {
- atLeastOneLineToAllin = true;
- }
- var appliedBetsizes = new Dictionary<int, Game>();
- var appliedFormulas = new Dictionary<int, List<string>>();
- Action<int, Game, string> apply = (betsize, game, formula) =>
- {
- appliedBetsizes[betsize] = game;
- if (!appliedFormulas.ContainsKey(betsize))
- {
- appliedFormulas[betsize] = new List<string>();
- }
- appliedFormulas[betsize].Add(formula);
- };
- //below I calculate the size of the bet and create a new game object.
- //If it is null, than it means the bet/raise should not be made
- var streetConfig = TreeData.StreetConfigForRoundAndPosition(g.Round, g.ToAct);
- var betSizes = BetBetSizesFromConfig(g, streetConfig);
- //only if betsizes are not null - so betting / raising is allowed
- if (!string.IsNullOrWhiteSpace(betSizes))
- {
- bool canBetAtAll = true;
- bool canUseBetsizesOtherThanAllin = true;
- bool useCap = TreeData.CapEnabled && TreeData.Cap != 0;
- if (useCap)
- {
- switch (TreeData.CapMode)
- {
- case CapStyle.NoLimit:
- //allin
- if (TreeData.Cap == g.NumberOfBetsThisStreet + 1)
- {
- var gAllin = g.Raise("allin");
- apply(gAllin.Invested[g.ToAct], gAllin, "allin");
- canUseBetsizesOtherThanAllin = false;
- }
- break;
- case CapStyle.Limit:
- if (TreeData.Cap == g.NumberOfBetsThisStreet)
- {
- canBetAtAll = false;
- }
- break;
- //nothing to do
- }
- }
- //don't allow 3bet IP if don't 3bet selected
- if (g.NumberOfBetsThisStreet == 2 && g.ToAct == 1 && ((IPStreetConfig)streetConfig).Dont3bet)
- {
- canBetAtAll = false;
- }
- if (canUseBetsizesOtherThanAllin && canBetAtAll)
- {
- foreach (var betSize in betSizes.Split(',', ';', ' '))
- {
- if (!string.IsNullOrWhiteSpace(betSize))
- {
- string b = betSize.Trim();
- var ng = g.Raise(b);
- int raiseDifference = ng.Invested[g.ToAct] - ng.Invested[1 - g.ToAct];
- if (raiseDifference < TreeData.MinimumBetsize)
- {
- ng = g.Raise(ng.Invested[1 - g.ToAct] + TreeData.MinimumBetsize);
- }
- if (ng.PostflopCurrentInvested > (TreeData.AllinThreshold / 100) * ng.PostflopEffectiveStacks)
- {
- ng = g.Raise("allin");
- }
- apply(ng.Invested[g.ToAct], ng, b);
- }
- }
- }
- if (canBetAtAll)
- {
- if (streetConfig.AddAllin)
- {
- float allin = g.GetAllinSizeInPercent();
- if (allin < TreeData.AddAllinOnlyIfLessThanThisTimesThePot)
- {
- var gAllin = g.Raise("allin");
- apply(gAllin.Invested[g.ToAct], gAllin, "allin");
- }
- }
- foreach (var kvp in appliedBetsizes.OrderBy(kvp => kvp.Key))
- {
- var actionLine = ActionLine.Create(path, kvp.Key, string.Join("|", appliedFormulas[kvp.Key]));
- if (!GenerateLines(actionLine, kvp.Value))
- {
- _lines.Add(actionLine);
- }
- atLeastOneLineToAllin = true;
- }
- }
- }
- return atLeastOneLineToAllin;
- }
- private string BetBetSizesFromConfig(Game g, StreetConfig streetConfig)
- {
- string betSizes = null;
- if (g.Invested[0] == g.Invested[1])
- {
- //bet
- var isDonk = g.ToAct == 0 && g.StreetAggressor[g.Round - 1] == 1;
- if (isDonk)
- {
- var oopData = (OOPStreetConfig)streetConfig;
- betSizes = oopData.DonkBetSize;
- }
- else
- {
- betSizes = streetConfig.BetSize;
- if (TreeData.UseUnifiedBetAfterRaise && g.AtLeastOneRaiseHasHappened)
- {
- betSizes = TreeData.UnifiedBetAfterRaise;
- }
- }
- }
- else
- {
- //raise
- betSizes = streetConfig.RaiseSize;
- if (TreeData.UseUnifiedRaiseAfterRaise && g.AtLeastOneRaiseHasHappened)
- {
- betSizes = TreeData.UnifiedRaiseAfterRaise;
- }
- }
- return betSizes;
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement