package squidpony.squidmath;
import regexodus.Matcher;
import regexodus.Pattern;
import squidpony.annotation.Beta;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Class for emulating various traditional RPG-style dice rolls.
*
* Based on code from the Blacken library.
*
* @author yam655
* @author Eben Howard - http://squidpony.com - howard@squidpony.com
*/
@Beta
public class Dice implements Serializable {
private static final long serialVersionUID = -488902743486431146L;
private static final Pattern guessPattern = Pattern.compile("\\s*(\\d+)?\\s*(?:([:])\\s*(\\d+))??\\s*(?:([d:])\\s*(\\d+))?\\s*(?:([+-/*])\\s*(\\d+))?\\s*");
private RNG rng;
/**
* Creates a new dice roller that uses a random RNG seed for an RNG that it owns.
*/
public Dice() {
rng = new RNG();
}
/**
* Creates a new dice roller that uses the given RNG, which can be seeded before it's given here. The RNG will be
* shared, not copied, so requesting a random number from the same RNG in another place may change the value of the
* next die roll this makes, and dice rolls this makes will change the state of the shared RNG.
* @param rng an RNG that can be seeded; will be shared (dice rolls will change the RNG state outside here)
*/
public Dice(RNG rng)
{
this.rng = rng;
}
/**
* Creates a new dice roller that will use its own RNG, seeded with the given seed.
* @param seed a long to use as a seed for a new RNG (can also be an int, short, or byte)
*/
public Dice(long seed)
{
rng = new RNG(seed);
}
/**
* Creates a new dice roller that will use its own RNG, seeded with the given seed.
* @param seed a String to use as a seed for a new RNG
*/
public Dice(String seed)
{
rng = new RNG(seed);
}
/**
* Sets the random number generator to be used.
*
* This method does not need to be called before using the methods of this
* class.
*
* @param rng the source of randomness
*/
public void setRandom(RNG rng) {
this.rng = rng;
}
/**
* Rolls the given number of dice with the given number of sides and returns
* the total of the best n dice.
*
* @param n number of best dice to total
* @param dice total number of dice to roll
* @param sides number of sides on the dice
* @return sum of best n out of <em>dice</em><b>d</b><em>sides</em>
*/
public int bestOf(int n, int dice, int sides) {
int rolls = Math.min(n, dice);
ArrayList<Integer> results = new ArrayList<>(dice);
for (int i = 0; i < dice; i++) {
results.add(rollDice(1, sides));
}
return bestOf(rolls, results);
}
/**
* Totals the highest n numbers in the pool.
*
* @param n the number of dice to be totaled
* @param pool the dice to pick from
* @return the sum
*/
public int bestOf(int n, List<Integer> pool) {
int rolls = Math.min(n, pool.size());
Collections.sort(pool);
int ret = 0;
for (int i = 0; i < rolls; i++) {
ret += pool.get(pool.size() - 1 - i);
}
return ret;
}
/**
* Find the best n totals from the provided number of dice rolled according
* to the roll group string.
*
* @param n number of roll groups to total
* @param dice number of roll groups to roll
* @param group string encoded roll grouping
* @return the sum
*/
public int bestOf(int n, int dice, String group) {
int rolls = Math.min(n, dice);
ArrayList<Integer> results = new ArrayList<>(dice);
for (int i = 0; i < rolls; i++) {
results.add(rollGroup(group));
}
return bestOf(rolls, results);
}
/**
* Emulate a dice roll and return the sum.
*
* @param n number of dice to sum
* @param sides number of sides on the rollDice
* @return sum of rollDice
*/
public int rollDice(int n, int sides) {
int ret = 0;
for (int i = 0; i < n; i++) {
ret += rng.nextInt(sides) + 1;
}
return ret;
}
/**
* Get a list of the independent results of n rolls of dice with the given
* number of sides.
*
* @param n number of dice used
* @param sides number of sides on each die
* @return list of results
*/
public List<Integer> independentRolls(int n, int sides) {
List<Integer> ret = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
ret.add(rng.nextInt(sides) + 1);
}
return ret;
}
/**
* Turn the string to a randomized number.
*
* The following types of strings are supported "42": simple absolute string
* "10:20": simple random range (inclusive between 10 and 20) "d6": synonym
* for "1d6" "3d6": sum of 3 6-sided dice "3:4d6": best 3 of 4 6-sided dice
*
* The following types of suffixes are supported "+4": add 4 to the value
* "-3": subtract 3 from the value "*100": multiply value by 100 "/8":
* divide value by 8
*
* @param group string encoded roll grouping
* @return random number
*/
public int rollGroup(String group) {//TODO -- rework to tokenize and allow multiple chained operations
Matcher mat = guessPattern.matcher(group);
int ret = 0;
if (mat.matches()) {
String num1 = mat.group(1); // number constant
String wmode = mat.group(2); // between notation
String wnum = mat.group(3); // number constant
String mode = mat.group(4); // db
String num2 = mat.group(5); // number constant
String pmode = mat.group(6); // math operation
String pnum = mat.group(7); // number constant
int a = num1 == null ? 0 : Integer.parseInt(num1);
int b = num2 == null ? 0 : Integer.parseInt(num2);
int w = wnum == null ? 0 : Integer.parseInt(wnum);
if (num1 != null && num2 != null) {
if (wnum != null) {
if (":".equals(wmode)) {
if ("d".equals(mode)) {
ret = bestOf(a, w, b);
}
}
} else if ("d".equals(mode)) {
ret = rollDice(a, b);
} else if (":".equals(mode)) {
ret = rng.between(a, b + 1);
}
} else if (num1 != null) {
if (":".equals(wmode)) {
ret = rng.between(a, w + 1);
} else {
ret = a;
}
} else if (num2 != null) {
if (mode != null) {
switch (mode) {
case "d":
ret = rollDice(1, b);
break;
case ":":
ret = rng.between(0, b + 1);
break;
}
}
}
if (pmode != null) {
int p = pnum == null ? 0 : Integer.parseInt(pnum);
switch (pmode) {
case "+":
ret += p;
break;
case "-":
ret -= p;
break;
case "*":
ret *= p;
break;
case "/":
ret /= p;
break;
}
}
}
return ret;
}
}