Java Arena
Step 6 of 8

A practice app, part 1: the logic

This step and the next build a larger application, in the way step 5 described: the logic first, in classes that know nothing about windows, and then the views on top of them.

What the app does

The app trains the capitals of countries, with cards:

  • The user adds cards: a country and its capital, such as Peru and Lima.
  • The user trains: the app shows a country, the user types its capital, and the app says whether that was right and goes on to the next card. It counts the right and the wrong answers.
  • The cards come in the order they were added, and after the last one the first one comes again.

A real trainer might shuffle the cards with Random (module 46). A fixed order keeps every run the same, which makes the app easy to check, and shuffling can be added later in one place: the logic.

Three classes

The logic has two jobs, so it gets two main classes. A CardBox stores the cards, each a small Card object with a country and its capital. A PracticeRound runs the training: whose turn it is, and the counts.

The diagram as text
  • Card, a class
    • private field country: String
    • private field capital: String
    • public constructor Card(country: String, capital: String)
    • public method getCountry(): String
    • public method getCapital(): String
  • CardBox, a class
    • private field cards: ArrayList<Card>
    • public constructor CardBox()
    • public method add(country: String, capital: String): boolean
    • public method capitalOf(country: String): String
    • public method countryAt(index: int): String
    • public method size(): int
  • PracticeRound, a class
    • private field box: CardBox
    • private field next: int
    • private field right: int
    • private field wrong: int
    • public constructor PracticeRound(box: CardBox)
    • public method question(): String
    • public method answer(text: String): boolean
    • public method getRight(): int
    • public method getWrong(): int

Connections:

  • Each CardBox has any number of Card objects.
  • Each PracticeRound has one CardBox object.

The window will use only some of these: the view for adding cards calls add and size, and the training view calls question, answer, the getters, and capitalOf for its feedback. Deciding what the logic offers before any window exists is the point of starting with it.

The card box

Main.java
public class Main {
    public static void main(String[] args) {
        CardBox box = new CardBox();
        System.out.println(box.add("Peru", "Lima"));
        System.out.println(box.add("Kenya", " Nairobi "));
        System.out.println(box.add("Peru", "Cusco"));
        System.out.println(box.add(" ", "Oslo"));
        System.out.println(box.size() + " cards, the first is " + box.countryAt(0));
        System.out.println(box.capitalOf("Kenya") + " " + box.capitalOf("Chile"));
    }
}
CardBox.java
import java.util.ArrayList;

public class CardBox {
    private ArrayList<Card> cards;

    public CardBox() {
        this.cards = new ArrayList<>();
    }

    public boolean add(String country, String capital) {
        String name = country.trim();
        String city = capital.trim();
        if (name.isEmpty() || city.isEmpty() || this.capitalOf(name) != null) {
            return false;
        }
        this.cards.add(new Card(name, city));
        return true;
    }

    public String capitalOf(String country) {
        for (Card card : this.cards) {
            if (card.getCountry().equals(country)) {
                return card.getCapital();
            }
        }
        return null;
    }

    public String countryAt(int index) {
        return this.cards.get(index).getCountry();
    }

    public int size() {
        return this.cards.size();
    }
}
Card.java
public class Card {
    private String country;
    private String capital;

    public Card(String country, String capital) {
        this.country = country;
        this.capital = capital;
    }

    public String getCountry() {
        return this.country;
    }

    public String getCapital() {
        return this.capital;
    }
}
Output
true
true
false
false
2 cards, the first is Peru
Nairobi null
  • The box keeps its cards in one ArrayList of Card objects, so they stay in the order they were added, and countryAt gives a card's country by its place in that order. A HashMap from country to capital would find a capital faster, but its order is its own (module 31), so the training would jump around.
  • capitalOf walks through the cards and returns the capital of the first card with that country, or null when no card has it, as HashMap's get would. add uses it to find out whether a country is in the box already.
  • add refuses a card with an empty country or capital, and a country that is in the box already, and returns false for them. The view will turn that false into a message, but the rule itself lives here.
  • The texts are trimmed before they're stored, so " Nairobi " becomes Nairobi.

The practice round

A PracticeRound gets the box in its constructor, the way step 5's views got the jar. It keeps next, the index of the card whose turn it is. It starts at 0, and each answer moves it on by one; after the last card it goes back to 0. Since the round asks the box each time, cards added during the training simply join it.

Your task

Challenge 1 of 3

Write the class PracticeRound in PracticeRound.java. CardBox is ready.

  • a constructor PracticeRound(CardBox box)
  • question(): returns the country of the card whose turn it is, or null when the box is empty
  • answer(String text): when the box is empty, returns false and counts nothing. Otherwise the answer is right when text, trimmed, is the capital of the card whose turn it is, ignoring upper and lower case (equalsIgnoreCase). It counts the answer as right or wrong, moves the turn to the next card (after the last card, back to the first), and returns true for a right answer, false for a wrong one.
  • getRight() and getWrong(): the numbers of right and wrong answers so far

The check runs:

CardBox box = new CardBox();
box.add("Peru", "Lima");
box.add("Kenya", "Nairobi");
PracticeRound round = new PracticeRound(box);
System.out.println(round.question());
System.out.println(round.answer(" lima"));
System.out.println(round.question());
System.out.println(round.answer("Mombasa"));
System.out.println(round.question() + " " + round.getRight() + " " + round.getWrong());

and expects:

Peru
true
Kenya
false
Peru 1 1

Challenge 2

A trainer should help with the capitals that went wrong. Add a method missed() to PracticeRound (the class from the first task): it returns an ArrayList<String> of the countries whose capital was answered wrong, each country once, in the order of their first wrong answer. A country stays on the list even when it's answered right later. With an empty box, answer counts nothing, so nothing is added to the list either. The check runs:

CardBox box = new CardBox();
box.add("Peru", "Lima");
box.add("Kenya", "Nairobi");
PracticeRound round = new PracticeRound(box);
round.answer("Cusco");
round.answer("Nairobi");
round.answer("Arequipa");
System.out.println(round.missed());

and expects:

[Peru]

Challenge 3

The card box and the round together, with a card added in the middle of the training. What does the program print? CardBox is the class of this step's text, and PracticeRound the one from the first task.