前言:
想起来做这个是因为那时候某天知道了原来黑框框里面的光标是可以控制的,而且又经常听人说起这个,就锻炼一下好了。
之前就完成了那1.0的版本,现在想放上来分享却发现有蛮多问题的,而且最重要的是没什么注释【果然那时候太年轻】!现在看了也是被那时候的自己逗笑了,就修改了一些小bug,增加了算是详尽而清楚的注释,嗯,MSDN上面对各种函数的解释很详细的【又锻炼一下英语】,顺便让开头和结尾的展示“动”了起来,就当作1.5的版本好了。
这个只是给出了一个实现的思路,其中肯定也有很多不合理的地方和可优化之处,希望能供大家参考和交流。
过程:
期间也是遇到了蛮多困惑的。
1.最先的是怎么知道按了方向键,左查右找,说法有好几个版本呢,就想看能不能自己测试一下自己的好了,再查再找,好了,感谢写了测试方向键的人;
2.再比如说怎么消除窗口中一行的缓冲,因为不消除就一直在哪,视觉效果不好,翻查了一下资料,就写了delLine()这个来做这个事情了;
3.设定颜色时,在cmd里面help color知道了颜色的参数,但是通过数字0-9来设定的太暗了,发现有更亮的,比如0A,在setColor()里面用它却说类型不对,于是上MSDN,发现还可以用宏,就想通过如'BACKGROUND_INTENSITY | BACKGROUND_RED '之类来完成,就想怎么去代替那个宏,觉得每次写一长串好麻烦。然后换了各种类型的参数类型和不定长参数什么的,发现还是不行,后来一想,万一它支持数字10呢,A不就是10么?!一测,成了;
4.还有一些判断状态的顺序,嗯啊,这些要先想好再下手,不然左改右改很麻烦呢;
5.别的困惑不怎么记得了。。。
代码:
下面分别给出LittleMines【好弱的名字】,测试颜色,测试方向键的代码。【反映说有行号不好复制,那取消好了】
/*********************************
* c语言命令行+方向键简易版扫雷
* Author:AnnsShadoW
* Version:1.5
* Time:2015-11-29
********************************/
/********************************
* 运行环境:Windows10-64bit
* 编译环境:Codeblocks-13.12
********************************/
//用到的都导进去吧
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <windows.h>
//定义各种判断状态的ASCII码
//MINE是定义翻开格子中的‘*'号
#define MINE 42
#define ESC 27
#define ENTER 13
#define SPACE 32
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
//定义类型状态,方便后续判断
#define bool int
#define true 1
#define false 0
#define ROW 10
#define COLUMN 10
#define ALL_MINES 15
//当前位置的结构体
typedef struct currentPosition_struct
{
int x;
int y;
} currentPosition;
//每一个小格的结构体
typedef struct blockCondition_struct
{
//是否被覆盖了
bool beCovered;
//以它为中心周围的雷数
int minesNum;
} blockCondition;
//光标的位置数组
currentPosition cursorPos[ROW][COLUMN];
//雷区地图的数组
blockCondition minesMap[ROW][COLUMN];
//剩下的格子数
int leftBlocksNum = ROW * COLUMN;
//光标在光标位置、雷区地图中的下标
int index_x = 0, index_y = 0;
//设置窗口前后背景色
void setColor(unsigned short color);
//开头的欢迎“动画”
void welcomeToMyGame();
//游戏地图初始化
void gameInitailize();
//以某格子为中心计算惊天雷数量
void countMines();
//获取键盘的输入
void keyBoardInput();
//指定光标的位置
void setCurPos(int y, int x);
//移动光标的位置
void moveCursor(int y, int x);
//检测每一步的结果
bool checkResult(int y, int x);
//输出游戏界面
void printMap();
//游戏退出后的“动画”
void gameOver(char *str);
//删除窗口中一行的缓冲
void delLine(int y);
int main()
{
setColor(10);
system("cls");
welcomeToMyGame();
gameInitailize();
countMines();
printMap();
for(;;)
{
setCurPos(cursorPos[index_y][index_x].y, cursorPos[index_y][index_x].x);
keyBoardInput();
}
return EXIT_SUCCESS;
}
void setColor(unsigned short color)
{
HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE);
//对设置之后的输出有效
SetConsoleTextAttribute(hCon, color);
};
void welcomeToMyGame()
{
int i = 0;
char introductions0[] = "LittleMines";
char introductions1[] = "--";
char introductions2[] = "Version 1.5";
char introductions3[] = "Author:AnnsShadow,thank you ╮( ̄▽ ̄)╭";
//控制台窗口默认大小是80*25,所以能达到最大的位置是[79,24]
for(i = 0; i <= 5; ++i)
{
//每次输出之前都清屏,就会有看起来是动的效果
system("cls");
//纵坐标不断加,形成向下效果
setCurPos(i, (80 - strlen(introductions0)) / 2);
printf("%s", introductions0);
//缓冲一下,太快了看不到呢
Sleep(50);
}
//为了对称,从边边78开始到中间39好了
for(i = 78; i >= 39; --i)
{
//上面用了5行了,大于它吧
setCurPos(7, i);
printf("%s", introductions1);
setCurPos(7, 78 - i);
printf("%s", introductions1);
Sleep(40);
}
//从左边一步步进入屏幕中间
for(i = 0; i <= (80 - strlen(introductions2)) / 2; ++i)
{
//要删除这一行缓冲的原因:
//上一次循环的输出会影响到下一次,如输出VVVVVVVVVVersion1.0
//换成中文就不会,中文要两个字节才能显示完整呀
delLine(9);
//这里就会有闪闪发亮的效果哦
Sleep(10);
setCurPos(9, i);
printf("%s", introductions2);
Sleep(50);
}
//从底部进入
for(i = 24; i >= 12; --i)
{
setCurPos(i, (80 - strlen(introductions3)) / 2);
printf("%s", introductions3);
Sleep(20);
//删除上一次的缓冲,不加1的话最后一行就会残留,其它都不见了
delLine(i + 1);
Sleep(50);
}
Sleep(500);
char help0[] = "动啊:←↑↓→╮(╯▽╰)╭";
char help1[] = "点击啊:Space / Enter (ΘェΘ)";
char help2[] = "不玩啦:Esc (>﹏<)";
char help3[] = "<<愿你玩的开心 _(:з」∠)_>>";
setCurPos(14, (80 - strlen(help0)) / 2);
setColor(14);
printf("%s", help0);
setCurPos(15, (80 - strlen(help1)) / 2);
printf("%s", help1);
setCurPos(16, (80 - strlen(help2)) / 2);
printf("%s", help2);
setCurPos(17, (80 - strlen(help3)) / 2);
setColor(15);
printf("%s", help3);
getch();
}
void gameInitailize()
{
int i = 0, j = 0;
int allMines = ALL_MINES;
//设置随机值
srand((unsigned int)time(NULL));
//雷区地图初始化
for(i = 0; i < ROW; ++i)
{
for(j = 0; j < COLUMN; ++j)
{
minesMap[i][j].beCovered = true;
minesMap[i][j].minesNum = 0;
}
}
//放置惊天雷!
while(allMines)
{
i = rand() % ROW;
j = rand() % COLUMN;
if(minesMap[i][j].minesNum == 0)
{
//这个‘-1'就作为判断惊天雷的依据了
minesMap[i][j].minesNum = -1;
--allMines;
}
}
//光标位置初始化
for(i = 0; i < ROW; ++i)
{
for(j = 0; j < COLUMN; ++j)
{
cursorPos[i][j].x = j * 6 + 3;
cursorPos[i][j].y = i * 2 + 1;
}
}
}
void countMines()
{
int i = 0, j = 0, m = 0, n = 0;
//以格子为中心周围的雷数
int minesNum = 0;
for(i = 0; i < ROW; ++i)
{
for(j = 0; j < COLUMN; ++j)
{
//遇到惊天雷就放弃统计吧
if(minesMap[i][j].minesNum == -1)
continue;
minesNum = 0;
//九宫格嘛,那3次好了
for(m = -1; m <= 1; ++m)
{
//行溢出了没,不能算没有的哦
if(i + m < 0 || i + m >= ROW)
{
continue;
}
for(n = -1; n <= 1; ++n)
{
//这次就是看列溢出了没
if(j + n < 0 || j + n >= COLUMN)
{
continue;
}
//周边有惊天雷赶紧加起来
if(minesMap[i + m][j + n].minesNum == -1)
{
++minesNum;
}
}
}
minesMap[i][j].minesNum = minesNum;
}
}
}
void keyBoardInput()
{
bool lose;
int key1 = getch();
/*****************************
测试之后才知道方向键两个字节
第一个字节ASCII 0x00e0 224
第二个字节分别是:
上:0x0048 72
下:0x0050 80
左:0x012b 75
右:0x012d 77
*****************************/
if(key1 == 224)
{
int key2 = getch();
switch(key2)
{
case UP:
moveCursor(index_y - 1, index_x);
break;
case DOWN:
moveCursor(index_y + 1, index_x);
break;
case LEFT:
moveCursor(index_y, index_x - 1);
break;
case RIGHT:
moveCursor(index_y, index_x + 1);
break;
default:
break;
}
}
else
{
switch(key1)
{
case ENTER:
case SPACE:

