基于PierreDellacherie算法MFC俄罗斯方块

一、 系统分析

简要概括:
基于对话框实现俄罗斯方块小游戏。可以玩家亲自下,也可以由算法控制自动下。并且能够连接数据库,记录玩家的得分情况以及查看得分记录。
类图:

  1. 主要由五个类组成
    在这里插入图片描述

图1 项目结构图

  1. CGJXDlg类
    在这里插入图片描述

图 2 GJXDlg类图

表1 CGJXDlg类成员变量

变量类型 变量名 功能描述
Game game; Game类成员变量,用于控制游戏的进行以及调用Game类方法。实现游戏的可视化
GJXMYSQL
sql; 数据库成员变量,用于连接数据库并进行相关的数据存取。
int m_score; 与分数编辑框绑定,用于记录玩家的得分
int m_Speed; 与速度编辑框绑定,用户通过编辑框根据调节游戏的速度
CString m_name; 与玩家用户名编辑框绑定。
int bestX;
int bestR 用于暂存当前游戏状态下最好的下落位置x与旋转次数。

  1. Ai类,对当前状态进行评估,并给出分数与优先级。
    在这里插入图片描述
    图3 AI类简要结构图

  2. Game类

在这里插入图片描述
图4 Game类结构图

表2 Game类主要成员变量
变量类型 变量名 功能描述
AI ai; AI类成员变量。来对游戏状态进行评估
static const int NET_WIDTH = 9;
static const int NET_HEIGHT = 20; 全局静态变量,游戏界面的宽和高
int BigNet; 游戏的主页面int指针。用一个9*20大小的一维数组记录当前状态。数组中1代表有小方块,0代表没有。
int SmallNet;
int Loc_X;
int Loc_Y; 游戏右边的小窗口,用于显示当前出场的小方块形状,用一个4
4的一维数组表示。Loc_x,lox_y记录Smallnet实时的左上角坐标。
Tool *tool;
Tool *nextTool; Tool类指针。用实现小方块的生成,旋转操作。Tool指向当前的小方块,nexttool指向下一个将要出现的小方块。
int SCORE; 记录玩家得分
GAME_STATE state; 当前的游戏状态。有Go,STOP,HALL分别是游戏继续,游戏停止,游戏暂停三种状态。
struct Jilv{
int score;
int w;
int roll;
int priority;
}; 自定义结构体Jilv,用于记录某一个游戏状态的相关信息。把当前出场的方块移到横坐标为w并旋转rool次时所得到的状态,该状态的评分为score以及优先级为priority。

  1. Tool类
    在这里插入图片描述

图5 Tool类简要结构图

表3 Tool类主要成员变量
变量类型 变量名 功能描述
int _data[4][4]; 对应于4*4小窗口的状态。
int _type; 小窗口状态对应于那一种模式。一共有七种;正方形,L形,I形,J形状,Z形,倒Z形,山形。从1到7编号

  1. CGJXDlg类
    在这里插入图片描述

图6 CDGJXDLg类简要结构图

  1. GJXMYSQL类
    在这里插入图片描述

图7 GJXMYSQL类简要结构图

二、 主要函数代码说明

  1. AI类中Pierre Dellacherie算法的实现
    1.1. Pierre Dellacheriee评估算法主要有六个评价指标:
    1.1.1. landingHeight(下落高度):指板块在放置后,放置点距离游戏底部区域的距离
    1.1.2. 游戏中,loc_x与loc_y保存的就是板块左上角的坐标。放置点距离游戏底部区域的距离则为Height-loc_y。
    1.1.3. erodedPieceCellsMetric(消行数):每个板块都由4个小方块 组成,若在消行的过程中,将这4个小方块全部消除掉,则记为 “行数*4”,这是期盼的结果。但若在消行的过程中,仅消除了 一个小方块。则此时结果是1,这样容易导致“空洞”产生。
