Skip to content

Instantly share code, notes, and snippets.

@yadavvi91
Created July 27, 2017 09:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yadavvi91/91703f11b05cbc4659dd4325158e8d13 to your computer and use it in GitHub Desktop.
Save yadavvi91/91703f11b05cbc4659dd4325158e8d13 to your computer and use it in GitHub Desktop.
A stateful implementation of TicTacToe in Java.
package tictactoe.state;
import tictactoe.statemanager.StateManager;
public class Draw implements State {
private final StateManager manager;
public Draw(StateManager manager) {
this.manager = manager;
}
@Override
public void onEnterState() {
manager.printDraw();
}
@Override
public void onExitState() {
}
}
package tictactoe.state;
import tictactoe.statemanager.StateManager;
public class End implements State {
private final StateManager manager;
public End(StateManager manager) {
this.manager = manager;
}
@Override
public void onEnterState() {
manager.end();
}
@Override
public void onExitState() {
}
}
package tictactoe.state;
import tictactoe.Player;
public interface Input extends State {
int getRow();
int getColumn();
Player getPlayer();
}
package tictactoe;
public enum Player {
PROGRAM(1),
USER(2);
public int value;
Player(int value) {
this.value = value;
}
public static Player getPlayerFromValue(int value) {
for (Player player : Player.values()) {
if (player.value == value) return player;
}
return null;
}
}
package tictactoe.state;
import tictactoe.Player;
import tictactoe.statemanager.ProgramInputManager;
import tictactoe.statemanager.StateManager;
public class ProgramInput<T extends StateManager & ProgramInputManager> implements Input {
private final T manager;
private int row;
private int column;
public ProgramInput(T manager) {
this.manager = manager;
}
@Override
public void onEnterState() {
int row = manager.getProgramRow();
int column = manager.getProgramColumn();
while (!manager.isMoveValid(row, column)) {
row = manager.getProgramRow();
column = manager.getProgramColumn();
}
this.row = row;
this.column = column;
manager.makeAMove(this.row, this.column);
}
@Override
public void onExitState() {
manager.printGrid();
}
@Override
public int getRow() {
return row;
}
@Override
public int getColumn() {
return column;
}
@Override
public Player getPlayer() {
return Player.PROGRAM;
}
}
package tictactoe.statemanager;
public interface ProgramInputManager {
int getProgramRow();
int getProgramColumn();
}
package tictactoe.state;
import tictactoe.statemanager.StateManager;
public class Setup implements State {
private StateManager manager;
public Setup(StateManager manager) {
this.manager = manager;
}
@Override
public void onEnterState() {
this.manager.reset();
}
@Override
public void onExitState() {
}
}
package tictactoe.state;
public interface State {
void onEnterState();
void onExitState();
}
package tictactoe.statemanager;
import tictactoe.Player;
import tictactoe.state.State;
public interface StateManager {
void startEvents();
void nextEvent();
void changeState(State state);
boolean isMoveValid(int row, int column);
void makeAMove(int row, int column);
void printGrid();
void printWin();
void printDraw();
void setWinner(Player winner);
void reset();
void end();
}
package tictactoe;
import tictactoe.statemanager.StateManager;
import tictactoe.statemanager.TicTacToeManager;
import java.util.Scanner;
public class TicTacToe {
// 0 is available, 1 is for the program and 2 is for the user.
private static final int AVAILABLE = 0;
private static final int PROGRAM = Player.PROGRAM.value;
private static final int USER = Player.USER.value;
private final int[] grid;
private int userScore;
private int programScore;
private Player firstMover;
private int moves;
TicTacToe(Scanner scanner) {
grid = new int[9];
StateManager stateManager = new TicTacToeManager(this, scanner);
stateManager.startEvents();
}
public static void main(String[] args) {
TicTacToe ticTacToe = new TicTacToe(new Scanner(System.in));
}
public void setFirstPlayer(Player firstMover) {
this.firstMover = firstMover;
}
public boolean isMoveValid(int row, int column) {
int position = row * 3 + column;
return grid[position] == AVAILABLE;
}
public void makeAMove(int row, int column, Player currentPlayer) {
moves++;
grid[row * 3 + column] = currentPlayer.value;
}
public void printGrid() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
int moveBy = grid[i * 3 + j];
if (moveBy == AVAILABLE) {
builder.append(" ");
} else if (moveBy == firstMover.value) {
builder.append(" X ");
} else {
builder.append(" O ");
}
if (j == 2) {
builder.append("");
} else {
builder.append("|");
}
}
builder.append("\n");
if (i != 2) {
builder.append("-----------")
.append("\n");
}
}
System.out.println(builder.toString());
}
public boolean hasWon(int row, int column, int player) {
// Same row
int i;
for (i = 0; i < 3; i++) {
if (grid[row * 3 + i] != player) {
break;
}
}
if (i == 3) return true;
// Same column
for (i = 0; i < 3; i++) {
if (grid[i * 3 + column] != player) {
break;
}
}
if (i == 3) return true;
// Same diagonal
if ((grid[0] == player && grid[4] == player && grid[8] == player) ||
(grid[2] == player && grid[4] == player && grid[6] == player)) {
return true;
}
return false;
}
public boolean areAllMovesExhausted() {
return moves == 9;
}
public void setWinner(Player player) {
if (player == Player.USER) {
userScore++;
} else if (player == Player.PROGRAM) {
programScore++;
}
}
public void reset() {
moves = 0;
clearGrid();
}
private void clearGrid() {
for (int i = 0; i < 9; i++) {
grid[i] = 0;
}
}
public void printWin(Player currentPlayer) {
if (currentPlayer == Player.USER) {
System.out.println("Yay you won!");
} else {
System.out.println("Better luck next time");
}
printScore();
}
public void printDraw() {
System.out.println("Nobody Won!");
printScore();
}
private void printScore() {
System.out.printf("The score is - USER: %d, PROGRAM: %d%n", userScore, programScore);
}
}
package tictactoe.statemanager;
import tictactoe.Player;
import tictactoe.TicTacToe;
import tictactoe.state.*;
import java.util.Random;
import java.util.Scanner;
public class TicTacToeManager implements StateManager, ProgramInputManager, UserInputManager {
private final TicTacToe ticTacToe;
private Scanner scanner;
private State state;
private Player winner;
private Random random;
public TicTacToeManager(TicTacToe ticTacToe, Scanner scanner) {
this.ticTacToe = ticTacToe;
this.scanner = scanner;
this.random = new Random();
this.state = new Setup(this);
}
@Override
public void startEvents() {
if (!(state instanceof Setup)) {
throw new IllegalStateException("Events should be started only when the application is in Setup state.");
}
nextEvent();
}
@Override
public void nextEvent() {
if (state instanceof Setup) {
changeState(getStateAfterSetup());
} else if (state instanceof UserInput) {
changeState(getStateAfterInput());
} else if (state instanceof ProgramInput) {
changeState(getStateAfterInput());
} else if (state instanceof Draw) {
changeState(getStateAfterDraw());
} else if (state instanceof Win) {
changeState(getStateAfterWin());
} else if (state instanceof End) {
return;
}
nextEvent();
}
private State getStateAfterSetup() {
System.out.print("Who goes first? 2 for USER, 1 for PROGRAM and 0 to EXIT: ");
int input = scanner.nextInt();
Player player = Player.getPlayerFromValue(input);
ticTacToe.setFirstPlayer(player); // This is a sideEffect. There should be some other way to deal with this.
if (player == Player.PROGRAM) {
return new ProgramInput<>(this);
} else if (player == Player.USER) {
return new UserInput<>(this);
} else {
return new End(this);
}
}
private State getStateAfterInput() {
Input inputState = (Input) state;
Player player = inputState.getPlayer();
int row = inputState.getRow();
int column = inputState.getColumn();
if (hasWon(row, column, player)) {
return new Win(this, player);
} else if (areAllMovesExhausted()) {
return new Draw(this);
}
if (player == Player.PROGRAM) {
return new UserInput<>(this);
} else if (player == Player.USER) {
return new ProgramInput<>(this);
} else {
return null;
}
}
private boolean hasWon(int row, int column, Player player) {
return ticTacToe.hasWon(row, column, player.value);
}
private boolean areAllMovesExhausted() {
return ticTacToe.areAllMovesExhausted();
}
private State getStateAfterWin() {
return new Setup(this);
}
private State getStateAfterDraw() {
return new Setup(this);
}
@Override
public void changeState(State newState) {
state = newState;
state.onEnterState();
state.onExitState();
}
@Override
public boolean isMoveValid(int row, int column) {
return ticTacToe.isMoveValid(row, column);
}
@Override
public void makeAMove(int row, int column) {
if (state instanceof UserInput) {
ticTacToe.makeAMove(row, column, Player.USER);
} else if (state instanceof ProgramInput) {
ticTacToe.makeAMove(row, column, Player.PROGRAM);
}
}
@Override
public void printGrid() {
ticTacToe.printGrid();
}
@Override
public void printWin() {
ticTacToe.printWin(winner);
}
@Override
public void printDraw() {
ticTacToe.printDraw();
}
@Override
public void setWinner(Player winner) {
this.winner = winner;
ticTacToe.setWinner(winner);
}
@Override
public void reset() {
ticTacToe.reset();
}
@Override
public void end() {
scanner.close();
}
@Override
public int getProgramRow() {
return random.nextInt(3);
}
@Override
public int getProgramColumn() {
return random.nextInt(3);
}
@Override
public int getUserRow() {
return scanner.nextInt() - 1;
}
@Override
public int getUserColumn() {
return scanner.nextInt() - 1;
}
}
package tictactoe.state;
import tictactoe.Player;
import tictactoe.statemanager.StateManager;
import tictactoe.statemanager.UserInputManager;
public class UserInput<T extends StateManager & UserInputManager> implements Input {
private final T manager;
private int row;
private int column;
public UserInput(T manager) {
this.manager = manager;
}
@Override
public void onEnterState() {
System.out.print("Enter a position - row and column: ");
int row = manager.getUserRow();
int column = manager.getUserColumn();
while (!manager.isMoveValid(row, column)) {
System.out.print("Sorry that position is already occupied, please make another choice: ");
row = manager.getUserRow();
column = manager.getUserColumn();
}
this.row = row;
this.column = column;
manager.makeAMove(this.row, this.column);
}
@Override
public void onExitState() {
manager.printGrid();
}
@Override
public int getRow() {
return row;
}
@Override
public int getColumn() {
return column;
}
@Override
public Player getPlayer() {
return Player.USER;
}
}
package tictactoe.statemanager;
public interface UserInputManager {
int getUserRow();
int getUserColumn();
}
package tictactoe.state;
import tictactoe.Player;
import tictactoe.statemanager.StateManager;
public class Win implements State {
private final StateManager manager;
private final Player winner;
public Win(StateManager manager, Player winner) {
this.manager = manager;
this.winner = winner;
}
@Override
public void onEnterState() {
manager.setWinner(winner);
manager.printWin();
}
@Override
public void onExitState() {
}
}
@mibac138
Copy link

