2025-05-10

ai06NalMok

#import pdb
import sys
import random
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox, QPushButton
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt, QTimer, QPoint
BOARD_SIZE = 19
CELL_SIZE = 40
RADIUS = CELL_SIZE>>1
EMPTY = 0
BLACK = 1
WHITE = 2
#보드 바탕색
BOARD_COLOR = QColor(222, 184, 135)
# 나이트 이동 방향
NALMOK_DIRECTIONS = [(1, 2), (-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (2, -1), (2, 1)]
class NalmokGame(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("날목")
        self.setFixedSize(BOARD_SIZE * CELL_SIZE, BOARD_SIZE * CELL_SIZE + 50)
        self.board = [[EMPTY for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)]
        self.move_history = []
        self.game_over_flag = False
        self.last_black_move = None
        self.last_white_move = None
        undo_button = QPushButton("Undo", self)
        undo_button.move(10, self.height() - 40)
        undo_button.clicked.connect(self.undo_move)
        # 컴퓨터가 흑으로 시작
        center = BOARD_SIZE // 2
        self.place_stone(center, center, BLACK)
        self.turn = WHITE
        self.show()
    def paintEvent(self, event):
        qp = QPainter()
        qp.begin(self)
        self.draw_board(qp)
        self.draw_stones(qp)
        qp.end()
    def draw_board(self, qp):
        qp.setBrush(BOARD_COLOR)
        qp.drawRect(0, 0, self.width(), self.height())
        qp.setPen(QPen(Qt.black, 1))
        for i in range(BOARD_SIZE):
            qp.drawLine(RADIUS, RADIUS + i * CELL_SIZE,
                            RADIUS + (BOARD_SIZE - 1) * CELL_SIZE, RADIUS + i * CELL_SIZE)
            qp.drawLine(RADIUS + i * CELL_SIZE, RADIUS,
                            RADIUS + i * CELL_SIZE, RADIUS + (BOARD_SIZE - 1) * CELL_SIZE)
        star_points = [(3, 3), (3, 9), (3, 15), (9, 3), (9, 9), (9, 15), (15, 3), (15, 9), (15, 15)]
        for x, y in star_points:
            cx = RADIUS + x * CELL_SIZE
            cy = RADIUS + y * CELL_SIZE
            qp.setBrush(BLACK)
            qp.drawEllipse(cx-3, cy-3, 6, 6)
    def draw_stones(self, qp):
        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] != EMPTY:
                    qp.setBrush(QColor(0, 0, 0) if self.board[y][x] == BLACK else QColor(255, 255, 255))
                    qp.drawEllipse(
                        RADIUS + x * CELL_SIZE - RADIUS,
                        RADIUS + y * CELL_SIZE - RADIUS,
                        RADIUS * 2,
                        RADIUS * 2
                    )
        last = self.last_black_move if self.turn == WHITE else self.last_white_move
#       pdb.set_trace()
        if last:
            x, y = last
            cx = RADIUS + x * CELL_SIZE
            cy = RADIUS + y * CELL_SIZE
            qp.setPen(QPen(BOARD_COLOR, 2))
            qp.drawRect(cx-1, cy-1, 3, 3)
    def mousePressEvent(self, event):
        if self.game_over_flag or self.turn != WHITE:
            return
        x = int((event.x() - RADIUS + CELL_SIZE / 2) // CELL_SIZE)
        y = int((event.y() - RADIUS + CELL_SIZE / 2) // CELL_SIZE)
        if 0 <= x < BOARD_SIZE and 0 <= y < BOARD_SIZE and self.board[y][x] == EMPTY:
            self.place_stone(x, y, WHITE)
            if self.check_win(x, y, WHITE):
                self.update()
                self.game_over("사용자(백) 승리!")
                return
            self.turn = BLACK
            self.update()
            QTimer.singleShot(1, self.computer_move)
    def place_stone(self, x, y, stone):
        self.board[y][x] = stone
        self.move_history.append((x, y, stone))
        if stone == BLACK:
            self.last_black_move = (x, y)
        else:
            self.last_white_move = (x, y)
    def undo_move(self):
        if len(self.move_history) >= 2:
            for _ in range(2):
                x, y, stone = self.move_history.pop()
                self.board[y][x] = EMPTY
                if stone == BLACK:
                    self.last_black_move = None
                else:
                    self.last_white_move = None
            self.turn = WHITE
            self.update()
    def check_win(self, x, y, stone):
        for dx, dy in NALMOK_DIRECTIONS:
            count = 1
            for dir in [1, -1]:
                for i in range(1, 5):
                    nx, ny = x + dx * i * dir, y + dy * i * dir
                    if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny][nx] == stone:
                        count += 1
                    else:
                        break
            if count >= 5:
                return True
        return False
    def should_block_nalmok(self, x, y, stone, length):
        for dx, dy in NALMOK_DIRECTIONS:
            count = 0
            blocked = 0
            for dir in [1, -1]:
                temp_count = 0
                temp_blocked = 0
                for i in range(1, length):
                    nx = x + dx * i * dir
                    ny = y + dy * i * dir
                    if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE:
                        cell = self.board[ny][nx]
                        if cell == stone:
                            temp_count += 1
                        elif cell == EMPTY:
                            continue
                        else:
                            temp_blocked += 1
                            break
                    else:
                        temp_blocked += 1
                        break
                if dir == 1:
                    count += temp_count
                    blocked += temp_blocked
                else:
                    count += temp_count
                    blocked += temp_blocked
            if count == length -1 and blocked < 2:
                # Check if placing a stone at (x, y) would complete the sequence
                placed = False
                for dir in [1, -1]:
                    nx_check = x + dx * length * dir
                    ny_check = y + dy * length * dir
                    if 0 <= nx_check < BOARD_SIZE and 0 <= ny_check < BOARD_SIZE:
                        if self.board[ny_check][nx_check] == stone:
                            continue # Already blocked on this side
                    else:
                        continue # Already blocked on this side
                    temp_board = [row[:] for row in self.board]
                    temp_board[y][x] = stone
                    win = False
                    check_count = 1
                    for check_dir in [1, -1]:
                        for i in range(1, 5):
                            cx, cy = x + dx * i * check_dir, y + dy * i * check_dir
                            if 0 <= cx < BOARD_SIZE and 0 <= cy < BOARD_SIZE and temp_board[cy][cx] == stone:
                                check_count += 1
                            else:
                                break
                    if check_count >= 5:
                        win = True
                if count == length - 1 and blocked < 2:
                    can_extend = False
                    for dir in [1, -1]:
                        nx_extend = x + dx * length * dir
                        ny_extend = y + dy * length * dir
                        if 0 <= nx_extend < BOARD_SIZE and 0 <= ny_extend < BOARD_SIZE and self.board[ny_extend][nx_extend] == EMPTY:
                            can_extend = True
                            break
                    if can_extend or blocked == 0:
                        return True
        return False
    def evaluate_board(self, stone):
        score = 0
        opponent = WHITE if stone == BLACK else BLACK
        # Evaluate score for the given stone
        for length in range(2, 5):
            for y in range(BOARD_SIZE):
                for x in range(BOARD_SIZE):
                    if self.board[y][x] == EMPTY and self.should_block_nalmok(x, y, stone, length):
                        if length == 4:
                            score += 1000
                        elif length == 3:
                            score += 100
                        elif length == 2:
                            score += 10
        # Evaluate score for the opponent to subtract potential threats
        for length in range(2, 5):
            for y in range(BOARD_SIZE):
                for x in range(BOARD_SIZE):
                    if self.board[y][x] == EMPTY and self.should_block_nalmok(x, y, opponent, length):
                        if length == 4:
                            score -= 900  # Less priority to block than to create own 4
                        elif length == 3:
                            score -= 90
                        elif length == 2:
                            score -= 9
        return score
    def computer_move(self):
        if self.game_over_flag:
            return
        # 우선순위 1: 컴퓨터 승리 수 (5개)
        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] == EMPTY:
                    self.place_stone(x, y, BLACK)
                    if self.check_win(x, y, BLACK):
                        self.update()
                        self.game_over("컴퓨터(흑) 승리!")
                        return
                    self.board[y][x] = EMPTY
                    self.move_history.pop()
        # 우선순위 2: 컴퓨터가 만들 수 있는 열린 4
        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] == EMPTY and self.should_block_nalmok(x, y, BLACK, 4):
                    self.place_stone(x, y, BLACK)
                    self.turn = WHITE
                    self.update()
                    return
        # 우선순위 3: 상대방의 즉각적인 승리 차단 (5개)
        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] == EMPTY:
                    self.place_stone(x, y, WHITE)
                    if self.check_win(x, y, WHITE):
                        self.board[y][x] = EMPTY
                        self.move_history.pop()
                        self.place_stone(x, y, BLACK)
                        self.turn = WHITE
                        self.update()
                        return
                    self.board[y][x] = EMPTY
                    self.move_history.pop()
        # 우선순위 4: 상대 열린 4 차단
        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] == EMPTY and self.should_block_nalmok(x, y, WHITE, 4):
                    self.place_stone(x, y, BLACK)
                    self.turn = WHITE
                    self.update()
                    return
        # 우선순위 5: 컴퓨터 공격 - 열린 3 만들기
        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] == EMPTY and self.should_block_nalmok(x, y, BLACK, 3):
                    self.place_stone(x, y, BLACK)
                    self.turn = WHITE
                    self.update()
                    return
        # 우선순위 6: 상대 열린 3 차단
        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] == EMPTY and self.should_block_nalmok(x, y, WHITE, 3):
                    self.place_stone(x, y, BLACK)
                    self.turn = WHITE
                    self.update()
                    return
        # 우선순위 8: 평가 기반 최적 수
        best_score = -float('inf')
        best_move = None
        center = BOARD_SIZE // 2
        search_range = 5  # 중앙 근처 탐색으로 연산량 감소
        for y in range(max(0, center - search_range), min(BOARD_SIZE, center + search_range)):
            for x in range(max(0, center - search_range), min(BOARD_SIZE, center + search_range)):
                if self.board[y][x] == EMPTY:
                    self.board[y][x] = BLACK
                    score = self.evaluate_board(BLACK) - self.evaluate_board(WHITE)
                    self.board[y][x] = EMPTY
                    if score > best_score:
                        best_score = score
                        best_move = (x, y)
        if best_move:
            x, y = best_move
            self.place_stone(x, y, BLACK)
            self.turn = WHITE
            self.update()
            return
        # 대체 수: 최근 돌 근처 나이트 이동 (더 안전한 수)
        possible_moves = []
        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] == BLACK:
                    for dx, dy in NALMOK_DIRECTIONS:
                        nx, ny = x + dx, y + dy
                        if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny][nx] == EMPTY:
                            possible_moves.append((nx, ny))
        if possible_moves:
            move = random.choice(possible_moves)
            self.place_stone(move[0], move[1], BLACK)
            self.turn = WHITE
            self.update()
            return
        # 정말 둘 곳이 없다면 랜덤으로
        empty_cells = []
        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] == EMPTY:
                    empty_cells.append((x, y))
        if empty_cells:
            move = random.choice(empty_cells)
            self.place_stone(move[0], move[1], BLACK)
            self.turn = WHITE
            self.update()
            return
    def game_over(self, message):
        self.game_over_flag = True
        QMessageBox.information(self, "게임 종료", message)
        self.close()
if __name__ == "__main__":
    app = QApplication(sys.argv)
    game = NalmokGame()
    sys.exit(app.exec_())

댓글 없음:

댓글 쓰기