Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 100 additions & 19 deletions tic-tac-toe-cli/src/main/java/com/scaler/tictactoe/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,80 @@

import lombok.Getter;

import java.util.List;
import java.util.Map;

public class Game {
@Getter
private Player p1;
private final Player p1;
@Getter
private Player p2;

private final Player p2;
@Getter
private Player nextTurn;
@Getter
private final String[][] gameState = new String[3][3];

private String[][] gameState = new String[3][3];
private int emptyBoxes;

public Game(String p1Char, String p2Char) {
p1 = new Player(p1Char);
p2 = new Player(p2Char);
private static final List<Integer> topRowBoxes = List.of(1, 2, 3);
private static final List<Integer> midRowBoxes = List.of(4, 5, 6);
private static final List<Integer> bottomRowBoxes = List.of(7, 8, 9);
private static final List<Integer> leftColBoxes = List.of(1, 4, 7);
private static final List<Integer> midColBoxes = List.of(2, 5, 8);
private static final List<Integer> rightColBoxes = List.of(3, 6, 9);
private static final List<Integer> leftDiagonalBoxes = List.of(1, 5, 9);
private static final List<Integer> rightDiagonalBoxes = List.of(3, 5, 7);

private static final Map<Integer, List<List<Integer>>> boxToWinningLines = Map.ofEntries(
Map.entry(1, List.of(topRowBoxes, leftColBoxes, leftDiagonalBoxes)),
Map.entry(2, List.of(topRowBoxes, midColBoxes)),
Map.entry(3, List.of(topRowBoxes, rightColBoxes, rightDiagonalBoxes)),
Map.entry(4, List.of(midRowBoxes, leftColBoxes)),
Map.entry(5, List.of(midRowBoxes, midColBoxes, leftDiagonalBoxes, rightDiagonalBoxes)),
Map.entry(6, List.of(midRowBoxes, rightColBoxes)),
Map.entry(7, List.of(bottomRowBoxes, leftColBoxes, rightDiagonalBoxes)),
Map.entry(8, List.of(bottomRowBoxes, midColBoxes)),
Map.entry(9, List.of(bottomRowBoxes, rightColBoxes, leftDiagonalBoxes))
);

public Game(String p1Char, String p2Char) {
this.p1 = new Player(p1Char);
this.p2 = new Player(p2Char);
// init next turn for player 1
nextTurn = p1;
this.nextTurn = p1;
this.emptyBoxes = 9;
}

public void playGame() {
Player victor = null;

while (!isGameOverByExhaustionOfBoxes()) {
IOHelper.printGameState(getGameState());
Player nextTurnPlayer = getNextTurn();
IOHelper.printNextTurn(nextTurnPlayer);
int boxNumber = IOHelper.getBoxNumberToBeFilled();
while (!fillBoxSuccess(boxNumber)) {
System.out.println("Invalid Box number. Please try again.\n");
IOHelper.printNextTurn(getNextTurn());
boxNumber = IOHelper.getBoxNumberToBeFilled();
}
victor = checkVictory(boxNumber, nextTurnPlayer);
if (victor != null) {
break;
}
}

IOHelper.printGameState(getGameState());
IOHelper.declareVictor(victor);
}

public boolean fillBoxSuccess(int boxNumber) {
try {
nextAttempt(boxNumber);
return true;
} catch (IllegalArgumentException | IllegalStateException e) {
return false;
}
}

public String getCharInBox(int box) {
Expand All @@ -41,29 +98,53 @@ public void nextAttempt(int box) {

if (box < 1 || box > 9) throw new IllegalArgumentException("Box no. must be between 1 and 9");
if (gameState[row][col] != null) throw new IllegalStateException("This box is not empty");
if (isGameOverByExhaustionOfBoxes()) throw new IllegalStateException("All boxes have been filled. Game over.");

gameState[row][col] = nextTurn.getCharacter();
gameState[row][col] = getNextTurn().getCharacter();
emptyBoxes -= 1;

// switch turn of players
if (nextTurn == p1) nextTurn = p2;
else nextTurn = p1;
}

/**
* Checks board state and tells if any winners
* Checks if the game has been won by a player
*
* @return p1 or p2 whoever has won; or null if no winner yet
* @param lastFilledBox Box number that was last filled correctly
* @param playerWhoLastPlayed The player who played the last move
* @return The player who has won the game, null if no one has won yet
*/
public Player checkVictory() {
// TODO
public Player checkVictory(int lastFilledBox, Player playerWhoLastPlayed) {

if (!playerWhoLastPlayed.getCharacter().equals(getCharInBox(lastFilledBox))) {
throw new IllegalStateException("Filled character does not match player's marking character");
}
String playerCharacter = playerWhoLastPlayed.getCharacter();

for (List<Integer> possibleWinningLine : boxToWinningLines.get(lastFilledBox)) {

// init number of character matches for this player to 1 -- for the currently filled box
int lineMatches = 0;

// check every line to see a possible complete line made for this box
for (int boxNo : possibleWinningLine) {
if (playerCharacter.equals(getCharInBox(boxNo))) {
lineMatches += 1;
continue;
}
break;
}

// check if this current line is complete with the character for this player
if (lineMatches == 3) {
return playerWhoLastPlayed;
}
}
return null;
}

public String printGameState() {
return " " + gameState[0][0] + " | " + gameState[0][1] + " | " + gameState[0][2] + "\n" +
"------------\n" +
" " + gameState[1][0] + " | " + gameState[1][1] + " | " + gameState[1][2] + "\n" +
"------------\n" +
" " + gameState[2][0] + " | " + gameState[2][1] + " | " + gameState[2][2];
public boolean isGameOverByExhaustionOfBoxes() {
return emptyBoxes == 0;
}
}
87 changes: 87 additions & 0 deletions tic-tac-toe-cli/src/main/java/com/scaler/tictactoe/IOHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.scaler.tictactoe;

import java.io.BufferedReader;
import java.io.Console;
import java.io.IOException;
import java.io.InputStreamReader;

public class IOHelper {

private static String readLine() {
Console console = System.console();
if (console != null) {
return console.readLine();
} else {
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
try {
return consoleReader.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

static void declareVictor(Player victor) {
if (victor == null) {
System.out.println("This game has ended in a draw");
} else {
System.out.println("Player " + victor.getCharacter() + " has won! 🎊 🍾");
}
}

static int getBoxNumberToBeFilled() {
System.out.println("Enter box number (1-9) to be filled.");
int boxNumber;
try {
boxNumber = Integer.parseInt(readLine());
System.out.println();
if (boxNumber < 1 || boxNumber > 9) {
System.out.println("Invalid Box number. Please try again.\n");
return getBoxNumberToBeFilled();
}
} catch (NumberFormatException e) {
System.out.println("Invalid Box number. Please try again.\n");
return getBoxNumberToBeFilled();
}
return boxNumber;
}

static void printNextTurn(Player nextTurn) {
System.out.println("Player " + nextTurn.getCharacter() + " to play.");
}

static void printGameState(final String[][] gameState) {
System.out.println("Current Game State : \n");
for (String[] row : gameState) {
for (String col : row) {
if (col == null)
col = "-";
System.out.print(col + " ");
}
System.out.println();
}
System.out.println();
}

static String getPlayerString(int playerNum) {
System.out.println("Enter character for Player " + playerNum);
String character = readLine();
System.out.println();
while (character.length() > 1) {
System.out.println("Please enter a single character.");
character = readLine();
System.out.println();
}
return character;
}

static String getPlayerStringNotEqualTo(int playerNum, String character) {
String newChar = getPlayerString(playerNum);
while (newChar.equals(character)) {
System.out.println("Character cannot be same as " + character);
System.out.println();
newChar = getPlayerString(playerNum);
}
return newChar;
}
}
28 changes: 16 additions & 12 deletions tic-tac-toe-cli/src/main/java/com/scaler/tictactoe/Main.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
package com.scaler.tictactoe;

public class Main {
public static void main(String[] args) {

Game game = new Game("X", "O");
System.out.println(game.printGameState());

public static void main(String[] args) {
/*
TODO: Create the entire game; steps are:
1. Construct game object with 2 player characters
2. For every turn, print whose turn it is, and state of game (3x3 box)
3. Read input (between 1-9) as the box to be marked by next player
Create entire game. Steps:
1. Construct game object with 2 players
2. For every turn, print whose turn it is, with the state
3. Read input (1-9) as the box to be marked
4. Validate input and mark the box
4.1 If input invalid, make player enter box no again
5. Repeat steps 2-4 until either;
5.1 checkVictory function shows any player has won
5.2 all boxes have been marked
4.1. If the input is invalid, make the player input the box again
5. Repeat steps 2-4 until
5.1. checkVictory function shows if any player has won
5.2. all boxes have been marked
*/

String p1Char = IOHelper.getPlayerString(1);
String p2Char = IOHelper.getPlayerStringNotEqualTo(2, p1Char);
Game game = new Game(p1Char, p2Char);
game.playGame();
System.out.println("Thanks for playing! 🎊🎊🎊");
}

}
42 changes: 35 additions & 7 deletions tic-tac-toe-cli/src/test/java/com/scaler/tictactoe/GameTests.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.scaler.tictactoe;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;

import static org.junit.jupiter.api.Assertions.*;

Expand Down Expand Up @@ -38,19 +37,48 @@ void canMarkBoxesViaAttempts() {

// check that already marked boxes are not allowed to be marked

assertThrowsExactly(IllegalStateException.class, () -> {
g.nextAttempt(1);
});
assertThrowsExactly(IllegalStateException.class, () -> g.nextAttempt(1));

}

@Test
void throwsExceptionForInvalidBoxAttempt() {
Game g = new Game("❌", "⭕️");

assertThrowsExactly(IllegalArgumentException.class, () -> {
g.nextAttempt(10);
});
assertThrowsExactly(IllegalArgumentException.class, () -> g.nextAttempt(10));

}

@Test
void checkVictoryTest() {
Game game = new Game("X", "O");

// Player 1 marks box 1
game.nextAttempt(1);
// Player 2 plays box 5
Player p = game.getNextTurn();
game.nextAttempt(5);
assertEquals(game.getP2(), p); // Player 1 should have the next turn
// Game has only been played by two moves. No winner yet
assertNull(game.checkVictory(5, p));

p = game.getNextTurn();
assertEquals(game.getP1(), p); // Player 1 should have the next turn
game.nextAttempt(2);
// box number 4 has not been filled
Player finalP = p;
assertThrowsExactly(IllegalStateException.class, () -> game.checkVictory(4, finalP));
// box number 5 was filled by player 2 and not player 1
assertThrowsExactly(IllegalStateException.class, () -> game.checkVictory(5, finalP));
// Game has only been played by three total moves. No winner yet
assertNull(game.checkVictory(2, p));

// Player 2
game.nextAttempt(6);
// Player 1 has played boxes 1,2,3
p = game.getNextTurn();
assertEquals(game.getP1(), p); // Player 1 should have the next turn
game.nextAttempt(3);
assertEquals(game.getP1(), game.checkVictory(3, p));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
public class PlayerTests {

@Test
void constructPlayerWithCharacter () {
void constructPlayerWithCharacter() {
Player p1 = new Player("❌");
assertEquals("❌", p1.getCharacter());
}
Expand Down