JTic‐Tac‐Toe - rFronteddu/general_wiki GitHub Wiki
import java.util.Scanner; // for input
public class TicTacToe {
static char[][] board = {
{' ', ' ', ' '},
{' ', ' ', ' '},
{' ', ' ', ' '}
};
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
char currPlayer = 'X';
boolean gameEnded = false;
System.out.println("Welcome to TTT!");
printBoard();
while (!gameEnded) {
System.out.println("Player " + currPlayer + ", enter your move (row and column: 0, 1, or 2): ");
int row = sc.nextInt();
int col = sc.nextInt();
// validate row and col
if (row < 0 || row > 2 || col < 0 || col > 2) {
System.out.println("This position is off the board, try again");
continue;
}
if (board[row][col] != ' ') {
System.out.println("Cell already taken, try again");
continue;
}
board[row][col] = currPlayer;
printBoard();
// check victory
if (checkWinner(currPlayer)) {
System.out.println(currPlayer + " won!");
gameEnded = true;
} else if (isBoardFull()) {
// tie
gameEnded = true;
} else {
// switch player
currPlayer = (currPlayer == 'X') ? 'O' : 'X';
}
}
sc.close();
}
static void printBoard() {
System.out.println("-------------");
for (int i = 0; i < 3; i++) {
System.out.print("| ");
for (int j = 0; j < 3; j++) {
System.out.print(board[i][j] + " | ");
}
System.out.println();
System.out.println("-------------");
}
}
static boolean checkWinner(char player) {
// row and col
for (int i = 0; i < 3; i++) {
if ((board[i][0] == player && board[i][1] == player && board[i][2] == player) ||
(board[0][i] == player && board[1][i] == player && board[2][i] == player)) return true;
}
// diagonal
if ((board[0][0] == player && board[1][1] == player && board[2][2] == player) ||
(board[0][2] == player && board[1][1] == player && board[2][0] == player)) return true;
return false;
}
static boolean isBoardFull() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if(board[i][j] == ' ') return false;
}
}
return true;
}
}
/**
* Implements the Minimax algorithm to determine the optimal move in Tic Tac Toe.
* <p>
* This recursive function explores all possible future moves and assigns a score to each:
* <ul>
* <li>+10 → Computer (AI) wins</li>
* <li>-10 → Human player wins</li>
* <li>0 → Draw</li>
* </ul>
* The algorithm chooses the move that maximizes the AI’s score and minimizes the human’s.
* A depth penalty is applied to favor faster wins and slower losses.
* <p>
* It loops over all empty positions, simulates a move, and recursively evaluates outcomes,
* and returns the best score based on whether it’s maximizing (AI’s turn) or minimizing (Human’s turn).
*
* Minmax has exponential growth, at each step it checks all possible moves of the ai and
* the player (the game tree with b as branching factor
* (moves per turn and d as depth turns ahead to look => O(b^d)).
* The growth is actually factorial, but in big O notation it can be considered exponential.
* For TTT, at the start, there are 9 possible moves with at
* most 9 possible turns => 9^9. Possible ways to make it faster are pruning: ignore paths that
* have worse scores than the ones we are examining, so
* if player has score alpha, if ai score is below we can stop examining the path and vice versa,
* memoization (store already processed paths), limit
* depth and use an euristic to estmate what's left, evaluate independent branches/moves in parallel.
*
* @param board The current game board represented as a 2D character array.
* @param depth The depth of the recursive call — used to adjust scores for quicker outcomes.
* @param isMaximizing True if it's the AI’s turn (maximizing player), false if it's the human’s turn.
* @param ai The character representing the AI (e.g., 'O').
* @param human The character representing the human player (e.g., 'X').
* @return An integer score representing the evaluated utility of the current game state.
*/
static int minmax(char[][] board, int depth, boolean isMaximizing, char ai, char human) {
if (checkWinner(ai)) return 10 - depth;
if (checkWinner(human)) return depth - 10;
if (isBoardFull()) return 0;
int bestScore = isMaximizing ? Integer.MIN_VALUE : Integer.MAX_VALUE;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j] == ' ') {
board[i][j] = isMaximizing ? ai : human;
int score = minmax(board, depth + 1, !isMaximizing, ai, human);
board[i][j] = ' ';
if (isMaximizing)
bestScore = Math.max(bestScore, score);
else
bestScore = Math.min(bestScore, score);
}
}
}
return bestScore;
}
static int[] findBestMove(char ai, char human) {
int bestScore = Integer.MIN_VALUE;
int[] move = new int[2];
for (int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
if(board[i][j] == ' ') {
board[i][j] = ai;
int score = minmax(board, 0, false, ai, human);
board[i][j] = ' ';
if(score > bestScore) {
bestScore = score;
move[0] = i;
move[1] = j;
}
}
}
}
return move;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
char human = 'X';
char ai = 'O';
System.out.println("Welcome to TTT (you = " + human + ", Computer = " + ai + ")");
printBoard();
while (true) {
System.out.println("Your turn (row and column: 0, 1, or 2): ");
int row = sc.nextInt();
int col = sc.nextInt();
// validate row and col
if (row < 0 || row > 2 || col < 0 || col > 2) {
System.out.println("This position is off the board, try again");
continue;
}
if (board[row][col] != ' ') {
System.out.println("Cell already taken, try again");
continue;
}
board[row][col] = human;
printBoard();
// check victory
if (checkWinner(human)) {
System.out.println(human + " won!");
break;
} else if (isBoardFull()) {
System.out.println("It's a draw!");
break;
}
// ai turn
System.out.println("Computer's turn...");
int[] bestMove = findBestMove(ai, human);
board[bestMove[0]][bestMove[1]] = ai;
printBoard();
// check victory
if (checkWinner(ai)) {
System.out.println("AI won!");
break;
} else if (isBoardFull()) {
System.out.println("It's a draw!");
break;
}
}
sc.close();
}
import java.util.Random;
// ....
// add a variable count to count the moves
if (moves == 1) { // human acted first, now is the first ai turn
Random rand = new Random();
int row, col;
do {
row = rand.nextInt(3);
col = rand.nextInt(3);
} while (board[row][col] != ' ');
bestMove = new int[]{row, col};
} else {
bestMove = findBestMove(ai, human); // Use Minimax after that
}
static int[] bestMove(Board board, char ai, char hu, Difficulty level) {
switch (level) {
case EASY:
return randomMove(board); // random move
case MEDIUM:
// 50% chance random, 50% Minimax
if (Math.random() < 0.5)
return randomMove(board);
else
return minimaxMove(board, ai, hu);
case HARD:
default:
return minimaxMove(board, ai, hu); // optimal move
}
}
The idea is to use the command pattern to encapsulate a request as an object, thereby letting you parametrize clients with queues, requests, and operations. In TTT, each move can be represented as a command that knows how to execute the move, to undo it, and that can be restored from a history.
import java.util.Scanner;
import java.util.Stack;
class Main {
static public class Board {
private char[][] grid = new char[3][3];
public Board() {
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
grid[i][j] = ' ';
}
public boolean placeMark(int row, int col, char player) {
if (grid[row][col] == ' ') {
grid[row][col] = player;
return true;
}
return false;
}
public void removeMark(int row, int col) {
grid[row][col] = ' ';
}
public void print() {
System.out.println("---------");
for (int i = 0; i < 3; i++) {
System.out.printf("| %c | %c | %c |%n", grid[i][0], grid[i][1], grid[i][2]);
System.out.println("---------");
}
}
}
public interface Command {
boolean execute();
void undo();
}
static class MoveCommand implements Command {
private final Board board;
private final int row;
private final int col;
private final char player;
private boolean executed = false;
public MoveCommand(Board board, int row, int col, char player) {
this.board = board;
this.row = row;
this.col = col;
this.player = player;
}
@Override public boolean execute() {
if (board.placeMark(row, col, player)) {
executed = true;
return true;
} else {
System.out.println("Invalid move!");
return false;
}
}
@Override public void undo() {
if (executed) {
board.removeMark(row, col);
executed = false;
}
}
}
public static class CommandManager {
private final Stack<Command> undoStack = new Stack<>();
private final Stack<Command> redoStack = new Stack<>();
public boolean executeCommand(Command command) {
if (!command.execute()) {
return false;
}
undoStack.push(command);
redoStack.clear();
return true;
}
public void undo() {
// to undo it has to be the player turn, so I have to undo
if (undoStack.size() < 2) {
System.out.println("Nothing to undo.");
return;
}
// ai move
Command command = undoStack.pop();
command.undo();
redoStack.push(command);
// player move
command = undoStack.pop();
command.undo();
redoStack.push(command);
}
public void redo() {
if (redoStack.size() < 2) {
System.out.println("Nothing to redo.");
return;
}
// player move
Command command = redoStack.pop();
command.execute();
undoStack.push(command);
// ai move
command = redoStack.pop();
command.execute();
undoStack.push(command);
}
}
public static void main(String[] args) {
Board board = new Board();
CommandManager manager = new CommandManager();
Scanner scanner = new Scanner(System.in);
char hu = 'X';
char ai = 'O';
boolean humanTurn = true;
while(true) {
board.print();
if (humanTurn) {
System.out.println("Player turn, enter r/u to redo/undo or row col to add a new move.");
String input = scanner.nextLine().trim();
if (input.equalsIgnoreCase("u")) {
manager.undo();
continue;
} else if (input.equalsIgnoreCase("r")) {
manager.redo();
continue;
}
try {
String[] parts = input.split(" ");
int row = Integer.parseInt(parts[0]);
int col = Integer.parseInt(parts[1]);
if (!manager.executeCommand(new MoveCommand(board, row, col, hu))) {
System.out.println("Invalid, try again..");
}
if (hasWon(ai)) {
System.out.println("AI has won!");
break;
}
if (boardFull()) {
System.out.println("It's a tie!");
break;
}
humanTurn = false;
} catch (Exception e) {
System.out.println("Invalid, try again..");
}
} else {
System.out.println("AI turn.. thinking...");
int[] move = bestMove(ai, hu);
manager.executeCommand(new MoveCommand(board, move[0], move[1], ai));
if (hasWon(ai)) {
System.out.println("AI has won!");
break;
}
if(boardFull()) {
System.out.println("It's a tie!");
break;
}
// get best move execute it, check for tie victory
humanTurn = true;
}
}
}
}