Example #1
0
        /// <summary>Runs the MoveFinder using the specified options.</summary>
        /// <param name="level">The Sokoban level under consideration.</param>
        /// <param name="stopIfFourSides">If not null, runs a MoveFinder only so far as
        /// necessary to ascertain that all four cells adjacent to the specified cell
        /// are reachable.</param>
        /// <param name="ignorePieces">If true, only walls are considered obstacles.</param>
        private void run(SokobanLevel level, Point?stopIfFourSides, bool ignorePieces)
        {
            // Contains the set of cells yet to be considered
            Queue <Point> queue = new Queue <Point>();

            _level       = level;
            _pathLength  = new int[_level.Width * _level.Height];
            _predecessor = new Point[_level.Width * _level.Height];
            queue.Enqueue(_level.SokobanPos);
            _pathLength[_level.SokobanPos.Y * _level.Width + _level.SokobanPos.X] = 1;

            // Breadth-first search: extract an item from the queue, process it, and add
            // the newly-discovered items to the queue (Examine() does that).
            while (queue.Count > 0)
            {
                Point pivot = queue.Dequeue();
                examine(new Point(pivot.X, pivot.Y - 1), pivot, ignorePieces, queue);
                examine(new Point(pivot.X - 1, pivot.Y), pivot, ignorePieces, queue);
                examine(new Point(pivot.X + 1, pivot.Y), pivot, ignorePieces, queue);
                examine(new Point(pivot.X, pivot.Y + 1), pivot, ignorePieces, queue);

                if (stopIfFourSides != null &&
                    _pathLength[stopIfFourSides.Value.Y * _level.Width + stopIfFourSides.Value.X - _level.Width] > 0 &&
                    _pathLength[stopIfFourSides.Value.Y * _level.Width + stopIfFourSides.Value.X + _level.Width] > 0 &&
                    _pathLength[stopIfFourSides.Value.Y * _level.Width + stopIfFourSides.Value.X - 1] > 0 &&
                    _pathLength[stopIfFourSides.Value.Y * _level.Width + stopIfFourSides.Value.X + 1] > 0)
                {
                    queue.Clear();
                }
            }
        }
Example #2
0
        /// <summary>
        /// Initialisation. Called by each of the overloaded constructors to perform the
        /// common initialisation work.
        /// </summary>
        /// <param name="level">The SokobanLevel that is to be rendered.</param>
        /// <param name="clientWidth">The width (in pixels) of the client area into which the level is to be rendered.</param>
        /// <param name="clientHeight">The height (in pixels) of the client area into which the level is to be rendered.</param>
        private void initialize(SokobanLevel level, int clientWidth, int clientHeight)
        {
            _clientWidth  = clientWidth;
            _clientHeight = clientHeight;
            _level        = level;
            int idealWidth = _clientHeight * level.Width / level.Height;

            _cellWidth = _cellHeight =
                _clientWidth > idealWidth ? _clientHeight / level.Height : _clientWidth / level.Width;

            _originX = (int)(_clientWidth / 2f - _cellWidth * level.Width / 2f);
            _originY = (int)(_clientHeight / 2f - _cellHeight * level.Height / 2f);
        }
