/** * Main method Boy, does this need testing! * * @param args */ public static void main(final String[] args) { Logging.setDebugMode(true); for (int i = 0; i < args.length; ++i) { final RollInfo ri = new RollInfo(args[i]); Logging.debugPrint(ri + ": " + RollInfo.roll()); } }
public static String parseRollInfo(RollInfo rollInfo, String rollString) { // To really do this right, we change the token string // as we go along so that we maintain parser state by // means of the tokens rather than something more // explicit. In truth, this is an ideal application // of flex and friends for a "mini-language" whose // statements evaluate to dice rolls. Too much LISP // on the brain. --bko final StringTokenizer st = new StringTokenizer(rollString, " ", true); try { String tok = st.nextToken("d"); if ("d".equals(tok)) { rollInfo.times = 1; } else { rollInfo.times = Integer.parseInt(tok); if (st.hasMoreTokens()) { tok = st.nextToken("d"); // discard the 'd' if (!"d".equals(tok)) { return "Bad roll parsing in '" + rollString + "': missing 'd'"; } } else { rollInfo.sides = 1; return ""; } } String parseChars = "/\\|mM+-tT"; rollInfo.sides = Integer.parseInt(st.nextToken(parseChars)); if (rollInfo.sides < 1) { return "Bad roll parsing in '" + rollString + "': sides < 1: " + rollInfo.sides; } while (st.hasMoreTokens()) { tok = st.nextToken(parseChars); switch (tok.charAt(0)) { case '/': parseChars = "mM+-tT"; final int keepTop = Integer.parseInt(st.nextToken(parseChars)); if (keepTop > rollInfo.times) { return "Bad keepTop in '" + rollString + "': times: " + rollInfo.times + "; keepTop: " + keepTop; } rollInfo.keepList = new boolean[rollInfo.times]; // Rely on fact boolean is false by default. --bko for (int i = rollInfo.times - keepTop; i < rollInfo.times; ++i) { rollInfo.keepList[i] = true; } break; case '\\': parseChars = "mM+-tT"; final int keepBottom = Integer.parseInt(st.nextToken(parseChars)); if (keepBottom > rollInfo.times) { return "Bad keepBottom in '" + rollString + "': times: " + rollInfo.times + "; keepBottom: " + keepBottom; } rollInfo.keepList = new boolean[rollInfo.times]; // Rely on fact boolean is false by default. --bko for (int i = 0; i < keepBottom; ++i) { rollInfo.keepList[i] = true; } break; case '|': parseChars = "mM+-tT"; tok = st.nextToken(parseChars); rollInfo.keepList = new boolean[rollInfo.times]; final StringTokenizer keepSt = new StringTokenizer(tok, ","); while (keepSt.hasMoreTokens()) { rollInfo.keepList[Integer.parseInt(keepSt.nextToken(",")) - 1] = true; } break; case 'm': parseChars = "M+-tT"; rollInfo.rerollBelow = Integer.parseInt(st.nextToken(parseChars)); break; case 'M': parseChars = "m+-tT"; rollInfo.rerollAbove = Integer.parseInt(st.nextToken(parseChars)); break; case '+': parseChars = "tT"; rollInfo.modifier = Integer.parseInt(st.nextToken(" ")); break; case '-': parseChars = "tT"; rollInfo.modifier = -Integer.parseInt(st.nextToken(" ")); break; case 't': parseChars = "T"; rollInfo.totalFloor = Integer.parseInt(st.nextToken(" ")); break; case 'T': parseChars = "t"; rollInfo.totalCeiling = Integer.parseInt(st.nextToken(" ")); break; default: Logging.errorPrint( "Bizarre dice parser error in '" + rollString + "': not a valid delimiter"); return "Bad roll parsing in '" + rollString + "': invalid delimiter '" + tok.charAt(0) + "'."; } } } catch (NumberFormatException ex) { if (Logging.isDebugMode()) { Logging.debugPrint("Bad roll string in '" + rollString + "': " + ex, ex); } return "Bad roll string in '" + rollString + "': " + ex; } return ""; }
/** * Construct a <code>RollInfo</code> from a string. The rules: * * <ol> * <li>Optional positive integer, <var>times</var>. * <li>Literal 'd' followed by positive integer, <var>sides</var>. * <li>Optional literal '/' followed by positive integer, <var>keepTop</var>, or literal '\' * followed by positive integer, <var>keepBottom</var>, or literal '|' followed by * comma-separated list of postitive integers, <var>keepList</var> (1-indexed after dice * have been sorted). * <li>Optional literal 'm' (minimum) followed by positive integer, <var>rerollAbove</var>, or * literal 'M' (maximum) followed by postive integer, <var>rerollBelow</var>. * <li>Optional literal '+' or '-' followed by positive integer, <var>modifier</var>. * <li>Optional literal 't' followed by positive integer, <var>totalFloor</var>, or literal 'T' * followed by a positive *integer, <var>totalCeiling</var>. * </ol> * * Unlike previous versions of this method, it is <strong>case-sensitive</strong> with respect to * the alphabetic characters, e.g., only <code>d</code> (lower-case) is now valid, not also <code> * D</code> (upper-case). This is to accommodate the expanded ways to roll. * * @param rollString String compact representation of dice rolls */ public RollInfo(final String rollString) { String errMsg = RollInfo.parseRollInfo(this, rollString); if (!StringUtils.isBlank(errMsg)) { Logging.errorPrint(errMsg); } }