int removelinNum=0;//消除的行数
int RemoveIndex[4];//记录消除的h坐标
memset(RemoveIndex,-1,4);
for(int k=0;k<this->Height;++k)
{
	if(this->CanRemoveLine(Copybignet,k))
	{
		RemoveIndex[removelinNum]=k;
		removelinNum++;
	}	
}
int sum=0;//用与统计smallnet中对消除行的贡献值
for(int k=0;k<removelinNum;++k)//统计消除的行中smallnet所贡献的方块数
{
	if(RemoveIndex[k]-locy<=3)//说明所消失的这一行属于smallnet的4*4范围。所消除的行h值RemoveIndex[k]
	{
		for(int m=0;m<4;++m)
		{
			if(CopySmallNet[(RemoveIndex[k]-locy)*4+m]==1)
			{
					sum++;
			}
		}
	}
}

1.1.4. boardRowTransitions(行变换):每行中出现的空的小方格变 换为实的小方格,或者实的小方格变换为空的小方格,这个过 程视作一次行变换。该参数是各行发生行变换的次数之和,反映小方块摆放的紧密程度。小方块摆放得越紧密,则其间的空 格就越少,变换则越少。
何为“变换”?以图8为例:
途中第一行有7次变换,第二行有六次变换。

在这里插入图片描述

图8 变换举例说明

对窗口进行遍历,当前格子与其前一个格子不同时,则判定为一次变换。注意的是不把两侧算作边界。而是当作一个环去计算行变换数。对每行最后一个的下一个要进行特殊赋值为行首。

// 行变换数。按行遍历,共20行,从空白格进入被占格算作一次变换,
从被占格进入空白格也算作一次变换。所有行的变换次数之和即为返回值。

int AI::GetBoardRowTransitions(int* BigNet,int h,int w)
{
	int sum=0;
	for(int i=0;i<h;i++)
	{
		for(int j=0;j<w-1;j++)
		{
			if(BigNet[i*w+j]!=BigNet[i*w+j+1])
//两侧不算做边界,即从左侧平移接触到边界后会从右侧平移出来),
//不把两侧算作边界。而是当作一个环去计算行变换数。同时防止溢出
			{
				sum++;
			}
		}
		if(BigNet[i*w+w-1]!=BigNet[i*w])
			sum++;
	}
	return sum;
}

1.1.5. boardColTranstions(列变换):每列中出现的空的小方格变 换为实的小方格,或者实的小方格变换为空的小方格,这个过 程视作一次列变换。该参数是各列发生行变换的次数之和。 该参数同样反映了小方块摆放的紧密程度。

// 列变换数:同行变换数类似,只不过换成了按列遍历
int AI::GetBoardColTransitions(int* BigNet,int h,int w)
{
	int sum=0;
	for(int j=0;j<w;++j)
	{
		for(int i=0;i<h-1;++i)
		{
			if(BigNet[i*w+j]!=BigNet[i*w+j+w])
			{
				sum++;
			}
		}
	}
	return sum;
}

1.1.6. boardBuriedHoles:每列中“空洞”的小方格数之和。“空洞” 指某一列中顶端被消方块填堵住的小方格,如图所示。
在这里插入图片描述

图9 空洞举例说明
图9中就有空洞数位6。实现过程:
每列设置一个标志位f,初始值为false。在遇到第一个方格时设置为true,开始计数。通过观察发现从0到1变换则为一个空洞。对于最后一行的小格子要特殊判断一下。所以列遍历从0到height-2,逐次比较当前与下一列的值。最后一列特殊判断。

// 空洞数,空洞指的是,每列中某个方块下面没有方块的空白位置,该空白可能由
1 个单位或多个单位组成,
//但只要没有被方块隔断,都只算一个空洞。注意,空洞的计算以列为单位,

//若不同列的相邻空格连在一起,不可以将它们算作同一个空洞。
int AI::GetBoardBuriedHoles(int* BigNet,int h,int w)
{
	int sum=0;
	for(int i=0;i<w;++i)
	{
		bool f=false;
		for(int j=0;j<h-1;++j)
		{
			if(BigNet[j*w+i]==1)
			{
				f = true;
			}
			if(f&&BigNet[j*w+i]==0&&(BigNet[j*w+i+w]==1)){
				sum++;
			}
		}
		if(f&&BigNet[(h-1)*w+i]==0){
			sum++;
		}
	}
	return sum;
}

