用 Bash 打造交互式五子棋:从脚本实现到技术优化(附脚本)

发布于:2025-06-24 ⋅ 阅读:(20) ⋅ 点赞:(0)

引言:当 Shell 遇上棋类游戏

在大多数人的印象中,Bash(Bourne Again Shell)常用于系统管理、自动化脚本编写,很少与游戏开发关联。然而,借助关联数组、循环逻辑和终端交互能力,Bash 完全可以实现轻量级的交互式游戏。本文将以五子棋为案例,拆解 Bash 游戏开发的核心技术,并探讨从基础实现到工程优化的完整路径。

一、核心实现:Bash 五子棋的技术架构

1. 数据结构设计

五子棋的棋盘本质是二维网格,Bash 中通过关联数组模拟二维数组:

declare -A board  # 键为坐标i,j,值为棋子状态
BOARD_SIZE=10     # 10×10棋盘(可调整为15×15标准规格)

  • 空位用.表示,玩家棋子分别用XO标识。
  • 坐标映射:board[$i,$j]对应棋盘第 i 行第 j 列的位置。
2. 核心功能模块
(1)棋盘初始化与渲染
init_board() {
    for ((i=0; i<BOARD_SIZE; i++)); do
        for ((j=0; j<BOARD_SIZE; j++)); do
            board[$i,$j]='.'  # 所有位置初始为空
        done
    done
}

print_board() {
    clear  # 清屏优化显示
    echo -n "   "
    for ((i=0; i<BOARD_SIZE; i++)); do printf "%2d " $i; done  # 列坐标
    echo
    for ((i=0; i<BOARD_SIZE; i++)); do
        printf "%2d " $i  # 行坐标
        for ((j=0; j<BOARD_SIZE; j++)); do
            printf " %s " "${board[$i,$j]}"
        done
        echo
    done
}

  • 输出格式示例:
      0 1 2 3 4 5 6 7 8 9  
    0 . . . . . . . . . .  
    1 . . . . . . . . . .  
    ...
    
(2)胜负判定算法

五子棋的胜利条件是某方向连续 5 子同色,需检查 4 个方向:

check_win() {
    local x=$1 y=$2 player=$3
    local directions=("1 0" "0 1" "1 1" "1 -1")  # 横、竖、主对角、副对角
    
    for dir in "${directions[@]}"; do
        local dx=$(echo $dir | cut -d' ' -f1)
        local dy=$(echo $dir | cut -d' ' -f2)
        local count=1  # 当前落子本身计数1
        
        # 正方向检查(如横向向右)
        local nx=$((x+dx)) ny=$((y+dy))
        while is_in_bounds "$nx" "$ny" && [[ ${board[$nx,$ny]} == $player ]]; do
            ((count++)); nx=$((nx+dx)); ny=$((ny+dy))
        done
        
        # 反方向检查(如横向向左)
        nx=$((x-dx)) ny=$((y-dy))
        while is_in_bounds "$nx" "$ny" && [[ ${board[$nx,$ny]} == $player ]]; do
            ((count++)); nx=$((nx-dx)); ny=$((ny-dy))
        done
        
        if ((count >= 5)); then return 0; fi  # 满足5子连珠
    done
    return 1
}

is_in_bounds() {  # 新增边界检查函数
    local x=$1 y=$2
    ((x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE))
}

  • 算法核心:对每个方向,分别向正负方向遍历,统计连续同色棋子数。

二、从可用到好用:脚本优化实践

1. 原脚本的三大隐患
  1. 边界溢出风险:原check_win未验证坐标范围,当棋子靠近边界时可能访问board[-1,5]等非法位置。
  2. 变量作用域混乱:未用local声明的nxny可能在循环中被意外修改。
  3. 交互体验缺失:无清屏功能、错误提示模糊、游戏结束直接退出。
2. 优化后的关键改进
# 主游戏循环优化对比(部分)
while ((moves < max_moves)); do
    print_board
    echo "Player $current, enter your move (row col):"
    read -r row col
    
    # 输入验证增强
    if [[ ! $row =~ ^[0-9]+$ || ! $col =~ ^[0-9]+$ || $row -ge $BOARD_SIZE || $col -ge $BOARD_SIZE ]]; then
        echo "❌ 输入非法!请输入0-$((BOARD_SIZE-1))范围内的数字"
        sleep 1  # 延迟显示错误,便于用户查看
        continue
    fi
    
    # 落子逻辑
    board[$row,$col]=$current
    ((moves++))
    
    if check_win "$row" "$col" "$current"; then
        print_board
        echo "🎉 Player $current wins!"
        read -p "按Enter键退出..."  # 等待用户确认
        return
    fi
    
    current=$([[ $current == "X" ]] && echo "O" || echo "X")
done

  • 优化点总结
    • 新增is_in_bounds函数,彻底避免数组越界;
    • 错误提示明确化,加入颜色符号(需终端支持);
    • 游戏结束后增加用户确认环节,提升交互友好性。

三、进阶扩展:从单机到可演进的游戏框架

