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
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.green, 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):
if self.move_history:
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:
# 가상으로 흰 돌을 놓고 승리 여부 확인
self.board[y][x] = WHITE
if self.check_win(x, y, WHITE):
# 막아야 하는 자리
self.board[y][x] = BLACK
self.turn = WHITE
self.update()
return
# 원래 상태로 되돌림
self.board[y][x] = EMPTY
# 2. 컴퓨터 승리 수
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
# 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, BLACK, 3):
self.place_stone(x, y, BLACK)
self.turn = WHITE
self.update()
return
# 4. 상대 열린 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
# 5. 상대 열린 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
# 6. 중앙 혹은 임의 착수
base_x, base_y = self.last_black_move if self.last_black_move else (BOARD_SIZE // 2, BOARD_SIZE // 2)
possible_moves = []
for dx, dy in NALMOK_DIRECTIONS + [(0, 0)]: # 마지막 수 주변 또는 중앙, (0, 0) 추가로 현재 위치도 고려
nx = base_x + dx
ny = base_y + dy
if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[ny][nx] == EMPTY:
possible_moves.append((nx, ny))
if possible_moves:
nx, ny = random.choice(possible_moves)
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
elif not self.game_over_flag:
# 둘 곳이 없으면 무승부 처리
self.game_over("무승부!")
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_())
댓글 없음:
댓글 쓰기