Chuck-a-luck

/** Chuck-a-luck and Exercises.
 *
 *  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
 *
 *    This is the version of Chuck-a-luck that Mr. Spurgeon presented to his
 *    AP Computer Science A class on Tuesday, 12 February 2019. Students had the
 *    entire class period (45 minutes) to attempt the exercises in teams of 2 or 3.
 *    Each team used one computer. Team members took turns making changes to the
 *    source code while the other team members watched and assisted. At the end of
 *    the class period each team sent a single working program containing answers
 *    to the exercises they completed to Mr. Spurgeon via email.
 *
 *  CHANGE SUMMARY
 *
 *  - Look-and-Feel.
 *      Changed ASCII die faces (reduced size).
 *  - Interfaces (NEW).
 *      Added Die.
 *      Added CarnivalGame.
 *      Added DiceGame.
 *  - Classes.
 *      Removed Dice: obsoleted by new classes.
 *      Changed ChuckALuck: no longer public; added constructor.
 *      Added AbstractDie.
 *      Added AbstractFairDie.
 *      Added FairSixSidedDie.
 *      Added FairSixSidedAsciiDie.
 *      Added Carny.
 *      Added Dealer (public).
 *  - Exercises (NEW).
 *      Added exercises 1-5.
 *
 *  NOTE ON THE EXERCISES
 *
 *    The five exercises found at the bottom of this page are somewhat vaguely
 *    worded and can be answered in many different ways. The intent is to
 *    encourage students to be creative and ask questions when necessary. Real-
 *    world programming rarely begins with a clean slate or a perfect program.
 *    In addition to providing exposure to a variety of object-oriented Java
 *    programming constructs and techniques--interfaces, constructors, inheritance,
 *    abstract classes and methods, etc.--the exercises are designed to provide
 *    practice that is needed to develop practical skills including: having
 *    constructive conversations with bearers of problems, becoming familiar with
 *    existing source code that is far from perfect, working within constraints,
 *    and identifying and considering alternate solutions to problems.
 */
import java.util.*;

interface Die
{
    Die roll();
    Integer valueOf();
}

interface CarnivalGame
{
    CarnivalGame recordBets(String[] bets);
    void announceWinners();
}

interface DiceGame
{
    DiceGame analyzeDice();
    DiceGame rollDice();
    DiceGame showDice();
}

abstract class AbstractDie implements Die
{
    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
{
    private 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" +
        "---------"
    };
    public String toString() {
        return sides[valueOf() - 1];
    }
}

/** 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.
 *
 *  Source: https://en.wikipedia.org/wiki/Chuck-a-luck
 * 
 *  WAGERS
 * 
 *  Single: A specific number will appear.
 *  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).
 */
class ChuckALuck implements DiceGame, CarnivalGame
{
    public static final String
        Triple = "Triple", Big = "Big", Small = "Small", Field = "Field";
    private final Die[] dice;
    private final List<String> results = new LinkedList<String>();
    private String[] bets;

    public ChuckALuck(FairSixSidedDie[] dice) {
        if (dice.length != 3) {
            throw new IllegalArgumentException("Wrong number of dice.");
        }
        this.dice = 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();
        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 void announceWinners() {
        for (int i = 0; i < bets.length; i++) {
            String bet = bets[i];
            if (results.indexOf(bet) >= 0) {
                System.out.println("Bet " + (i + 1) + " (" + bet + ") wins!");
            }
        }
    }
    public ChuckALuck recordBets(String[] bets) {
        this.bets = bets;
        return this;
    }
    public ChuckALuck rollDice() {
        for (Die die : dice) die.roll();
        return this;
    }
    public ChuckALuck showDice() {
        for (Die die : dice) System.out.println(die);
        return this;
    }
    public String toString() {
        return "Chuck-a-luck";
    }
}

final class Carny
{
    public static void acceptBets(CarnivalGame game, String[] bets) {
        game.recordBets(bets);
    }
    public static void payWinners(CarnivalGame game) {
        game.announceWinners();
    }
    public static void playGame(DiceGame game) {
        game.rollDice();
        game.showDice();
        game.analyzeDice();
    }
}

public class Dealer
{
    private static final FairSixSidedDie[] diceCage = {
        new FairSixSidedDie(),
        new FairSixSidedDie(),
        new FairSixSidedDie()
    };
    public static void main(String args[]) {
        final ChuckALuck game = new ChuckALuck(diceCage);
        System.out.println(game);
        Carny.acceptBets(game, args);
        Carny.playGame(game);
        Carny.payWinners(game);
    }
}

Exercises

  1. Change the program above so that it displays ASCII images of die faces instead of their values.
  2. Create a class called Dodecahedron and use it in place of FairSixSidedDie.
  3. Create a class called AlternateChuckALuck that defines analyzeDice based on the alternate wager conditions described above.
  4. Create a class called AbstractChuckALuck where analyzeDice is abstract. Define ChuckALuck and AlternateChuckALuck such that they both extend AbstractChuckALuck.
  5. Create a different type of dice game that also implements the DiceGame and CarnivalGame interfaces. Modify the Dealer class so that it plays either your new game or Chuck-A-Luck depending on the value of the first command line argument.