2048格子位置判定

最近发现2048可以自定义:

只看下面的16个格子,不看上面的统计数据。

看到这么多格子,首先很明显,至少有1个是2,而且至少有1个是4

可能全部由2和4组成,也可能由2和4和8组成,等等。

总之,这样就有个问题,因为分不清楚不同的数字,所以很难玩下去。

下面考虑这个问题:

如果2、4、8、16等等,所有的格子都是一模一样的,那么2048会有多少种看起来不一样的局面?

根据时间,可以分为3类。

第一类:刚开始的时候,有2个格子。

第二类:某次滑动之后,系统暂时还没添加2

第三类:系统添加2之后,玩家还没滑动

这只是根据时间来分而已,它们所包含的局面互相都各有重合的,所以考虑重合的问题是本文的关键。

虽然第一类比较简单,但是第二类更规则,应该从第二类开始分析。

第二类满足一个重要的规则:所有的格子都靠着某条边。

比如说,如果最后一次滑动是向下滑,那么所有的格子都是靠着下面的。

这样,每一列都有5种情况,即这一列的格子数目是0、1、2、3、4

这样,一共是555*5=625种情况。

但是,第三类就非常复杂了。

一方面,最新添加的2的位置不好分析,另外一方面,存在既靠着下面又靠着左边的情况,这样讨论就十分复杂了。

解决这种讨论十分繁琐的问题的途径之一,自然是编程了。

首先,信息的表示。

用1个16位二进制整数代表一个局面,从上到下,从左往右,比如上面的图片就是0000 0010 0011 1111

然后定义合法的局面。

合法的局面有2种,一种是刚开始的时候有2个格子,即第一类时间,

还有一种是第二类时间和第三类时间的合并,即要么全部靠着某条边,要么除了某个格子之外全部靠着某条边。

(没有格子是不算靠着边的)

除此之外的局面自然是非法的。

但是要说这些局面就一定都是合法的,还要看2048的规则。

实际上,只要是满足上述条件就一定是合法的,因为存在仅由2、4、8、16构成的对应的局面,详情略。

因为总局面只有2^16=65536种,所以直接枚举就可以了,不需要用计数的方法。

虽然4个方向是等价的,但是现在有了确定的编码方案,4个方向就不一样了,最容易判断的,是是否靠右边。

靠右边就是4行都靠右边,而一行里面,靠右边就只有5种情况:0000、0001、0011、0111、1111

判断一行的话,只需要一个简单的函数ok就行

bool ok(int n)
{
if (n != 0 && n != 1 && n != 3 && n != 7 && n != 15)return false;
return true;
}
判断一个局面是否靠右,只需要4次调用ok函数即可
bool right(int n)
{
return ok(n & 15) && ok((n >> 4) & 15) && ok((n >> 8) & 15) && ok((n >> 12) & 15);
}
判断一个局面是否靠左,可以转化成是否靠右的问题。
总局面有65536种,即从0到65535,这些局面可以两两配对。
i+j=65535的2个局面i和j即为配对的局面,这样的2个局面是互补的。

这样,是否靠左的问题就可以转化成是否靠右的问题了。
所以我们可以得到,判断是否靠左或者靠右的函数left_or_right:
bool left_or_right(int n)
{
if (right(n))return true;
return right(65535 - n);
}

然后判断是否靠上或者靠下,这个问题可以转化成是否靠左或者靠右。
类似上面的做法,这里同样需要一种变换,不过不像上面的互补这么简洁,这里的变换是沿着对角线翻转。

所以,这样我们就得到了判断一个局面是否靠着某条边的函数all_direction:
bool all_direction(int n)
{
if (n == 0)return false;
if (left_or_right(n))return true;
return left_or_right(n - (((n>>4)%2 - (n>>1)%2) * 14 + ((n>>8)%2 - (n>>2)%2) * 252 + ((n>>9)%2 - (n>>6)%2) * 448 + ((n>>12)%2 - (n>>3)%2) * 4088 + ((n>>13)%2 - (n>>7)%2) * 8064 + ((n>>14)%2 - (n>>11)%2) * 14336));
}
其中专门把n=0的情况排除掉了。

现在可以在主函数里面统计第二种合法局面的个数了。
int main()
{ int r[65536];
for (int i = 0; i < 65536; i++)
{
r[i] = 0;
if (all_direction(i))r[i] = 1;
for (int k = 0; k < 16; k++)if ((i&(1<<k))!=0)
if (all_direction(i^(1<<k)))r[i] = 1;
}
int result = 0;
for (int i = 0; i < 65536; i++)result += r[i];
cout << result;
return 0;
}
结果是11755

除此之外,还有第一种合法局面,即刚开始的2个格子。
任选2个格子,得到的局面都是第一种合法局面,但是这2种情况有重合的部分,只需要考虑,不在这1175种情况之中的情况。
即2个格子是不靠边的,去掉其中一个格子之后,另外一个格子也是不靠边的。
这样,2个格子都不能靠边,即只能在中间的4个格子里面选取2个不同的格子,所以一共有6种情况。

所以最后的结果是11755+6=11761
————————————————
版权声明:本文为CSDN博主「csuzhucong」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/nameofcsdn/article/details/52961854

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值