2048游戏控制器

1 evaluate

要用程序来处理就得对现实的问题进行量化,用数字来表示。在2048游戏中,我们的输入是一个棋局,让我们输出一个移动方向,这样我们需要对棋局进行量化,即我们要评估棋局的好坏,用一个score来表示,score越大棋局越好,score越小棋局就越差。

玩过2048后就会知道,一个好的棋局会与这几个方面有关系:

1、数据块的单调性 :monotonous

2、空格数:emptys

3、可合并数 :merges

通过计数单调性monotonous、统计空格数empty和可合并的个数merges我们就能得到我们的评估函数:

score = monotonous*w1 + emtpys * w2 + merges * w3

其中w1、w2、w3是各个要素对评估函数的影响的权重。这个需要手动去调整,也也可以通过演进算法CMA-ES去搜索最优值。

2 算法

有了合理的评估方式我们就可以将当前棋局以四个方向进行移动(up,down,left,right),对移动后的棋局进行评估,得到4个score_left、score_right、score_up、score_down,此时我们会取4个score的最大值score_max = max(score_left,score_right,score_up,score_down)对应的移动方向作为当前棋局的移动方向,但是这样的移动是最优的吗?显然对应当前棋局来说是最优的,但是考虑到未来的可能棋局就不一定是最优的了,因为我们移动后电脑会随机加入一个2或4的块(概率为别为0.9和0.1),电脑的这个操作(棋手对弈也一样)会改变棋局,影响后续的移动。所以,如果只对移动一次后的棋局进行评估就决定移动方向只能得到当前最好的棋局,但是后续的棋局好不好就不能保证了,说不定电脑在某个特殊点放置一个块,后面就game over了。为了到达最高分,我们即要保证当前的移动不坏,同时也要考虑后续的情况,这样才能不断的保持一个好的棋局,使自己不断移动下去,取得最高分,(移动的次数越多分就越高),考虑长远收益。

举例说明当前最优,但长远来看不是最优:



上图中,在原棋局的基础上进行4个方向的移动,分别得到4个移动后的棋局,用我们上面的的evaluate方法对移动后的棋局进行评分,我们很容易就能知道up移动后的棋局得分会最高。

如果我们选择up,那么电脑(对手)会随机的放置一个2的块(0.9的概率)或者一个4的块(0.1的概率)会得到如下的棋局



其中出现虚线框中两种棋局的概率是1/5,当出现这个棋局,我们只有一种移动棋局选择,就是down,down以后的棋局单调性就很差了(空格认为是0),可能就是因为我们这一步down就导致后面很快输掉棋局。然而真正导致这种结果的源头却在前几部的移动上,如果起名前一步选择left就完全可避开这种导致满盘皆输的错误移动。这个例子就说明了,当前一步的最好选择可能会导致未来移动的挫折,所以每移动异步需要考虑未来可能出现的棋局,通过大局观的考虑来决定当前怎么移动。

在上面这种考虑未来的情况思想下(求期望),有两种博弈算法相对应,一种是MinMax一种是ExpectiMax

2.1 MinMax

MinMax算法在预测未来棋局的时候,对手下棋后得到的总是一个Min棋局(最坏棋局,对手最优棋局),自己下棋后得到一个Max棋局(自己的最优棋局)

如下图所示,对手和我每次下棋只有两种选择,到底当前选择1下法还是选择2下法呢,当然你会说那种下法的赢的概率大就选那个。如何知道那种赢的概率大呢,当然是棋局越来越好赢的概率就大。

假设选择1下法得到b棋局,同时遍历对手可能的选择得到d和c棋局,更进一步遍历我在d和e棋局下的下法得到i、j、k、l棋局。同样的对2下法也进行同样的操作会得到m、n、o、p

对这8个棋局进行评估分数,假设分值如下

上面从b到4个棋局和从c到4个棋局是遍历的过程,还不知道b移两步后4个棋局的概率分布,这个时候需要从b树和c树的叶子节点进行逆向反推,反推的依据就是棋手(我和对手)选择棋局的分布

我的分布 选未来好棋局 选未来差棋局
概率 1 0
对手分布 选未来好棋局 选未来差棋局
概率 0 1

这里的好与坏是相对于我来说的。选好棋局的概率为1,就是求Max,选差棋局的概率为1,就是求Min.

第3步是我下棋,我只会选择好的棋局,所以从d、e、f、g下棋只会出现得分为5、3、4、6的棋局

第2步是对手下棋,他只会选择对于我未来差的棋局,所以从b和c棋局只会得到e和f

这样我们就预测出了3步以后(未来)的棋局,我们发现如果选择1下法,未来棋局得分是3,选择2下法的话,未来棋局的得分是4,所以我们应该选择2下法。

上面我下棋的过程是Max过程,对手下棋是Min过程,就是MaxMin算法的思想。其实就是预测未来棋局的分布,选择当前下法会导致未来出现好棋局的概率大的下法进行下棋。

上面通过一个例子解释什么是MaxMin,为什么要用MaxMin。我们可以把MaxMin的过程用公式表示出来。

k和n表示预测的深度(第n步后直接使用evaluate的值),rival_childs表示对手在$Gk$棋局时能够得到的所以棋局$G(k+1)_i$,my_childs表示我在$G(k+1)$棋局值能够得到所以棋局$G(k+2)_i$。在对手局的时候,对手选择所以棋局中最差的。在我的棋局时,我会选择最好的棋局。在原始棋局选择哪个棋局作为下一个棋局就是一个求期望的过程,对手棋局的期望是它所以可能棋局最差棋局的得分,我的棋局就是我的所以可能棋局中最好的得分。在计算期望的过程中选先要把棋局往下展开,并在第n步以后对棋局进行评估得到棋局的分数,然后往上计算棋局的期望,最后能得到初始棋局能到达的N个棋局的期望,取则N个棋局最后最好的棋局作为初始棋局的下一关棋局。

在用程序实现的时候会采用递归的方式来时序,在递归中,分两个步骤,先是递归往深处走,将棋局不断遍历展开,然后是评估最深哪一步得到棋局的评分,然后通过Min和Max不断缩小棋局的个数,最后剩下N个棋局,选择N个棋局中最优的那个对应的下法作为当前棋局的下法是最好的。

说到根本,博弈的目的不仅要当下占优势,更要在未来占优势,这使得我们需要预测未来棋局的情况,并且选择未来赢的概率最大分支对应的下法作为当下的下法;

预测未来就需要知道事件的分布情况,在下棋的这个博弈游戏中,棋手都倾向于未来棋局好的下法下棋;在MaxMin算法中就比较极端,直接认为棋手都很厉害(对手和自己),每次下棋都是最优的(在evaluate棋局很准确的情况下),对手选择对于我最差的棋局,我选择对于我最好的棋局,且对手选择最差棋局的概率为1,我选择最好棋局的概率也为1。(这套程序与一个差棋手对弈的话长远预测就不准确了,但是比差棋手一步都走不好还是要好,所以还是能赢。)

MaxMin算法适用于对手很强大的前提下(Min)

2.2 ExpectiMax算法

ExpectiMax算法也是博弈算法的一种,它不像MinMax那样极端,认为对手一定会选最优,而是认为对手会按照一种分布去选择,但是自己一定会选最优(不然自己怎么赢)。同样的ExpectiMax也是预测未来的棋局,假设当前我有N种下法,那么每种下法通过通过遍历能够得到未来的棋局,然后看N种下法中,那种遍历后能够得到好棋局的概率大就选那种下法下棋。

还是选择上面的例子进行分析,同样遍历两步后得到棋局如下。

但是这里的对手的分布有所变化:

我的分布 选未来好棋局 选未来差棋局
概率 1 0
对手分布 选未来好棋局 选未来差棋局
概率 0.7 0.3

对第3步后的棋局进行评分:

从d棋局出发得到1分棋局的概率为0.3,从d棋局出发得到5分的棋局的概率为0.7,那么d棋局未来得分的期望值为 10.3+50.7=3.8,同理可以得到e、f、g棋局的得分。

如果从b棋局出发,得到3.8分棋局的概率为1,所以b棋局未来得分的期望为3.8,同样的c棋局未来得分的期望为5.1

这个时候我们知道我们在a棋局下,选择1下法,未来棋局的期望值为3.8,选择2下法,未来棋局得分的期望值为5.1,当然我们会选择2下法。

