2025-05-03

Ai NalMok 2

컴퓨터가 시작하면 그럭저럭 함. 사람이 시작하면 진행 안 되는 버그 있음.

import sys
import random
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox, QPushButton, QDialog, QVBoxLayout, QLabel
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt, QTimer

BOARD_SIZE = 19
CELL_SIZE = 30
STONE_RADIUS = 12

EMPTY = 0
BLACK = 1
WHITE = 2

# 나이트 이동 방향
NALMOK_DIRECTIONS = [(2, 1), (1, 2), (-2, 1), (-1, 2), (2, -1), (1, -2), (-2, -1), (-1, -2)]

class FirstTurnDialog(QDialog):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("누가 먼저 시작할까요?")
        self.choice = None

        layout = QVBoxLayout()
        layout.addWidget(QLabel("누가 먼저 시작할까요?"))

        btn_human = QPushButton("사람")
        btn_computer = QPushButton("컴퓨터")
        layout.addWidget(btn_human)
        layout.addWidget(btn_computer)

        btn_human.clicked.connect(self.choose_human)
        btn_computer.clicked.connect(self.choose_computer)

        self.setLayout(layout)

    def choose_human(self):
        self.choice = "human_first"
        self.accept()

    def choose_computer(self):
        self.choice = "computer_first"
        self.accept()

class NalmokGame(QWidget):
    def __init__(self, first_turn_mode):
        super().__init__()
        self.setWindowTitle("날목")
        self.setFixedSize(BOARD_SIZE * CELL_SIZE, BOARD_SIZE * CELL_SIZE)

        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)

        if first_turn_mode == "computer_first":
            center = BOARD_SIZE // 2
            self.place_stone(center, center, BLACK)
            self.turn = WHITE
        elif first_turn_mode == "human_first":
            self.turn = BLACK
        else:
            self.turn = BLACK

        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:
                    if self.board[y][x] == BLACK:
                        qp.setBrush(QColor(0, 0, 0))
                    else:
                        qp.setBrush(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(500, 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_ends = 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_ends += 1
                        break
                else:
                    blocked_ends += 1
                    break
            if count == length:
                return blocked_ends == 0  # true only if open-ended
        return False

    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 and self.should_block_nalmok(x, y, BLACK, 4):
                    self.place_stone(x, y, BLACK)
                    if self.check_win(x, y, BLACK):
                        self.update()
                        self.game_over("컴퓨터(흑) 승리!")
                        return
                    self.turn = WHITE
                    self.update()
                    return

        # 2. 컴퓨터 공격 - 열린 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

        # 3. 상대 열린 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

        # 4. 상대 열린 3 차단 (막힌 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

        # 5. 중앙 혹은 임의 착수
        base_x, base_y = self.last_black_move if self.last_black_move else (BOARD_SIZE // 2, BOARD_SIZE // 2)
        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)
                if self.check_win(nx, ny, BLACK):
                    self.update()
                    self.game_over("컴퓨터(흑) 승리!")
                    return
                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)
    dialog = FirstTurnDialog()
    if dialog.exec_() == QDialog.Accepted:
        game = NalmokGame(dialog.choice)
        sys.exit(app.exec_())

댓글 없음:

댓글 쓰기