I made a game of tic-tac-toe, but as it turned out when I started writing tests, testing it through a unit with tests was simply not possible. Help fix so that the principles of the PLO are respected.
A game:
interface FirstMove { void firstMove(); } interface Winner { Gamers getWinner(); } public interface Round extends FirstMove, Winner { } public class Game implements Round { /** * Desc for play. */ private Board desc = new Desc(); /** * Winner. */ private Gamers winner; /** * For console input. */ private In input = new Input(); /** * List contain all gamer: bot and user. */ private ArrayList<Gamers> gamers = new ArrayList<>(); /** * Util class contain algorithm determining winner. */ private Validation valid = new ValidationWinnerUtil(); /** * Getter for winner. * * @return gamer which win. */ @Override public Gamers getWinner() { return this.winner; } /** * Check correct move. * * @param player player which move. * @return true if move success. False if move fail */ private boolean move(Gamers player) { player.setPosit(); if (this.desc.getDesc()[player.getPosit().getY()][player.getPosit().getX()] == ' ') { this.desc.getDesc()[player.getPosit().getY()][player.getPosit().getX()] = player.getColor(); return true; } else { System.err.println("Так ходить нельзя!"); return false; } } /** * Determines fist move. */ @Override public void firstMove() { this.desc.initDescSize(); System.out.println("Кто ходит первым? Enter: Bot / I"); if (this.input.getStrInput().toUpperCase().equals("BOT")) { this.fstMoveBot(); } else { this.fstMoveUsr(); } Printer.printDesc(this.desc.getDesc()); this.loopMoves(); } /** * Configurable statement game if bot move first. */ private void fstMoveBot() { this.gamers.add(new Bot(Colors.X.getColor())); this.gamers.add(new User(Colors.O.getColor())); } /** * Configurable statement game if user choice move first. */ private void fstMoveUsr() { this.gamers.add(new User(Colors.X.getColor())); this.gamers.add(new Bot(Colors.O.getColor())); } /** * Loop game process. */ private void loopMoves() { Gamers winner = null; while (this.valid.gameCanGoOn(this.desc.getDesc())) { for (Gamers gamer : this.gamers) { if (this.valid.gameCanGoOn(this.desc.getDesc()) && this.move(gamer) ) { Printer.printDesc(this.desc.getDesc()); winner = gamer; } else if (this.valid.gameCanGoOn(this.desc.getDesc())) { this.mistakeMove(gamer); Printer.printDesc(this.desc.getDesc()); } } } this.initResultGame(winner); } /** * Give more chance when player which mistake - try move in busy cell. * * @param gamer player which mistake. */ private void mistakeMove(Gamers gamer) { while (!this.move(gamer)) { mistakeMove(gamer); } } /** * Init result game. * * @param winner gamer for estimated award. * @see TickTack#winners */ private void initResultGame(Gamers winner) { if (!this.valid.emptyCellExist(this.desc.getDesc()) && !this.valid.winnerDetermines(this.desc.getDesc()) ) { System.out.println("Ничья."); } else if (this.valid.winnerDetermines(this.desc.getDesc())) { this.winner = winner; System.out.println(format("Победитель: %s", winner.getColor())); } } } Board:
interface StatementDesk { /** * Getter for desc. Init infoDesc. * @return desc. */ char[][] getDesc(); } interface DescSize { /** * Init desc size in starting game. */ void initDescSize(); } public interface Board extends StatementDesk, DescSize, StubInputInterface { } public class Desc implements Board { /** * Information about statement desc for bot. */ private static char[][] infoDesc; /** * Input for get console in. */ private In input = new Input(); /** * Desc for game. */ private char[][] desc = new char[3][3]; /** * Pointer on desc. It's information for bot about statement desc. * * @return current statement desc. */ public static char[][] getInfoDesc() { return infoDesc; } /** * Use for test class StubInput. * * @param input emulation console input stream. */ @Override public void setInput(In input) { this.input = input; } /** * Getter for desc. Init infoDesc. * * @return desc. */ @Override public char[][] getDesc() { infoDesc = this.desc; return this.desc; } /** * For choice desc size. */ @Override public void initDescSize() { System.out.println("Хотите использовать стандартный размер поля: y/n"); String answer = this.input.getStrInput(); if (answer.equals("y")) { System.out.println("Установлен стандартный размер поля: 3/3"); } else if (answer.equals("n")) { this.initNonstandardDesc(); } this.initContentDesc(); } /** * Install desc nonstandard size. */ private void initNonstandardDesc() { System.out.println("Введите размер сторон:"); int i = this.input.getNumInput(); this.desc = new char[i][i]; System.out.println(format("Установлен размер поля: %s/%s", i, i)); } /** * Fill desc empty call. Empty is ' '. */ private void initContentDesc() { for (int i = 0; i != this.desc.length; i++) { for (int j = 0; j != this.desc.length; j++) { this.desc[j][i] = ' '; } } } } Input from console:
public class Input implements In { /** * Input stream to console. */ private Scanner scanner = new Scanner(System.in); /** * Console input for string. * * @return input string. */ @Override public String getStrInput() { return this.scanner.next(); } /** * Console input for int. * * @return input int. */ @Override public int getNumInput() { return this.scanner.nextInt(); } } User:
public class User implements Gamers, StubInputInterface { /** * Side on current game. May be 'X' or 'O'. */ private final char color; /** * Position for move. */ private Position posit = new Posit(); /** * Console input for get data from user. */ private In input = new Input(); /** * Default constructor. * * @param color 'X' or 'O'. */ public User(char color) { this.color = color; } /** * Getter for position. * * @return position. */ @Override public Position getPosit() { return this.posit; } /** * Getter for color. * * @return color. */ @Override public char getColor() { return this.color; } /** * For get data about move position from user by console input. */ @Override public void setPosit() { System.out.println("По вертикали: "); this.posit.setX(this.input.getNumInput()); System.out.println("По горизонтали: "); this.posit.setY(this.input.getNumInput()); } /** * This setter for tests. He need for use StubInput class. * * @param input emulation console input. */ @Override public void setInput(In input) { this.input = input; } } Computer user:
public class Bot implements Gamers { /** * Side for battle. */ private final char color; /** * Current position. */ private Position posit; /** * Default constructor. * * @param color side battle. Init only once in begin game. */ public Bot(char color) { this.color = color; } /** * Getter gor color. * * @return color. May be 'X' or 'O'. */ @Override public char getColor() { return this.color; } /** * Getter for current position. * * @return current position. */ @Override public Position getPosit() { return this.posit; } /** * Setter for position. */ @Override public void setPosit() { this.posit = this.generatePos(); } /** * Generator new positions. * * @return position. */ private Position generatePos() { char[][] desc = Desc.getInfoDesc(); for (int i = 0; i < desc.length; i++) { for (int j = 0; j < desc.length; j++) { if (desc[j][i] == ' ') { return new Posit(i, j); } } } System.err.println("Что-то пошло не так"); return new Posit(); } } The class that creates a series of games and that makes the game up to five victories:
class TickTack { /** * Contain gamers every time his win. */ private ArrayList<Gamers> winners = new ArrayList<>(); /** * Contain all games. */ private ArrayList<Round> games = new ArrayList<>(); /** * Default constructor. */ TickTack() { this.initGames(); } /** * Init all games. */ private void initGames() { for (int i = 0; i < 10; i++) { this.games.add(new Game()); } } /** * Loop games to 5 points. * After 5 wins one of players - break. */ void start() { for (Round game : this.games) { if (this.resultAllGames(winners).equals("")) { game.firstMove(); addWinner(game); } else { System.out.println(String.format( "Победитель: %s", this.resultAllGames(winners))); break; } } } /** * Add winner in list goals. * * @param game held game. */ private void addWinner(Round game) { if (game.getWinner() != null) this.winners.add(game.getWinner()); } /** * Check wins to five points. * * @param users list involved gamers. * @return "" if 5 points not success. if 5 points success name winner. */ private String resultAllGames(ArrayList<Gamers> users) { int u = 0, b = 0; for (Gamers obj : users) { if (obj instanceof User) { u++; if (u == 5) return "user"; } else { b++; if (b == 5) return "bot"; } } return ""; } } Here it is necessary to correct it in the style of the OOD. Well corrected myself need to tell how and what. Here it is.