HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Tic Tac Toe with 3 AI modes or Two-Player

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withtoeplayertictactwomodes

Problem

This is my first stab at a game AI, and I've been working on multiple game mode difficulties, so I decided to start with a simple game. :) I'm most concerned about efficiency of the AI algorithms, and any possible way of reducing the duplicate code. I know I can't change the int values by reference, so would it be more efficient to use Integers, or would that cause other problems? Also, what is the most readable/informative way to document array returns in javadocs that show how the output array is ordered? (I also left out the more intuitive Javadocs to save space).

ConsoleGame.java

```
import java.util.Random;
import java.util.Scanner;

public class ConsoleGame {
private final static Board board = new Board();
private final static Scanner scanner = new Scanner(System.in);
private final static Random generator = new Random();
private final static int MAX_TURNS = 9;

public static void main(String[] args) {
String playAgain;
do {
AI computer = null;

System.out.print("Play against the computer [Y/n]? ");
String input = scanner.nextLine();
boolean playComputer = false;
if (!input.equalsIgnoreCase("n") && !input.equalsIgnoreCase("no")) {
playComputer = true;

do {
System.out.print("Pick game mode [easy | medium | hard]: ");
input = scanner.nextLine();

if (input.equalsIgnoreCase("easy")) {
computer = new EasyAI();
} else if (input.equalsIgnoreCase("medium")) {
computer = new MediumAI();
} else if (input.equalsIgnoreCase("hard")) {
computer = new HardAI();
} else {
System.out.print("Invalid mode. ");
}
} while (!input.equalsIgnoreCase("easy") && !input.equalsIgnoreCase("medium")

Solution

first stab at a game AI


reducing the duplicate code


readable/informative way to document array returns

WARNING: this answer may be a little over-engineered.

In Game Development, we often talk about the concept of a Controller. The Controller is an Interface/Abstract that contains all of the method signatures that will be used to control the actions of a player or AI player; this allows the Controller to be effectively hot-swapped between player and AI, or between a "smart" and "not as smart" AI.

Another key idea is the separation of game logic and game I/O (the Player Controller is a necessary exception). In your ConsoleGame class, you have both logic to set up the game and run it. I would extract your main class (what is run) to a seperate class, probably Main.java because I suck at names, and rename ConsoleGame to TicTacToeGame, which would only handle the actual game logic.

More generally talking about Java design, it's a bad idea to pollute the global package. You should have a package declaration, e.g. package brainfrz.tictactoe, at the top of each java file. The .java will then be located in a folder hiearchy ProjectBase/brainfrz/tictactoe/*.java.

One final design point is that you've implemented the entire ConsoleGame statically. Obviously the main() has to be static, but it is best design to create a TicTacToeGame instance that contains all state info, for what if you want to run two at the same time? Or nine, if we extend this to a game of super-tic-tac-toe?

Now, let's get to some code, shall we?

I'm focusing on the public facing API, and there may be more private helper methods that I've glossed over as / Implementation /

package brainfrz.tictactoe;

class TicTacToeGame {
private final TicTacToeBoard board = new TicTacToeBoard();
private final TicTacToeController p1, p2;

public TicTacToeGame(TicTacToeController p1, TicTacToeController p2) {
this.p1 = p1;
this.p2 = p2;
}

/**
* Runs a loop taking moves from p1 and p2 until the game is finished.
*
* In a bigger application, you'd want to split this singular call
* into many piece-meal calls, often that you can call every frame.
* Since we're just doing a console application, however, stalling
* for input is OK.
*/
public void gameLoop() {
bool oddTurn = true;
while (!board.gameEnded()) {
if (oddTurn) {
p1.makeMove(board);
} else {
p2.makeMove(board);
}
}
}

/**
* Determine who won this game of TicTacToe.
*
* @return the TicTacToeController of the winning player.
* null if the game hasn't been won or is a tie.
*/
public TicTacToeController getWinner() {
if (board.winningState() == p1.getToken()) {
return p1;
} else if (board.winningState() == p2.getToken()) {
return p2;
} else {
return null;
}
}
}


That's pretty clean, isn't it? That's because we've delegated responsibility to the best actor for each job. So let's go down the hiearchy, and take a look at TicTacToeController.

package brainfrz.tictactoe;

interface TicTacToeController {
/**
* Make a move.
*
* Plays in-place.
* @param board the board to move on.
*/
public void makeMove(TicTacToeBoard board);

public TicTacToeBoard.CellState getToken();
}


Yes, that's it. All that this interface is is a specification for how the specific Controllers interact with the Board. I would structure the Board itself something like the following:

package brainfrz.tictactoe;

import java.util.Arrays;

class TicTacToeBoard {
static enum CellState {
EMPTY, X, Y
/ toString and matches /
}

static class TicTacToePosition {
public final int x,y;
public TicTacToePosition(int x, int y) {
this.x = x;
this.y = y;
}
}

private final CellState[][] cells = CellState[3][3];

public Board() {
for (CellState[] row : cells) {
Arrays.fill(row, CellState.EMPTY);
}
}

public void placePiece(TicTacToePosition pos, CellState piece) {
cells[pos.y][pos.x] = piece;
}

/**
* Determine which CellState won.
*
* @return the winning cell state.
* null if there is no win.
*/
public CellState winningState() {
/ Implementation from verifyWinner() /
}

/**
* @return if the game has ended
*/
public boolean gameEnded() {
/ Implementation from verifyWinner() /
}
}


Now on to the actual Controllers. I'm putting them all in one code block to save a small amount of vertical space, but of course the different classes need to be in different files.

You mentioned wanting to clarify the return int[]. I would recommend to instead use a BoardPosition, as I

Context

StackExchange Code Review Q#125432, answer score: 2

Revisions (0)

No revisions yet.