patternjavaMinor
Tic Tac Toe with 3 AI modes or Two-Player
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")
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
More generally talking about Java design, it's a bad idea to pollute the global package. You should have a package declaration, e.g.
One final design point is that you've implemented the entire
Now, let's get to some code, shall we?
I'm focusing on the
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
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:
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
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 IContext
StackExchange Code Review Q#125432, answer score: 2
Revisions (0)
No revisions yet.