Hopping dots (独立钻石棋变种)攻略

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/nameofcsdn/article/details/60339354

前言:这篇文章,我想完整的讨论一个游戏,叫Hopping dots,被翻译为逻辑难题。

关键词:独立钻石棋、深度优先搜索、可能性分析、状态压缩、动态规划的备忘录方法


一,APK下载链接

点击打开链接(来自互联网)  或者 点击打开链接(来自我的百度云)

我的版本:Hopping dots 1.1


二,游戏界面、棋盘、棋子

(下图为第一关)

(棋盘由13个点组成)


棋子:1个红子和若干个绿子


三,规则

按照独立钻石棋的规则进行吃子,最后只剩下一个子,且为红子,即为胜利。(无论红子在何处)

这是我自己总结的规则,非常简洁,初一看貌似不准确,仔细一想实际上和官方的定义是一样的。


四,初步认识

1,棋子的数量

棋子最多有12个,最少有1个。

13个棋子肯定是死局面(无法胜利的局面),因为没法进行操作。

12个棋子的局面中,存不存在活局面(能够胜利的局面),这个暂且不知。


2,棋子数量变化

每次操作,棋子数量都是少1,所以,任何局面,最多只能再进行11次操作


3,问题的简化

问题的简化版:只有绿子没有红子,只要最后只剩下一个子即为胜利(同样不论位置)

这样的版本就更接近独立钻石棋了,准确说来,除了棋盘和独立钻石棋不一样之外,没有任何差异。

对于玩家来说,原问题和问题的简化版差异并不是很大,玩起来难度差不多。

而对于想做理论分析的笔者来说,原问题分析起来很繁琐,掩盖了很多规律。

如果只是想直接尝试编写深度优先搜索的程序解决问题,其实是不需要简化的,但是如果要严谨一些,先从理论上分析可能性大小,所以如此简化正是第一步便要做的事情。


4,局面的数量(问题的简化版)

有13个点是可能有子的,所以局面的数量为2^13=8192

其中还包括了大量的死局面,具体有多少活局面,我们并不关心。


5,编号



6,记录所有的边(不考虑方向)

不难数出,一共有16条边

以2、6、8、12为中点的边各有1条,如1-2-3

以4、5、9、10位中点的边各有2条,如1-4-7,2-4-6

以7为中点的边有4条,如2-7-12,4-7-10

之所以不考虑方向,是因为任何时候,一条边最多对应一种可行操作,

以边1-2-3为例,同一局面下,“从1跳到3”和“从3跳到1”不可能都是可行的操作。

注意到,每条边的3个数都是等差数组,所以一条边只需要2个数字即可记录下来,

这样,便可以用2个长度为16的数组来记录下16条边了。

int st[16] = { 1, 1, 3, 11, 1, 2, 2, 3, 6, 7, 8, 7, 4, 5, 6, 2 };
int en[16] = { 3, 11, 13, 13, 7, 6, 8, 7, 12, 11, 12, 13, 10, 9, 8, 12 };


五,深度优先搜索的可能性分析(问题的简化版)

因为最多只能进行11次操作,所以深度优先搜索的深度不深。现在需要计算的是,每一次操作有多少种选择?


1,任一局面有多少种选择

我们需要一个函数,输入一个局面,输出一个数值,告诉我们有多少种选择。

如何输入呢?使用状态压缩最为方便。

13个点,每个点对应一位,1表示有子,0表示没有子,这样,8192个局面便可以和8192个13位二进制数一一对应了。

输入之后,函数就可以直接计算出有多少种选择了,时间为O(1)

有了这个函数,只要从0到8191枚举n,就能求出f(n)的最大值了,结果是10

代码:

#include <iostream>
using namespace std;

int st[16] = { 1, 1, 3, 11, 1, 2, 2, 3, 6, 7, 8, 7, 4, 5, 6, 2 };
int en[16] = { 3, 11, 13, 13, 7, 6, 8, 7, 12, 11, 12, 13, 10, 9, 8, 12 };

int f(int n)
{
	int r = 0, s, e, m;
	for (int i = 0; i < 16; i++)
	{
		s = st[i], e = en[i], m = (s + e) / 2;
		if ((n >> (13 - m)) & 1)r += ((n >> (13 - s)) & 1) ^ ((n >> (13 - e)) & 1);
	}
	return r;
}

