编写扫雷游戏提高 Bash 技巧

那些令人怀念的经典游戏可是提高编程能力的好素材。今天就让我们仔细探索一番,怎么用 Bash 编写一个扫雷程序。

我在编程教学方面不是专家,但当我想更好掌握某一样东西时,会试着找出让自己乐在其中的方法。比方说,当我想在 shell 编程方面更进一步时,我决定用 Bash 编写一个扫雷游戏来加以练习。

如果你是一个有经验的 Bash 程序员,希望在提高技巧的同时乐在其中,那么请跟着我编写一个你的运行在终端中的扫雷游戏。完整代码可以在这个 GitHub 存储库中找到。

做好准备

在我编写任何代码之前,我列出了该游戏所必须的几个部分:

  1. 显示雷区
  2. 创建游戏逻辑
  3. 创建判断单元格是否可选的逻辑
  4. 记录可用和已查明(已排雷)单元格的个数
  5. 创建游戏结束逻辑

显示雷区

在扫雷中,游戏界面是一个由 2D 数组(列和行)组成的不透明小方格。每一格下都有可能藏有地雷。玩家的任务就是找到那些不含雷的方格,并且在这一过程中,不能点到地雷。这个 Bash 版本的扫雷使用 10x10 的矩阵,实际逻辑则由一个简单的 Bash 数组来完成。

首先,我先生成了一些随机数字。这将是地雷在雷区里的位置。控制地雷的数量,在开始编写代码之前,这么做会容易一些。实现这一功能的逻辑可以更好,但我这么做,是为了让游戏实现保持简洁,并有改进空间。(我编写这个游戏纯属娱乐,但如果你能将它修改的更好,我也是很乐意的。)

下面这些变量在整个过程中是不变的,声明它们是为了随机生成数字。就像下面的 a - g 的变量,它们会被用来计算可排除的地雷的值:

  1. #变量
  2. score=0#会用来存放游戏分数
  3. #下面这些变量,用来随机生成可排除地雷的实际值
  4. a="1 10 -10 -1"
  5. b="-1 0 1"
  6. c="0 1"
  7. d="-1 0 1 -2 -3"
  8. e="1 2 20 21 10 0 -10 -20 -23 -2 -1"
  9. f="1 2 3 35 30 20 22 10 0 -10 -20 -25 -30 -35 -3 -2 -1"
  10. g="1 4 6 9 10 15 20 25 30 -30 -24 -11 -10 -9 -8 -7"
  11. #
  12. #声明
  13. declare-a room #声明一个 room 数组,它用来表示雷区的每一格。

接下来,我会用列(0-9)和行(a-j)显示出游戏界面,并且使用一个 10x10 矩阵作为雷区。(M[10][10] 是一个索引从 0-99,有 100 个值的数组。)

创建一个叫 plough 的函数,我们先将标题显示出来:两个空行、列头,和一行 -,以示意往下是游戏界面:

  1. printf‘\n\n‘
  2. printf‘%s‘"     a   b   c   d   e   f   g   h   i   j"
  3. printf‘\n   %s\n‘"-----------------------------------------"

然后,我初始化一个计数器变量,叫 r,它会用来记录已显示多少横行。注意,稍后在游戏代码中,我们会用同一个变量 r,作为我们的数组索引。 在 Bash for 循环中,用 seq 命令从 0 增加到 9。我用数字(d%)占位,来显示行号($row,由 seq 定义):

  1. r=0#计数器
  2. for row in $(seq 09);do
  3. printf‘%d ‘"$row"#显示行数0-9

在我们接着往下做之前,让我们看看到现在都做了什么。我们先横着显示 [a-j] 然后再将 [0-9] 的行号显示出来,我们会用这两个范围,来确定用户排雷的确切位置。

