Java Arena
Step 5 of 6

A larger program across packages

Step 4's quiz asked a fixed set of questions once. This step's quiz is a whole small application: the user adds questions, players take turns, and the program remembers who did best. It has four classes in three packages, and Main:

  • quiz.domain: Question, as before, and a new class Player, with a name and points.
  • quiz.logic: Quiz, which keeps the questions and the players, checks the answers and finds the best player.
  • quiz.ui: TextUI, which reads the commands.

The commands, each on a line of its own:

  • add, then three lines: the question, its answer and its points; it prints how many questions there are
  • play, then the player's name, then a reply to each question in turn
  • best, for the player with the most points so far
  • quit
Main.java
import java.util.Scanner;
import quiz.logic.Quiz;
import quiz.ui.TextUI;

public class Main {
    public static void main(String[] args) {
        TextUI ui = new TextUI(new Scanner(System.in), new Quiz());
        ui.start();
    }
}
quiz/ui/TextUI.java
package quiz.ui;

import java.util.Scanner;
import quiz.domain.Player;
import quiz.domain.Question;
import quiz.logic.Quiz;

public class TextUI {
    private Scanner scanner;
    private Quiz quiz;

    public TextUI(Scanner scanner, Quiz quiz) {
        this.scanner = scanner;
        this.quiz = quiz;
    }

    public void start() {
        while (true) {
            System.out.print("> ");
            String command = this.scanner.nextLine();
            if (command.equals("quit")) {
                System.out.println("Bye!");
                break;
            }
            if (command.equals("add")) {
                this.addQuestion();
            } else if (command.equals("play")) {
                this.play();
            } else if (command.equals("best")) {
                this.printBest();
            } else {
                System.out.println("Unknown command: " + command);
            }
        }
    }

    private void addQuestion() {
        String prompt = this.scanner.nextLine();
        String answer = this.scanner.nextLine();
        int points = Integer.valueOf(this.scanner.nextLine());
        this.quiz.addQuestion(new Question(prompt, answer, points));
        System.out.println("Questions: " + this.quiz.getQuestions().size());
    }

    private void play() {
        String name = this.scanner.nextLine();
        Player player = this.quiz.newPlayer(name);
        for (Question question : this.quiz.getQuestions()) {
            System.out.println(question);
            String reply = this.scanner.nextLine();
            if (this.quiz.answer(player, question, reply)) {
                System.out.println("Right!");
            } else {
                System.out.println("Not quite.");
            }
        }
        System.out.println(player);
    }

    private void printBest() {
        Player best = this.quiz.best();
        if (best == null) {
            System.out.println("No one has played yet");
        } else {
            System.out.println("Best: " + best);
        }
    }
}
quiz/logic/Quiz.java
package quiz.logic;

import java.util.ArrayList;
import quiz.domain.Player;
import quiz.domain.Question;

public class Quiz {
    private ArrayList<Question> questions;
    private ArrayList<Player> players;

    public Quiz() {
        this.questions = new ArrayList<>();
        this.players = new ArrayList<>();
    }

    public void addQuestion(Question question) {
        this.questions.add(question);
    }

    public ArrayList<Question> getQuestions() {
        return this.questions;
    }

    public Player newPlayer(String name) {
        Player player = new Player(name);
        this.players.add(player);
        return player;
    }

    public boolean answer(Player player, Question question, String reply) {
        if (question.isRight(reply)) {
            player.addPoints(question.getPoints());
            return true;
        }
        return false;
    }

    public Player best() {
        Player best = null;
        for (Player player : this.players) {
            if (best == null || player.getPoints() > best.getPoints()) {
                best = player;
            }
        }
        return best;
    }
}
quiz/domain/Question.java
package quiz.domain;

public class Question {
    private String prompt;
    private String answer;
    private int points;

    public Question(String prompt, String answer, int points) {
        this.prompt = prompt;
        this.answer = answer;
        this.points = points;
    }

    public boolean isRight(String reply) {
        return reply.trim().equalsIgnoreCase(this.answer);
    }

    public int getPoints() {
        return this.points;
    }

    @Override
    public String toString() {
        return this.prompt + " (" + this.points + " points)";
    }
}
quiz/domain/Player.java
package quiz.domain;

public class Player {
    private String name;
    private int points;

    public Player(String name) {
        this.name = name;
        this.points = 0;
    }

