Example #1
0
        /// <summary>
        /// Solves the Tower of Hanoi problem recursively.
        /// </summary>
        /// <param name="discNumber">
        /// The current disc number counted from 1. The number is also
        /// identical to the size of the (sub-)tower to be solved.
        /// </param>
        /// <param name="source">
        /// The source/initial tower in form of a <see cref="Stack{T}"/>.
        /// </param>
        /// <param name="temporary">
        /// The tower for temporary operations in form of a
        /// <see cref="Stack{T}"/>.
        /// </param>
        /// <param name="destination">
        /// The destination/goal tower in form of a <see cref="Stack{T}"/>.
        /// </param>
        /// <exception cref="ArgumentException">
        /// Thrown when <paramref name="discNumber"/> is smaller than 1 or
        /// any of the stack references (<paramref name="source"/>,
        /// <paramref name="temporary"/>, <paramref name="destination"/>)
        /// is null.
        /// </exception>
        /// <remarks>
        /// The essential part to understand the algorithm is to realize that
        /// it is the target/goal tower that varies according to the
        /// sub-problem.
        /// At least some previous knowledge of recursive methods is
        /// absolutely required or you might not take the leap at all.
        /// If you have played the puzzle on paper for towers of 1 to 3 discs
        /// you may intuitively - thinking ahead - have moved discs
        /// in similar constellations differently and may fail to see the
        /// generic pattern and why behind it.
        /// For a 1-disc tower it is trivial - you directly move from
        /// the start peg to the goal peg.
        /// For a 2-disc tower you (hopefully) are not going to move
        /// top disc 1 directly to goal but to the temporary peg instead
        /// as you are anticipating that you could then move disc 2
        /// directly to its final destination, the goal peg.
        /// Finally you move disc 1 from current peg (temporary) to goal.
        /// For a 3-disc tower you should realize that it makes sense to
        /// first get the 2-disc "sub-tower" out of the way by erecting it
        /// on the temporary peg. Then you can simply move disc 3 to the
        /// final destination, the goal peg. Now the problem is reduced
        /// again to a 2-disc tower problem where the sub-tower is
        /// currently on the temporary peg and has to move to the goal peg.
        /// Hopefully you may start to see that it is only the "goal tower"
        /// that varies by the current needs. You move the current n - 1
        /// tower of discs out of the way to be able to move the last or
        /// nth disc to the final destination. Then you move the n - 1
        /// tower to the final destination.
        /// The more discs there are the more similar groups of steps
        /// there are that repeat over and over with varying source
        /// and destination pegs.
        /// </remarks>
        private void HanoiSolve(int discNumber, Stack <int> source, Stack <int> temporary, Stack <int> destination)
        {
            // Argument checking - discNumber must be >= 1
            if (discNumber < 1)
            {
                throw new ArgumentException(
                          $"Disc number must equal 1 or greater.",
                          nameof(discNumber));
            }

            // Null checks
            if (source == null ||
                temporary == null ||
                destination == null)
            {
                throw new ArgumentException(
                          "Neither stack must be null.");
            }

            // Base case - 1 disc; recursion ends
            if (discNumber == 1)
            {
                // Move directly from source to goal
                var disc = source.Pop();
                destination.Push(disc);

                // Cause a notification event to eg. draw and delay UI.
                DiscMoved?.Invoke(this, new DiscMovedEventArgs <int>(discNumber, source, destination));
            }
            else
            {
                // Recursive procedure - first move all discs less than
                // current discNumber out of the way thus using temporary
                // as goal and goal as temporary storage.
                HanoiSolve(discNumber - 1, source, destination, temporary);

                // We freed the current disc on the current from-stack.
                // Now move the current disc from target to goal.
                var disc = source.Pop();
                destination.Push(disc);

                // Cause a notification event to eg. draw and delay UI.
                DiscMoved?.Invoke(this, new DiscMovedEventArgs <int>(discNumber, source, destination));

                // Finally move the "sub-tower" of (n - 1) parked discs
                // (ie. < discNumber) to the goal peg.
                // We know we are currently on temporary and have to go to
                // goal, using from as temporary as necessary.
                HanoiSolve(discNumber - 1, temporary, source, destination);
            }
        }
Example #2
0
        /// <summary>
        /// Runs the game simulation.
        /// </summary>
        /// <param name="numberOfDiscs">
        /// The number of discs to use in the simulation.
        /// Defaults to 5.
        /// </param>
        /// <exception cref="ArgumentException">
        /// Thrown when <paramref name="numberOfDiscs"/> is smaller than 1 or
        /// larger than the number of discs that could fit the current console
        /// window.
        /// </exception>
        public void Run(int numberOfDiscs = 5)
        {
            var discCandidates =
                OddNumbers(3)
                .Take(_maximumDiscs)
                .ToList();

            var maximumDiscSelection =
                discCandidates
                .Where(n => n <= _maximumDiscWidth)
                .ToList();

            WriteLine("~~~ Hanoi Stats ~~~");
            WriteLine($"Console height: {_consoleHeight}");
            WriteLine($"Console width: {_consoleWidth}");
            WriteLine($"Maximum number of discs: {_maximumDiscs}");
            WriteLine($"Width candidates: {String.Join(", ", discCandidates)}");
            WriteLine($"Maximum disc width: {_maximumDiscWidth}");
            WriteLine($"Maximum disc selection: {String.Join(", ", maximumDiscSelection)}");
            WriteLine($"User-requested discs: {numberOfDiscs}");

            if (numberOfDiscs < 1)
            {
                throw new ArgumentException(
                          "The simulation must be run with a number of discs of 1 or greater.",
                          nameof(numberOfDiscs));
            }

            if (numberOfDiscs > _maximumDiscs)
            {
                throw new ArgumentException(
                          $"Too many discs: A console of {_consoleWidth}x{_consoleHeight} can support a maximum of {_maximumDiscs} discs.",
                          nameof(numberOfDiscs));
            }

            Reset();

            _userNumberOfDiscs = numberOfDiscs;

            var gameDiscs =
                maximumDiscSelection
                .Take(numberOfDiscs);

            WriteLine($"User discs: {String.Join(", ", gameDiscs)}");
            WriteLine();

            // Now put the selection of game discs on the source stack
            foreach (var gameDisc in gameDiscs.Reverse())
            {
                _sourceStack.Push(gameDisc);
            }

            DiscMoved += Visualize;

            // Initial dummy disc move (no discs moved) to get the
            // starting point drawn to screen
            DiscMoved?.Invoke(this, DiscMovedEventArgs <int> .Empty);

            // Recursively solve to move from-stack to goal-stack
            HanoiSolve(_userNumberOfDiscs, _sourceStack, _temporaryStack, _destinationStack);

            DiscMoved -= Visualize;
        }