2025-04-30

NalMokG

#NalMok G
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_())

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

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

2025-04-25

AddPdf With Python and PyQt

간단한 프로그램. 연습 삼아 만들어 봄.

pdftk 프로그램 설치. 사실 pdftk 만으로도 pdf 파일 합치는 건 가능하다. 명령어 치는 게 귀찮을 뿐.

GUI 프로그램을 만들어 보자.

창 하나. 그 창 안에 버튼 하나. 버튼 누르면 합칠 pdf 파일들을 선택하게 한다. 선택한 pdf 파일들을 합쳐서 새 파일 하나 만들기.

말로는 간단한데 프로그램 만드는 것은 말처럼 쉽지는 않다.

인공지능의 도움을 받아 프로그램 완성. 처음 프로그램 하는데 그 과정에서 에러가 안 나면 오히려 이상한 일. 에러 해결 역시 인공지능의 도움을 받음. 아래는 그 소스 코드.

import sys

import subprocess

from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QFileDialog, QMessageBox, QVBoxLayout

class PDFMerger(QWidget):

    def __init__(self):

        super().__init__()

        self.init_ui()

    def init_ui(self):

        self.setWindowTitle('PDF 합치기 도구')

        self.setGeometry(100, 100, 300, 100)

        self.button = QPushButton('PDF 합치기', self)

        self.button.clicked.connect(self.merge_pdfs)

        layout = QVBoxLayout()

        layout.addWidget(self.button)

        self.setLayout(layout)

    def merge_pdfs(self):

        files, _ = QFileDialog.getOpenFileNames(

            self,

            "PDF 파일 선택",

            "",

            "PDF Files (*.pdf)"

        )

        if not files:

            return

        save_path, _ = QFileDialog.getSaveFileName(

            self,

            "저장할 파일 이름",

            "merged.pdf",

            "PDF Files (*.pdf)"

        )

        if not save_path:

            return

        try:

            # pdftk 명령어로 파일 합치기

            command = ['pdftk'] + files + ['cat', 'output', save_path]

            subprocess.run(command, check=True)

            QMessageBox.information(self, "성공", f"PDF가 성공적으로 저장되었습니다:\n{save_path}")

        except subprocess.CalledProcessError:

            QMessageBox.critical(self, "오류", "PDF를 합치는 중 오류가 발생했습니다.")

if __name__ == '__main__':

    app = QApplication(sys.argv)

    merger = PDFMerger()

    merger.show()

    sys.exit(app.exec_())

A poem made by AI

바람이 머물다 간 자리

가만히 눈을 감으면
내 마음 속에도 바람이 분다
세찬 날엔 흔들리고
고요한 날엔 울먹인다

그래도 괜찮아
바람은 머물지 않고
언제나 지나가는 거니까

한숨도, 눈물도,
결국엔 지나가고 나면
조금은 단단해진 나를 남기지

오늘은 그냥
그 바람 속에 잠시 기대어
쉬어가도 괜찮아

AI에게 시 하나 지어 달라고 하니 뚝딱. 참 좋은 세상.

2025-04-24

chatGPT and AI with Python

제대로 시작하는 챗GPT와 AI활용 with 파이썬 
책 제목은 이러하다. 
영어와 한글이 섞여 있다. 
프롬프트 엔지니어링, 음성인식, 이미지 생성, 챗봇 웹 서비스까지 요즘 얘기 자주 나오는 인공지능 관련 내용이 모두 들어 있다. 
에이먼 엘 암리 지음 
대니얼 WJ 옮김 
왜 옮긴이 이름이 영어? 
지은이, 옮긴이 소개를 보면 알 수 있다. 
졸업 후 해외 취업, 일본, 미국 IT 업계에서 근무, 통신사 엔지니어, 컴퓨터 학원 강사, 프리랜서 번역가, 작가 등으로 활동. 
지은이 이력 또한 다양하다. 
작가, 기업가, 트레이너, 소프트웨어 엔지니어 등. 복잡한 개념을 쉽게 이해할 수 있는 언어로 단순화.
챗GPT와 AI 활용 with 파이썬

책값: 32,000원.

자세한 내용을 보려면 아래 링크

