用shell脚本设计的『扫雷』

发布时间 2023-08-16 18:33:09作者: 之于言者

不知道为什么,这个脚本和CentOS 7不兼容。

载入脚本后,用WASD键控制光标移动,按空格挖开地块,挖到的数字是地块周围的地雷数量,挖到地雷后游戏失败;
按F标记有地雷的地块,按E表示可能有地雷。已挖开的地块无法被标记。将所有地雷标记完毕后游戏胜利。
按Q键退出游戏。无论如何退出游戏,脚本都会总结扫到雷的数量和本局游戏的时间。

#!/bin/bash
# ========================================
# Author: Kide_Lee
# Date: 2023.8.15
# Blog: https://www.cnblogs.com/-zyyz-/
# ========================================
# 用户设置
# ========================================
width=8      # 界面的宽度
height=8     # 界面的高度
mineCount=10 # 地雷数量
mine="#"     # 地雷样式
title="MineSweeper"

# ========================================
# 方法
# 用一维数组模拟一张二维表;
# 下面提供若干方法,使脚本能够通过查询二维坐标的方式访问数组;
# 其中横纵坐标按书写方向,从零开始计数。
# ========================================
function ReadTable() {
    local x=$2
    local y=$3

    # matrix和field是脚本维护的两张表,
    # 其分别记录了雷区信息和用户界面。
    case "$1" in
    matrix) local info=("${matrix[@]}") ;;
    field) local info=("${field[@]}") ;;
    esac

    # 若读取的坐标越界或无值,则返回0;否则返回坐标对应值。
    local result=${info[$(("$y" * "$width" + "$x"))]}
    if [ -z "$result" ]; then
        return 0
    elif [ "$x" -lt 0 ]; then
        return 0
    elif [ "$y" -lt 0 ]; then
        return 0
    elif [ "$x" -ge $width ]; then
        return 0
    elif [ "$y" -ge $height ]; then
        return 0
    else
        return "$result"
    fi
}
function WriteTable() {
    local x=$2
    local y=$3
    local new=$4

    if [ "$x" -lt 0 ]; then
        return 0
    elif [ "$y" -lt 0 ]; then
        return 0
    elif [ "$x" -ge $width ]; then
        return 0
    elif [ "$y" -ge $height ]; then
        return 0
    fi

    if [ "$1" == "matrix" ]; then
        matrix["$y" * "$width" + "$x"]=$new
    elif [ "$1" == "field" ]; then
        field["$y" * "$width" + "$x"]=$new
    fi
}

