#import pdb
import sys
import random
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox, QPushButton
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt, QTimer, QPoint
BOARD_SIZE = 19
CELL_SIZE = 40
STONE_RADIUS = CELL_SIZE>>1
EMPTY = 0
BLACK = 1
WHITE = 2
#보드 바탕색
BOARD_COLOR = QColor(222, 184, 135)
# 나이트 이동 방향
NALMOK_DIRECTIONS = [(1, 2),(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (2, -1), (2, 1)]
class NalmokGame(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("날목")
self.setFixedSize(BOARD_SIZE * CELL_SIZE, BOARD_SIZE * CELL_SIZE +50)
self.board = [[EMPTY for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)]
self.move_history = []
self.game_over_flag = False
self.winning_line = [] # 승리한 5개 돌의 좌표를 저장할 리스트
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)
# 컴퓨터가 흑으로 시작
center = BOARD_SIZE // 2
self.place_stone(center, center, BLACK)
self.turn = WHITE
self.show()
def paintEvent(self, event):
qp = QPainter()
qp.begin(self)
self.draw_board(qp)
self.draw_stones(qp)
self.draw_winning_line(qp) # 승리한 돌을 표시하는 함수 호출
qp.end()
def draw_winning_line(self, qp):
if self.winning_line:
qp.setPen(QPen(QColor(0, 255, 0), 3)) # 녹색으로 강조
for x, y in self.winning_line:
cx = STONE_RADIUS + x * CELL_SIZE
cy = STONE_RADIUS + y * CELL_SIZE
qp.drawEllipse(cx - STONE_RADIUS // 2, cy - STONE_RADIUS // 2, STONE_RADIUS, STONE_RADIUS)
def draw_board(self, qp):
qp.setBrush(BOARD_COLOR)
qp.drawRect(0, 0, self.width(), self.height())
qp.setPen(QPen(Qt.black, 1))
for i in range(BOARD_SIZE):
qp.drawLine(STONE_RADIUS, STONE_RADIUS + i * CELL_SIZE,
STONE_RADIUS + (BOARD_SIZE - 1) * CELL_SIZE, STONE_RADIUS + i * CELL_SIZE)
qp.drawLine(STONE_RADIUS + i * CELL_SIZE, STONE_RADIUS,
STONE_RADIUS + i * CELL_SIZE, STONE_RADIUS + (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 = STONE_RADIUS + x * CELL_SIZE
cy = STONE_RADIUS + y * CELL_SIZE
qp.setBrush(BLACK)
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:
qp.setBrush(QColor(0, 0, 0) if self.board[y][x] == BLACK else QColor(255, 255, 255))
qp.drawEllipse(
STONE_RADIUS + x * CELL_SIZE - STONE_RADIUS,
STONE_RADIUS + 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
# pdb.set_trace()
if last:
x, y = last
cx = STONE_RADIUS + x * CELL_SIZE
cy = STONE_RADIUS + y * CELL_SIZE
qp.setPen(QPen(BOARD_COLOR, 2))
qp.drawRect(cx-1, cy-1, 3, 3)
def mousePressEvent(self, event):
if self.game_over_flag or self.turn != WHITE:
return
x = int((event.x() - STONE_RADIUS + CELL_SIZE / 2) // CELL_SIZE)
y = int((event.y() - STONE_RADIUS + 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, True): # 승리 시 winning_line 업데이트
self.update()
self.game_over("사용자(백) 승리!")
return
self.turn = BLACK
self.update()
QTimer.singleShot(1, 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):
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.winning_line = [] # Undo 시 승리선 초기화
self.update()
def check_win(self, x, y, stone, update_winning_line=False):
winning_stones = []
for dx, dy in NALMOK_DIRECTIONS:
line = [(x, y)]
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:
line.append((nx, ny))
count += 1
else:
break
if count >= 5:
if update_winning_line:
self.winning_line = line
return True
return False
def should_block_nalmok(self, x, y, stone, length):
for dx, dy in NALMOK_DIRECTIONS:
count = 0
blocked = 0
for dir in [1, -1]:
temp_count = 0
temp_blocked = 0
for i in range(1, length):
nx = x + dx * i * dir
ny = y + dy * i * dir
if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE:
cell = self.board[ny][nx]
if cell == stone:
temp_count += 1
elif cell == EMPTY:
continue
else:
temp_blocked += 1
break
else:
temp_blocked += 1
break
if dir == 1:
count += temp_count
blocked += temp_blocked
else:
count += temp_count
blocked += temp_blocked
if count == length -1 and blocked < 2:
# Check if placing a stone at (x, y) would complete the sequence
placed = False
for dir in [1, -1]:
nx_check = x + dx * length * dir
ny_check = y + dy * length * dir
if 0 <= nx_check < BOARD_SIZE and 0 <= ny_check < BOARD_SIZE:
if self.board[ny_check][nx_check] == stone:
continue # Already blocked on this side
else:
continue # Already blocked on this side
temp_board = [row[:] for row in self.board]
temp_board[y][x] = stone
win = False
check_count = 1
for check_dir in [1, -1]:
for i in range(1, 5):
cx, cy = x + dx * i * check_dir, y + dy * i * check_dir
if 0 <= cx < BOARD_SIZE and 0 <= cy < BOARD_SIZE and temp_board[cy][cx] == stone:
check_count += 1
else:
break
if check_count >= 5:
win = True
if count == length - 1 and blocked < 2:
can_extend = False
for dir in [1, -1]:
nx_extend = x + dx * length * dir
ny_extend = y + dy * length * dir
if 0 <= nx_extend < BOARD_SIZE and 0 <= ny_extend < BOARD_SIZE and self.board[ny_extend][nx_extend] == EMPTY:
can_extend = True
break
if can_extend or blocked == 0:
return True
return False
def evaluate_board(self, stone):
score = 0
opponent = WHITE if stone == BLACK else BLACK
# Evaluate score for the given stone
for length in range(2, 5):
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, stone, length):
if length == 4:
score += 1000
elif length == 3:
score += 100
elif length == 2:
score += 10
# Evaluate score for the opponent to subtract potential threats
for length in range(2, 5):
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, opponent, length):
if length == 4:
score -= 900 # Less priority to block than to create own 4
elif length == 3:
score -= 90
elif length == 2:
score -= 9
return score
def computer_move(self):
if self.game_over_flag:
return
# 우선순위 1: 컴퓨터 승리 수 (5개)
for y in range(BOARD_SIZE):
for x in range(BOARD_SIZE):
if self.board[y][x] == EMPTY:
self.place_stone(x, y, BLACK)
if self.check_win(x, y, BLACK, True): # 승리 시 winning_line 업데이트
self.update()
self.game_over("컴퓨터(흑) 승리!")
return
self.board[y][x] = EMPTY
self.move_history.pop()
# 우선순위 2: 컴퓨터가 만들 수 있는 열린 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, BLACK, 4):
self.place_stone(x, y, BLACK)
self.turn = WHITE
self.update()
return
# 우선순위 3: 상대방의 즉각적인 승리 차단 (5개)
for y in range(BOARD_SIZE):
for x in range(BOARD_SIZE):
if self.board[y][x] == EMPTY:
self.place_stone(x, y, WHITE)
if self.check_win(x, y, WHITE, True): # 승리 시 winning_line 업데이트
self.board[y][x] = EMPTY
self.move_history.pop()
self.place_stone(x, y, BLACK)
self.turn = WHITE
self.update()
return
self.board[y][x] = EMPTY
self.move_history.pop()
# 우선순위 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 만들기
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
# 우선순위 6: 상대 열린 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
# 우선순위 8: 평가 기반 최적 수
best_score = -float('inf')
best_move = None
center = BOARD_SIZE // 2
search_range = 5 # 중앙 근처 탐색으로 연산량 감소
for y in range(max(0, center - search_range), min(BOARD_SIZE, center + search_range)):
for x in range(max(0, center - search_range), min(BOARD_SIZE, center + search_range)):
if self.board[y][x] == EMPTY:
self.board[y][x] = BLACK
score = self.evaluate_board(BLACK) - self.evaluate_board(WHITE)
self.board[y][x] = EMPTY
if score > best_score:
best_score = score
best_move = (x, y)
if best_move:
x, y = best_move
self.place_stone(x, y, BLACK)
self.turn = WHITE
self.update()
return
# 대체 수: 최근 돌 근처 나이트 이동 (더 안전한 수)
possible_moves = []
for y in range(BOARD_SIZE):
for x in range(BOARD_SIZE):
if self.board[y][x] == BLACK:
for dx, dy in NALMOK_DIRECTIONS:
nx, ny = x + dx, 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:
move = random.choice(possible_moves)
self.place_stone(move[0], move[1], BLACK)
self.turn = WHITE
self.update()
return
# 정말 둘 곳이 없다면 랜덤으로
empty_cells = []
for y in range(BOARD_SIZE):
for x in range(BOARD_SIZE):
if self.board[y][x] == EMPTY:
empty_cells.append((x, y))
if empty_cells:
move = random.choice(empty_cells)
self.place_stone(move[0], move[1], BLACK)
self.turn = WHITE
self.update()
return
def game_over(self, message):
self.game_over_flag = True
self.update() # 마지막 돌을 포함한 승리선을 표시하기 위해 update 호출
QMessageBox.information(self, "게임 종료", message)
self.close()
if __name__ == "__main__":
app = QApplication(sys.argv)
game = NalmokGame()
sys.exit(app.exec_())
댓글 없음:
댓글 쓰기