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

简而言之就两点:

  1. 通过随机游戏评估给定局面的得分;
  2. 从博弈树的父节点往下选择子节点时,综合考虑子节点的历史得分与尝试次数。

针对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(&current_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.相关推荐

  1. 蒙特卡罗(Monte Carlo)方法

    蒙特卡罗(Monte Carlo)方法,也称为计算机随机模拟方法,是一种基于"随机数"的计算方法.          一 起源 这一方法源于美国在第二次世界大战进研制原子弹的&qu ...

  2. 蒙特卡洛(Monte Carlo)方法的介绍和应用

    蒙特卡洛(Monte Carlo)方法的介绍和应用 蒙特卡洛(Monte Carlo)方法 在渲染中,我们经常听到术语"蒙特卡洛"(通常缩写为MC).但是这是什么意思?实际上,它所 ...

  3. Monte Carlo方法

    又称为计算机随机模拟算法 理论支撑:概率=频率 在最近看的论文<A Discussion on Solving Partial Differential Equations using Neur ...

  4. 蒙特卡洛python求解派_利用蒙特卡洛(Monte Carlo)方法计算π值[ 转载]

    圆周率π是一个无理数,没有任何一个精确公式能够计算π值,π的计算只能采用近似算法. 国际公认的π值计算采用蒙特卡洛方法. 一.蒙特卡洛方法 蒙特卡洛(Monte Carlo)方法,又称随机抽样或统计试 ...

  5. 利用蒙特卡洛(Monte Carlo)方法计算π值

    圆周率π是一个无理数,没有任何一个精确公式能够计算π值,π的计算只能采用近似算法. 国际公认的π值计算采用蒙特卡洛方法. 蒙特卡洛方法 蒙特卡洛(Monte Carlo)方法,又称随机抽样或统计试验方 ...

  6. 蒙特卡洛(Monte Carlo)方法简介

    蒙特卡洛(Monte Carlo)方法的本质 蒙特卡洛(Monte Carlo)方法,即蒙特卡洛采样,是一种根据某已知分布的概率密度函数f(x),产生服从此分布的样本X的方法. 蒙特卡洛采样有很多种, ...

  7. Monte Carlo方法的基本思路

    Monte Carlo方法的基本思路 (1)针对实际问题建立一个简单且便于实现的概率统计模型,使所求的解恰好是所建模型的概率分布或其某个数字特征,比如是某个事件的概率,或者是该模型的期望值. (2)对 ...

  8. Matlab 下的 Monte Carlo方法高斯信道BPSK基带通信系统仿真

    1  仿真原理 1.1    理论基础 1.2    信噪比数学表达 1.3    框架结构 2  仿真代码 3 仿真的结果 4 参考资料 1  仿真原理 1.1 理论基础 BPSK基带数字通信系统. ...

  9. Matlab 下的 Monte Carlo方法高斯信道QPSK基带通信系统仿真

    目录 1 原理 2 代码实现 3 实验结果 4 参考资料 1 原理 如下图所示﹐利用一个随机数发生器﹐产生(0,1)范围内的随机数.再将这个范围分成四个 相等的区间(0 , 0.25),(0.25 , ...

最新文章

  1. 从命令行传递其他变量来制作
  2. python简单使用
  3. java中跳出当前循环怎么做_在java中,如何跳出当前的多重循环?
  4. 互联网巨头基于全球产业链打造ARM CPU
  5. 再不解决延迟不当,小心你的内存被打爆
  6. artcore html5,值得收藏的25款免费响应式网页模板_CSS_网页制作
  7. 删除China Lucky系列病毒 后缀.evopro勒索病毒数据恢复方法,解密处理方式
  8. 前端之JavaScript进阶
  9. python实现topsis法
  10. bootstrap 可以拖动 表格宽度_table表格列宽可拖动
  11. android png 图标制作,ico图标怎么制作?png图片文件转换成ico图标文件的教程
  12. HTML5+CSS+DIV 新海诚电影简介
  13. android 市场 上传,安卓市场APP上传流程及审核要求
  14. mac word 2016中文输入问题解决
  15. jetson nano笔记
  16. 我要曝光!CDN 省钱大法!
  17. 推特由于技术问题,我们无法完成此次请求,请重试
  18. 字节跳动测试岗位面试题
  19. Online Tools
  20. 如何设置QTableWideget和行高和列宽

热门文章

  1. linux中fopen和open的区别,Linux下open与fopen的区别
  2. php 00截断,00截断之追本溯源
  3. chart.js 饼图显示百分比_Excel制作华夫饼图,其实很简单
  4. ubuntu安装ros_ROS--Melodic 安装
  5. markdown 本地链接_记录笔记、markdown工具推荐
  6. java字符串构造函数的应用_构造函数中的参数0需要找不到类型为'java.lang.String'的bean...
  7. 安装arm虚拟机_虚拟机Parallels出手:苹果M1的Mac能运行Win 10 还挺顺畅
  8. 密码可见_无密码身份认证即将来临
  9. java环境变量win8_win8java环境变量设置
  10. 大屏数据可视化源码_AxureBI数据大屏可视化原型设计软件