Skip to main content
Tweeted twitter.com/StackCodeReview/status/1119118559802617856
deleted 17 characters in body; edited tags
Source Link
200_success
  • 144.2k
  • 22
  • 188
  • 473

I made a Connect four AiFour AI with minmaxminimax algorithm. It's my first bigger JavaFX project. Any help for improvements would be really appreciated.

enter image description herescreenshot

I made a Connect four Ai with minmax algorithm. It's my first bigger JavaFX project. Any help for improvements would be really appreciated.

enter image description here

I made a Connect Four AI with minimax algorithm. It's my first bigger JavaFX project. Any help for improvements would be really appreciated.

screenshot

Source Link
Marten
  • 585
  • 6
  • 18

JavaFX Connect Four AI

I made a Connect four Ai with minmax algorithm. It's my first bigger JavaFX project. Any help for improvements would be really appreciated.

enter image description here

Board:

import java.util.ArrayList;

class Move {

    private int row, col;

    public Move(int row, int col) {
        this.row = row;
        this.col = col;
    }

    public int getRow() {
        return row;
    }

    public int getCol() {
        return col;
    }

    @Override
    public String toString() {
        return "Move{" +
                "row=" + row +
                ", col=" + col +
                '}';
    }
}


public class Board {

    private final int playerX = 1;
    private final int playerO = 2;

    private int rows;
    private int cols ;
    private int board[][];
    private int moves;
    private Move lastMove;
    private Ai ai;

    public Board(int rows, int cols, int depth) {
        this.rows = rows;
        this.cols = cols;
        this.board = new int[rows][cols];
        this.moves = 0;
        this.lastMove = new Move(0, 0);
        this.ai = new Ai(this, depth);
    }

    public int[][] getBoard() {
        return board;
    }

    public int getMoves() {
        return moves;
    }

    public Move getLastMove() {
        return lastMove;
    }

    public void doMove(Move m, int player) {
        board[m.getRow()][m.getCol()] = player;
    }

    public void undoMove(Move m) {
        board[m.getRow()][m.getCol()] = 0;
    }

    public void doAiMove(int player) {
        Move best = ai.getBestMove();
        board[best.getRow()][best.getCol()] = player;
        moves++;
        lastMove = best;
    }

    private Move generateMove(int col) {

        for(int r = rows - 1; r >= 0; r--) {
            if(board[r][col] == 0) {
                return new Move(r, col);
            }
        }
        return null;
    }

    public ArrayList<Move> generateMoves() {
        ArrayList<Move> moves = new ArrayList<>();

        for(int c = 0; c < cols; c++) {

            Move m = generateMove(c);

            if(m != null) {
                moves.add(m);
            }
        }
        return moves;
    }

    public void playerMove(int col) {
        Move m = generateMove(col);

        if(m != null) {
            doMove(m, playerX);
            moves++;
            lastMove = m;
        }
    }

    private int checkWin(int row, int col, int player) {

        if(col >= cols - 4) {
            if(board[row][col] == player && board[row][col-1] == player &&
                    board[row][col-2] == player && board[row][col-3] == player) {
                return player;
            }
        }

        if(col <= cols - 4) {
            if(board[row][col] == player && board[row][col+1] == player &&
                    board[row][col+2] == player && board[row][col+3] == player) {
                return player;
            }
        }

        if(row <= rows - 4) {
            if(board[row][col] == player && board[row+1][col] == player &&
                    board[row+2][col] == player && board[row+3][col] == player) {
                return player;
            }
        }

        if(row >= rows - 3) {
            if(board[row][col] == player && board[row-1][col] == player &&
                    board[row-2][col] == player && board[row-3][col] == player) {
                return player;
            }
        }

        if(col >= cols - 4 && row >= rows - 3) {
            if(board[row][col] == player && board[row-1][col-1] == player &&
                    board[row-2][col-2] == player && board[row-3][col-3] == player) {
                return player;
            }
        }

        if(col <= cols - 4 && row <= rows - 4) {
            if(board[row][col] == player && board[row+1][col+1] == player &&
                    board[row+2][col+2] == player && board[row+3][col+3] == player) {
                return player;
            }
        }

        if(col <= cols - 4 && row >= rows - 3) {
            if(board[row][col] == player && board[row-1][col+1] == player &&
                    board[row-2][col+2] == player && board[row-3][col+3] == player) {
                return player;
            }
        }

        if(col >= cols - 4 && row <= rows - 4) {
            if(board[row][col] == player && board[row+1][col-1] == player &&
                    board[row+2][col-2] == player && board[row+3][col-3] == player) {
                return player;
            }
        }

        return 0;
    }