# ========================================
# 初始化
# ========================================
# 主函数
function StartGame() {
    X=$(("$width" / 2 - 1))
    Y=$(("$height" / 2 - 1))
    size=$(("$width" * "$height"))
    tput civis
    # 生成雷区
    for ((i = 0; i < "$size"; i++)); do
        matrix+=("0")
    done
    # 生成界面
    for ((i = 0; i < "$size"; i++)); do
        field+=("10")
    done
    DrawField
    while [ -z "$checkFirst" ]; do
        DrawFocus $X $Y
        IFS_back=$IFS
        IFS=""
        read -srn1 input
        IFS=$IFS_back
        case "$input" in
        w | W) MoveUp ;;
        a | A) MoveLeft ;;
        s | S) MoveDown ;;
        d | D) MoveRight ;;
        f | F) MarkMatrix $X $Y 11 ;;
        e | E) MarkMatrix $X $Y 12 ;;
        q | Q) return 1 ;;
        " ")
            CreateMine
            CreateNumber
            startTime=$(date +%s)
            CheckMatrix $X $Y
            local checkFirst="yes"
            ;;
        esac
    done
}
# 绘制界面
function DrawField() {
    # 绘制标题栏
    tput clear
    local fieldWidth=$(("$width" * 3 + 2))
    local titleWidth=${#title}
    if [ "$fieldWidth" -gt "$titleWidth" ]; then
        tput cup 0 $(("$fieldWidth" / 2 - "$titleWidth" / 2))
        echo $title
    else
        tput cup 0 0
        echo $title
    fi
    # 绘制上边框
    echo -n +
    for ((i = 0; i < "$width"; i++)); do
        echo -n "---"
    done
    echo +
    # 绘制游戏区域
    for ((i = 0; i < "$height"; i++)); do
        echo -n "|"
        for ((j = 0; j < "$width"; j++)); do
            echo -n "[ ]"
        done
        echo "|"
    done
    # 绘制下边框
    echo -n +
    for ((i = 0; i < "$width"; i++)); do
        echo -n "---"
    done
    echo +
    # 增加游戏提示
    echo "move: WASD  check: Space"
    echo "mark: F  question: E  quit: Q"
}
# 铺设地雷
function CreateMine() {
    # 定义安全区,保证首次排雷安全
    local safeFieldList=()
    for x in $X-1 $X $X+1; do
        for y in $Y-1 $Y $Y+1; do
            x=$(("$x"))
            y=$(("$y"))
            local safeNum=$(("$y" * "$width" + "$x"))
            if [ $safeNum -ge 0 ] || [ $safeNum -lt "$size" ]; then
                safeFieldList+=("$safeNum")
            fi
        done
    done
    # 定义地雷,本质是一串不重复的随机数
    local mineList=()
    while [ ${#mineList[*]} -lt $mineCount ]; do
        local random=$(("$RANDOM" % "$size"))
        local isRepeat=false
        for i in "${mineList[@]}" "${safeFieldList[@]}"; do
            if [ $random == "$i" ]; then
                isRepeat=true
                break
            fi
        done

        if [ $isRepeat == false ]; then
            mineList+=("$random")
        fi
    done
    # 布置地雷,在雷区中,9代表有地雷
    for i in "${mineList[@]}"; do
        matrix["$i"]=9
    done
}
# 生成数字
function CreateNumber() {
    function PutOne() {
        local x=$1
        local y=$2
        for i in $x-1 $x $x+1; do
            for j in $y-1 $y $y+1; do
                local i=$(("$i"))
                local j=$(("$j"))
                ReadTable matrix $i $j
                tmpNum=$?
                if [ $tmpNum != 9 ]; then
                    WriteTable matrix $i $j $(("$tmpNum" + 1))
                fi
            done
        done
    }
    # 遇到地雷,非地雷邻格数字+1。
    for ((y = 0; y < "$height"; y++)); do
        for ((x = 0; x < "$width"; x++)); do
            ReadTable matrix $x $y
            if [ $? == 9 ]; then
                PutOne $x $y
            fi
        done
    done
}

# ========================================
# 游戏过程
# ========================================
# 主函数
function PlayGame() {
    while true; do
        DrawFocus $X $Y
        IFS_back=$IFS
        IFS=""
        read -srn1 input
        IFS=$IFS_back
        case "$input" in
        w | W) MoveUp ;;
        a | A) MoveLeft ;;
        s | S) MoveDown ;;
        d | D) MoveRight ;;
        f | F) MarkMatrix $X $Y 11 ;;
        e | E) MarkMatrix $X $Y 12 ;;
        q | Q) return 1 ;;
        " ")
            CheckMatrix $X $Y
            if [ $? == 9 ]; then
                tput bel
                return 2
            fi
            ;;
        esac
        Judging
        if [ $? == 2 ]; then
            return 3
        fi
    done
}
# 检查雷区
function CheckCell() {
    local x=$1
    local y=$2
    ReadTable field "$x" "$y"
    local result=$?
    if [ $result -ge 10 ]; then
        ReadTable matrix "$x" "$y"
        local result=$?
        WriteTable field "$x" "$y" "$result"
        Draw "$x" "$y"
    elif [ $result == 0 ]; then
        local result=10
    fi
    return "$result"
}
# 递归检查雷区
function CheckMatrix() {
    local x_1=$1
    local y_1=$2
    local edgeList=("$x_1" "$y_1")
    local edgeLength=2
    while [ "$edgeLength" -gt 0 ]; do
        local x=${edgeList[$edgeLength - 2]}
        local y=${edgeList[$edgeLength - 1]}
        unset "edgeList[$edgeLength-2]"
        unset "edgeList[$edgeLength-1]"
        CheckCell "$x" "$y"
        local result=$?
        if [ $result == 0 ]; then
            for i in $x-1 $x $x+1; do
                for j in $y-1 $y $y+1; do
                    local i=$(("$i"))
                    local j=$(("$j"))
                    edgeList+=("$i" "$j")
                done
            done
        fi
        local edgeLength=${#edgeList[@]}
    done
    # 提供返回值
    ReadTable field "$x_1" "$y_1"
    return $?
}
# 渲染界面
# 本函数同时仅能渲染一个格子。
function Draw() {
    local x=$1
    local y=$2
    ReadTable field "$x" "$y"
    local result=$?
    case "$result" in
    0) display="   " ;;
    9) display="$(tput setaf 1) ${mine} $(tput sgr0)" ;;
    10) display="[ ]" ;;
    11) display="$(tput setaf 2)[F]$(tput sgr0)" ;;
    12) display="$(tput setaf 3)[?]$(tput sgr0)" ;;
    *) display="$(tput setaf $((8 - "$result"))) ${result} $(tput sgr0)" ;;
    esac
    tput cup $(("$y" + 2)) $(("$x" * 3 + 1))
    echo -n "$(tput sgr0)${display}"
}
# 渲染焦点
function DrawFocus() {
    local x=$1
    local y=$2

    if [ "$old_X" ]; then
        Draw "$old_X" "$old_Y"
    fi

    Draw "$x" "$y"
    tput cup $(("$y" + 2)) $(("$x" * 3 + 1))
    echo -n "$(tput rev)${display}"

    old_X=$1
    old_Y=$2
}
# 标记雷区
function MarkMatrix() {
    local x=$1
    local y=$2
    local mark=$3
    ReadTable field "$x" "$y"
    local fieldChar=$?
    if [ $fieldChar == "$mark" ]; then
        WriteTable field "$x" "$y" 10
    elif [ $fieldChar -gt 9 ]; then
        WriteTable field "$x" "$y" "$mark"
    fi
}
# 移动
function MoveUp() {
    Y=$(("$Y" - 1))
    if [ $Y -lt 0 ]; then
        Y=$(("$Y" + "$height"))
    fi
}
function MoveDown() {
    Y=$(("$Y" + 1))
    if [ $Y -ge $height ]; then
        Y=$(("$Y" - "$height"))
    fi
}
function MoveLeft() {
    X=$(("$X" - 1))
    if [ $X -lt 0 ]; then
        X=$(("$X" + "$width"))
    fi
}
function MoveRight() {
    X=$(("$X" + 1))
    if [ $X -ge $width ]; then
        X=$(("$X" - "$width"))
    fi
}
# 判断
function Judging() {
    for i in "${field[@]}"; do
        if [ "$i" == 10 ] || [ "$i" == 12 ]; then
            return 1
        fi
    done
    return 2
}