k和n表示预测的深度(第n步后直接使用evaluate的值),rival_childs表示对手$Gk$在棋局时能够得到的所以棋局$G{k+1}_i$,my_childs表示我在$G{k+1}$棋局值能够得到所以棋局。在对手局的时候,对手会根据一定的分布选择下一个棋局$G{k+2}_i$。在我的棋局时,我会选择最好的棋局。在计算期望的过程中选先要把棋局往下展开,并在第n步以后对棋局进行评估得到棋局的分数,然后往上计算棋局的期望,最后能得到初始棋局能到达的N个棋局的期望,取则N个棋局最后最好的棋局作为初始棋局的下一关棋局。

2048的对手不是智能的,而是一个按照一定分布随机放置一个块,所以使用expectimax算法比较合适。

除了求期望的算法,决定性能好坏的因素就是evaluate方式,对棋局评估的准确性能就好。在2048中评估的棋局好坏的因素有如下几个,通过这些方面然后乘上一个权重就能相对准确的描述棋局的好坏。

  1. 数据块的单调性 :monotonous
  2. 空格数:emptys
  3. 可合并数 :merges

3 CMA-ES算法

这里会遇到权重怎么设的问题,通常我们会自己根据结果手动调整,但是这种方式麻烦且不准确,不一定能选出好的权重值。

4 C++多线程处理及获取子线程的返回值

#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h> using namespace std; #define NUM_THREADS 5 void *wait(void *t)
{
int i;
long tid; tid = (long)t; sleep(1);
cout << "Sleeping in thread " << endl;
cout << "Thread with id : " << tid << " ...exiting " << endl;
pthread_exit(NULL);
} int main ()
{
int rc;
int i;
pthread_t threads[NUM_THREADS];
pthread_attr_t attr;
void *status; // 初始化线程属性
pthread_attr_init(&attr);
// 设置线程为可连接的(joinable),如果为不可连接的就不能用join函数同步。
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); for( i=0; i < NUM_THREADS; i++ ){
cout << "main() : creating thread, " << i << endl;
rc = pthread_create(&threads[i], NULL, wait, (void *)&i );
if (rc){
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
} // 删除属性,释放空间
pthread_attr_destroy(&attr);
for( i=0; i < NUM_THREADS; i++ ){
// 等待子线程结束
rc = pthread_join(threads[i], &status);
if (rc){
cout << "Error:unable to join," << rc << endl;
exit(-1);
}
cout << "Main: completed thread id :" << i ;
cout << " exiting with status :" << status << endl;
} cout << "Main: program exiting." << endl;
pthread_exit(NULL);
}

多线程返回参数是通过给子线程传一个结构体子针,子线程返回的时候结果保存在结构体的score域中。



5 python多进程并获取返回值

import multiprocessing
import time def func(msg):
print multiprocessing.current_process().name + '-' + msg if __name__ == "__main__":
pool = multiprocessing.Pool(processes=4) # 创建4个进程
for i in xrange(10):
msg = "hello %d" %(i)
pool.apply_async(func, (msg, ))
pool.close() # 关闭进程池,表示不能在往进程池中添加进程
pool.join() # 等待进程池中的所有进程执行完毕,必须在close()之后调用
print "Sub-process(es) done."

通过get()函数获取子线程的返回值。

7 python调用C/C++

  1. 在需要调用的函数上添加extern “C”

  2. 编译的时候需要添加 -fPIC -shared参数
g++ -fPIC -shared -lpthread -o 2048 2048.cpp

参考

  1. maxmin算法
  2. expectimax算法
  3. C++多线程处理及多线程获取子线程的返回值

    -lpthread编译参数
  4. python的多进程处理及获取子进程的返回值
  5. python调用C/C++ (编译参数)
g++ -fPIC -shared -lpthread -o 2048 2048.cpp

原文写于2019-02-02,2021-12-07改为markdown

