001package squidpony.squidmath; 002 003import regexodus.Matcher; 004import regexodus.Pattern; 005import squidpony.annotation.Beta; 006 007import java.io.Serializable; 008import java.util.ArrayList; 009import java.util.Collections; 010import java.util.List; 011 012/** 013 * Class for emulating various traditional RPG-style dice rolls. 014 * 015 * Based on code from the Blacken library. 016 * 017 * @author yam655 018 * @author Eben Howard - http://squidpony.com - howard@squidpony.com 019 */ 020@Beta 021public class Dice implements Serializable { 022 023 private static final long serialVersionUID = -488902743486431146L; 024 025 private static final Pattern guessPattern = Pattern.compile("\\s*(\\d+)?\\s*(?:([:])\\s*(\\d+))??\\s*(?:([d:])\\s*(\\d+))?\\s*(?:([+-/*])\\s*(\\d+))?\\s*"); 026 private RNG rng; 027 028 /** 029 * Creates a new dice roller that uses a random RNG seed for an RNG that it owns. 030 */ 031 public Dice() { 032 rng = new RNG(); 033 } 034 035 /** 036 * Creates a new dice roller that uses the given RNG, which can be seeded before it's given here. The RNG will be 037 * shared, not copied, so requesting a random number from the same RNG in another place may change the value of the 038 * next die roll this makes, and dice rolls this makes will change the state of the shared RNG. 039 * @param rng an RNG that can be seeded; will be shared (dice rolls will change the RNG state outside here) 040 */ 041 public Dice(RNG rng) 042 { 043 this.rng = rng; 044 } 045 046 /** 047 * Creates a new dice roller that will use its own RNG, seeded with the given seed. 048 * @param seed a long to use as a seed for a new RNG (can also be an int, short, or byte) 049 */ 050 public Dice(long seed) 051 { 052 rng = new RNG(seed); 053 } 054 /** 055 * Creates a new dice roller that will use its own RNG, seeded with the given seed. 056 * @param seed a String to use as a seed for a new RNG 057 */ 058 public Dice(String seed) 059 { 060 rng = new RNG(seed); 061 } 062 /** 063 * Sets the random number generator to be used. 064 * 065 * This method does not need to be called before using the methods of this 066 * class. 067 * 068 * @param rng the source of randomness 069 */ 070 public void setRandom(RNG rng) { 071 this.rng = rng; 072 } 073 074 /** 075 * Rolls the given number of dice with the given number of sides and returns 076 * the total of the best n dice. 077 * 078 * @param n number of best dice to total 079 * @param dice total number of dice to roll 080 * @param sides number of sides on the dice 081 * @return sum of best n out of <em>dice</em><b>d</b><em>sides</em> 082 */ 083 public int bestOf(int n, int dice, int sides) { 084 int rolls = Math.min(n, dice); 085 ArrayList<Integer> results = new ArrayList<>(dice); 086 087 for (int i = 0; i < dice; i++) { 088 results.add(rollDice(1, sides)); 089 } 090 091 return bestOf(rolls, results); 092 } 093 094 /** 095 * Totals the highest n numbers in the pool. 096 * 097 * @param n the number of dice to be totaled 098 * @param pool the dice to pick from 099 * @return the sum 100 */ 101 public int bestOf(int n, List<Integer> pool) { 102 int rolls = Math.min(n, pool.size()); 103 Collections.sort(pool); 104 105 int ret = 0; 106 for (int i = 0; i < rolls; i++) { 107 ret += pool.get(pool.size() - 1 - i); 108 } 109 return ret; 110 } 111 112 /** 113 * Find the best n totals from the provided number of dice rolled according 114 * to the roll group string. 115 * 116 * @param n number of roll groups to total 117 * @param dice number of roll groups to roll 118 * @param group string encoded roll grouping 119 * @return the sum 120 */ 121 public int bestOf(int n, int dice, String group) { 122 int rolls = Math.min(n, dice); 123 ArrayList<Integer> results = new ArrayList<>(dice); 124 125 for (int i = 0; i < rolls; i++) { 126 results.add(rollGroup(group)); 127 } 128 129 return bestOf(rolls, results); 130 } 131 132 /** 133 * Emulate a dice roll and return the sum. 134 * 135 * @param n number of dice to sum 136 * @param sides number of sides on the rollDice 137 * @return sum of rollDice 138 */ 139 public int rollDice(int n, int sides) { 140 int ret = 0; 141 for (int i = 0; i < n; i++) { 142 ret += rng.nextInt(sides) + 1; 143 } 144 return ret; 145 } 146 147 /** 148 * Get a list of the independent results of n rolls of dice with the given 149 * number of sides. 150 * 151 * @param n number of dice used 152 * @param sides number of sides on each die 153 * @return list of results 154 */ 155 public List<Integer> independentRolls(int n, int sides) { 156 List<Integer> ret = new ArrayList<>(n); 157 for (int i = 0; i < n; i++) { 158 ret.add(rng.nextInt(sides) + 1); 159 } 160 return ret; 161 } 162 163 /** 164 * Turn the string to a randomized number. 165 * 166 * The following types of strings are supported "42": simple absolute string 167 * "10:20": simple random range (inclusive between 10 and 20) "d6": synonym 168 * for "1d6" "3d6": sum of 3 6-sided dice "3:4d6": best 3 of 4 6-sided dice 169 * 170 * The following types of suffixes are supported "+4": add 4 to the value 171 * "-3": subtract 3 from the value "*100": multiply value by 100 "/8": 172 * divide value by 8 173 * 174 * @param group string encoded roll grouping 175 * @return random number 176 */ 177 public int rollGroup(String group) {//TODO -- rework to tokenize and allow multiple chained operations 178 Matcher mat = guessPattern.matcher(group); 179 int ret = 0; 180 181 if (mat.matches()) { 182 String num1 = mat.group(1); // number constant 183 String wmode = mat.group(2); // between notation 184 String wnum = mat.group(3); // number constant 185 String mode = mat.group(4); // db 186 String num2 = mat.group(5); // number constant 187 String pmode = mat.group(6); // math operation 188 String pnum = mat.group(7); // number constant 189 190 int a = num1 == null ? 0 : Integer.parseInt(num1); 191 int b = num2 == null ? 0 : Integer.parseInt(num2); 192 int w = wnum == null ? 0 : Integer.parseInt(wnum); 193 194 if (num1 != null && num2 != null) { 195 if (wnum != null) { 196 if (":".equals(wmode)) { 197 if ("d".equals(mode)) { 198 ret = bestOf(a, w, b); 199 } 200 } 201 } else if ("d".equals(mode)) { 202 ret = rollDice(a, b); 203 } else if (":".equals(mode)) { 204 ret = rng.between(a, b + 1); 205 } 206 } else if (num1 != null) { 207 if (":".equals(wmode)) { 208 ret = rng.between(a, w + 1); 209 } else { 210 ret = a; 211 } 212 } else if (num2 != null) { 213 if (mode != null) { 214 switch (mode) { 215 case "d": 216 ret = rollDice(1, b); 217 break; 218 case ":": 219 ret = rng.between(0, b + 1); 220 break; 221 } 222 } 223 } 224 if (pmode != null) { 225 int p = pnum == null ? 0 : Integer.parseInt(pnum); 226 switch (pmode) { 227 case "+": 228 ret += p; 229 break; 230 case "-": 231 ret -= p; 232 break; 233 case "*": 234 ret *= p; 235 break; 236 case "/": 237 ret /= p; 238 break; 239 } 240 } 241 } 242 return ret; 243 } 244}