aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/utils/tictactoe/TicTacToeUtils.java
blob: 0725b2cefa823350f806ce247ecba224388e4edc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package de.hysky.skyblocker.utils.tictactoe;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.stream.Stream;

import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;

public class TicTacToeUtils {

	public static BoardIndex getBestMove(char[][] board) {
		Object2IntOpenHashMap<BoardIndex> moves = new Object2IntOpenHashMap<>();

		for (int row = 0; row < board.length; row++) {
			for (int column = 0; column < board[row].length; column++) {
				// Simulate the move as O if the square is empty to determine a solution
				if (board[row][column] != '\0') continue;
				board[row][column] = 'O';
				int score = alphabeta(board, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, false);
				board[row][column] = '\0';

				moves.put(new BoardIndex(row, column), score);
			}
		}

		return Collections.max(moves.object2IntEntrySet(), Comparator.comparingInt(Object2IntMap.Entry::getIntValue)).getKey();
	}

	private static boolean hasMovesAvailable(char[][] board) {
		return Arrays.stream(board).flatMap(row -> Stream.of(row[0], row[1], row[2])).anyMatch(c -> c == '\0');
	}

	private static int getScore(char[][] board) {
		// Check if X or O has won horizontally
		for (int row = 0; row < 3; row++) {
			if (board[row][0] == board[row][1] && board[row][0] == board[row][2]) {
				switch (board[row][0]) {
					case 'X': return -10;
					case 'O': return 10;
				}
			}
		}

		// Check if X or O has won vertically
		for (int column = 0; column < 3; column++) {
			if (board[0][column] == board[1][column] && board[0][column] == board[2][column]) {
				switch (board[0][column]) {
					case 'X': return -10;
					case 'O': return 10;
				}
			}
		}

		// Check if X or O has won diagonally
		// Top left to bottom right
		if (board[0][0] == board[1][1] && board[0][0] == board[2][2]) {
			switch (board[0][0]) {
				case 'X': return -10;
				case 'O': return 10;
			}
		}

		// Top right to bottom left
		if (board[0][2] == board[1][1] && board[0][2] == board[2][0]) {
			switch (board[0][2]) {
				case 'X': return -10;
				case 'O': return 10;
			}
		}

		return 0;	
	}

	private static int alphabeta(char[][] board, int alpha, int beta, int depth, boolean maximizePlayer) {
		int score = getScore(board);

		if (score == 10 || score == -10) return score;
		if (!hasMovesAvailable(board)) return 0;

		if (maximizePlayer) {
			int bestScore = Integer.MIN_VALUE;

			for (int row = 0; row < 3; row++) {
				for (int column = 0; column < 3; column++) {
					if (board[row][column] == '\0') {
						board[row][column] = 'O';
						bestScore = Math.max(bestScore, alphabeta(board, alpha, beta, depth + 1, false));
						board[row][column] = '\0';
						alpha = Math.max(alpha, bestScore);

						//Is this correct? Well the algorithm seems to solve it so I will assume it is
						if (beta <= alpha) break; // Pruning
					}
				}
			}

			return bestScore - depth;
		} else {
			int bestScore = Integer.MAX_VALUE;

			for (int row = 0; row < 3; row++) {
				for (int column = 0; column < 3; column++) {
					if (board[row][column] == '\0') {
						board[row][column] = 'X';
						bestScore = Math.min(bestScore, alphabeta(board, alpha, beta, depth + 1, true));
						board[row][column] = '\0';
						beta = Math.min(beta, bestScore);

						if (beta <= alpha) break; // Pruning
					}
				}
			}

			return bestScore + depth;
		}
	}
}