墨盘游戏 算法求解

游戏定义

整个六边形用二维数组保存(逻辑上),为了方便计算

游戏状态

使用数组保存游戏状态会占用大量的内存空间,运算速度也慢,所以使用位压缩的方式保存游戏的状态,这可以节省内存空间以及提高运算速度

游戏操作

游戏只有顺时针、原点对称、逆时针三种方式交换黑点,定义为 L、C、R。
大概整个编码中最难的部分就是这里。

分析

先来张图
在这里插入图片描述
让游戏只用代码运行,就要摒弃一切无关信息,保留我们所需的关键信息。
所以只保留游戏开始、结束的两个状态,三个简单的操作。
游戏的状态与黑点位置有关,三个操作中受影响的也是黑点,黑点的变化是游戏的关键。

问题一

别嫌弃图丑
在这里插入图片描述
假设现在用L的操作方式点击图中的红点(2行 3列)的位置,通过计算可以确定红点周围一圈的点,从0的位置开始判断,大致可以写出以下方式

long state = 1149501712;
for (int i = 0; i < 6; i++) {
	int index = getIndex(i); // 对应一维数组的索引
	if (isExist(state, index)) { // 该点是否存在黑点
		state -= getValue(i);
		state += getValue((i + 1) % 6);
	}
}

以上图的状态执行L操作,可以得到想要的结果(state = 1149501697),现在以下图的状态再执行一次相同的操作
在这里插入图片描述
结果state = 1149504883,对应的游戏状态是这样的
在这里插入图片描述
为什么会这样?多了5个点?哪来的?
逐步分析下:
i = 0,1 的位置会有黑点,这没错,接下来
i = 1,因为i = 0的操作 1 的位置有了黑点,而在执行上个步骤前 1 的位置没有黑点,所以 2 不该有黑点,但是i = 0的步骤影响了接下来的所有步骤,其余两个也有这样的问题(点之间的关联性)

问题二

由于这不是规则的矩形,计算黑点的一维数组索引就必需知道前面的点的个数和,如果每走一步都计算一次则很浪费时间

解决问题思路

对于问题二的解决很简单,先计算一遍每行前面的点的数量和存放在一维数组里,用对应的行数去取就行了
重点是问题二
因为点之间的关联性,前面的方式在计算下一个节点的状态时,会影响其它的点,原因是在同一个状态下操作,所以要分离下一个节点的状态计算。计算下一个状态时,只关心被点击的点的周围一圈,与其它黑点无关,代码如下

private long originSymmetric(long parentState, int row, int col) {
		long childState = 0;
		int choiceCol = getColIndex(row);
		for (int i = 0; i < 6; i++) {
			int index = previousLength[row + offsetRow[i]] + col + offsetCol[choiceCol][i];
			if (isExist(parentState, index)) {
				childState |= getValue(previousLength[row + offsetRow[(i + 3) % 6]] + col + offsetCol[choiceCol][(i + 3) % 6]);
				parentState ^= getValue(index);
			}
		}
		return childState | parentState;
	}

父节点移除被移动的黑点

parentState ^= getValue(index); 

子节点增加移动后的黑点

childState |= getValue(previousLength[row + offsetRow[(i + 3) % 6]] + col + offsetCol[choiceCol][(i + 3) % 6]);

以上运算后父节点被点击的点的周围一圈黑点全部存到子节点上,该点周围信息丢失,子节点的情况则相反,最后合并两个节点的信息,得到新的节点,即真正所需的子节点

基本问题都解决了,接下来的工作就只是挑选可能最快达到目标的节点,在可点击的范围内重复L、C、R操作了

代码

Node.java

public class Node {

	private Node parent;
	private long state, weight;
	private int row, col, step;
	private char direction;
	public Node getParent() {
		return parent;
	}
	public void setParent(Node parent) {
		this.parent = parent;
	}
	public long getState() {
		return state;
	}
	public void setState(long state) {
		this.state = state;
	}
	public long getWeight() {
		return weight;
	}
	public void setWeight(long weight) {
		this.weight = weight;
	}
	public int getRow() {
		return row;
	}
	public void setRow(int row) {
		this.row = row;
	}
	public int getCol() {
		return col;
	}
	public void setCol(int col) {
		this.col = col;
	}
	public int getStep() {
		return step;
	}
	public void setStep(int step) {
		this.step = step;
	}
	public char getDirection() {
		return direction;
	}
	public void setDirection(char direction) {
		this.direction = direction;
	}
}

NodeTree.java

public class NodeTree {
	
	private long targetState, sum;
	private int[] range, previousLength; // 六边形次外圈的范围、六边形对应的行前面的点数量之和
	private Node[] nodes;
	private int nodesIndex, depth;
	