python 2048游戏控制器的更多相关文章

  1. [python] python实现2048游戏,及代码解析。

    我初学python,有不对之处望大家指教.转载请征得同意. 我在网络上也找了一些2048游戏代码的讲解,但都不是特别详细.所以我希望能够尽量详细的讲解.同时,有的地方我也不懂,希望大家能帮助补充.我会 ...

  2. 用Python做2048游戏 网易云课堂配套实验课。通过GUI来体验编程的乐趣。

    第1节 认识wxpython 第2节 画几个形状 第3节 再做个计算器 第4节 最后实现个2048游戏 实验1-认识wxpython 一.实验说明 1. 环境登录 无需密码自动登录,系统用户名shiy ...

  3. powershell字符界面的,powershell加WPF界面的,2048游戏

    ------[序言]------ 1 2048游戏,有段时间很火,我在地铁上看有人玩过.没错,坐地铁很无聊,人家玩我就一直盯着看. 2 我在电脑上找了一个,试玩了以下,没几次格子就满了.我就气呼呼的放 ...

  4. 利用 Python_tkinter 完成 2048 游戏

    成品展示 具备基本的数据合并以及分数统计,不同数字的色块不同 产生随机数, 数据无法合并判定以及重新开始选项 同时可以判定游戏失败条件 需求分析 完成基本数据合并算法 游戏结束条件 界面展示 重置按钮 ...

  5. 用javascript实现一个2048游戏

    早就想自己写一个2048游戏了,昨晚闲着没事,终于写了一个 如下图,按方向键开始玩吧. 如果觉得操作不方便,请直接打开链接玩吧: http://gujianbo.1kapp.com/2048/2048 ...

  6. 让Windows2008R2也能进入手柄设置(游戏控制器设置)

    在Windows2008 R2系统中,插入XB360手柄后能自动完成驱动安装,在[设备和打印机]中也会出现手柄,但在上面右键→游戏控制器设置却没反应,什么都没打开,虽然不影响实际游戏,但总感觉有点堵. ...

  7. Android项目开发实战-2048游戏

    <2048>是一款比较流行的数字游戏,最早于2014年3月20日发行.原版2048首先在GitHub上发布,原作者是Gabriele Cirulli,后被移植到各个平台.这款游戏是基于&l ...

  8. 对弈类游戏的人工智能(5)--2048游戏AI的解读

    前言: 闲得没事, 网上搜"游戏AI", 看到一篇<<2048游戏的最佳算法是?来看看AI版作者的回答>>的文章. 而这篇文章刚好和之前讲的对弈类游戏AI对 ...

  9. 最少javascript代码完成一个2048游戏

    原生javascript代码写的2048游戏.建议在谷歌浏览器下跑.'WASD'控制方向.演示地址请移步:http://runjs.cn/detail/bp8baf8b 直接贴代码~ html: &l ...

随机推荐

  1. Centos安装与配置

    一.安装 默认安装 二.配置 配置网卡 BOOTPROTO=none ONBOOT=yes IPADDR=xxx.xxx.x.xx PREFIX=24 GATEWAY=xxx.xxx.x.x DNS1 ...

  2. scrollTop、scrollHeight与clientHeight

    MDN上概念 scrollTop:获取或设置一个元素的内容垂直滚动的像素数. scrollHeight:一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容. clientHeight:元素内部 ...

  3. 图形验证插件,百度编辑器拓展功能,NodeJs消息机制以及聊天室

    图形验证插件 网上找了很多图形验证插件,比较推荐verify.js <link rel="stylesheet" type="text/css" href ...

  4. HIVE理论学习笔记

    概述 参加了新的公司新的工作新的环境之后,本人必须学习更多的知识,所以稳固之前的知识和学习新的知识是重中之重,新的公司把hadoop大部分的组件都进行了架构源码深度改造,所以使用过程确实遇到一些麻烦, ...

  5. Java实现二叉搜索树的插入、删除

    前置知识 二叉树的结构 public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() { } TreeNode( ...

  6. day23 结构体

    (1).若有说明和定义: typedef int *integer: integer p,*q: 则下列叙述正确的是[C] (A).q是基类型位int的指针变量 (B).p是int型变量 (C).p是 ...

  7. Cesium入门12 - Camera Modes - 相机模式

    Cesium入门12 - Camera Modes - 相机模式 Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ ...

  8. golang中的反射解析结构体标签tag

    package main import ( "fmt" "reflect" ) type resume struct { // 反射解析结构体标签tag Nam ...

  9. gin中自定义http的配置

    package main import ( "github.com/gin-gonic/gin" "net/http" "time" ) f ...

  10. 什么是Listener监听器

    什么是Listener监听器 1,Listener 监听器它是JavaWeb的三大组件之一.JavaWeb的三大组件:Servlet程序,Filter过滤器,Listener监听器. 2,Listen ...