Linux 和 Windows 下的「仿」Greed 游戏

2020-10-12, 星期一, 00:00

MAKE

在网络上闲逛时看到一篇文章介绍了 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)已被围困,无法移动

我没有认真研究过配色,所以如果你的终端没有配置过颜色方案的话应该是比较伤害视力的,请自行修改源码(刷新屏幕的两个函数在输出前会设置字符颜色),另外如果觉得地图太小,也请自行修改源码(宏定义 FieldHeightFieldWidth),但确保加上计分栏后内容不会超出窗口大小(否则 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:源码也丢了