2026-01-27

NalMok 2026

게임 이름:날목 (python3 으로 개발)

import sys
import math # 중앙 거리를 계산하기 위해 추가
from PyQt6.QtWidgets import QApplication, QMainWindow, QMessageBox
from PyQt6.QtGui import QPainter, QPen, QColor, QBrush
from PyQt6.QtCore import Qt, QPoint

class FixedNalGomoku(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("세벌의 날목 (Fixed AI)")
        self.setFixedSize(640, 640)
        self.grid_size = 30
        self.margin = 50
        self.board = [[0] * 19 for _ in range(19)]
        self.current_player = 1  # 흑(컴퓨터) 선공
        self.directions = [(2, 1), (1, 2), (-1, 2), (-2, 1)]
        self.win_stones = []
        
        # 첫 수는 무조건 중앙(천원)
        self.board[9][9] = 1
        self.current_player = 2

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        painter.fillRect(self.rect(), QColor("#E6C38A"))

        # 바둑판 선 및 화점
        painter.setPen(QPen(Qt.GlobalColor.black, 1))
        for i in range(19):
            painter.drawLine(self.margin, self.margin + i * self.grid_size, self.margin + 18 * self.grid_size, self.margin + i * self.grid_size)
            painter.drawLine(self.margin + i * self.grid_size, self.margin, self.margin + i * self.grid_size, self.margin + 18 * self.grid_size)
        
        painter.setBrush(QBrush(Qt.GlobalColor.black))
        for r in [3, 9, 15]:
            for c in [3, 9, 15]:
                painter.drawEllipse(QPoint(self.margin + c * self.grid_size, self.margin + r * self.grid_size), 3, 3)

        # 돌 그리기
        for y in range(19):
            for x in range(19):
                if self.board[y][x] != 0:
                    center = QPoint(self.margin + x * self.grid_size, self.margin + y * self.grid_size)
                    color = Qt.GlobalColor.black if self.board[y][x] == 1 else Qt.GlobalColor.white
                    painter.setBrush(QBrush(color))
                    painter.setPen(QPen(QColor("#CCCCCC") if self.board[y][x] == 2 else Qt.GlobalColor.black, 1))
                    painter.drawEllipse(center, 13, 13)

                    if (y, x) in self.win_stones:
                        painter.setBrush(QBrush(QColor("lime")))
                        painter.setPen(Qt.PenStyle.NoPen)
                        painter.drawEllipse(center, 4, 4)

    def mousePressEvent(self, event):
        if self.current_player != 2 or self.win_stones: return
        x = round((event.position().x() - self.margin) / self.grid_size)
        y = round((event.position().y() - self.margin) / self.grid_size)

        if 0 <= x < 19 and 0 <= y < 19 and self.board[y][x] == 0:
            self.board[y][x] = 2
            win, stones = self.check_win(y, x, 2)
            if win:
                self.win_stones = stones
                self.update()
                self.end_game("세벌 님 승리!")
            else:
                self.current_player = 1
                self.ai_move()
            self.update()

    def ai_move(self):
        best_score = -1
        best_moves = [] # 동점일 경우를 위해 리스트로 관리

        for y in range(19):
            for x in range(19):
                if self.board[y][x] == 0:
                    score = self.evaluate_move(y, x)
                    if score > best_score:
                        best_score = score
                        best_moves = [(y, x)]
                    elif score == best_score:
                        best_moves.append((y, x))

        if best_moves:
            # 동점일 경우 중앙에서 가장 가까운 곳 선택
            best_move = min(best_moves, key=lambda pos: math.sqrt((pos[0]-9)**2 + (pos[1]-9)**2))
            y, x = best_move
            self.board[y][x] = 1
            win, stones = self.check_win(y, x, 1)
            if win:
                self.win_stones = stones
                self.update()
                self.end_game("컴퓨터 승리!")
            self.current_player = 2
        self.update()

    def evaluate_move(self, y, x):
        score = 0
        for dy, dx in self.directions:
            # 공격 가중치와 방어 가중치를 전략적으로 배분
            score += self.get_line_score(y, x, 1, dy, dx) * 1.5 # 공격 중시
            score += self.get_line_score(y, x, 2, dy, dx) * 1.2 # 방어 필수
        
        # 중앙 선호도 점수 (0~10점 사이의 아주 작은 보너스)
        dist_from_center = math.sqrt((y-9)**2 + (x-9)**2)
        score += (15 - dist_from_center) 
        
        return score

    def get_line_score(self, y, x, player, dy, dx):
        count = 1
        open_ends = 0 # 양끝이 열려있는지 확인 (3-3 등 파악용)
        
        for direction in [1, -1]:
            ty, tx = y + dy*direction, x + dx*direction
            while 0 <= ty < 19 and 0 <= tx < 19 and self.board[ty][tx] == player:
                count += 1
                ty, tx = ty + dy*direction, tx + dx*direction
            if 0 <= ty < 19 and 0 <= tx < 19 and self.board[ty][tx] == 0:
                open_ends += 1
        
        # 가중치 대폭 상향
        if count >= 5: return 100000
        if count == 4: return 10000 if open_ends > 0 else 5000
        if count == 3: return 2000 if open_ends == 2 else 500
        if count == 2: return 100
        return 0

    def check_win(self, y, x, player):
        for dy, dx in self.directions:
            stones = [(y, x)]
            for direction in [1, -1]:
                ty, tx = y + dy*direction, x + dx*direction
                while 0 <= ty < 19 and 0 <= tx < 19 and self.board[ty][tx] == player:
                    stones.append((ty, tx))
                    ty, tx = ty + dy*direction, tx + dx*direction
            if len(stones) >= 5:
                return True, stones[:5]
        return False, []

    def end_game(self, message):
        QMessageBox.information(self, "게임 종료", message)
        self.board = [[0] * 19 for _ in range(19)]
        self.win_stones = []
        self.board[9][9] = 1 # 리셋 후에도 흑(AI)이 중앙 선공
        self.current_player = 2

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = FixedNalGomoku()
    window.show()
    sys.exit(app.exec())

댓글 없음:

댓글 쓰기