斗地主AI算法——第二章の数据结构


上一章我们已经确立了基本的业务逻辑处理流程。从本章开始,我们便进入开发阶段。

首先就是明确我们都需要哪些数据,且它们以怎样的形式存储。


首先从上一章反复提到的手牌权值结构说起,也就是F()的返回值,他包含了两个成员,①手牌总价值②需要打几手牌。


  
  
  1. //手牌权值结构
  2. struct HandCardValue
  3. {
  4. int SumValue; //手牌总价值
  5. int NeedRound; // 需要打几手牌
  6. };

这个不难理解吧,我们出牌的时候总是希望把牌划分成手数少且相对价值又大的组合方式,比如说AKQJ10,虽然都挺大,但你总不见得故意拆开出吧。


接下来就是组合牌型的数据结构,就算是上一章G()的返回值吧,因为G()主要返回的就是一个组合牌结构及出牌的序列


  
  
  1. //牌型组合数据结构
  2. struct CardGroupData
  3. {
  4. //枚举类型
  5. CardGroupType cgType=cgERROR;
  6. //该牌的价值
  7. int nValue= 0;
  8. //含牌的个数
  9. int nCount= 0;
  10. //牌中决定大小的牌值,用于对比
  11. int nMaxCard= 0;
  12. };

说明一下nMaxCard,比如说99,他的nMaxCard就是9,比如说33366,他的nMaxCard就是3。被动出牌规则便是通过这个值进行判断是否可以出牌。

cgType是所有牌型的枚举,具体定义为:


  
  
  1. //手牌组合枚举
  2. enum CardGroupType
  3. {
  4. cgERROR = -1, //错误类型
  5. cgZERO = 0, //不出类型
  6. cgSINGLE = 1, //单牌类型
  7. cgDOUBLE = 2, //对牌类型
  8. cgTHREE = 3, //三条类型
  9. cgSINGLE_LINE = 4, //单连类型
  10. cgDOUBLE_LINE = 5, //对连类型
  11. cgTHREE_LINE = 6, //三连类型
  12. cgTHREE_TAKE_ONE = 7, //三带一单
  13. cgTHREE_TAKE_TWO = 8, //三带一对
  14. cgTHREE_TAKE_ONE_LINE = 9, //三带一单连
  15. cgTHREE_TAKE_TWO_LINE = 10, //三带一对连
  16. cgFOUR_TAKE_ONE = 11, //四带两单
  17. cgFOUR_TAKE_TWO = 12, //四带两对
  18. cgBOMB_CARD = 13, //炸弹类型
  19. cgKING_CARD = 14 //王炸类型
  20. };

接下来便是两个主要的类,首先是游戏全局类,主要用于储存当前游戏的发展情况


  
  
  1. //游戏全局类
  2. class GameSituation
  3. {
  4. public:
  5. //构造函数
  6. GameSituation::GameSituation()
  7. {
  8. }
  9. //析构函数
  10. virtual GameSituation::~GameSituation()
  11. {
  12. }
  13. public:
  14. //地主玩家
  15. int nDiZhuID = -1;
  16. //本局叫分
  17. int nLandScore = 0;
  18. //当前地主玩家——还未确定
  19. int nNowDiZhuID = -1;
  20. //当前本局叫分——还未确定
  21. int nNowLandScore = 0;
  22. //三张底牌
  23. int DiPai[ 3] = { 0 };
  24. //已经打出的牌——状态记录,便于一些计算,值域为该index牌对应的数量0~4
  25. int value_aAllOutCardList[ 18] = { 0 };
  26. //三名玩家已经打出的手牌记录
  27. int value_aUnitOutCardList[ 3][ 18] = { 0 };
  28. //三名玩家已经剩余手牌个数
  29. int value_aUnitHandCardCount[ 3] = { 0 };
  30. //本局当前底分倍数
  31. int nMultiple = 0;
  32. //当前控手对象(用于区分是否可以自身任意出牌以及是否地主已经放弃出牌从而不去管队友)
  33. int nCardDroit = 0;
  34. //当前打出牌的类型数据,被动出牌时玩家根据这里做出筛选
  35. CardGroupData uctNowCardGroup;
  36. //本局游戏是否结束
  37. bool Over = false;
  38. };

然后是自身手牌的数据类,这里重点说明的是手牌序列相关,大家可以看到,类成员里定义了很多,感觉蛮晕的。

首先,在我们出牌逻辑中,是没有花色的概念的,即我们后续所有的逻辑计算只需要考虑当前手牌权值部分即可,所以定义了vector <int> value_nHandCardList手牌序列

当我们要反馈出牌情况时,再根据返回的无花色出牌序列映射到自己有花色的手牌序列中,再返回有花色的出牌序列。这些下一章会给出实现方法。

而无花色value手牌序列为了便于计算又设立了状态记录的数组int value_aHandCardList[18]。因为后续算法肯定需要大量通过回溯法深度遍历出牌策略的操作。

这个在后续的算法里会看到,也就是说,当程序在做出牌逻辑计算时,受影响的是int value_aHandCardList[18],当然最终都会回溯到原点。当确定好了出牌的序列,返回无花色出牌序列vector <int> value_nPutCardList。最后通过处理无花色出牌序列,改变其他数组的值。


  
  
  1. //手牌数据类
  2. class HandCardData
  3. {
  4. public:
  5. //构造函数
  6. HandCardData::HandCardData()
  7. {
  8. }
  9. //析构函数
  10. virtual HandCardData::~HandCardData()
  11. {
  12. }
  13. public:
  14. //手牌序列——无花色,值域3~17
  15. vector < int> value_nHandCardList;
  16. //手牌序列——状态记录,便于一些计算,值域为该index牌对应的数量0~4
  17. int value_aHandCardList[ 18] = { 0 };
  18. //手牌序列——有花色,按照从大到小的排列 56,52:大王小王……4~0:红3黑3方3花3
  19. vector < int> color_nHandCardList;
  20. //手牌个数
  21. int nHandCardCount = 17 ;
  22. //玩家角色地位 0:地主 1:农民——地主下家 2:农民——地主上家
  23. int nGameRole = -1;
  24. //玩家座位ID
  25. int nOwnIndex = -1;
  26. //玩家要打出去的牌类型
  27. CardGroupData uctPutCardType;
  28. //要打出去的牌——无花色
  29. vector < int> value_nPutCardList;
  30. //要打出去的牌——有花色
  31. vector < int> color_nPutCardList;
  32. HandCardValue uctHandCardValue;
  33. };


最后定义一些极限值


  
  
  1. //最多手牌
  2. #define HandCardMaxLen 20
  3. //价值最小值
  4. #define MinCardsValue -25
  5. //价值最大值
  6. #define MaxCardsValue 106


那么目前所需要的数据基本制定完毕,接下来是手牌中类成员函数的实现方法。


敬请关注下一章:斗地主AI算法——第三章の数据处理




猜你喜欢

转载自blog.csdn.net/King_fengzi/article/details/89325875