微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

独立钻石棋

目录

一,独立钻石棋

二,搜索求解(剩3颗)

三,正确解法(剩1颗)

1,解法1

2,解法2

四,最少步数

五,独立钻石棋变种——八边形

六,独立钻石棋变种——9行9列

七,独立钻石棋变种——Hopping dots

1,Hopping dots

2,规则

3,问题的简化

(3.1)局面的数量

(3.2)编号

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

(3.4)深度优先搜索的可能性分析

4,原问题的求解

八,独立钻石棋变种——六边形坐标系


一,独立钻石棋

        独立钻石源于18世纪法国的宫廷贵族,是一种自我挑战的单人棋游戏,可以锻炼逻辑思维能力。游戏玩法似中国跳棋,但不能走步,只能跳。棋子只能跳过相邻的柜子到空位上,并且把被跳过的柜子吃掉。棋子可以沿格线横、纵方向跳,但是不能斜跳,剩下越少棋子越好。它与中国人发明的“华容道”,匈牙利人发明的“魔方”并称智力游戏界的三大不可思议之一。
        玩法是在棋盘33孔中,每孔都放下一棋,但是取中心的一孔是空着的。玩的时候是像跳棋一样行子。一棋子依直线在平行或垂直(不能依斜线)的方向跳过一棋子,而放在此棋子之后的一个空格内。故此,棋子后必要有空的孔才可跳过。每次棋子跳去一个空孔,被跳过的棋便移离棋盘。这时棋盘上便少了一只棋子。如此一直玩下去,使剩下来的棋子越少越好。

        独立钻石的棋盘有很多种变形,这个棋盘就是最初的样子,走法就是按照跳棋的走法,但是被跳过的棋子全部被吃掉,这样每跳一下棋盘上的棋子就会少一颗,而如果像跳棋那样连跳,每步就可能吃掉很多颗棋子。游戏的目的是要使棋盘上留下来的棋子越少越好。如果最后剩一子,而且正好位于棋盘正中心的洞孔上,那就是最好的结果。此种局势称为“独立(粒)钻石”。之所以把这种游戏取上这一名称,是因为人们喜爱“金鸡独立”,视为祥瑞之故。

        APK下载链接资源分享汇总_nameofcsdn的博客-CSDN博客

二,搜索求解(剩3颗)

深度优先搜索求解的代码

#include<iostream>
using namespace std;

int sum(int list[][7])			//sum函数用来返回最后剩余多少个棋子
{
	int sum = 0;
	for (int i = 0; i < 7; i++)for (int j = 0; j < 7; j++)sum += list[i][j];
	return sum - 33;
}

void up(int list[][7], int i, int j)	//跳过(i,j)往上跳
{
	list[i - 1][j] ++;
	list[i][j] --;
	list[i + 1][j] --;
}

void down(int list[][7], int i, int j)	//跳过(i,j)往下跳
{

	list[i + 1][j] ++;
	list[i][j] --;
	list[i - 1][j] --;
}

void left(int list[][7], int i, int j)	//跳过(i,j)往左跳
{

	list[i][j - 1] ++;
	list[i][j] --;
	list[i][j + 1] --;
}

void right(int list[][7], int i, int j)	//跳过(i,j)往右跳
{

	list[i][j + 1] ++;
	list[i][j] --;
	list[i][j - 1] --;
}

bool move(int list[][7])		//找出任何可以跳的一步,并跳,跳完之后递归调用move(),直到不能再跳
{
	if (sum(list) < 4)return true;		//当得到一种只剩下3颗棋子的方法,程序结束
	for (int i = 1; i < 6; i++)for (int j = 0; j<7; j++)
	{
		if ((i == 3 || j>1 && j < 5) && list[i][j] == 2)
		{
			if (list[i - 1][j] == 1 && list[i + 1][j] == 2)	//满足往上跳的条件
			{
				up(list, i, j);
				if (move(list))
				{
					cout << i << j << "up  ";
					return true;
				}
				down(list, i, j);	//恢复list和result
				list[i][j] += 2;
			}
			else if (list[i - 1][j] == 2 && list[i + 1][j] == 1)//满足往下跳的条件
			{
				down(list, i, j);
				if (move(list))
				{
					cout << i << j << "down  ";
					return true;
				}
				up(list, i, j);
				list[i][j] += 2;
			}
		}
	}
	for (int i = 0; i < 7; i++)for (int j = 1; j < 6; j++)
	{
		if ((j == 3 || i>1 && i < 5) && list[i][j] == 2)
		{
			if (list[i][j - 1] == 1 && list[i][j + 1] == 2)//满足往左跳的条件
			{
				left(list, i, j);
				if (move(list))
				{
					cout << i << j << "left  ";
					return true;
				}
				right(list, i, j);
				list[i][j] += 2;
			}
			else if (list[i][j - 1] == 2 && list[i][j + 1] == 1)//满足往右跳的条件
			{
				right(list, i, j);
				if (move(list))
				{
					cout << i << j << "right  ";
					return true;
				}
				left(list, i, j);
				list[i][j] += 2;
			}
		}
	}
	return false;
}

