Java Tic Tac Toe — 4x4 Board

Yesterday I extended my Tic Tac Toe game to offer the user a choice between a normal sized (3x3) or a big (4x4) board.

In my original design, I’d made a point of trying not to lock myself into a 3x3 board wherever possible — so my board and game classes already supported a bigger board.

It took a bit longer initially to come up with a way of checking for winning combinations, but I’m glad I did, because now that I’m using a bigger board, I just wrote some extra tests and confirmed that a bigger board would work.

To check winning combinations, I slice my array into smaller arrays of rows, columns and diagonals based on the number of rows. Then I check if everything matches in those smaller arrays. If any of them are true, we have a winner.

Rows:

ArrayList<ArrayList<String>> getRows() {
ArrayList<ArrayList<String>> rows = new ArrayList<>(numberOfRows);
for (int i = 0; i < numberOfRows; i++) {
rows.add(
new ArrayList<>(
getSpaces().subList(i * numberOfRows, (numberOfRows * i) + numberOfRows)));
}
return rows;
}

Columns:

ArrayList<ArrayList<String>> getColumns() {
ArrayList<ArrayList<String>> columns = new ArrayList<>(numberOfRows);
for (int i = 0; i < numberOfRows; i++) {
ArrayList<String> column = new ArrayList<>(numberOfRows);
for (int j = 0; j < numberOfRows; j++) {
column.add(getRows().get(j).get(i));
}
columns.add(column);
}
return columns;
}

Left Diagonal:

ArrayList<String> getLeftDiagonal() {
ArrayList<String> leftDiagonal = new ArrayList<>(numberOfRows);
for (int i = 0; i < numberOfRows; i++) {
leftDiagonal.add(getRows().get(i).get(i));
}
return leftDiagonal;
}

Right Diagonal:

ArrayList<String> getRightDiagonal() {
ArrayList<String> rightDiagonal = new ArrayList<>(numberOfRows);
for (int i = 0; i < numberOfRows; i++) {
rightDiagonal.add(getRows().get(i).get(numberOfRows - (i + 1)));
}
return rightDiagonal;
}

Then to put them all together into a two dimensional array:

private ArrayList<ArrayList<String>> winningCombinations() {
ArrayList<ArrayList<String>> winningCombinations = new ArrayList<>();
winningCombinations.addAll(board.getRows());
winningCombinations.addAll(board.getColumns());
winningCombinations.add(board.getLeftDiagonal());
winningCombinations.add(board.getRightDiagonal());
return winningCombinations;
}

Finally, to check for win:

private boolean isGameWonBy(Player player) {
for (ArrayList<String> line : winningCombinations()) {
if (line.stream().allMatch(space -> space.equals(player.getMarker()))) {
winner = player;
return true;
}
}
return false;
}

As you can see, at no stage in any of that code have I hard coded in a three.

When it came to making the bigger board, the only time I’d locked myself into a 3x3 board was when I was printing the board on the screen. Here’s how it looked for 3x3.

void board(Game game) {
String line = System.lineSeparator() + "-----------" + System.lineSeparator();
output.println(formatRow(game, 0, 1, 2)
+ line
+ formatRow(game, 3, 4, 5)
+ line
+ formatRow(game, 6, 7, 8)
+ System.lineSeparator());
}

private String formatRow(Game game, int a, int b, int c) {
return " " + getSpace(game, a) + " | " + getSpace(game, b) + " | " + getSpace(game, c);
}

private String getSpace(Game game, int index) {
return game.board.getSpaces().get(index);
}

To get a similar result programmatically (and pad the numbers/lines to make a 4x4 board look ok) required quite a bit more code. I’m hoping I can look at it again this morning and simplify it a bit. Here’s how it looks right now:

void board(Board board) {
StringBuilder printedBoard = new StringBuilder();
ArrayList<ArrayList<String>> rows = board.getRows();
for (ArrayList<String> row : rows) {
printedBoard.append(formatRow(row)).append(getLine(board));
}
printedBoard.append(System.lineSeparator());
output.println(printedBoard.substring(0, printedBoard.length() - getLine(board).length()));
}

private String formatRow(List<String> row) {
String separator = " | ";
String offset = " ";
StringBuilder formattedRow = new StringBuilder(offset);
for (String space : row) {
String paddedSpace = padSpace(offset, space);
formattedRow.append(paddedSpace).append(separator);
}
return String.valueOf(formattedRow.substring(0, formattedRow.length() - separator.length()));
}

private String padSpace(String offset, String space) {
String paddedSpace;
if (space.length() == 1){
paddedSpace = offset + space;
} else {
paddedSpace = space;
}
return paddedSpace;
}

private String getLine(Board board) {
final String line;
int normalBoard = 3;
if (board.getRows().size() == normalBoard) {
line = System.lineSeparator() + "--------------" + System.lineSeparator();
} else {
line = System.lineSeparator() + "-------------------" + System.lineSeparator();
}
return line;
}

void outcome(Game game) {
if (game.getWinner() != null) {
output.println(game.getWinner().getMarker() + " is the winner");
} else {
output.println("Game tied");
}
}

Today I will be looking at printing the board again, drawing my first UML diagram, and learn about factory methods to see if I prefer these to the Command Pattern.