2025-05-05

Ai 16

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 = 30

STONE_RADIUS = 12


EMPTY = 0

BLACK = 1

WHITE = 2


# 나이트 이동 방향

NALMOK_DIRECTIONS = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)]


class DraggableButton(QPushButton):

    def __init__(self, title, parent):

        super().__init__(title, parent)

        self.setMouseTracking(True)

        self.dragging = False

        self.offset = QPoint()


    def mousePressEvent(self, event):

        if event.button() == Qt.LeftButton:

            self.dragging = True

            self.offset = event.pos()

        super().mousePressEvent(event)


    def mouseMoveEvent(self, event):

        if self.dragging:

            new_pos = self.mapToParent(event.pos() - self.offset)

            self.move(new_pos)

        super().mouseMoveEvent(event)


    def mouseReleaseEvent(self, event):

        if event.button() == Qt.LeftButton:

            self.dragging = False

        super().mouseReleaseEvent(event)


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 = DraggableButton("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(QColor(222, 184, 135))

        qp.drawRect(0, 0, self.width(), self.height())


        qp.setPen(QPen(Qt.black, 1))

        for i in range(BOARD_SIZE):

            qp.drawLine(CELL_SIZE // 2, CELL_SIZE // 2 + i * CELL_SIZE,

                        CELL_SIZE // 2 + (BOARD_SIZE - 1) * CELL_SIZE, CELL_SIZE // 2 + i * CELL_SIZE)

            qp.drawLine(CELL_SIZE // 2 + i * CELL_SIZE, CELL_SIZE // 2,

                        CELL_SIZE // 2 + i * CELL_SIZE, CELL_SIZE // 2 + (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 = CELL_SIZE // 2 + x * CELL_SIZE

            cy = CELL_SIZE // 2 + y * CELL_SIZE

            qp.setBrush(QColor(0, 0, 0))

            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(

                        CELL_SIZE // 2 + x * CELL_SIZE - STONE_RADIUS,

                        CELL_SIZE // 2 + y * CELL_SIZE - STONE_RADIUS,

                        STONE_RADIUS * 2,

                        STONE_RADIUS * 2

                    )


        last = self.last_black_move if self.turn == WHITE else self.last_white_move

        if last:

            x, y = last

            cx = CELL_SIZE // 2 + x * CELL_SIZE

            cy = CELL_SIZE // 2 + y * CELL_SIZE

            qp.setPen(QPen(Qt.red, 2))

            qp.drawEllipse(cx - 5, cy - 5, 10, 10)


    def mousePressEvent(self, event):

        if self.game_over_flag or self.turn != WHITE:

            return


        x = int((event.x() - CELL_SIZE // 2 + CELL_SIZE / 2) // CELL_SIZE)

        y = int((event.y() - CELL_SIZE // 2 + 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 = 1

            blocked = 0

            for i in range(1, length):

                nx = x + dx * i

                ny = y + dy * i

                if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE:

                    cell = self.board[ny][nx]

                    if cell == stone:

                        count += 1

                    elif cell == EMPTY:

                        continue

                    else:

                        blocked += 1

                        break

                else:

                    blocked += 1

                    break

            if count == length and blocked == 0:

                return True

        return False


    def check_special_attack_pattern(self):

        base_patterns = [(-3, -6), (-1, -2), (1, 2), (3, 6)]

        all_patterns = []

        for dx, dy in base_patterns:

            all_patterns.append((dx, dy))

            all_patterns.append((-dx, dy))

            all_patterns.append((dx, -dy))

            all_patterns.append((-dx, -dy))

            all_patterns.append((dy, dx))

            all_patterns.append((-dy, dx))

            all_patterns.append((dy, -dx))

            all_patterns.append((-dy, -dx))

        all_patterns = list(set(all_patterns))


        for y in range(BOARD_SIZE):

            for x in range(BOARD_SIZE):

                if self.board[y][x] != EMPTY:

                    continue

                for pattern in [base_patterns, [(dy, dx) for dx, dy in base_patterns]]:

                    found = True

                    for dx, dy in pattern:

                        nx, ny = x + dx, y + dy

                        if not (0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny][nx] == WHITE):

                            found = False

                            break

                    if found:

                        return x, y

        return None


    def evaluate_board(self, stone):

        score = 0

        for y in range(BOARD_SIZE):

            for x in range(BOARD_SIZE):

                if self.board[y][x] == stone:

                    for dx, dy in NALMOK_DIRECTIONS:

                        count = 1

                        blocked = 0

                        for i in range(1, 5):

                            nx, ny = x + dx * i, y + dy * i

                            if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE:

                                if self.board[ny][nx] == stone:

                                    count += 1

                                elif self.board[ny][nx] != EMPTY:

                                    blocked += 1

                                    break

                            else:

                                blocked += 1

                                break

                        if blocked == 0:  # 열린 패턴

                            if count == 4:

                                score += 100

                            elif count == 3:

                                score += 50

                            elif count == 2:

                                score += 10

        return score


    def computer_move(self):

        if self.game_over_flag:

            return


        # 우선순위 1: 컴퓨터 승리 수

        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: 상대 승리 차단

        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

###

        # 우선순위 3: 특별 공격 패턴 차단

        result = self.check_special_attack_pattern()

        if result:

            x, y = result

            self.place_stone(x, y, BLACK)

            self.turn = WHITE

            self.update()

            return

                

###

                

        # 우선순위 7: 평가 기반 최적 수

        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


        # 대체 수: 최근 돌 근처 나이트 이동

        base_x, base_y = self.last_black_move if self.last_black_move else (center, center)

        for dx, dy in NALMOK_DIRECTIONS:

            nx = base_x + dx

            ny = base_y + dy

            if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny][nx] == EMPTY:

                self.place_stone(nx, ny, 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_())


댓글 없음:

댓글 쓰기