/** Play variations of Chuck-a-luck or Risk. (Answers to Exercises 1-5.) * * AUTHORS * * Original: John P. Spurgeon with support from Akil, Jonathan, Kai, Katherine, and Leon. * This version: John P. Spurgeon. * * VERSION LINEAGE * * 0. https://apcsafreeresponsequestions.blogspot.com/2019/02/original-chuck-a-luck.html. (ORIGINAL) * 1. https://apcsafreeresponsequestions.blogspot.com/2019/02/chuck-a-luck.html. (EXERCISES) * 2. https://apcsafreeresponsequestions.blogspot.com/2019/02/chuck-a-luck-answers.html. (ANSWERS TO EXERCISES 1-4) * 3. https://apcsafreeresponsequestions.blogspot.com/2019/02/chuck-a-luck-or-risk.html. (ANSWERS TO EXERCISES 1-5) * * DESCRIPTION * * Chuck-a-luck or Risk includes the answers to Exercises 1-4 that were part of * the previous version of the program and adds an answer to Exercise 5. * * A PROCEDURE FOR ADDING NEW GAMES * * 1. Choose an available (not already in use) game key (a String value) that * will be used to identify the new game. Game keys currently in use include * "[Alt-]Chuck-a-luck", "Risk[-Shot|-Shootout]", and "RiskiKo![-Shot|-Shootout]". * 2. Dice games need one or more sets of dice. Determine what dice your game * needs and make sure they are available: * 2.1. Check to see whether the CarnivalJoint class already contains * sets of dice that can be used by the new game. If the dice you * need are already available, you might want to use the ones that * are already there. * 2.2. If the dice your games needs are not defined yet, then create them. * If necessary, create new dice types. Then add instances of the dice * that your game will use to the CarnivalJoint class. * 3. Modify the CarnivalJoint.getDiceGame method so that it returns an object * representing an instance of the new game when the game's game key is * passed as the argument to CarnivalJoint.getDiceGame. * 4. If you are practicing top-down programming, you will now have a program * that does not compile, because the changes you made in the previous step * include the invocation of a constructor method of a class that does not * yet exist. Create the missing class now. * 4.1. If you anticipate creating more than one variation of your game, * then you might want to consider creating an abstract class in which * you define the methods and values that are common to all instances * of the game. * 4.2. The class you create should implement the CarnivalDiceGame interface * so that the object returned by CarnivalJoint.getDiceGame can be used as * the argument when the Carny class's constructor method is called i * CarnivalJoint.main. * 4.2.1. Create a constructor method (or methods) that are defined in * terms of the dice set(s) needed by your game, and initialize * private instance variables using the value(s) passed to the * constructor method(s). * 4.2.2. Implement the methods needed to implement the CarnivalDiceGame * interface, which are the methods needed to implement the * CarnivalGame and DiceGame interfaces. * 5. Document your changes. * 5.1. Update CarnivalJoint.help. * 5.2. Make sure the source code looks at least as good as how you found it. * Comment new code judiciously. * 6. Test your changes. * 6.1. Test the new game. * 6.2. Test the previously existing games to make sure you didn't break them in * the process of adding the new game. * 7. Show off your new game! * 7.1. Show off your game to friends and family. * 7.2. Send a copy of your program's source code to jspurgeon@vcstudent.org. */ import java.util.*; import java.util.concurrent.TimeUnit; /* In North America "joint" refers to any kind of carnival stand. In other parts of * the world, it is a slang term for a gathering place. On the fairground it is used * as a generic term for any form of portable sideshow of the ground booth variety. * Walkup shows, which have raised platforms for the performers, are not so described. * * Source: https://conklinshows.com/info/dictionary/j.html */ public class CarnivalJoint { /** Advertises games or conducts one. */ public static void main(String args[]) { if (args.length == 0) System.out.println(advert); else getCarny(args[0]).setupGame(args).conductGame().rewardWinners(); } private static String advert = "Play a single round of a dice game that accepts wagers.\n" + "\n" + "COMMAND LINE ARGUMENTS\n" + "\n" + "args[0]: Game key (selects a game; default is Chuck-a-luck).\n" + "args[i] (i > 0): Game configuration values (e.g. wagers).\n" + "\n" + "GAME KEY EXAMPLES\n" + "\n" + "Chuck-a-luck: Typical chuck-a-luck using regular six-sided dice.\n" + "Alt-Chuck-a-luck: Alternate chuck-a-luck (six-sided dice).\n" + "Chuck-a-luck-12: Chuck-a-luck using 12-sided dice (dodecahedrons).\n" + "Triple-Threes: Typical Triple Threes game with five dice.\n" + "\n" + "COMMAND LINE ARGUMENTS EXAMPLES\n" + "\n" + "Chuck-a-luck-6 Big Triple 12 24\n" + "\n" + "Typical chuck-a-luck using six-sided dice, wagering that the sum of the\n" + "values shown on each die will be a 'Big' value or a 'Triple' or 12 or 24.\n" + "\n" + "Risk-Shootout 20 20 -1 No-Dice\n" + "\n" + "A game of no-limit, no-dice Risk-Shootout where both sides start with 20 dice.\n" + " args[0]: Game key (required).\n" + " args[1]: Attacker's army count (required).\n" + " args[2]: Defender's army count (required).\n" + " args[3]: Max number of shots (optional; negative means no-limit).\n" + " args[args.length -1]: \"No-Dice\" (optional; means do not print dice).\n" + "\n" + "RisiKo!-Shot 3 3\n" + "\n" + "Attacker rolls 3; defender rolls 3; outputs number of armies lost by whom.\n" + "\n" + "Triple-Threes (wager)\n" + "\n" + "Roll five dice; player gains one point for each 3 rolled; if no 3s were rolled, player loses;\n" + "if at least one 3 was rolled, player rolls again; player gains 3 points to win.\n" + "Special rules: If the player rolls three 3s on the first roll, the reward is tripled\n" + " If the player rolls five 3s on the first roll, the reward is five times the wager."; ; /* The joint's dice sets. */ private static final Die[] twelveSidedDiceCage = { new Dodecahedron(), new Dodecahedron(), new Dodecahedron() }; private static final Die[] sixSidedDiceCage = { new FairSixSidedAsciiDie(), new FairSixSidedAsciiDie(), new FairSixSidedAsciiDie() }; private static final Die[] redRiskDice = { new FairSixSidedAsciiDie(), new FairSixSidedAsciiDie(), new FairSixSidedAsciiDie() }; private static final Die[] whiteRiskDice = { new FairSixSidedAsciiDie(), new FairSixSidedAsciiDie(), new FairSixSidedAsciiDie() }; private static final Die[] fiveDice = { new FairSixSidedAsciiDie(), new FairSixSidedAsciiDie(), new FairSixSidedAsciiDie(), new FairSixSidedAsciiDie(), new FairSixSidedAsciiDie() }; private static Croupier getCarny(String key) { final CarnivalDiceGame game = getDiceGame(key); System.out.println(key + ": " + game); if (key.equals("Risk-Shootout") || key.equals("RisiKo!-Shootout")) return new RiskyCarny((RiskShot)game); else if (key.equals("Triple-Threes")) { return new TripleCarny((TripleThrees)game); } else if (key.equals("3s")) { return new TripleCarny((TripleThrees)game); } else return new Carny(game); } /** Given a game key, returns a game or throws an exception. */ private static CarnivalDiceGame getDiceGame(String key) { // Risk[-Shot] or RisiKo![-Shot] if (key.indexOf("Risk") >=0) return new StandardRiskShot(redRiskDice, whiteRiskDice); if (key.indexOf("RisiKo!") >= 0) return new RisiKoShot(redRiskDice, whiteRiskDice); // Chuck-a-luck or Alternate Chuck-a-luck final Die[] diceCage = (key.indexOf("12") >= 0) ? twelveSidedDiceCage : sixSidedDiceCage; if (key.indexOf("Alt-Chuck-a-luck") >= 0) return new AlternateChuckALuck(diceCage); if (key.indexOf("Chuck-a-luck") >= 0) return new ChuckALuck(diceCage); // New added game of Triple-Threes if (key.indexOf("Triple-Threes") >= 0) return new TripleThrees(fiveDice); if (key.indexOf("3s") >= 0) return new TripleThrees(fiveDice); // Bad game key. throw new IllegalArgumentException("Invalid game key:" + key); } } /**************/ /* INTERFACES */ /**************/ /* A croupier is someone appointed at a gambling table to assist in the conduct of the game, * especially in the distribution of bets and payouts. Croupiers are typically employed by * casinos. Originally a "croupier" meant one who stood behind a gambler, with extra reserves * of cash to back him up during a gambling session. The word derived from "croup" (the rump * of a horse) and was by way of analogy to one who rode behind on horseback. It later came to * refer to one who was employed to collect the money from a gaming-table. * * Source: https://en.wikipedia.org/wiki/Croupier */ interface Croupier { Croupier setupGame(String[] inputs); Croupier conductGame(); Croupier rewardWinners(); } interface CarnivalGame { CarnivalGame processInputs(String[] inputs); CarnivalGame printResults(); } interface DiceGame { DiceGame analyzeDice(); DiceGame rollDice(); DiceGame showDice(); } interface CarnivalDiceGame extends CarnivalGame, DiceGame { } interface Die { Die roll(); Integer valueOf(); } /********************/ /* CARNIVAL WORKERS */ /********************/ /* Carny, also spelled carnie, is an informal term used in North America * for a traveling carnival employee, and the language they use, particularly * when the employee plays a game ("joint"), food stand ("grab" or "popper"), * or ride at a carnival. The term "showie" is used synonymously in Australia. * Carny is thought to have become popularized around 1931 in North America, * when it was first colloquially used to describe one who works at a carnival. * The word carnival, originally meaning a "time of merrymaking before Lent," * came into use circa 1549. * * Source: https://en.wikipedia.org/wiki/Carny */ /** A Croupier that oversees a game. */ final class Carny implements Croupier { private final CarnivalDiceGame game; public Carny(CarnivalDiceGame game) { this.game = game; } public Carny setupGame(String[] inputs) { game.processInputs(inputs); return this; } public Carny conductGame() { game.rollDice().showDice().analyzeDice(); return this; } public Carny rewardWinners() { game.printResults(); return this; } } /** A Croupier that oversees a game of Risk or RisiKo! in which players duel * until the defender is defeated or the attacker can no longer attack. */ final class RiskyCarny implements Croupier { private static final String NO_DICE = "No-Dice"; private final RiskShot game; private int offensiveArmies; private int defensiveArmies; private String[] gameInputs = new String[3]; private boolean showDice; private int remainingShots = -1; public RiskyCarny(RiskShot riskyGame) { game = riskyGame; } public RiskyCarny setupGame(String[] inputs) { gameInputs[0] = inputs[0]; offensiveArmies = Integer.parseInt(inputs[1]); defensiveArmies = Integer.parseInt(inputs[2]); showDice = !inputs[inputs.length - 1].equals(NO_DICE); if (inputs.length > 3 && !inputs[3].equals(NO_DICE)) remainingShots = Integer.parseInt(inputs[3]); return this; } public RiskyCarny conductGame() { while (remainingShots-- != 0 && offensiveArmies > 1 && defensiveArmies > 0) { prepareGameInputs(); game.processInputs(gameInputs).rollDice(); if (showDice) game.showDice(); game.analyzeDice().printResults(); offensiveArmies -= game.getOffensiveCasualties(); defensiveArmies -= game.getDefensiveCasualties(); System.out.print("Attacker's armies remaining: "); System.out.println(offensiveArmies); System.out.print("Defender's armies remaining: "); System.out.println(defensiveArmies); } return this; } public RiskyCarny rewardWinners() { return this; } private void prepareGameInputs() { gameInputs[1] = Integer.toString(Math.min(game.maxRedDice, offensiveArmies - 1)); System.out.println("Attacker rolls " + gameInputs[1]); gameInputs[2] = Integer.toString(Math.min(game.maxWhiteDice, defensiveArmies)); System.out.println("Defender rolls " + gameInputs[2]); } } final class TripleCarny implements Croupier { private final CarnivalDiceGame game; public TripleCarny(CarnivalDiceGame game) { this.game = game; } public TripleCarny setupGame(String[] inputs) { game.processInputs(inputs); return this; } public TripleCarny conductGame() { while (AbstractTripleThrees.rollAgain == true && AbstractTripleThrees.score < 3) { game.rollDice().showDice().analyzeDice(); } return this; } public TripleCarny rewardWinners() { game.printResults(); return this; } } /*********/ /* GAMES */ /*********/ /** A game of chance involving a single roll of two sets of dice similar to a roll used to * determine the units lost in round of a battle in variations of the board game Risk. * Inputs are the numbers of dice rolled by an attacker and a defender. The output is the * dice rolled grouped by attacker and defender, sorted by value, and the number of armies * lost and by either the attacker or the defender or both. * * Risk is a strategy board game of diplomacy, conflict and conquest for two to six players. * The standard version is played on a board depicting a political map of Earth, divided into * forty-two territories, which are grouped into six continents. Turn rotates among players * who control armies of playing pieces with which they attempt to capture territories from * other players, with results determined by dice rolls. * * See: https://en.wikipedia.org/wiki/Risk_(game) */ abstract class AbstractTripleThrees implements CarnivalDiceGame { public static boolean rollAgain = true; public static int score = 0; protected int roll = 1; protected final Die[] dice; protected final List<String> results = new LinkedList<String>(); private String[] inputs; public AbstractTripleThrees(Die[] dice) { if (dice.length != 5) { throw new IllegalArgumentException("Wrong number of dice."); } this.dice = dice; } public abstract AbstractTripleThrees analyzeDice(); public AbstractTripleThrees printResults() { for (int i = 1; i < inputs.length; i++) { String bet = inputs[i]; if ((score == 5) && (roll == 2)) { System.out.println("You win $" + (Integer.parseInt(bet) * 5)); } else if ((score >= 3) && (roll == 2)) { System.out.println("You win $" + (Integer.parseInt(bet) * 3)); } else if (score >= 3) { System.out.println("You win $" + bet); } else { System.out.println("You lose $" + bet); } } return this; } public AbstractTripleThrees processInputs(String[] inputs) { this.inputs = inputs; return this; } public AbstractTripleThrees rollDice() { for (Die die : dice) die.roll(); return this; } public AbstractTripleThrees showDice() { System.out.println("\nRoll " + roll + ":"); roll++; for (Die die : dice) System.out.println(die); return this; } } class TripleThrees extends AbstractTripleThrees { public TripleThrees(Die[] dice) { super(dice); } public TripleThrees analyzeDice() { rollAgain = false; boolean check = false; for (int i = 0; i < 5; i++) { int value = dice[i].valueOf(); if (value == 3) { score++; check = true; } } if (check == true) rollAgain = true; results.clear(); return this; } public String toString() { return "Three threes for the win!"; } } abstract class RiskShot implements CarnivalDiceGame { public final int maxRedDice; public final int maxWhiteDice; private final Die[] redDice; private final Die[] whiteDice; protected final List<Die> attackingDice = new LinkedList<Die>(); protected final List<Die> defendingDice = new LinkedList<Die>(); protected int offensiveCasualties = 0; protected int defensiveCasualties = 0; protected RiskShot(Die[] redDice, int maxRedDice, Die[] whiteDice, int maxWhiteDice) { if (redDice.length < maxRedDice) { throw new IllegalArgumentException("Too few red dice."); } if (whiteDice.length < maxWhiteDice) { throw new IllegalArgumentException("Too few white dice."); } this.redDice = redDice; this.whiteDice = whiteDice; this.maxRedDice = maxRedDice; this.maxWhiteDice = maxWhiteDice; } public abstract RiskShot analyzeDice(); public RiskShot printResults() { reportCasualties("Attacker", offensiveCasualties); reportCasualties("Defender", defensiveCasualties); return this; } public int getOffensiveCasualties() { return offensiveCasualties; } public int getDefensiveCasualties() { return defensiveCasualties; } public RiskShot processInputs(String[] inputs) { if (inputs.length < 3) { throw new IllegalArgumentException("Too few inputs."); } selectDice(Integer.parseInt(inputs[1]), Integer.parseInt(inputs[2])); return this; } public RiskShot rollDice() { for (Die redDie : attackingDice) { redDie.roll(); } Collections.sort(attackingDice, AbstractDie.descendingComparator); for (Die whiteDie : defendingDice) { whiteDie.roll(); } Collections.sort(defendingDice, AbstractDie.descendingComparator); return this; } public RiskShot showDice() { System.out.println("Attacking (Red)"); for (Die redDie : attackingDice) { System.out.println(redDie); } System.out.println("Defending (White)"); for (Die whiteDie : defendingDice) { System.out.println(whiteDie); } return this; } public String toString() { return "Shots fired in a game of Risk."; } private void selectDice(final int attackWager, final int defendWager) { verifyWagers(attackWager, defendWager); attackingDice.clear(); for (int i = 0; i < attackWager; i++) { attackingDice.add(redDice[i]); } defendingDice.clear(); for (int i = 0; i < defendWager; i++) { defendingDice.add(whiteDice[i]); } } private void verifyWagers(final int attackWager, final int defendWager) { if (attackWager < 1 || attackWager > maxRedDice) { throw new IllegalArgumentException("Offensive wager out of range: " + attackWager); } if (defendWager < 1 || defendWager > maxWhiteDice) { throw new IllegalArgumentException("Defensive wager out of range: " + defendWager); } } private static void reportCasualties(final String who, final int count) { if (count == 0) return; System.out.print(who + " loses " + count + " arm"); System.out.println(count == 1 ? "y." : "ies."); } } /** An instance of a RiskShot game that adopts rules of a standard game of Risk. * * Defenders always win ties when dice are rolled. This gives the defending player the advantage * in "one-on-one" fights, but the attacker's ability to use more dice offsets this advantage. * It is always advantageous to roll the maximum number of dice, unless an attacker wishes to * avoid moving men into a 'dead-end' territory, in which case they may choose to roll fewer than * three. Thus when rolling three dice against two, three against one, or two against one, the * attacker has a slight advantage, otherwise the defender has an advantage. * * See also: https://en.wikipedia.org/wiki/Risk_(game) */ class StandardRiskShot extends RiskShot { protected StandardRiskShot(Die[] redDice, int maxRedDice, Die[] whiteDice, int maxWhiteDice) { super(redDice, maxRedDice, whiteDice, maxWhiteDice); } public StandardRiskShot(Die[] redDice, Die[] whiteDice) { this(redDice, 3, whiteDice, 2); } /** * Precondition: Wagers have been made, dice have been rolled, and the * attacking and defending dice have been arranged in order from largest * to smallest value. */ public StandardRiskShot analyzeDice() { defensiveCasualties = 0; offensiveCasualties = 0; final int n = attackingDice.size(); final int m = defendingDice.size(); for (int i = 0; i < n && i < m; i++) { if (attackingDice.get(i).valueOf() > defendingDice.get(i).valueOf()) { defensiveCasualties++; } else { offensiveCasualties++; } } return this; } } /** An instance of a RiskShot game that adopts rules of RisiKo! (a variation of Risk). * * RisiKo! derives from the 1957 French game La ConquĂȘte du Monde, better known worldwide as Risk. * RisiKo! is a variant of the game released in Italy, in which the defender is allowed to roll up * to three dice to defend. This variation dramatically shifts the balance of power towards defense. * * See: https://en.wikipedia.org/wiki/RisiKo! * See also: https://en.wikipedia.org/wiki/Risk_(game) */ class RisiKoShot extends StandardRiskShot { public RisiKoShot(Die[] redDice, Die[] whiteDice) { super(redDice, 3, whiteDice, 3); } public String toString() { return "Shots fired in a game of RisiKo!"; } } /** A partial simulation of the dice game Chuck-a-luck that determines winners * but does not compute gains or losses based on odds. * * Chuck-a-luck, also known as birdcage, is a game of chance played with three * dice. It is derived from grand hazard and both can be considered a variant * of sic bo, which is a popular casino game, although chuck-a-luck is more of * a carnival game than a true casino game. The game is sometimes used as a * fundraiser for charity. * * Chuck-a-luck is played with three standard dice that are kept in a device * shaped somewhat like an hourglass that resembles a wire-frame bird cage * and pivots about its centre. The dealer rotates the cage end over end, with * the dice landing on the bottom. Wagers are placed based on possible * combinations that can appear on the three dice. * * WAGER DESCRIPTION * n A specific number will appear. (Known as "Single".) * Triple Any of the triples (all three dice show the same number) will appear. * Big The total score will be 11 (alternatively 12) or higher and not a triple. * Small The total score will be 10 (alternatively 9) or lower and not a triple. * Field The total score will be outside the range of 8 to 12 (inclusive). * * Source: https://en.wikipedia.org/wiki/Chuck-a-luck */ abstract class AbstractChuckALuck implements CarnivalDiceGame { public static final String Triple = "Triple", Big = "Big", Small = "Small", Field = "Field"; protected final Die[] dice; protected final List<String> results = new LinkedList<String>(); private String[] inputs; public AbstractChuckALuck(Die[] dice) { if (dice.length != 3) { throw new IllegalArgumentException("Wrong number of dice."); } this.dice = dice; } public abstract AbstractChuckALuck analyzeDice(); public AbstractChuckALuck printResults() { for (int i = 1; i < inputs.length; i++) { String bet = inputs[i]; if (results.indexOf(bet) >= 0) { System.out.println("Number " + i + " wagered " + bet + " and wins!"); } } return this; } public AbstractChuckALuck processInputs(String[] inputs) { this.inputs = inputs; return this; } public AbstractChuckALuck rollDice() { for (Die die : dice) die.roll(); return this; } public AbstractChuckALuck showDice() { for (Die die : dice) System.out.println(die); return this; } } /** An instance of AbstractChuckALuck that uses typical rules. * * WAGER DESCRIPTION * Big The total score will be 11 or higher and not a triple. * Small The total score will be 10 or lower and not a triple. */ class ChuckALuck extends AbstractChuckALuck { public ChuckALuck(Die[] dice) { super(dice); } public ChuckALuck analyzeDice() { final Integer v1 = dice[0].valueOf(), v2 = dice[1].valueOf(), v3 = dice[2].valueOf(); final int sum = v1 + v2 + v3; results.clear(); // For "Single" wagers. results.add(v1.toString()); results.add(v2.toString()); results.add(v3.toString()); if (v1 == v2 && v2 == v3) { results.add(Triple); } else if (sum >= 11) { results.add(Big); } else if (sum <= 10 ) { results.add(Small); } if (sum < 8 || sum > 12) { results.add(Field); } return this; } public String toString() { return "Chuck-a-Luck"; } } /** An instance of AbstractChuckALuck that uses alternate rules. * * WAGER DESCRIPTION * Big The total score will be 12 or higher and not a triple. * Small The total score will be 9 or lower and not a triple. */ class AlternateChuckALuck extends AbstractChuckALuck { public AlternateChuckALuck(Die[] dice) { super(dice); } public AlternateChuckALuck analyzeDice() { final Integer v1 = dice[0].valueOf(), v2 = dice[1].valueOf(), v3 = dice[2].valueOf(); final int sum = v1 + v2 + v3; results.clear(); // For "Single" wagers. results.add(v1.toString()); results.add(v2.toString()); results.add(v3.toString()); if (v1 == v2 && v2 == v3) { results.add(Triple); } else if (sum >= 12) { // ALTERNATE RULE results.add(Big); } else if (sum <= 9 ) { // ALTERNATE RULE results.add(Small); } if (sum < 8 || sum > 12) { results.add(Field); } return this; } public String toString() { return "Alternate Chuck-a-Luck"; } } /********/ /* DICE */ /********/ class AscendingDiceComparator implements Comparator<Die> { public final int compare(Die d1, Die d2) { return d1.valueOf().compareTo(d2.valueOf()); } } class DescendingDiceComparator implements Comparator<Die> { public final int compare(Die d1, Die d2) { return -1 * d1.valueOf().compareTo(d2.valueOf()); } } class AsciiDie3x7 { public static final String[] sides = { "---------\n" + "| |\n" + "| * |\n" + "| |\n" + "---------" , "---------\n" + "| * |\n" + "| |\n" + "| * |\n" + "---------" , "---------\n" + "| * |\n" + "| * |\n" + "| * |\n" + "---------" , "---------\n" + "| * * |\n" + "| |\n" + "| * * |\n" + "---------" , "---------\n" + "| * * |\n" + "| * |\n" + "| * * |\n" + "---------" , "---------\n" + "| * * |\n" + "| * * |\n" + "| * * |\n" + "---------" , "---------\n" + "| * * |\n" + "| * * * |\n" + "| * * |\n" + "---------" , "---------\n" + "| * * * |\n" + "| * * |\n" + "| * * * |\n" + "---------" , "---------\n" + "| * * * |\n" + "| * * * |\n" + "| * * * |\n" + "---------" , "---------\n" + "| ***** |\n" + "| |\n" + "| ***** |\n" + "---------" , "---------\n" + "| ***** |\n" + "| * |\n" + "| ***** |\n" + "---------" , "---------\n" + "| ***** |\n" + "| * * |\n" + "| ***** |\n" + "---------" , "---------\n" + "| ***** |\n" + "| * * * |\n" + "| ***** |\n" + "---------" , "---------\n" + "|*******|\n" + "| |\n" + "|*******|\n" + "---------" , "---------\n" + "|*******|\n" + "| * |\n" + "|*******|\n" + "---------" , "---------\n" + "|*******|\n" + "|* *|\n" + "|*******|\n" + "---------" , "---------\n" + "|*******|\n" + "|* * *|\n" + "|*******|\n" + "---------" , "---------\n" + "|*** ***|\n" + "|*** ***|\n" + "|*** ***|\n" + "---------" , "---------\n" + "|*** ***|\n" + "|*******|\n" + "|*** ***|\n" + "---------" , "---------\n" + "|*******|\n" + "|*** ***|\n" + "|*******|\n" + "---------" , "---------\n" + "|*******|\n" + "|*******|\n" + "|*******|\n" + "---------" }; } abstract class AbstractDie implements Die { public static Comparator<Die> ascendingComparator = new AscendingDiceComparator(); public static Comparator<Die> descendingComparator = new DescendingDiceComparator(); private Integer value; protected AbstractDie() { value = nextValue(); } public abstract int numberOfSides(); public final Die roll() { value = nextValue(); return this; } public String toString() { return value.toString(); } public final Integer valueOf() { return value; } protected abstract int nextValue(); } abstract class AbstractFairDie extends AbstractDie { protected final int nextValue() { return (int)(1 + Math.random() * numberOfSides()); } } class FairSixSidedDie extends AbstractFairDie { public int numberOfSides() { return 6; } } class FairSixSidedAsciiDie extends FairSixSidedDie { public String toString() { return AsciiDie3x7.sides[valueOf() - 1]; } } class Dodecahedron extends AbstractFairDie { public int numberOfSides() { return 12; } public String toString() { return AsciiDie3x7.sides[valueOf() - 1]; } }