71 체스 게임 (Chess)
고전적인 보드 게임인 체스를 처음부터 끝까지 코딩해 봅시다. 보드를 구성하고, 각 기물(폰, 나이트, 비숍, 룩, 퀸, 킹)의 이미지를 사용하여 화면을 꾸밉니다. 모든 기물의 이동 규칙을 정확하게 코딩하고, 잘못된 수(Invalid Moves)를 둘 수 없도록 철저히 검증해야 합니다.
체스는 규칙이 상당히 많고 복잡하기 때문에 세심한 설계가 필요합니다. 캐슬링(Castling), 앙파상(En passant), 프로모션(Promotion)과 같은 특수 규칙들까지 완벽하게 구현하는 것이 목표입니다.
71.1 주요 개발 포인트
- 8x8 보드 표현: 2차원 리스트 또는 배열을 사용하여 체스판의 상태를 관리합니다.
- 기물 이동 로직: 각 기물의 독특한 이동 범위를 계산하고 경로가 막혔는지 확인합니다.
- 특수 규칙 구현: 캐슬링, 앙파상, 폰의 승진(Promotion) 조건을 체크합니다.
- 체크 및 체크메이트 판정: 현재 킹이 공격받고 있는지, 더 이상 피할 곳이 없는지 판단합니다.
- 턴 관리: 백과 흑이 번갈아 가며 수를 두는 시스템을 구축합니다.
71.2 Python 구현 예시 (보드 초기화 및 출력)
class ChessGame:
"""
체스 게임의 보드 상태와 기본 규칙을 관리합니다.
"""
def __init__(self):
# 대문자는 백(White), 소문자는 흑(Black)
self.board = [
['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'],
['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'],
['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']
]
self.turn = 'White'
self.castling_rights = {'W': {'K': True, 'Q': True}, 'B': {'k': True, 'q': True}}
self.en_passant_target = None # Position (r, c)
def display(self):
"""
체스판을 보기 좋게 출력합니다.
"""
print(f"\n--- {self.turn} 턴 ---")
print(" a b c d e f g h")
print(" ----------------")
for i, row in enumerate(self.board):
print(f"{8-i}|{' '.join(row)}|{8-i}")
print(" ----------------")
print(" a b c d e f g h")
def to_coords(self, pos_str):
col = ord(pos_str[0].lower()) - ord('a')
row = 8 - int(pos_str[1])
return row, col
def is_valid_move(self, start_pos, end_pos, check_check=True):
r1, c1 = self.to_coords(start_pos)
r2, c2 = self.to_coords(end_pos)
return self._is_valid_move_coords(r1, c1, r2, c2, check_check)
def _is_valid_move_coords(self, r1, c1, r2, c2, check_check=True):
if not (0 <= r1 < 8 and 0 <= c1 < 8 and 0 <= r2 < 8 and 0 <= c2 < 8):
return False
piece = self.board[r1][c1]
target = self.board[r2][c2]
if piece == ' ' or (self.turn == 'White' and not piece.isupper()) or \
(self.turn == 'Black' and not piece.islower()):
return False
if target != ' ' and piece.isupper() == target.isupper():
return False
if not self._validate_piece_move(piece, r1, c1, r2, c2):
return False
if check_check:
original_board = [row[:] for row in self.board]
original_ep = self.en_passant_target
original_castling = {k: v.copy() for k, v in self.castling_rights.items()}
self._execute_move(r1, c1, r2, c2, piece)
king_pos = self._find_king(self.turn)
in_check = self._is_under_attack(king_pos[0], king_pos[1], 'Black' if self.turn == 'White' else 'White')
self.board, self.en_passant_target, self.castling_rights = original_board, original_ep, original_castling
if in_check: return False
return True
def _validate_piece_move(self, piece, r1, c1, r2, c2, include_castling=True):
dr, dc = r2 - r1, c2 - c1
p = piece.lower()
if p == 'p':
dir = -1 if piece.isupper() else 1
if dc == 0:
if dr == dir and self.board[r2][c2] == ' ': return True
if dr == 2 * dir and r1 == (6 if piece.isupper() else 1) and \
self.board[r2][c2] == ' ' and self.board[r1 + dir][c1] == ' ': return True
elif abs(dc) == 1 and dr == dir:
if self.board[r2][c2] != ' ' or (r2, c2) == self.en_passant_target: return True
elif p == 'r':
if (dr == 0 or dc == 0) and self._is_path_clear(r1, c1, r2, c2): return True
elif p == 'n':
if (abs(dr), abs(dc)) in [(1, 2), (2, 1)]: return True
elif p == 'b':
if abs(dr) == abs(dc) and self._is_path_clear(r1, c1, r2, c2): return True
elif p == 'q':
if (dr == 0 or dc == 0 or abs(dr) == abs(dc)) and self._is_path_clear(r1, c1, r2, c2): return True
elif p == 'k':
if abs(dr) <= 1 and abs(dc) <= 1: return True
if include_castling and dr == 0 and abs(dc) == 2: return self._can_castle(piece, r1, c1, r2, c2)
return False
def _is_path_clear(self, r1, c1, r2, c2):
dr = 0 if r1 == r2 else (1 if r2 > r1 else -1)
dc = 0 if c1 == c2 else (1 if c2 > c1 else -1)
r, c = r1 + dr, c1 + dc
while (r, c) != (r2, c2):
if self.board[r][c] != ' ': return False
r, c = r + dr, c + dc
return True
def _can_castle(self, piece, r1, c1, r2, c2):
color, side = ('W', 'K' if c2 > c1 else 'Q') if piece.isupper() else ('B', 'k' if c2 > c1 else 'q')
if not self.castling_rights[color][side]: return False
if self._is_under_attack(r1, c1, 'Black' if piece.isupper() else 'White'): return False
step = 1 if c2 > c1 else -1
rook_col = 7 if c2 > c1 else 0
for c in range(min(c1, rook_col) + 1, max(c1, rook_col)):
if self.board[r1][c] != ' ': return False
return not self._is_under_attack(r1, c1 + step, 'Black' if piece.isupper() else 'White')
def _find_king(self, turn):
target = 'K' if turn == 'White' else 'k'
for r in range(8):
for c in range(8):
if self.board[r][c] == target: return r, c
def _is_under_attack(self, r, c, attacker_color):
for r_idx in range(8):
for c_idx in range(8):
piece = self.board[r_idx][c_idx]
if piece != ' ' and ((attacker_color == 'White' and piece.isupper()) or (attacker_color == 'Black' and piece.islower())):
if piece.lower() == 'p':
dir = -1 if piece.isupper() else 1
if r == r_idx + dir and abs(c - c_idx) == 1: return True
elif self._validate_piece_move(piece, r_idx, c_idx, r, c, include_castling=False):
return True
return False
def is_checkmate(self, turn):
king_pos = self._find_king(turn)
if not self._is_under_attack(king_pos[0], king_pos[1], 'Black' if turn == 'White' else 'White'):
return False
return self._has_no_valid_moves(turn)
def is_stalemate(self, turn):
king_pos = self._find_king(turn)
if self._is_under_attack(king_pos[0], king_pos[1], 'Black' if turn == 'White' else 'White'):
return False
return self._has_no_valid_moves(turn)
def _has_no_valid_moves(self, turn):
for r1 in range(8):
for c1 in range(8):
piece = self.board[r1][c1]
if piece != ' ' and ((turn == 'White' and piece.isupper()) or (turn == 'Black' and piece.islower())):
for r2 in range(8):
for c2 in range(8):
if self._is_valid_move_coords(r1, c1, r2, c2, check_check=True):
return False
return True
def move_piece(self, start_pos, end_pos):
"""
기물을 이동시키고 턴을 넘깁니다. 규칙을 검증합니다.
"""
if not self.is_valid_move(start_pos, end_pos):
print(f"잘못된 이동: {start_pos} -> {end_pos}")
return False
print(f"기물 이동: {start_pos} -> {end_pos}")
r1, c1 = self.to_coords(start_pos)
r2, c2 = self.to_coords(end_pos)
self._execute_move(r1, c1, r2, c2, self.board[r1][c1])
self.turn = 'Black' if self.turn == 'White' else 'White'
king_pos = self._find_king(self.turn)
in_check = self._is_under_attack(king_pos[0], king_pos[1], 'Black' if self.turn == 'White' else 'White')
if self.is_checkmate(self.turn):
print(f"체크메이트! {'White' if self.turn == 'Black' else 'Black'} 승리.")
elif self.is_stalemate(self.turn):
print("스테일메이트! 무승부.")
elif in_check:
print(f"체크! {self.turn}의 킹이 공격받고 있습니다.")
return True
def _execute_move(self, r1, c1, r2, c2, piece):
target_piece = self.board[r2][c2]
if target_piece == 'R':
if r2 == 7 and c2 == 7: self.castling_rights['W']['K'] = False
elif r2 == 7 and c2 == 0: self.castling_rights['W']['Q'] = False
elif target_piece == 'r':
if r2 == 0 and c2 == 7: self.castling_rights['B']['k'] = False
elif r2 == 0 and c2 == 0: self.castling_rights['B']['q'] = False
if piece.lower() == 'p' and (r2, c2) == self.en_passant_target: self.board[r1][c2] = ' '
self.board[r2][c2], self.board[r1][c1] = piece, ' '
self.en_passant_target = ((r1 + r2) // 2, c1) if piece.lower() == 'p' and abs(r2 - r1) == 2 else None
if piece == 'P' and r2 == 0: self.board[r2][c2] = 'Q'
if piece == 'p' and r2 == 7: self.board[r2][c2] = 'q'
if piece.lower() == 'k' and abs(c2 - c1) == 2:
rook_col, target_col = (7, 5) if c2 > c1 else (0, 3)
self.board[r2][target_col], self.board[r2][rook_col] = self.board[r2][rook_col], ' '
if piece == 'K': self.castling_rights['W']['K'] = self.castling_rights['W']['Q'] = False
elif piece == 'k': self.castling_rights['B']['k'] = self.castling_rights['B']['q'] = False
elif piece == 'R':
if r1 == 7 and c1 == 7: self.castling_rights['W']['K'] = False
elif r1 == 7 and c1 == 0: self.castling_rights['W']['Q'] = False
elif piece == 'r':
if r1 == 0 and c1 == 7: self.castling_rights['B']['k'] = False
elif r1 == 0 and c1 == 0: self.castling_rights['B']['q'] = False
if __name__ == "__main__":
game = ChessGame()
game.display()
# 예시 이동
game.move_piece("e2", "e4")
game.display()