package cs3500.connectn.consoleui; import cs3500.connectn.model.Model; import cs3500.connectn.model.Player; import java.io.IOException; import java.io.InputStream; import java.util.*; import static java.util.Objects.requireNonNull; /** * Controller for the Connect N console UI. Mediates between the * view and the model by taking user input and acting on the view, and then * taking information from the view and showing it to the user. */ public final class Controller { /** * Constructs a controller for playing the given game model. Uses stdin and * stdout as the user's console. * * @param model the game to play */ public Controller(Model model) { this(model, System.in, System.out); } /** * Constructs a controller for playing the given game model, with the given * input and output for communicating with the user. * * @param model0 the game to play * @param in where to read user input from * @param out where to send output to for the user to see */ public Controller(Model model0, InputStream in, Appendable out) { model = requireNonNull(model0); vm = adaptModelToViewModel(model); view = ConsoleView.builder() .input(new Scanner(in)) .output(out) .build(); } private final Model model; private final ViewModel vm; private final View view; /** * Runs the game using this controller until it's over. * * @throws IOException if there's an IO error */ public void run() throws IOException { while (! model.isGameOver()) { view.draw(vm); oneMove(); } view.draw(vm); if (model.getStatus() == Model.Status.Stalemate) { view.announceStalemate(); } else { view.announceWinner(model.getWinner()); } } /** * Runs one game round consisting of asking the user for a move and then * performing it. * * @throws IOException if there's an IO error */ private void oneMove() throws IOException { Player who = model.getNextPlayer(); // This loop tries repeatedly to read user input and make a move until it // succeeds. If there is a problem either reading or executing, the // exception is caught, an informative message displayed, and then the // loop repeats to try again. for (;;) { try { int where = view.readMove(who); model.move(who, where); return; } catch (InputMismatchException e) { view.reportNotANumber(); } catch (Model.ColumnOutOfBoundsException e) { view.reportColumnIndexOOB(e.getMaxColumn()); } catch (Model.ColumnFullException e) { view.reportColumnFull(e.getColumn()); } catch (Model.IllegalMoveException e) { throw new RuntimeException("Unknown IllegalMoveException", e); } } } /** * Adapts a {@link Model} into a {@link ViewModel}. The adapted result shares * state with its adaptee. * * @param adaptee the {@code Model} to adapt * @return a {@code ViewModel} backed by {@code adaptee} */ private static ViewModel adaptModelToViewModel(Model adaptee) { return new ViewModel(adaptee.getWidth(), adaptee.getHeight()) { @Override public Player getPlayerAt(int x, int y) { return adaptee.getPlayerAt(x, y); } }; } }