导入
有了整个游戏的流程构想,就需要把它写进类里,但要清楚类的互相包含关系
正题
以下树状图写出了互相包含的关系:
- Maj_system 是整的一局游戏,从洗牌发牌,到荣和算点,包含了四家(Opponent)、136 张牌山(shan)、牌山计数(shan_count)、场风(wind)和现在轮到操作的玩家(turn)
- Opponent 是四家的数据,每家都有 14 张手牌(Tile),副露区(Fulu),还有舍张区(牌河)(she)
- 每个 Fulu 类型包含了四张牌(Tile),副露来源方(jing),副露类型(lu)
- Wind 类型记录风,只有 0 1 2 3(东南西北)四个值,-fulu-(下划线)是副露的枚举类型,以后会介绍
以上,先写 Maj_system 类,除了包含以上的一些数据,还有上一篇所提到的那些函数,都要写进去:
typedef signed __int16 _int_; //定义类型别名,方便有需要时候修改
typedef unsigned long long _ull_; //下标转化
using namespace std;
class Maj_system
{
private:
Wind turn; //出牌家
array<Opponent, 4> _op_; //四家
Wind wind; //场风
Round_info shan_count; //牌山计数
array<Tile*, 136> shan; //牌山 注意包括头文件<array>
public:
explicit Maj_system(_wind_ _wind) :wind{ _wind } //(场风)开局
{return;}
//以上System()为开始构造函数
void _mo() //摸牌
{return;}
void _da() //打牌
{return;}
//以上_mo(),_da()为一般性过程
_fulu_ _jiaangang() //加杠、暗杠判断
{return 0;}
_int_ _penggang() //碰、大明杠判断
{return 0;}
bool _chi() //吃判断
{return 0;}
bool _lizhi() //立直判断
{return 0;}
//以上_jiaangang(),_penggang(),_chi(),_lizhi()为特殊性过程(鸣牌)
bool _zimo() //自摸判断
{return 0;}
bool _ronghe(char mode) //荣和判断
{return 0;}
//以上_zimo(),_ronghe()为总结性过程(特殊鸣牌)
~Maj_system() //结算
{return;}
//以上jiesuan()为结束析构函数
};
以上这些我放在了 main.hpp 中,和 main 函数分开
至此 Maj_system 类大体框架完成,以后不会有很大的变动了
- enum 类型相当于把一堆宏定义(#define)(或常量表达式(constexpr))打包,成为一个集合,可以美化代码
- 数组(array<类型名, 长度> 对象名)是一个长度一定的数组,较一般的数组功能更多,数据更安全
- 容器(vector<类型名> 对象名(长度, 初值))(括号内可只有长度,也可以没有括号(空容器))是一个可变长度的数组,功能强大,也更安全;但是类成员 vector 不能直接初始化,要在构造函数内进行 resize(n)1 操作
- 构造函数后用":",也是赋初值的意思,可以将成员函数在这里赋值,他会在构造函数执行前赋值
- 大括号赋初值是 C++11 的新标准,它统一了所有赋初值的方法(如普通的等于号赋值,数组的大括号赋值,对象的构造函数赋值等):
//旧赋值法
int a = 1;
int b[3] = {1,2,3};
T c(3); // T 是一个类,构造函数接受一个整数参数
//新赋值法
int a{ 1 };
int b[3]{ 1,2,3 }; //注意!这里没有等于号
T c{ 3 }; //同上
- explicit 是针对于构造函数的关键字,加上后就会禁止隐式调用构造函数2,在最新版VS上,相当于禁止了用等号赋初值(因为等号是将等号后的字符先实现成对象,再赋值)(大、小括号的赋初值仍然允许)
enum _wind_ { EAST, SOUTH, WEST, NORTH }; //风
enum _fulu_ { //副露 0b指的是二进制数,用处会在以后解释
NULL_FULU = 0b1, //空
CHI = 0b10, //吃
PENG = 0b100, //碰
JIA = 0b1000, //加杠
DAMING = 0b10000, //大明杠
AN = 0b100000, //暗杠
LIZHI = 0b1000000, //立直
LIUJV = 0b10000000, //流局
RONGHE = 0b100000000, //荣和
ZIMO = 0b1000000000, //自摸
DA = 0b10000000000, //打牌 打、摸这两个只是为了传递信息
MO = 0b100000000000 //摸牌
};
- 这里有个优化:%4相当于&3,2的正整数次幂有这种性质3
class Wind
{
public:
_wind_ wind{ EAST }; //值范围0-3
explicit Wind(_int_ _wind = EAST) : wind{ (_wind_)(_wind & 3) } {} //对负数失效
operator _int_() const { return wind; }
void operator=(_int_ _wind) { wind = (_wind_)_wind; }
_wind_& operator++() { wind = (_wind_)((wind + 1) & 3); return wind; } //&3相当于%4
_wind_& operator--()
{
wind = wind == EAST ? NORTH : (_wind_)(wind - 1);
return wind;
}
};
class Fulu
{
public:
vector<Tile*> tile;
/*vector<Tile*> tile(4, nullptr);*/ //(E0079)应输入类型说明符
_fulu_ lu{ NULL_FULU }; //记录副露类型
Wind jing; //记录牌来源方
explicit Fulu(){tile.reserve(4);} //分配空间
};
class Opponent
{
public:
vector<Tile*> tile; //手牌区 注意包括头文件<vector>
vector<Fulu> fulu; //副露区
vector<Tile*> she; //牌河(舍张)
explicit Opponent(){ tile.reserve(14);tile.resize(14); } //分配空间
};
这里的 Round_info 为了更好地贴近面麻,加入了壁、幢等说法,如果要简化,这个可以只用一个整型变量记录
class Round_info
{
private:
_int_ dice1{ 0 }; //记录骰子点数(每点表示1幢)
_int_ dice2{ 0 };
Wind bi; //壁牌计数(0-3)
_int_ zhuang{ 0 }; //幢计数(0-16)
bool height{ 0 }; //是某幢上面(0)还是下面(1)的牌
_int_ count{ 0 }; //牌山计数
public:
void rand_dice() //随机生成骰子
{
dice1 = rand() % 6 + 1;
dice2 = rand() % 6 + 1;
zhuang = dice1 + dice2;
}
constexpr operator _int_() const { return count; }
_int_& operator++()
{
if (height == 0)
height = 1;
else if (zhuang < 16)
{
height = 0;
++zhuang;
}
else
{
height = 0;
zhuang = 0;
++bi;
}
++count;
return count;
}
constexpr _int_ remains() const { return 122 - count - gang; }
//count 是下一张牌的序号,但下一张牌的序号正好是现在已取走的牌数,所以剩余数=136-14-count-gang
//gang 是杠牌的计数,以后会引入
};
规则参考
[1] 日麻百科
[2] 资深麻友
[3] 雀魂麻将
[4] 雀姬麻将
原创文章,转载请标明出处,如有谬误欢迎各位指正
欢迎交流麻将算法,QQ:2639914082