现在让我们使用所学的知识完成一个游戏程序。这里我们将不使用任何图形界面,而是制作一个简单的、控制台上运行的字符界面的游戏。
需要注意Windows的控制台程序和Linux的控制台程序需要使用各自不同的方法来实现诸如光标移动,颜色设置等操作,下面分别讲解。
清屏
在Windows下控制台窗口的控制是基于win32 api, 就是那些在cmd下可以执行的命令, 使用之前需要引入头文件windows.h。
例如Windows下清除屏幕:
system("cls");
而在Linux下是通过Shell命令,只需要引入stdlib.h即可,比如要实现清除屏幕:
system("clear);
休眠
Window的控制台中休眠,可以调用windows.h中的Sleep(),比如Sleep(1000)函数,这里1000为毫秒数,功能为延时1s后程序向下运行,下面是一个3秒倒计时程序:
for(int i = 3; i >= 1; i--) {
printf("%d\n", i);
Sleep(1000);
}
Linux的控制台休眠可以使用stdlib.h中的system()调用Shell命令sleep n, 这里n代表秒数。
Linux下程序需要这样改写:
for(int i = 3; i >= 1; i--) {
printf("%d\n", i);
system("sleep 1");
}
windows控制台窗口操作API
Windows.h中定义的用于控制台窗口操作的API函数如下:
- GetConsoleScreenBufferInfo 获取控制台窗口信息
- GetConsoleTitle 获取控制台窗口标题
- ScrollConsoleScreenBuffer 在缓冲区中移动数据块
- SetConsoleScreenBufferSize 更改指定缓冲区大小
- SetConsoleTitle 设置控制台窗口标题
- SetConsoleWindowInfo 设置控制台窗口信息
#include
SetConsoleTitle("hello world!"); // 设置
由于Linux中往往不安装图形界面的,因此一般也不需要控制控制台窗口,这里就不介绍了。
控制台初始化
#include
#include
using namespace std;
int main()
{
//设置控制台窗口标题
SetConsoleTitle("hello world!");
//获取控制台窗口信息;
//GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO *bInfo)
//第一个hConsoleOutput参数(标准控制句柄)通过GetStdHandle()函数返回值获得
//第二个参数CONSOLE_SCREEN_BUFFER_INFO 保存控制台信息结构体指针
/*数据成员如下:
{
COORD dwSize; // 缓冲区大小
COORD dwCursorPosition; //当前光标位置
WORD wAttributes; //字符属性
SMALL_RECT srWindow; //当前窗口显示的大小和位置
COORD dwMaximumWindowSize; //最大的窗口缓冲区大小
}
*/
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO bInfo;
GetConsoleScreenBufferInfo(hOutput, &bInfo);
cout << "窗口缓冲区大小:" << bInfo.dwSize.X << ", " << bInfo.dwSize.Y << endl;
cout << "窗口坐标位置:" << bInfo.srWindow.Left << ", " << bInfo.srWindow.Top
<< ", "<< bInfo.srWindow.Right << ", " << bInfo.srWindow.Bottom << endl;
//设置显示区域坐标
//SetConsoleWindowInfo(HANDLE, BOOL, SMALL_RECT *);
SMALL_RECT rc = {0,0, 79, 24}; // 坐标位置结构体初始化
SetConsoleWindowInfo(hOutput,true ,&rc);
cout << "窗口显示坐标位置:" << bInfo.srWindow.Left << ", " << bInfo.srWindow.Top
<< ", "<< bInfo.srWindow.Right << ", " << bInfo.srWindow.Bottom << endl;
//更改指定缓冲区大小
//SetConsoleScreenBufferSize(HANDLE hConsoleOutput, COORD dwSize)
//COORD为一个数据结构体
COORD dSiz = {80, 25};
SetConsoleScreenBufferSize(hOutput, dSiz);
cout << "改变后大小:" << dSiz.X << ", " << dSiz.Y << endl;
//获取控制台窗口标题
//GetConsoleTitle(LPTSTR lpConsoleTitle, DWORD nSize)
//lpConsoleTitle为指向一个缓冲区指针以接收包含标题的字符串;nSize由lpConsoleTitle指向的缓冲区大小
char cTitle[255];
GetConsoleTitleA(cTitle, 255);
cout << "窗口标题:" << cTitle << endl;
// 关闭标准输出设备句柄
CloseHandle(hOut);
return 0;
}
设置文本颜色和光标移动控制
#include
#include
using namespace std;
int main()
{
/*设置文本属性
BOOL SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttributes);//句柄, 文本属性*/
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
WORD wr1 = 0xfa;//定义颜色属性;第一位为背景色,第二位为前景色
SetConsoleTextAttribute(hOut, wr1);
cout << "hello world!" << endl;
WORD wr2 = FOREGROUND_RED | FOREGROUND_INTENSITY;//方法二用系统宏定义颜色属性
SetConsoleTextAttribute(hOut, wr2);
cout << "hello world!" << endl;
/*移动文本位置位置
BOOL ScrollConsoleScreenBuffer(HANDLE hConsoleOutput, CONST SMALL_RECT* lpScrollRectangle, CONST SMALL_RECT* lpClipRectangle,
COORD dwDestinationOrigin,CONST CHAR_INFO* lpFill);
// 句柄// 裁剪区域// 目标区域 // 新的位置// 填充字符*/
//输出文本
SetConsoleTextAttribute(hOut, 0x0f);
cout << "01010101010101010101010101010" << endl;
cout << "23232323232323232323232323232" << endl;
cout << "45454545454545454545454545454" << endl;
cout << "67676767676767676767676767676" << endl;
SMALL_RECT CutScr = {1, 2, 10, 4}; //裁剪区域与目标区域的集合行成剪切区域
SMALL_RECT PasScr = {7, 2, 11, 9}; //可以是NULL,即全区域
COORD pos = {1, 8}; //起点坐标,与裁剪区域长宽构成的区域再与目标区域的集合为粘贴区
//定义填充字符的各个参数及属性
SetConsoleTextAttribute(hOut, 0x1);
CONSOLE_SCREEN_BUFFER_INFO Intsrc;
GetConsoleScreenBufferInfo(hOut, &Intsrc);
CHAR_INFO chFill = {'A', Intsrc.wAttributes}; //定义剪切区域填充字符
ScrollConsoleScreenBuffer(hOut, &CutScr, &PasScr, pos, &chFill); //移动文本
CloseHandle(hOut); // 关闭标准输出设备句柄
return 0;
}
WORD文本属性预定义宏:(可以直接用16进制表示,WORD w = 0xf0;前一位表示背景色,后一位代表前景色)
FOREGROUND_BLUE 蓝色
FOREGROUND_GREEN 绿色
FOREGROUND_RED 红色
FOREGROUND_INTENSITY 加强
BACKGROUND_BLUE 蓝色背景
BACKGROUND_GREEN 绿色背景
BACKGROUND_RED 红色背景
BACKGROUND_INTENSITY 背景色加强
COMMON_LVB_REVERSE_VIDEO 反色
当前文本属性信息可通过调用函数
GetConsoleScreenBufferInfo后,在CONSOLESCREEN BUFFER_INFO结构成员wAttributes中得到。
在指定位置处写属性
BOOL WriteConsoleOutputAttribute(HANDLE hConsoleOutput, CONST WORD *lpAttribute, DWORD nLength,
COORD dwWriteCoord, LPDWORD lpNumberOfAttrsWritten);
//句柄, 属性, 个数, 起始位置, 已写个数*/
填充指定数据的字符
BOOL FillConsoleOutputCharacter(HANDLE hConsoleOutput, TCHAR cCharacter,DWORD nLength,
COORD dwWriteCoord, LPDWORD lpNumberOfCharsWritten);
// 句柄, 字符, 字符个数, 起始位置, 已写个数*/
在当前光标位置处插入指定数量的字符
BOOL WriteConsole(HANDLE hConsoleOutput, CONST VOID *lpBuffer, DWORD nNumberOfCharsToWrite,
LPDWORD lpNumberOfCharsWritten,LPVOID lpReserved);
//句柄, 字符串, 字符个数, 已写个数, 保留*/
向指定区域写带属性的字符
BOOL WriteConsoleOutput(HANDLE hConsoleOutput, CONST CHAR_INFO *lpBuffer, COORD dwBufferSize,
COORD dwBufferCoord,PSMALL_RECT lpWriteRegion );
// 句柄 // 字符数据区// 数据区大小// 起始坐标// 要写的区域*/
在指定位置处插入指定数量的字符
BOOL WriteConsoleOutputCharacter(HANDLE hConsoleOutput, LPCTSTR lpCharacter, DWORD nLength,
COORD dwWriteCoord, LPDWORD lpNumberOfCharsWritten);
// 句柄// 字符串// 字符个数// 起始位置// 已写个数*/
填充字符属性
BOOL FillConsoleOutputAttribute(HANDLE hConsoleOutput, WORD wAttribute,DWORD nLength,
COORD dwWriteCoord, LPDWORD lpNumberOfAttrsWritten);
//句柄, 文本属性, 个数, 开始位置, 返回填充的个数*/
设置代码页,代码页是字符集编码的别名,也有人称"内码表"。
SetConsoleOutputCP(437);
//如(简体中文) 设置成936
光标操作控制
#include
#include
using namespace std;
int main()
{
cout << "hello world!" << endl;
//设置光标位置
//SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD dwCursorPosition);
//设置光标信息
//BOOL SetConsoleCursorInfo(HANDLE hConsoleOutput, PCONST PCONSOLE_CURSOR_INFO lpConsoleCursorInfo);
//获取光标信息
//BOOL GetConsoleCursorInfo(HANDLE hConsoleOutput, PCONSOLE_CURSOR_INFO lpConsoleCursorInfo);
//参数1:句柄;参数2:CONSOLE_CURSOR_INFO结构体{DWORD dwSize;(光标大小取值1-100)BOOL bVisible;(是否可见)}
Sleep(2000);//延时函数
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
COORD w = {0, 0};
SetConsoleCursorPosition(hOut, w);
CONSOLE_CURSOR_INFO cursorInfo = {1, FALSE};
Sleep(2000);//延时函数
SetConsoleCursorInfo(hOut, &cursorInfo);
CloseHandle(hOut); // 关闭标准输出设备句柄
return 0;
}
键盘操作控制
#include
#include
#include
using namespace std;
HANDLE hOut;
//清除函数
void cle(COORD ClPos)
{
SetConsoleCursorPosition(hOut, ClPos);
cout << " " << endl;
}
//打印函数
void prin(COORD PrPos)
{
SetConsoleCursorPosition(hOut, PrPos);
cout << "hello world!" << endl;
}
//移动函数
void Move(COORD *MoPos, int key)
{
switch(key)
{
case 72: MoPos->Y--;break;
case 75: MoPos->X--;break;
case 77: MoPos->X++;break;
case 80: MoPos->Y++;break;
default: break;
}
}
int main()
{
cout << "用方向键移动下行输出内容" << endl;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);//取句柄
COORD CrPos = {0, 1};//保存光标信息
prin(CrPos);//打印
//等待键按下
while(1)
{
if(kbhit())
{
cle(CrPos);//清除原有输出
Move(&CrPos, getch());
prin(CrPos);
}
}
return 0;
}
可以用方向键任意移动hello world!
注意区分:
getch()、 getche()、getcher()函数
Linux下控制台操作替代办法
字体颜色
在 Linux 下若想输出 类似与 Windows 下的多颜色字体如何做呢?本文就来介绍实现的方法。
首先,来看下 在Linux 下颜色的表示
注意自定义的配置需在写在\033[和m之间
\033[22;30m - black
\033[22;31m - red
\033[22;32m - green
\033[22;33m - brown
\033[22;34m - blue
\033[22;35m - magenta
\033[22;36m - cyan
\033[22;37m - gray
\033[01;30m - dark gray
\033[01;31m - light red
\033[01;32m - light green
\033[01;33m - yellow
\033[01;34m - light blue
\033[01;35m - light magenta
\033[01;36m - light cyan
\033[01;37m - white
可以看出都是使用的转义字体来实现的。
比如:
Linux 终端输入:echo -e "\033[35;1m Shocking \033[0m"
C代码: printf("\033[34mThis is blue.\033[0m\n");
是不是出错不同的颜色了,记得最后要 "\033[0m" 关闭所有属性,这样又回到了系统默认的颜色了。
一个定义宏的办法如下:([0;是用来清除之前或之后的设置)
#define COLOR(msg, code) "\033[0;1;" #code "m" msg "\033[0m"
#define RED(msg) COLOR(msg, 31)
#define GREEN(msg) COLOR(msg, 32)
#define YELLOW(msg) COLOR(msg, 33)
#define BLUE(msg) COLOR(msg, 34)
光标控制
Linux 下终端 C 语言控制光标的技巧
// 清除屏幕
#define CLEAR() printf("\033[2J")
// 上移光标
#define MOVEUP(x) printf("\033[%dA", (x))
// 下移光标
#define MOVEDOWN(x) printf("\033[%dB", (x))
// 左移光标
#define MOVELEFT(y) printf("\033[%dD", (y))
// 右移光标
#define MOVERIGHT(y) printf("\033[%dC",(y))
// 定位光标
#define MOVETO(x,y) printf("\033[%d;%dH", (x), (y))
// 光标复位
#define RESET_CURSOR() printf("\033[H")
// 隐藏光标
#define HIDE_CURSOR() printf("\033[?25l")
// 显示光标
#define SHOW_CURSOR() printf("\033[?25h")
//反显
#define HIGHT_LIGHT() printf("\033[7m")
#define UN_HIGHT_LIGHT() printf("\033[27m")
模拟按键监听
windows平台接受字符并不回显可以调用<conio.h>库的getch函数, 但是这是依赖于windows的BIOS。
linux下可以使用命令stty -echo来关闭回显,当接受字符后,使用stty echo命令恢复设置即可.
linux下如何向windows的getch或者getche一样,不需要回车就可以接受字符?
这里可以使用命令raw开启一次接受一个字符的模式,接受字符后,再次使用cooked(回车之后一锅端模式)模式即可
#include
#include
#define ENTER_KEY 13
#define ESCAPE_KEY 27
char getch()
{
char ch;
system("stty -echo raw");
ch = getchar();
system("stty echo cooked");
return ch;
}
int main(int argc, char *argv[])
{
char ch;
system("clear\n");
while (1)
{
printf("Press Enter to continue, ESC to exit\n");
ch = getch();
if (ch == ESCAPE_KEY)
break;
if (ch == ENTER_KEY)
{
printf("Input a character\n");
ch = getch();
printf("=%c\n", ch);
}
}
return 0;
}
Hangman 猜词游戏
我们将从构建经典的猜词游戏,需要有两个玩家,一个玩家负责出题,另一个玩家负责猜。如果你从来没有听过这个游戏,
规则
-
在给定的选词范围内,比如"动物"或""国家",玩家一选择一个秘密单词并根据字母数画相应数量的短横,即“”,短横用来表示单词中的每个字母。比如秘密单词是"cat"则可以用" "表示。
-
游戏开始后,玩家二猜一个字母,玩家一判断这个字符是否在秘密单词中出现,如果出现则将对应位置的短横替换成正确的字符。比如猜的是
a,则表示成_ _ a。 -
如果玩家二猜错了字母,玩家一会逐步从上到下的完成一副小人上吊的图像,如果玩家2在完整的图像被画出来之前猜出来秘密单词的所有字母他就赢了,否则当完整的图像画出来之后,小人的脚离开地面就预示着玩家二输了。
第1次猜错
________第2次猜错
________ | |第3次猜错
________ | | | 0第4次猜错
________ | | | 0 | /|\第5次猜错
________ | | | 0 | /|\ | / \第6次猜错,小人脚离地,玩家二输了,游戏结束
________ | | | 0 | /|\ | / \ | -
如果玩家在小人脚离地之前猜出了所有字母,例如
c a t.则玩家二获胜。
注意:以下代码是在linux环境中运行:
游戏的初始化
// 初始化词库
char words[5][100] = {"china", "japan", "india", "korea", "syria"};
// 初始化词库的数量
int words_num = sizeof(words) / sizeof(words[0]);
// 初始化要猜的词
char secret_word[100] = "";
// 初始化要猜的词的字母数
int secret_word_len = 0;
// 显示的题板, 未猜出的字母用_代替
// 打印的上吊小人的画像
char stages[][15] = {" ________ ", // stage 1
" | | ", // stage 2
" | 0 ", // stage 3
" | /|\\ ", // stage 4
" | / \\ ", // stage 5
" |________ "}; // stage 6
int stage_num = sizeof(stages) / sizeof(stages[0]);
// 初始化猜错的次数
int wrong_guess = 0;
// 初始化是否赢得游戏
bool win = false;
// FUNCTION PROTOTYPES
void PrintBoard(int stage);
void StartGame();
char getch();
int main(int argc, char *argv[]) {
// init
srand((unsigned int)time(NULL));
int random_index = rand() % words_num;
strcpy(secret_word, words[random_index]);
secret_word_len = strlen(secret_word);
StartGame();
return 0;
}
玩法部分
游戏的玩法部分封装到了StartGame函数中,玩法部分通常是个循环
// GAME LOOP
while (wrong_guess < stage_num) {
...
}
判断是否猜中
char *find_pos = strstr(remaining_letters, guess);
if (find_pos != NULL) {
int pos = find_pos - remaining_letters;
remaining_letters[pos] = '$';
letter_board[pos] = guess[0];
}
else {
wrong_guess++;
PrintBoard(wrong_guess);
system("sleep 1");
}
如果输了就打印上吊图像
void PrintBoard(int stage) {
for (int i = 0; i < stage_num; i++) {
if (i < stage)
printf("%s\n", stages[i]);
else
puts("");
}
}
练习
你也可以试着完成一个属于你自己的控制台小游戏, 多花些时间做些类似的练习可以帮助你提高编程能力,同时也能让你对编程保持兴趣。
我还记得在DOS时期(95年左右)我第一个使用QuickBasic语言制作的猜飞机头的游戏,虽然是简单的游戏但是最终被我制作的越来越复杂,比如增加关卡,添加声效,游戏记录的保存等,可惜是存储在软盘中现在已经遗失了。
-
三子棋或者五子棋游戏
-
根据心情选歌
-
根据品牌打印广告语。
寻求帮助
如果你写代码时卡壳,大多数的问题通过百度都可以解决,不行的话就CSDN或者知乎。
如果英文水平不赖就科学上网去谷歌搜索,这里要强调的是浏览Stack Exchange这个神奇的网站会很有帮助。有两个版块非常有用。
一个是Stack Overflow,程序员应该没有不知道它的,上面几乎涵盖所有编程可能遇到的问题。即便你的问题是独一无二其他人都没有经历过的,这是一个QA类型的网站,你可以在上面发布问题,会有大佬帮你解答的。
https://www.codementor.io/ama/0926528143/stackoverflow-python-moderator-martijn-pieters-zopatista
另外一个有用的板块叫Code Review, 你只要发布你的代码就会有人给你"指手画脚", 告诉你哪些地方做的好,哪些地方做的不好,如何改进之类的建议。
Views: 18
