Gomoku(Five in a Row, Omok) (1/5) - 기본 구현 (minimax, alpha-beta pruning)
개발/AI,ML,ALGORITHM 2023. 10. 27. 10:30tic tac toe 게임 머신러닝에 이어 오목 게임을 만들어 보자.
아래와 같은 순서로 총 5회로 구성하였다.
Gomoku(Five in a Row, Omok) (1/5) - 기본 구현 (minimax, alpha-beta pruning)
Gomoku(Five in a Row, Omok) (2/5) - 속도 최적화 1차 (minimax 속도 개선)
Gomoku(Five in a Row, Omok) (3/5) - 속도 최적화 2차 (RANDOM모드 추가)
Gomoku(Five in a Row, Omok) (4/5) - 훈련 데이터 생성 및 학습
Gomoku(Five in a Row, Omok) (5/5) - 머신러닝으로 게임 구현
9x9보드에서 minimax알고리즘을 사용하여 간단하게 구현해 보았다.
참고로 3x3을 막고있지 않다.
DIFFICULTY_LEVEL 값을 높이면 좀더 잘 두지만 속도는 느려진다. 반대로 값을 낮추면 속도는 빨라지지만 잘 두지는 못한다.
가끔씩 엉뚱한 수를 두는 이유는 DIFFICULTY_LEVEL 제한으로 minimax트리검색이 중간에 중단되어 마지막으로 중단된 위치에 수를 놓기 때문이다. 해당 속도문제를 속도 최적화 1차, 속도 최적화 2차를 통해 개선하도록 하겠다.
# 필요한 모듈 임포트
import pygame
import random
import numpy as np
import sys
# 게임 보드 설정
BOARD_SIZE = 9 # 게임 보드 크기 (9x9)
CELL_SIZE = 40 # 셀 크기
MARGIN = 20 # 보드와 화면 경계의 여백
CLICK_RADIUS = CELL_SIZE // 2 # 클릭 시 지정된 반지름
# 게임 보드의 셀 상태
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GRAY = (100, 100, 100)
# 게임 변수 및 난이도 설정
NUM_ITEMS = (BOARD_SIZE * BOARD_SIZE)
PLAYER = 1
AI = -1
DIFFICULTY_LEVEL = 3 # 게임 난이도 (컴퓨터가 어려운 수를 계산하는 깊이)
# 게임 클래스 정의
class FIAR:
def __init__(self):
self.init_game()
def init_game(self):
# Pygame 초기화
pygame.init()
pygame.display.set_caption("FIAR")
w = (BOARD_SIZE-1) * CELL_SIZE + 2 * MARGIN
h = (BOARD_SIZE-1) * CELL_SIZE + 2 * MARGIN
self.screen = pygame.display.set_mode((w, h))
self.font = pygame.font.Font(None, CELL_SIZE-4)
# 게임 초기화
def reset_game(self):
self.board = np.zeros((BOARD_SIZE, BOARD_SIZE), dtype=int)
self.sequences = np.zeros((BOARD_SIZE, BOARD_SIZE), dtype=int)
self.sequence = 0
self.game_over = False
self.turn_player = PLAYER
self.draw_board(self.screen)
pygame.display.flip()
# 빈 셀 찾기
def find_empty_cells(self):
empty_cells = []
for row in range(BOARD_SIZE):
for col in range(BOARD_SIZE):
if self.board[row][col] == 0:
empty_cells.append((row, col))
random.shuffle(empty_cells)
return empty_cells
# 승자 확인
def check_winner(self, board, row, col):
# 승자 판별 로직
def check_direction(dx, dy):
count = 1
r, c = row + dx, col + dy
while 0 <= r < len(board) and 0 <= c < len(board[0]) and board[r][c] == board[row][col]:
count += 1
r += dx
c += dy
return count
directions = [(0, 1), (1, 0), (1, 1), (-1, 1)]
for dx, dy in directions:
count1 = check_direction(dx, dy)
count2 = check_direction(-dx, -dy)
total_count = count1 + count2 - 1
if total_count == 5:
return True
return False
# 게임 보드가 가득 찼는지 확인
def is_board_full(self, board):
return all(cell != 0 for row in board for cell in row)
# 무작위로 수를 두는 함수
def random_move(self, player):
if self.game_over:
return -1, -1
row, col = random.choice(self.find_empty_cells())
self.make_move(row, col, player)
return row, col
# 미니맥스 알고리즘을 사용해 수를 두는 함수
def minimax_move(self, player):
if self.game_over:
return -1, -1
row, col = self.find_best_move(player)
self.make_move(row, col, player)
return row, col
# 수를 두는 함수
def make_move(self, row, col, player):
# 게임 보드에 수를 두고 승자를 확인하는 로직
if self.board[row][col] == 0:
self.board[row][col] = player
self.sequence += 1
self.sequences[row][col] = self.sequence
if self.check_winner(self.board, row, col):
self.game_over = True
elif self.is_board_full(self.board):
self.game_over = True
self.turn_player = 0
else:
self.turn_player *= -1
# 미니맥스 알고리즘
def minimax(self, depth, is_maximizing, alpha, beta, row2, col2):
# 미니맥스 알고리즘으로 최적의 수를 찾는 로직
if self.is_board_full(self.board):
return 0
if is_maximizing:
if self.check_winner(self.board, row2, col2):
return -(NUM_ITEMS - depth + 1)
else:
if self.check_winner(self.board, row2, col2):
return (NUM_ITEMS - depth + 1)
if depth >= DIFFICULTY_LEVEL:
return 0
if is_maximizing:
best_score = -float('inf')
for row, col in self.find_empty_cells():
self.board[row][col] = AI
score = self.minimax(depth + 1, False, alpha, beta, row, col)
self.board[row][col] = 0
best_score = max(best_score, score)
alpha = max(alpha, best_score)
if beta <= alpha:
break
return best_score
else:
best_score = float('inf')
for row, col in self.find_empty_cells():
self.board[row][col] = PLAYER
score = self.minimax(depth + 1, True, alpha, beta, row, col)
self.board[row][col] = 0
best_score = min(best_score, score)
beta = min(beta, best_score)
if beta <= alpha:
break
return best_score
# 최적의 수를 찾는 함수
def find_best_move(self, player):
# 최적의 수를 찾는 함수
if self.sequence == 0:
row = BOARD_SIZE // 2 + random.randint(-2, 2)
col = BOARD_SIZE // 2 + random.randint(-2, 2)
return (row, col)
alpha = -float('inf')
beta = float('inf')
best_move = None
best_score = -float('inf') if player == AI else float('inf')
empty_cells = self.find_empty_cells()
for index, (row, col) in enumerate(empty_cells):
self.board[row][col] = player
is_maximizing = False if player == AI else True
score = self.minimax(0, is_maximizing, alpha, beta, row, col)
self.board[row][col] = 0
if (player == AI and score > best_score) or (player == PLAYER and score < best_score):
best_score = score
best_move = (row, col)
percentage = (index / len(empty_cells)) * 100
print(f" [{percentage:.1f}%] <{player}> {row},{col} -> {score}")
return best_move
# 화면에 메시지 표시
def show_message(self, message, is_exit=False):
popup = True
while popup:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if is_exit:
if event.key == pygame.K_y:
self.reset_game()
popup = False
return
elif event.key == pygame.K_n:
pygame.quit()
sys.exit()
else:
popup = False
break
# 메시지 텍스트 표시
text_lines = message.split('\n')
for i, line in enumerate(text_lines):
text = self.font.render(line, True, (0, 0, 0))
text_rect = text.get_rect(topleft=(20, 20 + i * 20))
self.screen.blit(text, text_rect)
pygame.display.flip()
# 게임 보드 그리기
def draw_board(self, screen):
screen.fill(GRAY)
for row in range(BOARD_SIZE):
pygame.draw.line(screen, BLACK,
(0 * CELL_SIZE + MARGIN, row * CELL_SIZE + MARGIN),
((BOARD_SIZE-1) * CELL_SIZE + MARGIN, row * CELL_SIZE + MARGIN),
1)
for col in range(BOARD_SIZE):
if row == 0:
pygame.draw.line(screen, BLACK,
(col * CELL_SIZE + MARGIN, 0 * CELL_SIZE + MARGIN),
(col * CELL_SIZE + MARGIN, (BOARD_SIZE-1) * CELL_SIZE + MARGIN),
1)
x = col * CELL_SIZE + MARGIN
y = row * CELL_SIZE + MARGIN
if self.board[row][col] == PLAYER:
pygame.draw.circle(screen, BLACK, (x, y), CLICK_RADIUS)
elif self.board[row][col] == AI:
pygame.draw.circle(screen, WHITE, (x, y), CLICK_RADIUS)
seq_no = self.sequences[row][col]
if seq_no != 0:
if seq_no == self.sequence:
pygame.draw.circle(screen, RED, (x, y), CLICK_RADIUS+1, 1)
color = WHITE if self.board[row][col] == PLAYER else BLACK
text = self.font.render(f"{seq_no}", True, color)
text_rect = text.get_rect(center=(x, y))
screen.blit(text, text_rect)
# 이벤트 처리
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.game_over = True
elif event.type == pygame.MOUSEBUTTONDOWN and not self.game_over:
x, y = pygame.mouse.get_pos()
row = int(round((y - MARGIN) / CELL_SIZE))
col = int(round((x - MARGIN) / CELL_SIZE))
if 0 <= row < BOARD_SIZE and 0 <= col < BOARD_SIZE and self.board[row][col] == 0:
self.make_move(row, col, self.turn_player)
self.draw_board(self.screen)
pygame.display.flip()
if self.turn_player == AI:
best_move = self.find_best_move(self.turn_player)
if best_move is not None:
row, col = best_move
self.make_move(row, col, self.turn_player)
self.draw_board(self.screen)
pygame.display.flip()
if self.game_over == True:
if self.turn_player == 0:
self.show_message("Game draw!")
else:
self.show_message(f"Game Over!\n{'Player' if self.turn_player == PLAYER else 'AI'} wins!")
if __name__ == "__main__":
game = FIAR()
game.reset_game()
while True:
while not game.game_over:
game.handle_events()
game.show_message("Play Again? (y/n)", is_exit=True)
아래는 게임 실행화면이다.
게임 오버 상태에서 텍스트가 겹치는 오류가 있네.. -_-;;
중요한 문제 아니니 그건 나중에 수정하도록 하겠다.
2023.10.27 - [AI,ML, Algorithm] - Gomoku(Five in a Row, Omok) (2/5) - 속도 최적화 1차 (minimax 속도 개선)
2023.10.28 - [AI,ML, Algorithm] - Gomoku(Five in a Row, Omok) (3/5) - 속도 최적화 2차 (RANDOM모드 추가)
2023.10.29 - [AI,ML, Algorithm] - Gomoku(Five in a Row, Omok) (4/5) - 훈련 데이터 생성 및 학습
2023.10.29 - [AI,ML, Algorithm] - Gomoku(Five in a Row, Omok) (5/5) - 머신러닝으로 게임 구현
2023.11.03 - [AI,ML, Algorithm] - Gomoku(Five in a Row, Omok) (5/5) - 3x3 체크 추가
'개발 > AI,ML,ALGORITHM' 카테고리의 다른 글
Gomoku(Five in a Row, Omok) (3/5) - 속도 최적화 2차 (RANDOM모드 추가) (1) | 2023.10.28 |
---|---|
Gomoku(Five in a Row, Omok) (2/5) - 속도 최적화 1차 (minimax 속도 개선) (0) | 2023.10.27 |
Tic-Tac-Toe 게임 제작 (4/4) - 머신러닝을 이용한 게임 구현 (0) | 2023.09.12 |
Tic-Tac-Toe 게임 제작 (3/4) - 머신러닝 훈련 데이터 생성 (0) | 2023.09.12 |
Tic-Tac-Toe 게임 제작 (2/4) - alpha–beta pruning (0) | 2023.09.12 |