Created
July 27, 2017 09:35
-
-
Save yadavvi91/91703f11b05cbc4659dd4325158e8d13 to your computer and use it in GitHub Desktop.
A stateful implementation of TicTacToe in Java.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tictactoe.state; | |
import tictactoe.Player; | |
public interface Input extends State { | |
int getRow(); | |
int getColumn(); | |
Player getPlayer(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tictactoe.statemanager; | |
public interface ProgramInputManager { | |
int getProgramRow(); | |
int getProgramColumn(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tictactoe.state; | |
public interface State { | |
void onEnterState(); | |
void onExitState(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tictactoe.statemanager; | |
public interface UserInputManager { | |
int getUserRow(); | |
int getUserColumn(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
UserInput
(andProgramInput
) you requireT
to be bothStateManager
andUserInputManager
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
andinputManager
(and remove the generics as they aren't really needed now).getRow
instead ofgetProgram/UserRow
) which would have 2 implementations:ProgramInputManager
andUserInputManager
. You'd then have both of these implementations stored in theTicTacToeManager
and use them.UserInput
andProgramInput
every time. You could just create one of each and reuse them throughout the code.TicTacToeManager
you could simplify this code:To this:
ticTacToe.makeAMove(row, column, ((Input) state).getPlayer())
(you should do a check before though to give a more meaningful exception thatClassCastException
)5.
ProgramInput
andUserInput
are doing basically the same thing. I'd delete one of them and rename the second. Then add aPlayer
parameter to the left class and makegetPlayer
return that given player.6.
InputManager
isn't really too descriptive. I think you should think of a different name, likeInputProvider
or somethingIf 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