int main()
{
	int list[7][7];		//list用来表示状态,1表示空格,2表示有棋子,0表示角上的无效格子
	for (int i = 0; i < 7; i++)for (int j = 0; j < 7; j++)
	{
		list[i][j] = 0;
		if (i == 2 || i == 3 || i == 4 || j == 2 || j == 3 || j == 4)list[i][j] = 2;
	}
	list[3][3] = 1;		//初始化list
	move(list);
	cout << endl << "注意,输出的顺序是反着的";
	system("pause>nul");
	return 0;
}

为了稍微加快一点点速度,代码可以变成:

#include<iostream>
using namespace std;

int list[7][7];		//list用来表示状态,1表示空格,2表示有棋子,0表示角上的无效格子
int num = 32;

bool move()		//找出任何可以跳的一步,并跳,跳完之后递归调用move(),直到不能再跳
{
	if (num < 4)return true;		//当得到一种只剩下3颗棋子的方法,程序结束
	for (int i = 1; i < 6; i++)for (int j = 0; j<7; j++)
	{
		if ((i == 3 || j>1 && j < 5) && list[i][j] == 2)
		{
			if (list[i - 1][j] == 1 && list[i + 1][j] == 2)	//满足往上跳的条件
			{
				list[i - 1][j] ++, list[i][j] --, list[i + 1][j] --, num--;
				if (move())
				{
					cout << i << j << "up  ";
					return true;
				}
				list[i - 1][j] --, list[i][j] ++, list[i + 1][j] ++, num++;
			}
			if (list[i - 1][j] == 2 && list[i + 1][j] == 1)//满足往下跳的条件
			{
				list[i + 1][j] ++, list[i][j] --, list[i - 1][j] --, num--;
				if (move())
				{
					cout << i << j << "down  ";
					return true;
				}
				list[i + 1][j] --, list[i][j] ++, list[i - 1][j] ++, num++;
			}
		}
	}
	for (int i = 0; i < 7; i++)for (int j = 1; j < 6; j++)
	{
		if ((j == 3 || i>1 && i < 5) && list[i][j] == 2)
		{
			if (list[i][j - 1] == 1 && list[i][j + 1] == 2)//满足往左跳的条件
			{
				list[i][j - 1] ++, list[i][j] --, list[i][j + 1] --, num--;
				if (move())
				{
					cout << i << j << "left  ";
					return true;
				}
				list[i][j - 1] --, list[i][j] ++, list[i][j + 1] ++, num++;
			}
			if (list[i][j - 1] == 2 && list[i][j + 1] == 1)//满足往右跳的条件
			{
				list[i][j + 1] ++, list[i][j] --, list[i][j - 1] --, num--;
				if (move())
				{
					cout << i << j << "right  ";
					return true;
				}
				list[i][j + 1] --, list[i][j] ++, list[i][j - 1] ++, num++;
			}
		}
	}
	return false;
}

int main()
{
	for (int i = 0; i < 7; i++)for (int j = 0; j < 7; j++)
		list[i][j] = (i == 2 || i == 3 || i == 4 || j == 2 || j == 3 || j == 4) * 2;
	list[3][3] = 1;		//初始化list
	move();
	cout << endl << "注意,输出的顺序是反着的";
	return 0;
}

输出

41right  42left  44left  30down  41right  43right  12down  13left  24up  25left  24right  44up  36up  14down  24up  25left  22right  24down  23up  34left  52up  32up  13down  23up  22down  32right  53up  33up  23down
注意,输出的顺序是反着的。

对应的结果:

最后还剩下3个子。

程序如果改成求只剩下2个子的方案,理论上是求的出来的,不过时间会很长,反正我运行了20分钟还没有答案输出

三,正确解法(剩1颗)

其实,要想最后只剩一个棋子,而且还在最中间,这并不难。

1,解法1

我自己找出了一个最后只剩一个棋子,而且还在最中间的方案,然后把上面的代码稍微修改,得到下面的代码,用来显示我发现的方案。

代码

#include<iostream>
using namespace std;

int list[7][7];	