    public int checkBoardState(Move lastMove, int board[][]) {

            int x = checkWin(lastMove.getRow(), lastMove.getCol(), playerX);
            int o = checkWin(lastMove.getRow(), lastMove.getCol(), playerO);

            if(x == playerX) return x;
            if(o == playerO) return o;

            if(moves == rows * cols) return 0;

            return -1;
    }

}

Ai:

import java.util.ArrayList;

public class Ai {

    private final int playerX = 1;
    private final int playerO = 2;
    private Board board;

    private int depth;
    private Move bestMove;

    public Ai(Board board, int depth) {
        this.board = board;
        this.depth = depth;
    }

    /*public void setDepth(int depth) {
        this.depth = depth;
    }*/

    private int min(int depth, int alpha, int beta) {

        ArrayList<Move> moves = board.generateMoves();
        int minValue = beta;

        if(depth == 0 || moves.size() == 0) {
            return evaluate(board.getBoard());
        }

        for(Move m: moves) {

            board.doMove(m, playerO);
            int v = max(depth - 1, alpha, minValue);
            board.undoMove(m);

            if(v < minValue) {
                minValue = v;

                if(minValue <= alpha) break;

                if(depth == this.depth) {
                    bestMove = m;
                }
            }

        }
        return minValue;
    }

    private int max(int depth, int alpha, int beta) {

        ArrayList<Move> moves = board.generateMoves();
        int maxValue = alpha;

        if(depth == 0 || moves.size() == 0) {
            return evaluate(board.getBoard());
        }

        for(Move m: moves) {

            board.doMove(m, playerX);
            int v = min(depth - 1, maxValue, beta);
            board.undoMove(m);

            if(v > maxValue) {
                maxValue = v;

                if(maxValue >= beta) break;

                if(depth == this.depth) {
                    bestMove = m;
                }

            }

        }
        return maxValue;
    }

    private int evaluateSegment(int[] s) {

        int countX = 0, countO = 0;

        for(int i = 0; i < s.length; i++) {
            if(s[i] == playerX) countX++;
            if(s[i] == playerO) countO++;
        }

        if(countX == 0) {
            if(countO == 4) return -1000;
            if(countO == 3) return -50;
            if(countO == 2) return -10;
            if(countO == 1) return -1;
        }

        if(countO == 0) {
            if(countX == 4) return 1000;
            if(countX == 3) return 50;
            if(countX == 2) return 10;
            if(countX == 1) return 1;
        }

        return 0;
    }

    private int evaluate(int board[][], int row, int col) {

        int rows = board.length;
        int cols = board[0].length;
        int score = 0;

        if(col >= cols - 4) {
           score += evaluateSegment(new int[] {board[row][col], board[row][col-1], board[row][col-2], board[row][col-3]});
        }

        if(col <= cols - 4) {
            score += evaluateSegment(new int[] {board[row][col], board[row][col+1], board[row][col+2], board[row][col+3]});
        }

        if(row <= rows - 4) {
            score += evaluateSegment(new int[] {board[row][col], board[row+1][col], board[row+2][col], board[row+3][col]});
        }

        if(row >= rows - 3) {
            score += evaluateSegment(new int[] {board[row][col], board[row-1][col], board[row-2][col], board[row-3][col]});
        }

        if(col >= cols - 4 && row >= rows - 3) {
            score += evaluateSegment(new int[]{board[row][col], board[row-1][col-1], board[row-2][col-2], board[row-3][col-3]});
        }

        if(col <= cols - 4 && row <= rows - 4) {
            score += evaluateSegment(new int[]{board[row][col], board[row+1][col+1], board[row+2][col+2], board[row+3][col+3]});
        }

        if(col <= cols - 4 && row >= rows - 3) {
            score += evaluateSegment(new int[]{board[row][col], board[row-1][col+1], board[row-2][col+2], board[row-3][col+3]});
        }

        if(col >= cols - 4 && row <= rows - 4) {
            score += evaluateSegment(new int[]{board[row][col], board[row+1][col-1], board[row+2][col-2], board[row+3][col-3]});
        }

        return score;
    }

