吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2472|回复: 11
收起左侧

[Python 原创] [游戏] - pygame 开发 小时候经常玩的 象棋的暗棋游戏

[复制链接]
pyjiujiu 发表于 2025-4-29 12:15


* 说明:
是用 AI 开发的,主体迭代了四五次,但修 bug 花费不少时间(非报错型的,如游戏机制理解错误)
网上随便找的素材,处理部分音频 也花了点时间,大概合起来两天时间

还是非常原始,之前没有用 pygame 写过游戏,这次主要是 想借助 AI 挑战下(望各位游戏高手海涵)

- 涉及的 AI 模型:
主要是 gemini-2.5-pro , 辅助是 gemini-2.5-flash, qwen-32B( qwen-3 今天刚发布,没来得及用)

* 游戏截图:

截图2.JPG

截图1.JPG

---分割线---

* 文件说明:

需要下载下面的素材(放在压缩包中),
- 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)




免费评分

参与人数 4威望 +1 吾爱币 +21 热心值 +4 收起 理由
苏紫方璇 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
mockmoon + 1 我很赞同!
Zercher + 1 鼓励转贴优秀软件安全工具和文档!
cheng050231 + 1 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| pyjiujiu 发表于 2025-4-29 23:09
本帖最后由 pyjiujiu 于 2025-4-29 23:12 编辑
yxf515321 发表于 2025-4-29 22:26
感觉象棋的规则写起来好麻烦呐

说的没错,这个规则确实不容易写,细节比较多,描述反复

不过 ,现在用 AI 大模型,也是很方便,直接让它起草一个,然后自己修改 即可
(因为 AI 对象棋非常之了解,不需要什么 prompt技巧,简单问就行 )

如果你要设计的游戏比较偏僻,或者完全自己新发明的,那就要想点办法,给大模型 喂点材料(图片,视频等都可)

我LLM 直接生成的,类似下面这样:
**暗棋(半盘象棋)游戏规则**

暗棋是一种两人对弈的象棋变体,使用一副象棋的棋子,在半个象棋盘上进行。

**1. 棋盘:**

* 使用象棋盘的一半,即由 **4行 x 8列** 组成的矩形区域。
* 棋盘上没有楚河汉界。

**2. 棋子:**

* 使用一副完整的象棋棋子(共32个),分为红黑两方。
* 每方棋子数量和种类与象棋相同:将/帅1、士/仕2、象/相2、车2、马2、炮2、兵/卒5。

**3. 开局:**

* 游戏开始时,所有棋子面朝下随机摆放在棋盘的 4x8 = 32个格子上。
* 双方轮流翻开一个棋子,确定自己的颜色(通常先翻出红色为红方,先翻出黑色为黑方)。
* 翻开的棋子立即展示其兵种。

jiedao747 发表于 2025-5-9 22:28
以前很喜欢玩。入坑作品,我记得。那个里面有更多象棋走法在里面的。比如士可以斜着走。马可以日子走法。当时第一次遇到的时候很惊喜。后来才发现这规则在暗棋里面相当另类。
luodeman 发表于 2025-4-29 12:58
wanibo 发表于 2025-4-29 13:09
围观学习,感谢楼主分享。
cagu 发表于 2025-4-29 15:02
一直玩军旗的暗棋,看看这个怎么样,多谢分享了
jackal 发表于 2025-4-29 20:43

玩玩试试,感谢分享
yxf515321 发表于 2025-4-29 22:26
感觉象棋的规则写起来好麻烦呐
shenwq 发表于 2025-4-30 09:25
玩一下试试,非常感谢分享,楼主辛苦了。
wangMaple 发表于 2025-4-30 10:15
感谢楼主分享,辛苦了,正好在学python,学习一下
minminawl 发表于 2025-4-30 14:44
先试试,赞一个
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-17 09:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表