C語言開發(fā)簡易版掃雷小游戲
前言:
想起來做這個是因為那時候某天知道了原來黑框框里面的光標(biāo)是可以控制的,而且又經(jīng)常聽人說起這個,就鍛煉一下好了。
之前就完成了那1.0的版本,現(xiàn)在想放上來分享卻發(fā)現(xiàn)有蠻多問題的,而且最重要的是沒什么注釋【果然那時候太年輕】!現(xiàn)在看了也是被那時候的自己逗笑了,就修改了一些小bug,增加了算是詳盡而清楚的注釋,嗯,MSDN上面對各種函數(shù)的解釋很詳細(xì)的【又鍛煉一下英語】,順便讓開頭和結(jié)尾的展示“動”了起來,就當(dāng)作1.5的版本好了。
這個只是給出了一個實現(xiàn)的思路,其中肯定也有很多不合理的地方和可優(yōu)化之處,希望能供大家參考和交流。
過程:
期間也是遇到了蠻多困惑的。
1.最先的是怎么知道按了方向鍵,左查右找,說法有好幾個版本呢,就想看能不能自己測試一下自己的好了,再查再找,好了,感謝寫了測試方向鍵的人;
2.再比如說怎么消除窗口中一行的緩沖,因為不消除就一直在哪,視覺效果不好,翻查了一下資料,就寫了delLine()這個來做這個事情了;
3.設(shè)定顏色時,在cmd里面help color知道了顏色的參數(shù),但是通過數(shù)字0-9來設(shè)定的太暗了,發(fā)現(xiàn)有更亮的,比如0A,在setColor()里面用它卻說類型不對,于是上MSDN,發(fā)現(xiàn)還可以用宏,就想通過如'BACKGROUND_INTENSITY | BACKGROUND_RED '之類來完成,就想怎么去代替那個宏,覺得每次寫一長串好麻煩。然后換了各種類型的參數(shù)類型和不定長參數(shù)什么的,發(fā)現(xiàn)還是不行,后來一想,萬一它支持?jǐn)?shù)字10呢,A不就是10么?!一測,成了;
4.還有一些判斷狀態(tài)的順序,嗯啊,這些要先想好再下手,不然左改右改很麻煩呢;
5.別的困惑不怎么記得了。。。
代碼:
下面分別給出LittleMines【好弱的名字】,測試顏色,測試方向鍵的代碼。【反映說有行號不好復(fù)制,那取消好了】
/********************************* * c語言命令行+方向鍵簡易版掃雷 * Author:AnnsShadoW * Version:1.5 * Time:2015-11-29 ********************************/ /******************************** * 運行環(huán)境:Windows10-64bit * 編譯環(huán)境:Codeblocks-13.12 ********************************/ //用到的都導(dǎo)進去吧 #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <time.h> #include <windows.h> //定義各種判斷狀態(tài)的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 //定義類型狀態(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; //每一個小格的結(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); //開頭的歡迎“動畫” void welcomeToMyGame(); //游戲地圖初始化 void gameInitailize(); //以某格子為中心計算驚天雷數(shù)量 void countMines(); //獲取鍵盤的輸入 void keyBoardInput(); //指定光標(biāo)的位置 void setCurPos(int y, int x); //移動光標(biāo)的位置 void moveCursor(int y, int x); //檢測每一步的結(jié)果 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); //對設(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 ╮( ̄▽ ̄)╭"; //控制臺窗口默認(rèn)大小是80*25,所以能達到最大的位置是[79,24] for(i = 0; i <= 5; ++i) { //每次輸出之前都清屏,就會有看起來是動的效果 system("cls"); //縱坐標(biāo)不斷加,形成向下效果 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) { //要刪除這一行緩沖的原因: //上一次循環(huán)的輸出會影響到下一次,如輸出VVVVVVVVVVersion1.0 //換成中文就不會,中文要兩個字節(jié)才能顯示完整呀 delLine(9); //這里就會有閃閃發(fā)亮的效果哦 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[] = "動?。骸r(╯▽╰)╭"; 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; //設(shè)置隨機值 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) { //這個‘-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)計吧 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(); /***************************** 測試之后才知道方向鍵兩個字節(jié) 第一個字節(jié)ASCII 0x00e0 224 第二個字節(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("| 誒喲,還差一點點哦! ╥﹏╥ |\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("| 哎喲,挺不錯哦~ ( ̄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) { //在窗口緩沖中定義每個位置的狀態(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; } //如果下一個是沒開過的,就檢查它吧 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[] = "動啊"; char help2[] = "Space / Enter"; char help3[] = "點擊啊"; char help4[] = "Esc 不玩啦"; //因為要輸出提示,所以地圖不能太大了,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; //這個輸出的就是格子被覆蓋的時候輸出的圖形,可以換成1-6試試 //1-4是正方形的4個角,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); //對設(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; }
運行截圖:圖片不會動啦,在自己機子跑起來就看得到動的效果了~~~
后話:
雖然不是什么很厲害的事情,稍微懂點的都可以自己做出來,不過在實踐的過程中還是收獲蠻多的,在這分享也算個小小的記錄吧,繼續(xù)加油~
相關(guān)文章
Matlab實現(xiàn)鼠標(biāo)光標(biāo)變成愛心和瞄準(zhǔn)鏡形狀
這篇文章主要為大家詳細(xì)介紹了如何利用Matlab實現(xiàn)將鼠標(biāo)光標(biāo)變成愛心和瞄準(zhǔn)鏡等形狀,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-08-08Qt下調(diào)用vlc庫實現(xiàn)RTSP拉流播放和截圖過程詳解
這篇文章主要為大家介紹了Qt下調(diào)用vlc庫實現(xiàn)RTSP拉流播放和截圖過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08