'Why my connect4 minimax doesn't work properly?

Can you please explain to me why my AI can play properly, until player has 3 in a row in sixth or seventh column then my AI refuses to block him. And another problem is in a position when my AI can win in 1 move, but he will rather choose a longer game. Can anyone help me please.

Returns a value for a winning move

def utility(board):
    if terminal(board):
        if winning_move(board, X):
            return 1000000
        elif winning_move(board, O):
            return -10000
        else:
            return 0

Calculates the score of position when depth = 0

def compute_score(block, board, col, row):
    score = 0

    if block.count(X) == 4 and block.count("-") == 0:
        score += 100000
    elif block.count(X) == 3 and block.count("-") == 1:
        score += 10
    elif block.count(X) == 2 and block.count("-") == 2:
        score += 3

    if block.count(O) == 4 and block.count("-") == 0:
        score -= 1000000
    elif block.count(O) == 3 and block.count("-") == 1:
        score -= 6
    elif block.count(O) == 2 and block.count("-") == 2:
        score -= 2
    return score

Gives value to every possible "four block"

def score_position(board):
    score = 0
    mid_section = []

#increases score because playing in middle is usually good move
    for col in range(5):
        mid_section.append(board[col][x // 2])
    score += mid_section.count(X) * 3

#claculates score of all horizontal 4 in a rows
    for row in range(y):
        for col in range(x - 3):
            block = board[col: col + 4]
            score += compute_score(block, board, col, row)

#claculates score of all vertical 4 in a rows
    for col in range(x):
        for row in range(y - 3):
            block = [board[row][col], board[row + 1][col], board[row + 2][col], board[row + 3][col]]
            score += compute_score(block, board, col, row)


#claculates score of all diagonals from left bottom to right top 4 in a rows
    for row in range(y - 3):
        for col in range(x - 3):
            block = [board[row][col], board[row + 1][col + 1], board[row + 2][col + 2], board[row + 3][col + 3]]
            score += compute_score(block, board, col, row)

#claculates score of all diagonals from right bottom to left top 4 in a rows
    for row in range(y - 3):
        for col in range(x - 1, 2, -1):
            block = [board[row][col], board[row + 1][col - 1], board[row + 2][col - 2], board[row + 3][col - 3]]
            score += compute_score(block, board, col, row)
    return score

The minimax algorithm (if you want to know more about this algorithm check this video - https://www.youtube.com/watch?v=l-hh51ncgDI)

def minimax(board, depth):
    #if terminal(board):
     #   return None

    best_move = None
    alpha = -math.inf
    beta = math.inf

    if player(board) == X:
        if board == initial_state():
            best_move = x//2
            return best_move

        best_score = -math.inf
        for action in actions(board):
            score = minimaze(result(board, action), alpha, beta, depth)
            alpha = max(alpha, score)
            if score > best_score:
                best_score = score
                best_move = action
        return best_move

    else:
        best_score = math.inf
        for action in actions(board):
            score = maximaze(result(board, action), alpha, beta, depth)
            beta = min(beta, score)
            if score < best_score:
                best_score = score
                best_move = action
        print(best_move)
        return best_move

The minimazing player (returns the best player move four the specific position)

def minimaze(board, alpha, beta, depth):
    if terminal(board):
        return utility(board)
    elif depth <= 0:
        return score_position(board)

    v = math.inf
    for action in actions(board):
        v = min(v, maximaze(result(board, action), alpha, beta, depth - 1))
        beta = min(beta, v)
    return v

The maximazing AI (returns the best AI move four the specific position)

def maximaze(board, alpha, beta, depth):
    if terminal(board):
        return utility(board)
    elif depth <= 0:
        return score_position(board)

    v = -math.inf
    for action in actions(board):
        v = max(v, minimaze(result(board, action), alpha, beta, depth - 1))
        alpha = max(alpha, v)
    return v


Solution 1:[1]

I have been working on the exact same project recently :)
For your second problem, to make the AI win as fast as it can you should change your score system.

  • When the game hasn't over, You can use your score_position function.
  • When the game is over (some player had won the game), Give score based on how many turns passed in the game. But also indicate that the game had over.
    You can do that by changing the score addition when there are four pieces in a row:
if block.count(X) == 4 and block.count("-") == 0:
    score += 100000 * (places_in_board - turns_played + 1)

if block.count(O) == 4 and block.count("-") == 0:
    score -= 1000000 * (places_in_board - turns_played + 1)

Where places_in_board is the number of places of your board and turns_played is the number of turns that had passed in the game.

Notice that as you win the game in less turns, the score you get is higher.
This way the AI would 'prefer' to take the short way to a win- The score it would get just will be greater.

AND, as a side effect you get another improvement to your AI!!
If the AI got somehow into a situation which it will always lose at (if the opponent plays perfectly), It would prefer to take the longest way to its lost. Why? Because in the 'longest lose' turns_played will be greater, so

score -= 1000000 * (places_in_board - turns_left + 1)

will reduce less to the score. The score would be higher so the AI would prefer to take that 'long lose'.

Here is link for my connect4 AI if you need some reference: https://github.com/YotamZaiger1/connect_4_AI
My scoring function is in Board file, called "state_value".

Hope I helped, good luck!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Y_Z