// Game public class Game { private final Grid grid; private final Map<Mark, Player> players; private final Mark playingMark; public Game(Map<Mark, Player> players) { this(new Grid(), players, Mark.x()); } public Game(Grid grid, Map<Mark, Player> players, Mark playingMark) { this.grid = grid; this.playingMark = playingMark; this.players = players; } public Game submit(Placement placement) { placement.validate(playingMark, grid, this); return this; } public Game validPlacement(Placement placement) { placement.place(grid, this); return this; } public Game gridUpdated(Grid newGrid) { Mark nextPlayingMark = playingMark.opponent(); Game newGame = new Game(newGrid, players, nextPlayingMark); for(Player p : players.values()) p.updatedGameWithCurrentPlayer(newGame, players.get(nextPlayingMark)); return this; } public Game invalidPlacementByMark(Placement placement, Mark mark) { players.get(mark).invalidPlacement(placement); return this; } public Game printOn(GameMedia gameMedia) { grid.printOn(gameMedia); return this; } public Game start() { players.get(playingMark).start(this); return this; } } // Placement public class Placement { private final Mark mark; private final Position position; public Placement(Mark mark, Position position) { this.mark = mark; this.position = position; } public Placement validate(Mark playingMark, Grid grid, Game game) { mark.validateAndContinue(playingMark, this, grid, game); return this; } public Placement validMarkContinueWith(Grid grid, Game game) { grid.validate(position, this, game); return this; } public Placement validPositionContinueWith(Mark playingMark, Game game) { mark.validate(playingMark, this, game); return this; } public Placement validPosition(Game game) { game.validPlacement(this); return this; } public Placement invalidPosition(Game game) { game.invalidPlacementByMark(this, mark); return this; } public Placement validMark(Game game) { game.validPlacement(this); return this; } public Placement invalidMark(Game game) { game.invalidPlacementByMark(this, mark); return this; } public Placement place(Grid grid, Game game) { grid.place(mark, position, game); return this; } public Placement printOn(PlacementMedia placementMedia) { placementMedia.printMarkAndPosition(mark, position); return this; } } // Mark public class Mark { private static Mark X = new Mark("X"); private static Mark O = new Mark("O"); private static Mark Empty = new Mark(" "); private final String symbol; private Mark(String symbol) { this.symbol = symbol; } public void printOn(MarkMedia markMedia) { markMedia.printMark(symbol); } public static Mark x() { return Mark.X; } public static Mark o() { return Mark.O; } public static Mark empty() { return Mark.Empty; } public Mark opponent() { return this.equals(x()) ? Mark.o() : Mark.x(); } public Mark validateAndContinue(Mark playingMark, Placement placement, Grid grid, Game game) { if(this.equals(playingMark)) { placement.validMarkContinueWith(grid, game); } else { placement.invalidMark(game); } return this; } public Mark validate(Mark playingMark, Placement placement, Game game) { if(this.equals(playingMark)) { placement.validMark(game); } else { placement.invalidMark(game); } return this; } } // Grid public class Grid { private final HashMap<Position, Mark> gridPositions; private final Mark nullMark; public Grid() { this(new HashMap<Position, Mark>()); } public Grid(HashMap<Position, Mark> gridPositions) { this.nullMark = Mark.empty(); this.gridPositions = gridPositions; } public Grid validate(Position position, Placement placement, Game game) { if(isPositionAvailable(position)) { placement.validPosition(game); } else { placement.invalidPosition(game); } return this; } public Grid validateAndContinue(Position position, Placement placement, Mark playingMark, Game game) { if(isPositionAvailable(position)) { placement.validPositionContinueWith(playingMark, game); } else { placement.invalidPosition(game); } return this; } private boolean isPositionAvailable(Position position) { return !gridPositions.containsKey(position) && gridPositions.get(position) == null; } public Grid place(Mark mark, Position position, Game game) { game.gridUpdated(new Grid(newPositionsWith(mark, position))); return this; } private HashMap<Position, Mark> newPositionsWith(Mark mark, Position position) { HashMap<Position, Mark> newPositions = new HashMap<Position, Mark>(gridPositions); newPositions.put(position, mark); return newPositions; } public Grid printOn(GridMedia gridMedia) { gridMedia.printRow(markAt(Position.TopLeft()), markAt(Position.TopCenter()), markAt(Position.TopRight())); gridMedia.printRowDivider(); gridMedia.printRow(markAt(Position.MiddleLeft()), markAt(Position.MiddleCenter()), markAt(Position.MiddleRight())); gridMedia.printRowDivider(); gridMedia.printRow(markAt(Position.BottomLeft()), markAt(Position.BottomCenter()), markAt(Position.BottomRight())); gridMedia.printBreak(); return this; } private Mark markAt(Position position) { Mark mark = gridPositions.get(position); return mark != null ? mark : nullMark; } } // ConsolePlayer public class ConsolePlayer implements PlacementMedia, MarkMedia, PositionMedia, Player { private final GameMedia gameMedia; private String symbol; private String position; private PlannedPlacements plannedPlacements; public ConsolePlayer(PlannedPlacements plannedPlacements) { this.plannedPlacements = plannedPlacements; gameMedia = new ConsoleGameMedia(); } @Override public void invalidPlacement(Placement placement) { placement.printOn(this); System.out.printf("Invalid placement: %s at %s", symbol, position); System.out.println(); System.out.println(); } @Override public void printMarkAndPosition(Mark mark, Position position) { mark.printOn(this); position.printOn(this); } @Override public void printPosition(String position) { this.position = position; } @Override public void printMark(String symbol) { this.symbol = symbol; } @Override public void updatedGameWithCurrentPlayer(Game newGame, Player player) { newGame.printOn(gameMedia); plannedPlacements.iAmNotAnAISoDeferNextPlayToPlan(newGame); } @Override public void start(Game game) { plannedPlacements.iAmNotAnAISoDeferNextPlayToPlan(game); } } // Main public class Main { public static void main(String[] args) { new Main().run(); } private final Game game; public Main() { Map<Mark, Player> players = new HashMap<Mark, Player>(); PlannedPlacements plannedPlacements = new PlannedPlacements(); players.put(Mark.x(), new ConsolePlayer(plannedPlacements)); players.put(Mark.o(), new ConsolePlayer(plannedPlacements)); this.game = new Game(players); } private void run() { game.start(); } public static class PlannedPlacements { private final LinkedList<Placement> plannedPlacements; public PlannedPlacements() { plannedPlacements = new LinkedList<Placement>(); plannedPlacements.add(new Placement(Mark.x(), Position.TopLeft())); plannedPlacements.add(new Placement(Mark.o(), Position.MiddleCenter())); plannedPlacements.add(new Placement(Mark.o(), Position.TopRight())); // Try out of turn player, valid position plannedPlacements.add(new Placement(Mark.x(), Position.TopLeft())); // Try correct player, invalid position } public void iAmNotAnAISoDeferNextPlayToPlan(Game game) { if(plannedPlacements.isEmpty()) System.exit(0); game.submit(plannedPlacements.pop()); } } } // Output X | | ---+---+--- | | ---+---+--- | | X | | ---+---+--- | O | ---+---+--- | | Invalid placement: O at TR X | | ---+---+--- | O | ---+---+--- | | Invalid placement: X at TL X | | ---+---+--- | | ---+---+--- | |