接着,在每行中,插入列,所以是时候写一个新的 for 循环了。这一循环管理着每一列,也就是说,实际上是生成游戏界面的每一格。我添加了一些辅助函数,你能在源码中看到它的完整实现。 对每一格来说,我们需要一些让它看起来像地雷的东西,所以我们先用一个点(.)来初始化空格。为了实现这一想法,我们用的是一个叫 is_null_field 的自定义函数。 同时,我们需要一个存储每一格具体值的数组,这儿会用到之前已定义的全局数组 room , 并用 变量 r作为索引。随着 r 的增加,遍历所有单元格,并随机部署地雷。

  1.   for col in $(seq 09);do
  2. ((r+=1))#循环完一列行数加一
  3. is_null_field $r #假设这里有个函数,它会检查单元格是否为空,为真,则此单元格初始值为点(.)
  4. printf‘%s \e[33m%s\e[0m ‘"|""${room[$r]}"#最后显示分隔符,注意,${room[$r]}的第一个值为‘.‘,等于其初始值。
  5. #结束 col 循环
  6. done

最后,为了保持游戏界面整齐好看,我会在每行用一个竖线作为结尾,并在最后结束行循环:

  1. printf‘%s\n‘"|"#显示出行分隔符
  2. printf‘ %s\n‘"-----------------------------------------"
  3. #结束行循环
  4. done
  5. printf‘\n\n‘

完整的 plough 代码如下:

  1. plough()
  2. {
  3.   r=0
  4.   printf‘\n\n‘
  5.   printf‘%s‘"     a   b   c   d   e   f   g   h   i   j"
  6.   printf‘\n   %s\n‘"-----------------------------------------"
  7.   for row in $(seq 09);do
  8.     printf‘%d  ‘"$row"
  9.     for col in $(seq 09);do
  10.        ((r+=1))
  11.        is_null_field $r
  12.        printf‘%s \e[33m%s\e[0m ‘"|""${room[$r]}"
  13.     done
  14.     printf‘%s\n‘"|"
  15.     printf‘   %s\n‘"-----------------------------------------"
  16.   done
  17.   printf‘\n\n‘
  18. }

我花了点时间来思考,is_null_field 的具体功能是什么。让我们来看看,它到底能做些什么。在最开始,我们需要游戏有一个固定的状态。你可以随便选择个初始值,可以是一个数字或者任意字符。我最后决定,所有单元格的初始值为一个点(.),因为我觉得,这样会让游戏界面更好看。下面就是这一函数的完整代码:

  1. is_null_field()
  2. {
  3. local e=$1 #在数组 room 中,我们已经用过循环变量‘r‘了,这次我们用‘e‘
  4. if[[-z "${room[$e]}"]];then
  5. room[$r]="."#这里用点(.)来初始化每一个单元格
  6. fi
  7. }

现在,我已经初始化了所有的格子,现在只要用一个很简单的函数就能得出当前游戏中还有多少单元格可以操作:

  1. get_free_fields()
  2. {
  3. free_fields=0#初始化变量
  4. for n in $(seq 1 ${#room[@]});do
  5. if[["${room[$n]}"="."]];then#检查当前单元格是否等于初始值(.),结果为真,则记为空余格子。
  6. ((free_fields+=1))
  7.     fi
  8.   done
  9. }

这是显示出来的游戏界面,[a-j] 为列,[0-9] 为行。

Minefield

创建玩家逻辑

玩家操作背后的逻辑在于,先从 stdin 中读取数据作为坐标,然后再找出对应位置实际包含的值。这里用到了 Bash 的参数扩展,来设法得到行列数。然后将代表列数的字母传给分支语句,从而得到其对应的列数。为了更好地理解这一过程,可以看看下面这段代码中,变量 o 所对应的值。 举个例子,玩家输入了 c3,这时 Bash 将其分成两个字符:c 和 3。为了简单起见,我跳过了如何处理无效输入的部分。

  1. colm=${opt:0:1}#得到第一个字符,一个字母
  2. ro=${opt:1:1}#得到第二个字符,一个整数
  3. case $colm in
  4. a ) o=1;;#最后,通过字母得到对应列数。
  5. b ) o=2;;
  6.     c ) o=3;;
  7.     d ) o=4;;
  8.     e ) o=5;;
  9.     f ) o=6;;
  10.     g ) o=7;;
  11.     h ) o=8;;
  12.     i ) o=9;;
  13.     j ) o=10;;
  14.   esac

