独立钻石棋

目录

一,独立钻石棋

1,独立钻石棋

2,计算机求解V1

3,手动求解

(1)解法1

(2)解法2

4,计算机求解V2(状态压缩DP)

5,最少步数

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

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

一个挑战

四, 欢乐跳跳棋


一,独立钻石棋

 APK下载链接

1,独立钻石棋

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

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

2,计算机求解V1

深度优先搜索求解的代码:

#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分钟还没有答案输出。

3,手动求解

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

(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

4,计算机求解V2(状态压缩DP)

用状态压缩的方式表达局面,就可以用备忘录避免重复计算。


#include <iostream>
#include <vector>
#include <map>
#include <string>
using namespace std;

int r, c, num;
vector<vector<int>>inboard;//所有合法格子坐标
map<int, int>m,m2;//所有合法格子编号
map<int,vector<pair<int, int>>>canMove;//所有可以移动的方案
long long s;
long long one = 1;
#define Mi(x) (one<<(x))
#define SAt(x) (s&Mi(x))

void SetInboard()//自定义
{
	inboard = {
		{0,0,1,1,1,0,0},
		{0,0,1,1,1,0,0},
		{1,1,1,1,1,1,1},
		{1,1,1,1,1,1,1},
		{1,1,1,1,1,1,1},
		{0,0,1,1,1,0,0},
		{0,0,1,1,1,0,0}
	};
	num = 32;
	s = (one << num+1) - 1 - (1 << num/2);
	r = inboard.size();
	c = inboard[0].size();
}
void GetMap()
{
	int k = 0;
	for (int i = 0; i < r; i++)for (int j = 0; j < c; j++) {
		if (inboard[i][j])m[i * c + j] = k, m2[k] = i * c + j, k = k + 1;
	}
}
int id(int i, int j)
{
	return m[i * c + j];
}
void GetCanMove()
{
	for (int i = 0; i < r; i++)for (int j = 0; j < c; j++) {
		if (!inboard[i][j])continue;
		if (j > 0 && j < c - 1 && inboard[i][j - 1] && inboard[i][j + 1])
			canMove[id(i, j)].push_back(make_pair(id(i, j - 1), id(i, j + 1))),
			canMove[id(i, j)].push_back(make_pair(id(i, j + 1), id(i, j - 1)));
		if (i > 0 && i < r - 1 && inboard[i - 1][j] && inboard[i + 1][j])
			canMove[id(i, j)].push_back(make_pair(id(i - 1, j), id(i + 1, j))),
			canMove[id(i, j)].push_back(make_pair(id(i + 1, j), id(i - 1, j)));
	}
}
void show(long long s)
{
	static long long old = 0;
	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			if (inboard[i][j] && SAt(id(i, j))) {
				if (old & (Mi(id(i, j))))cout << "○";
				else cout << "●";
			}
			else cout << "  ";
		}
		cout << endl;
	}
	cout << endl;
	cout << endl;
	old = s;
}

bool dp(long long s, int deep)
{
	if (deep >= num-1) {
		return true;
	}
	static map<long long, int>m;
	for (auto& p : canMove) {
		if (!SAt(p.first))continue;
		s ^= Mi(p.first);
		for (auto pa : p.second) {
			if (SAt(pa.first) && !SAt(pa.second)) {
				s ^= (Mi(pa.first)^ Mi(pa.second));
				if (m[s] == 0) {
					m[s] = 1;
					if (dp(s, deep + 1)) {
						show(s);
						return true;
					}
				}
				s ^= (Mi(pa.first) ^ Mi(pa.second));
			}
		}
		s ^= Mi(p.first);
	}
	return false;
}
int main()
{
	SetInboard();
	GetMap();
	GetCanMove();
	dp(s, 0);
	return 0;
}

输出:


      ●

      ●
      ●

  ●●
      ○

  ○
    ●○
    ●

......中间省略


    ○○○
    ○  ○
○●●  ○○○
○○○○○○○
○○○○○○○
    ○○○
    ○○○
 

5,最少步数

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

并不是所有的方案都需要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

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

一个挑战

Taptap游戏,只有一关。

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

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

 

 

四, 欢乐跳跳棋

这里面既有直角坐标系的,也有六边形坐标系的。

总体来说六边形坐标系的简单一些。

无论是直角坐标系的还是六边形坐标系的,都有一个策略:先处理边角位置,把棋子往中间汇聚

在线play

(1)

 

 (2)

 

上面的《一个挑战》其实就可以选择去掉最上面的棋子变成本关卡,不过因为那个是我先玩的,我想到的是去掉第四行第一列的另一种解法。

本关卡首先可以变成:

这后面就和《一个挑战》一样了。

(3)

 

(4)

这就是独立钻石棋,随便搞一下就行了:

(5)

 

 

 

(6)

这就和独立钻石棋变种——八边形操作了第一步之后是一样的。

 

(7)

(8)

 

 

(9)

用计算机求解代码V2求解,只需要改2个变量即可:

	inboard = {
		{0,0,0,1,1,1,0,0,0},
		{0,0,0,1,1,1,0,0,0},
		{0,0,0,1,1,1,0,0,0},
		{1,1,1,1,1,1,1,1,1},
		{1,1,1,1,1,1,1,1,1},
		{1,1,1,1,1,1,1,1,1},
		{0,0,0,1,1,1,0,0,0},
		{0,0,0,1,1,1,0,0,0},
		{0,0,0,1,1,1,0,0,0}
	};
	num = 44;

然而问题规模实在太大了,10步操作之后的情况就有350万种,即使考虑对称性也有约50万种,而43次操作的所有中间状态,更是天文数字。

不过,自己凭感觉试一试,还是能找到答案:

(10)

 

(11)

 

(12)

 

(13)

 

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值