int main()
{
	int maxx = 0;
	for (int n = 0; n < 8192; n++)if (maxx < f(n))maxx = f(n);
	cout << maxx;
	return 0;
}


2,深度优先搜索的复杂度

前面分别算出,一个局面最多能再进行11次操作,每次操作最多10种选择,那么,用深度优先搜索解决这个问题最多需要10^11次枚举计算。这是一个很大的数,普通的笔记本要算很久。

然而,我们不难发现,并非每次操作都有10种选择,比如只剩2个子的时候最多只有2种选择。

那么如果一个局面有k(1<k<13)个子,这个局面最多有多少种选择?

只需利用上面的函数f即可。

代码:

#include <iostream>
using namespace std;

int st[16] = { 1, 1, 3, 11, 1, 2, 2, 3, 6, 7, 8, 7, 4, 5, 6, 2 };
int en[16] = { 3, 11, 13, 13, 7, 6, 8, 7, 12, 11, 12, 13, 10, 9, 8, 12 };
int num[14];//k个棋子的所有局面中最多有多少种选择

void f(int n)
{
	int r = 0, s, e, m, k = 0;
	for (int i = 0; i < 16; i++)
	{
		s = st[i], e = en[i], m = (s + e) / 2;
		if ((n >> (13 - m)) & 1)r += ((n >> (13 - s)) & 1) ^ ((n >> (13 - e)) & 1);
	}
	while (n)
	{
		k += n & 1;
		n /= 2;
	}
	if (num[k] < r)num[k] = r;
}

int main()
{
	for (int i = 0; i < 14; i++)num[i] = 0;
	for (int n = 0; n < 8192; n++) f(n);
	for (int i = 2; i <= 12; i++)cout << num[i] << "  ";
	return 0;
}

运行结果:

2  4  7  7  9  10  9  9  8  6  4

2*4*7*7*9*10*9*9*8*6*4=548674560,所以任何一个局面,进行深度优先搜索的话,最多需要约5亿次枚举计算

计算机一秒可以进行约1亿次计算,所以这个时间是可以接受的。


七,原问题的求解

原问题由于有红子的限制,所以编程起来要复杂一些,但是需要枚举的情况少一些。

这样,就可以深度优先搜索求解了,同时,因为局面的数量很有限,所以用动态规划的备忘录方法来避免重复工作。

代码:

#include <iostream>
#include<stack>
using namespace std;

int r[8192][14];//0表示未知,-1表示死局面,1表示活局面
int st[16] = { 1, 1, 3, 11, 1, 2, 2, 3, 6, 7, 8, 7, 4, 5, 6, 2 };
int en[16] = { 3, 11, 13, 13, 7, 6, 8, 7, 12, 11, 12, 13, 10, 9, 8, 12 };
stack<int>ans;

bool f(int n, int k)
{
	if (n == (n&-n))return true;//只有1个子
	if (r[n][k] < 0)return false;
	int s, e, m;
	for (int i = 0; i < 16; i++)
	{
		s = st[i], e = en[i], m = (s + e) / 2;
		if (m == k)continue;//红子不能被跳过
		if (!((n >> (13 - m)) & 1))continue;
		if (!(((n >> (13 - s)) & 1) ^ ((n >> (13 - e)) & 1)))continue;
		int nn, kk = k, tem;
		if (k == s || k == e)kk = s + e - k;
		tem = (1 << (13 - s)) + (1 << (13 - e));
		nn = n - (n&tem) + tem - (n&tem) - (1 << (13 - m));
		if (f(nn, kk))
		{
			ans.push(i);
			m = -1;
			break;
		}
	}
	if (m == -1)return true;
	r[n][k] = -1;
	return false;
}