# ========================================
# 游戏清算
# ========================================
function ClearGame() {
    endTime=$(date +%s)
    Draw $X $Y
    if [ "$startTime" ]; then
        time=$(("$endTime" - "$startTime"))
    else
        time=0
    fi
    mineSweeper=0
    if [ $result == 1 ]; then
        for ((i = 0; i < ${#matrix[@]}; i++)); do
            x=$(("$i" % "$width"))
            y=$(("$i" / "$height"))
            if [ "${matrix[$i]}" == 9 ]; then
                if [ "${field[$i]}" == 11 ]; then
                    mineSweeper=$(("$mineSweeper" + 1))
                    Draw "$x" "$y"
                    tput cup $(("$y" + 2)) $(("$x" * 3 + 1))
                    echo -n "$(tput rev)$(tput setaf 2)[F]$(tput sgr0)"
                else
                    Draw "$x" "$y"
                    tput cup $(("$y" + 2)) $(("$x" * 3 + 1))
                    echo -n "[${mine}]"
                fi
            fi
        done
        clearInfo="Game Exited.\nYou swept $mineSweeper mines in $time seconds."
    elif [ $result == 2 ]; then
        for ((i = 0; i < ${#matrix[@]}; i++)); do
            x=$(("$i" % "$width"))
            y=$(("$i" / "$height"))
            if [ "${matrix[$i]}" == 9 ]; then
                if [ "${field[$i]}" == 11 ]; then
                    mineSweeper=$(("$mineSweeper" + 1))
                    Draw "$x" "$y"
                    tput cup $(("$y" + 2)) $(("$x" * 3 + 1))
                    echo -n "$(tput rev)$(tput setaf 2) ${mine} $(tput sgr0)"
                else
                    Draw "$x" "$y"
                    tput cup $(("$y" + 2)) $(("$x" * 3 + 1))
                    echo -n "$(tput rev)$(tput setaf 1) ${mine} $(tput sgr0)"
                fi
            fi
        done
        clearInfo="$(tput setaf 1)The Game Failed. \nYou swept $mineSweeper mines in $time seconds."
    elif [ $result == 3 ]; then
        clearInfo="$(tput setaf 2)You Win!\nYou swept all mines in $time seconds."
    fi
    tput cnorm
    tput cup $(("$height" + 3)) 0
    tput el
    echo -e "$(tput rev)${clearInfo}$(tput sgr0)"
}

# ========================================
# 游戏运行
# ========================================
StartGame
result=$?
if [ $result == 0 ]; then
    PlayGame
    result=$?
fi
ClearGame