Example #3
0
        /// <summary>
        /// Sets the form's contents in such a way that the specified highscores are displayed.
        /// </summary>
        /// <param name="highscores">A dictionary mapping from player name to highscores.</param>
        /// <param name="level">The level whose highscores are being displayed.</param>
        public void SetContents(Dictionary <string, Highscore> highscores, SokobanLevel level)
        {
            pnlHighscores.ColumnCount = 3;

            ctLevelPicture.ClientSize = new Size(ctLevelPicture.ClientSize.Width,
                                                 (int)(ctLevelPicture.ClientSize.Width * level.Height / level.Width));
            _level = level.Clone();
            _level.EnsureSpace(1);

            List <string> playerNames = new List <string>();

            foreach (string s in highscores.Keys)
            {
                Highscore h = highscores[s];
                int       i = 0;
                while ((i < playerNames.Count) && (h.CompareTo(highscores[playerNames[i]]) < 0))
                {
                    i++;
                }
                playerNames.Insert(i, s);
            }
            for (int i = 0; i < playerNames.Count; i++)
            {
                Label lblPlayerName = new Label();
                lblPlayerName.Font     = new Font(Font, FontStyle.Bold);
                lblPlayerName.Text     = playerNames[i];
                lblPlayerName.AutoSize = true;
                lblPlayerName.Margin   = new Padding(5);
                pnlHighscores.Controls.Add(lblPlayerName, 1, i);

                Highscore h        = highscores[playerNames[i]];
                Label     lblScore = new Label();
                lblScore.Text     = Program.Tr.Highscores.Highscores.Fmt(Program.Tr.Language.GetNumberSystem(), h.BestPushScore.Moves, h.BestPushScore.Pushes);
                lblScore.AutoSize = true;
                lblScore.Margin   = new Padding(5);
                pnlHighscores.Controls.Add(lblScore, 2, i);
            }
            pnlHighscores.Controls.Add(btnOK, 2, playerNames.Count);

            pnlHighscores.Controls.Add(ctLevelPicture, 0, 0);
            pnlHighscores.SetRowSpan(ctLevelPicture, playerNames.Count + 1);

            while (pnlHighscores.ColumnStyles.Count < 3)
            {
                pnlHighscores.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
            }
            while (pnlHighscores.RowStyles.Count < playerNames.Count + 1)
            {
                pnlHighscores.RowStyles.Add(new RowStyle(SizeType.AutoSize));
            }
        }
Example #4
0
        private void saveLevel(object sender, EventArgs e)
        {
            SokobanLevel level = ctMainArea.Level.Clone();

            level.EnsureSpace(0);

            // LevelList.EditAccept() will trigger the LevelActivating event, which in
            // turn will ask the user if they want to discard their changes to the level.
            // Since we don't want this, we have to set the Modified flag for the MainArea
            // to false before calling LevelList.EditAccept().
            ctMainArea.Modified = false;

            lstLevels.EditAccept(level);
        }
Example #5
0
        /// <summary>
        /// Renders the specified level and returns the finished image. Renderings are
        /// cached for efficiency.
        /// </summary>
        /// <param name="level">The SokobanLevel to be rendered.</param>
        /// <param name="width">Width of the area to render into.</param>
        /// <param name="height">Height of the area to render into.</param>
        private Image getRendering(SokobanLevel level, int width, int height)
        {
            if (_cachedRenderings.ContainsKey(level))
            {
                return((Image)_cachedRenderings[level]);
            }

            Image rendering = new Bitmap(width, height);

            using (var graphics = Graphics.FromImage(rendering))
                new Renderer(level, width, height, new SolidBrush(Color.Transparent)).Render(graphics);
            _cachedRenderings[level] = rendering;
            return(rendering);
        }
Example #6
0
 /// <summary>
 /// Accept the specified level as the new version of the level being edited.
 /// Turns off the Edit mode and starts playing the new level (if it is valid).
 /// </summary>
 public void EditAccept(SokobanLevel level)
 {
     // Update the level
     Items[_activeLevelIndex.Value] = level;
     _modified = true;
     // Play this level
     if (level.Validity == SokobanLevelStatus.Valid)
     {
         playingIndex  = _activeLevelIndex;
         SelectedIndex = _activeLevelIndex.Value; // select this level in the box - probably desirable?
     }
     else
     {
         playingIndex = null;
     }
 }
