Simple, Quality, Awesome Software

Frog Racing

Below is the complete code for the Frog Racing Try It Out! problem.

Simple Version

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace FrogRace
{
    class Program
    {
        static void Main(string[] args)
        {
            int totalFrogs = 5;

            List<Thread> threads = new List<Thread>();

            for (int index = 0; index < totalFrogs; index++)
            {
                Thread thread = new Thread(HandleSingleFrog);
                thread.Start(index + 1);
                threads.Add(thread);
            }

            foreach (Thread thread in threads)
            {
                thread.Join();
            }

            Console.WriteLine("The race is over!");
            Console.ReadKey();
        }

        private static Random random = new Random();

        private static void HandleSingleFrog(object numberAsObject)
        {
            int number = (int)numberAsObject;

            for (int jump = 0; jump < 10; jump++)
            {
                Console.WriteLine("Frog " + number + " jumped.");
                Thread.Sleep(random.Next(5000) + 500);
            }

            Console.WriteLine("***Frog " + number + " is finished!***");
        }
    }
}

Advanced Version Demonstrating Thread Safety

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace FrogRace
{
    class Program
    {
        /// <summary>
        /// The total number of jumps required by a frog to finish.
        /// </summary>
        private static readonly int TotalJumps = 10;

        /// <summary>
        /// The total number of frogs in the race.
        /// </summary>
        private static readonly int TotalFrogs = 5;

        /// <summary>
        /// The current jump for each frog in the race.
        /// </summary>
        private static int[] jumpCount;

        /// <summary>
        /// The finishing place of each frog in the race. A value of 0
        /// means that they haven't finished yet.
        /// </summary>
        private static int[] finishingPlace;

        /// <summary>
        /// The place that the next frog to finish will take.
        /// </summary>
        private static int place;

        /// <summary>
        /// A plain old object, used as a lock for accessing shared code.
        /// </summary>
        private static object raceLock = new object();

        /// <summary>
        /// A shared random number generator, for making the frogs jump
        /// at random intervals.
        /// </summary>
        private static Random random = new Random();

        /// <summary>
        /// Runs the frog race.
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // Set things up.
            place = 0;
            finishingPlace = new int[TotalFrogs];
            jumpCount = new int[TotalFrogs];

            for (int index = 0; index < TotalFrogs; index++)
            {
                jumpCount[index] = 0;
                finishingPlace[index] = 0;
            }

            // Draw things at the starting line.
            PrintRace();

            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("Press any key to start the race!");

            Console.ReadKey(false);

            // Kick off the race.
            List<Thread> threads = new List<Thread>();
            for (int index = 0; index < TotalFrogs; index++)
            {
                Thread thread = new Thread(HandleSingleFrog);
                thread.Start(index + 1);
                threads.Add(thread);
            }

            // Wait for all of the threads/frogs to finish.
            foreach (Thread thread in threads)
            {
                thread.Join();
            }

            // End the race.
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("The race is over!");
            Console.ReadKey();
        }

        /// <summary>
        /// Determines what place a finishing frog took, and assigns it.
        /// The next time this method is called, a different number will
        /// be assigned.
        /// </summary>
        /// <returns></returns>
        private static int GetPlace()
        {
            // We lock on this critical section, because if we don't, it's
            // possible for two frogs to be assigned the same finishing place
            // if a context switch happens at the wrong time.
            lock (raceLock)
            {
                place++;
                return place;
            }
        }

        /// <summary>
        /// Runs the work for a single frog. The frog will periodically
        /// jump until it has jumped as many times as required, at which
        /// point it will finish up and the thread will end.
        /// </summary>
        /// <param name="numberAsObject"></param>
        private static void HandleSingleFrog(object numberAsObject)
        {
            int number = (int)numberAsObject;

            for (int jump = 0; jump < TotalJumps; jump++)
            {
                Thread.Sleep(random.Next(3000) + 500);
                jumpCount[number - 1]++;
                PrintRace(); // Whenever a frog moves, we'll redraw.
            }

            // When we're done, we'll figure out what place the frog took.
            finishingPlace[number - 1] = GetPlace();
            PrintRace();
        }


        private static void PrintRace()
        {
            // It would be bad if we tried to print twice at the same time,
            // so we lock here to protect context switches from occuring right
            // in the middle of this.
            // In theory, it would make sense to use a different lock for this
            // than the one that chooses the frog's finishing place.
            lock (raceLock)
            {
                Console.Clear();

                // Print the top banner.
                Console.ForegroundColor = ConsoleColor.Blue;
                Console.WriteLine("FROG");
                Console.WriteLine("    RACE!");

                // Print the finishing place (or ?) of each of the frogs.
                Console.ForegroundColor = ConsoleColor.White;
                for (int frog = 0; frog < TotalFrogs; frog++)
                {
                    if (finishingPlace[frog] > 0)
                    {
                        Console.Write(finishingPlace[frog] + " ");
                    }
                    else
                    {
                        Console.Write("? ");
                    }
                }

                Console.WriteLine();

                // Print out the race, one row at a time.
                for (int row = 0; row < TotalJumps + 1; row++)
                {
                    for (int frog = 0; frog < TotalFrogs; frog++)
                    {
                        // If this is the row the frog is on, print a frog...
                        if (jumpCount[frog] == TotalJumps - row)
                        {
                            Console.ForegroundColor = ConsoleColor.Green;
                            Console.Write("Ω ");
                        }
                        else // Otherwise, print an empty marker.
                        {
                            Console.ForegroundColor = ConsoleColor.DarkGray;
                            Console.Write(". ");
                        }
                    }

                    Console.WriteLine();
                }
            }
        }
    }
}