2025-04-23

2025-04-21

nephron superbin

빈 캔을 넣으면 포인트 적립하고 어느 정도 모이면 현금처럼 쓸 수 있는 서비스가 있다.

자동판매기 비슷하게 생긴 기계.

수퍼빈 앱을 설치하고, 해당 기계에 빈 캔을 넣으면 갯수에 따라 포인트적립.

전에 없던 절차가 하나 더 생겼다.

기계에 휴대전화번호를 입력하면 그 전화에 4자리 숫자가 문자로 온다. 그 숫자를 기계에 입력해야 포인트 쌓인다. 전에는 없던 절차.

아마도 여러 사람이 한 사람 것을 적립해주는 것을 막으려는 장치인 듯.

각자 내 것은 내가 적립하라는 얘기. 다른 사람 시키지 말고.

2025-04-20

LGUplus Boot touch not work. Solved

LG U+ 전화기 껐다가 켜는데 암호 입력 화면에서 터치 인식 안 됨
전원버튼과 음량(하) 버튼을 동신에 누르고 7초이상 기다림.
다시 화면 터치해서 정상 부팅 성공.

2025-04-17

Find Fourier expansion of x² where x in (-2𝜋,2𝜋)

\[ f(x)=a_0 + \sum_{n=1}^{\infty} a_n \cos(\frac{n \pi x}{2 \pi}) + \sum_{n=1}^{\infty} b_n \sin(\frac{n \pi x}{2 \pi}) \]

\[ f(x)=a_0 + \sum_{n=1}^{\infty} a_n \cos(\frac{n x}{2}) \]

\[ a_0 = \frac{1}{4 \pi}\int_{-2\pi}^{2\pi} x^2 \, dx = \frac{2}{4 \pi}\int_{0}^{2\pi} x^2 \, dx = \frac{8\pi^{2}}{3} \]

\[ a_n = \frac{1}{L} \int_{-L}^{L} x^{2} \cos (\frac{n \pi x}{L}) dx = \frac{16}{n^{2}}(-1)^{n} \]

Find Fourier expansion of x² where x in (-2𝜋,2𝜋)

LaTeX Laplace transform

\[ \mathcal{L}\{f(t)\} = F(s) = \int_0^{\infty} e^{-st} f(t) \, dt \] For example, the Laplace transform of \( f(t) = e^{at} \) is: \[ \mathcal{L}\{e^{at}\} = \int_0^{\infty} e^{-st} e^{at} \, dt = \int_0^{\infty} e^{-(s-a)t} \, dt = \frac{1}{s-a}, \text{for } s > a \]

2025-04-16

Fourier series exta

Fourier seriese

다음 함수를 Fourier 급수로 전개하라

f(x) = x, -1 < x < 1

1단계

f(x)=x 는 기함수(odd fuction) 이므로 Fourier 급수는 sin 항만 포함

f(x) = Σ n=1 bn sin(n𝜋x)

2단계: bn 계산하기

bn = 1 -1 x sin(n𝜋x) dx

x sin(n𝜋x)는 기함수이므로

bn = 2 1 0 x sin(n𝜋x) dx

부분적분 이용

u = x 로 놓으면 du = dx

dv=sin(n𝜋x) dx, v= -(1/n𝜋) cos(n𝜋x)

bn =2(-x/(n𝜋) cos(n𝜋x) | 1 0 + 1/(n𝜋) 1 0 x cos(n𝜋x) dx )

-x/(n𝜋) cos(n𝜋x) | 1 0 = -1/(n𝜋) + 0 = -(-1)n/(n𝜋)

1/(n𝜋)  1 0 x cos(n𝜋x) dx = 1/(n𝜋) sin(n𝜋x) = 0

bn = 2(-1)n+1 n𝜋

f(x) = Σ n=1 2(-1)n+1 n𝜋 sin(n𝜋x)

LaTeX Test Sigma

신기하네요. blogspot에서 LaTeX 쓸 수 있다는 것을 이제서야 알았네요.

\( \sum_{n=0}^{1} f(n) \)

\[ \sum_{n=0}^{1} f(n) \]