Example #7
0
        /// <summary>
        /// Occurs when the list box needs to determine the size (height) of an item.
        /// Levels are measured in such a way that the height of the item is to its
        /// width as the height of the level is to its width. The "currently playing"
        /// or "currently editing" and the "solved" messages are taken into account.
        /// </summary>
        private void measureItem(object sender, MeasureItemEventArgs e)
        {
            if (e.Index < 0 || e.Index >= Items.Count || DesignMode)
            {
                return;
            }

            if (Items[e.Index] is SokobanLevel)
            {
                SokobanLevel level = (SokobanLevel)Items[e.Index];
                e.ItemHeight = (ClientSize.Width - 10) * level.Height / level.Width + 10;

                if (e.Index == playingIndex)    // also covers "Just Solved"
                {
                    e.ItemHeight += (int)e.Graphics.MeasureString(Program.Tr.LevelList_CurrentlyPlaying, Font).Height + 5;
                }
                else if (e.Index == editingIndex)
                {
                    e.ItemHeight += (int)e.Graphics.MeasureString(Program.Tr.LevelList_CurrentlyEditing, Font).Height + 5;
                }

                if (Program.Settings.IsSolved(Items[e.Index].ToString()))
                {
                    e.ItemHeight += (int)e.Graphics.MeasureString(Program.Tr.LevelList_LevelSolved, Font).Height + 5;
                }
            }
            else if (Items[e.Index] is string && (Items[e.Index] as string).Length == 0)
            {
                e.ItemHeight = (int)e.Graphics.MeasureString("Mg", Font).Height + 10;
            }
            else if (Items[e.Index] is string)
            {
                e.ItemHeight = (int)e.Graphics.MeasureString((string)Items[e.Index], Font).Height + 10;
            }
            else
            {
                e.ItemHeight = (int)e.Graphics.MeasureString(Items[e.Index].ToString(), Font).Height + 10;
            }

            if (e.ItemHeight > 255)
            {
                e.ItemHeight = 255;
            }
        }
Example #8
0
 /// <summary>
 /// Constructs a Renderer object.
 /// </summary>
 /// <param name="level">The SokobanLevel that is to be rendered.</param>
 /// <param name="clientWidth">The width (in pixels) of the client area into which the level is to be rendered.</param>
 /// <param name="clientHeight">The height (in pixels) of the client area into which the level is to be rendered.</param>
 /// <param name="backgroundBrush">The Brush used to fill the background of the level.</param>
 public Renderer(SokobanLevel level, int clientWidth, int clientHeight, Brush backgroundBrush)
 {
     _backgroundBrush = backgroundBrush;
     initialize(level, clientWidth, clientHeight);
 }
Example #9
0
 /// <summary>Runs a MoveFinder using the specified mode of operation.</summary>
 /// <param name="level">The Sokoban level under consideration.</param>
 /// <param name="specialOption">A set of <see cref="MoveFinderOption"/> flags
 /// specifying the behaviour of the MoveFinder.</param>
 public MoveFinder(SokobanLevel level, MoveFinderOption specialOption)
 {
     run(level, null, specialOption == MoveFinderOption.IgnorePieces);
 }
Example #10
0
 /// <summary>
 /// Runs a MoveFinder only so far as necessary to ascertain that all four cells
 /// adjacent to the specified cell are reachable.
 /// </summary>
 /// <param name="level">The Sokoban level under consideration.</param>
 /// <param name="stopIfFourSides">The cell whose four adjacent cells are under
 /// consideration.</param>
 public MoveFinder(SokobanLevel level, Point stopIfFourSides)
 {
     run(level, stopIfFourSides, false);
 }
Example #11
0
 /// <summary>Runs a normal MoveFinder.</summary>
 /// <param name="level">The Sokoban level under consideration.</param>
 public MoveFinder(SokobanLevel level)
 {
     run(level, null, false);
 }
Example #12
0
 /// <summary>
 /// Main constructor.
 /// </summary>
 public MoveFinderOutline(SokobanLevel level)
     : base(level, MoveFinderOption.IgnorePieces)
 {
     // This space intentionally left blank
     // (the base constructor calls run())
 }
