Exemplo n.º 1
0
 /// <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);
     }
 }
Exemplo n.º 2
0
 /// <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);
     }
 }
Exemplo n.º 3
0
        /// <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);
            }
        }