호그와트

Kaggle ConnectX야 놀쟈

영웅*^%&$ 2023. 5. 23. 13:14
728x90

from learntools.core import binder
binder.bind(globals())
from learntools.game_ai.ex1 import *

# Gets board at next step if agent drops piece in selected column
def drop_piece(grid, col, piece, config):
    next_grid = grid.copy()
    for row in range(config.rows-1, -1, -1):
        if next_grid[row][col] == 0:
            break
    next_grid[row][col] = piece
    return next_grid

# Returns True if dropping piece in column results in game win
def check_winning_move(grid, config, col, piece):
    next_grid = drop_piece(grid, col, piece, config)
    # horizontal
    for row in range(config.rows):
        for col in range(config.columns-(config.inarow-1)):
            window = list(next_grid[row,col:col+config.inarow])
            if window.count(piece) == config.inarow:
                return True
    # vertical
    for row in range(config.rows-(config.inarow-1)):
        for col in range(config.columns):
            window = list(next_grid[row:row+config.inarow,col])
            if window.count(piece) == config.inarow:
                return True
    # positive diagonal
    for row in range(config.rows-(config.inarow-1)):
        for col in range(config.columns-(config.inarow-1)):
            window = list(next_grid[range(row, row+config.inarow), range(col, col+config.inarow)])
            if window.count(piece) == config.inarow:
                return True
    # negative diagonal
    for row in range(config.inarow-1, config.rows):
        for col in range(config.columns-(config.inarow-1)):
            window = list(next_grid[range(row, row-config.inarow, -1), range(col, col+config.inarow)])
            if window.count(piece) == config.inarow:
                return True
    return False 

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

이 코드는 ConnectX 게임에서 에이전트의 행동을 정의하는 함수입니다. ConnectX는 2인용 보드 게임으로, 플레이어는 빨간색 또는 노란색 돌을 놓으며 4개의 돌을 연속으로 놓는 것을 목표로 합니다.

grid: 게임 보드를 나타내는 2차원 배열입니다.
col: 돌을 놓을 열의 인덱스입니다.
piece: 돌의 색상을 나타내는 값으로, 1은 빨간색, 2는 노란색을 의미합니다.
config: 게임 설정 정보를 담고 있는 객체로, 게임의 행과 열의 수, 연속된 돌의 개수 등을 포함합니다.
코드의 주요 부분을 자세히 설명하겠습니다:

drop_piece 함수: 선택한 열(col)에 돌을 놓은 다음의 보드 상태를 반환합니다. 선택한 열에서 가장 아래에 있는 빈 칸에 돌을 놓습니다.
check_winning_move 함수: 선택한 열(col)에 돌을 놓았을 때, 해당 위치에서 게임 승리 조건을 만족하는지 확인합니다. 가로, 세로, 대각선 방향으로 연속된 돌이 config.inarow 개수만큼 있는지 검사합니다.
가로 확인: 모든 행에 대해 연속된 config.inarow 개수의 돌이 있는지 확인합니다.
세로 확인: 모든 열에 대해 연속된 config.inarow 개수의 돌이 있는지 확인합니다.
양 대각선 확인: 왼쪽 상단부터 시작하여 우측 아래로 향하는 대각선 방향에 대해 연속된 config.inarow 개수의 돌이 있는지 확인합니다.
음 대각선 확인: 왼쪽 하단부터 시작하여 우측 상단으로 향하는 대각선 방향에 대해 연속된 config.inarow 개수의 돌이 있는지 확인합니다.
함수는 해당 조건을 만족하면 True를 반환하고, 그렇지 않으면 False를 반환합니다.

이 코드는 ConnectX 게임에서 에이전트의 행동을 결정하기 위해 게임 보드와 승리 조건을 확인하는 기능을 제공합니다.

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

# Minimax implementation
def minimax(input_board, depth, maximizingPlayer, col, config, alpha, beta):
    # Convert the board to a 2D grid
    grid = np.asarray(input_board.board).reshape(config.rows, config.columns)
    if depth == 0 or check_winning_move(grid, config, col, maximizingPlayer):
        if check_winning_move(grid, config, col, maximizingPlayer):
            return (None, np.Inf if maximizingPlayer else -np.Inf)
        else:
            return (None, 0)

    valid_moves = [c for c in range(config.columns) if input_board.board[c] == 0]

    if maximizingPlayer:
        value = -np.Inf
        column = random.choice(valid_moves)
        for col in valid_moves:
            child = drop_piece(grid, col, maximizingPlayer, config)
            (child_column, child_value) = minimax(child, depth-1, False, col, config, alpha, beta)
            if child_value > value:
                value = child_value
                column = col
            alpha = max(alpha, value)
            if alpha >= beta:
                break
        return (column, value)

    else: # Minimizing player
        value = np.Inf
        column = random.choice(valid_moves)
        for col in valid_moves:
            child = drop_piece(grid, col, maximizingPlayer, config)
            (child_column, child_value) = minimax(child, depth-1, True, col, config, alpha, beta)
            if child_value < value:
                value = child_value
                column = col
            beta = min(beta, value)
            if alpha >= beta:
                break
        return (column, value)

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