    public void addPoints(int points) {
        this.points += points;
    }

    public int getPoints() {
        return this.points;
    }

    @Override
    public String toString() {
        return this.name + ", " + this.points + " points";
    }
}
Input
best
add
Which planet is the largest?
Jupiter
2
add
What is the chemical symbol for gold?
Au
3
play
Ada
jupiter
Au
play
Ben
Saturn
au
best
quit
Output
> No one has played yet
> Questions: 1
> Questions: 2
> Which planet is the largest? (2 points)
Right!
What is the chemical symbol for gold? (3 points)
Right!
Ada, 5 points
> Which planet is the largest? (2 points)
Not quite.
What is the chemical symbol for gold? (3 points)
Right!
Ben, 3 points
> Best: Ada, 5 points
> Bye!

Following a command through the packages

Here's what happens for play, package by package:

  1. TextUI reads the name and asks the logic for a new player: this.quiz.newPlayer(name). Quiz creates the Player, a domain object, keeps it in its list of players, and returns it.
  2. For each question, TextUI prints the question and reads the reply. this.quiz.answer(player, question, reply) decides: Quiz asks the Question whether the reply is right, and if it is, gives the Player the points.
  3. TextUI prints Right! or Not quite., and at the end the player.

Each package does its own part. The ui talks with the user, the logic decides, and the domain holds the data and knows its own rules. TextUI uses the domain objects only to show them and to pass them on: it never adds points itself.

Main is short again. It only makes the objects that the others need and starts the user interface. It imports only what it names itself: Scanner, Quiz and TextUI. Question and Player appear nowhere in Main.java, so they need no import there.

Private helper methods

In TextUI, start() only reads a command and picks what to do. The work of each command is in a method of its own: addQuestion(), play() and printBest(). They're private, because nothing outside TextUI needs them, and they keep start() short enough to read at a glance, however many commands the program gets.

Growing the program: find the package first

Before writing a new feature, decide where each part of it belongs. Say the quiz should list every player. The players are already kept by Quiz, so giving them out is the logic's part. Printing them is the user interface's part. That's your task.

Your task

Challenge 1 of 3

Add the command players to the quiz. It prints each player who has played, in the order they played, one per line, like Ada, 5 points, or No one has played yet if no one has. Split the work between the packages:

  • in Quiz.java, the method getPlayers(), which returns the list of players
  • in TextUI.java, the command, which uses getPlayers() and prints

For the input shown, the program prints:

> No one has played yet
> Questions: 1
> How many legs does a spider have? (2 points)
Right!
Ada, 2 points
> How many legs does a spider have? (2 points)
Not quite.
Ben, 0 points
> Ada, 2 points
Ben, 0 points
> Bye!

Challenge 2

A tailor's shop takes in clothes to alter, and its program is in three packages: tailor.domain with the class Garment, tailor.logic with the class Workroom, and tailor.ui with a TextUI. Main gives the user interface a Scanner that reads a String, as in module 25, so the program runs without anyone typing. What does it print?

Challenge 3

The shoe store's program gets its user interface. Shoe.java now also has getModel(), and Stock.java is the one from step 2. Main.java is ready. Two parts are yours:

  • In Stock.java, add sell(String model, int size): if the stock has a pair of that model (exactly as written) and size, it removes one such pair and returns true; otherwise it returns false. Stock prints nothing.
  • In shoestore/ui/TextUI.java, write the class TextUI, in the package shoestore.ui. Its constructor takes a Scanner and a Stock, and start() reads commands, with the prompt > before each one:
    • add: the next two lines are a model and a size. Add the pair to the stock, and print Added and the shoe, like Added Loafer, size 42.
    • sell: the next two lines are a model and a size. Sell a pair and print Sold Loafer, size 42, or print Not in stock: Loafer, size 42 if the stock has none.
    • size: the next line is a size. Print Pairs in size 42: and the number of pairs of that size.
    • quit: print Pairs left: and the number of pairs in the stock, and return.
    • Anything else: print Unknown command: and the command.

For the input shown, the program prints:

> Added Trail runner, size 42
> Added Loafer, size 42
> Added Trail runner, size 39
> Pairs in size 42: 2
> Not in stock: Loafer, size 40
> Sold Loafer, size 42
> Pairs in size 42: 1
> Pairs left: 2