public void Execute(IPluginProgressProvider progress) { //If the user selects plugin from the menu we can ask controller about currently selected node. IServerNode currentlySelectedNode = Context.Controller.SelectedNode; if (currentlySelectedNode == null) { MessageBox.Show("There is no node selected at the moment"); return; } // We want to ask for range, ev and equity of a current player to act. // If it is not action node - we will show those values for OOP Player IServerPlayer player; // We can get the action player from currently selected node if (currentlySelectedNode.ActionPlayer != null) { player = currentlySelectedNode.ActionPlayer; } else { // This is the way to get OOP player object from the API player = this.Context.ServerUtils.OOP; } // We will get the weight, ev and equity of the single holding AsQs, // average of all the holding in the AQ+ category // and total for all hands // We can construct a hand object by parsing single hand PioViewerApi.Util.Hand asqs = new Hand("AQss"); // We can also parse range using utility method. Range object is a holder of hand, weight pairs PioViewerApi.Util.IRange aqplus = this.Context.ServerUtils.CreateRange("AQ+"); //We will collect the information in the String Builder object. StringBuilder result = new StringBuilder(); result.AppendLine("Basic report for " + player.Name + " in node " + currentlySelectedNode.NodeId); // This is how you convert a hand to nice looking string, and get the human readable format of a range result.AppendLine("Reporting range, EV and equity for " + asqs.ToUnicodeString() + " and subrange " + aqplus.ToSingleLineString()); // Ask solver for the range of a given player in a node IRange range = this.Context.ServerWrapper.ShowRange(player, currentlySelectedNode); // Ask solver for equity EVRange equity = this.Context.ServerWrapper.CalcEquityInNode(player, currentlySelectedNode); // Ask solver for EV EVRange ev = this.Context.ServerWrapper.CalcEVInNode(player, currentlySelectedNode); ReportRange(result, asqs, aqplus, range); result.AppendLine("Equity:"); ReportEVEquity(result, equity, asqs, aqplus); result.AppendLine("EV:"); ReportEVEquity(result, ev, asqs, aqplus); MessageBox.Show(result.ToString(), "Tutorial 4"); }
private void ReportEVEquity(StringBuilder result, EVRange equity, Hand hand, IRange manyHands) { // EVRange class, which is a holder for EV and Equity results contains for each hand values of // calculated EV, total number of matchups and total winnings. // Except for some corner cases EV is equal Wins/ Matchups double handEV = equity[hand].EV; // Calculation of total EV for a given range we have to a be careful. // We cannot simply average p out the EV's, because different hands have different weights. // It is also not enough to simply weight them with the current range as this does not // take into account the card removal effect. The proper way is to calculated weighted EV // weighted by the number of different matchups the given hand will play. // To do this we will sum the total winnings of all hands in the range and divide it by the total // number of matchups played. double rangeWins = 0; double rangeMatchups = 0; foreach (var singleCombo in manyHands) { EVHolder ev = equity[singleCombo]; // This is important consideration. Impossible hands are reported by the solver as 0 matchups and NaN Wins. // We don't want to add NaNs to the running sum so we need this check if (ev.Matchups > 0) { rangeWins += ev.Wins; rangeMatchups += ev.Matchups; } } //To calculate the total EV for all range we repeat the same process double totalWins = 0; double totalMatchups = 0; foreach (var singleCombo in equity) { EVHolder ev = equity[singleCombo]; if (ev.Matchups > 0) { totalWins += ev.Wins; totalMatchups += ev.Matchups; } } result.AppendLine("for single hand " + hand.ToUnicodeString() + " = " + handEV); result.AppendLine("for hands " + manyHands.ToSingleLineString() + " = " + (rangeWins / rangeMatchups)); result.AppendLine("total = " + (totalWins / totalMatchups)); }