1.1.7. BoardWells井是俄罗斯方块中最差的局面,很容易导致程序结束,应最大程度的避免该情况的发生。
在这里插入图片描述

图10 井举例说明
如图10 所示,图中一共有两个井深度分别为2和3。所以其评估返回值为(1+2)+(1+2+3)=9。
井数,与字面意义一样–水井一样的个数。井指的是某一列中,两边都有方块的连续空格,(左右两侧看成一个环,不算做边界)。实现方法如下:

int AI::GetBoardWells(int* BigNet,int h,int w)
{
	int sum=0;
	for(int i=0;i<w;++i)
	{
		int deep=0;
		for(int j=0;j<h;++j)
		{
			int now=j*w+i;
			int lef=j*w+i-1;
			int right=j*w+i+1;
			if(lef==j*w-1)
			{
				lef=j*w+w-1;
			}
			if(right==j*w+w)
			{
				right=j*w;
			}
			if(BigNet[now]==0&&BigNet[lef]==1&&BigNet[right]==0)
			{
				deep++;
				sum+=deep;
			}
			else
			{
				deep=0;
			}
		}
	}
	return sum;
}
  1. Game类中
    2.1. GJXAIPlay方法。实现对于当前的方块自动模拟下,找到最好的下法并返回给CGJXDlg类中执行。要想找到最好的方法,必须的模拟出所有的下法,并且调用AI类中的评估函数的到每一下法的evaluate评分,最后根据分数来选择最好的下法。
    2.2. 实现框架:在实现时,需要双重循环来遍历所有的下法,外层循环是方块下落位置的横坐标x,从0到width-1。内层循环是旋转次数从0到4。(一个方块通过旋转,最多有四种形式,有的甚至只有一种或两种,这里为了方便统一进行旋转四次,会有一些冗余但不影响评分)。具体实现步骤如下:
    2.2.1. 首先对当前的Bignet与smallNet进行拷贝。
int*  Game::CopyBigN()
{
	int *copy;
	copy=(int*)malloc(sizeof(int)*this->Height*this->Width);
	for(int i=0;i<Height;i++)
		for(int j=0;j<Width;j++)
			copy[i*Width+j] = BigNet[i*Width+j];
	
	return copy;
}
int*  Game::CopySmallN()
{
	int *copy;
	copy=(int*)malloc(sizeof(int)*this->Height*this->Width);
	for(int i=0;i<4;i++)
		for(int j=0;j<4;j++)
			copy[i*4+j] = SmallNet[i*4+j];
	return copy;
}

2.2.2. 然后执行旋转操作。(旋转同时,将方块移动到4*4窗口的左上角)

void Tool::Roll()
{
	int tmp[4][4];
	for(int i=0;i<4;i++)
		for(int j=0;j<4;j++)
			tmp[i][j] = _data[i][j];//tem暂存当前形状

	for(int i=0;i<4;i++)
		for(int j=0;j<4;j++)
			_data[i][j] = tmp[j][3-i];

	for(int k=0;k<4;++k)//向上对其
	{

		int cont=0;
		for(int i=0;i<4;++i)
		{
			if(_data[0][i]==0)
				cont++;
		}
		if(cont==4)
		{
			for(int i=0;i<3;i++)
				for(int j=0;j<4;j++)
					_data[i][j] = _data[i+1][j];//行向前平移
			_data[3][0] = _data[3][1]=_data[3][2]=_data[3][3]=0;
//最后一行赋值为0
		}
		else
			break;
	}
	for(int k=0;k<4;++k)//向左对其
	{		
		int cont=0;
		for(int i=0;i<4;++i)
		{
			if(_data[i][0]==0)
				cont++;
		}
		if(cont==4)
		{			
			for(int i=0;i<4;i++)
				for(int j=0;j<3;j++)
					_data[i][j] = _data[i][j+1];//列向前平移
			_data[0][3] = _data[1][3]=_data[2][3]=_data[3][3]=0;
//最后一列赋值为0
		}
		else
			break;
	}
}

