大一實訓---貪喫蛇+走全圖AI實現(2)

              接下來就是ai部分,老實說,樓樓的ai其實寫得並不是很好。樓樓隨意測試N遍,目測成功機率最高10%,失敗原因是策略性死循環。但還是獻醜一下,若有什麼好的意見,也請各位指導一下我。

              總體上的ai策略是分別先對3個方向的下一步進行bfs尋找蛇尾,對於能找到蛇尾的方向進行求蛇頭與食物的最短距離,然後進行動作,策略完成了。

              在這裏的策略中,bfs蛇尾是第一步,原因是無論到未來的局面有多麼的複雜,能從蛇頭bfs到蛇尾就說明蛇不管怎麼樣都不會死。接下來就是考慮喫的問題,這裏的求距離並沒有使用bfs去求,而是直接採用兩點間的距離來求,原因就是每走一步,局勢都會有變化,3,4步下來就變得完全不一樣,而且還有就是到了最後蛇身很長的時候,很多時候都並不能bfs求解了(想象一下那個揉成一團的蛇身的情況你就知道了)。接下來上代碼:

string autofound()//ai實現
{
	int i,j,t,i1,vi[4],pan;
	double fo,keepfo[4];
	sanke ke,hd,keep[4];
	hd = s.back();
	for(i=0;i<=3;i++)
	{
		keep[i]=ke;
		keepfo[i]=0;
	}
	pan = 0;//是否可以搜蛇尾判定
	for(i = 0;i <= 3;i++)
	{
here:
		j = rand()%4;
		for(i1 = 0;i1 < i;i1++)//生成隨機方向		{
			if(vi[i1]==j)
			{
				goto here;
			}
		}
		vi[i] = j;
		ke.x = dire[j][0],ke.y = dire[j][1];//記錄方向狀態
		if(bfs(ke,s.front()))//此方向可以搜到蛇尾就求次方向和食物的距離
		{
			keep[j] = ke;
			t = j;
			keepfo[j] =sqrt(double((hd.x+ke.x - foodxy.x)*(hd.x+ke.x - foodxy.x) + (hd.y+ke.y - foodxy.y)*(hd.y+ke.y - foodxy.y)));
			fo = keepfo[j];
			pan = 1;
		}
		else
		{
			keepfo[j] = 100000;
		}
	}
	if(pan)
	{
		pan = 0;
		for(i = 0;i <= 3 ;i++)//尋找離食物最近的方向走
		{
			if(fo>keepfo[i])
			{
				t = i;
				fo = keepfo[i];
			}
		}
		if(judge(keep[t].x,keep[t].y))//行走
		{
			if(s.size() == ((n-2)*(m-2)-1))
			{
				return "win";
			}
			else
			{
				return "go on";
			}
		}
	}

}	


還有就是bfs的:

bool bfs(sanke a,sanke b)//bfs尋找如今蛇尾
{
	queue<sanke> q;
	int i,j;
	sanke tem,hd,ke;
	tem = s.front();
	hd = s.front();
	vis[tem.x][tem.y]=' ';
	tem = s.back();
	vis[tem.x][tem.y]='*';
	tem.x = tem.x + a.x,tem.y = tem.y + a.y;
	if(vis[tem.x][tem.y] == '*' || vis[tem.x][tem.y] == 'I' || vis[tem.x][tem.y] == '-' || (tem.x == hd.x && tem.y == hd.y))
	{
		if((tem.x == hd.x && tem.y == hd.y))
		{
			for(i=0;i<n;i++)//狀態返回
			{
				for(j=0;j<m;j++)
				{
					vis[i][j] = map[i][j];
				}
			}
			return true;
		}
		else
		{
			for(i=0;i<n;i++)//狀態返回
			{
				for(j=0;j<m;j++)
				{
					vis[i][j] = map[i][j];
				}
			}
			return false;
		}
	}
	hd = tem;
	vis[hd.x][hd.y] = '*';
	q.push(hd);
	while(!q.empty())
	{
		hd = q.front();
		q.pop();
		for(i = 0;i <= 3;i++)
		{
			ke.x = hd.x + dire[i][0],ke.y = hd.y + dire[i][1];
			if(ke.x == b.x && ke.y == b.y)
			{
				for(i=0;i<n;i++)//狀態返回
				{
					for(j=0;j<m;j++)
						vis[i][j] = map[i][j];
				}
				return true;
			}
			if(vis[ke.x][ke.y] == ' ' || vis[ke.x][ke.y] == '$')
			{
				vis[ke.x][ke.y] = '*';
				q.push(ke);
			}
		}
	}
	for(i=0;i<n;i++)//狀態返回
	{
		for(j=0;j<m;j++)
		{
			vis[i][j] = map[i][j];
		}
	}
	return false;
}


