文章目錄
- 前言
- 一、思路
- 二、代碼實作
- 1.頭檔案
- 2.全域變數和宏定義
- 3.main()
- 初始化
- 核心
- 4.功能函式
- 生成雷區new_mine()
- 游戲主要操作operate()
- 顯示雷區影像display()
- 檢查贏check_win()
- 三、效果展示
- 總結
前言
經過一整子的瞎折騰,我終于完成了ncurses版的掃雷,這讓我進一步認識到了ncurses庫的強大,這里主要的新知識點是"滑鼠事件的獲取和處理",可以參考這篇文章,雖然排版湊合但還是挺實用的,相比寫情詩,掃雷的代碼量還是比較大的,我的版本前后有100多行的樣子,我用思維導圖整理了我的思路,一個功能基本就一個函式,擴展功能我這里完成了前兩個,但已經夠用了,掃雷的基本操作測驗下來很流暢,
一、思路

二、代碼實作
1.頭檔案
#include <stdlib.h> //要用到srand()和rand()生成雷
#include <time.h> //要用到clock()生成亂數種子
#include <ncurses.h> //主角不用解釋
2.全域變數和宏定義
#define MINENUM 10 //雷的總數
#define row 9 //雷區的行數
#define line 9 //雷區的列數
int a[row][line]; //0:周圍沒有雷 -1:此處有雷 1~8:周圍雷的數量
int f[row][line]; //0:未探索 1:已探索
int win; //用于記錄輸贏的資訊
3.main()
初始化
initscr(); //初始化螢屏
curs_set(0); //不顯示游標
noecho(); //禁止輸入的字符出現在螢屏上
keypad(stdscr, TRUE); //因為缺少這行一直出錯
mousemask(BUTTON1_RELEASED,0);//激活滑鼠事件左鍵釋放,使得getch()能獲取滑鼠事件
MEVENT event; //創建事件變數
核心
while(1){ //這個回圈使游戲可以玩很多局
new_mine(MINENUM); //新建雷區
display(); //顯示雷區(剛開始全是不可見的)
while(1){ //這個回圈使游戲的操作可以持續
switch (getch()){ //獲取操作
case KEY_MOUSE: //如果是滑鼠事件,進行游戲操作
win=operate(event);//如果踩雷operate()會回傳-2
break;
case 'q': win=-2; //如果輸入的是字符'q'則退出本局游戲
}
display();
if(win==-2){ //輸掉游戲(包括中途退出)
printw("You lose!"); break;
}
if(check_win()){ //檢查是否贏得游戲
printw("You win!"); break;
}
}
if(getch()=='q') break; //如果再次輸入字符'q'則退出游戲
}
endwin(); //退出curses模式
4.功能函式
生成雷區new_mine()
int add_mine(int r,int l){ //用于累加雷區的數字
if(r>=0&&r<row&&l>=0&&l<line&&a[r][l]!=-1)//排除在界外的情況,而且雷上面不用進行處理
a[r][l]++;
}
int new_mine(int num){
int n=0,r=0,l=0;
win=0; //千萬不要忘記初始化,下一局開始時win要再次初始化為0
for(r=0;r<row;r++){
for(l=0;l<line;l++){
a[r][l]=0; //雷區的狀態也要初始化
f[r][l]=0; //雷區的探索情況也要初始化
}
} //生成雷
srand(clock()); //用clock()生成隨機種子
while(n<num){
r=rand()%row; //用亂數獲取行數
l=rand()%line; //用亂數獲取列數
if(a[r][l]==0){ //確保即使有重復位置的雷也能生成所需的數量的雷
a[r][l]=-1; //放置雷
n++;
}
}
for(r=0;r<row;r++) //布置雷區數字
for(l=0;l<line;l++)
if(a[r][l]==-1){//在雷的周圍累加數字
add_mine(r-1,l-1);
add_mine(r-1,l);
add_mine(r-1,l+1);
add_mine(r,l-1);
add_mine(r,l+1);
add_mine(r+1,l-1);
add_mine(r+1,l);
add_mine(r+1,l+1);
}
}
游戲主要操作operate()
int show_mine(int r,int l){ //用于顯示雷區的狀態
if(r>=0&&r<row&&l>=0&&l<line&&a[r][l]!=-1)//排除在界外的情況,而且雷不用顯示
f[r][l]=1;
}
int operate(MEVENT mouse){ //游戲的主要操作
static int r=0,l=0;
getmouse(&mouse); //翻譯獲取的滑鼠事件
mouse_trafo(&mouse.y,&mouse.x,1); //將獲取的滑鼠的坐標轉換為對應的螢屏坐標
if (!wenclose(stdscr,mouse.y, mouse.x))
return -1; //如果坐標不在螢屏內,直接回傳
if(mouse.bstate==BUTTON1_RELEASED){ //當滑鼠事件為左鍵釋放時
l=mouse.x/3; //將滑鼠坐標轉化為對應的雷區坐標
r=mouse.y/2;
if(a[r][l]==-1){ //該坐標上有雷,踩到雷了
for(int i=0;i<=row;i++)
for(int j=0;j<=line;j++)
f[i][j]=1; //全部雷區的狀態都設為可見
return -2; //回傳-2
}
show_mine(r,l); //沒踩到雷,就設定該坐標的雷區狀態為可見
if(a[r][l]==0){ //如果該坐標周圍沒有雷,則設定周圍的雷區狀態為可見
show_mine(r-1,l-1);
show_mine(r-1,l);
show_mine(r-1,l+1);
show_mine(r,l-1);
show_mine(r,l+1);
show_mine(r+1,l-1);
show_mine(r+1,l);
show_mine(r+1,l+1);
}
}
}
顯示雷區影像display()
int display(){ //用于顯示雷區
int r=0,l=0;
clear(); //清屏
for(r=0;r<row;r++){ //遍歷整個雷區
for(l=0;l<line;l++){
if(f[r][l]==1) //如果該雷區坐標狀態為可見
if(a[r][l]==-1) //如果該坐標是雷,列印雷
mvaddch(r*2+1,l*3+1,'*');
else //不是雷就列印數字
mvaddch(r*2+1,l*3+1,'0'+a[r][l]);
}
}
//繪制網格,細節就不解釋了
for(r=0;r<row;r++){
for(l=0;l<line;l++){
mvaddch(r*2,l*3+1,'-');
mvaddch(r*2,l*3+2,'-');
mvaddch(r*2,l*3,'|');
mvaddch(r*2+1,l*3,'|');
mvaddch(row*2,l*3,'|');
mvaddch(row*2,l*3+1,'-');
mvaddch(row*2,l*3+2,'-');
}
mvaddch(r*2,line*3,'|');
mvaddch(r*2+1,line*3,'|');
}
mvaddch(row*2,line*3,'|');
refresh(); //將緩沖區的所有字符都列印到螢屏上
}
檢查贏check_win()
int check_win(void){ //看看游戲有沒有贏
int count=0; //用于統計未探索的雷區的數量
for(int i=0;i<row;i++)
for(int j=0;j<line;j++)
count+=1-f[i][j]; //因為未探索是0,已探索是1,只有未探索才+1
return count==MINENUM; //剩下的雷區的數量就是雷的總數,說明雷全找出來了
}
三、效果展示
如果中途按q退出,會判定為輸
我輸的時候
當然也有贏的時候
總結
通過這次用ncurses寫掃雷的嘗試,除了加深對c語言和ncurses庫的理解和熟練度,更讓我了解到思路的重要性,c語言和ncurses庫只是工具,如果思路不清晰明朗就很難完成目標,反之是總有辦法實作的,所以在敲代碼之前,尤其是做復雜的任務的時候,一定要先做需求分析整理思路,將需要的資料和功能都剖析出來,這樣對代碼的實作非常有利,養成這種良好的習慣對于提高效率和代碼品質是十分有利的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/135101.html
標籤:其他
下一篇:小鴉的個人簡介