2.2.3. 最后将方块竖直向下放置然后更新BigNet

while (this->CanMoveDown(Copybignet,i,locy,CopySmallNet))
{
	locy++;
}

	locy--;
	for(int k=0;k<4;k++)//再将新方块添加进去
		for(int m=0;m<4;m++)
		{
			if( k+locy>=0      &&
				k+locy<Height  &&
				m+locx>=0      &&
				m+locx<Width   &&
				CopySmallNet[k*4+m])
				Copybignet[(k+locy)*Width+m+locx] = 1;
		}

2.2.4. 最后调用AI.Evaluate()方法评估当前下法。
2.2.5. 在模拟的过程中,对每一种下法都定义了临时自定义结构体Jilv变量tem来保存方块的位置x,旋转次数roll,评分score以及优先级。
同时定义了一个Jilv类型的Vector用来存放当前所有下法的信息。JILU是Jilv类型的Vector。Tem是临时jilv类型的变量。
2.2.6. 最后通过遍历vector来找到最好的下法,并返回最好下法的横坐标w与旋次数rool。

int max=0;//最优解下标
	int a[2];
	for (int i=1;i<JILU.size();++i)
	{
		if(JILU[max].score<JILU[i].score)
			max=i;
		else if(JILU[max].score==JILU[i].score)
		{
			if(JILU[max].priority<JILU[i].priority)
				max=i;
		}
	}
	a[0]=JILU[max].roll;
	a[1]=JILU[max].w;
	//printf("旋转:%d x:%d\n",a[0],a[1]);
	return a;

2.3. StartAI方法。是AI开始的入口,完成初始化工作。

int Game::StartAi(void)
{
	tool     = NULL;
	nextTool = NULL;
	state = GO;

	BigNet   = (int *)malloc(sizeof(int)*Height*Width);
	SmallNet = (int *)malloc(sizeof(int)*4*4);

	for(int i=0;i<Height;i++)
		for(int j=0;j<Width;j++)
			BigNet[i*Width+j] =0;
	// 小窗口积木出场,更换新的积木,设置出场位置
	Loc_Y = 0;
	Loc_X = (Width-4)/2;
	tool = nextTool;
	//更新小窗口信息
	srand((unsigned int) time(0));
	nextTool = new Tool(0);
	//rand()%7+1
	SmallNet = nextTool->GetData();
	NextTool();
	return 0;
}

2.4. GoAI方法。对游戏过程中的每一步进行判断。如果当前方块还没下落到底部则继续下落,否则进行消除操作,最后判断游戏是否结束。没有结束时则生成下一个方块,调用GJXAIPlay方法继续游戏

bool Game::GOAI(void)
{
	if(CanMoveDown())
	{
		MoveDown();
		return true;
	}
	else
	{
		AddTool(this->BigNet);
		RemoveLines();
		if(IsDead())
		{
			state = STOP;
			return false;
		}
		else
		{
			NextTool();
			int* a=this->GJXAIPlay();
//返回最好的位置x以及变换次数R。a[0]是变换次数
			int b[2];
			b[1]=a[1];
			b[0]=a[0];
			int x=this->Loc_X;				
			while (b[0]>0 )
			{
				//if(this->CanRoll())
				{
					this->Roll();
					b[0]--;
				}
			}
			while(b[1]!=this->Loc_X)
			{
				if(b[1]>this->Loc_X)
				{
					this->MoveRight();
				}else if (b[1]<this->Loc_X)
				{
					this->MoveLeft();
				}
				else break;
			}
				printf("locx%d",this->Loc_X);
			return true;
		}
	}
}

2.5. 可执行判断:
这些方法判断的主要依据就是:
执行变换操作后,小方块落入到游戏场景后,游戏场景的方块总数是正确的。例如判断能否下落,当下落后界面的总方块数是当前小方块数4+已有方块数。如果执行变换操作后数量不对,说明这次操作是不合理的,不能执行。判断能否左右移与旋转也是类似的原理。
bool CanMoveDown();//判断能否下落
bool CanMoveDown(int* Bnet,int x,int y,int *Snet);
bool CanMoveLeft();判断能否左移
bool CanMoveRight();判断能否右移
bool CanRoll();判断能否旋转。