1. AI 对手实现思路

Bash 实现简单 AI 可基于以下策略:

  1. 防御优先:检查对手是否有四连珠(如X X X X .),优先阻断;
  2. 进攻其次:寻找自身三连珠(如X X . X),扩展成四连珠;
  3. 棋盘权重:中心区域权重高于边缘(如 (4,4) 位置优先级最高)。

# AI落子伪代码(简化逻辑)
ai_move() {
    # 1. 检查对手四连珠
    check_enemy_four_in_a_row && block_move
    
    # 2. 检查自身三连珠
    check_self_three_in_a_row && extend_move
    
    # 3. 落子棋盘中心区域
    place_at_center()
}
2. 界面升级:引入颜色与图形元素

借助 ANSI 转义序列实现棋子颜色区分:

# 定义颜色变量
RED='\033[0;31m'    # X为红色
GREEN='\033[0;32m'  # O为绿色
NC='\033[0m'        # 重置颜色

# 打印时应用颜色
printf " ${RED}X${NC} "  # 红色X
printf " ${GREEN}O${NC} "  # 绿色O

  • 效果示例:红色X与绿色O在棋盘上形成视觉区分,提升游戏体验。

四、实践价值与技术延伸

1. Bash 游戏开发的局限与意义
  • 局限:性能有限(纯文本界面、无图形加速),不适合复杂游戏;
  • 意义
    • 深入理解 Shell 编程的边界与可能性;
    • 为 Linux 系统下轻量级交互工具提供开发思路(如终端版小游戏、教育演示工具)。
2. 技术延伸方向
  • 跨平台适配:通过readline库增强输入体验,或用expect脚本实现自动化测试;
  • 混合开发:Bash 作为前端交互层,后端用 Python/Go 实现复杂逻辑(如 AI 算法);
  • 实战案例:可扩展为井字棋、扫雷等更多终端游戏,形成 Shell 游戏开发系列。

总结

从一个简单的 Bash 五子棋脚本,我们看到了 Shell 编程的灵活性 —— 即使不依赖复杂框架,也能通过基础语法构建交互式应用。优化过程中涉及的边界检查、用户体验设计和算法逻辑,本质上与其他编程语言的开发思想相通。对于想深入 Shell 编程或探索小众开发场景的开发者,这类项目是极佳的实践入口。

五、脚本

#!/bin/bash

# 初始化变量
BOARD_SIZE=10
declare -A board

# 初始化棋盘
init_board() {
    for ((i=0; i<BOARD_SIZE; i++)); do
        for ((j=0; j<BOARD_SIZE; j++)); do
            board[$i,$j]='.'
        done
    done
}

# 打印棋盘
print_board() {
    echo -n "   "
    for ((i=0; i<BOARD_SIZE; i++)); do
        printf "%2d " $i
    done
    echo
    for ((i=0; i<BOARD_SIZE; i++)); do
        printf "%2d " $i
        for ((j=0; j<BOARD_SIZE; j++)); do
            printf " %s " "${board[$i,$j]}"
        done
        echo
    done
}

# 判断是否胜利
check_win() {
    local x=$1
    local y=$2
    local player=$3

    directions=(
        "1 0"  # 横
        "0 1"  # 竖
        "1 1"  # 主对角
        "1 -1" # 副对角
    )

    for dir in "${directions[@]}"; do
        dx=$(echo $dir | cut -d' ' -f1)
        dy=$(echo $dir | cut -d' ' -f2)
        count=1

        # 正方向
        nx=$((x + dx))
        ny=$((y + dy))
        while [[ ${board[$nx,$ny]} == $player ]]; do
            ((count++))
            nx=$((nx + dx))
            ny=$((ny + dy))
        done

        # 反方向
        nx=$((x - dx))
        ny=$((y - dy))
        while [[ ${board[$nx,$ny]} == $player ]]; do
            ((count++))
            nx=$((nx - dx))
            ny=$((ny - dy))
        done

        if ((count >= 5)); then
            return 0
        fi
    done

    return 1
}

# 主游戏循环
play_game() {
    init_board
    current="X"
    moves=0
    max_moves=$((BOARD_SIZE * BOARD_SIZE))

    while ((moves < max_moves)); do
        print_board
        echo "Player $current, enter your move (row col):"
        read -r row col

        if [[ ! $row =~ ^[0-9]+$ || ! $col =~ ^[0-9]+$ || $row -ge $BOARD_SIZE || $col -ge $BOARD_SIZE ]]; then
            echo "Invalid input. Try again."
            continue
        fi

        if [[ ${board[$row,$col]} != '.' ]]; then
            echo "Cell already taken. Try again."
            continue
        fi

        board[$row,$col]=$current
        ((moves++))

        if check_win "$row" "$col" "$current"; then
            print_board
            echo "Player $current wins!"
            return
        fi

        current=$([[ $current == "X" ]] && echo "O" || echo "X")
    done

    echo "It's a draw!"
}

play_game


网站公告

今日签到

点亮在社区的每一天
去签到