    private int evaluate(int board[][]) {

        int score = 0;

        for(int r = 0; r < board.length; r++) {
            for(int c = 0; c < board[r].length; c++) {

                score += evaluate(board, r, c);

            }
        }

        return score;
    }


    public Move getBestMove() {
        max(depth, Integer.MIN_VALUE, Integer.MAX_VALUE);
        return bestMove;
    }

}

Main:

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.event.ActionEvent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class Main extends Application {

    private final int rows = 6;
    private final int cols = 7;

    private final int playerX = 1;
    private final int playerO = 2;

    private final int canvasHeight = 600;
    private final int canvasWidth = 700;
    private final double cellHeight = (double) canvasHeight / rows;
    private final double cellWidth = (double) canvasWidth / cols;

    private int clickedButton = -1;

    private Board board;
    private int depth = 8;

    public Main() {
        board = new Board(rows, cols, depth);
    }

    @Override
    public void start(Stage primaryStage) {

        primaryStage.setTitle("Connect Four");

        GridPane gridPane = new GridPane();
        gridPane.setPadding(new Insets(20.0, 0.0, 20.0, 0.0));

        VBox box = new VBox();
        box.setPadding(new Insets(20.0,20.0,20.0,50.0));

        Canvas canvas = new Canvas(canvasWidth, canvasHeight);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        drawBoard(gc);


        Button buttonArray[] = new Button[cols];

        for(int i = 0; i < cols; i++) {
            buttonArray[i] = new Button("Input");
            buttonArray[i].setMinWidth(cellWidth);
            buttonArray[i].setMaxWidth(cellWidth);
            buttonArray[i].setId(""+i);
            gridPane.add(buttonArray[i], i, 0);
        }

        for(Button b: buttonArray) {

            b.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent event) {

                    if(board.getMoves() % 2 == 0 && board.checkBoardState(board.getLastMove(), board.getBoard()) == -1) {

                        clickedButton = Integer.valueOf(b.getId());
                        board.playerMove(clickedButton);
                        repaintCanvas(gc);

                        for(Button button: buttonArray) {
                            button.setDisable(true);
                        }


                        if(board.getMoves() % 2 == 1 && board.checkBoardState(board.getLastMove(), board.getBoard()) == -1) {

                            ExecutorService es = Executors.newFixedThreadPool(1);
                            Runnable r = new Runnable() {

                                @Override
                                public void run() {

                                        board.doAiMove(playerO);
                                        repaintCanvas(gc);

                                        for(Button button: buttonArray) {
                                            button.setDisable(false);
                                        }

                                    }
                            };

                            es.execute(r);
                            es.shutdown();

                        }
                    }

                }

            });
        }

        VBox b = new VBox();
        b.setPadding(new Insets(20.0,20.0,20.0,350.0));

        Button restart = new Button("Restart");
        restart.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                board = new Board(rows, cols, depth);
                repaintCanvas(gc);
            }
        });

        b.getChildren().add(restart);


        box.getChildren().addAll(gridPane, canvas, b);
        primaryStage.setScene(new Scene(box, 800, 800));
        primaryStage.setResizable(false);


        primaryStage.show();
    }


    private void drawBoard(GraphicsContext gc) {

        gc.setFill(Color.rgb(128, 255, 0));
        gc.fillRect(0, 0, canvasWidth, canvasHeight);

        gc.setFill(Color.BLACK);

        for(int i = 0; i <= rows; i++) {
            gc.strokeLine(0, i * cellHeight, canvasWidth, i * cellHeight);
        }

        for(int i = 0; i <= cols; i++) {
            gc.strokeLine(i * cellWidth, 0, i * cellWidth, canvasHeight);
        }

        int offset = 3;
        int board[][] = this.board.getBoard();

        for(int r = 0; r < rows; r++) {
            for(int c = 0; c < cols; c++) {

                if(board[r][c] == playerX) {
                    gc.setFill(Color.RED);
                    gc.fillOval(c * cellHeight, r * cellWidth, cellWidth - offset, cellHeight - offset);
                }

                if(board[r][c] == playerO) {
                    gc.setFill(Color.BLUE);
                    gc.fillOval(c * cellHeight, r * cellWidth, cellWidth - offset, cellHeight - offset);
                }

            }
        }
    }

    private void repaintCanvas(GraphicsContext gc) {
        gc.clearRect(0, 0, canvasWidth, canvasHeight);
        drawBoard(gc);
    }


    public static void main(String[] args) {
        Main m = new Main();
        launch(args);

    }
}