public DiceExpression(string expression, DiceExpressionOptions options = DiceExpressionOptions.None) { // A well-formed dice expression's tokens will be either +, -, an integer, or XdY. var tokens = expression.Replace("+", " + ").Replace("-", " - ") .Split(' ', StringSplitOptions.RemoveEmptyEntries); // Blank dice expressions end up being DiceExpression.Zero. if (!tokens.Any()) { tokens = new[] { "0" } } ; // Since we parse tokens in operator-then-operand pairs, make sure the first token is an operand. if (tokens[0] != "+" && tokens[0] != "-") { tokens = new[] { "+" } }
public DiceExpression(string expression, DiceExpressionOptions options) { // A well-formed dice expression's tokens will be either +, -, an integer, or XdY. var tokens = expression.Replace("+", " + ").Replace("-", " - ").Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); // Blank dice expressions end up being DiceExpression.Zero. if (!tokens.Any()) { tokens = new[] { "0" }; } // Since we parse tokens in operator-then-operand pairs, make sure the first token is an operand. if (tokens[0] != "+" && tokens[0] != "-") { tokens = (new[] { "+" }).Concat(tokens).ToArray(); } // This is a precondition for the below parsing loop to make any sense. if (tokens.Length % 2 != 0) { throw new ArgumentException("The given dice expression was not in an expected format: even after normalization, it contained an odd number of tokens."); } // Parse operator-then-operand pairs into this.nodes. for (int tokenIndex = 0; tokenIndex < tokens.Length; tokenIndex += 2) { var token = tokens[tokenIndex]; var nextToken = tokens[tokenIndex + 1]; if (token != "+" && token != "-") { throw new ArgumentException("The given dice expression was not in an expected format."); } int multiplier = token == "+" ? +1 : -1; if (DiceExpression.numberToken.IsMatch(nextToken)) { this.nodes.Add(new KeyValuePair <int, IDiceExpressionNode>(multiplier, new NumberNode(int.Parse(nextToken)))); } else if (DiceExpression.diceRollToken.IsMatch(nextToken)) { var match = DiceExpression.diceRollToken.Match(nextToken); int numberOfDice = match.Groups[1].Value == string.Empty ? 1 : int.Parse(match.Groups[1].Value); int diceType = match.Groups[2].Value == "%" ? 100 : int.Parse(match.Groups[2].Value); this.nodes.Add(new KeyValuePair <int, IDiceExpressionNode>(multiplier, new DiceRollNode(numberOfDice, diceType))); } else { throw new ArgumentException("The given dice expression was not in an expected format: the non-operand token was neither a number nor a dice-roll expression."); } } // Sort the nodes in an aesthetically-pleasing fashion. var diceRollNodes = this.nodes.Where(pair => pair.Value.GetType() == typeof(DiceRollNode)) .OrderByDescending(node => node.Key) .ThenByDescending(node => ((DiceRollNode)node.Value).DiceType) .ThenByDescending(node => ((DiceRollNode)node.Value).NumberOfDice); var numberNodes = this.nodes.Where(pair => pair.Value.GetType() == typeof(NumberNode)) .OrderByDescending(node => node.Key) .ThenByDescending(node => node.Value.Evaluate()); // If desired, merge all number nodes together, and merge dice nodes of the same type together. if (options == DiceExpressionOptions.SimplifyStringValue) { int number = numberNodes.Sum(pair => pair.Key * pair.Value.Evaluate()); var diceTypes = diceRollNodes.Select(node => ((DiceRollNode)node.Value).DiceType).Distinct(); var normalizedDiceRollNodes = from type in diceTypes let numDiceOfThisType = diceRollNodes.Where(node => ((DiceRollNode)node.Value).DiceType == type).Sum(node => node.Key * ((DiceRollNode)node.Value).NumberOfDice) where numDiceOfThisType != 0 let multiplicand = numDiceOfThisType > 0 ? +1 : -1 let absNumDice = Math.Abs(numDiceOfThisType) orderby multiplicand descending orderby type descending select new KeyValuePair <int, IDiceExpressionNode>(multiplicand, new DiceRollNode(absNumDice, type)); this.nodes = (number == 0 ? normalizedDiceRollNodes : normalizedDiceRollNodes.Concat(new[] { new KeyValuePair <int, IDiceExpressionNode>(number > 0 ? +1 : -1, new NumberNode(number)) })).ToList(); } // Otherwise, just put the dice-roll nodes first, then the number nodes. else { this.nodes = diceRollNodes.Concat(numberNodes).ToList(); } }