bool Game::CanMoveDown(int* Bnet,int x,int y,int *Snet)
{
	if(y>=Height)
		return false;
	int cnt1 = 4,cnt2=0;
	int *tmp = (int *)malloc(sizeof(int)*Height*Width);
	// 复制一个副本,统计原有方块数+tool中的块数
	for(int i=0;i<Height;i++)
		for(int j=0;j<Width;j++)
		{
			tmp[i*Width+j] = Bnet[i*Width+j];
			cnt1 += tmp[i*Width+j];
		}
		// 假设发生变换
		for(int i=0;i<4;i++)
			for(int j=0;j<4;j++)
			{
				if( i+y>=0      &&
					i+y<Height  &&
					j+x>=0      &&
					j+x<Width   &&
					Snet[i*4+j])
					tmp[(i+y)*Width+j+x] = 1;
			}
			// 统计变换后方块数
			for(int i=0;i<Height;i++)
				for(int j=0;j<Width;j++)
					cnt2 += tmp[i*Width+j];
			delete(tmp);
			return cnt2==cnt1;
}

2.6. 行消除操作:
2.6.1. bool CanRemoveLine(int index)判断第index行能否消除

bool Game::CanRemoveLine(int* net,int index)
{
	int count = 0;

	for(int i=0;i<Width;i++)
		if(net[index*Width+i]==1)
//二维转一维索引,对index行进行判断,1表示index行i列有方块
			count ++;
	return count==Width;
}

2.6.2. void RemoveLine(int index)消除第index行

void Game::RemoveLine(int index)
{
	for(int i=index;i>0;i--)
		for(int j=0;j<Width;j++)
			BigNet[i*Width+j] = BigNet[(i-1)*Width+j];
	for(int j=0;j<Width;j++)
		BigNet[j] = 0;
	
}

2.6.3. void RemoveLines();在执行消除操作时,更新分数。

void Game::RemoveLines()
{
	int sum=0;
	for(int i=Height-1;i>0;i--)
		while(CanRemoveLine(i))
		{
			sum++;
			RemoveLine(i);
		}
		if(sum==1)
		{
			SCORE+=10;
		}
		else if(sum==2)
		{
			SCORE+=30;
		}
		else if(sum==3)
		{
			SCORE+=60;
		}
		else if(sum==4)
		{
			SCORE+=100;
		}			
}
  1. CGJXDlg类
    3.1. 该类中,主要实现游戏界面的绘制工作,同时实现玩家与程序的交互功能。通过界面,玩家可以根据需要修改游戏的速度,输入玩家的姓名。在游戏结束时,能够展示玩家得分以及历史分数的记录。界面效果如图11所示:
    在这里插入图片描述

图 11 程序界面图

3.2. 游戏界面的绘制
3.2.1. 绘制主界面

void CGJXDlg::DrawBigNet()
{
	CRect rect;
	CWnd *wnd = GetDlgItem(IDC_PIC_MAIN);
	CPaintDC dc(wnd);

	wnd->GetClientRect(&rect);

	if(game->GetBigNet())
	{
		for(int i=0;i<game->NET_HEIGHT;i++)
			for(int j=0;j<game->NET_WIDTH;j++)
				if(game->GetBigNet()[i*(game->NET_WIDTH)+j]==1)
				{
					dc.Rectangle(
						j*rect.Width()/game->NET_WIDTH,
						i*rect.Height()/game->NET_HEIGHT,
						(j+1)*rect.Width()/game->NET_WIDTH,
						(i+1)*rect.Height()/game->NET_HEIGHT);
				}
	}
	wnd->RedrawWindow();
}

3.2.2. 绘制方块的小界面

void CGJXDlg::DrawSmallNet()
{
	CRect rect;
	CWnd *wnd = GetDlgItem(IDC_PIC_SMALL);
	CPaintDC dc(wnd);
	wnd->GetClientRect(&rect)
	if(game->GetSmallNet())	
	{
		for(int i=0;i<4;i++)
			for(int j=0;j<4;j++)
				if(game->GetSmallNet()[i*4+j]==1)
				{
					dc.Rectangle(
						j*rect.Width()/4,
						i*rect.Height()/4,
						(j+1)*rect.Width()/4,
						(i+1)*rect.Height()/4);
				}
	}
	wnd->RedrawWindow();
}

