C語言開發(fā)簡易版掃雷小游戲
前言:
想起來做這個(gè)是因?yàn)槟菚r(shí)候某天知道了原來黑框框里面的光標(biāo)是可以控制的,而且又經(jīng)常聽人說起這個(gè),就鍛煉一下好了。
之前就完成了那1.0的版本,現(xiàn)在想放上來分享卻發(fā)現(xiàn)有蠻多問題的,而且最重要的是沒什么注釋【果然那時(shí)候太年輕】!現(xiàn)在看了也是被那時(shí)候的自己逗笑了,就修改了一些小bug,增加了算是詳盡而清楚的注釋,嗯,MSDN上面對(duì)各種函數(shù)的解釋很詳細(xì)的【又鍛煉一下英語】,順便讓開頭和結(jié)尾的展示“動(dòng)”了起來,就當(dāng)作1.5的版本好了。
這個(gè)只是給出了一個(gè)實(shí)現(xiàn)的思路,其中肯定也有很多不合理的地方和可優(yōu)化之處,希望能供大家參考和交流。
過程:
期間也是遇到了蠻多困惑的。
1.最先的是怎么知道按了方向鍵,左查右找,說法有好幾個(gè)版本呢,就想看能不能自己測試一下自己的好了,再查再找,好了,感謝寫了測試方向鍵的人;
2.再比如說怎么消除窗口中一行的緩沖,因?yàn)椴幌鸵恢痹谀?,視覺效果不好,翻查了一下資料,就寫了delLine()這個(gè)來做這個(gè)事情了;
3.設(shè)定顏色時(shí),在cmd里面help color知道了顏色的參數(shù),但是通過數(shù)字0-9來設(shè)定的太暗了,發(fā)現(xiàn)有更亮的,比如0A,在setColor()里面用它卻說類型不對(duì),于是上MSDN,發(fā)現(xiàn)還可以用宏,就想通過如'BACKGROUND_INTENSITY | BACKGROUND_RED '之類來完成,就想怎么去代替那個(gè)宏,覺得每次寫一長串好麻煩。然后換了各種類型的參數(shù)類型和不定長參數(shù)什么的,發(fā)現(xiàn)還是不行,后來一想,萬一它支持?jǐn)?shù)字10呢,A不就是10么?!一測,成了;
4.還有一些判斷狀態(tài)的順序,嗯啊,這些要先想好再下手,不然左改右改很麻煩呢;
5.別的困惑不怎么記得了。。。
代碼:
下面分別給出LittleMines【好弱的名字】,測試顏色,測試方向鍵的代碼。【反映說有行號(hào)不好復(fù)制,那取消好了】
/*********************************
* c語言命令行+方向鍵簡易版掃雷
* Author:AnnsShadoW
* Version:1.5
* Time:2015-11-29
********************************/
/********************************
* 運(yùn)行環(huán)境:Windows10-64bit
* 編譯環(huán)境:Codeblocks-13.12
********************************/
//用到的都導(dǎo)進(jìn)去吧
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <windows.h>
//定義各種判斷狀態(tài)的ASCII碼
//MINE是定義翻開格子中的‘*'號(hào)
#define MINE 42
#define ESC 27
#define ENTER 13
#define SPACE 32
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
//定義類型狀態(tài),方便后續(xù)判斷
#define bool int
#define true 1
#define false 0
#define ROW 10
#define COLUMN 10
#define ALL_MINES 15
//當(dāng)前位置的結(jié)構(gòu)體
typedef struct currentPosition_struct
{
int x;
int y;
} currentPosition;
//每一個(gè)小格的結(jié)構(gòu)體
typedef struct blockCondition_struct
{
//是否被覆蓋了
bool beCovered;
//以它為中心周圍的雷數(shù)
int minesNum;
} blockCondition;
//光標(biāo)的位置數(shù)組
currentPosition cursorPos[ROW][COLUMN];
//雷區(qū)地圖的數(shù)組
blockCondition minesMap[ROW][COLUMN];
//剩下的格子數(shù)
int leftBlocksNum = ROW * COLUMN;
//光標(biāo)在光標(biāo)位置、雷區(qū)地圖中的下標(biāo)
int index_x = 0, index_y = 0;
//設(shè)置窗口前后背景色
void setColor(unsigned short color);
//開頭的歡迎“動(dòng)畫”
void welcomeToMyGame();
//游戲地圖初始化
void gameInitailize();
//以某格子為中心計(jì)算驚天雷數(shù)量
void countMines();
//獲取鍵盤的輸入
void keyBoardInput();
//指定光標(biāo)的位置
void setCurPos(int y, int x);
//移動(dòng)光標(biāo)的位置
void moveCursor(int y, int x);
//檢測每一步的結(jié)果
bool checkResult(int y, int x);
//輸出游戲界面
void printMap();
//游戲退出后的“動(dòng)畫”
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);
//對(duì)設(shè)置之后的輸出有效
SetConsoleTextAttribute(hCon, color);
};
void welcomeToMyGame()
{
int i = 0;
char introductions0[] = "LittleMines";
char introductions1[] = "--";
char introductions2[] = "Version 1.5";
char introductions3[] = "Author:AnnsShadow,thank you ╮( ̄▽ ̄)╭";
//控制臺(tái)窗口默認(rèn)大小是80*25,所以能達(dá)到最大的位置是[79,24]
for(i = 0; i <= 5; ++i)
{
//每次輸出之前都清屏,就會(huì)有看起來是動(dòng)的效果
system("cls");
//縱坐標(biāo)不斷加,形成向下效果
setCurPos(i, (80 - strlen(introductions0)) / 2);
printf("%s", introductions0);
//緩沖一下,太快了看不到呢
Sleep(50);
}
//為了對(duì)稱,從邊邊78開始到中間39好了
for(i = 78; i >= 39; --i)
{
//上面用了5行了,大于它吧
setCurPos(7, i);
printf("%s", introductions1);
setCurPos(7, 78 - i);
printf("%s", introductions1);
Sleep(40);
}
//從左邊一步步進(jìn)入屏幕中間
for(i = 0; i <= (80 - strlen(introductions2)) / 2; ++i)
{
//要?jiǎng)h除這一行緩沖的原因:
//上一次循環(huán)的輸出會(huì)影響到下一次,如輸出VVVVVVVVVVersion1.0
//換成中文就不會(huì),中文要兩個(gè)字節(jié)才能顯示完整呀
delLine(9);
//這里就會(huì)有閃閃發(fā)亮的效果哦
Sleep(10);
setCurPos(9, i);
printf("%s", introductions2);
Sleep(50);
}
//從底部進(jìn)入
for(i = 24; i >= 12; --i)
{
setCurPos(i, (80 - strlen(introductions3)) / 2);
printf("%s", introductions3);
Sleep(20);
//刪除上一次的緩沖,不加1的話最后一行就會(huì)殘留,其它都不見了
delLine(i + 1);
Sleep(50);
}
Sleep(500);
char help0[] = "動(dòng)啊:←↑↓→╮(╯▽╰)╭";
char help1[] = "點(diǎn)擊?。篠pace / 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;
//設(shè)置隨機(jī)值
srand((unsigned int)time(NULL));
//雷區(qū)地圖初始化
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)
{
//這個(gè)‘-1'就作為判斷驚天雷的依據(jù)了
minesMap[i][j].minesNum = -1;
--allMines;
}
}
//光標(biāo)位置初始化
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;
//以格子為中心周圍的雷數(shù)
int minesNum = 0;
for(i = 0; i < ROW; ++i)
{
for(j = 0; j < COLUMN; ++j)
{
//遇到驚天雷就放棄統(tǒng)計(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();
/*****************************
測試之后才知道方向鍵兩個(gè)字節(jié)
第一個(gè)字節(jié)ASCII 0x00e0 224
第二個(gè)字節(jié)分別是:
上: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:
lose = checkResult(index_y, index_x);
system("cls");
printMap();
if(lose)
{
setColor(13);
printf("| 誒喲,還差一點(diǎn)點(diǎn)哦! ╥﹏╥ |\n");
printf("| 按\"r\"重玩,Esc不玩啦。 |\n");
printf("[%c]-------------------------------------------------------[%c]\n", MINE, MINE);
setColor(10);
Sleep(1000);
char key3 = getch();
if(key3 == 'r' || key3 == 'R')
{
//重來,跟main中過程是一樣的
setColor(10);
gameInitailize();
countMines();
printMap();
}
}
//剩余的格子比雷還要多,可以繼續(xù)玩
else if(leftBlocksNum > ALL_MINES)
{
setColor(13);
printf("| 哎喲,挺不錯(cuò)哦~ ( ̄0  ̄) |\n");
printf("[%c]-------------------------------------------------------[%c]\n", MINE, MINE);
setColor(10);
}
//來到這你已經(jīng)贏了
else
{
setColor(13);
printf("| 喲,恭喜你贏了(/≧▽≦/) |\n");
printf("| 按\"r\"重玩,Esc就不玩啦。 |\n");
printf("[%c]-------------------------------------------------------[%c]\n", MINE, MINE);
setColor(10);
Sleep(1000);
char key3 = getch();
if(key3 == 'r' || key3 == 'R')
{
setColor(10);
gameInitailize();
countMines();
printMap();
}
}
break;
case ESC:
system("cls");
gameOver("\t\t\t啦啦啦~很逗很扯吧~最后感謝你的玩耍呀(≧Д≦)\n\n\n\n\n\n\n\n");
default:
break;
}
}
}
void setCurPos(int y, int x)
{
//在窗口緩沖中定義每個(gè)位置的狀態(tài)
COORD currentPosition;
currentPosition.Y = y;
currentPosition.X = x;
//所以現(xiàn)在的位置是在{y,x}
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), currentPosition);
}
void moveCursor(int y, int x)
{
//限定能走的地方
if((x >= 0 && x < COLUMN) && (y >= 0 && y < ROW))
{
setCurPos(cursorPos[y][x].y, cursorPos[y][x].x);
index_x = x;
index_y = y;
}
}
bool checkResult(int y, int x)
{
int i = 0, j = 0;
//檢測有沒有溢出地圖了
if(x < 0 || x >= COLUMN || y < 0 || y >= ROW)
{
return false;
}
//就是你了!被選中的格子!
minesMap[y][x].beCovered = false;
//被驚天雷炸了
if(minesMap[y][x].minesNum == -1)
{
minesMap[y][x].minesNum = 9;
return true;
}
//如果沒有雷,就當(dāng)作空格吧
if(minesMap[y][x].minesNum > 0 && minesMap[y][x].minesNum < 9)
{
return false;
}
//九宮格,3x3咯
for(i = -1; i <= 1; ++i)
{
//檢查一下在這一行溢出了沒吧
if(y + i < 0 || y + i >= ROW)
{
continue;
}
for(j = -1; j <= 1; ++j)
{
//這次就到列了吧
if(x + j < 0 || x + j >= COLUMN)
{
continue;
}
//如果下一個(gè)是沒開過的,就檢查它吧
if(minesMap[y + i][x + j].beCovered)
{
minesMap[y + i][x + j].beCovered = false;
checkResult(y + i, x + j);
}
}
}
return false;
}
void printMap()
{
system("cls");
char help0[] = "←↑↓→";
char help1[] = "動(dòng)啊";
char help2[] = "Space / Enter";
char help3[] = "點(diǎn)擊啊";
char help4[] = "Esc 不玩啦";
//因?yàn)橐敵鎏崾?,所以地圖不能太大了,10x10就差不多了
setColor(14);
setCurPos(4, 62);
printf("%s", help0);
setCurPos(6, 62);
printf("%s", help1);
setCurPos(9, 62);
printf("%s", help2);
setCurPos(11, 62);
printf("%s", help3);
setCurPos(14, 62);
printf("%s", help4);
setCurPos(0, 0);
setColor(10);
int i = 0, j = 0, k = 0;
leftBlocksNum = 0;
setColor(11);
printf("[開]--");
setColor(10);
for(k = 1; k < COLUMN - 1; ++k)
{
printf("+-----");
}
setColor(11);
printf("+--[心]\n");
setColor(10);
for(i = 0; i < ROW; ++i)
{
for(j = 0; j < COLUMN; ++j)
{
if(minesMap[i][j].beCovered)
{
++leftBlocksNum;
//這個(gè)輸出的就是格子被覆蓋的時(shí)候輸出的圖形,可以換成1-6試試
//1-4是正方形的4個(gè)角,5-6是雙豎線和雙橫線
printf("| %c ", 3);
}
else if(minesMap[i][j].minesNum == -1 || minesMap[i][j].minesNum == 9)
{
printf("| %c ", MINE);
}
else if(minesMap[i][j].minesNum == 0)
{
printf("| %c ", ' ');
}
else
{
printf("| %d ", minesMap[i][j].minesNum);
}
}
printf("|\n");
if(i < ROW - 1)
{
for(k = 0; k < COLUMN; ++k)
{
printf("+-----");
}
printf("+\n");
}
}
setColor(11);
printf("[就]--");
setColor(10);
for(k = 1; k < COLUMN - 1; ++k)
{
printf("+-----");
}
setColor(11);
printf("+--[好]\n");
setColor(10);
}
void gameOver(char *str)
{
setColor(12);
system("cls");
setCurPos(10, 0);
int i = 0;
do
{
//逐字輸出
printf("%c", str[i]);
Sleep(60);
}
while(str[i++]);
setColor(15);
system("pause");
//隨意終止程序并返回給OS,0是正常的
exit(0);
}
void delLine(int y)
{
HANDLE hOutput;
//窗口緩存信息
CONSOLE_SCREEN_BUFFER_INFO sbi;
DWORD len, nw;
//用MSDN上的TCHAR類型跪了,換成char就好
char fillchar = ' ';
//定位光標(biāo)
COORD startPosition = {0, y};
//獲取輸出句柄
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//獲取窗口緩沖中的信息
GetConsoleScreenBufferInfo(hOutput, &sbi);
//窗口緩沖的位置,這里取得X值
len = sbi.dwSize.X;
//從特定的位置用特定的字符去填充窗口的緩沖特定次數(shù)
//成功返回非0值,一般都成功,就不判斷了
FillConsoleOutputCharacter(hOutput, fillchar, len, startPosition, &nw);
}
測試顏色:
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
void setColor(unsigned short color)
{
HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE);
//對(duì)設(shè)置之后的輸出有效
SetConsoleTextAttribute(hCon, color);
};
int main()
{
//測試顏色啊~~
for(int i = 0; i <= 255; i++)
{
setColor(i);
printf("%d\n", i);
system("pause");
}
return 0;
}
測試方向鍵:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>
int main()
{
unsigned short int k;
while(1)
{
_sleep(100);
if(_kbhit())
{
k = _getch();
if(0 == k)
k = _getch() << 8;
_cprintf("key:0x%04x pressed\r\n", k);
}
}
system("pause");
return 0;
}
運(yùn)行截圖:圖片不會(huì)動(dòng)啦,在自己機(jī)子跑起來就看得到動(dòng)的效果了~~~



后話:
雖然不是什么很厲害的事情,稍微懂點(diǎn)的都可以自己做出來,不過在實(shí)踐的過程中還是收獲蠻多的,在這分享也算個(gè)小小的記錄吧,繼續(xù)加油~
相關(guān)文章
C++符號(hào)優(yōu)先級(jí)(詳細(xì)整理)
C++符號(hào)優(yōu)先級(jí),我詳細(xì)整理了一下。需要的朋友可以過來參考下。希望對(duì)大家有所幫助2013-10-10
Matlab實(shí)現(xiàn)鼠標(biāo)光標(biāo)變成愛心和瞄準(zhǔn)鏡形狀
這篇文章主要為大家詳細(xì)介紹了如何利用Matlab實(shí)現(xiàn)將鼠標(biāo)光標(biāo)變成愛心和瞄準(zhǔn)鏡等形狀,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-08-08
Qt下調(diào)用vlc庫實(shí)現(xiàn)RTSP拉流播放和截圖過程詳解
這篇文章主要為大家介紹了Qt下調(diào)用vlc庫實(shí)現(xiàn)RTSP拉流播放和截圖過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
C++實(shí)現(xiàn)簡單的HTTP服務(wù)器
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)簡單的HTTP服務(wù)器的相關(guān)資料,感興趣的朋友可以參考下2016-05-05

