基于Monte Carlo方法的2048 A.I.
2048 A.I. 在 stackoverflow 上有个讨论:http://stackoverflow.com/questions/22342854/what-is-the-optimal-algorithm-for-the-game-2048
得票最高的回答是基于 Min-Max-Tree + alpha beta 剪枝,启发函数的设计很优秀。
其实也可以不用设计启发函数就写出 A.I. 的,我用的方法是围棋 A.I. 领域的经典算法——Monte Carlo 局面评估 + UCT 搜索。
算法的介绍见我几年前写的一篇博文:http://www.cnblogs.com/qswang/archive/2011/08/28/2360489.html
简而言之就两点:
- 通过随机游戏评估给定局面的得分;
- 从博弈树的父节点往下选择子节点时,综合考虑子节点的历史得分与尝试次数。
针对2048游戏,我对算法做了一个改动——把 Minx-Max-Tree 改为 Random-Max-Tree,因为增加数字是随机的,而不是理性的博弈方,所以猜想 Min-Max-Tree 容易倾向过分保守的博弈策略,而不敢追求更大的成果。
UCT搜索的代码:
Orientation UctPlayer::NextMove(const FullBoard& full_board) const {int mc_count = 0;while (mc_count < kMonteCarloGameCount) {FullBoard current_node;Orientation orientation = MaxUcbMove(full_board);current_node.Copy(full_board);current_node.PlayMovingMove(orientation);NewProfit(¤t_node, &mc_count);}return BestChild(full_board); }
NewProfit函数用于更新该节点到某叶子节点的记录,是递归实现的:
float UctPlayer::NewProfit(board::FullBoard *node,int* mc_count) const {float result;HashKey hash_key = node->ZobristHash();auto iterator = transposition_table_.find(hash_key);if (iterator == transposition_table_.end()) {FullBoard copied_node;copied_node.Copy(*node);MonteCarloGame game(move(copied_node));if (!HasGameEnded(*node)) game.Run();result = GetProfit(game.GetFullBoard());++(*mc_count);NodeRecord node_record(1, result);transposition_table_.insert(make_pair(hash_key, node_record));} else {NodeRecord *node_record = &(iterator->second);int visited_times = node_record->VisitedTimes();if (HasGameEnded(*node)) {++(*mc_count);result = node_record->AverageProfit();} else {AddingNumberRandomlyPlayer player;AddingNumberMove move = player.NextMove(*node);node->PlayAddingNumberMove(move);Orientation max_ucb_move = MaxUcbMove(*node);node->PlayMovingMove(max_ucb_move);result = NewProfit(node, mc_count);float previous_profit = node_record->AverageProfit();float average_profit = (previous_profit * visited_times + result) /(visited_times + 1);node_record->SetAverageProfit(average_profit);}node_record->SetVisitedTimes(visited_times + 1);}return result; }
起初用结局的最大数字作为得分,后来发现当跑到512后,Monte Carlo棋局的结果并不会出现更大的数字,各个节点变得没有区别。于是作了改进,把移动次数作为得分,大为改善。
整个程序的设计分为 board、player、game 三大模块,board 负责棋盘逻辑,player 负责移动或增加数字的逻辑,game把board和player连起来。
Game类的声明如下:
class Game { public:typedef std::unique_ptr<player::AddingNumberPlayer>AddingNumberPlayerUniquePtr;typedef std::unique_ptr<player::MovingPlayer> MovingPlayerUniquePtr;Game(Game &&game) = default;virtual ~Game();const board::FullBoard& GetFullBoard() const {return full_board_;}void Run();protected:Game(board::FullBoard &&full_board,AddingNumberPlayerUniquePtr &&adding_number_player,MovingPlayerUniquePtr &&moving_player);virtual void BeforeAddNumber() const {}virtual void BeforeMove() const {}private:board::FullBoard full_board_;AddingNumberPlayerUniquePtr adding_number_player_unique_ptr_;MovingPlayerUniquePtr moving_player_unique_ptr_;DISALLOW_COPY_AND_ASSIGN(Game); };
Run函数的实现:
void Game::Run() {while (!HasGameEnded(full_board_)) {if (full_board_.LastForce() == Force::kMoving) {BeforeAddNumber();AddingNumberMovemove = adding_number_player_unique_ptr_->NextMove(full_board_);full_board_.PlayAddingNumberMove(move);} else {BeforeMove();Orientation orientation =moving_player_unique_ptr_->NextMove(full_board_);full_board_.PlayMovingMove(orientation);}} }
这样就可以通过继承 Game 类,实现不同的构造函数,组合出不同的 Game,比如 MonteCarloGame 的构造函数:
MonteCarloGame::MonteCarloGame(FullBoard &&full_board) :Game(move(full_board),std::move(Game::AddingNumberPlayerUniquePtr(new AddingNumberRandomlyPlayer)),std::move(Game::MovingPlayerUniquePtr(new MovingRandomlyPlayer))) {}
一个新的2048棋局,会先放上两个数字,新棋局应该能方便地build。默认应该随机地增加两个数字,builder 类可以这么写:
template<class G> class NewGameBuilder { public:NewGameBuilder();~NewGameBuilder() = default;NewGameBuilder& SetLastForce(board::Force last_force);NewGameBuilder& SetAddingNumberPlayer(game::Game::AddingNumberPlayerUniquePtr&&initialization_player);G Build() const;private:game::Game::AddingNumberPlayerUniquePtr initialization_player_; };template<class G> NewGameBuilder<G>::NewGameBuilder() :initialization_player_(game::Game::AddingNumberPlayerUniquePtr(new player::AddingNumberRandomlyPlayer)) { }template<class G> NewGameBuilder<G>& NewGameBuilder<G>::SetAddingNumberPlayer(game::Game::AddingNumberPlayerUniquePtr &&initialization_player) {initialization_player_ = std::move(initialization_player);return *this; }template<class G> G NewGameBuilder<G>::Build() const {board::FullBoard full_board;for (int i = 0; i < 2; ++i) {board::AddingNumberMove move = initialization_player_->NextMove(full_board);full_board.PlayAddingNumberMove(move);}return G(std::move(full_board)); }
很久以前,高效的 C++ 代码不提倡在函数中 return 静态分配内存的对象,现在有了右值引用就方便多了。
main 函数:
int main() {InitLogConfig();AutoGame game = NewGameBuilder<AutoGame>().Build();game.Run(); }
./fool2048:
这个A.I.的移动不像基于人为设置启发函数的A.I.那么有规则,不会把最大的数字固定在角落,但最后也能有相对不错的结果,游戏过程更具观赏性~
项目地址:https://github.com/chncwang/fool2048
最后发个招聘链接:http://www.kujiale.com/about/join
我这块的工作主要是站内搜索、推荐算法等,欢迎牛人投简历到hr邮箱~
转载于:https://www.cnblogs.com/qswang/p/3749685.html
基于Monte Carlo方法的2048 A.I.相关推荐
- 蒙特卡罗(Monte Carlo)方法
蒙特卡罗(Monte Carlo)方法,也称为计算机随机模拟方法,是一种基于"随机数"的计算方法. 一 起源 这一方法源于美国在第二次世界大战进研制原子弹的&qu ...
- 蒙特卡洛(Monte Carlo)方法的介绍和应用
蒙特卡洛(Monte Carlo)方法的介绍和应用 蒙特卡洛(Monte Carlo)方法 在渲染中,我们经常听到术语"蒙特卡洛"(通常缩写为MC).但是这是什么意思?实际上,它所 ...
- Monte Carlo方法
又称为计算机随机模拟算法 理论支撑:概率=频率 在最近看的论文<A Discussion on Solving Partial Differential Equations using Neur ...
- 蒙特卡洛python求解派_利用蒙特卡洛(Monte Carlo)方法计算π值[ 转载]
圆周率π是一个无理数,没有任何一个精确公式能够计算π值,π的计算只能采用近似算法. 国际公认的π值计算采用蒙特卡洛方法. 一.蒙特卡洛方法 蒙特卡洛(Monte Carlo)方法,又称随机抽样或统计试 ...
- 利用蒙特卡洛(Monte Carlo)方法计算π值
圆周率π是一个无理数,没有任何一个精确公式能够计算π值,π的计算只能采用近似算法. 国际公认的π值计算采用蒙特卡洛方法. 蒙特卡洛方法 蒙特卡洛(Monte Carlo)方法,又称随机抽样或统计试验方 ...
- 蒙特卡洛(Monte Carlo)方法简介
蒙特卡洛(Monte Carlo)方法的本质 蒙特卡洛(Monte Carlo)方法,即蒙特卡洛采样,是一种根据某已知分布的概率密度函数f(x),产生服从此分布的样本X的方法. 蒙特卡洛采样有很多种, ...
- Monte Carlo方法的基本思路
Monte Carlo方法的基本思路 (1)针对实际问题建立一个简单且便于实现的概率统计模型,使所求的解恰好是所建模型的概率分布或其某个数字特征,比如是某个事件的概率,或者是该模型的期望值. (2)对 ...
- Matlab 下的 Monte Carlo方法高斯信道BPSK基带通信系统仿真
1 仿真原理 1.1 理论基础 1.2 信噪比数学表达 1.3 框架结构 2 仿真代码 3 仿真的结果 4 参考资料 1 仿真原理 1.1 理论基础 BPSK基带数字通信系统. ...
- Matlab 下的 Monte Carlo方法高斯信道QPSK基带通信系统仿真
目录 1 原理 2 代码实现 3 实验结果 4 参考资料 1 原理 如下图所示﹐利用一个随机数发生器﹐产生(0,1)范围内的随机数.再将这个范围分成四个 相等的区间(0 , 0.25),(0.25 , ...
最新文章
- 从命令行传递其他变量来制作
- python简单使用
- java中跳出当前循环怎么做_在java中,如何跳出当前的多重循环?
- 互联网巨头基于全球产业链打造ARM CPU
- 再不解决延迟不当,小心你的内存被打爆
- artcore html5,值得收藏的25款免费响应式网页模板_CSS_网页制作
- 删除China Lucky系列病毒 后缀.evopro勒索病毒数据恢复方法,解密处理方式
- 前端之JavaScript进阶
- python实现topsis法
- bootstrap 可以拖动 表格宽度_table表格列宽可拖动
- android png 图标制作,ico图标怎么制作?png图片文件转换成ico图标文件的教程
- HTML5+CSS+DIV 新海诚电影简介
- android 市场 上传,安卓市场APP上传流程及审核要求
- mac word 2016中文输入问题解决
- jetson nano笔记
- 我要曝光!CDN 省钱大法!
- 推特由于技术问题,我们无法完成此次请求,请重试
- 字节跳动测试岗位面试题
- Online Tools
- 如何设置QTableWideget和行高和列宽
热门文章
- linux中fopen和open的区别,Linux下open与fopen的区别
- php 00截断,00截断之追本溯源
- chart.js 饼图显示百分比_Excel制作华夫饼图,其实很简单
- ubuntu安装ros_ROS--Melodic 安装
- markdown 本地链接_记录笔记、markdown工具推荐
- java字符串构造函数的应用_构造函数中的参数0需要找不到类型为'java.lang.String'的bean...
- 安装arm虚拟机_虚拟机Parallels出手:苹果M1的Mac能运行Win 10 还挺顺畅
- 密码可见_无密码身份认证即将来临
- java环境变量win8_win8java环境变量设置
- 大屏数据可视化源码_AxureBI数据大屏可视化原型设计软件