Chuck-a-luck or Risk

/** 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.*;

/*  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" +
      "\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.";

    /* 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 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
            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);

        // 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]);
    }
}

/*********/
/* 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 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];
    }
}