\[ f(x) = \sum_{n=1}^{\infty} \frac{2(-1)^{n+1}}{n\pi} \sin(n\pi x) \]

MathML test Σ

Σ n = 0 1 f ( n )

2025-04-15

Fraction example

5252 357

fraction example

12343758

inverse Laplace transform

F(s) = 5s+6 s(s²+1) 일 때 L-1(F(s))를 구하라

1단계: 부분 분수

5s+6 s(s²+1) = A s + (Bs+C) (s²+1)

5s + 6 = A(s²+1) + (Bs+C)s = (A+B)s²+ Cs + A

A+B = 0, C = 5, A = 6

So, B = -6

5s+6 s(s²+1) = 6 s + (-6) s (s²+1) + 5 1 (s²+1)

2단계: 라플라스 변환 표 이용

  • L-1{ 1 s } = 1
  • L-1{ s s²+1 } = cos(t)
  • L-1{ 1 s²+1 } = sin(t)

를 이용하면

L-1{ 5s+6 s(s²+1) } = 6 - 6cos(t) + 5sin(t)

2025-04-14

Laplace Transform. L{e^(2t) sin(t)}

Laplace Transform

L{e2t sin(t)}

L{eat f(t)} = F(s-a) where F(s) = L{f(t)}

L{sin(t)} = 1 s² + 1

Since a = 2, s → s - 2

L{e2t sin(t)} = 1 (s-2)² + 1

2025-04-12

20250411Kyobo

2025.4.11.금 19:30 교보 
김호성, 내마음다친줄모르고어른이되었다
저자 강연

교보 직원 등장. 예쁘죠? 안 예쁘다고요? 그럼 제가 사진 잘 못찍어서 그런 거예요. 행사에 대해 설명하고 있어요.

김호성 저자 소개
뇌 비밀

뇌는 이렇게 생겼죠.

뇌 발달 순서
뇌 설명.

그리고, 여기에 적지 못한 많은 이야기들이 있었어요.
조금씩 조금씩 업데이트 예정.

2025-04-10

The root formula

ax²+ bx + c = 0 근의 공식

x = -b ±√b² - 4ac 2a

x = (-b ±√b²-4ac) / 2a

Laplace Transform with step function

L{f(t)} 를 구하라. 단 f(t) = 0 if t in (0,1), t if t>1

f(t) = t u(t-1)

f(t) = ((t-1)+1) u(t-1)

2nd Shifting Theorem

If f(t) = g(t-a) u(t-a)

then L{f(t)} = e-as L{g(t)}

f(t) = ((t-1)+1) u(t-1) = (t-1)u(t-1) + u(t-1)

L{u(t-1)} = e-s/s

L{(t-1)u(t-1)} = e-s L{t} = e-s(1/s²)

L{f(t)} = e-s/s + e-s/s²

How to write upper small number 2

How can I write upper small number 2?

I found two methods.

Normal 2 upper 2

Normal 2 upper ²

First upper 2 is written with html tag sup.

Second upper 2 is written with unicode U+00B2

2025-04-09

gimp 3 release and Manual

gimp 3 나왔는데 모르고 있었다.

https://www.gimp.org/news/2025/03/23/gimp-3-0-2-released/

설명서, 한국어 번역: https://docs.gimp.org/3.0/ko/

한국어 번역률 그다지 높지 않다. 번역에 참여하는 분도 많지 않고. 덕분에 번역자 명단에 내 이름과 메일 주소가 아직도 남아있다. 오래 전에 번역 참여하다가 손 놓은 지 오래됨.

요즘은 저 공식 번역이 큰 의미가 있나 모르겠다. 매뉴얼 안 보고 그냥 감으로 익혀서 쓰는 분이 많아서.

2025-04-07

y''+2y' = 2x+5-e^(-2x)

y'' + 2y' = 2x + 5 - e-2x

Step1:

Solve y'' + 2y' = 0

characteristic equation: r2 + 2r = 0

r(r+2) = 0

r = 0 or r = -2

General solution:

yh = c1 + c2 e-2x

Step2: Find particular solution

1) 2x + 5:

yp1 = Ax2 + Bx

y' = 2Ax + B

y'' = 2A