3.3. 按钮的响应函数
3.3.1. AIPLay按钮

void CGJXDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	game->SCORE=0;
	UpdateData(TRUE);//控件值到变量
	this->sql->Select(m_diaplay);//查询数据,读入到listBox中
	UpdateData(FALSE);//拷贝值到控件
	game->StartAi();
	SetTimer(2,510-m_Speed,NULL);
		int* a=game->GJXAIPlay();
//返回最好的位置x以及变换次数R。a[0]是变换次数
		int b[2];
		b[1]=a[1];
		b[0]=a[0];		
		int x=game->Loc_X;	
		while (b[0]>0 )
		{
			game->Roll();
			b[0]--;			
		}
		while(b[1]!=game->Loc_X)
		{
			if(b[1]>game->Loc_X)
			{
				game->MoveRight();
			}else if (b[1]<game->Loc_X)
			{
				game->MoveLeft();
			}
			else break;
		}
		printf("locx%d",game->Loc_X);
}

3.3.2. 暂停开始按钮

// 继续与暂停按钮点击
void CGJXDlg::OnBnClickedButtonHalt()
{
	UpdateData(TRUE);//控件值到变量
	UpdateData(FALSE);//拷贝值到控件
	
	game->HaltOrContinue();
	if(game->GetState()==HALT)
	{
		KillTimer(1);
		KillTimer(2);
	}

	if(game->GetState()==GO)
		SetTimer(1,510-m_Speed,NULL);
}

3.3.3. 开始重新开始按钮

void CGJXDlg::OnBnClickedButtonStart()
{
	game->SCORE=0;
	UpdateData(TRUE);//控件值到变量
	this->sql->Select(m_diaplay);//查询数据,读入到listBox中
	UpdateData(FALSE);//拷贝值到控件	
	game->Start();
	SetTimer(1,510-m_Speed,NULL);
}

3.4. Ontimer响应函数
在游戏开始完成初始化时,都设置了一个timer定时器。游戏能够有序进行下去主要依靠定时器与响应函数。
在响应函数中进行游戏状态的判断以及规划游戏的下一个操作。

// 实现这个方法  定时器
void CGJXDlg::OnTimer(UINT_PTR nIDEvent)
{
	if(nIDEvent==2)
	{
		if(!game->GOAI())
		{
			KillTimer(2);
			TCHAR *msg = _T("Game Over!");
			this->sql->Query(m_name,m_score);
			MessageBox(msg);
		}
		m_score=game->SCORE;
		UpdateData(FALSE);
		Invalidate(true);// 重绘画面
	}
	else if(!game->Go())
	{
		KillTimer(1);
		TCHAR *msg = _T("Game Over!");
		this->sql->Query(m_name,m_score);		
		MessageBox(msg);
	}
	else
	{
		m_score=game->SCORE;
		UpdateData(FALSE);
		Invalidate(true);// 重绘画面
	}	
}
  1. GJXMYSQL类
    4.1. 查询数据库,并将查询结果显示在listBox中
