2025-04-30

NalMokE

전에 만든 날목 편집해서 개선. 아래는 소스. 계속 버전 업 중.
import sys
import random
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
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 NalmokGame(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("날목 (Nalmok)")
        self.setFixedSize(BOARD_SIZE * CELL_SIZE, BOARD_SIZE * CELL_SIZE)

        self.board = [[EMPTY for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)]
        self.turn = BLACK  # 컴퓨터가 흑으로 먼저

        center = BOARD_SIZE // 2
        self.board[center][center] = BLACK  # 첫 수 중앙에 둠
        self.last_move = (center, center)
        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:
                    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
                    )

        # 마지막 수 표시
        if self.last_move:
            x, y = self.last_move
            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.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.board[y][x] = WHITE
            self.last_move = (x, y)
            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 find_user_threat(self, count_required):
        best_priority = None
        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] == WHITE:
                    for dx, dy in NALMOK_DIRECTIONS:
                        stones = [(x, y)]
                        for i in range(1, count_required):
                            nx, ny = x + dx * i, y + dy * i
                            if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny][nx] == WHITE:
                                stones.append((nx, ny))
                            else:
                                break
                        if len(stones) == count_required:
                            bx, by = x - dx, y - dy
                            ex, ey = x + dx * count_required, y + dy * count_required

                            b_open = 0 <= bx < BOARD_SIZE and 0 <= by < BOARD_SIZE and self.board[by][bx] == EMPTY
                            e_open = 0 <= ex < BOARD_SIZE and 0 <= ey < BOARD_SIZE and self.board[ey][ex] == EMPTY

                            if b_open and e_open:
                                return (bx, by)  # 열린 삼 우선
                            elif b_open and best_priority is None:
                                best_priority = (bx, by)
                            elif e_open and best_priority is None:
                                best_priority = (ex, ey)
        return best_priority

    def computer_move(self):
        for threat_level in [4, 3, 2]:
            threat_move = self.find_user_threat(threat_level)
            if threat_move:
                x, y = threat_move
                self.board[y][x] = BLACK
                self.last_move = (x, y)
                if self.check_win(x, y, BLACK):
                    self.update()
                    self.game_over("컴퓨터(흑) 승리!")
                    return
                self.turn = WHITE
                self.update()
                return

        # 공격 우선 로직으로 나머지 수를 두기
        best_score = float('-inf')
        best_move = None

        for y in range(BOARD_SIZE):
            for x in range(BOARD_SIZE):
                if self.board[y][x] == EMPTY:
                    self.board[y][x] = BLACK
                    if self.check_win(x, y, BLACK):
                        self.board[y][x] = EMPTY
                        best_move = (x, y)
                        break
                    score = self.evaluate_board(BLACK) - 2 * self.evaluate_board(WHITE)
                    self.board[y][x] = EMPTY
                    if score > best_score:
                        best_score = score
                        best_move = (x, y)
            if best_move and best_score >= 1000:
                break

        if best_move:
            x, y = best_move
            self.board[y][x] = BLACK
            self.last_move = (x, y)
            if self.check_win(x, y, BLACK):
                self.update()
                self.game_over("컴퓨터(흑) 승리!")
                return
            self.turn = WHITE
            self.update()

    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
                        for i in range(1, 5):
                            nx, ny = x + dx * i, y + dy * i
                            if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny][nx] == stone:
                                count += 1
                            else:
                                break
                        if count == 5:
                            score += 100000
                        elif count == 4:
                            score += 10000
                        elif count == 3:
                            score += 500
                        elif count == 2:
                            score += 100
        return score

    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 game_over(self, message):
        QMessageBox.information(self, "게임 종료", message)
        self.close()

if __name__ == "__main__":    
    app = QApplication(sys.argv)
    game = NalmokGame()
    sys.exit(app.exec_())

댓글 없음:

댓글 쓰기