	public NodeTree(int[][] startState, int[][] endState, int depth) {
		this.targetState = NodeUtil.getStateValue(endState);
		this.range = NodeUtil.getRange(endState);
		this.previousLength = NodeUtil.getPreviousLength(endState);
		this.depth = depth;
		this.nodes = new Node[40];
		Node root = new Node();
		root.setState(NodeUtil.getStateValue(startState));
		nodes[nodesIndex++] = root;
		sum++;
	}

	private char[] direction = {'L', 'C', 'R'}; // 对应顺时针、原点交换、逆时针
	public void search() {
		long startTime = System.currentTimeMillis();
		while (nodesIndex > 0) {
			Node parent = nodes[0];
			// 找到目标,停止搜索
			if (parent.getState() == targetState) {
				printSearchInfo(startTime, "success");
				printPath(parent);
				return;
			}
			shiftDown(--nodesIndex);
			// 节点达到最大深度,不再搜索
			if (parent.getStep() < depth) {
				for (int i = 0; i < range.length; i++) {
					for (int j = 1; j <= range[i]; j++) {
						for (int d = 0; d < direction.length; d++) {
							if (nodesIndex == nodes.length) {
								Node[] newNodes = new Node[nodesIndex * 2];
								System.arraycopy(nodes, 0, newNodes, 0, nodesIndex);
								nodes = newNodes;
							}
							Node child = nextStep(parent, direction[d], i + 1, j);
							if (child != null) {
								nodes[nodesIndex++] = child;
								shiftUp(nodesIndex - 1);
								sum++;
							}
						}
					}
				}
			}
		}
		printSearchInfo(startTime, "fail");
	}
	
	// 走一步
	private Node nextStep(Node parent, char direction, int row, int col) {
		long childState = 0;
		switch (direction) {
			case 'L':
				childState = clockwiseRotate(parent.getState(), row, col);
				break;
			case 'C':
				childState = originSymmetric(parent.getState(), row, col);
				break;
			case 'R':
				childState = anticlockwiseRotate(parent.getState(), row, col);
				break;
		}
		if (childState != parent.getState()) {
			Node child = new Node();
			child.setParent(parent);
			child.setRow(row);
			child.setCol(col);
			child.setDirection(direction);
			child.setState(childState);
			child.setStep(parent.getStep() + 1);
			child.setWeight(getEstimatedValue(child));
			return child;
		}
		return null;
	}

	private int[] offsetRow = {-1, -1, 0, 1, 1, 0};
	private int[][] offsetCol = { // 行数对应的列变化规则
			{-1, 0, 1, 1, 0, -1}, // 上
			{-1, 0, 1, 0, -1, -1}, // 中
			{0, 1, 1, 0, -1, -1} // 下
	};
	
	// 左边,顺时针旋转
	private long clockwiseRotate(long parentState, int row, int col) {
		long childState = 0;
		int choiceCol = getColIndex(row);
		for (int i = 0; i < 6; i++) {
			int index = previousLength[row + offsetRow[i]] + col + offsetCol[choiceCol][i];
			if (isExist(parentState, index)) {
				childState |= getValue(previousLength[row + offsetRow[(i + 1) % 6]] + col + offsetCol[choiceCol][(i + 1) % 6]);
				parentState ^= getValue(index);
			}
		}
		return childState | parentState;
	}

	// 原点对称交换
	private long originSymmetric(long parentState, int row, int col) {
		long childState = 0;
		int choiceCol = getColIndex(row);
		for (int i = 0; i < 6; i++) {
			int index = previousLength[row + offsetRow[i]] + col + offsetCol[choiceCol][i];
			if (isExist(parentState, index)) {
				childState |= getValue(previousLength[row + offsetRow[(i + 3) % 6]] + col + offsetCol[choiceCol][(i + 3) % 6]);
				parentState ^= getValue(index);
			}
		}
		return childState | parentState;
	}
	
	// 右边,逆时针旋转
	private long anticlockwiseRotate(long parentState, int row, int col) {
		long childState = 0;
		int choiceCol = getColIndex(row);
		for (int i = 5; i >= 0; i--) {
			int index = previousLength[row + offsetRow[i]] + col + offsetCol[choiceCol][i];
			if (isExist(parentState, index)) {
				if (i > 0)
					childState |= getValue(previousLength[row + offsetRow[i - 1]] + col + offsetCol[choiceCol][i - 1]);
				else
					childState |= getValue(previousLength[row + offsetRow[5]] + col + offsetCol[choiceCol][5]);
				parentState ^= getValue(index);
			}
		}
		return childState | parentState;
	}
	
	// 选择行数对应的列变化规则
	private int getColIndex(int row) {
		if (row < previousLength.length / 2) {
			return 0;
		} else if (row == previousLength.length / 2) {
			return 1;
		} else {
			return 2;
		}
	}
	
