一、研究背景

中国象棋是起源于中国古代的传统棋类游戏,有着悠久的历史和深厚的文化底蕴,是中华民族智慧的结晶。随着计算机技术的发展,将传统象棋游戏数字化、电子化成为传承和推广这一文化遗产的重要方式。

在现代游戏开发领域,基于 Pygame 库开发桌面棋牌游戏是一种常见的技术路径。Pygame 作为 Python 语言的游戏开发库,提供了图形渲染、音效处理、事件响应等基础功能,非常适合开发中小型游戏。本项目正是基于这一背景,利用 Pygame 实现了一款功能完整的中国象棋游戏,让用户可以在计算机上体验传统象棋的乐趣。

二、研究目的

  1. 实现中国象棋的完整游戏逻辑,包括棋盘绘制、棋子移动规则、胜负判定等核心功能
  2. 开发友好的用户交互界面,支持鼠标操作和游戏状态显示
  3. 加入音效反馈机制,提升游戏的沉浸感和体验感
  4. 实现悔棋等辅助功能,增强游戏的可玩性
  5. 确保中文显示正常,符合中国象棋的文化特征

通过这些目标的实现,构建一个规则准确、操作便捷、体验良好的电子象棋游戏,为传统棋类游戏的数字化提供参考案例。

三、技术方案

  1. 开发环境:基于 Python 语言和 Pygame 库进行开发
  2. 图形渲染:使用 Pygame 的绘图功能实现棋盘、棋子和 UI 元素的可视化
  3. 游戏逻辑:通过二维数组表示棋盘状态,实现各类棋子的移动规则判断
  4. 交互处理:基于 Pygame 的事件系统处理鼠标点击等用户操作
  5. 音效系统:利用 Pygame 的混音器功能加载和播放游戏音效
  6. 状态管理:通过游戏状态变量控制游戏流程,包括进行中、游戏结束等状态

核心技术点包括:棋盘坐标系统设计、棋子移动规则算法实现、将军和胜负判定逻辑、游戏状态保存与恢复(悔棋功能)。

四、实现流程

4.1 初始化阶段

# 初始化pygame及混音器
pygame.init()
pygame.mixer.init()

# 初始化字体支持中文显示
pygame.font.init()
font_path = pygame.font.match_font('simsun') or pygame.font.match_font('microsoftyahei')

# 创建游戏窗口
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))

4.2 数据结构设计

  • 使用 9×10 的二维数组表示棋盘状态
  • 定义棋子类型常量(如 RED_GENERAL、BLACK_HORSE 等)
  • 使用字典映射棋子类型与显示名称

4.3 棋盘与棋子绘制

# 绘制棋盘
def draw_board():
    # 绘制横线、竖线、楚河汉界、九宫格斜线等

# 绘制棋子
def draw_pieces(board):
    # 遍历棋盘数组,绘制每个棋子的颜色和文字

棋盘的可视化界面如下图所示:

4.4 游戏逻辑实现

  • 棋子移动规则判断(is_valid_move函数)
  • 将军判定(is_check函数)
  • 移动安全性检查(is_move_safe函数)
  • 胜负判定(is_game_over函数)

棋子移动规则判断是核心算法,符合中国象棋规则的走法判断:

  • 将帅不能出九宫,只能一步一格或对面照将
  • 士 / 仕只能在九宫内斜走一步
  • 象 / 相不能过河,走田字格且不能塞象眼
  • 马走日字格,不能绊马腿
  • 车直线行走,不能越子
  • 炮直线行走,吃子需翻山
  • 兵 / 卒未过河只能前进,过河后可左右移动

其中,is_valid_move函数的代码如下所示:

def is_valid_move(board, from_row, from_col, to_row, to_col, is_red_turn, check_check=True):
    # 检查是否移动到相同位置
    if from_row == to_row and from_col == to_col:
        return False

    # 检查目标位置是否是己方棋子
    target_piece = board[to_row][to_col]
    if is_own_piece(target_piece, is_red_turn):
        return False

    piece = board[from_row][from_col]

    # 根据不同棋子类型检查移动规则
    if piece in [RED_GENERAL, BLACK_GENERAL]:
        # 将帅移动规则
        general_row_min, general_row_max = (0, 2) if piece == RED_GENERAL else (7, 9)
        if not (general_row_min <= to_row <= general_row_max and 3 <= to_col <= 5):
            return False

        # 将帅只能走一步
        if abs(from_row - to_row) + abs(from_col - to_col) != 1:
            return False

        # 将帅对面
        if abs(from_row - to_row) == 0:
            # 检查列是否相同
            if from_col != to_col:
                return False

            # 检查中间是否有棋子
            min_r, max_r = min(from_row, to_row), max(from_row, to_row)
            has_obstacle = False
            for r in range(min_r + 1, max_r):
                if board[r][from_col] != EMPTY:
                    has_obstacle = True
                    break
            if not has_obstacle:
                return True

        return True

    elif piece in [RED_ADVISOR, BLACK_ADVISOR]:
        # 士/仕移动规则
        advisor_row_min, advisor_row_max = (0, 2) if piece == RED_ADVISOR else (7, 9)
        if not (advisor_row_min <= to_row <= advisor_row_max and 3 <= to_col <= 5):
            return False

        # 士/仕只能斜走一步
        if abs(from_row - to_row) == 1 and abs(from_col - to_col) == 1:
            return True
        return False

    elif piece in [RED_ELEPHANT, BLACK_ELEPHANT]:
        # 象/相移动规则
        # 不能过河
        if (piece == RED_ELEPHANT and to_row >= 5) or (piece == BLACK_ELEPHANT and to_row <= 4):
            return False

        # 走田字格
        if abs(from_row - to_row) == 2 and abs(from_col - to_col) == 2:
            # 检查象眼是否被塞住
            eye_row = (from_row + to_row) // 2
            eye_col = (from_col + to_col) // 2
            if board[eye_row][eye_col] == EMPTY:
                return True
        return False

    elif piece in [RED_HORSE, BLACK_HORSE]:
        # 马移动规则
        # 马走日字
        row_diff = abs(from_row - to_row)
        col_diff = abs(from_col - to_col)
        if (row_diff == 1 and col_diff == 2) or (row_diff == 2 and col_diff == 1):
            # 检查马腿是否被绊住
            if row_diff == 2:
                leg_row = from_row + 1 if to_row > from_row else from_row - 1
                leg_col = from_col
            else:
                leg_row = from_row
                leg_col = from_col + 1 if to_col > from_col else from_col - 1

            if board[leg_row][leg_col] == EMPTY:
                return True
        return False

    elif piece in [RED_CHARIOT, BLACK_CHARIOT]:
        # 车移动规则
        # 直线移动
        if from_row != to_row and from_col != to_col:
            return False

        # 检查路径上是否有障碍物
        if from_row == to_row:
            # 水平移动
            min_c, max_c = min(from_col, to_col), max(from_col, to_col)
            for c in range(min_c + 1, max_c):
                if board[from_row][c] != EMPTY:
                    return False
        else:
            # 垂直移动
            min_r, max_r = min(from_row, to_row), max(from_row, to_row)
            for r in range(min_r + 1, max_r):
                if board[r][from_col] != EMPTY:
                    return False

        return True

    elif piece in [RED_CANNON, BLACK_CANNON]:
        # 炮移动规则
        # 直线移动
        if from_row != to_row and from_col != to_col:
            return False

        # 计算路径上的障碍物数量
        obstacle_count = 0
        if from_row == to_row:
            # 水平移动
            min_c, max_c = min(from_col, to_col), max(from_col, to_col)
            for c in range(min_c + 1, max_c):
                if board[from_row][c] != EMPTY:
                    obstacle_count += 1
        else:
            # 垂直移动
            min_r, max_r = min(from_row, to_row), max(from_row, to_row)
            for r in range(min_r + 1, max_r):
                if board[r][from_col] != EMPTY:
                    obstacle_count += 1

        # 炮翻山吃子或无子可吃
        if target_piece == EMPTY:
            return obstacle_count == 0
        else:
            return obstacle_count == 1

    elif piece in [RED_SOLDIER, BLACK_SOLDIER]:
        # 兵/卒移动规则
        row_diff = to_row - from_row
        col_diff = abs(from_col - to_col)

        # 未过河只能前进
        if (piece == RED_SOLDIER and from_row < 5) or (piece == BLACK_SOLDIER and from_row >= 5):
            if col_diff != 0:
                return False

        # 过河后可以横向移动一步
        else:
            if col_diff > 1:
                return False

        # 兵/卒只能走一步
        if col_diff == 1:
            return row_diff == 0
        elif row_diff == -1 and piece == BLACK_SOLDIER:  # 黑方卒向上走
            return True
        elif row_diff == 1 and piece == RED_SOLDIER:  # 红方兵向下走
            return True

        return False

    return False

棋子移动的可视化界面如下所示:

在将军判定(is_check函数)中,如果找不到将 / 帅(通常是因为游戏已经结束),则直接返回False,代码如下所示:

