H5版俄罗斯方块(3)---游戏的AI算法
前言:
算是"long long ago"的事了, 某著名互联网公司在我校举行了一次"lengend code"的比赛, 其中有一题就是"智能俄罗斯方块". 本着一向甘做分母, 闪耀分子的绿叶精神, 着着实实地打了一份酱油. 这次借学习H5的机会, 再来重温下俄罗斯方块的AI编写.
本系列的文章链接如下:
1). 需求分析和目标创新
2). 游戏的基本框架和实现
这些博文和代码基本是同步的, 并不确定需求是否会改变, 进度是否搁置, 但期翼自己能坚持和实现.
演示&下载:
该版本依旧较为简陋, 效果如图所示:
其代码下载地址为: http://pan.baidu.com/s/1sjyY7FJ
下载解压目录结构如下所示:
点击tetris.html, 在浏览器上运行(由于HTML5程序, 最好在Chrome/Firefox上运行).
算法分析:
核心算法参考了如下博文:
• 传统规则俄罗斯方块AI技术介绍
• 控制台彩色版带AI的『俄罗斯方块』
本程序也采用改进的Pierre Dellacherie算法(只考虑当前方块).
其对局面的评估, 采用6项指标:
1). Landing Height(下落高度): The height where the piece is put (= the height of the column + (the height of the piece / 2))
2). Rows eliminated(消行数): The number of rows eliminated.
3). Row Transitions(行变换): The total number of row transitions. A row transition occurs when an empty cell is adjacent to a filled cell on the same row and vice versa.
4). Column Transitions(列变换): The total number of column transitions. A column transition occurs when an empty cell is adjacent to a filled cell on the same column and vice versa.
5). Number of Holes(空洞数): A hole is an empty cell that has at least one filled cell above it in the same column.
6). Well Sums(井数): A well is a succession of empty cells such that their left cells and right cells are both filled.
对各个指标进行加权求和, 权重系数取自经验值:
1 -4.500158825082766
2 3.4181268101392694
3 -3.2178882868487753
4 -9.348695305445199
5 -7.899265427351652
6 -3.3855972247263626
源码解读:
代码文件结构如图所示:
• tetris_base.js: 公共的数据结构, 包括方块定义和方块池等
• tetris_ai.js: 具体定义了AI的核心算法和数据结构.
• tetris_game.js: 是整个程序的展示和驱动.
这边主要讲讲tetris_ai.js这个代码文件, 里面有三个重要的类, MoveGenerator, Evaluator, AIStrategy.
MoveGenerator用于生成各个可行落点以及对应的路径线路:
/*
* @brief 走法生成器
*/
function MoveGenerator() {
} MoveGenerator.prototype.generate = function(tetrisUnit, shape) { var keymapFunc = function(x, y, idx) {
return "" + x + ":" + y + ":" + idx;
} var moveMapFunc = function(step) {
return {x:step.x, y:step.y, idx:step.idx};
} var results = []; var boards = tetrisUnit.boards;
var rownum = tetrisUnit.row;
var colnum = tetrisUnit.col;
var shapeArrs = shape.shapes; var occupy = {} var actionQueues = [];
actionQueues.push({x:shape.x, y:shape.y, idx:shape.idx, prev:-1});
occupy[keymapFunc(shape.x, shape.y, shape.idx)] = true; var head = 0;
while ( head < actionQueues.length ) {
var step = actionQueues[head]; // 1). 向左移动一步
var tx = step.x - 1;
var ty = step.y;
var tidx = step.idx;
if ( tetrisUnit.checkAvailable(tx, ty, shapeArrs[tidx]) ) {
var key = keymapFunc(tx, ty, tidx);
if ( !occupy.hasOwnProperty(key) ) {
actionQueues.push({x:tx, y:ty, idx:tidx, prev:head});
occupy[key] = true;
}
} // 2). 向右移动一步
tx = step.x + 1;
ty = step.y;
tidx = step.idx;
if ( tetrisUnit.checkAvailable(tx, ty, shapeArrs[tidx]) ) {
var key = keymapFunc(tx, ty, tidx);
if ( !occupy.hasOwnProperty(key) ) {
actionQueues.push({x:tx, y:ty, idx:tidx, prev:head});
occupy[key] = true;
}
} // 3). 旋转一步
tx = step.x;
ty = step.y;
tidx = (step.idx + 1) % 4;
if ( tetrisUnit.checkAvailable(tx, ty, shapeArrs[tidx]) ) {
var key = keymapFunc(tx, ty, tidx);
if ( !occupy.hasOwnProperty(key) ) {
actionQueues.push({x:tx, y:ty, idx:tidx, prev:head});
occupy[key] = true;
}
} // 4). 向下移动一步
tx = step.x;
ty = step.y + 1;
tidx = step.idx;
if ( tetrisUnit.checkAvailable(tx, ty, shapeArrs[tidx]) ) {
var key = keymapFunc(tx, ty, tidx);
if ( !occupy.hasOwnProperty(key) ) {
actionQueues.push({x:tx, y:ty, idx:tidx, prev:head});
occupy[key] = true;
}
} else { // *) 若不能向下了, 则为方块的一个终结节点.
var tmpMoves = [];
tmpMoves.push(moveMapFunc(step));
var tprev = step.prev;
while ( tprev != -1 ) {
tmpMoves.push(moveMapFunc(actionQueues[tprev]));
tprev = actionQueues[tprev].prev;
}
tmpMoves.reverse(); results.push({last:step, moves:tmpMoves});
}
head++;
}
return results; }
Evaluator类, 则把之前的评估因素整合起来:
function Evaluator() {
} Evaluator.prototype.evaluate = function(boards) {
} function PierreDellacherieEvaluator() {
} PierreDellacherieEvaluator.prototype = new Evaluator();
PierreDellacherieEvaluator.prototype.constructor = PierreDellacherieEvaluator; PierreDellacherieEvaluator.prototype.evaluate = function(boards, shape) {
return (-4.500158825082766) * landingHeight(boards, shape) // 下落高度
+ (3.4181268101392694) * rowsEliminated(boards, shape) // 消行个数
+ (-3.2178882868487753) * rowTransitions(boards) // 行变换
+ (-9.348695305445199) * colTransitions(boards) // 列变化
+ (-7.899265427351652) * emptyHoles(boards) // 空洞个数
+ (-3.3855972247263626) * wellNums(boards); // 井数
}
AIStrategy整合了落地生成器和评估函数, 用于具体决策最优的那个落地点, 以及行动路线.
function AIStrategy() {
this.generator = new MoveGenerator();
this.evalutor = new PierreDellacherieEvaluator();
} /*
* @brief 作出最优的策略
* @return {dest:{x:{x}, y:{y}, idx:{idx}}, [{action_list}]}
*/
AIStrategy.prototype.makeBestDecision = function(tetrisUnit, shape) { var bestMove = null;
var bestScore = -1000000; // 1) 生成所有可行的落点, 以及对应的路径线路
var allMoves = this.generator.generate(tetrisUnit, shape); // 2) 遍历每个可行的落点, 选取最优的局面落点
for ( var i = 0; i < allMoves.length; i++ ) {
var step = allMoves[i].last; var shapeArrs = shape.shapes;
var bkBoards = tetrisUnit.applyAction2Data(step.x, step.y, shapeArrs[step.idx]); // 2.1) 对每个潜在局面进行评估
var tscore = this.evalutor.evaluate(bkBoards, {x:step.x, y:step.y, shapeArr:shapeArrs[step.idx]}); // 2.2) 选取更新最好的落点和路径线路
if ( bestMove === null || tscore > bestScore ) {
bestScore = tscore;
bestMove = allMoves[i].moves;
}
} // 3) 返回最优可行落点, 及其路径线路
return {score:bestScore, action_moves:bestMove}; }
注: 该代码注释, 诠释了决策函数的整个流程.
效果评估:
该AI算法的效果不错, 在演示模式下, 跑了一晚上, 依旧好好的活着. 这也满足了之前想要的需求和功能.
总结:
该算法的权重系数采用了经验值. 当然了, 也可以借助模拟退火算法来学习参数, 不过由于游戏本身的不确定性/偶然性影响, 收敛的效果并非如预期那么好. 有机会再讲讲.
无论怎么样, 该AI可以扮演一个合格的"麻烦制造者", 让游戏充满趣味和挑战性. 勿忘初心, let's go!!!
写在最后:
如果你觉得这篇文章对你有帮助, 请小小打赏下. 其实我想试试, 看看写博客能否给自己带来一点小小的收益. 无论多少, 都是对楼主一种由衷的肯定.
H5版俄罗斯方块(3)---游戏的AI算法的更多相关文章
- H5版俄罗斯方块(2)---游戏的基本框架和实现
前言: 上文中谈到了H5版俄罗斯方块的需求和目标, 这次要实现一个可玩的版本. 但饭要一口一口吃, 很多东西并非一蹴而就. 本文将简单实现一个可玩的俄罗斯方块版本. 下一步会引入AI, 最终采用coc ...
- H5版俄罗斯方块(4)---火拼对战的雏形
前言: 勿忘初心, 本系列的目标是实现一款类似QQ"火拼系列"的人机对战版俄罗斯方块. 在完成了基本游戏框架和AI的算法探索后, 让我们来尝试一下人机大战雏形编写. 本系列的文章链 ...
- H5版俄罗斯方块(5)---需求演进和产品迭代
前言: 产品的形态是不断迭代的, 从粗糙到精致, 从简易到立体. 有了最初的技术积累和时间思考后, 终于明确了该游戏的方向. 我想说的是: 技术不是重点, 产品/用户体验才是核心议题. 结合朋友的游戏 ...
- H5版俄罗斯方块(1)---需求分析和目标创新
前言: 俄罗斯方块和五子棋一样, 规则简单, 上手容易. 几乎每个开发者, 都会在其青春年华时, 签下"xx到此一游". 犹记得大一老师在布置大程作业的时候提过: "什么 ...
- 游戏人工智能 读书笔记 (四) AI算法简介——Ad-Hoc 行为编程
本文内容包含以下章节: Chapter 2 AI Methods Chapter 2.1 General Notes 本书英文版: Artificial Intelligence and Games ...
- Java五子棋小游戏(控制台纯Ai算法)
Java五子棋小游戏(控制台纯Ai算法) 继续之前的那个五子棋程序 修复了一些已知的小Bug 这里是之前的五子棋程序 原文链接 修复了一些算法缺陷 本次增加了AI算法 可以人机对战 也可以Ai对Ai看 ...
- 浅析初等贪吃蛇AI算法
作为小学期程序设计训练大作业的一部分,也是自己之前思考过的一个问题,终于利用小学期完成了贪吃蛇AI的一次尝试,下作一总结. 背景介绍: 首先,我针对贪吃蛇AI这一关键词在百度和google上尽心了检索 ...
- Android版俄罗斯方块的实现
学习Android的基本开发也有一段时间了,可是由于没有常常使用Android渐渐的也就忘记了. Android编程学的不深,不过为了对付逆向,可是有时还是会感到力不从心的.毕竟不是一个计算机专业毕业 ...
- 怎样制作一个横版格斗过关游戏 Cocos2d-x 2.0.4
本文实践自 Allen Tan 的文章<How To Make A Side-Scrolling Beat 'Em Up Game Like Scott Pilgrim with Coco ...
随机推荐
- GZFramwork数据库层《四》单据主从表增删改查
同GZFramwork数据库层<三>普通主从表增删改查 不同之处在于:实例 修改为: 直接上效果: 本系列项目源码下载地址:https://github.com/GarsonZhang/G ...
- svn server
svn server: 1.c:\Program Files\TortoiseSVN\bin>svnserve -d -r C:\Jasper\Repositories2.change the ...
- hdu4758Walk Through Squares(ac自动机+dp)
链接 dp[x][y][node][sta] 表示走到在x,y位置node节点时状态为sta的方法数,因为只有2个病毒串,这时候的状态只有4种,根据可走的方向转移一下. 这题输入的是m.N,先列后行, ...
- Deep Learning 4_深度学习UFLDL教程:PCA in 2D_Exercise(斯坦福大学深度学习教程)
前言 本节练习的主要内容:PCA,PCA Whitening以及ZCA Whitening在2D数据上的使用,2D的数据集是45个数据点,每个数据点是2维的.要注意区别比较二维数据与二维图像的不同,特 ...
- [java基础]循环结构2
[java基础]循环结构2 写了几个循环结构练习~记录一下~~ 1:99乘法表 /** 文件路径:G:\JavaByHands\循环语句\ 文件名称:GameForFor.java 编写时间:2016 ...
- CSS实现垂直居中
Hack技术就可以啊!所以在这里我还要啰嗦两句,CSS中的确是有vertical-align属性,但是它只对(X)HTML元素中拥有valign特性的元素才生效,例如表格元素中的<td>. ...
- linux -- 基于mysql tomcat 部署web项目
一.导入数据库 二.配置web项目访问端口 vim /var/lib/tomcat8/conf/server.xml <Connector port=" protocol=" ...
- JDK注解替代Hibernate的Entity映射
1.在entity(实体类)模块中使用注解 1_1.注解的位置出现在 [类定义的前面] 和 [属性的get方法前面] [属性的get方法前面] Java代码: package app.entity; ...
- linux驱动初探之字符驱动
关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...
- 亚马逊云服务器AWS安装CentOS
亚马逊云服务器默认创建的实例,在停止之后再启动的情况下,IP会发生改变.所以我们最好先创建一个弹性IP,即EIP,不过我也不清楚这个费用. 1.按如图操作创建一个弹性IP,弹性IP创建之后可以随便绑定 ...