위의 코드는 Minimax 알고리즘을 구현한 부분입니다. Minimax는 미니멕스라고도 불리며, 턴 기반의 게임에서 최적의 수를 선택하는 데 사용되는 알고리즘입니다.

minimax 함수: Minimax 알고리즘을 구현한 함수입니다. 함수는 현재 보드 상태(input_board)와 플레이어(maximizingPlayer)를 기반으로 최적의 수를 선택합니다.
depth: 현재 탐색하는 깊이로, 알고리즘이 얼마나 많은 수를 미리 탐색할지 결정합니다.
col: 현재 선택한 열의 인덱스입니다.
config: 게임 설정 정보를 담고 있는 객체로, 게임의 행과 열의 수, 연속된 돌의 개수 등을 포함합니다.
alpha와 beta: 알파-베타 가지치기(Alpha-Beta Pruning)를 위한 값으로, 최적의 수를 탐색하는 동안 이 값들을 갱신하며 가지치기를 수행합니다.
Minimax 알고리즘의 주요 동작은 다음과 같습니다:

현재 깊이가 0이거나 승리 조건을 만족하는 경우:

만약 승리 조건을 만족하는 수를 선택하면 해당 수와 무한대(무조건 이길 수 있음)를 반환합니다.
그렇지 않으면 None과 0을 반환합니다.

최대화 플레이어인 경우:

최적의 값을 초기화하고, 무작위로 열을 선택합니다.
각 유효한 열에 대해 다음 단계를 수행합니다:
선택한 열에 돌을 놓은 다음 보드를 생성합니다.
재귀적으로 Minimax 함수를 호출하여 하위 노드를 탐색하고, 값을 가져옵니다.
만약 하위 노드의 값이 현재 최적의 값보다 크다면, 최적의 값을 갱신하고 해당 열을 선택합니다.
알파 값을 업데이트하고, 만약 알파 값이 베타 값 이상이면 가지치기를 수행합니다.
선택한 열과 최적의 값(최대화된 값)을 반환합니다.

최소화 플레이어인 경우:

최적의 값을 초기화하고, 무작위로 열을 선택합니다.
각 유효한 열에 대해 다음 단계를 수행합니다:
선택한 열에 돌을 놓은 다음 보드를 생성합니다.
재귀적으로 Minimax 함수를 호출하여 하위 노드를 탐색하고, 값을 가져옵니다.
만약 하위 노드의 값이 현재 최적의 값보다 작다면, 최적의 값을 갱신하고 해당 열을 선택합니다.
베타 값을 업데이트하고, 만약 알파 값이 베타 값 이상이면 가지치기를 수행합니다.
선택한 열과 최적의 값(최소화된 값)을 반환합니다.
위의 코드는 Minimax 알고리즘을 사용하여 현재 게임 상태에서 최적의 수를 선택하는 데 활용됩니다.


++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

def my_agent(obs, config):
    # Convert the board to a 2D grid
    grid = np.asarray(obs.board).reshape(config.rows, config.columns)
    (column, value) = minimax(grid, 4, True, obs.mark, config, -np.Inf, np.Inf)
    if column == None:
        column = random.choice([c for c in range(config.columns) if obs.board[c] == 0])
    return column

from kaggle_environments import evaluate, make