至於上面的代碼,有一個叫"隨機生成方向"的東西,其實樓樓當時是按照一定順序去bfs的,即這個數組

int dire[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//右下左上


但寫完後運行發現出現的策略性的死循環,即明明食物就在蛇頭面前且吃了食物還能bfs到蛇尾,但就是不去喫,一直死磕着蛇尾,並無限走同一個軌跡。這種情況讓我很腦疼,我覺得應該是有同多個方向到食物的最短距離是一樣的,於是就出現的rand編號這麼一個東西來增加他的可變性,但儘管已經做到這樣了,通關率也只是上升了一點。接着就上程序源碼了:

#include <iostream>
#include <stdlib.h>
#include <Windows.h>
#include <conio.h>
#include <time.h>
#include <queue>
#include <string >
#include <algorithm>
#include <math.h>
using namespace std;
//#牆 $食物 *蛇身 @頭
int dire[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//右下左上
const int MAX = 30;
char map[MAX][MAX],vis[MAX][MAX];//map爲當前狀態地圖 vis爲map的複製在尋路時作模擬行走
int n,m;
int score,ti,ju,to;//分數,控制變速變量,自動尋路判定
double second;//時間控制
bool judge(int i,int j);
void Gotoxy(int x, int y)//光標定位函數
{
 COORD pos = {y,x};
 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
 CONSOLE_CURSOR_INFO cursor_info;
 GetConsoleCursorInfo(hOut, &cursor_info);
 cursor_info.bVisible = false;
 cursor_info.dwSize = 20;
 SetConsoleCursorPosition(hOut, pos);
 SetConsoleCursorInfo(hOut, &cursor_info);
}
struct sanke//用結構體儲存蛇身座標
{
	int x,y;
	sanke(int i1=0,int j1=0)//構造函數
	{
		x=i1;
		y=j1;
	}
}keepdire,foodxy;//保存當前方向 記錄食物座標
queue<sanke> s; //用隊列特點儲存蛇身
void foodpro()//生成食物函數
{
	int i,j;
	i=rand()%(n-1);
	j=rand()%(m-1);
	while(1)
	{
		if(map[i][j]=='*' || map[i][j]=='@' || map[i][j]=='-' || map[i][j]=='I')
		{
			i=rand()%(n-1);
			j=rand()%(n-1);
		}
		else
		{
			map[i][j]='$';
			vis[i][j]='$';
			foodxy.x = i,foodxy.y = j;
			Gotoxy(i,2*j);
			cout<<"$";
			return;
		}
	}
}
void printf()
{
	Gotoxy(0,0);
	int i,j;
	for(i=0;i<n;i++)
	{
		for(j=0;j<m;j++)
		{
			cout<<map[i][j]<<' ';
		}
		cout<<endl;
	}
	Gotoxy(n,0);
	cout<<"當前分數:"<<score;
}
void mapint()//數據初始化函數
{
	while(1)
	{
		cout<<"請選擇模式"<<endl;
		cout<<"1: ai 快速測試模式"<<'\t'<<"2: ai  正常速度模式"<<"\t"<<"3: 遊戲模式"<<endl;
		char ke[100];
		cin>>ke;
		if(strlen(ke)>1 ||(strlen(ke)==1&& (ke[0]-'0'!=1) && (ke[0]-'0'!=2) && (ke[0]-'0'!=3)))
		{
			system("cls");
			cout<<"輸入錯誤  請重新輸入"<<endl;
		}
		else if(ke[0]=='1')
		{
			m = 10;
			n = 10;
			ju = 1;
			second = 0;
			system("cls");
			break;
		}
		else if(ke[0]=='2')
		{
			m = 10;
			n = 10;
			ju = 1;
			second = 300;
			system("cls");
			break;
		}
		else
		{
			system("cls");
			cout<<"使用wasd控制方向 p爲暫停 f爲自動尋路"<<endl;
			system("pause");
			system("cls");
			m =20;
			n =20;
			ju = 0;
			second = 300;
			break;
		}
	}
	int i,j;
	sanke a;
	score=0;
	to = 0;
	keepdire.x=0,keepdire.y=1;
	while(!s.empty())
	{
		s.pop();
	}
	for(i=0;i<n;i++)
	{
		for(j=0;j<m;j++)
		{
			map[i][j]=' ';
			vis[i][j]=' ';
		}
	}
	for(i=0;i<m;i++)   //地圖畫牆 
	{
		map[0][i]='-';
		vis[0][i]='-';
		map[n-1][i]='-';
		vis[n-1][i]='-';
	}
	for(i=1;i<=n-2;i++)
	{
		map[i][0]='I';
		vis[i][0]='I';
		map[i][m-1]='I';
		vis[i][m-1]='I';
	}
	for(i=1;i<=6;i++)//蛇身構造
	{
		map[1][i]='*';
		vis[1][i]='*';
		a.x=1;
		a.y=i;
		s.push(a);
		if(i==6)
		{
			map[1][i]='@';
			vis[1][i]='@';
		}
	}
	Gotoxy(n+1,0);
	cout<<"按p鍵暫停"<<endl;
}
bool bfs(sanke a,sanke b)//bfs尋找如今蛇尾
{
	queue<sanke> q;
	int i,j;
	sanke tem,hd,ke;
	tem = s.front();
	hd = s.front();
	vis[tem.x][tem.y]=' ';
	tem = s.back();
	vis[tem.x][tem.y]='*';
	tem.x = tem.x + a.x,tem.y = tem.y + a.y;
	if(vis[tem.x][tem.y] == '*' || vis[tem.x][tem.y] == 'I' || vis[tem.x][tem.y] == '-' || (tem.x == hd.x && tem.y == hd.y))
	{
		if((tem.x == hd.x && tem.y == hd.y))
		{
			for(i=0;i<n;i++)//狀態返回
			{
				for(j=0;j<m;j++)
				{
					vis[i][j] = map[i][j];
				}
			}
			return true;
		}
		else
		{
			for(i=0;i<n;i++)//狀態返回
			{
				for(j=0;j<m;j++)
				{
					vis[i][j] = map[i][j];
				}
			}
			return false;
		}
	}
	hd = tem;
	vis[hd.x][hd.y] = '*';
	q.push(hd);
	while(!q.empty())
	{
		hd = q.front();
		q.pop();
		for(i = 0;i <= 3;i++)
		{
			ke.x = hd.x + dire[i][0],ke.y = hd.y + dire[i][1];
			if(ke.x == b.x && ke.y == b.y)
			{
				for(i=0;i<n;i++)//狀態返回
				{
					for(j=0;j<m;j++)
						vis[i][j] = map[i][j];
				}
				return true;
			}
			if(vis[ke.x][ke.y] == ' ' || vis[ke.x][ke.y] == '$')
			{
				vis[ke.x][ke.y] = '*';
				q.push(ke);
			}
		}
	}
	for(i=0;i<n;i++)//狀態返回
	{
		for(j=0;j<m;j++)
		{
			vis[i][j] = map[i][j];
		}
	}
	return false;
}
string autofound()//ai實現
{
	int i,j,t,i1,vi[4],pan;
	double fo,keepfo[4];
	sanke ke,hd,keep[4];
	hd = s.back();
	for(i=0;i<=3;i++)
	{
		keep[i]=ke;
		keepfo[i]=0;
	}
	pan = 0;//是否可以搜蛇尾判定
	for(i = 0;i <= 3;i++)
	{
here:
		j = rand()%4;
		for(i1 = 0;i1 < i;i1++)//生成隨機方向		{
			if(vi[i1]==j)
			{
				goto here;
			}
		}
		vi[i] = j;
		ke.x = dire[j][0],ke.y = dire[j][1];//記錄方向狀態
		if(bfs(ke,s.front()))//此方向可以搜到蛇尾就求次方向和食物的距離
		{
			keep[j] = ke;
			t = j;
			keepfo[j] =sqrt(double((hd.x+ke.x - foodxy.x)*(hd.x+ke.x - foodxy.x) + (hd.y+ke.y - foodxy.y)*(hd.y+ke.y - foodxy.y)));
			fo = keepfo[j];
			pan = 1;
		}
		else
		{
			keepfo[j] = 100000;
		}
	}
	if(pan)
	{
		pan = 0;
		for(i = 0;i <= 3 ;i++)//尋找離食物最近的方向走
		{
			if(fo>keepfo[i])
			{
				t = i;
				fo = keepfo[i];
			}
		}
		if(judge(keep[t].x,keep[t].y))//行走
		{
			if(s.size() == ((n-2)*(m-2)-1))
			{
				return "win";
			}
			else
			{
				return "go on";
			}
		}
	}

}	
char ifscanf()//按鍵判斷
{
	char ch;
	if(kbhit())
	{
		ch=getch();
		return ch;
	}
	else
	{
		return 0;
	}
}
bool judge(int i,int j)//行走判定函數
{
	sanke b,f,tem;
	int k;
	b=s.back ();
	f=s.front();
	if(map[b.x+i][b.y+j]=='$')//若吃了食物
	{
		map[b.x][b.y]='*';
		vis[b.x][b.y]='*';
		Gotoxy(b.x,2*b.y);
		cout<<'*';
		map[b.x+i][b.y+j]='@';
		vis[b.x+i][b.y+j]='@';
		Gotoxy(b.x+i,2*(b.y+j));
		cout<<'@';
		b.x=b.x+i,b.y=b.y+j;
		s.push(b);
		foodpro();
		keepdire.x=i,keepdire.y=j;
		score++;
		ti++;
		Gotoxy(n,10);
		for(k=10;k<=5;k++)
		{
			cout<<' ';
		}
		Gotoxy(n,10);
		cout<<score;
		if(ti==3)
		{
			ti=0;
			second=second*0.95;
		}
		if(ju!=1 && second<100)
		{
			second = 100;
		}
		return true;
	}
	else if((map[b.x+i][b.y+j]=='*' || map[b.x+i][b.y+j]=='I' || map[b.x+i][b.y+j]=='-') && (!(b.x + i == f.x && b.y + j == f.y)))//若撞牆和撞到自己
	{    
		                                                                     //(b.x + i != f.x && b.y + j != f.y  對蛇尾預判忽略
		return false;
	}
	else 
	{
		map[f.x][f.y]=' ';
		vis[f.x][f.x]=' ';
		Gotoxy(f.x,2*f.y);
		cout<<' ';
		s.pop();
		map[b.x][b.y]='*';
		vis[b.x][b.y]='*';
		Gotoxy(b.x,2*b.y);
		cout<<'*';
		map[b.x+i][b.y+j]='@';
		vis[b.x+i][b.y+j]='@';
		Gotoxy(b.x+i,2*(b.y+j));
		cout<<'@';
		b.x=b.x+i,b.y=b.y+j;
		s.push(b);
		keepdire.x=i,keepdire.y=j;
		return true;
	}
}
string move(char ch=0)//按鍵篩選並行走
{
	int i,j;
	if(ch=='p')
	{
		Gotoxy(n+1,0);
		cout<<"暫停中 按任意鍵恢復"<<'\b';
		getch();
		Gotoxy(n+1,0);
		cout<<"按p鍵暫停            "<<endl;

	}
	if(ch == 'f')
	{
		ju = 1;
		return autofound();
	}
	if(keepdire.y)//向左向右走的狀態
	{
		if(ch=='w')
		{
			i = -1, j = 0;
			if(judge(i,j))
			{
				if(s.size()==(n-2)*(m-2)-1)
				{
					return "win";
				}
			}
			else 
			{
				return "loser";
			}
		}
		else if(ch=='s')
		{
			i = 1,j = 0;
			if(judge(i,j))
			{
				if(s.size()==(n-2)*(m-2)-1)
				{
					return "win";
				}
			}
			else 
			{
				return "loser";
			}
		}
		else
		{
			if(judge(keepdire.x,keepdire.y))
			{
				if(s.size()==(n-2)*(m-2)-1)
				{
					return "win";
				}

			}
			else 
			{
				return "loser";
			}
		}
	}
	else//向上或下走的狀態
	{
		if(ch=='a')
		{
			i = 0;j = -1;
			if(judge(i,j))
			{
				if(s.size()==(n-2)*(n-2)-1)
				{
					return "win";
				}
			}
			else 
			{
				return "loser";
			}
		}
		else if(ch=='d')
		{
			i = 0,j = 1;
			if(judge(i,j))
			{
				if(s.size()==(n-2)*(m-2)-1)
				{
					return "win";
				}
			}
			else 
			{
				return "loser";
			}
		}
		else
		{
			if(judge(keepdire.x,keepdire.y))
			{
				if(s.size()==(n-2)*(m-2)-1)
				{
					return "win";
				}
			}
			else 
			{
				return "loser";
			}
		}
	}
	return "go on";
}
int main()
{
	sanke tem;
	int ch,q,q1,i;
	char again;//是否在來一局的判定
	string pan;//輸和贏的判定
	while(1)
	{
		q=0,q1=0;
		srand((unsigned)time(NULL));//以系統時間作爲隨機數的種子
		mapint();
		printf();
		foodpro();
		while(1) 
		{  
			ch=ifscanf();
			if(ju)
			{
				if(ch == 'f')
				{
					ju = 0;
					Gotoxy(n+2,0);
					for(i = 0 ;i <= 20 ;i++)
					{
						cout<<" ";
					}
					continue;
				}
				if(ch == 'p')
				{
					ch = 'p';
				}
				else 
				{
					if(to==0)
					{
						Gotoxy(n+2,0);
					    cout<<"自動尋路中 按f解除..."<<endl;
					}
					ch = 'f';
				}
				to++;
			}
			else
			{
				if(ch == 'f' )
				{
					Gotoxy(n+2,0);
					cout<<"自動尋路中 按f解除..."<<endl;
					ch = 'f';
					to++;
				}
			}
 			pan=move(ch);
			if(pan == "win")
			{
				cout<<"你贏了"<<endl;
				cout<<"再來一把??(y or n)"<<endl;
				cin>>again;
				system("cls");
				q1=1;
			}
			else if(pan=="loser")
			{
				system("cls");
				cout<<"你輸了"<<endl;
				cout<<"再來一把??(y or n)"<<endl;
				cin>>again;
				system("cls");
				q1=1;
			}
			if(q1)
			{
				while(1)
				{
					if(again=='n' || again=='N') exit(0);  
					else if(again=='y' || again=='Y') 
					{
						q=1;
						break;
					}
					else 
					{
						cout<<"你耍我??"<<endl;
						cout<<"再來一把??(y or n)"<<endl;
						cin>>again;
					}
				}
			}
			if(q)break;
			Sleep(second);
		}
	}
	return 0;
}


如果看官們只是看一下ai的話,看到這句話就直接可以alt+f4了,下面我要廢話一下。

                  關於這次的實訓,收穫當然不僅僅是寫出了貪喫蛇,對於樓樓的代碼風格,變量命名習慣,代碼的調試方式都有的一定程度上的糾正。風格自然不用說,篇幅較長的代碼變量名的命名就顯得尤爲重要,隨便就出來了一個a,b什麼的就會讓人看着蛋疼(相信平時刷慣了題習慣了寫短小精悍的程序的acmer應該是多少有點體會)。至於調試(大牛教主請無視),樓樓學到一招在你認爲有問題代碼處用getch()來停頓程序,然後輸出"關鍵信息",當然關鍵信息看具體程序而定,在這裏就是蛇頭座標,蛇尾座標,現在的運動趨勢,將來的運動趨勢等等,對比一下信息就知道哪裏出了問題。

                              還有就是跟我做同一項目的基友,他的通過關率就比我高多了,起碼在80%以上(慚愧啊),我跟他交流了一下思想,本質上差不多,唯一的明顯不同就是他有一個"推空格"操作,簡單的說就是他的蛇尾與蛇頭起碼保持了一個空格的距離,至於爲什麼,他也說不清楚。還有就是一些其他的項目,有一個是手機上比較流行的"pop star",只是在這裏遊戲方式就變成了手動輸入座標,看着個座標的圖案能否消除,本質上就是dfs+圖案移動,看着貌似還挺不錯的樣子,反正之後就是他在也沒玩過這個遊戲,因爲測bug的時候玩到他想吐(其實樓樓玩貪喫蛇也玩到想吐了)。還有就是有人寫五子棋之類的。

                   好吧廢話完畢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章