/// <summary> /// Examines a cell to see if the length of the path to it can be shortened (or /// whether the cell has even been discovered yet). /// </summary> /// <param name="pos">Cell under consideration.</param> /// <param name="pivot">Becomes the new predecessor if it makes the path length /// shorter.</param> /// <param name="ignorePieces">If true, only walls are considered obstacles.</param> /// <param name="queue">The queue to insert newly discovered cells into.</param> private void examine(Point pos, Point pivot, bool ignorePieces, Queue <Point> queue) { if (pos.X < 0 || pos.X >= _level.Width || pos.Y < 0 || pos.Y >= _level.Height) { return; } if ((_level.IsFree(pos) || (ignorePieces && _level.Cell(pos) != SokobanCell.Wall)) && _pathLength[pos.Y * _level.Width + pos.X] == 0) { if (pos.X > 0 && pos.X < _level.Width - 1 && pos.Y > 0 && pos.Y < _level.Height - 1) { queue.Enqueue(pos); } _pathLength[pos.Y * _level.Width + pos.X] = _pathLength[pivot.Y * _level.Width + pivot.X] + 1; _predecessor[pos.Y * _level.Width + pos.X] = pivot; } }
/// <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); } }