	// 点是否存在
	private boolean isExist(long state, int index) {
		return (state >> index & 1) == 1;
	}

	// 2的index次方
	private long getValue(int index) {
		return (long) Math.pow(2, index);
	}
	
	// 估值函数
	private long getEstimatedValue(Node node) {
		return node.getState() + Math.abs(node.getState() - targetState);
	}
	
	// 节点下沉
	private void shiftDown(int index) {
		nodes[0] = nodes[index];
		for (int parentIndex = 0, childIndex = (parentIndex << 1) + 1;
				childIndex < index;
				parentIndex = childIndex, childIndex = (parentIndex << 1) + 1) {
			if (childIndex + 1 < index &&
					nodes[childIndex + 1].getWeight() <= nodes[childIndex].getWeight()) {
				++childIndex;
			}
			if (nodes[childIndex].getWeight() > nodes[parentIndex].getWeight()) {
				break;
			}
			Node node = nodes[parentIndex];
			nodes[parentIndex] = nodes[childIndex];
			nodes[childIndex] = node;
		}
	}
	
	// 节点上浮
	private void shiftUp(int index) {
		for (int parentIndex = index - 1 >> 1;
				parentIndex >= 0 && nodes[parentIndex].getWeight() > nodes[index].getWeight();
				index = parentIndex, parentIndex = index - 1 >> 1) {
			Node node = nodes[parentIndex];
			nodes[parentIndex] = nodes[index];
			nodes[index] = node;
		}
	}

	// 打印搜索信息
	private void printSearchInfo(long startTime, String result) {
        System.out.println("搜索时间:" + (System.currentTimeMillis() - startTime) + "毫秒");
        System.out.println(result);
        System.out.println("搜索节点数量:" + sum);
        System.out.format("节点数组长度:%d 有效长度:%d 存储空间利用率:%f%%%n", nodes.length, nodesIndex, (double) nodesIndex * 100 / nodes.length);
	}

	// 打印路径
	private void printPath(Node node) {
		while (node.getParent() != null) {
			System.out.format("step:%d direction:%c row:%d col:%d%n", node.getStep(), node.getDirection(), node.getRow(), node.getCol());
			node = node.getParent();
		}
	}
}

NodeUtil.java

public class NodeUtil {

	// 点击的边界,六边形的次外圈大小
	public static int[] getRange(int[][] state) {
		int[] range = new int[state.length - 2];
		for (int i = 1; i < state.length - 1; i++) {
			range[i - 1] = state[i].length - 2;
		}
		return range;
	}
	
	// 状态值
	public static long getStateValue(int[][] state) {
		int sum = 0;
		long value = 0;
		for (int row = 0; row < state.length; row++) {
			for (int col = 0; col < state[row].length; col++) {
				if (state[row][col] == 1) {
					value |= (long) Math.pow(2, sum + col);
				}
			}
			sum += state[row].length;
		}
		return value;
	}

	// 六边形对应的行前面的点数量之和
	public static int[] getPreviousLength(int[][] state) {
		int[] previousLength = new int[state.length];
		int sum = 0;
		for (int row = 0; row < previousLength.length; row++) {
			previousLength[row] = sum;
			sum += state[row].length;
		}
		return previousLength;
	}
}

NodeTreeTest.java

public class NodeTreeTest {
	public static void main(String[] args) {
		int[][] startState = {
				{0, 0, 0, 0},
				{1, 0, 0, 0, 1},
				{0, 0, 0, 0, 0, 0},
				{0, 0, 0, 1, 0, 0, 0},
				{0, 1, 0, 0, 1, 0},
				{0, 0, 1, 0, 0},
				{0, 0, 0, 0}
		};
		int[][] endState = {
				{0, 0, 0, 0},
				{0, 0, 0, 0, 0},
				{0, 0, 1, 1, 0, 0},
				{0, 0, 0, 1, 0, 0, 0},
				{0, 0, 1, 0, 0, 0},
				{0, 1, 1, 0, 0},
				{0, 0, 0, 0}
		};
		int depth = 5;
		System.out.println("startState:" + NodeUtil.getStateValue(startState));
		System.out.println("endState:" + NodeUtil.getStateValue(endState));
		System.out.println("depth:" + depth);
		NodeTree nodeTree = new NodeTree(startState, endState, depth);
		nodeTree.search();
	}
}

测试结果

在这里插入图片描述

总结

不要以为这个结果的时间很慢或者很快,有些情况内存溢出也没算出来(起始状态和目标状态差值的绝对值大),有些情况则在毫秒级别之内找到答案(起始状态和目标状态差值的绝对值小),我想这和估值函数有关,如果估值函数写的更好,这些情况发生可能性就会很小。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值