int main()
{
	int n = 0, k;//n表示无颜色局面,k表示红子位置
	cout << "按照编号\n1    2    3\n  4    5\n6    7    8\n  9    10\n11   12   13\n";
	cout << "即从上到下,从左往右,依次输入每个格子\n1表示有子,0表示没有子,全部用空格隔开\n";
	for (int i = 1; i <= 13; i++)
	{
		cin >> k;
		n = n * 2 + k;
	}
	cout << "输入红子所在格子的序号(1-13)\n";
	cin >> k;
	for (int i = 0; i < 8192; i++)for (int j = 0; j < 14; j++)r[i][j] = 0;
	while (!ans.empty())ans.pop();
	f(n, k);
	cout << "答案为:(一行表示一次操作,每行2个整数分别代表起点和终点的序号)\n";
	while (!ans.empty())
	{
		int i = ans.top();
		ans.pop();
		cout << st[i] << "   " << en[i] << endl;
	}
	return 0;
}

示例:





展开阅读全文

关于“独立钻石棋”

05-20

“独立钻石棋”是起源与法国的一种棋类游戏,具体布局如下:rn      口口口rn      口口口rn    口口口口口口口rn    口口口 口口口rn    口口口口口口口rn      口口口rn      口口口rn  行棋规则:每个子只能沿着棋盘上的纵横线“隔子跳”(像跳棋一样,跳过一个相rn邻的棋子),跳到一个空格处,跳后被跳过的棋子将拿掉。rn  这样,棋子跳到最后,无子可再动时,游戏结束。rn  当游戏结束时,只剩一个棋子,并且这个棋子落在棋盘的中央,为最佳结果! rnrnrn独立钻石棋有以下算法:(P43.0一分钟之内算出:)rn#include rnrnint map[7][7]=rn2,2,1,1,1,2,2,rn2,2,1,1,1,2,2,rn1,1,1,1,1,1,1,rn1,1,1,0,1,1,1,rn1,1,1,1,1,1,1,rn2,2,1,1,1,2,2,rn2,2,1,1,1,2,2;rnrnstruct jumprnrn int x1,y1,x2,y2;rn l[32];rnrnint p=0;rnrnbool move(int x1,int y1,int x2,int y2);rnrnmain()rnrn if(!move(1,3,3,3))printf("\nFailure!");rnrnrnbool move(int x1,int y1,int x2,int y2)rnrn bool tmp=false;rn int i,j;rn if(0<=x2&&x2<=7&&0<=y2&&y2<=7&&map[x2][y2]==0&&map[(x1+x2)/2][(y1+y2)/2]==1)rn rn map[x2][y2]=1;map[x1][y1]=0;map[(x1+x2)/2][(y1+y2)/2]=0;rn l[p].x1=x1;l[p].y1=y1;l[p].x2=x2;l[p].y2=y2;p++;rn if(p==31&&map[3][3]==1)rn rn for(i=0;i(%d,%d) ",l[i].x1,l[i].y1,l[i].x2,l[i].y2);rn rn tmp=true;rn rn for(i=0;i<7&&!tmp;i++)for(j=0;j<7&&!tmp;j++)if(map[i][j]==1)rn tmp=move(i,j,i-2,j)||move(i,j,i,j-2)||move(i,j,i+2,j)||move(i,j,i,j+2);rn map[x2][y2]=0;map[x1][y1]=1;map[(x1+x2)/2][(y1+y2)/2]=1;p--;rn rn return tmp;rnrnrn但如果把棋盘变复杂了(加一层):rn#include rnint diamond[9][9]=rn2,2,2,1,1,1,2,2,2rn,2,2,2,1,1,1,2,2,2rn,2,2,2,1,1,1,2,2,2rn,1,1,1,1,1,1,1,1,1rn,1,1,1,1,0,1,1,1,1rn,1,1,1,1,1,1,1,1,1rn,2,2,2,1,1,1,2,2,2rn,2,2,2,1,1,1,2,2,2rn,2,2,2,1,1,1,2,2,2;rnstruct processionrnrn int x1,x2,y1,y2;rnpro[44];rnint p=0;rnbool move(int x1,int y1,int x2,int y2);rnvoid main()rnrn if(!move(2,4,4,4))rn cout<<"不存在走法。";rnrnbool move(int x1,int y1,int x2,int y2)rnrn bool flag=false;rn int i,j;rn if(x2>=0&&x2<=9&&y2>=0&&y2<=9&&diamond[x2][y2]==0&&diamond[(x1+x2)/2][(y1+y2)/2]==1)rn rn diamond[x2][y2]=1;rn diamond[x1][y1]=0;rn diamond[(x1+x2)/2][(y1+y2)/2]=0;rn pro[p].x1=x1;rn pro[p].y1=y1;rn pro[p].x2=x2;rn pro[p].y2=y2;rn p++;rn if(p==43&&diamond[4][4]==1)rn rn for(i=0;i"< 论坛

