一、 系统分析
简要概括:
基于对话框实现俄罗斯方块小游戏。可以玩家亲自下,也可以由算法控制自动下。并且能够连接数据库,记录玩家的得分情况以及查看得分记录。
类图:
- 主要由五个类组成
图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与旋转次数。
-
Ai类,对当前状态进行评估,并给出分数与优先级。
图3 AI类简要结构图 -
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; 游戏右边的小窗口,用于显示当前出场的小方块形状,用一个44的一维数组表示。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。
- Tool类
图5 Tool类简要结构图
表3 Tool类主要成员变量
变量类型 变量名 功能描述
int _data[4][4]; 对应于4*4小窗口的状态。
int _type; 小窗口状态对应于那一种模式。一共有七种;正方形,L形,I形,J形状,Z形,倒Z形,山形。从1到7编号
- CGJXDlg类
图6 CDGJXDLg类简要结构图
- GJXMYSQL类
图7 GJXMYSQL类简要结构图
二、 主要函数代码说明
- 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;
}
- 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;
}
}
- 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);// 重绘画面
}
}
- 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);
}
三、程序运行界面截图
- 由玩家控制:
点击开始按钮后,玩家通过键盘上下左右来控制下落的方块。
图12玩家运行界面图
- 由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