首先,感謝一下這篇博客,幫助了我很多,教會了我很多語法,而且由于博主寫不出一個合理的亂數和難度函式,是直接運用的該博客的,
先上代碼,由于博主是第一次接觸此類Windows程式設計的程式,學到啥都想往上用,除去單獨的貪吃蛇,還加了一些別的東西,思路在最后,中間是博主的碎碎念,
/************************貪吃蛇***********************/
/**********************2020-12-12*********************/
#include<bits/stdc++.h>
#include<windows.h>
#include<conio.h>
#include<time.h>
using namespace std;
int length;
int modx,mody;
queue <int> follow;
//申請一個通用的用于輸出的句柄
HANDLE Printf=GetStdHandle(STD_OUTPUT_HANDLE);
//定義一個坐標型別的變數
COORD snakehead;
COORD snakelast;
COORD food;
COORD use;
//判斷食物是否重繪在了蛇身上,同時可以依靠此來判斷蛇是否死亡
bool pd(COORD x)
{
char ls;
DWORD read;
ReadConsoleOutputCharacterA(Printf,&ls,1,x,&read);
if(ls=='*'||ls=='@')
{
return false;
}
else return true;
}
//沒有操作時蛇的前進
int IfOutX(int x)
{
if(x==0)
{
return modx;
}
else if(x==modx+1)
{
return 1;
}
else return x;
}
int IfOutY(int y)
{
if(y==0)
{
return mody;
}
else if(y==mody+1)
{
return 1;
}
else return y;
}
COORD go(COORD x,int y)
{
switch(y)
{
case 1: x.Y--;
x.Y=IfOutY(x.Y);break;
case 3: x.Y++;
x.Y=IfOutY(x.Y);break;
case 2: x.X--;
x.X=IfOutX(x.X);break;
case 4: x.X++;
x.X=IfOutX(x.X);break;
}
return x;
}
//判斷玩家的操作
int direction;
int play(char x)
{
switch(x)
{
case 72://上
if(direction!=3)
{
return 1;
}
else break;
case 75://左
if(direction!=4)
{
return 2;
}
else break;
case 80://下
if(direction!=1)
{
return 3;
}
else break;
case 77://右
if(direction!=2)
{
return 4;
}
else break;
}
return 0;
}
//判斷有無吃到食物
bool eat()
{
if(snakehead.X==food.X&&snakehead.Y==food.Y)
{
return true;
}
else return false;
}
//隱藏游標
void hide()
{
CONSOLE_CURSOR_INFO nowcursor;
nowcursor.bVisible=0;
nowcursor.dwSize=1;
SetConsoleCursorInfo(Printf,&nowcursor);
}
/*** 生成亂數 ***/
double random(double start, double end)
{
return start+(end-start)*rand()/(RAND_MAX + 1.0);
}
//列印食物
void printf_food()
{
while(false==false)
{
food.X=random(0,modx)+1;
food.Y=random(0,mody)+1;
if(pd(food))
{
break;
}
}
SetConsoleCursorPosition(Printf,food);
cout<<"$";
}
//列印蛇
char hit;
int yx;
bool printf_snake()
{
if(kbhit())
{
hit=getch();
if(hit==-32)
{
hit=getch();
int ls=play(hit);
if(ls)
{
direction=ls;
}
}
}
SetConsoleCursorPosition(Printf,snakehead);
cout<<"*";
snakehead=go(snakehead,direction);
follow.push(direction);
if(eat())
{
printf_food();
length++;
use.X=12;
use.Y=mody+2;
SetConsoleCursorPosition(Printf,use);
cout<<length;
}
else
{
SetConsoleCursorPosition(Printf,snakelast);
cout<<" ";
yx=follow.front();
follow.pop();
snakelast=go(snakelast,yx);
}
if(pd(snakehead)==false)
{
return true;
}
SetConsoleCursorPosition(Printf,snakehead);
cout<<"@";
return false;
}
//列印墻
void printf_wall(int n,int m)
{
cout<<" ";
for(int i=1;i<=m;i++)
{
cout<<"-";
}
cout<<endl;
for(int i=1;i<=n;i++)
{
cout<<"|";
for(int j=1;j<=m;j++)
{
cout<<" ";
}
cout<<"|"<<endl;
}
cout<<" ";
for(int i=1;i<=m;i++)
{
cout<<"-";
}
}
int main()
{
int n;
irresistible:n=MessageBox(NULL,"想明白生命的意義嗎?想真正的……活著嗎?","邀請函",MB_YESNO);
if(n==IDYES)
{
MessageBox(NULL,"游戲開始!","那么",MB_OK);
again:SetConsoleTextAttribute(Printf,FOREGROUND_INTENSITY|FOREGROUND_BLUE);
cout<<"--------------------貪吃蛇---------------------"<<endl;
cout<<"請先輸入兩個數,表示地圖大小.要求長寬均不小于10."<<endl;
cout<<"其中長度值最高為40,寬度值最高為99,請不要超出."<<endl;
cout<<"請注意視窗大小,以免發生錯位.建議將視窗調為最大."<<endl;
cout<<"再選擇難度.請在1-20中輸入1個數,1最簡單,20則最難"<<endl;
cout<<"然后進入游戲畫面,以方向鍵控制方向.祝你游戲愉快!"<<endl;
cout<<"-----------------------------------------------"<<endl;
while(false==false)
{
cin>>mody>>modx;
if(mody<10||modx<10||mody>40||modx>100)
{
MessageBox(NULL,"請重新輸入","蠢材",MB_OK);
continue;
}
else break;
}
int hard;
while(false==false)
{
cin>>hard;
if(hard<=0||hard>20)
{
MessageBox(NULL,"請重新輸入","蠢材",MB_OK);
continue;
}
else break;
}
//游戲開始
system("cls");
while(follow.size()!=0)
{
follow.pop();
}
hide();
printf_wall(mody,modx);
printf_food();
use.X=modx/2;
use.Y=mody/2;
SetConsoleCursorPosition(Printf,use);
cout<<"*@";
snakelast=use;
use.X++;
snakehead=use;
length=2;
follow.push(4);
use.X=0;
use.Y=mody+2;
SetConsoleCursorPosition(Printf,use);
cout<<"輸入任意方向鍵開始游戲";
while(false==false)
{
if(kbhit())
{
hit=getch();
if(hit==-32)
{
hit=getch();
int ls=play(hit);
if(ls)
{
direction=ls;
break;
}
}
}
}
use.X=0;
use.Y=mody+2;
SetConsoleCursorPosition(Printf,use);
cout<<" ";
use.X=0;
use.Y=mody+2;
SetConsoleCursorPosition(Printf,use);
cout<<"Now length: ";
use.X=12;
use.Y=mody+2;
SetConsoleCursorPosition(Printf,use);
cout<<length;
double hard_len;
clock_t a,b;
while(false==false)
{
/*** 難度隨長度增加而提高 ***/
hard_len=(double)length/(double)(modx*mody);
/*** 調節時間,單位是ms ***/
a=clock();
while(true==true)
{
b=clock();
if(b-a>=(int)(400-30*hard)*(1-sqrt(hard_len)))
{
break;
}
}
//如果死亡
if(printf_snake())
{
use.X=12;
use.Y=mody+2;
SetConsoleCursorPosition(Printf,use);
cout<<length;
system("pause");
system("cls");
if(length==modx*mody||modx==100)
{
MessageBox(NULL,"足夠貪婪,一種美德!","恭喜您獲勝",MB_OK);
n=MessageBox(NULL,"請問您是否再次挑戰?","邀請函",MB_OKCANCEL);
if(n==IDOK)
{
goto again;
}
else break;
}
else if(length==5)
{
MessageBox(NULL,"知足是一種美德!","恭喜您獲勝",MB_OK);
n=MessageBox(NULL,"請問您是否再次挑戰?","邀請函",MB_OKCANCEL);
if(n==IDOK)
{
goto again;
}
else break;
}
else
{
MessageBox(NULL,"貪婪是一種罪!","你已經輸了",MB_OK);
goto again;
}
}
}
}
else goto irresistible;
return 0;
}
代碼如上,接下了談談博主寫這個的心路歷程,分享一些易錯點,
博主寫這個是因為學了很多演算法,但是沒學任何關于基礎的工程實作 (我也不知道一般是怎么描述這個的,反正就是寫能實際應用的程式的意思) ,就很想試著去實作一下,于是就瞄準了這個貪吃蛇,
由于博主基本上算是零基礎,只會一點C++的皮毛,不懂任何關于工程實作的語法,博主還特意跑圖書館借了一堆書 (不過基本沒用上,還是度娘好用!),
首先,Windows程式設計運用的基本上都是API函式,記住這個名詞,博主因為不知道這個于是像個無頭蒼蠅找了很久,問了很多人,也記住如果想用C++來實作,請注意,API函式好像Java也有,博主第一次跑圖書館借書借了本Java的,回去之后一翻當場懵掉,
其次,關于Windows程式設計,我所學到的最重要的名詞便是句柄,在此我根據百度知道上一位大佬的回答總結一下,他的回答讓我感徑訓然開朗,句柄就好像你去銀行辦事,你得先去取號,號有不同,所以句柄也有不同,不同的號,或者說不同的句柄會有區別對待,就好比貴賓號能干普通號能干的事情,但是普通號不一定能享受貴賓服務, 不過,俺覺得,像貪吃蛇這種小游戲,直接用HANDLE萬能句柄應該沒啥大問題吧?
至于其他的語法,都是博主一個個的測驗出效果,根據得到的結果反推函式變數的意義,所以應該也算是學懂了吧,
然后,博主在度娘搜索資料的時候還查出了一些錯誤資料,分享出來大家注意一下,比方說上下左右方向鍵的ascll碼
| ascll碼 | 方向 |
|---|---|
| 72 | ↑ |
| 80 | ↓ |
| 75 | ← |
| 77 | → |
博主搜到的一篇博客上寫的是錯的,導致博主因此查了很久bug,也怪博主沒仔細看,拿來就用,明明下方評論都在說這個是錯的,引以為戒,
然后寫幾個會用到的函式,解釋一下他的意思
//申請一個通用的用于輸出的句柄
HANDLE Printf=GetStdHandle(STD_OUTPUT_HANDLE);
COORD snakehead;
SetConsoleCursorPosition(Printf,snakehead);
cout<<"@*";
比方說這個,申請了一個名稱叫Printf的用于輸出的句柄,申請了一個名稱叫snakehead的坐標,然后,在snakehead的所表示的坐標輸出“@”,“@”在snakehead所表示的坐標,“”在snakehead.X+1,snakehead.Y所表示的坐標,坐標的話,以左上角為(0,0),向右為X軸,向左為Y軸,一個像素(也就是一個英語字符)為一個單位,
char hit;
if(kbhit())
{
hit=getch();
if(hit==-32)
{
hit=getch();
int ls=play(hit);
if(ls)
{
direction=ls;
}
}
}
kbhit()是已有函式,用于判斷玩家是否敲擊過鍵盤,
getch()和getchar()一樣都是讀入一個字符,但是不同的是getch()讀入字符后并不顯示在螢屏上,
if(hit==-32)我也沒明白,在文末提出了問題,但是加上這個操作立桿見影的順暢了起來,
play()函式是我定義的一個判斷輸入的字符是否是有效字符(方向鍵),該把方向改為哪個方向,
現在我們已經知道怎么在相應的位置輸出我們想要的字符影像了,那我們還需要洗掉一些影像,不然這蛇不吃都胖,我不會洗掉,但是經過我的一系列試驗測驗,我發現,字符是可以被覆寫的,所以我用輸出“ ”來替代洗掉,一邊列印蛇頭,一邊用空格覆寫蛇尾,于是畫面就更新了,
然后怎么維持畫面的重繪的速度呢,我是用死回圈,通過時間函式在里面跑一定的時間再跳出來,就可以消耗時間,使界面重繪變慢,小蛇自然也就運動的慢了起來,
主要需要用到的語法就是這些了,其余的全是一些界面的優化,有興趣的可以自己去搜索一些,
現在講講我的思路,
在我學會以上語法之后,我的思路就漸漸完善了,刪減了一些我最初思路中我無法實作的東西,改為用以上語法來等效替代,比如洗掉字符我不會,用了替代字符,
我一直認為,寫程式就像壘積木,組裝零件,每一個程式都是通過一些函式通過精巧的思路組裝,逐漸成型成一個完美的程式,這也是編程最吸引我的我一點,我覺得編程更注重思路而不是其他,就像數學一樣,
啊跑偏了,現在真的講思路了,
貪吃蛇,蛇不斷的在區域里移動,蛇頭觸碰到了食物就變長,沒吃到食物就不變,需要程式有效運行很久,所以我們需要在最外層寫上一個死回圈,從影像上來看,似乎每次操作,只要把蛇頭變更為蛇身,蛇頭向它的行進方向進行判定,有無自撞或者有無吃到食物,如果自撞游戲結束,如果吃到食物,我們就不進行其他操作,因為其實此時原蛇身應該是沒有任何變化的,但如果沒有吃到食物,其實很容易發現,每次變化的也只有蛇尾而已,每次把蛇尾的那個字符給消失,并不需要模擬蛇的身子都向前走,不過用陣列指標模擬蛇前進也是一種可行辦法,不過效率應該會低上一點,畢竟每次移動都是整個蛇身在動,而我的想法只需要動兩個——蛇頭和蛇尾,
但這個也有一個問題,用陣列或指標模擬的話,蛇身該怎么動一目了然,走到上一截身體的位置就行了, 而我的想法蛇尾就不能跟著上一截身體走了,畢竟他們根本沒動,那如果遇上拐彎什么的,我不就找不到蛇尾了嗎?
我的尾巴它不見了嗚嗚嗚!
博主想了想,發現蛇尾需要有延遲的跟著蛇頭動,假如蛇長為4,那么,我蛇尾運動的方向應該是蛇頭三格前運動的方向,我就照著這個方向覆寫蛇尾不就好起來了嗎!
所以,博主掏出了他的法寶,佇列!
佇列的先進先出,真的是完完全全符合博主對于它的期望,將蛇頭的運動方向存進佇列,如果沒吃到食物,那么從佇列前頭取出一個方向,正是蛇頭很久之前的運動方向,
另外,博主還有一些疑惑,希望有路過的大佬能幫助解決一下,博主在此先謝過,
1.博主最開始除了方向鍵,還弄了一套wasd作為作業系統的,可是在判斷按鍵的case那里寫上了卻并不能觸發,還有空格也是,本來博主還想加一個暫停的功能的,
2.博主想知道ascll碼-32到底是啥東西,實在是百度不到,嘗試輸出也是一片空白,在只取一次的按鍵的時候界面卡頓的不行,但是加上了這個之后,取兩次按鍵,操作立竿見影的順暢了起來,
順帶一提,除錯真好用!
本博客鳴謝那些所有在網路上分享自己所學的大佬,感謝你們分享的知識,讓我能實作這個貪吃蛇的代碼,
本博客轉載還請注明出處,謝謝,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/233943.html
標籤:其他
上一篇:C語言實作三子棋(五子棋、掃雷)
