现在让我们使用所学的知识完成一个游戏程序。这里我们将不使用任何图形界面,而是制作一个简单的、控制台上运行的字符界面的游戏。
需要注意Windows的控制台程序和Linux的控制台程序需要使用各自不同的方法来实现诸如光标移动,颜色设置等操作,下面分别讲解。
清屏
在Windows下控制台窗口的控制是基于win32 api, 就是那些在cmd下可以执行的命令, 使用之前需要引入头文件windows.h
。
例如Windows下清除屏幕:
1 |
system("cls"); |
而在Linux下是通过Shell命令,只需要引入stdlib.h
即可,比如要实现清除屏幕:
1 |
system("clear); |
休眠
Window的控制台中休眠,可以调用windows.h
中的Sleep()
,比如Sleep(1000)
函数,这里1000
为毫秒数,功能为延时1s
后程序向下运行,下面是一个3秒倒计时程序:
1 2 3 4 |
for(int i = 3; i >= 1; i--) { printf("%d\n", i); Sleep(1000); } |
Linux的控制台休眠可以使用stdlib.h
中的system()
调用Shell命令sleep n
, 这里n代表秒数。
Linux下程序需要这样改写:
1 2 3 4 |
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 设置控制台窗口信息
1 2 |
#include <windows.h> SetConsoleTitle("hello world!"); // 设置 |
由于Linux中往往不安装图形界面的,因此一般也不需要控制控制台窗口,这里就不介绍了。
控制台初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
#include <iostream> #include <windows.h> 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; } |
设置文本颜色和光标移动控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#include <iostream> #include <windows.h> 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;前一位表示背景色,后一位代表前景色)
1 2 3 4 5 6 7 8 9 |
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中得到。
在指定位置处写属性
1 2 3 |
BOOL WriteConsoleOutputAttribute(HANDLE hConsoleOutput, CONST WORD *lpAttribute, DWORD nLength, COORD dwWriteCoord, LPDWORD lpNumberOfAttrsWritten); //句柄, 属性, 个数, 起始位置, 已写个数*/ |
填充指定数据的字符
1 2 3 |
BOOL FillConsoleOutputCharacter(HANDLE hConsoleOutput, TCHAR cCharacter,DWORD nLength, COORD dwWriteCoord, LPDWORD lpNumberOfCharsWritten); // 句柄, 字符, 字符个数, 起始位置, 已写个数*/ |
在当前光标位置处插入指定数量的字符
1 2 3 |
BOOL WriteConsole(HANDLE hConsoleOutput, CONST VOID *lpBuffer, DWORD nNumberOfCharsToWrite, LPDWORD lpNumberOfCharsWritten,LPVOID lpReserved); //句柄, 字符串, 字符个数, 已写个数, 保留*/ |
向指定区域写带属性的字符
1 2 3 |
BOOL WriteConsoleOutput(HANDLE hConsoleOutput, CONST CHAR_INFO *lpBuffer, COORD dwBufferSize, COORD dwBufferCoord,PSMALL_RECT lpWriteRegion ); // 句柄 // 字符数据区// 数据区大小// 起始坐标// 要写的区域*/ |
在指定位置处插入指定数量的字符
1 2 3 |
BOOL WriteConsoleOutputCharacter(HANDLE hConsoleOutput, LPCTSTR lpCharacter, DWORD nLength, COORD dwWriteCoord, LPDWORD lpNumberOfCharsWritten); // 句柄// 字符串// 字符个数// 起始位置// 已写个数*/ |
填充字符属性
1 2 3 |
BOOL FillConsoleOutputAttribute(HANDLE hConsoleOutput, WORD wAttribute,DWORD nLength, COORD dwWriteCoord, LPDWORD lpNumberOfAttrsWritten); //句柄, 文本属性, 个数, 开始位置, 返回填充的个数*/ |
设置代码页,代码页是字符集编码的别名,也有人称"内码表"。
1 2 |
SetConsoleOutputCP(437); //如(简体中文) 设置成936 |
光标操作控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <iostream> #include <windows.h> 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; } |
键盘操作控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
#include <iostream> #include <windows.h> #include <conio.h> 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之间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
\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;是用来清除之前或之后的设置)
1 2 3 4 5 |
#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 语言控制光标的技巧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// 清除屏幕 #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
(回车之后一锅端模式)模式即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <stdio.h> #include <stdlib.h> #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次猜错
1________第2次猜错
12________| |第3次猜错
123________| || 0第4次猜错
1234________| || 0| /|\第5次猜错
12345________| || 0| /|\| / \第6次猜错,小人脚离地,玩家二输了,游戏结束
123456________| || 0| /|\| / \| -
如果玩家在小人脚离地之前猜出了所有字母,例如
c a t
.则玩家二获胜。
注意:以下代码是在linux环境中运行:
游戏的初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// 初始化词库 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函数中,玩法部分通常是个循环
1 2 3 4 5 |
// GAME LOOP while (wrong_guess < stage_num) { ... } |
判断是否猜中
1 2 3 4 5 6 7 8 9 10 11 |
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"); } |
如果输了就打印上吊图像
1 2 3 4 5 6 7 8 9 10 |
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