独立钻石棋”算法讨论

05-23

rn“独立钻石棋”是起源与法国的一种棋类游戏,具体布局如下:rn      口口口rn      口口口rn    口口口口口口口rn    口口口 口口口rn    口口口口口口口rn      口口口rn      口口口rn  行棋规则:每个子只能沿着棋盘上的纵横线“隔子跳”(像跳棋一样,跳过一个相rn邻的棋子),跳到一个空格处,跳后被跳过的棋子将拿掉。rn  这样,棋子跳到最后,无子可再动时,游戏结束。rn  当游戏结束时,只剩一个棋子,并且这个棋子落在棋盘的中央,为最佳结果! rnrnrn独立钻石棋有以下算法:(P43.0一分钟之内算出:)rn#include rnrnint map[7][7]=rn2,2,1,1,1,2,2,rn2,2,1,1,1,2,2,rn1,1,1,1,1,1,1,rn1,1,1,0,1,1,1,rn1,1,1,1,1,1,1,rn2,2,1,1,1,2,2,rn2,2,1,1,1,2,2;rnrnstruct jumprnrn int x1,y1,x2,y2;rn l[32];rnrnint p=0;rnrnbool move(int x1,int y1,int x2,int y2);rnrnmain()rnrn if(!move(1,3,3,3))printf("\nFailure!");rnrnrnbool move(int x1,int y1,int x2,int y2)rnrn bool tmp=false;rn int i,j;rn if(0<=x2&&x2<=7&&0<=y2&&y2<=7&&map[x2][y2]==0&&map[(x1+x2)/2][(y1+y2)/2]==1)rn rn map[x2][y2]=1;map[x1][y1]=0;map[(x1+x2)/2][(y1+y2)/2]=0;rn l[p].x1=x1;l[p].y1=y1;l[p].x2=x2;l[p].y2=y2;p++;rn if(p==31&&map[3][3]==1)rn rn for(i=0;i(%d,%d) ",l[i].x1,l[i].y1,l[i].x2,l[i].y2);rn rn tmp=true;rn rn for(i=0;i<7&&!tmp;i++)for(j=0;j<7&&!tmp;j++)if(map[i][j]==1)rn tmp=move(i,j,i-2,j)||move(i,j,i,j-2)||move(i,j,i+2,j)||move(i,j,i,j+2);rn map[x2][y2]=0;map[x1][y1]=1;map[(x1+x2)/2][(y1+y2)/2]=1;p--;rn rn return tmp;rnrnrn但如果把棋盘变复杂了(加一层):rn#include rnint diamond[9][9]=rn2,2,2,1,1,1,2,2,2rn,2,2,2,1,1,1,2,2,2rn,2,2,2,1,1,1,2,2,2rn,1,1,1,1,1,1,1,1,1rn,1,1,1,1,0,1,1,1,1rn,1,1,1,1,1,1,1,1,1rn,2,2,2,1,1,1,2,2,2rn,2,2,2,1,1,1,2,2,2rn,2,2,2,1,1,1,2,2,2;rnstruct processionrnrn int x1,x2,y1,y2;rnpro[44];rnint p=0;rnbool move(int x1,int y1,int x2,int y2);rnvoid main()rnrn if(!move(2,4,4,4))rn cout<<"不存在走法。";rnrnbool move(int x1,int y1,int x2,int y2)rnrn bool flag=false;rn int i,j;rn if(x2>=0&&x2<=9&&y2>=0&&y2<=9&&diamond[x2][y2]==0&&diamond[(x1+x2)/2][(y1+y2)/2]==1)rn rn diamond[x2][y2]=1;rn diamond[x1][y1]=0;rn diamond[(x1+x2)/2][(y1+y2)/2]=0;rn pro[p].x1=x1;rn pro[p].y1=y1;rn pro[p].x2=x2;rn pro[p].y2=y2;rn p++;rn if(p==43&&diamond[4][4]==1)rn rn for(i=0;i"< 论坛

没有更多推荐了,返回首页