好友
阅读权限10
听众
最后登录1970-1-1
|
* 说明:
是用 AI 开发的,主体迭代了四五次,但修 bug 花费不少时间(非报错型的,如游戏机制理解错误)
网上随便找的素材,处理部分音频 也花了点时间,大概合起来两天时间
还是非常原始,之前没有用 pygame 写过游戏,这次主要是 想借助 AI 挑战下(望各位游戏高手海涵)
- 涉及的 AI 模型:
主要是 gemini-2.5-pro , 辅助是 gemini-2.5-flash, qwen-32B( qwen-3 今天刚发布,没来得及用)
* 游戏截图:
---分割线---
* 文件说明:
需要下载下面的素材(放在压缩包中),
- images 是棋子的图片 (有一张棋盘图(默认不用),去掉注释,设置 board.jpg 可用)
- music bgm 加上 两个小音效
文件夹截图
另外 images 中 还有个小工具(LLM 顺手写的),可以用来生成自定义字体的棋子图(windows 可用)
源码不放,截图如下:
棋子生成
---分割线---
下面是源代码:
[Python] 纯文本查看 复制代码
import pygame
import sys
import random
import os
# --- Pygame Setup ---
pygame.init() # Initialize Pygame early
pygame.mixer.init() # Initialize the mixer for sound
pygame.font.init() # Initialize font system
# --- Constants ---
ROWS, COLS = 4, 8
SQUARE_SIZE = 65
BOARD_WIDTH = COLS * SQUARE_SIZE
BOARD_HEIGHT = ROWS * SQUARE_SIZE
INFO_HEIGHT = 50
# Increased height significantly for captured pieces (2 rows per color comfortably)
CAPTURED_AREA_HEIGHT = SQUARE_SIZE * 2 + 40 # Adjusted height (e.g., 2 rows + padding)
CAPTURED_PIECE_SIZE = SQUARE_SIZE * 0.5 # Smaller size for captured pieces
BUTTON_AREA_HEIGHT = 50
PADDING = 10
# Adjusted Screen Height
SCREEN_WIDTH = BOARD_WIDTH
SCREEN_HEIGHT = INFO_HEIGHT + BOARD_HEIGHT + CAPTURED_AREA_HEIGHT + BUTTON_AREA_HEIGHT + PADDING * 4
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (180, 180, 180)
DARK_GRAY = (100, 100, 100)
RED = (200, 0, 0)
BLUE = (0, 0, 200)
HIGHLIGHT_COLOR = (100, 255, 100, 150)
POSSIBLE_MOVE_COLOR = (255, 255, 0, 100)
# Piece Info (棋子信息)
PIECE_LEVELS = {
'general': 7, 'advisor': 6, 'elephant': 5,
'chariot': 4, 'horse': 3, 'cannon': 2, 'soldier': 1
}
# Assets 资产
IMAGE_FOLDER = './images/'
SOUND_FOLDER = './music/'
DEFAULT_BACK_IMAGE_FILENAME = None
CUSTOM_BACK_IMAGE_FILENAME = "piece_back_custom.png"
BOARD_IMAGE_FILENAME = "board.jpg" #棋盘可换成自定义背景
# --- Fonts ---
try:
INFO_FONT = pygame.font.SysFont('simhei', 30)
BUTTON_FONT = pygame.font.SysFont('simhei', 20)
CAPTURED_FONT = pygame.font.SysFont('simhei', 16)
except pygame.error:
print("Warning: 'simhei' font not found. Using default font.")
INFO_FONT = pygame.font.Font(None, 40)
BUTTON_FONT = pygame.font.Font(None, 25)
CAPTURED_FONT = pygame.font.Font(None, 20)
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("暗棋 (Anqi / Dark Chess)") #游戏标题
# --- Load Images ---
piece_images = {}
try:
for color in ['red', 'black']:
piece_images[color] = {}
for piece_type in PIECE_LEVELS.keys():
filename = f"{color}_{piece_type}.png"
path = os.path.join(IMAGE_FOLDER, filename)
img = pygame.image.load(path).convert_alpha()
piece_images[color][piece_type] = pygame.transform.scale(img, (SQUARE_SIZE, SQUARE_SIZE))
except pygame.error as e:
print(f"Error loading piece image: {path}. {e}")
print(f"Please ensure all piece images are in the '{IMAGE_FOLDER}' folder.")
sys.exit()
except FileNotFoundError:
print(f"Error: Image file not found at {path}.")
print(f"Please ensure all piece images are in the '{IMAGE_FOLDER}' folder.")
sys.exit()
# Load piece back image
piece_back_image = None
back_image_path_custom = os.path.join(IMAGE_FOLDER, CUSTOM_BACK_IMAGE_FILENAME) if CUSTOM_BACK_IMAGE_FILENAME else None
back_image_path_default = os.path.join(IMAGE_FOLDER, DEFAULT_BACK_IMAGE_FILENAME) if DEFAULT_BACK_IMAGE_FILENAME else None
if back_image_path_custom and os.path.exists(back_image_path_custom):
try:
img = pygame.image.load(back_image_path_custom).convert_alpha()
piece_back_image = pygame.transform.scale(img, (SQUARE_SIZE, SQUARE_SIZE))
print(f"Loaded custom back image: {CUSTOM_BACK_IMAGE_FILENAME}")
except pygame.error as e:
print(f"Warning: Error loading custom back image: {back_image_path_custom}. {e}")
piece_back_image = None
elif back_image_path_default and os.path.exists(back_image_path_default):
try:
img = pygame.image.load(back_image_path_default).convert_alpha()
piece_back_image = pygame.transform.scale(img, (SQUARE_SIZE, SQUARE_SIZE))
print(f"Loaded default back image: {DEFAULT_BACK_IMAGE_FILENAME}")
except pygame.error as e:
print(f"Warning: Error loading default back image: {back_image_path_default}. {e}")
piece_back_image = None
if piece_back_image is None:
print("Using default blue color for piece back.")
piece_back_image = pygame.Surface((SQUARE_SIZE, SQUARE_SIZE))
piece_back_image.fill(BLUE)
pygame.draw.rect(piece_back_image, WHITE, piece_back_image.get_rect(), 3)
# Load board image
board_image = None
board_image_path = os.path.join(IMAGE_FOLDER, BOARD_IMAGE_FILENAME)
if os.path.exists(board_image_path):
try:
img = pygame.image.load(board_image_path).convert()
board_image = pygame.transform.scale(img, (BOARD_WIDTH, BOARD_HEIGHT))
print(f"Loaded board image: {BOARD_IMAGE_FILENAME}")
except pygame.error as e:
print(f"Error loading board image: {board_image_path}. {e}")
board_image = None
else:
print(f"Warning: Board image '{BOARD_IMAGE_FILENAME}' not found in '{IMAGE_FOLDER}'. Will draw colored squares.")
# --- Load Sounds ---
try:
click_sound = pygame.mixer.Sound(os.path.join(SOUND_FOLDER, "choose.mp3")) #选择 或 翻起
move_sound = pygame.mixer.Sound(os.path.join(SOUND_FOLDER, "move_short_1.mp3")) #吃子
# Load BGM
bgm_path = os.path.join(SOUND_FOLDER, "bgm.mp3")
pygame.mixer.music.load(bgm_path)
pygame.mixer.music.set_volume(0.3)
pygame.mixer.music.play(-1)
print("Loaded sound effects and BGM.")
except pygame.error as e:
print(f"Warning: Could not load sound file(s). {e}")
print("Make sure 'click.wav', 'move.wav', and 'bgm.mp3' are in the correct folder.")
click_sound = None
move_sound = None
except FileNotFoundError as e:
print(f"Warning: Sound file not found. {e}")
print("Make sure 'click.wav', 'move.wav', and 'bgm.mp3' are in the correct folder.")
click_sound = None
move_sound = None
# Game States
STATE_PLAYING = 2
STATE_GAME_OVER = 3
class Piece:
def __init__(self, color, piece_type, row, col):
self.color = color
self.piece_type = piece_type
self.level = PIECE_LEVELS[piece_type]
self.image = piece_images[color][piece_type]
self.back_image = piece_back_image
self.is_face_up = False
self.row = row
self.col = col
self.rect = pygame.Rect(col * SQUARE_SIZE, INFO_HEIGHT + PADDING + row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
def draw(self, surface):
if self.is_face_up:
surface.blit(self.image, self.rect.topleft)
else:
surface.blit(self.back_image, self.rect.topleft)
def move(self, row, col):
self.row = row
self.col = col
self.rect.topleft = (col * SQUARE_SIZE, INFO_HEIGHT + PADDING + row * SQUARE_SIZE)
def __repr__(self):
return f"{self.color} {self.piece_type} ({self.row},{self.col}) {'Up' if self.is_face_up else 'Down'}"
class Button:
def __init__(self, x, y, width, height, text, action):
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.action = action
self.color = GRAY
self.hover_color = DARK_GRAY
self.is_hovered = False
def draw(self, surface):
color = self.hover_color if self.is_hovered else self.color
pygame.draw.rect(surface, color, self.rect)
pygame.draw.rect(surface, BLACK, self.rect, 2)
text_surf = BUTTON_FONT.render(self.text, True, BLACK)
text_rect = text_surf.get_rect(center=self.rect.center)
surface.blit(text_surf, text_rect)
def handle_event(self, event):
if event.type == pygame.MOUSEMOTION:
self.is_hovered = self.rect.collidepoint(event.pos)
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if self.is_hovered:
self.play_sound(click_sound)
return self.action
return None
def play_sound(self, sound):
if sound:
sound.play()
class Game:
def __init__(self):
self.board = [[None for _ in range(COLS)] for _ in range(ROWS)]
self.all_pieces = [] # Keep track of all pieces currently on the board
self.captured_pieces = {'red': [], 'black': []}
self.turn = None
self.player_color = {'player1': None, 'ai': None}
self.selected_piece = None
self.possible_moves = []
self.game_state = STATE_PLAYING
self.winner = None
self.game_over_reason = "" # Store why the game ended
self.vs_ai = False
self.ai_thinking = False
self.ai_delay = 500 # ms
self.buttons = []
self.setup_buttons()
self.setup_board()
def setup_buttons(self):
self.buttons = []
button_y = INFO_HEIGHT + PADDING + BOARD_HEIGHT + PADDING + CAPTURED_AREA_HEIGHT + PADDING
button_width = 120
button_height = BUTTON_AREA_HEIGHT - PADDING
spacing = 20
restart_button = Button(SCREEN_WIDTH / 2 - button_width - spacing / 2, button_y, button_width, button_height, "重新开始", "restart")
ai_button_text = "电脑对战 (AI)" if not self.vs_ai else "双人对战 (2P)"
ai_button = Button(SCREEN_WIDTH / 2 + spacing / 2, button_y, button_width + 20, button_height, ai_button_text, "toggle_ai")
self.buttons.append(restart_button)
self.buttons.append(ai_button)
def setup_board(self):
self.board = [[None for _ in range(COLS)] for _ in range(ROWS)]
self.all_pieces = []
self.captured_pieces = {'red': [], 'black': []}
self.turn = None
self.player_color = {'player1': None, 'ai': None}
self.selected_piece = None
self.possible_moves = []
self.game_state = STATE_PLAYING
self.winner = None
self.game_over_reason = ""
self.ai_thinking = False
pieces_to_place = []
piece_counts = {'general': 1, 'advisor': 2, 'elephant': 2, 'horse': 2, 'chariot': 2, 'cannon': 2, 'soldier': 5}
for color in ['red', 'black']:
for piece_type, count in piece_counts.items():
for _ in range(count):
pieces_to_place.append((color, piece_type))
random.shuffle(pieces_to_place)
idx = 0
for r in range(ROWS):
for c in range(COLS):
if idx < len(pieces_to_place):
color, piece_type = pieces_to_place[idx]
piece = Piece(color, piece_type, r, c)
self.board[r][c] = piece
self.all_pieces.append(piece)
idx += 1
else:
self.board[r][c] = None
def draw_board(self, surface):
board_y = INFO_HEIGHT + PADDING
if board_image:
surface.blit(board_image, (0, board_y))
else:
for r in range(ROWS):
for c in range(COLS):
color = (230, 210, 170) if (r + c) % 2 == 0 else (180, 150, 100)
pygame.draw.rect(surface, color, (c * SQUARE_SIZE, board_y + r * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))
pygame.draw.rect(surface, DARK_GRAY, (c * SQUARE_SIZE, board_y + r * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE), 1)
def draw_pieces(self, surface):
for piece in self.all_pieces:
piece.draw(surface)
def draw_highlight(self, surface):
if self.selected_piece:
s = pygame.Surface(self.selected_piece.rect.size, pygame.SRCALPHA)
s.fill(HIGHLIGHT_COLOR)
surface.blit(s, self.selected_piece.rect.topleft)
board_y = INFO_HEIGHT + PADDING
for r, c in self.possible_moves:
move_rect = pygame.Rect(c * SQUARE_SIZE, board_y + r * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
s = pygame.Surface(move_rect.size, pygame.SRCALPHA)
s.fill(POSSIBLE_MOVE_COLOR)
surface.blit(s, move_rect.topleft)
def draw_info(self, surface):
info_y = PADDING // 2
if self.game_state == STATE_GAME_OVER:
win_text = f"{'红方' if self.winner == 'red' else '黑方'} 胜利!"
color = RED if self.winner == 'red' else BLACK
text = f"游戏结束 (Game Over) - {win_text}"
elif self.turn:
text = f"{'红方' if self.turn == 'red' else '黑方'} 回合"
color = RED if self.turn == 'red' else BLACK
if self.vs_ai and self.turn == self.player_color['ai'] and self.ai_thinking:
text += " [AI思考中...]"
else:
text = "请翻开棋子开始"
color = BLACK
info_surf = INFO_FONT.render(text, True, color)
info_rect = info_surf.get_rect(center=(SCREEN_WIDTH // 2, info_y + INFO_HEIGHT // 2))
surface.blit(info_surf, info_rect)
def draw_captured(self, surface):
captured_y_start = INFO_HEIGHT + PADDING + BOARD_HEIGHT + PADDING
captured_box_height = CAPTURED_AREA_HEIGHT
capture_area_rect = pygame.Rect(0, captured_y_start, SCREEN_WIDTH, captured_box_height)
pygame.draw.rect(surface, GRAY, capture_area_rect)
pygame.draw.rect(surface, BLACK, capture_area_rect, 1)
label_offset_x = PADDING
piece_start_x = label_offset_x + CAPTURED_FONT.render("红方吃:", True, BLACK).get_width() + 15
max_pieces_per_row = int((SCREEN_WIDTH - piece_start_x - PADDING) // (CAPTURED_PIECE_SIZE + PADDING * .5))
if max_pieces_per_row <= 0: max_pieces_per_row = 1
row_height = CAPTURED_PIECE_SIZE + PADDING * .5
y_base = captured_y_start + PADDING
label_y_black = y_base + row_height - CAPTURED_PIECE_SIZE // 2 - CAPTURED_FONT.get_height() // 2
red_label = CAPTURED_FONT.render("黑方吃:", True, BLACK)
surface.blit(red_label, (label_offset_x, label_y_black))
y_offset_black_eats_1 = y_base
y_offset_black_eats_2 = y_base + row_height
for i, piece in enumerate(self.captured_pieces['red']):
img = pygame.transform.scale(piece.image, (int(CAPTURED_PIECE_SIZE), int(CAPTURED_PIECE_SIZE)))
row_num = i // max_pieces_per_row
col_num = i % max_pieces_per_row
x = piece_start_x + col_num * (CAPTURED_PIECE_SIZE + PADDING *0.5)
y = y_offset_black_eats_1 if row_num == 0 else y_offset_black_eats_2
if y + CAPTURED_PIECE_SIZE <= captured_y_start + captured_box_height - PADDING // 2:
surface.blit(img, (x, y))
red_capture_base_y = y_base + row_height * 2 + PADDING
if red_capture_base_y + row_height <= captured_y_start + captured_box_height:
label_y_red = red_capture_base_y + row_height - CAPTURED_PIECE_SIZE // 2 - CAPTURED_FONT.get_height() // 2
black_label = CAPTURED_FONT.render("红方吃:", True, RED)
surface.blit(black_label, (label_offset_x, label_y_red))
y_offset_red_eats_1 = red_capture_base_y
y_offset_red_eats_2 = red_capture_base_y + row_height
for i, piece in enumerate(self.captured_pieces['black']):
img = pygame.transform.scale(piece.image, (int(CAPTURED_PIECE_SIZE), int(CAPTURED_PIECE_SIZE)))
row_num = i // max_pieces_per_row
col_num = i % max_pieces_per_row
x = piece_start_x + col_num * (CAPTURED_PIECE_SIZE + PADDING * .5)
y = y_offset_red_eats_1 if row_num == 0 else y_offset_red_eats_2
if y + CAPTURED_PIECE_SIZE <= captured_y_start + captured_box_height - PADDING // 2:
surface.blit(img, (x, y))
else:
warn_font = pygame.font.SysFont('simhei', 14)
warn_surf = warn_font.render("空间不足显示红方吃子", True, RED)
surface.blit(warn_surf, (label_offset_x, captured_y_start + captured_box_height - warn_font.get_height() - 5))
def draw_buttons(self, surface):
for button in self.buttons:
button.draw(surface)
def draw(self, surface):
surface.fill(WHITE)
self.draw_board(surface)
self.draw_pieces(surface)
self.draw_highlight(surface)
self.draw_info(surface)
self.draw_captured(surface)
self.draw_buttons(surface)
def get_piece(self, row, col):
if 0 <= row < ROWS and 0 <= col < COLS:
return self.board[row][col]
return None
def play_sound(self, sound):
if sound:
sound.play()
def can_capture(self, attacker: Piece, defender: Piece) -> bool:
if not attacker or not defender or not attacker.is_face_up or not defender.is_face_up:
return False
if attacker.color == defender.color:
return False
if attacker.piece_type == 'cannon':
return False
if attacker.piece_type == 'soldier' and defender.piece_type == 'general':
return True
if attacker.piece_type == 'general' and defender.piece_type == 'soldier':
return False
return attacker.level >= defender.level
def get_valid_moves(self, piece: Piece) -> list:
"""Gets valid move/capture coordinates for a face-up piece."""
if not piece or not piece.is_face_up:
return []
moves = []
r, c = piece.row, piece.col
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
if piece.piece_type != 'cannon':
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < ROWS and 0 <= nc < COLS:
target_piece = self.board[nr][nc]
if target_piece is None:
moves.append((nr, nc))
elif target_piece.is_face_up and target_piece.color != piece.color:
if self.can_capture(piece, target_piece):
moves.append((nr, nc))
else:
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < ROWS and 0 <= nc < COLS and self.board[nr][nc] is None:
moves.append((nr, nc))
for dr, dc in directions:
jump_piece_found = False
for i in range(1, max(ROWS, COLS)):
nr, nc = r + dr * i, c + dc * i
if not (0 <= nr < ROWS and 0 <= nc < COLS):
break
target_piece = self.board[nr][nc]
if not jump_piece_found:
if target_piece is not None:
jump_piece_found = True
else:
if target_piece is not None:
if target_piece.is_face_up and target_piece.color != piece.color:
moves.append((nr, nc))
break
return moves
def has_any_valid_action(self, color: str) -> bool:
"""Checks if the player of the given color has any possible action (move, capture, or flip)."""
for piece in self.all_pieces:
if piece.color == color:
if not piece.is_face_up:
return True
elif self.get_valid_moves(piece):
return True
return False
def handle_click(self, pos):
for button in self.buttons:
event = pygame.event.Event(pygame.MOUSEBUTTONDOWN, {'button': 1, 'pos': pos})
action = button.handle_event(event)
if action:
if action == "restart":
print("Restarting game...")
self.setup_board()
self.setup_buttons()
return
elif action == "toggle_ai":
self.vs_ai = not self.vs_ai
print(f"AI Mode: {'On' if self.vs_ai else 'Off'}")
self.setup_board()
self.setup_buttons()
return
if self.game_state == STATE_GAME_OVER:
return
if self.vs_ai and self.turn == self.player_color['ai'] and self.ai_thinking:
print("AI is thinking, please wait.")
return
board_y_start = INFO_HEIGHT + PADDING
if not (0 <= pos[0] < BOARD_WIDTH and board_y_start <= pos[1] < board_y_start + BOARD_HEIGHT):
self.selected_piece = None
self.possible_moves = []
return
col = pos[0] // SQUARE_SIZE
row = (pos[1] - board_y_start) // SQUARE_SIZE
if not (0 <= row < ROWS and 0 <= col < COLS):
self.selected_piece = None
self.possible_moves = []
return
clicked_piece = self.get_piece(row, col)
if self.selected_piece:
if (row, col) in self.possible_moves:
target_piece = clicked_piece
is_capture = False
if target_piece:
self.capture_piece(self.selected_piece, target_piece)
is_capture = True
self.move_piece(self.selected_piece, row, col)
self.play_sound(move_sound)
self.selected_piece = None
self.possible_moves = []
self.switch_turn()
if self.check_game_over():
print(f"Game Over detected after move by {self.winner}.")
return
else:
self.selected_piece = None
self.possible_moves = []
self.handle_click(pos)
return
else:
if clicked_piece:
if not clicked_piece.is_face_up:
if self.turn is None:
clicked_piece.is_face_up = True
first_color = clicked_piece.color
self.player_color['player1'] = first_color
opponent_color = 'black' if first_color == 'red' else 'red'
if self.vs_ai:
self.player_color['ai'] = opponent_color
self.turn = opponent_color
print(f"First flip: Player 1 is {self.player_color['player1']}. Next turn: {self.turn}")
self.play_sound(click_sound)
if self.check_game_over():
print("Game Over detected immediately after first flip.")
elif clicked_piece.color == self.turn:
print("Standard rules usually allow flipping any piece on your turn.")
print("Allowing flip even if piece color doesn't match turn.")
clicked_piece.is_face_up = True
print(f"{self.turn} flipped {clicked_piece.color} {clicked_piece.piece_type}")
self.play_sound(click_sound)
self.switch_turn()
if self.check_game_over():
print(f"Game Over detected after flip by {self.winner}.")
elif self.turn is not None:
clicked_piece.is_face_up = True
print(f"{self.turn} flipped {clicked_piece.color} {clicked_piece.piece_type}")
self.play_sound(click_sound)
self.switch_turn()
if self.check_game_over():
print(f"Game Over detected after flip by {self.winner}.")
else:
print("Error: Cannot flip yet.")
else:
if self.turn is None:
print("Cannot select face-up piece before first flip.")
return
if clicked_piece.color == self.turn:
self.selected_piece = clicked_piece
self.possible_moves = self.get_valid_moves(clicked_piece)
self.play_sound(click_sound)
else:
pass
else:
pass
def switch_turn(self):
if self.turn:
self.turn = 'black' if self.turn == 'red' else 'red'
def move_piece(self, piece, new_row, new_col):
old_row, old_col = piece.row, piece.col
self.board[old_row][old_col] = None
self.board[new_row][new_col] = piece
piece.move(new_row, new_col)
def capture_piece(self, attacker, defender):
print(f"{attacker.color} {attacker.piece_type} captures {defender.color} {defender.piece_type}")
defender_color = defender.color
self.captured_pieces[defender_color].append(defender)
self.captured_pieces[defender_color].sort(key=lambda p: p.level, reverse=True)
if defender in self.all_pieces:
self.all_pieces.remove(defender)
else:
print(f"Warning: Captured piece {defender} not found in all_pieces list.")
def check_game_over(self) -> bool:
"""Checks if the game has ended based on global state. Sets winner and state."""
if self.game_state == STATE_GAME_OVER:
return True
red_pieces = [p for p in self.all_pieces if p.color == 'red']
black_pieces = [p for p in self.all_pieces if p.color == 'black']
if not red_pieces:
self.winner = 'black'
self.game_state = STATE_GAME_OVER
self.game_over_reason = "红方无子 (Red has no pieces)"
print(f"Game Over: {self.game_over_reason}. Winner: Black")
return True
if not black_pieces:
self.winner = 'red'
self.game_state = STATE_GAME_OVER
self.game_over_reason = "黑方无子 (Black has no pieces)"
print(f"Game Over: {self.game_over_reason}. Winner: Red")
return True
if self.turn:
current_player_can_move = self.has_any_valid_action(self.turn)
if not current_player_can_move:
self.winner = 'black' if self.turn == 'red' else 'red'
self.game_state = STATE_GAME_OVER
opponent_color_cn = "黑方" if self.winner == 'black' else "红方"
current_color_cn = "红方" if self.turn == 'red' else "黑方"
self.game_over_reason = f"{current_color_cn})"
print(f"Game Over: {self.game_over_reason}. Winner: {self.winner}")
return True
return False
def ai_move(self):
if self.game_state != STATE_PLAYING or self.turn != self.player_color['ai'] or self.ai_thinking:
return
self.ai_thinking = True
print(f"AI ({self.player_color['ai']}) is thinking...")
self.draw(screen) # Update screen to show "thinking" message
pygame.display.flip()
pygame.time.wait(self.ai_delay // 2) # Short wait before calculation
# --- AI Logic V2: Evaluate all actions together ---
ai_color = self.player_color['ai']
possible_actions = [] # List of tuples: (score, action_type, piece, target_coords/piece_to_flip)
# 1. Evaluate all possible actions
for piece in self.all_pieces:
# A. Evaluate Flips
if not piece.is_face_up:
# Score for flipping: relatively low, maybe higher if few pieces left?
flip_score = 5
# Add potential bonus if few pieces are face up? (根据场上翻开的棋子数量加分)
face_up_count = sum(1 for p in self.all_pieces if p.is_face_up)
if face_up_count < 8: flip_score += 3 # Encourage flipping early?
#5 分基础,8分是增益情况
possible_actions.append((flip_score, 'flip', piece, piece)) # Target is the piece itself
# B. Evaluate Moves/Captures (only for AI's own face-up pieces)评估移动/吃子 - 只针对AI自己翻开的棋子
elif piece.color == ai_color and piece.is_face_up: # 如果是AI自己的棋子且已翻开
valid_moves = self.get_valid_moves(piece) # 获取这个棋子所有合法的移动位置
for r, c in valid_moves:
target_piece = self.board[r][c]
if target_piece is None:
# Score for moving to empty square: very low base score
move_score = 1 # 移动到空位的基础分数很低
# Add small bonus for moving higher value pieces? (optional)
# move_score += piece.level * 0.1
possible_actions.append((move_score, 'move', piece, (r, c)))
else: # 如果目标位置有棋子
# Score for capturing: high base score + target value (吃子得分:高基础分 + 目标棋子的价值)
# Ensure it's a valid capture target (opponent piece) (确保是合法的吃子目标,也就是对方的棋子)
if target_piece.color != ai_color and target_piece.is_face_up:
capture_score = 50 + target_piece.level * 10 # Base score + value # 吃子得分计算:基础分 + 目标棋子等级*10
# Bonus for capturing General
if target_piece.piece_type == 'general':
capture_score += 100
# Bonus for using lower-level piece for capture (用低等级棋子吃高等级棋子有额外加分)
if piece.level <= 2: # Soldier, Cannon
capture_score += 5
possible_actions.append((capture_score, 'capture', piece, (r, c)))
# else: Should not happen if get_valid_moves is correct
# --- Decision Making ---
chosen_action = None
if not possible_actions:
# This should ideally be caught by check_game_over before calling ai_move
print("AI Error: No possible actions found!")
self.ai_thinking = False
# If somehow stuck, maybe force switch turn? Or rely on check_game_over
# self.switch_turn() # Risky, might cause infinite loop if check_game_over is flawed
return
# Sort actions by score (highest first)
possible_actions.sort(key=lambda x: x[0], reverse=True)
# Select the best action
best_score = possible_actions[0][0]
top_actions = [a for a in possible_actions if a[0] == best_score] #筛选出分数并列的情况
# top_actions = possible_actions[0]
chosen_action = random.choice(top_actions) # Choose randomly among the best actions
score, action_type, piece, target = chosen_action
print(f"AI chooses: {action_type} with {piece} -> {target} (Score: {score})")
# --- Execute Action ---
pygame.time.wait(self.ai_delay // 2) # Remainder of delay
if action_type == 'capture':
target_r, target_c = target
defender = self.board[target_r][target_c]
if defender:
self.capture_piece(piece, defender)
self.move_piece(piece, target_r, target_c)
self.play_sound(move_sound)
else:
print("AI Error: Capture target square is empty?")
self.move_piece(piece, target_r, target_c) # Move anyway
self.play_sound(move_sound)
elif action_type == 'move':
target_r, target_c = target
self.move_piece(piece, target_r, target_c)
self.play_sound(move_sound)
elif action_type == 'flip':
# Target is the piece to flip
piece_to_flip = target
if piece_to_flip and not piece_to_flip.is_face_up:
piece_to_flip.is_face_up = True
print(f"AI flipped {piece_to_flip.color} {piece_to_flip.piece_type}")
self.play_sound(click_sound) # Use click sound for flip
else:
print(f"AI Error: Tried to flip an invalid piece? {piece_to_flip}")
# Action finished
self.ai_thinking = False
self.selected_piece = None # Clear human selection state
self.possible_moves = []
if self.check_game_over():
print(f"Game Over detected after AI move by {self.winner}.")
self.switch_turn() #change self.turn
def main():
running = True
clock = pygame.time.Clock()
game = Game()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if game.game_state != STATE_GAME_OVER:
if not (game.vs_ai and game.turn == game.player_color['ai'] and game.ai_thinking):
game.handle_click(event.pos)
else:
print("Ignoring click during AI thinking.")
elif game.game_state == STATE_GAME_OVER:
game.handle_click(event.pos)
if event.type == pygame.MOUSEMOTION:
for button in game.buttons:
button.handle_event(event)
if game.game_state == STATE_PLAYING and game.vs_ai and game.turn is not None and game.turn == game.player_color['ai'] and not game.ai_thinking:
if not game.check_game_over():
game.ai_move()
else:
print("Skipping AI move because game is already over.")
game.draw(screen)
pygame.display.flip()
clock.tick(30)
pygame.mixer.music.stop()
pygame.quit()
sys.exit()
if __name__ == '__main__':
if not os.path.isdir(IMAGE_FOLDER):
print(f"Error: Image folder '{IMAGE_FOLDER}' not found.")
print("Please create the folder and place piece and board images inside.")
sys.exit()
if not os.path.isdir(SOUND_FOLDER):
print(f"Warning: Sound folder '{SOUND_FOLDER}' not found. Sounds may not play.")
main()
---分割---
最后:
- 玩法就不作介绍,相信大家都玩过,主要是适合双人对战
- 里面的 AI 电脑对战,是非常“愚蠢型”的,不过可以让玩家熟悉玩法 (其实 也稍微探索下 有搜索深度的,不过遇到了重复行动的问题,非认真设计不可)
代码层面来说,pygame 是第一次写,写过一次后,才发现这个库比较底层,很多东西需要自己写框架,比游戏引擎难很多(还好 暗棋本身不是很复杂)
用AI 开发游戏的体会:
1 要自己先写好游戏规则(在迭代过程中 就遇到自己规则没加上,规则之间有矛盾的情况,很浪费时间),
2 对比一般软件,游戏里面 状态管理是非常复杂,往往嵌套在一起,容易会晕,建议遇到这种情况,不要蛮横(脑子会过热),可设计个 prompt,让LLM梳理,或者画状态树
游戏素材:
发布--.7z
(902.18 KB, 下载次数: 56)
|
免费评分
-
查看全部评分
|