Example #13
0
        /// <summary>
        /// Loads a level file from the level list.
        /// </summary>
        public void LoadLevelPack(string path)
        {
            // Will contain the list of levels and comments that we have loaded.
            // (We only want to add them to the listbox at the end in case we
            // throw an exception, because then we want to keep the old file.)
            List <object> loadedItems = new List <object>();

            // Class to read from the text file
            StreamReader sr = new StreamReader(path, Encoding.UTF8);
            // State we're in (Empty, Comment or Level)
            levelReaderState state = levelReaderState.Empty;
            // Line last read
            String line;
            // Current comment (gets appended to until we reach the end of the comment)
            String comment = "";
            // Current level (gets appended to until we reach the end of the level)
            String levelEncoded = "";

            do
            {
                line = sr.ReadLine();

                // If this line begins with a semicolon (';'), it's a comment.
                // Otherwise, it is considered part of the level and must
                // contain only the characters #@$.*+ or space.
                if (line != null && line.Length > 0 && line[0] != ';')
                {
                    // check for invalid characters
                    for (int i = 0; i < line.Length; i++)
                    {
                        if (line[i] != ' ' && line[i] != '#' && line[i] != '@' &&
                            line[i] != '$' && line[i] != '.' && line[i] != '*' &&
                            line[i] != '+')
                        {
                            throw new InvalidLevelException();
                        }
                    }
                }

                // Decide whether this line belongs to a level or comment
                levelReaderState newState =
                    (line == null || line.Length == 0) ? levelReaderState.Empty :
                    line[0] == ';' ? levelReaderState.Comment :
                    levelReaderState.Level;

                // If we are switching from level to comment or vice versa,
                // or reaching the end of the file, add the level or comment
                // to the level list and empty the relevant variable

                if (newState != state && state == levelReaderState.Comment)
                {
                    loadedItems.Add(comment);
                    comment = "";
                }
                else if (newState != state && state == levelReaderState.Level)
                {
                    SokobanLevel newLevel = new SokobanLevel(levelEncoded);
                    newLevel.EnsureSpace(0);
                    loadedItems.Add(newLevel);
                    levelEncoded = "";
                }

                // Append the line we just read to the relevant variable
                if (newState == levelReaderState.Comment)
                {
                    comment += line.Substring(1) + "\n";
                }
                else if (newState == levelReaderState.Level)
                {
                    levelEncoded += line + "\n";
                }

                // Update the state
                state = newState;
            } while (line != null);

            _activeLevelIndex = null;
            BeginUpdate();
            Items.Clear();
            foreach (object item in loadedItems)
            {
                Items.Add(item);
            }
            EndUpdate();
            sr.Close();

            _modified = false;

            Program.Settings.LevelFilename = path;
        }
Example #14
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);
            }
        }
Example #15
0
 /// <summary>
 /// Constructs a Renderer object.
 /// </summary>
 /// <param name="level">The SokobanLevel that is to be rendered.</param>
 /// <param name="clientSize">The size (in pixels) of the client area into which the level is to be rendered.</param>
 /// <param name="backgroundBrush">The Brush used to fill the background of the level.</param>
 public Renderer(SokobanLevel level, Size clientSize, Brush backgroundBrush)
 {
     _backgroundBrush = backgroundBrush;
     initialize(level, clientSize.Width, clientSize.Height);
 }
Example #16
0
 /// <summary>
 /// Constructs a Renderer object.
 /// </summary>
 /// <param name="level">The SokobanLevel that is to be rendered.</param>
 /// <param name="clientWidth">The width (in pixels) of the client area into which the level is to be rendered.</param>
 /// <param name="clientHeight">The height (in pixels) of the client area into which the level is to be rendered.</param>
 public Renderer(SokobanLevel level, int clientWidth, int clientHeight)
 {
     initialize(level, clientWidth, clientHeight);
 }
Example #17
0
 /// <summary>
 /// Constructs a Renderer object.
 /// </summary>
 /// <param name="level">The SokobanLevel that is to be rendered.</param>
 /// <param name="clientSize">The size (in pixels) of the client area into which the level is to be rendered.</param>
 public Renderer(SokobanLevel level, Size clientSize)
 {
     initialize(level, clientSize.Width, clientSize.Height);
 }