背景
使用Qt5.12.9的QGraphicsItem來實現(xiàn)俄羅斯方塊,使用簡單的評估函數(shù),實現(xiàn)AI機器人玩俄羅斯方塊游戲。這是AI機器人的第一步,這個算法很簡單,但很有效,大多數(shù)情況能消5百層以上,最近的為數(shù)不多的測試中,最高紀錄已經(jīng)消了超過2500層。在這個基礎(chǔ)上,可以方便的積累原始數(shù)據(jù),我希望能抽取模式,進行模式識別及至機器學(xué)習(xí)。
思路
在手動游戲基礎(chǔ)上進行改造,借鑒回放的經(jīng)驗,只需要加入一個評估算法,為每一個新方塊找出一個放置的姿態(tài)(旋轉(zhuǎn)次數(shù))和最終位置坐標就可以了。我的算法設(shè)計也很簡單,就是為每一個方塊窮舉其放置方法,使用一個緊密程度的評估算法進行評分,取出最高分的操作,若有相同得分的操作,用隨機數(shù)二一添做五。
效果圖
關(guān)鍵代碼分析
流程控制
界面操作控制變量,做到隨時可以在手動與自動兩種模式之間進行切換。
if (isAutoRunning) { //自動模式
autoProcessCurBlock(); //處理當(dāng)前方塊,使用評估函數(shù)確定方塊的最終姿態(tài)與位置
block->relocate(curPos); //放置
block->setBlockNotActive(); //固定方塊
generateNextBlock(); //取下一個方塊,游戲繼續(xù)
}else //手動模式
this->moveBlockDown();
...
方塊放置評分函數(shù)
我的設(shè)計思想很直觀,俄羅斯方塊就是要盡量緊密的堆積在一起,所以對每一個組成方塊的block都檢測一個它周圍的四個位置,看是否有block(包括邊界)存在,若有就加1分,沒有不加分。這塊的加分,并沒有區(qū)別組成方塊自身的block和外界的block,因為每個方塊都是與自己進行比較,所以區(qū)分與不區(qū)分效果是一樣的。起始分由深度確定,越深我認為效果越好。另個,block的垂直下方最好不要有空洞,若有會減分。
int Game::evaluate(Tetris* t)
{
QPoint pos = t->getPos();
int ct = pos.y(); //深度為基礎(chǔ)分
int cct = t->cleanCount();
if (cct > 1) //能消層,加分
ct += 10 * (cct - 1);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (t->data[i][j]) {
ct += t->hasTetrisBlock(pos.x() + j + 1, pos.y() + i) ? 1 : 0; //檢測block右邊的位置
ct += t->hasTetrisBlock(pos.x() + j - 1, pos.y() + i) ? 1 : 0; //檢測block左邊的位置
ct += t->hasTetrisBlock(pos.x() + j, pos.y() + i + 1) ? 1 : 0; //檢測block下方的位置
ct += t->hasTetrisBlock(pos.x() + j, pos.y() + i - 1) ? 1 : 0; //檢測block上方的位置
if (i == 3 || t->data[i + 1][j] == 0) {
if (!t->hasTetrisBlock(pos.x() + j, pos.y() + i + 1)) { //block下方的緊臨空洞
ct -= 4;
}
else {
int k = 2;
while (pos.y() + i + k <= 19) {
if (!t->hasTetrisBlock(pos.x(), pos.y() + i + k)) { //block下方的非緊臨空洞
ct -= 1;
break;
}
k++;
}
}
}
}
}
}
return ct;
}
窮舉方塊的所有放置方式
一個方塊最多只有四種姿態(tài),把方塊的每一種姿態(tài)都從左到右moveDownEnd一次,進行評分,取得分最高的方案。
void Game::autoProcessCurBlock()
{
int max = 0;
QPoint initPos = block->getPos();
Tetris* tmp = new Tetris(initPos, block->getShape(), -1); //構(gòu)造當(dāng)前方塊的替身,blockType為-1,這種方塊不會顯示
int rotateCt = block->getRotateNum(); //同步替身初始姿態(tài)
for (int k = 0; k < rotateCt; k++)
tmp->rotate();
rotateCt = 0; //用于保存方塊的最終姿態(tài)
for (int r = 0; r < 4; r++) { //四種姿態(tài)遍歷,其實可以優(yōu)化,有的方塊不需要四次
if (r > 0) {
tmp->relocate(initPos); //注意,旋轉(zhuǎn)要在方塊進入游戲界面的地方旋轉(zhuǎn),不然可能旋轉(zhuǎn)不成功
tmp->rotate();
}
while (tmp->moveLeft()); //從最左邊開始
do {
tmp->moveDownEnd();
tmp->setBlockNotActive(); //固定方塊,以便進行評分
int score = evaluate(tmp); //評分
if (score > max) { //找到當(dāng)前最優(yōu)方案
max = score;
curPos = tmp->getPos();
rotateCt = r;
}
else if (score == max) { //出現(xiàn)相等評分,隨機取
if (qrand() % 2 == 1) {
curPos = tmp->getPos();
rotateCt = r;
}
}
//initPos.setX(tmp->getPos().x());
tmp->relocate(QPoint(tmp->getPos().x(), initPos.y())); //返回到游戲空間上方
tmp->setBlockTest(); //方塊恢復(fù)到測試狀態(tài)
} while (tmp->moveRight()); //方塊右移,直到不能移動
}
delete tmp; //銷毀測試方塊,突然想到這塊可以優(yōu)化,只需要建七個方塊就好,這樣就不用不斷的創(chuàng)建和銷毀了
for (int k = 0; k < rotateCt; k++)
block->rotate();
}
下一步的設(shè)想
使用python重新實現(xiàn)所有功能,也不再用Qt,就用python自帶的tkinter就好。把重點放在模式提取,讓AI自動玩游戲,寫個算法,提取優(yōu)秀的操作模式。然后使用模式匹配或機器學(xué)習(xí)算法來優(yōu)化AI。現(xiàn)在還沒有具體的想法,只有這么個大概的設(shè)想。
源代碼及運行方法
項目采用cmake組織,請安裝cmake3.10以上版本。下面腳本是windows下基于MSVC的,其它操作系統(tǒng)上基本類似,或者使用qtcreator打開進行操作。
cmake -A win32 -Bbuild .
cd build
cmake --build . --config Release
注:本項目采用方案能跨平臺運行,已經(jīng)適配過windows,linux,mac。
源代碼:
https://gitee.com/zhoutk/qtetris.git
或
https://gitee.com/zhoutk/qtdemo/tree/master/tetrisGraphicsItem
或
https://github.com/zhoutk/qtDemo/tree/master/tetrisGraphicsItem