2025-04-29

NalMok

내가 생각한 오목 변형 게임. 규칙은 오목과 비슷. 다른 점은 하나. 날일자 모양 방향으로 다섯 개 놓으면 됨. 가로 세로 대각선이 아니라.

Python만 가지고는 그림이 안 나오니 PyQt 활용. 프로그램 개발을 chatGPT에게 시킴. 수준은 향상 필요하나 일단 동작은 함.  그 뜻은 5개 놓았는데, 이겼는지 졌는지도 모르지는 않는다는 뜻. 처음엔 엉뚱하게 두기에 chatGPT에게 규칙을 가르쳐 주는 과정이 쉽지만은 않았지만, 나름 재미있었음.

아래는 그걸 구현한 소스 코드. 수시로 업그레이드 중.

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


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 = WHITE  # Player is WHITE, computer is BLACK


        # Computer plays first in the center

        mid = BOARD_SIZE // 2

        self.board[mid][mid] = 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)

    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)


    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

            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 computer_move(self):

        best_move = self.find_best_move()

        if best_move is None:

            self.game_over("무승부!")

            return


        x, y = best_move

        self.board[y][x] = BLACK

        if self.check_win(x, y, BLACK):

            self.update()

            self.game_over("컴퓨터(흑) 승리!")

            return


        self.turn = WHITE

        self.update()


    def find_best_move(self):

        # 우선순위: 내 승리 > 상대 승리 방어 > 3개 막기 > 랜덤

        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

                        return (x, y)

                    self.board[y][x] = EMPTY


        for y in range(BOARD_SIZE):

            for x in range(BOARD_SIZE):

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

                    self.board[y][x] = WHITE

                    if self.check_win(x, y, WHITE):

                        self.board[y][x] = EMPTY

                        return (x, y)

                    self.board[y][x] = EMPTY


        for y in range(BOARD_SIZE):

            for x in range(BOARD_SIZE):

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

                    self.board[y][x] = WHITE

                    if self.count_consecutive(x, y, WHITE, 3):

                        self.board[y][x] = EMPTY

                        return (x, y)

                    self.board[y][x] = EMPTY


        empty = [(x, y) for y in range(BOARD_SIZE) for x in range(BOARD_SIZE) if self.board[y][x] == EMPTY]

        return random.choice(empty) if empty else None


    def count_consecutive(self, x, y, stone, count_required):

        directions = [(1, 2), (2, 1), (2, -1), (1, -2),

                      (-1, -2), (-2, -1), (-2, 1), (-1, 2)]

        for dx, dy in directions:

            cnt = 1

            for step in range(1, 5):

                nx = x + dx * step

                ny = y + dy * step

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

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

                        cnt += 1

                    else:

                        break

            if cnt >= count_required:

                return True

        return False


    def check_win(self, x, y, stone):

        directions = [(1, 2), (2, 1), (2, -1), (1, -2),

                      (-1, -2), (-2, -1), (-2, 1), (-1, 2)]

        for dx, dy in directions:

            count = 1

            for dir in [1, -1]:

                for i in range(1, 5):

                    nx = x + dx * i * dir

                    ny = y + dy * i * dir

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

                        if 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_())

댓글 없음:

댓글 쓰기