C++抽象编程——回溯算法(5)——Nim游戏代码及其反思
来源:互联网 发布:ubuntu snmpwalk 编辑:程序博客网 时间:2024/06/05 18:46
实话,距离上一篇博客发表完到现在,我为了这个代码写了一个多小时。里面的思维方式让我受益匪浅。很累,但是很开心。下面我就分享出来大家一起看看。我写的时候调试了几次,最后我把我几次调试的过程都写在了注释里面,产生的bug我会一一解释。强烈建议大家自己写一遍。下面的实现,主要的实现是相互递归。
Nim游戏代码
首先由于代码比较庞大,我们把它封装在一个类上,以Nim头文件命名:
Nim.h文件
#ifndef _Nim_h#define _Nim_h/**这个文件提供了Nim游戏的基本操作*//** 类型名: Player* ------------* 这个枚举类型用来区分电脑跟玩家 */enum Player { HUMAN, COMPUTER };/*封装nim的操作*/ class simpleNim{ public: /* *方法:play *用法:game.play *--------------- *用途:开始游戏,使得电脑跟人类对局 */ void play(); /* * 方法: printInstructions * 用法: game.printInstructions(); * ------------------------------- * 这个方法向用户解释游戏的规则 */ void printInstructions(); #include "Nimpriv.h" }; #endif
Nimpriv.h
/**这个文件存放的是Nim类的私有成员*/ private: /* *方法: getComputerMove *用法: int nTaken = getComputerMove(); *----------------------------------- *计算出什么样的举动对于电脑玩家来说是最好的, *并返回所用的硬币数量。 该方法首先调用 *findGoodMove来查看是否存在获胜举动。 *如果没有,程序只拿走一枚硬币,使得人类玩家更多的机会犯错误。 */ int getComputerMove(); /* *方法: findGoodMove *用法: int nTaken = findGoodMove(nCoins); * ----------------------------------------- *给定指定数量的硬币,该方法寻找在这堆硬币中寻找一个获胜的举动。 如果在该位 *置获胜,该方法返回该值; 如果没有,该方法返回常量NO_GOOD_MOVE。 *一个好的举动是让你的对手处于不利的位置,而糟糕的位置是不会有好的举动。 */ int findGoodMove(int nCoins); /* * 方法: isBadPosition * 用法: if (isBadPosition(nCoins)) . . . * --------------------------------------- * 如果nCoins是不利的位置,则此方法返回true。一个不利的位置是没有好的举动。 * 剩下一个硬币显然是一个不好的位置,代表简单的递归的情况 */ bool isBadPosition(int nCoins); /* * 方法: getUserMove * 用法: int nTaken = getUserMove(); * ---------------------------------- * 要求用户拿走并返回所用的硬币数量。 *如果拿取不合法,则要求用户重新进入有效的移动。 */ int getUserMove(); /* * 方法: announceResult * 用法: announceResult(); * ------------------------ * 这个方法宣布游戏的最终结果 */ void announceResult(); /* * 方法: opponent * 用法: Player other = opponent(player); * --------------------------------------- * 返回这个回合的玩家是谁 */ Player opponent(Player player); /*实例化变量*/ int nCoins; /* 桌子上剩余的硬币数 */ Player whoseTurn;
Nim.cpp
#include <iostream>#include <string>#include "Nim.h"using namespace std;/*定义常数*/const int N_COINS = 13; //初始化硬币的数量const int MAX_MOVE = 3; //一次最多拿走3个const int NO_GOOD_MOVE = -1; //标记没有好的移动方案const Player STARTING_PLAYER = HUMAN; // 用于游戏由谁开始/*利用条件运算符决定下一个回合的是谁,opponent 对手*/ Player simpleNim::opponent(Player player) { return (player == HUMAN) ? COMPUTER : HUMAN;} /*开始游戏*/void simpleNim::play(){ nCoins = N_COINS; whoseTurn = STARTING_PLAYER; while(nCoins > 1){ cout << "这里有" << nCoins << "个硬币在桌面上" << endl; if(whoseTurn == HUMAN){ //不能写= nCoins -= getUserMove(); }else{ int nTaken = getComputerMove(); cout << "我将拿走" << nTaken << "个硬币" << endl; nCoins -= getComputerMove(); } whoseTurn = opponent(whoseTurn); //注意这里不能填STARTING_PLAYER } announceResult(); } /*实现打印规则*/ void simpleNim::printInstructions(){ cout << "欢迎来到Nim游戏" << endl; cout << "在这个游戏里,我们桌子有一堆含有" << N_COINS << "个硬币"; cout << endl; cout <<"每个回个你和我将从这里取走介于1跟"; cout << MAX_MOVE << " 个硬币." << endl; cout << "谁拿到最后一个硬币,谁就算输" << endl << endl;}/*实现计算机该拿走的数量*/ int simpleNim::getComputerMove(){ int nTaken = findGoodMove(nCoins); return (nTaken == NO_GOOD_MOVE) ? 1 : nTaken; }/*实现寻找好的策略*/int simpleNim::findGoodMove(int nCoins){ int limit = (nCoins < MAX_MOVE) ? nCoins : MAX_MOVE; /*在循环中,如果说我们把nTaken < limit,那么结果会如何?*/ for(int nTaken = 1; nTaken <= limit; nTaken++){ /*如果拿走nTaken个硬币后,剩下的处境为坏,那么就拿走nTaken个 *这个时候留给对手的始终是坏的处境,对于计算机来说这就是good move */ if(isBadPosition(nCoins - nTaken)) return nTaken; } return NO_GOOD_MOVE;} /*判断是否处于不利的处境*/bool simpleNim::isBadPosition(int nCoins){ if(nCoins == 1) return true; return findGoodMove(nCoins) == NO_GOOD_MOVE;} /*获取用户拿走的硬币数*/int simpleNim::getUserMove(){ while(true){ //试想一下,如果没有while(true)程序会怎么运行? int nTaken; cout << "你想拿走多少个硬币? "; cin >> nTaken; int limit = (nCoins < MAX_MOVE) ? nCoins : MAX_MOVE; if(nTaken > 0 && nTaken <= MAX_MOVE) return nTaken; cout << "输入不合法,请输入1到" << limit << "之间的数" << endl; cout << "这里有 " << nCoins << " 个硬币" << endl; }} /*宣布结果*/void simpleNim::announceResult(){ if(nCoins == 0){ cout << "你拿了最后一个硬币,你输了" << endl; }else{ cout << "这里只剩下一个硬币" << endl; if(whoseTurn == HUMAN){ cout << "你输了" << endl; } else { cout << "你赢了" << endl; } }}
测试代码
#include <iostream>#include "Nim.h"using namespace std;int main(){ simpleNim game; game.printInstructions(); game.play(); return 0;}
运行结果:
反思
- 第一个错误,是我的自己语法错误,在写头文件的时候忘记了加#endif,在编译的时候报错,这个错误还是容易找的。
- 第二个错误,是我第一次运行的时候,whoseTurn = opponent(whoseTurn); 括号里面填写了STARTING_PLAYER,这很明显会造成运行出错,因为STARTING_PLAYER是常量,它的值永远不会变,所以导致的结果就是一直是计算机在自己跟自己玩游戏。whoseTurn是变量,它的初始值是由STARTING_PLAYER赋值给它的,所以它的值可以改变。
- 第三个错误,是我在实现getUserMove()函数的时候,把while(true)函数忘写了,导致的结果就是在我输入1到3以外的数,它虽然会报错,但是它仍然视为你已经拿了硬币,只不过是不计入总数而已,相当于你进入了假的回合,这属于作弊行为。比如你一直这样,那么计算机总会拿到最后一个硬币
- 第四个错误,是我在实现findGoodMove函数的时候把nTaken <= limit,写成了把nTaken < =limit,这个时候相当于计算机只能考虑到至多两种情况,这就可能导致它不可能拿走3个硬币,这就导致了它一直都是只拿走一个硬币,因为它找不到最佳的解决方案。而我们定义的getComputerMove()是没有最佳方案的时候就拿一个。
- 第五个易错点是play函数中的这一句,if(whoseTurn == HUMAN),很容易写成if(whoseTurn =HUMAN),这个时候相当于赋值语句,那么出现的情况就会是只有计算机自己跟自己玩的情况.
- 这里我们还有一点值得关注,就是为什么程序的一些数据,我们用的是const?这个规则也就是一些数字,我们直接输入数字代替一些大写的字母不是更好?没错,确实如此。但是如果我的游戏规则有所修改呢?如果我输入过程中有错误呢?const能让我们只用修改值,就能修改涉及的其他代码。这也是一种值得学习的设计模式。
以上是我自己在写这个游戏的时候的一些调试过程以及个人感悟,希望对大家有帮助
0 0
- C++抽象编程——回溯算法(5)——Nim游戏代码及其反思
- C++抽象编程——回溯算法(4)——回溯在游戏中的应用
- C++抽象编程——回溯算法(6)——设计一般的双人游戏
- C++抽象编程——回溯算法(7)——极小化极大算法
- C++抽象编程——回溯算法(1)——迷宫问题
- C++抽象编程——回溯算法(2)——准备Direction文件
- C++抽象编程——回溯算法(3)——解决迷宫问题
- 博弈论—Nim游戏
- 博弈论—Nim游戏
- 编程之美——NIM(2) “拈”游戏分析
- 算法——回溯
- 【bzoj3150】 cqoi2013—新Nim游戏
- Nim游戏—改 博弈论 解题报告
- 回溯算法分析 —— basic代码实现
- hihocoder1172—博弈游戏·Nim游戏·二
- 编程之美1.13——NIM(3)两堆石头的游戏
- 编程之美——NIM(1)一排石头的游戏
- 【编程之美】1.11 NIM(1) —— 排石头的游戏
- 1.1.零web,js基础开发ReactNative_建立工程
- iOS CoreAnimation之CABasicAnimation:文字路径动画
- cocos2d-x 3.13 定时器Schedule 三种方式
- Bootstrap table的使用方法
- Opencv 特征训练分类器
- C++抽象编程——回溯算法(5)——Nim游戏代码及其反思
- Linux ftp 限制目录切换
- 换地方咯~
- SpringBoot 入门:整合MongoDB,做简单查找功能
- Hadoop基础教程-第1章 环境安装配置(1.7 目录规划)
- Shell 练习题 41-50,内附答案
- Docker的Swarm模式
- 回溯法解决排列组合问题
- mongoDB-基础教程笔记(一)