mibac138 commented Jul 28, 2017

  1. In UserInput (and ProgramInput) you require T to be both StateManager and UserInputManager at the same time. This isn't really the way you should do this because you unnecessarily and overly restrict your accepted type. Instead, you should have 2 variables - manager and inputManager (and remove the generics as they aren't really needed now).
  2. Why did you create a separate input manager interfaces for the user and program? You should have only one interface (with methods like getRow instead of getProgram/UserRow) which would have 2 implementations: ProgramInputManager and UserInputManager. You'd then have both of these implementations stored in the TicTacToeManager and use them.
  3. You don't need to create new instances of UserInput and ProgramInput every time. You could just create one of each and reuse them throughout the code.
  4. In TicTacToeManager you could simplify this code:
        if (state instanceof UserInput) {
            ticTacToe.makeAMove(row, column, Player.USER);
        } else if (state instanceof ProgramInput) {
            ticTacToe.makeAMove(row, column, Player.PROGRAM);
        }

To this:
ticTacToe.makeAMove(row, column, ((Input) state).getPlayer()) (you should do a check before though to give a more meaningful exception that ClassCastException)
5. ProgramInput and UserInput are doing basically the same thing. I'd delete one of them and rename the second. Then add a Player parameter to the left class and make getPlayer return that given player.
6. InputManager isn't really too descriptive. I think you should think of a different name, like InputProvider or something

If you'd like me to re-review the code or have any other questions please notify me or send me an email at mibacpb@gmail.com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment