connect4 with minimax AI

This commit is contained in:
Ticho Hidding
2025-10-19 22:01:33 +02:00
parent df493a5eba
commit c3f764f33d
12 changed files with 520 additions and 41 deletions

View File

@@ -0,0 +1,114 @@
package org.toop.game.Connect4;
import org.toop.game.TurnBasedGame;
import java.util.ArrayList;
public class Connect4 extends TurnBasedGame {
private int movesLeft;
public Connect4() {
super(6, 7, 2);
movesLeft = board.length;
}
public Connect4(Connect4 other) {
super(other);
movesLeft = other.movesLeft;
}
@Override
public Move[] getLegalMoves() {
final ArrayList<Move> legalMoves = new ArrayList<>();
final char currentValue = getCurrentValue();
for (int i = 0; i < columnSize; i++) {
if (board[i] == EMPTY) {
legalMoves.add(new Move(i, currentValue));
}
}
return legalMoves.toArray(new Move[0]);
}
@Override
public State play(Move move) {
assert move != null;
assert move.position() >= 0 && move.position() < board.length;
assert move.value() == getCurrentValue();
int lowestEmptySpot = move.position();
for (int i = 0; i < rowSize; i++) {
int checkMovePosition = move.position() + columnSize * i;
if (checkMovePosition < board.length) {
if (board[checkMovePosition] == EMPTY) {
lowestEmptySpot = checkMovePosition;
}
}
}
board[lowestEmptySpot] = move.value();
movesLeft--;
if (checkForWin()) {
return State.WIN;
}
nextTurn();
return State.NORMAL;
}
private boolean checkForWin() {
char[][] boardGrid = makeBoardAGrid();
for (int row = 0; row < rowSize; row++) {
for (int col = 0; col < columnSize; col++) {
char cell = boardGrid[row][col];
if (cell == ' ' || cell == 0) continue;
if (col + 3 < columnSize &&
cell == boardGrid[row][col + 1] &&
cell == boardGrid[row][col + 2] &&
cell == boardGrid[row][col + 3]) {
return true;
}
if (row + 3 < rowSize &&
cell == boardGrid[row + 1][col] &&
cell == boardGrid[row + 2][col] &&
cell == boardGrid[row + 3][col]) {
return true;
}
if (row + 3 < rowSize && col + 3 < columnSize &&
cell == boardGrid[row + 1][col + 1] &&
cell == boardGrid[row + 2][col + 2] &&
cell == boardGrid[row + 3][col + 3]) {
return true;
}
if (row + 3 < rowSize && col - 3 >= 0 &&
cell == boardGrid[row + 1][col - 1] &&
cell == boardGrid[row + 2][col - 2] &&
cell == boardGrid[row + 3][col - 3]) {
return true;
}
}
}
return false;
}
public char[][] makeBoardAGrid() {
char[][] boardGrid = new char[rowSize][columnSize];
for (int i = 0; i < rowSize*columnSize; i++) {
boardGrid[i / columnSize][i % columnSize] = board[i]; //boardGrid[y -> row] [x -> column]
}
return boardGrid;
}
private char getCurrentValue() {
return currentTurn == 0 ? 'X' : 'O';
}
}

View File

@@ -0,0 +1,63 @@
package org.toop.game.Connect4;
import org.toop.game.AI;
import org.toop.game.Game;
import org.toop.game.tictactoe.TicTacToe;
public class Connect4AI extends AI<Connect4> {
public Game.Move findBestMove(Connect4 game, int depth) {
assert game != null;
assert depth >= 0;
final Game.Move[] legalMoves = game.getLegalMoves();
if (legalMoves.length <= 0) {
return null;
}
int bestScore = -depth;
Game.Move bestMove = null;
for (final Game.Move move : legalMoves) {
final int score = getMoveScore(game, depth, move, true);
if (score > bestScore) {
bestMove = move;
bestScore = score;
}
}
return bestMove != null? bestMove : legalMoves[(int)(Math.random() * legalMoves.length)];
}
private int getMoveScore(Connect4 game, int depth, Game.Move move, boolean maximizing) {
final Connect4 copy = new Connect4(game);
final Game.State state = copy.play(move);
switch (state) {
case Game.State.DRAW: return 0;
case Game.State.WIN: return maximizing? depth + 1 : -depth - 1;
}
if (depth <= 0) {
return 0;
}
final Game.Move[] legalMoves = copy.getLegalMoves();
int score = maximizing? depth + 1 : -depth - 1;
for (final Game.Move next : legalMoves) {
if (maximizing) {
score = Math.min(score, getMoveScore(copy, depth - 1, next, false));
} else {
score = Math.max(score, getMoveScore(copy, depth - 1, next, true));
}
}
return score;
}
}