void GJXMYSQL::Select(CListBox& m_diaplay)
{
	const char name[] = "gong";
	const char pswd[] = "12345678";
	const char host[] = "localhost";
	const char table[] = "record";
	int port = 3306;
	MYSQL_RES *result; 
	MYSQL_ROW row;
	MYSQL mysqlCon;
	mysql_init(&mysqlCon);
	if (!mysql_real_connect(&mysqlCon, host, name, pswd, table, port, NULL, 0))
	{
		AfxMessageBox(_T("访问数据库失败!"));
	}
	else
	{
		mysql_query(&mysqlCon, "SET USER GBK"); //设置字符集
		//AfxMessageBox(_T("访问数据库成功!"));
	}
	CString insert;
	insert=_T("select * from  recodscore  order by score Desc");
	//AfxMessageBox(insert);
	if (mysql_query(&mysqlCon, (char*)(LPCTSTR)insert) == 0)
	{
		m_diaplay.ResetContent();
		int nIndex=m_diaplay.AddString("Player  score");
		CString Player,score;		mysql_real_query(&mysqlCon,insert,(unsignedlong)strlen(insert));
		result= mysql_store_result(&mysqlCon);//mysql_free_result(result);
		while((row = mysql_fetch_row(result)))
		{ 
//row是字符数组,元素个数取决于数据库表的关键字个数,按顺序一一对应
			Player=row[0];
			score=row[1];			nIndex=m_diaplay.InsertString(nIndex+1,(LPCTSTR)(Player+"         "+score));
		}
	}
	else {
		AfxMessageBox(_T("添加失败!"));
	}
	//释放内存空间
	mysql_free_result(result);
	mysql_close(&mysqlCon);	
}

4.2. 向数据库中添加信息。

void GJXMYSQL::Select(CListBox& m_diaplay)
{
	const char name[] = "gong";
	const char pswd[] = "12345678";
	const char host[] = "localhost";
	const char table[] = "record";
	int port = 3306;
	MYSQL_RES *result; 
	MYSQL_ROW row;
	MYSQL mysqlCon;
	mysql_init(&mysqlCon);
	if (!mysql_real_connect(&mysqlCon, host, name, pswd, table, port, NULL, 0))
	{
		AfxMessageBox(_T("访问数据库失败!"));
	}
	else
	{
		mysql_query(&mysqlCon, "SET USER GBK"); //设置字符集
		//AfxMessageBox(_T("访问数据库成功!"));
	}

	CString insert;
	insert=_T("select * from  recodscore  order by score Desc");
	//AfxMessageBox(insert);
	if (mysql_query(&mysqlCon, (char*)(LPCTSTR)insert) == 0)
	{
		m_diaplay.ResetContent();
		int nIndex=m_diaplay.AddString("Player  score");
		CString Player,score;
		mysql_real_query(&mysqlCon,insert, (unsigned long)strlen(insert));
		result = mysql_store_result(&mysqlCon);//mysql_free_result(result);
		while((row = mysql_fetch_row(result)))
		{ 
			//row是字符数组,元素个数取决于数据库表的关键字个数,按顺序一一对应
			Player=row[0];
			score=row[1];			nIndex=m_diaplay.InsertString(nIndex+1,(LPCTSTR)(Player+"         "+score));
		}
	}
	else {
		AfxMessageBox(_T("添加失败!"));
	}
	mysql_free_result(result); //释放内存空间
	mysql_close(&mysqlCon);	
}

三、程序运行界面截图

  1. 由玩家控制:
    点击开始按钮后,玩家通过键盘上下左右来控制下落的方块。
    在这里插入图片描述

图12玩家运行界面图

  1. 由AI控制。
    点击AIPlay按钮后,就能实现AI自动控制,自行下到游戏结束:
    在这里插入图片描述

图13 AI游戏界面图
在这里插入图片描述

图14 AI游戏界面图

演示录屏

基于Pierre Dellacherie算法MFC实现俄罗斯方块

源代码下载链接

源码下载

参考文档:

[1] 基于Pierre Dellacherie算法的俄罗斯方块机器人_wjl_zyl_1314的博客-CSDN博客
[2] 基于Pierre Dellacherie算法实现俄罗斯方块的人工智能(python实现)《三》_charming的博客-CSDN博客
[3] H5版俄罗斯方块(3)—游戏的AI算法 - mumuxinfei - 博客园 (cnblogs.com)
[4] El-Tetris – An Improvement on Pierre Dellacherie’s Algorithm | imake
[5] 基于Pierre Dellacherie算法的俄罗斯方块游戏的实现 - 中国知网 (cnki.net)
[6] 基于Pierre Dellacherie算法的俄罗斯方块游戏的研究和实现 - 中国知网 (cnki.net)
[7] 随机生成方块,随机数问题https://www.zhihu.com/question/313011021/answer/605863733

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嗯哼_Hello

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值