void display()
{
	for (int i = 0; i < 7; i++)
	{
		for (int j = 0; j < 7; j++)
		{
			if (i == 2 || i == 3 || i == 4 || j == 2 || j == 3 || j == 4)
			{
				if (list[i][j] == 2)cout << "●";
				else cout << "〇";
			}
			else cout << "  ";
		}
		cout << endl;
	}
	cout << endl << endl << endl;
}

void up(int i, int j)
{
	list[i - 1][j] ++;
	list[i][j] --;
	list[i + 1][j] --;
	display();
}

void down( int i, int j)
{

	list[i + 1][j] ++;
	list[i][j] --;
	list[i - 1][j] --;
	display();
}

void left(int i, int j)
{

	list[i][j - 1] ++;
	list[i][j] --;
	list[i][j + 1] --;
	display();
}

void right(int i, int j)
{

	list[i][j + 1] ++;
	list[i][j] --;
	list[i][j - 1] --;
	display();
}


int main()
{	
	for (int i = 0; i < 7; i++)for (int j = 0; j < 7; j++)
	{
		list[i][j] = 0;
		if (i == 2 || i == 3 || i == 4 || j == 2 || j == 3 || j == 4)list[i][j] = 2;
	}
	list[3][3] = 1;	

	display();
	down(2, 3);
	right(2, 2);
	up(3, 2);
	right(4, 1);
	down(2, 2);
	down(3, 0);
	left(4, 2);
	right(4, 1);
	left(2, 3);
	down(1, 4);
	right(0, 3);
	up(2, 4);
	down(1, 4);
	left(2, 4);
	left(2, 2);
	down(3, 1);
	right(4, 2);
	up(5, 2);
	up(4, 4);
	left(4, 5);
	down(3, 6);
	right(4, 4);
	left(4, 5);
	down(4, 4);
	up(5, 4);
	right(3, 3);
	up(5, 3);
	down(4, 4);
	right(4, 3);
	up(4, 4);
	left(3, 4);
	system("pause>nul");
	return 0;
}

 运行结果:

(由于排版的问题,我将输出的结果变成了图片

2,解法2

智力游戏 243中国跳棋(1)

 步骤分解:

四,最少步数

独立钻石棋真正难的是,如何在最少的步数达到要求。

并不是所有的方案都需要31步,独立钻石棋和跳棋一样,有连跳的现象存在。

实际上,最少的步数是18步。

五,独立钻石棋变种——八边形

任务是让最后只有1个子,并不限制最后这个子的位置。

5 5 5 7 3 6 5 6 5 7 5 5 3 7 5 7 5 4 5 6 5 7 5 5 7 4 5 4 5 4 5 6 5 2 5 4 7 5 5 5 6 6 4 6 7 3 5 3 5 4 5 2 5 1 5 3 3 4 5 4 5 4 5 2 3 3 5 3 3 1 3 3 4 1 4 3 6 2 4 2 1 4 3 4 3 4 3 2 3 2 5 2 5 2 5 4 5 4 5 6 5 6 3 6 3 6 3 4 1 3 3 3 1 5 3 5 4 3 2 3 4 5 2 5 2 6 2 4 3 4 1 4 2 2 2 4 1 4 3 4

六,独立钻石棋变种——9行9列

任务是让最后只有1个子,并不限制最后这个子的位置。

只找到最后剩2个子的解法,没找到只剩1个子的解法。

参见 本科毕业设计

七,独立钻石棋变种——Hopping dots

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

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

1,Hopping dots

 APK下载链接(Hopping dots 1.1):资源分享汇总_nameofcsdn的博客-CSDN博客

游戏界面(下图为第一关)

棋盘:由13个点组成

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

2,规则

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

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

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

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

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

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

3,问题的简化

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

这样的版本就更接近独立钻石棋了,对于玩家来说,原问题和问题的简化版差异并不是很大,玩起来难度差不多。而对于想做理论分析的笔者来说,原问题分析起来很繁琐,掩盖了很多规律。

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

(3.1)局面的数量

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

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

(3.2)编号

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

不难数出,一共有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 };

(3.4)深度优先搜索的可能性分析

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

(3.4.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;
}

(3.4.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亿次计算,所以这个时间是可以接受的。

4,原问题的求解

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

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

代码

#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;
}

示例:

八,独立钻石棋变种——六边形坐标系

Taptap游戏《一个挑战》

任选一个格子去掉棋子,变成空格,然后按照独立钻石棋的规则消除棋子,最后只剩一颗棋子。

先选中第四行第一个格子消掉,然后再把上面的消掉:

 

 

 

 

原文地址:https://www.jb51.cc/wenti/3280397.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