主题:AITTT(蒙特卡洛,极大极小,随机选择策略加编程思想) 分享人: 墨灵 时间:周日晚上8:00
需求分析和 建模 接口设计
今天借TicTacToe这个小游戏和大家聊一聊架构设计这个话题,也算是抛砖引玉吧,也是希望通过分享的方式来迭代自己的编程水平。
大家思考一个问题,当拿到一个需求时,是马上就开始写代码,还是先进行需求分析呢?
我以前的话,几乎不会进行需求分析,直接上来就写代码,到后来,维护的代码越来越大了,到处就是复制粘贴,自己也受不了,就整个推倒重来,效率极其低下,所幸我是个爱偷懒的人, 这样几次下来,我就开始思考,如何才能让自己少写一些代码,至少可以少改一些代码,如果一个功能已经实现,也通过测试,那部分的代码最好就不要再改动了。
那个时候,我只是想少维护一些历史代码而已,我根本没想到这就是需求分析和架构设计的动机, 架构设计的第一要点就是需求分析,区分需求的 稳定点和变化点。然后从模型到接口,最后才是实现。
那什么是稳定点 和 变化点呢
举个例子吧 稳定点就是那些不会改变化的东西,好比一台电脑,一定会有CPU,一定会有内存 但输入输出设备就是一个变化点
在我们要讲的TicTacToe中:
稳定点:棋盘是方的,棋格也是方的
改变点:棋盘的尺寸是不确定的,绘制的棋格尺寸也是不确定的。
把所有改变点设计为接口,参数,变量
分析需求
棋类(Board)
看到上周的作业,最初进行需求分析的时,我是想把井字棋和五子棋一起实现的,它们有很多的相同点。
- 他们都是有一个棋盘(Board),棋盘是方的,意味着宽高都相等,可以用尺寸(dimension)来表达,然后棋盘上都有dimension * dimension的棋格(Cells),
- 每种棋都是有两种棋子(Piece)棋子只有两个状态(PIECE_O|PIECE_X),于是每个棋格都是只有三个状态(PIECE_O|PIECE_X|EMPTY),每次下棋就是从所有的EMPTY棋格选出其中之一,
- 落子(move),然后切换棋子(switchPiece),直到检查(checkWin)到棋盘分了胜负(PIECE_O|PIECE_X|DRAW)。
这些都是稳定点。 这就是一个 棋类(board)的最核心需求。这就是我最初的设计。
关于需求分析也有很多见解 郭慧娟: 我做需求分析一般是搞明白输入输出
游戏场景类(Game)
- 光是有了棋盘数据是不够的,Board类顶多只是承担Model层的业务,我们不太可以只有一个命令行的地方用命令去下棋,还是要通过View层来展现。
- 在浏览器环境下,View层是天然存在的,就是HTML和CSS。 3.但还差一个Controller的角色,监听View层的事件,然后去修改Model的源数据,还通过Model层的一些业务事件,反过去修改View的展示。我把这个角色抽象成Game。
那我们现在对Game进行需求分析。一个棋类游戏是应该有一个棋盘(Board)的,而且它应该知道当前的游戏的状态(isRunning), 也应该知道当前是轮到哪个棋子(currentPiece)落子了,它也需要知道在哪个元素(containerEl)上绘制棋盘(_draw)。
game.move比borad.move多了不少的逻辑,游戏中的落子要先判断游戏是否进行中,还有所落的位置是不是空棋格,然后才能落子, 落子之后要切换棋子绘制棋盘,还要检查棋局是否有了胜负,更新游戏的状态。但是,game.move的业务依然是稳定的,无论是监听View事件产出的game.move,还是在命令行直接调用的game.move。
有了game.move这个基础,就可以把需求的变化点抽取出来了,虽然game.move是稳定的,但是AI策略却是变化点,
可以有 随机智障的算法,也可以有很多高级的AI(蒙特卡洛,minmax等等)的算法,也可以有很多高级的算法。
难道每多一种策略就需要在Game增加一种move方法吗?这种做法会让Game的代码越来越无法维护,那有没一种方案让Game的代码稳定下来?
下面我说一下我的,代码设计思路
只要每个策略类都提供一个calculatePosition (board, piece)方法就可以了,在别的语言可以用一个接口来进行约束 ,这个设计也可以说是多态和控制反转(IoC)的结合。
每个策略只需要根据当前的棋盘和棋子,计算一个落子位置(position), 调用game.moveByStratepy (strategy)就可以根据计算出来的position落子了。
FU½: 后面有点晕,是说GAME负责判断游戏是否在进行,和轮到谁了? 墨灵: 是的
FU½: 而不同的算法负责在哪儿落子? 墨灵: game主要负责用户交互部分 墨灵: 可以这么理解
郭慧娟: 感觉接口、类的划分挺明确的,点赞。目前业务中好像不太用到,可以有意识的培养一下,学到了
郭慧娟: 控制反转 墨灵: 比如说A类依赖B类,普通的做法就是在A类内部实例化B类 郭慧娟: 嗯呢 墨灵: 控制反转就是把B类的实例当成参数给A类直接使用 郭慧娟: 哦哦,get 墨灵: 如果B类是个数据库或者又依赖别的基础设施,要测试A类必须要和B类集成才能测试,那效率也很低下,用依赖注入,就可以mock一个B类实例,直接测试A类
AI策略
策略部分我就直接上代码吧。
RandomStrategy(随机策略:人工智障)
- 获取棋盘所有的空棋格
- 随机选择一个棋格落子
MonteCarloStrategy(蒙特卡罗策略:统计模拟方法)
0.创建一个和棋盘一模一样的记分板 1.在当前的棋局进行多次训练,把棋局进行到已分胜负 2.根据每次训练结果更新记分板,胜子位置分数+1,负子位置分数-1 3.统计出所有胜率最高的落子位置,随机选择一个
** MinimaxStrategy(极小化极大策略)** 1.先假设自己处在一个最差的结果 2.穷尽所有尝试去找一个稍微好一点的结果 3.假设对手也是采用这个策略 4.递归寻求一个不败的结果
其实winter老师那个就是一个minimax算法,我只是做了一些剪枝和优化,开局棋谱剪枝,胜局剪枝,用回退的方式代替棋盘的克隆。我也写了一个带log版部署到网站上,大家可以根据log的追踪来体会一下这算法的绝妙之处。
log版预览链接http://47.114.59.114:8012/
这是蒙特卡罗策略的
讨论
1.郭慧娟: 剪枝就是说不去往下算了,这里的胜局剪枝和棋谱剪枝能具体点吗?
郭慧娟:get 2. 郭慧娟: 这个分是怎么得出来的,和我想的一样吗?就是遍历所有可能下子的可能情况。然后统计得分情况 墨灵: 以当前棋盘为模板乱下一通,直到分出胜负
主题:AITTT(蒙特卡洛,极大极小,随机选择策略加编程思想) 分享人: 墨灵 地点: 刷题群
谢谢大家!!!
代码链接:github.com/moling3650/… 效果链接:http://47.114.59.114:8012/