# 检查将军
def is_check(board, is_red_turn):
    # 找到将/帅的位置
    general_pos = None
    target_general = RED_GENERAL if not is_red_turn else BLACK_GENERAL

    for row in range(10):
        for col in range(BOARD_SIZE):
            if board[row][col] == target_general:
                general_pos = (row, col)
                break
        if general_pos:
            break

    # 关键修复:如果找不到将/帅,说明游戏已经结束,返回False
    if general_pos is None:
        return False

    # 检查对方是否能攻击到将/帅
    for row in range(10):
        for col in range(BOARD_SIZE):
            piece = board[row][col]
            if is_enemy_piece(piece, is_red_turn):
                if is_valid_move(board, row, col, general_pos[0], general_pos[1], not is_red_turn, check_check=False):
                    return True
    return False

将军判定(is_check函数)的可视化界面如下所示,当红方执跑将军后,红方赢得对局:

4.5 用户交互处理

  • 鼠标点击事件处理,包括棋子选择和移动
  • 悔棋功能实现(基于历史记录的状态恢复)
  • 游戏状态切换控制

其中,悔棋功能的实现代码如下所示:

# 计算悔棋按钮位置并检查是否点击
button_width = 120
turn_text = "红方回合" if is_red_turn else "黑方回合"
text_surface = font.render(turn_text, True, RED if is_red_turn else BLACK)
text_width = text_surface.get_width()
total_width = text_width + button_width + 40
start_x = (WINDOW_WIDTH - total_width) // 2
undo_button_rect = pygame.Rect(start_x + text_width + 40, CONTROL_BAR_Y, button_width,
                               CONTROL_BAR_HEIGHT)

if undo_button_rect.collidepoint(x, y) and len(move_history) > 0:
    # 播放悔棋音效
    if undo_sound:
        undo_sound.play()
    # 从历史记录恢复棋盘状态
    board, is_red_turn = move_history.pop()
    selected_pos = None
    continue

# 计算点击的棋盘位置
col = round((x - BOARD_OFFSET_X) / CELL_SIZE)
row = round((y - BOARD_OFFSET_Y) / CELL_SIZE)

if is_on_board(row, col):
    # 如果点击了己方棋子,则选中它
    if is_own_piece(board[row][col], is_red_turn):
        # 播放选中音效
        if select_sound:
            select_sound.play()
        selected_pos = (row, col)
    # 如果已经选中了棋子,尝试移动
    elif selected_pos:
        from_row, from_col = selected_pos

        # 检查移动是否合法且不会导致被将军
        if (is_valid_move(board, from_row, from_col, row, col, is_red_turn) and
                is_move_safe(board, from_row, from_col, row, col, is_red_turn)):

            # 保存当前状态到历史记录,用于悔棋
            move_history.append((copy_board(board), is_red_turn))

            # 判断是否是吃子动作
            is_capture = board[row][col] != EMPTY

            # 执行移动
            board[row][col] = board[from_row][from_col]
            board[from_row][from_col] = EMPTY

            # 播放相应音效
            if is_capture and capture_sound:
                capture_sound.play()
            elif move_sound:
                move_sound.play()

            # 检查游戏是否结束
            if is_game_over(board):
                game_state = GAME_OVER
                # 确定获胜方
                has_red_general = any(RED_GENERAL in row for row in board)
                winner = "红方" if has_red_general else "黑方"
                # 播放胜利音效
                if win_sound:
                    win_sound.play()
            else:
                # 切换回合
                is_red_turn = not is_red_turn

        # 取消选中
        selected_pos = None

悔棋的可视化界面如下图所示:

4.6 主游戏循环

def main():
    board = init_board()
    selected_pos = None
    is_red_turn = True
    game_state = GAME_PLAYING
    
    while True:
        # 事件处理
        # 游戏逻辑更新
        # 画面渲染
        pygame.display.flip()
        clock.tick(60)

五、总结

本项目基于 Pygame 库成功实现了一款功能完整的中国象棋游戏,具备以下特点:

  1. 功能完整性:实现了中国象棋的全部规则,包括各类棋子的移动方式、将军判定、胜负判定等核心功能
  2. 用户体验良好:提供了直观的图形界面、清晰的回合提示、适当的音效反馈,操作简单直观
  3. 扩展性强:代码结构清晰,模块化设计使得后续可以方便地添加新功能(如 AI 对手、网络对战等)
  4. 文化适应性:支持中文显示,保留了中国象棋的传统元素和文化内涵

该实现方案为传统棋类游戏的数字化提供了一个可行的参考,展示了如何利用现代编程技术传承和推广传统游戏文化。后续可以进一步优化 AI 算法、增加游戏模式,提升游戏的趣味性和挑战性。

最后上传个该项目的简要演示视频,供大家了解。

手把手实现经典的象棋游戏

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