下面的代码会计算用户所选单元格实际对应的数字,然后将结果储存在变量中。

这里也用到了很多的 shuf 命令,shuf 是一个专门用来生成随机序列的 Linux 命令。-i 选项后面需要提供需要打乱的数或者范围,-n 选项则规定输出结果最多需要返回几个值。Bash 中,可以在两个圆括号内进行数学计算,这里我们会多次用到。

还是沿用之前的例子,玩家输入了 c3。 接着,它被转化成了 ro=3 和 o=3。 之后,通过上面的分支语句代码, 将 c 转化为对应的整数,带进公式,以得到最终结果 i 的值。

  1. i=$(((ro*10)+o))#遵循运算规则,算出最终值
  2. is_free_field $i $(shuf-i 0-5-n 1)#调用自定义函数,判断其指向空/可选择单元格。

仔细观察这个计算过程,看看最终结果 i 是如何计算出来的:

  1. i=$(((ro*10)+o))
  2. i=$(((3*10)+3))=$((30+3))=33

最后结果是 33。在我们的游戏界面显示出来,玩家输入坐标指向了第 33 个单元格,也就是在第 3 行(从 0 开始,否则这里变成 4),第 3 列。

创建判断单元格是否可选的逻辑

为了找到地雷,在将坐标转化,并找到实际位置之后,程序会检查这一单元格是否可选。如不可选,程序会显示一条警告信息,并要求玩家重新输入坐标。

在这段代码中,单元格是否可选,是由数组里对应的值是否为点(.)决定的。如果可选,则重置单元格对应的值,并更新分数。反之,因为其对应值不为点,则设置变量 not_allowed。为简单起见,游戏中警告消息这部分源码,我会留给读者们自己去探索。

  1. is_free_field()
  2. {
  3.   local f=$1
  4.   local val=$2
  5.   not_allowed=0
  6.   if[["${room[$f]}"="."]];then
  7.     room[$f]=$val
  8.     score=$((score+val))
  9.   else
  10.     not_allowed=1
  11.   fi
  12. }

Extracting mines

如输入坐标有效,且对应位置为地雷,如下图所示。玩家输入 h6,游戏界面会出现一些随机生成的值。在发现地雷后,这些值会被加入用户得分。

Extracting mines

还记得我们开头定义的变量,a - g 吗,我会用它们来确定随机生成地雷的具体值。所以,根据玩家输入坐标,程序会根据(m)中随机生成的数,来生成周围其他单元格的值(如上图所示)。之后将所有值和初始输入坐标相加,最后结果放在 i(计算结果如上)中。

请注意下面代码中的 X,它是我们唯一的游戏结束标志。我们将它添加到随机列表中。在 shuf 命令的魔力下,X 可以在任意情况下出现,但如果你足够幸运的话,也可能一直不会出现。

  1. m=$(shuf-e a b c d e f g X -n 1)#将 X 添加到随机列表中,当 m=X,游戏结束
  2. if[["$m"!="X"]];then# X 将会是我们爆炸地雷(游戏结束)的触发标志
  3. for limit in ${!m};do#!m 代表 m 变量的值
  4. field=$(shuf-i 0-5-n 1)#然后再次获得一个随机数字
  5. index=$((i+limit))#将 m 中的每一个值和 index 加起来,直到列表结尾
  6. is_free_field $index $field
  7.     done

我想要游戏界面中,所有随机显示出来的单元格,都靠近玩家选择的单元格。

