99 Bottles of Beer

While looking up other things, I came across an interesting site that contained programs written in 303 different programming languages, all of which output the song "99 Bottles of Beer On the Wall." Go take a look at the list, it's actually very entertaining and educational. For those of you unfamiliar with the song, it goes as follows:

99 bottles of beer on the wall,
99 bottles of beer.
Take one down, pass it around,
98 bottles of beer on the wall.

98 bottles of beer on the wall,
98 bottles of beer.
Take one down, pass it around,
97 bottles of beer on the wall.

And so it goes until there are no more bottles of beer on the wall.

When I found the page I saw that there was a C# implementation listed, but I didn't like it. It just didn't capture the spirit of the language. So, I got to work, and wrote what I thought would be a good C# solution. If you want to see this code in action, look here.

The Approach

It would be simple, although very dull, to just make a quick loop to go from 99 to 0:

// A very dull, lifeless way to sing the 99 Bottles song.

using System;

public class SingTheSong
{
    public static void Main()
    {
        int count = 99;

        while (count > 0)
        {
            System.Console.WriteLine("{0} bottles of beer on the wall,", count);
            System.Console.WriteLine("{0} bottles of beer.", count);
            System.Console.WriteLine("Take one down, pass it around,");
            --count;
            System.Console.WriteLine("{0} bottles of beer on the wall,", count);
            System.Console.WriteLine();
        }
    }
}

There's no cachet in that, if you ask me. What if I want to drink something else? What if I want more (or less) than 99? What do I do when I run out? Do I really want to sing "1 bottles of beer"? How do I send the output to, say, a browser instead of the console?

No, that just wouldn't do.

The Implementation

I wanted to write something that actually took advantage of the language features. I made a separate Binge class that handles the output of the song. It's provided with a delegate to do the actual output, and it fires an event when there are no more bottles. It also accepts a name for the drink, other than beer, and a starting count. It even outputs "bottle" when the count is one!

The source to the class is below, and you can even watch it run!

/// Implementation of Ninety-Nine Bottles of Beer Song in C#.
/// What's neat is that .NET makes the Binge class a 
/// full-fledged component that may be called from any other 
/// .NET component.
///
/// Paul M. Parks
/// http://www.parkscomputing.com/
/// February 8, 2002
/// 

using System;

namespace NinetyNineBottles
{
    /// <summary>
    /// References the method of output.
    /// </summary>
    public delegate void Writer(string format, params object[] arg);

    /// <summary>
    /// References the corrective action to take when we run out.
    /// </summary>
    public delegate int MakeRun();

    /// <summary>
    /// The act of consuming all those beverages.
    /// </summary>
    public class Binge
    {
        /// <summary>
        /// What we'll be drinking.
        /// </summary>
        private string beverage;

        /// <summary>
        /// The starting count.
        /// </summary>
        private int count = 0;

        /// <summary>
        /// The manner in which the lyrics are output.
        /// </summary>
        private Writer Sing;

        /// <summary>
        /// What to do when it's all gone.
        /// </summary>
        private MakeRun RiskDUI;

        public event MakeRun OutOfBottles;


        /// <summary>
        /// Initializes the binge.
        /// </summary>
        /// <param name="count">How many we're consuming.</param>
        /// <param name="disasterWaitingToHappen">
        /// Our instructions, should we succeed.
        /// </param>
        /// <param name="writer">How our drinking song will be heard.</param>
        /// <param name="beverage">What to drink during this binge.</param>
        public Binge(string beverage, int count, Writer writer)
        {
            this.beverage = beverage;
            this.count = count;
            this.Sing = writer;
        }

        /// <summary>
        /// Let's get started.
        /// </summary>
        public void Start()
        {
            while (count > 0)
            {
                Sing(
                    @"
{0} bottle{1} of {2} on the wall,
{0} bottle{1} of {2}.
Take one down, pass it around,", 
                    count, (count == 1) ? "" : "s", beverage);

                count--;

                if (count > 0)
                {
                    Sing("{0} bottle{1} of {2} on the wall.",
                        count, (count == 1) ? "" : "s", beverage);
                }
                else
                {
                    Sing("No more bottles of {0} on the wall.", beverage);
                }

            }

            Sing(
                @"
No more bottles of {0} on the wall,
No more bottles of {0}.", beverage);

            if (this.OutOfBottles != null)
            {
                count = this.OutOfBottles();
                Sing("{0} bottles of {1} on the wall.", count, beverage);
            }
            else
            {
                Sing("First we weep, then we sleep.");
                Sing("No more bottles of {0} on the wall.", beverage);
            }
        }
    }

    /// <summary>
    /// The song remains the same.
    /// </summary>
    class SingTheSong
    {
        /// <summary>
        /// Any other number would be strange.
        /// </summary>
        const int bottleCount = 99;

        /// <summary>
        /// The entry point. Sets the parameters of the Binge and starts it.
        /// </summary>
        /// <param name="args">unused</param>
        static void Main(string[] args)
        {
            Binge binge = 
               new Binge("beer", bottleCount, new Writer(Console.WriteLine));
            binge.OutOfBottles += new MakeRun(SevenEleven);
            binge.Start();
        }

        /// <summary>
        /// There's bound to be one nearby.
        /// </summary>
        /// <returns>Whatever would fit in the trunk.</returns>
        static int SevenEleven()
        {
            Console.WriteLine("Go to the store, get some more...");
            return bottleCount;
        }
    }
}