y'' + 2y = 2A + 2(2Ax+ B) = 4Ax + 2B + 2A

4Ax + 2B + 2A = 2x + 5

4A = 2

A = 1/2

2A + 2B = 5

1 + 2B = 5

B = 2

yp1 = x2/2 + 2x

2) -e-2x:

yp2 = Ae-2x

y' = -2Ae-2x

y'' = 4Ae-2x

y'' + 2y' = 4Ae-2x - 4Ae-2x = 0

yp2 = Axe-2x

y' = Ae-2x - 2Ax-2x

y'' = -2Ae-2x - 2Ae-2x + 4Axe-2x = (4Ax - 4A)e-2x

y'' + 2y' = (4Ax - 4A)e-2x + 2(Ae-2x - 2Axe-2x) = (-2A)e-2x

-2Ae-2x =  -e-2x

A = 1/2

yp2 = xe-2x/2

Step3: General solution

y(x) = yh + yp1 + yp2 = c1 + c2 e-2x + x2/2 + 2x + xe-2x/2

y(x) = yh + yp1+ yp2 = c1 + c2 e-2x + x2/2 + 2x + xe-2x/2

2025-04-03

y'' - 2y' + y = 3sin(2x)

y'' - 2y' + y = 3sin(2x)

Step1: 특성방정식

r- 2r + 1 = 0

(r - 1)= 0

r = 1 (중근)

일반해 yh = (c1 + c2x)ex

Step2: 미정계수법

yp = A cos(2x) + B sin(2x)

yp' = -2A sin(2x) + 2B cos(2x)

yp'' = -4A cos(2x) - 4B sin(2x)

원 식에 대입

y'' - 2y' +y = 3sin(2x)

(-4A cos(2x) - 4B sin(2x) ) - 2(-2A sin(2x) + 2B cos(2x)) +(A cos(2x) + B sin(2x)) = 3sin(2x)

(-3A - 4B) cos(2x) + (4A - 3B - 3) sin(2x) = 0

(-3A - 4B) = 0

(4A - 3B - 3) = 0

A = -4B/3

A(-4B/3) - 3B = 3

B = -9/25, A = (-4/3)(-9/25) = 12/25

y = (c1+c2x)ex + (12/25) cos(2x) - (9/25) sin(2x)

pdf compress

pdf 파일 압축하는 프로그램 여럿 있다.

그 중 하나가 Adobe 에 있다. 원래의 파일을 인터넷으로 전송하면 그 파일을 압축해서 내려받는 방식.

다른 곳도 여럿 있는데, 비교해보고 편한 것으로 골라 쓰면 될 듯.

2025-04-02

DE y'' - 4y' + 8y = 0

y'' - 4y' + 8y = 0

Step1:

r2 - 4r + 8 = 0

Step2:

r = -(-4)±√(-4)²- 4x1x8 2x1

r = -(-4) ± √-16 2x1

r = 2 ± 2i

Step 3:

y = eax(c1 cos(bx) + c2 sin(bx))

Substitute a = 2, b = 2

y = e2x(c1 cos(2x) + c2 sin(2x))

DE y''-5y-14=0

Differential Equation  y'' - 5y - 14 = 0 을 풀어라

Step 1: r- 5r - 14 = 0

Step 2: (r+2)(r-7) = 0. So, r = -2 or r = 7

Step 3: y = c1e-2x + c2e7x

convolution

Convolution 정의 주의

(f*g)(t) = f(𝜏) g(t-𝜏) dt

위의 정의에서 주의할 것이 있다.

컴퓨터에서 곱하기를 *로 표시하는데(영어 소문자 x와 구별하기 위함), 수학에서 convolution을 나타낼 때도 * 기호를 쓴다.

수학에서는 위와 같이 f(𝜏)와 g(t-𝜏) 사이에 * 표시를 안 넣으면 곱하기로 알고 있으면 된다.

컴퓨터 프로그램에서는 * 표시를 생략하면 안 된다.

수학에서 convolution 나타내는 기호 *와 컴퓨터 프로그램에서 쓰는 기호 *는 같은 기호를 쓰지만, 뜻이 다르므로 주의.