/// <summary> /// Determines whether the Sokoban can move from the source node to <paramref name="pos"/>, and if /// so, adds this as a newly-discovered node to the <paramref name="priorityQueue"/>. This is used /// only to add the first four items into the priority queue at the beginning of the algorithm. /// </summary> /// <param name="arrIndex">Index of the node the Sokoban is moving to.</param> /// <param name="pos">Co-ordinates of the cell the Sokoban is moving to.</param> /// <param name="priorityQueue">The priority queue.</param> private void addIfValid(int arrIndex, Point pos, specialHeap priorityQueue) { if (_moveFinder.Get(pos)) { _pushLength[arrIndex] = 1; _moveLength[arrIndex] = _moveFinder.PathLength(pos) + 1; priorityQueue.Add(arrIndex); } }
/// <summary> /// Relaxes an edge, which is Dijkstrian for: Given that going from <paramref name="fromNode"/> to <paramref name="toNode"/> /// requires <paramref name="pushLength"/> pushes and <paramref name="moveLength"/> moves, check if this gives rise to a /// shorter way to reach <paramref name="toNode"/> from the source node, and if so, update the node with the new path /// lengths and the new predecessor, and insert it into the priority queue. /// </summary> private void relaxEdge(int fromNode, int toNode, int pushLength, int moveLength, specialHeap priorityQueue) { if ( // Either we haven't discovered this node yet... _pushLength[toNode] == 0 || // ...or we can reduce its push length... _pushLength[toNode] > _pushLength[fromNode] + pushLength || // ...or its push length is the same, but we can reduce the move length (_pushLength[toNode] == _pushLength[fromNode] + pushLength && _moveLength[toNode] > _moveLength[fromNode] + moveLength) ) { _pushLength[toNode] = _pushLength[fromNode] + pushLength; _moveLength[toNode] = _moveLength[fromNode] + moveLength; _predecessor[toNode] = fromNode; priorityQueue.Add(toNode); } }
/// <summary>Main constructor. Runs the push finder.</summary> /// <param name="level">The Sokoban level under consideration.</param> /// <param name="selectedPiece">The co-ordinates of the currently selected piece /// (which is the source node for our algorithm).</param> /// <param name="moveFinder">The MoveFinder for the initial level situation.</param> public PushFinder(SokobanLevel level, Point selectedPiece, MoveFinder moveFinder) { _level = level; specialHeap priorityQueue = new specialHeap(this); Point origPiecePos = selectedPiece; Point origSokPos = _level.SokobanPos; int arraySize = 4 * level.Width * level.Height; _pushLength = new int[arraySize]; _moveLength = new int[arraySize]; _predecessor = new int[arraySize]; bool[] extracted = new bool[arraySize]; _path = new Point[arraySize][][]; _moveFinder = moveFinder; // In Dijkstra's algorithm, you usually start with a priority queue containing only // the source node. Then the first iteration extracts that source node from the // priority queue again, relaxes all its edges, and inserts the newly-discovered // nodes. We will run this "first iteration" manually because our source node is // special and not like all the others. We will end up inserting up to four nodes // into the priority queue, and then we will proceed with the iterative algorithm. addIfValid(nodeIndex(origPiecePos, 1), new Point(origPiecePos.X, origPiecePos.Y - 1), priorityQueue); addIfValid(nodeIndex(origPiecePos, 2), new Point(origPiecePos.X - 1, origPiecePos.Y), priorityQueue); addIfValid(nodeIndex(origPiecePos, 3), new Point(origPiecePos.X + 1, origPiecePos.Y), priorityQueue); addIfValid(nodeIndex(origPiecePos, 4), new Point(origPiecePos.X, origPiecePos.Y + 1), priorityQueue); // Dijkstra's algorithm: We extract a node from our priority queue and relax // all its outgoing edges. However, we might not yet know the length of those // edges; we might have to invoke MoveFinder to determine them. We also don't // have all nodes in the priority queue; we are adding them as we discover them. while (!priorityQueue.Empty) { // Extract the next item and filter out duplicates. // Since reheapification is only possible when adding or extracting items, // not when items change their value, we simply add nodes to the priority // queue every time a node changes its value. This means some nodes will // have several copies in the priority queue, and we are only interested // in each node the first time it is extracted. int node = priorityQueue.Extract(); if (extracted[node]) { continue; } extracted[node] = true; // The item we have extracted represents a node in our graph // that we want to run Dijkstra's algorithm on. That node, in // turn, represents a situation in which: // (1) the piece we are moving is at the position given by NodeToPos(node); Point position = nodeToPos(node); // (2) the Sokoban is located: // 1 = above the piece, 2 = left, 3 = right, 4 = below // as given by NodeToDir(node). int dirFrom = nodeToDir(node); // For the duration of this one iteration of the algorithm, we will // manipulate the level by placing the piece and the Sokoban in the // right place. We will undo this change at the end of the iteration. _level.MovePiece(origPiecePos, position); Point newSokPos = posDirToPos(position, dirFrom); _level.SetSokobanPos(newSokPos); // The node we're at has up to four possible outgoing edges. // These are: // (1) up to three of the following four: // (a) moving the Sokoban to above the piece // (b) moving the Sokoban to left of the piece // (c) moving the Sokoban to right of the piece // (d) moving the Sokoban to below the piece // (2) pushing the piece forward one cell. // We will first examine (1)(a-d). To do that, we need to find out // which of the other cells adjacent to the piece the Sokoban can move // to. We do this by running a MoveFinder. MoveFinder mf = new MoveFinder(_level, position); _path[node] = new Point[5][]; // For any of these cells, mf.Path() will return null if you can't walk to it if (dirFrom != 1) { _path[node][1] = mf.Path(new Point(position.X, position.Y - 1)); } if (dirFrom != 2) { _path[node][2] = mf.Path(new Point(position.X - 1, position.Y)); } if (dirFrom != 3) { _path[node][3] = mf.Path(new Point(position.X + 1, position.Y)); } if (dirFrom != 4) { _path[node][4] = mf.Path(new Point(position.X, position.Y + 1)); } if (_path[node][1] != null) { relaxEdge(node, nodeIndex(position, 1), 0, _path[node][1].Length, priorityQueue); } if (_path[node][2] != null) { relaxEdge(node, nodeIndex(position, 2), 0, _path[node][2].Length, priorityQueue); } if (_path[node][3] != null) { relaxEdge(node, nodeIndex(position, 3), 0, _path[node][3].Length, priorityQueue); } if (_path[node][4] != null) { relaxEdge(node, nodeIndex(position, 4), 0, _path[node][4].Length, priorityQueue); } // Finally, consider possibility (2): pushing the piece. Point pushTo = posDirToPos(position, oppositeDir(dirFrom)); if (_level.IsFree(pushTo)) { relaxEdge(node, nodeIndex(pushTo, dirFrom), 1, 1, priorityQueue); } // Before moving on to the next iteration, restore the level as it was. _level.MovePiece(position, origPiecePos); _level.SetSokobanPos(origSokPos); } }