def my_agent(obs, config):  
    import numpy as np  
    import random  

    def drop_piece(grid, col, piece, config):  
        next_grid = grid.copy()  
        for row in range(config.rows-1, -1, -1):  
            if next_grid[row][col] == 0:  
                break  
        next_grid[row][col] = piece  
        return next_grid  

    def check_winning_move(obs, config, col, piece):  
        grid = np.asarray(obs.board).reshape(config.rows, config.columns)  
        next_grid = drop_piece(grid, col, piece, config)  
        for row in range(config.rows):  
            for col in range(config.columns-(config.inarow-1)):  
                window = list(next_grid[row,col:col+config.inarow])  
                if window.count(piece) == config.inarow:  
                    return True  
        for row in range(config.rows-(config.inarow-1)):  
            for col in range(config.columns):  
                window = list(next_grid[row:row+config.inarow,col])  
                if window.count(piece) == config.inarow:  
                    return True  
        for row in range(config.rows-(config.inarow-1)):  
            for col in range(config.columns-(config.inarow-1)):  
                window = list(next_grid[range(row, row+config.inarow), range(col, col+config.inarow)])  
                if window.count(piece) == config.inarow:  
                    return True  
        for row in range(config.inarow-1, config.rows):  
            for col in range(config.columns-(config.inarow-1)):  
                window = list(next_grid[range(row, row-config.inarow, -1), range(col, col+config.inarow)])  
                if window.count(piece) == config.inarow:  
                    return True  
        return False  

    def get_heuristic(grid, mark, config):  
        A = 0  
        B = 1  
        C = 1e2  
        D = 1e3  
        E = 0  
        F = -1e2  
        G    = -1e3  
        H = -1e5  
        num_one = count_windows(grid, 1, mark, config)  
        num_two = count_windows(grid, 2, mark, config)  
        num_three = count_windows(grid, 3, mark, config)  
        num_four = count_windows(grid, 4, mark, config)  
        num_one_opp = count_windows(grid, 1, mark%2+1, config)  
        num_two_opp = count_windows(grid, 2, mark%2+1, config)  
        num_three_opp = count_windows(grid, 3, mark%2+1, config)  
        num_four_opp = count_windows(grid, 4, mark%2+1, config)  
        score = num_one * A + num_two * B + num_three * C + num_four * D + num_one_opp * E + num_two_opp * F + num_three_opp * G + num_four_opp * H  
        return score  

    def check_window(window, num_discs, piece, config):  
        return (window.count(piece) == num_discs and window.count(0) == config.inarow-num_discs)  

    def count_windows(grid, num_discs, piece, config):  
        num_windows = 0  
        for row in range(config.rows):  
            for col in range(config.columns-(config.inarow-1)):  
                window = list(grid[row, col:col+config.inarow])  
                if check_window(window, num_discs, piece, config):  
                    num_windows += 1  
        for row in range(config.rows-(config.inarow-1)):  
            for col in range(config.columns):  
                window = list(grid[row:row+config.inarow, col])  
                if check_window(window, num_discs, piece, config):  
                    num_windows += 1  
        for row in range(config.rows-(config.inarow-1)):  
            for col in range(config.columns-(config.inarow-1)):  
                window = list(grid[range(row, row+config.inarow), range(col, col+config.inarow)])  
                if check_window(window, num_discs, piece, config):  
                    num_windows += 1  
        for row in range(config.inarow-1, config.rows):  
            for col in range(config.columns-(config.inarow-1)):  
                window = list(grid[range(row, row-config.inarow, -1), range(col, col+config.inarow)])  
                if check_window(window, num_discs, piece, config):  
                    num_windows += 1  
        return num_windows  

    valid_moves = [col for col in range(config.columns) if obs.board[col] == 0]  
    for attempt in valid_moves:  
        if check_winning_move(obs, config, attempt, obs.mark):  
            return attempt  
    for attempt in valid_moves:  
        if check_winning_move(obs, config, attempt, obs.mark%2+1):  
            return attempt  
    heuristics = list()  
    for col in valid_moves:  
        temp = drop_piece(np.asarray(obs.board).reshape(config.rows, config.columns), col, obs.mark, config)  
        heuristics.append(get_heuristic(temp, obs.mark, config))  
    best_val = max(heuristics)  
    best_choices = list()  
    for i in range(len(heuristics)):  
        if heuristics[i] == best_val:  
            best_choices.append(valid_moves[i])  
    return random.choice(best_choices)  


env = make("connectx", debug=True)
env.run([my_agent, "random"])
env.render(mode="ipython")

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

위의 코드는 ConnectX 게임에서 사용되는 AI 에이전트 함수입니다. 이 함수는 현재 게임 상태(obs)와 게임 설정 정보(config)를 받아서, AI가 다음 수를 결정하고 선택하는 역할을 합니다.

주요 동작:

drop_piece 함수: 선택한 열(col)에 돌을 놓은 다음의 보드 상태를 반환합니다. 선택한 열에서 가장 아래에 있는 빈 칸에 돌을 놓습니다.

check_winning_move 함수: 선택한 열(col)에 돌을 놓았을 때, 해당 위치에서 게임 승리 조건을 만족하는지 확인합니다. 가로, 세로, 대각선 방향으로 연속된 돌이 config.inarow 개수만큼 있는지 검사합니다.

get_heuristic 함수: 현재 보드 상태(grid)와 플레이어의 돌 색상(mark), 게임 설정 정보(config)를 기반으로 휴리스틱 점수를 계산합니다. 이 점수는 AI가 해당 수의 유리성을 평가하는 지표입니다.

count_windows 함수: 현재 보드 상태(grid)에서 연속된 돌의 개수(num_discs)와 플레이어의 돌 색상(piece), 게임 설정 정보(config)를 기반으로 가능한 윈도우(연속된 돌의 조합)의 개수를 계산합니다.

AI 에이전트 함수: 현재 보드 상태에서 승리할 수 있는 수가 있는지 먼저 확인합니다. 만약 있으면 해당 수를 선택하여 승리를 시도합니다. 그렇지 않은 경우, 가능한 수들에 대해 각각 휴리스틱 점수를 계산하고, 가장 높은 점수를 가진 수 중에서 무작위로 선택합니다.

이 코드는 AI 에이전트가 ConnectX 게임에서 최적의 수를 선택하도록 구현되어 있습니다.

728x90