前言:
想起來做這個是因為那時候某天知道了原來黑框框裡面的光標是可以控制的,而且又經常聽人說起這個,就鍛煉一下好了。
之前就完成了那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: 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(); } } //剩余的格子比雷還要多,可以繼續玩 else if(leftBlocksNum > ALL_MINES) { setColor(13); printf("| 哎喲,挺不錯哦~ ( ̄0  ̄) |\n"); printf("[%c]-------------------------------------------------------[%c]\n", MINE, MINE); setColor(10); } //來到這你已經贏了 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) { //在窗口緩沖中定義每個位置的狀態 COORD currentPosition; currentPosition.Y = y; currentPosition.X = x; //所以現在的位置是在{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; } //如果沒有雷,就當作空格吧 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 = ' '; //定位光標 COORD startPosition = {0, y}; //獲取輸出句柄 hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //獲取窗口緩沖中的信息 GetConsoleScreenBufferInfo(hOutput, &sbi); //窗口緩沖的位置,這裡取得X值 len = sbi.dwSize.X; //從特定的位置用特定的字符去填充窗口的緩沖特定次數 //成功返回非0值,一般都成功,就不判斷了 FillConsoleOutputCharacter(hOutput, fillchar, len, startPosition, &nw); }
測試顏色:
1 #include <windows.h> 2 #include <stdlib.h> 3 #include <stdio.h> 4 5 void setColor(unsigned short color) 6 { 7 HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE); 8 //對設置之後的輸出有效 9 SetConsoleTextAttribute(hCon, color); 10 }; 11 12 int main() 13 { 14 //測試顏色啊~~ 15 for(int i = 0; i <= 255; i++) 16 { 17 setColor(i); 18 printf("%d\n", i); 19 system("pause"); 20 } 21 return 0; 22 }
測試方向鍵:
#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; }
運行截圖:圖片不會動啦,在自己機子跑起來就看得到動的效果了~~~
後話:
雖然不是什麼很厲害的事情,稍微懂點的都可以自己做出來,不過在實踐的過程中還是收獲蠻多的,在這分享也算個小小的記錄吧,繼續加油~