在网络上闲逛时看到一篇文章介绍了 Linux 的几个控制台小游戏,感觉里面有一个叫 Greed 的概念比较有意思,就想写一个试一下。Greed 在 Ubuntu 的仓库里有,可以直接用 apt 安装,但我想先有个大致概念的情况下实现一个,再看看别人的思路和自己有什么异同,所以您如果玩过 Greed 这个游戏的话,可能会发现两者并没有多少相似。
Description
可以在 https://gitlab.com/esr/greed 获取到 Eric S. Raymond 编写的开源版本的 Greed 源代码。
This is a curses-based clone of the DOS free-ware game Greed. The goal of this game is to try to eat as much as possible of the board before munching yourself into a corner.
下图是我实现的版本,使用 ./greed
运行单人游戏,WASD 控制移动。
2020-10-12:这里的图片使用了新浪微博图床,现在已经无法访问了
使用 ./greed -m
或 ./greed m
运行双人游戏,蓝方(P1)WASD 控制移动,红方(P2)IJKL 控制移动
2020-10-12:这里的图片使用了新浪微博图床,现在已经无法访问了
除此之外我加入了一点 TRON 的元素,当两名玩家进行游戏的时候,玩家可以对方围困在一个较小的范围内以增大自己获胜的机会,如图所示红方(P2)已被围困,无法移动
我没有认真研究过配色,所以如果你的终端没有配置过颜色方案的话应该是比较伤害视力的,请自行修改源码(刷新屏幕的两个函数在输出前会设置字符颜色),另外如果觉得地图太小,也请自行修改源码(宏定义 FieldHeight
和 FieldWidth
),但确保加上计分栏后内容不会超出窗口大小(否则 Windows 下滚屏玩,Linux 下定位失败字符乱飞)
编译的时候需要开启 C99 支持,因为我把计数器之类的变量都放到代码中间去了。
计划特性与实现
- [x] 下载一份源码兼容 Windows 和 Linux 平台的编译
- [x] 直接运行程序以单人模式进行游戏
- [x] 使用运行参数指定双人游戏,游戏难度等内容(使用参数 m 进行多人游戏,无难度划分)
- [x] 多人游戏下运行游戏直到所有玩家无法移动,通过总得分判定胜负
- [x] 退出或再来一局选项(有退出,在 shell 里重新运行程序更方便)
开发工具
- Windows 8.1 + Visual Studio 2013 Community Edition
- CentOS 7 Minimal Install + gcc 4.8.5 + clang 3.4.2 + PuTTY
本文中我将简单提一下两个平台(Linux 和 Windows)的同一功能的不同实现方法,至于游戏逻辑,是个人应该都能想出来,只是总有人会想出更巧妙更高明的方法(参见所有 8 位机游戏),就只简单说一说。
非标准库函数
getch()
记得上 C 语言课要做文本界面的时候都会有这种 等待输入 - 立即处理按键输入 这种需求
Windows/DOS 平台的大部分 C 语言环境都支持 conio.h
,其中就有 _getch()
满足这一要求
在 Linux 平台下通过修改终端的模式改变缓冲区行为解决,具体见 Capture characters from standard input without waiting for enter to be pressed ,但写了几个测试例子后发现 Falcon Momot & anon 的方法似乎会阻塞 printf()
的使用,轮流调用 getch()
和 printf()
应该会复现这个问题,不过也有可能是我其他环境配置的问题, linux 系统下的 getch 和 getche 函数的实现 一文采用相同的思路但没有出现这个问题。
char _getch() {
char ch;
system("stty -echo");
system("stty -icanon");
ch = getchar();
system("stty icanon");
system("stty echo");
return ch;
}
涉及到跨平台兼容性时应该先考虑使用 ncurses 库,但我的 CentOS 最小安装默认是没有这个库的,需要自行安装,此处只是提供一种思路,我并没有测试
引入一些非默认库的时候记得在编译时链接
实现 gotoxy()
gotoxy()
用于将光标定位到终端的指定位置处,此后的输出将覆盖从光标处开始的内容。使用这个函数可以达到部分刷新的效果(如果玩家移动一次就要重新输出整个屏幕内容的话,每次耗时大约是半秒,估计没有人会耐心地玩下去)。
Windows 下用 Windows API SetConsoleCursorPosition
实现
void gotoxy(int x, int y) {
COORD pos = { x, y };
HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(output, pos);
}
在 Linux 上使用万能的 ESC 控制字符(Terminal Control Escape Sequences)
需要注意的是 Windows 控制台(包括 CMD 和 PowerShell)的坐标从 (0,0)
开始,而 Linux 系统(至少在 PuTTY 和 CentOS 的文本模式下)的坐标从 (1,1)
开始,为了不让自己在写程序的时候脑子糊起来,统一从 (0,0)
开始。
void gotoxy(int x, int y) {
printf("\x1b[%d;%df", y +1, x + 1);
}
字符特效
有了上述内容,一个基本款游戏就可以运行了,接下来给字符加上特效,Linux 只要在 ANSI/VT100 Terminal Control Escape Sequences 里翻一翻总能找到各种简洁优雅的高级方法,Windows 还是得借助各种控制台函数 how to get background color back to previous color after use of std handle
原本打算在多人时用 Blink 区分当前回合的玩家,但效果不明显
零碎
获取命令行参数
如果参数比较复杂的话还是应该用 getopt()
,此处比较简单,直接读取判断了
更好用一些的随机数
利用系统时间作为生成随机数的种子
#include <time.h>
#include <stdlib.h>
srand(time(NULL));
int rand = rand();
源码见 GitHub Gist
2020-10-12:源码也丢了