Extracting mines

记录已选择和可用单元格的个数

这个程序需要记录游戏界面中哪些单元格是可选择的。否则,程序会一直让用户输入数据,即使所有单元格都被选中过。为了实现这一功能,我创建了一个叫 free_fields 的变量,初始值为 0。用一个 for 循环,记录下游戏界面中可选择单元格的数量。 如果单元格所对应的值为点(.),则 free_fields 加一。

  1. get_free_fields()
  2. {
  3.   free_fields=0
  4.   for n in $(seq 1 ${#room[@]});do
  5.     if[["${room[$n]}"="."]];then
  6.       ((free_fields+=1))
  7.     fi
  8.   done
  9. }

等下,如果 free_fields=0 呢? 这意味着,玩家已选择过所有单元格。如果想更好理解这一部分,可以看看这里的源代码。

  1. if[[ $free_fields -eq 0]];then#这意味着你已选择过所有格子
  2. printf‘\n\n\t%s: %s %d\n\n‘"You Win""you scored""$score"
  3.       exit0
  4. fi

创建游戏结束逻辑

对于游戏结束这种情况,我们这里使用了一些很巧妙的技巧,将结果在屏幕中央显示出来。我把这部分留给读者朋友们自己去探索。

  1. if[["$m"="X"]];then
  2. g=0#为了在参数扩展中使用它
  3. room[$i]=X #覆盖此位置原有的值,并将其赋值为X
  4. for j in{42..49};do#在游戏界面中央,
  5. out="gameover"
  6. k=${out:$g:1}#在每一格中显示一个字母
  7. room[$j]=${k^^}
  8.       ((g+=1))
  9.     done
  10. fi

最后,我们显示出玩家最关心的两行。

  1. if[["$m"="X"]];then
  2.       printf‘\n\n\t%s: %s %d\n‘"GAMEOVER""you scored""$score"
  3.       printf‘\n\n\t%s\n\n‘"You were just $free_fields mines away."
  4.       exit0
  5. fi

Minecraft Gameover

文章到这里就结束了,朋友们!如果你想了解更多,具体可以查看我的 GitHub 存储库,那儿有这个扫雷游戏的源代码,并且你还能找到更多用 Bash 编写的游戏。 我希望,这篇文章能激起你学习 Bash 的兴趣,并乐在其中。

原文地址:https://www.cnblogs.com/zq789/p/11664854.html

时间: 2024-11-13 09:05:49

编写扫雷游戏提高 Bash 技巧的相关文章

十点提高编程技巧

1.学习一门新的编程语言(Learn a new programming language) 学习一门新的编程语言将有助于你开拓新的思维方式,特别是当你使用不熟悉的语言时,你将学习到很多种思维方法应用到语言中.而所学习到的新思维方式,你可以运用在你所熟知的语言中.甚至有时你会使用新学的语言进行你的重要项目. 提供学习经验包含: Lisp (Scheme is good), Forth, PostScript or Factor (stack-oriented programming langua

Java Swing扫雷游戏demo分享

好多年前写过简略的扫雷游戏,模拟windows的. 后来由于多次搬迁环境,弄丢了,遗憾不已啊. 于是趁着这两年还在编程的道路上,趁热再次编写了一次,同时也扩展了功能,更接近windows的扫雷. 此次重写是用Javaswing实现的(eclipse开发),考虑到各位看客可能大部分是Android岗位,于是我着重注意了功能结构化的处理,使游戏核心算法与UI分离,使用回调交互,便于迁移到android环境. 本人对swing不是很熟练,一直以来用swing做项目的人很少,学习资料也少,所有的这些都是

15个提高编程技巧的JavaScript工具

原文地址:http://www.imooc.com/wenda/detail/243523 JavaScript脚本库是一个预先用JavaScript语言写好的库,它方便了我们开发基于JavaScript的应用程序,特别适合AJAX和其他一些以Web为中心的技术.JavaScript主要用于编写嵌入或者包含在HTML页面的函数,从而实现DOM之间的交互. 这篇文章收集了15个可用于提高编程技巧的JavaScript工具,助你轻松快速完成工作.下面的这些JavaScript工具能让你管理Javas

11个提高CSS技巧的小知识,你知道吗?

前端开发越来越侧重于效率和性能,使用LESS和SCSS资源的预处理器为我们前端CSS编写工作提供了很大的便利.但是也有很多简单的方法可以编写小巧快速的CSS代码,提高开发效率并解决许多常见的问题. 1.使用CSS reset 像normalize.css这样的CSS重置库非常受欢迎,它为您的站点样式提供了一个清爽的选择,能确保浏览器之间更好的一致性.而实际上并不是每个项目都需要这些库中包含的所有规则,我们可以通过一些简单的css规则就能规避浏览器之间的差异.请看下面的盒模型代码: 1 * { 2

Java练习(模拟扫雷游戏)

要为扫雷游戏布置地雷,扫雷游戏的扫雷面板可以用二维int数组表示.如某位置为地雷,则该位置用数字-1表示, 如该位置不是地雷,则暂时用数字0表示. 编写程序完成在该二维数组中随机布雷的操作,程序读入3个参数:布雷面板的行数(r),列数(c),布置的地雷个数(n), 且要满足0<n<r*c*0.75(即布置地雷的最大密度为75%),程序运行后将n个地雷随机地布置在r*c的二维数组,布置完成后进行扫雷游戏. import java.util.*; public class Minesweeper

洛谷P2670 扫雷游戏

题目描述 扫雷游戏是一款十分经典的单机小游戏.在n行m列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格).玩家翻开一个非地雷格时,该格将会出现一个数字--提示周围格子中有多少个是地雷格.游戏的目标是在不翻出任何地雷格的条件下,找出所有的非地雷格. 现在给出n行m列的雷区中的地雷分布,要求计算出每个非地雷格周围的地雷格数. 注:一个格子的周围格子包括其上.下.左.右.左上.右上.左下.右下八个方向上与之直接相邻的格子. 输入输出格式 输入格式: 输入文件第一行是用一个

c++ 控制台版 扫雷游戏

白天看了一天书看累了,晚上瞅见扫雷游戏,就自己琢磨着做一个呗.想了一会,也没看别人怎么做的,大概1个多小时完成了这个简单版本的扫雷游戏,由于没怎么学过c#,界面的事情可能迟几天再做,明天要回家啦,哈哈! 先说思路,其实挺简单的. (1) 随机生成10个雷,标记到二维数组里,然后计算八个方向的雷的总数记录下来,这是预处理阶段. (2)输入要翻开的位置的坐标,如果是数字直接显示,是空白的话,这里采用bfs即宽度优先搜索解决,搜到最外层是数字(仅一层)时结束,具体详见代码. // 扫雷程序 #incl

编写小游戏《贪头蛇》第三篇

源码下载地址:http://download.csdn.net/detail/oyangyufu/7492917 点击NEW GAME按钮,进入游戏主场景 代码: 游戏背景 layer = (CCLayer*)this->getChildren()->objectAtIndex(SnakeConstants::LAYER_BACKGROUND); layer->setTouchEnabled(false); //游戏背景 CCSize size = CCDirector::sharedD

扫雷游戏

扫雷游戏 发布时间: 2017年8月15日 22:17   最后更新: 2017年8月15日 22:21   时间限制: 1000ms   内存限制: 128M 描述 扫雷游戏是一款十分经典的单机小游戏.在n行m列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格).玩家翻开一个非地雷格时,该格将会出现一个数字--提示周围格子中有多少个是地雷格.游戏的目标是在不翻出任何地雷格的条件下,找出所有的非地雷格. 现在给出n行m列的雷区中的地雷分布,要求计算出每个非地雷格周围的