c-sharp

Mapping DataTables and DataRows to Objects in C# and .NET

My group regularly uses DataSet, DataTable, and DataRow objects in many of our apps.

(What? Don't look at me like that. These apps are old.)

Anyway, we're simultaneously trying to implement good C# and object-oriented programming principles while maintaining these old apps, so we often end up having to map data from a data set to a C# object. We did this enough times that I and a coworker (we'll call her Marlena) decided to sit down and just make up a new mapping system for use with these DataTable and DataRow objects.

As always with my code-based posts, there's a GitHub project with a full working example app, so check that out too!

One Jump Ahead

So here's a basic problem with mapping from DataSet, DataTable, and DataRow objects: we don't know at compile time what columns and tables exist in the set, so mapping solutions like AutoMapper won't work for this scenario. Our mapping system will have to assume what columns exist. But, in order to make it more reusable, we will make the mapping system return default values for any values which it does not locate.

There's also another, more complex problem: the databases we are acquiring our data from use many different column names to represent the same data. 20 years of different maintainers and little in the way of cohesive naming standards will do that do a database. So, if we needed a person's first name, the different databases might use:

  • first_name
  • firstName
  • fname
  • name_first

This, as might be imagined, makes mapping anything rather difficult. So our system will also need to be able to map from many different column names.

Finally, this system wouldn't be worth much if it couldn't handle collections of objects as well as single objects, so we'll need to allow for that as well.

So, in short, our system needs to:

  1. Map from DataTable and DataRow to objects.
  2. Map from multiple different column names.
  3. Handle mapping to a collection of objects as well as a single object.

We'll need several pieces to accomplish this. But before we can even start building the mapping system, we must first acquire some sample data.

Mine, Mine, Mine

We're going to create some DataSet objects that we can test our system against. In the real world, you would use an actual database, but here (for simplicity's sake) we're just going to manually create some DataSet objects. Here's a sample class which will create two DataSet objects, Priests and Ranchers, each of which use different column names for the same data:

public static class DataSetGenerator  
{
    public static DataSet Priests()
    {
        DataTable priestsDataTable = new DataTable();
        priestsDataTable.Columns.Add(new DataColumn()
        {
            ColumnName = "first_name",
            DataType = typeof(string)
        });
        priestsDataTable.Columns.Add(new DataColumn()
        {
            ColumnName = "last_name",
            DataType = typeof(string)
        });
        priestsDataTable.Columns.Add(new DataColumn()
        {
            ColumnName = "dob",
            DataType = typeof(DateTime)
        });
        priestsDataTable.Columns.Add(new DataColumn()
        {
            ColumnName = "job_title",
            DataType = typeof(string)
        });
        priestsDataTable.Columns.Add(new DataColumn()
        {
            ColumnName = "taken_name",
            DataType = typeof(string)
        });
        priestsDataTable.Columns.Add(new DataColumn()
        {
            ColumnName = "is_american",
            DataType = typeof(string)
        });

        priestsDataTable.Rows.Add(new object[] { "Lenny", "Belardo", new DateTime(1971, 3, 24), "Pontiff", "Pius XIII", "yes" });
        priestsDataTable.Rows.Add(new object[] { "Angelo", "Voiello", new DateTime(1952, 11, 18), "Cardinal Secretary of State", "", "no" });
        priestsDataTable.Rows.Add(new object[] { "Michael", "Spencer", new DateTime(1942, 5, 12), "Archbishop of New York", "", "yes" });
        priestsDataTable.Rows.Add(new object[] { "Sofia", "(Unknown)", new DateTime(1974, 7, 2), "Director of Marketing", "", "no" });
        priestsDataTable.Rows.Add(new object[] { "Bernardo", "Gutierrez", new DateTime(1966, 9, 16), "Master of Ceremonies", "", "no" });

        DataSet priestsDataSet = new DataSet();
        priestsDataSet.Tables.Add(priestsDataTable);

        return priestsDataSet;
    }

    public static DataSet Ranchers()
    {
        DataTable ranchersTable = new DataTable();
        ranchersTable.Columns.Add(new DataColumn()
        {
            ColumnName = "firstName",
            DataType = typeof(string)
        });
        ranchersTable.Columns.Add(new DataColumn()
        {
            ColumnName = "lastName",
            DataType = typeof(string)
        });
        ranchersTable.Columns.Add(new DataColumn()
        {
            ColumnName = "dateOfBirth",
            DataType = typeof(DateTime)
        });
        ranchersTable.Columns.Add(new DataColumn()
        {
            ColumnName = "jobTitle",
            DataType = typeof(string)
        });
        ranchersTable.Columns.Add(new DataColumn()
        {
            ColumnName = "nickName",
            DataType = typeof(string)
        });
        ranchersTable.Columns.Add(new DataColumn()
        {
            ColumnName = "isAmerican",
            DataType = typeof(string)
        });

        ranchersTable.Rows.Add(new object[] { "Colt", "Bennett", new DateTime(1987, 1, 15), "Ranchhand", "", "y" });
        ranchersTable.Rows.Add(new object[] { "Jameson", "Bennett", new DateTime(1984, 10, 10), "Ranchhand", "Rooster", "y" });
        ranchersTable.Rows.Add(new object[] { "Beau", "Bennett", new DateTime(1944, 8, 9), "Rancher", "", "y" });
        ranchersTable.Rows.Add(new object[] { "Margaret", "Bennett", new DateTime(1974, 7, 2), "Bar Owner", "Maggie", "y" });
        ranchersTable.Rows.Add(new object[] { "Abigail", "Phillips", new DateTime(1987, 4, 24), "Teacher", "Abby", "y" });

        DataSet ranchersDataSet = new DataSet();
        ranchersDataSet.Tables.Add(ranchersTable);

        return ranchersDataSet;
    }
}

We'll test our system against this sample data.

Something There

Now we can build our actual mapping solution. First off, we need a way to decide what column names map to object properties. It was Marlena's idea to keep those things together, and so we came up with a class called DataNamesAttribute that looks like this:

[AttributeUsage(AttributeTargets.Property)]
public class DataNamesAttribute : Attribute  
{
    protected List<string> _valueNames { get; set; }

    public List<string> ValueNames
    {
        get
        {
            return _valueNames;
        }
        set
        {
            _valueNames = value;
        }
    }

    public DataNamesAttribute()
    {
        _valueNames = new List<string>();
    }

    public DataNamesAttribute(params string[] valueNames)
    {
        _valueNames = valueNames.ToList();
    }
}

This attribute can then be used (in fact, can only be used, due to the AttributeUsage(AttributeTargets.Property) declaration) on properties of other classes. Let's say we're going to map to a Person class. We would use DataNamesAttribute like so:

public class Person  
{
    [DataNames("first_name", "firstName")]
    public string FirstName { get; set; }

    [DataNames("last_name", "lastName")]
    public string LastName { get; set; }

    [DataNames("dob", "dateOfBirth")]
    public DateTime DateOfBirth { get; set; }

    [DataNames("job_title", "jobTitle")]
    public string JobTitle { get; set; }

    [DataNames("taken_name", "nickName")]
    public string TakenName { get; set; }

    [DataNames("is_american", "isAmerican")]
    public bool IsAmerican { get; set; }
}

Now that we know where the data needs to end up, let's start mapping out the mapper (heh).

Reflection

Our mapper class will be a generic class so that we can map from DataTable or DataRow objects to any kind of object. We'll need two methods to get different kinds of data:

public class DataNamesMapper<TEntity> where TEntity : class, new()  
{
    public TEntity Map(DataRow row) { ... }
    public IEnumerable<TEntity> Map(DataTable table) { ... }
}

Let's start with the Map(DataRow row) method. We need to do three things:

  1. Figure out what columns exist in this row.
  2. Determine if the TEntity we are mapping to has any properties with the same name as any of the columns (aka the Data Names) AND
  3. Map the value from the DataRow to the TEntity.

Here's how we do this, using just a bit of reflection:

public TEntity Map(DataRow row)  
{
    //Step 1 - Get the Column Names
    var columnNames = row.Table.Columns
                               .Cast<DataColumn>()
                               .Select(x => x.ColumnName)
                               .ToList();

    //Step 2 - Get the Property Data Names
    var properties = (typeof(TEntity)).GetProperties()
                                      .Where(x => x.GetCustomAttributes(typeof(DataNamesAttribute), true).Any())
                                      .ToList();

    //Step 3 - Map the data
    TEntity entity = new TEntity();
    foreach (var prop in properties)
    {
        PropertyMapHelper.Map(typeof(TEntity), row, prop, entity);
    }

    return entity;
}

Of course, we also need to handle the other method, the one where we can get a collection of TEntity:

public IEnumerable<TEntity> Map(DataTable table)  
{
    //Step 1 - Get the Column Names
    var columnNames = table.Columns.Cast<DataColumn>().Select(x => x.ColumnName).ToList();

    //Step 2 - Get the Property Data Names
    var properties = (typeof(TEntity)).GetProperties()
                                        .Where(x => x.GetCustomAttributes(typeof(DataNamesAttribute), true).Any())
                                        .ToList();

    //Step 3 - Map the data
    List<TEntity> entities = new List<TEntity>();
    foreach (DataRow row in table.Rows)
    {
        TEntity entity = new TEntity();
        foreach (var prop in properties)
        {
            PropertyMapHelper.Map(typeof(TEntity), row, prop, entity);
        }
        entities.Add(entity);
    }

    return entities;
}

You might be wondering just what the heck the PropertyMapHelper class is. If you are, you might also be about to regret it.

Dig a Little Deeper

The PropertyMapHelper, as suggested by the name, maps values to different primitive types (int, string, DateTime, etc.). Here's that Map() method we saw earlier:

public static void Map(Type type, DataRow row, PropertyInfo prop, object entity)  
{
    List<string> columnNames = AttributeHelper.GetDataNames(type, prop.Name);

    foreach (var columnName in columnNames)
    {
        if (!String.IsNullOrWhiteSpace(columnName) && row.Table.Columns.Contains(columnName))
        {
            var propertyValue = row[columnName];
            if (propertyValue != DBNull.Value)
            {
                ParsePrimitive(prop, entity, row[columnName]);
                break;
            }
        }
    }
}

There are two pieces in this method that we haven't defined yet: the AttributeHelper class and the ParsePrimitive() method. AttributeHelper is a rather simple class that merely gets the list of column names from the DataNamesAttribute:

public static List<string> GetDataNames(Type type, string propertyName)  
{
    var property = type.GetProperty(propertyName).GetCustomAttributes(false).Where(x => x.GetType().Name == "DataNamesAttribute").FirstOrDefault();
    if (property != null)
    {
        return ((DataNamesAttribute)property).ValueNames;
    }
    return new List<string>();
}

The other we need to define in ParsePrimitive(), which as its name suggests will parse the values into primitive types. Essentially what this class does is assign a value to a passed-in property reference (represented by the PropertyInfo class). I'm not going to post the full code on this post (you can see it over on GitHub), so here's a snippet of what this method does:

private static void ParsePrimitive(PropertyInfo prop, object entity, object value)  
{
    if (prop.PropertyType == typeof(string))
    {
        prop.SetValue(entity, value.ToString().Trim(), null);
    }
    else if (prop.PropertyType == typeof(int) || prop.PropertyType == typeof(int?))
    {
        if (value == null)
        {
            prop.SetValue(entity, null, null);
        }
        else
        {
            prop.SetValue(entity, int.Parse(value.ToString()), null);
        }
    }
    ...
}

That's the bottom of the rabbit hole, as it were. Now, we can use the DataSet objects we created earlier and our mapping system to see if we can map this data correctly.

Two Worlds

Here's a quick program that can test our new mapping system:

class Program  
{
    static void Main(string[] args)
    {
        var priestsDataSet = DataSetGenerator.Priests();
        DataNamesMapper<Person> mapper = new DataNamesMapper<Person>();
        List<Person> persons = mapper.Map(priestsDataSet.Tables[0]).ToList();

        var ranchersDataSet = DataSetGenerator.Ranchers();
        persons.AddRange(mapper.Map(ranchersDataSet.Tables[0]));

        foreach (var person in persons)
        {
            Console.WriteLine("First Name: " + person.FirstName + ", Last Name: " + person.LastName
                                + ", Date of Birth: " + person.DateOfBirth.ToShortDateString()
                                + ", Job Title: " + person.JobTitle + ", Nickname: " + person.TakenName
                                + ", Is American: " + person.IsAmerican);
        }

        Console.ReadLine();
    }
}

When we run this app (which you can do too), we will get the following output:

Which is exactly what we want!

(I mean, really, did you expect me to blog about something that didn't work?)

Go the Distance

It concerns me that this system is overly complicated, and I'd happily take suggestions on how to make it more straightforward. While I do like how all we need to do is place the DataNamesAttribute on the correct properties and then call an instance of DataNamesMapper<T>, I feel like the whole thing could be easier somehow. Believe it or not, this version is actually simpler than the one we're actually using in our internal apps.

Also, check out the sample project over on GitHub, fork it, test it, whatever. If it helped you out, or if you can improve it, let me know in the comments!

Finally, extra special bonus points will go to anyone who can figure out a) what the hell those odd section titles are about and b) where I got the sample data from.

Happy Coding!

Decimal vs Double and Other Tips About Number Types in .NET

I've been coding with .NET for a long time. In all of that time, I haven't really had a need to figure out the nitty-gritty differences between float and double, or between decimal and pretty much any other type. I've just used them as I see fit, and hope that's how they were meant to be used.

Until recently, anyway. Recently, as I was attending the AngleBrackets conference, and one of the coolest parts of attending that conference is getting to be in an in-depth workshop. My particular workshop was called I Will Make You A Better C# Programmer by Kathleen Dollard, and my reaction was thus:

One of the most interesting things I learned at Kathleen's session was that the .NET number types don't always behave the way I think they do. In this post, I'm going to walk through a few (a VERY few) of Kathleen's examples and try to explain why .NET has so many different number types and what they are each for. Come along as I (with her code) attempt to show what the number types are, and what they are used for!

The Number Types in .NET

Let's start with a review of the more common number types in .NET. Here's a few of the basic types:

  • Int16 (aka short): A signed integer with 16 bits (2 bytes) of space available.
  • Int32 (aka int): A signed integer with 32 bits (4 bytes) of space available.
  • Int64 (aka long): A signed integer with 64 bits (8 bytes) of space available.
  • Single (aka float): A 32-bit floating point number.
  • Double (aka double): A 64-bit floating-point number.
  • Decimal (aka decimal): A 128-bit floating-point number with a higher precision and a smaller range than Single or Double.

There's an interesting thing to point out when comparing double and decimal: the range of double is ±5.0 × 10−324 to ±1.7 × 10308, while the range of decimal is (-7.9 x 1028 to 7.9 x 1028) / (100 to 28). In other words, the range of double is several times larger than the range of decimal. The reason for this is that they are used for quite different things.

Precision vs Accuracy

One of the concepts that's really important to discuss when dealing with .NET number types is that of precision vs. accuracy. To make matters more complicated, there are actually two different definitions of precision, one of which I will call arithmetic precision.

  • Precision refers to the closeness of two or more measurements to each other. If you measure something five times and get exactly 4.321 each time, your measurement can be said to be very precise.
  • Accuracy refers to the closeness of a value to standard or known value. If you measure something, and find it's weight to be 4.7kg, but the known value for that object is 10kg, your measurement is not very accurate.
  • Arithmetic precision refers to the number of digits used to represent a number (e.g. how many numbers after the decimal are used). The number 9.87 is less arithmetically precise than the number 9.87654332.

We always need mathematical operations in a computer system to be accurate; we cannot ever expect 4 x 6 = 32. Further, we also need these calculations to be precise using the common term; 4 x 6 must always be precisely 24 no matter how many times we make that calculation. However, the extent to which we want our systems to be either arithmetically precise has a direct impact on the performance of those system.

If we lose some arithmetic precision, we gain performance. The reverse is also true: if we need values to be arithmetically precise, we will spend more time calculating those values. Forgetting this can lead to incredible performance problems, problems which can be solved by using the correct type for the correct problem. These kinds of issues are most clearly shown during Test 3 later in this post.

Why Is Int The Default?

Here's something I've always wondered. If you take this line of code:

var number = 5;  

Why is the type of number always an int? Why not a short, since that takes up less space? Or maybe a long, since that will represent nearly any integer we could possibly use?

Turns out, the answer is, as it often is, performance. .NET optimizes your code to run in a 32-bit architecture, which means that any operations involving 32-bit integers will by definition be more performant than either 16-bit or 64-bit operations. I expect that this will change as we move toward a 64-bit architecture being standard, but for now, 32-bit integers are the most performant option.

Testing the Number Types

One of the ways we can start to see the inherent differences between the above types is by trying to use them in calculations. We're going to see three different tests, each of which will reveal a little more about how .NET uses each of these types.

Test 1: Division

Let's start with a basic example using division. Consider the following code:

private void DivisionTest1()  
{
    int maxDiscountPercent = 30;
    int markupPercent = 20;
    Single niceFactor = 30;
    double discount = maxDiscountPercent * (markupPercent / niceFactor);
    Console.WriteLine("Discount (double): ${0:R}", discount);
}

private void DivisionTest2()  
{
    byte maxDiscountPercent = 30;
    int markupPercent = 20;
    int niceFactor = 30;
    int discount = maxDiscountPercent * (markupPercent / niceFactor);
    Console.WriteLine("Discount (int): ${0}", discount);
}

Note that the only thing that's really different about these two methods are the types of the local variables.

Now here's the question: what will the discount be in each of these methods?

If you said that they'll both be $20, you're missing something very important.

The problem line is this one, from DivisionTest2():

int discount = maxDiscountPercent * (markupPercent / niceFactor);  

Here's the problem: because markupPercent is declared as an int (which in turn makes it an Int32), when you divide an int by another int, the result will be an int, even when we would logically expect it to be something like a double. .NET does this by truncating the result, so because 20 / 30 = 0.6666667, what you get back is 0 (and anything times 0 is 0).

In short, the discount for DivisionTest1 is the expected $20, but the discount for DivisionTest2 is $0, and the only difference between them is what types are used. That's quite a difference, no?

Test 2 - Double Addition

Now we get to see something really weird, and it involves the concept of arithmetic precision from earlier. Here's the next method:

public void DoubleAddition()  
{
    Double x = .1;
    Double result = 10 * x;
    Double result2 = x + x + x + x + x + x + x + x + x + x;

    Console.WriteLine("{0} - {1}", result, result2);
    Console.WriteLine("{0:R} - {1:R}", result, result2);
}

Just by reading this code, we expect result and result2 to be the same: multiplying .1 x 10 should equal .1 + .1 + .1 + .1 + .1 + .1 + .1 + .1 + .1 + .1.

But there's another trick here, and that's the usage of the "{O:R}" string formatter. That's called the round-trip formatter, and it tells .NET to display all parts of this number to its maximum arithmetic precision.

If we run this method, what does the output look like?

By using the round-trip formatter, we see that the multiplication result ended up being exactly 1, but the addition result was off from 1 by a miniscule (but still potentially significant) amount. Question is: why does it do this?

In most systems, a number like 0.1 cannot be accurately represented using binary. There will be some form of arithmetic precision error when using a number such as this. Generally, said arithmetic precision error is not noticeable when doing mathematical operations, but the more operations you perform, the more noticeable the error is. The reason we see the error above is because for the multiplication portion, we only performed one operation, but for the addition portion, we performed ten, and thus caused the arithmetic precision error to compound each time.

Test 3 - Decimal vs Double Performance

Now we get to see something really interesting. I'm often approached by new .NET programmers with a question like the following: why should we use decimal over double and vice-versa? This test pretty clearly spells out when and why you should use these two types.

Here's the sample code:

private int iterations = 100000000;

private void DoubleTest()  
{
    Stopwatch watch = new Stopwatch();
    watch.Start();
    Double z = 0;
    for (int i = 0; i < iterations; i++)
    {
        Double x = i;
        Double y = x * i;
        z += y;
    }
    watch.Stop();
    Console.WriteLine("Double: " + watch.ElapsedTicks);
    Console.WriteLine(z);
}

private void DecimalTest()  
{
    Stopwatch watch = new Stopwatch();
    watch.Start();
    Decimal z = 0;
    for (int i = 0; i < iterations; i++)
    {
        Decimal x = i;
        Decimal y = x * i;
        z += y;
    }
    watch.Stop();
    Console.WriteLine("Decimal: " + watch.ElapsedTicks);
    Console.WriteLine(z);
}

For each of these types, we are doing a series of operations (100 million of them) and seeing how many ticks it takes for the double operation to execute vs how many ticks it takes for the decimal operations to execute. The answer is startling:

The operations involving double take 790836 ticks, while the operations involving decimal take a whopping 16728386 ticks. In other words, the decimal operations take 21 times longer to execute than the double operations. (If you run the sample project, you'll notice that the decimal operations take visibly longer than the double ones).

But why? Why does double take so much less time than decimal?

For one thing, double uses base-2 math, while decimal uses base-10 math. Base-2 math is much quicker for computers to calculate.

Further, what double is concerned with is performance, while what decimal is concerned with is precision. When using double, you are accepting a known trade-off: you won't be super precise in your calculations, but you will get an acceptable answer quickly. Whereas with decimal, precision is built into its type: it's meant to be used for money calculations, and guess how many people would riot if those weren't accurate down to the 23rd place after the decimal point.

In short, double and decimal are two totally separate types for a reason: one is for speed, and the other is for precision. Make sure you use the appropriate one at the appropriate time.

Summary

As can be expected from such a long-lived framework, .NET has several number types to help you with your calculations, ranging from simple integers to complex currency-based values. As always, it's important to use the correct tool for the job:

  • Use double for non-integer math where the most precise answer isn't necessary.
  • Use decimal for non-integer math where precision is needed (e.g. money and currency).
  • Use int by default for any integer-based operations that can use that type, as it will be more performant than short or long.

Don't forget to check out the sample project over on GitHub!

Are there any other pitfalls or improvements we should be aware of? Feel free to sound off in the comments!

Happy Coding!

Huge thanks to Kathleen Dollard (@kathleendollard) for providing the code samples and her invaluable insight into how to effectively explain what's going on in these samples. Check out her Pluralsight course for more!

Modeling Practice: UNO in C# Part 3 - Final Steps and Playing The Game

Note: This post is the third in a three-part series which attempts to model the card game UNO as a C# application. Here's Part One and Part Two. You may want to use the GitHub repository to follow along.

In the previous parts of this series we first saw how to play UNO and how to model the cards and moved on to how to model the player behavior. In this post, the last part of the series, we're going to take the results from the first two parts and combine them together to make a fully-working UNO-playing robot.

Well, UNO-playing C# application anyway. What? I can dream.

The Game Manager

The most critical component that we haven't yet built is a class called GameManager. In a real UNO game, the players themselves are responsible for keeping track of everybody else following the rules. However, in our model, we'll need a non-player class to do this, as well as control the game flow and keep track of which player's turn is next.

Here's the skeleton of the GameManager class. The next step will be to establish what each of these methods actually do.

public class GameManager  
{
    public List<Player> Players { get; set; }
    public CardDeck DrawPile { get; set; }
    public List<Card> DiscardPile { get; set; }

    public GameManager(int numPlayers) { }
    public void PlayGame() { }
    private void AddToDiscardPile(PlayerTurn currentTurn) { }
}

Creating the Game

The first method is just the GameManager class's constructor, which we will use to set up the game being played. The only input to this method is an int numPlayers, which is the number of players that will be playing the game.

Given the number of players, the GameManager must set up the game, which consists of:

  1. Creating the deck of cards.
  2. Dealing seven cards to each player.
  3. Placing a single card from the draw pile into the discard pile.

Here's how our constructor does this:

public GameManager(int numPlayers)  
{
    Players = new List<Player>();
    DrawPile = new CardDeck();
    DrawPile.Shuffle();

    //Create the players
    for (int i = 1; i <= numPlayers; i++)
    {
        Players.Add(new Player()
        {
            Position = i
        });
    }

    int maxCards = 7 * Players.Count;
    int dealtCards = 0;

    //Deal 7 cards to each player
    while(dealtCards < maxCards)
    {
        for(int i = 0; i < numPlayers; i ++)
        {
            Players[i].Hand.Add(DrawPile.Cards.First());
            DrawPile.Cards.RemoveAt(0);
            dealtCards++;
        }
    }

    //Add a single card to the discard pile
    DiscardPile = new List<Card>();
    DiscardPile.Add(DrawPile.Cards.First());
    DrawPile.Cards.RemoveAt(0);

    //Game rules do not allow the first discard to be a wild.
    while(DiscardPile.First().Value == CardValue.Wild || DiscardPile.First().Value == CardValue.DrawFour)
    {
        DiscardPile.Insert(0, DrawPile.Cards.First());
        DrawPile.Cards.RemoveAt(0);
    }

    //And now we're ready to play!
}

With all that setup out of the way, we can finally let GameManager start an actual game!

Playing the Game

GameManager kicks off the game by telling Player 1 to take his turn. Play must then proceed (to Player 2, then Player 3, so on) until somebody plays a Reverse card. At that point, we need GameManager to note that a Reverse card was played and reverse the turn order.

GameManager also needs to stop the game when a player no longer has any cards in his/her hand.

We can implement the actual playing of the game using the PlayGame() and AddToDiscardPile() methods like so:

public void PlayGame()  
{
    int i = 0;
    bool isAscending = true;

    //First, let's show what each player starts with
    foreach (var player in Players)
    {
        player.ShowHand();
    }

    //Game won't start until user presses Enter
    Console.ReadLine();

    //We need a "mock" PlayerTurn representing the first discard
    PlayerTurn currentTurn = new PlayerTurn()
    {
        Result = TurnResult.GameStart,
        Card = DiscardPile.First(),
        DeclaredColor = DiscardPile.First().Color
    };

    Console.WriteLine("First card is a " + currentTurn.Card.DisplayValue + ".");

    //Game continues until somebody has no cards in their hand
    while(!Players.Any(x => !x.Hand.Any()))
    {
        //If the draw pile is getting low, shuffle the discard pile into the draw pile
        if(DrawPile.Cards.Count < 4)
        {
            var currentCard = DiscardPile.First();

            //Take the discarded cards, shuffle them, and make them the new draw pile.
            DrawPile.Cards = DiscardPile.Skip(1).ToList();
            DrawPile.Shuffle();

            //Reset the discard pile to only have the current card.
            DiscardPile = new List<Card>();
            DiscardPile.Add(currentCard);

            Console.WriteLine("Shuffling cards!");
        }

        //Now the current player can take their turn
        var currentPlayer = Players[i];
        currentTurn = Players[i].PlayTurn(currentTurn, DrawPile);

        //We must add the current player's discarded card to the discard pile.
        AddToDiscardPile(currentTurn);

        //When somebody plays a reverse card, we need to reverse the turn order
        if (currentTurn.Result == TurnResult.Reversed)
        {
            isAscending = !isAscending;
        }

        //Now we figure out who has the next turn.
        if (isAscending)
        {
            i++;
            if (i >= Players.Count) //Reset player counter
            {
                i = 0;
            }
        }
        else
        {
            i--;
            if (i < 0)
            {
                i = Players.Count - 1;
            }
        }        
    }

    //Let's see who won the game!
    var winningPlayer = Players.Where(x => !x.Hand.Any()).First();
    Console.WriteLine("Player " + winningPlayer.Position.ToString() + " wins!!");

    //Finally, calculate and display each player's score
    foreach(var player in Players)
    {
        Console.WriteLine("Player " + player.Position.ToString() + " has " + player.Hand.Sum(x => x.Score).ToString() + " points in his hand.");
    }
}

private void AddToDiscardPile(PlayerTurn currentTurn)  
{
    if (currentTurn.Result == TurnResult.PlayedCard
            || currentTurn.Result == TurnResult.DrawTwo
            || currentTurn.Result == TurnResult.Skip
            || currentTurn.Result == TurnResult.WildCard
            || currentTurn.Result == TurnResult.WildDrawFour
            || currentTurn.Result == TurnResult.Reversed)
    {
        DiscardPile.Insert(0, currentTurn.Card);
    }
}

Whew! We are finally done with our code. All that's left to do now is to run a sample game!

Running a Sample Game

With all the code in place, let's run the app a few times to make sure it works the way we think it does.

The first time we boot the app (there's a complete working version over on GitHub), we'll see something like this:

Well well, looks like Player 3 has a pretty good hand, what with all the wilds. But, let's run the app to see how everyone does.

And, sure enough, Player 3 ends up winning the game. Those wilds help.

Drawbacks of This Model

As I've said many times throughout this series, the point of those posts is not to model UNO precisely, the point is to take a complex real-world problem and break it down into smaller, more manageable little problems.

That said, I can identify a few ways in which, given unlimited time, I might improve this model:

  • Different player "personalities": Not every player is going to be a stupid jackass. I'd like to model different kinds of player strategy (e.g. offensive vs. defensive, hold wilds vs play them, etc.).
  • A GUI: I mean, I know the hardcore programmers among us LOVE them some command line, but really this could use a GUI to make it pop.
  • Rules modification: Different UNO sets use different kinds of rules, and I love to find a way to model lots of different rules and have the players react to them.

That said, I'm pretty darn happy with how this turned out.

Summary

The point of modeling practice is to practice. Sounds obvious, I know, but I firmly believe that the difficulty in creating complex software programs is not writing the code, but in getting the correct requirements. Modeling practice helps us consider all possibilities, and when we do it against a known game like UNO (or Candy Land or Minesweeper) we have a distinct set of rules to work against, something we often lack in real-world projects.

As always, feel free to leave any comments you may have (good or bad) in the comments section below, and check out the GitHub repository and maybe even run the app a few times. I'm quite proud of how it turned out.

Happy Coding!

Modeling Practice: UNO in C# Part 2 - Player Behavior

Note: This post is the second in a three-part series which attempts to model the card game UNO as a C# application. Here's Part One. You may want to use the GitHub repository to follow along.

Now that we have modeled the cards and the draw pile, we come to the first really tricky part of this modeling practice: we must model the players of the game AND how they will behave during the game itself.

The first part (modeling the players themselves) is simpler, and so we will do it first. A "player" in this game is really just a set of cards (the "hand"), the position that the player is sitting in (first, second, third, etc.) and the logic the player uses to decide which card to play. The simplest possible player class would then look like this:

public class Player  
{
    public List<Card> Hand { get; set; }

    //This determines the starting turn order
    public int Position { get; set; } 

    public Player()
    {
        Hand = new List<Card>();
    }
}

The problem with this simple class is that it leaves out the logic the player must use to determine which card s/he wants to play. That logic is not simple, and in my model it takes quite a few steps to work out.

Assumptions

First, we need to make some critical assumptions. There is no mathematical way to have a "perfect" game of UNO, so our players will often need to decide which card to play of two or they could play.

First, we must remember a few rules from earlier:

  • A player playing a card must match the current discard by either color or value, or play a regular Wild card.
  • A player following a Wild card must match the color declared by the person who laid that card down.
  • A player cannot use a Wild Draw Four card if another card in his/her hand can be played.

Those rules leave a lot of possible situations up to interpretation. For example, if the current discard is a Green Five and I have a Green Skip and a Yellow 5, which should I play?

I've made this model a bit simpler by assuming that my players are stupid jackasses.

That is, every time a player has to make a decision about which card to play, s/he will always play the card that causes the most pain to the next player, regardless of his/her own strategic situation. If I can play a Skip or a 7, I'm playing the Skip (and Lord help you if I have a Draw Two).

Also, if a player has no matches, they must draw a card. If that card can play, then the player will play it; otherwise, his/her turn is over and play moved to the next player.

The PlayerTurn Object

UNO is also a bit different than other games we've modeled (Candy Land, Minesweeper) because the actions of a player have a direct consequence on the next player. We will need our players to take that into account, and they must abide by whatever the "attacking" card says they do.

Therefore, in order for the player to know what action s/he must take, we need the player to be aware of what happened on the previous player's turn. To model the actions taken by a player, we will use the PlayerTurn object, which looks like this:

public class PlayerTurn  
{
    public Card Card { get; set; }
    public CardColor DeclaredColor { get; set; } //Used mainly for Wild cards
    public TurnResult Result { get; set; }
}

The TurnResult enum has all the possible situations which arise from a player completing his/her turn. Those values are as follows:

public enum TurnResult  
{
    //Start of game.
    GameStart,

    //Player played a normal number card.
    PlayedCard,

    //Player played a skip card.
    Skip,

    //Player played a draw two card.
    DrawTwo,

    //Player was forced to draw by other player's card.
    Attacked,

    //Player was forced to draw because s/he couldn't match the current discard.
    ForceDraw,

    //Player was forced to draw because s/he couldn't match the current discard, but the drawn card was playable.
    ForceDrawPlay,

    //Player played a regular wild card.
    WildCard,

    //Player played a draw-four wild card.
    WildDrawFour,

    //Player played a reverse card.
    Reversed
}

Each player takes a turn, and the action performed during that turn are represented by the PlayerTurn object. When the next player takes his/her turn, s/he will also receive the PlayerTurn object for the previous player's turn.

Player Order of Operations

With all the assumptions and the PlayerTurn object in place, let's lay out a skeleton for how our stupid jackass players will behave. Here's how each player will act during his/her turn.

  1. If the previous player "attacked" the current player (via a Skip, a Draw Two, or a Draw Four), then the current player must suffer the attack.
  2. If the current player can play an "attacking" card (within the rules), then s/he does so.
  3. If the current player can play a number card, then s/he does so.
  4. If the current player has no matching cards except a Wild card, s/he plays the Wild. The current player will then declare the color to be the one s/he has the most cards of (the declared color is random if the current player has the same number of card in two or more colors).
  5. If the current player has no cards to play, s/he draws a single card from the draw pile. If the drawn card can be played, s/he plays that card.

NOTE: In either step 2 or step 3, if the player has many possible cards to play, s/he will play the one which results in the color s/he has the most of.

All of those steps are pretty straightforward, but let me explain the reasoning behind Step 4. If we have only one card left, and that card is a Wild card, we are guaranteed to discard our final card on our next turn. Therefore, it behooves the players to hold on to Wild cards until the last possible moment.

Now comes the tricky part; how in the world do we model this?

Being Attacked

Let's start with Step 1 in our Player Order of Operations:

Step 1: If the previous player "attacked" the current player (via a Skip, a Draw Two, or a Draw Four), then the current player must suffer the attack.

The interesting thing about being "attacked" is that being attacked always results in the current player not being able to discard a card. This is why a Reverse card is not an attacking card.

So, let's create a method called ProcessAttack(), during which we will create a PlayerTurn object representing what action the current player took during his/her turn (when s/he suffered the attack).

private PlayerTurn ProcessAttack(Card currentDiscard, CardDeck drawPile)  
{
    PlayerTurn turn = new PlayerTurn();
    turn.Result = TurnResult.Attacked;

    //The player after the current player must match the card that attacked the current player; hence we pass those values through to the next PlayerTurn.
    turn.Card = currentDiscard; 
    turn.DeclaredColor = currentDiscard.Color;

    if(currentDiscard.Value == CardValue.Skip)
    {
        Console.WriteLine("Player " + Position.ToString() + " was skipped!");
    }
    else if(currentDiscard.Value == CardValue.DrawTwo)
    {
        Console.WriteLine("Player " + Position.ToString() + " must draw two cards!");
        Hand.AddRange(drawPile.Draw(2));
    }
    else if(currentDiscard.Value == CardValue.DrawFour)
    {
        Console.WriteLine("Player " + Position.ToString() + " must draw four cards!");
        Hand.AddRange(drawPile.Draw(4));
    }

    return turn;
}

On the Offensive

If the current player is not attacked, s/he will attempt to play an attacking card that matches the color or value of the current discard. Let's create several methods which will make the player decide which card to play.

public PlayerTurn PlayTurn(PlayerTurn previousTurn, CardDeck drawPile) { }

private PlayerTurn DrawCard(PlayerTurn previousTurn, CardDeck drawPile) { }

private bool HasMatch(Card card) { }

private bool HasMatch(CardColor color) { }

private PlayerTurn PlayMatchingCard(CardColor color) { }

private PlayerTurn PlayMatchingCard(Card currentDiscard) { }

private CardColor SelectDominantColor() { }  

Notice the overloads for HasMatch() and PlayMatchingCard(). One of the quirks of this model is that, whenever a player plays a Wild card, s/he declares a color to be played; the next discard can only be matched on color, not value. For my model, I decided to make matching color or value vs matching color only two completely separate "thought processes" as it were.

We'll start with a skeleton of the PlayTurn() method, since it will need to call the ProcessAttack() method we defined earlier. Here's an outline of this method:

public PlayerTurn PlayTurn(PlayerTurn previousTurn, CardDeck drawPile)  
{
    PlayerTurn turn = new PlayerTurn();

    //If the current player was attacked
    if (previousTurn.Result == TurnResult.Skip
        || previousTurn.Result == TurnResult.DrawTwo
        || previousTurn.Result == TurnResult.WildDrawFour)
    {
        return ProcessAttack(previousTurn.Card, drawPile);
    }

    //When the current discard is a Wild card
    else if ((previousTurn.Result == TurnResult.WildCard 
                || previousTurn.Result == TurnResult.Attacked 
                || previousTurn.Result == TurnResult.ForceDraw) 
                && previousTurn.Card.Color == CardColor.Wild
                && HasMatch(previousTurn.DeclaredColor))
    {
        turn = PlayMatchingCard(previousTurn.DeclaredColor);
    }

    //When the current discard is a non-wild card
    else if (HasMatch(previousTurn.Card))
    {
        turn = PlayMatchingCard(previousTurn.Card);
    }

    //When the player has no matching cards
    else //Draw a card and see if it can play
    {
        turn = DrawCard(previousTurn, drawPile);
    }

    DisplayTurn(turn);
    return turn;
}

Let's remind ourselves of Player Logic Steps 2, 3, and 4, as well as the pertinent note:

Step 2: If the player can play an "attacking" card (within the rules), then s/he does so.

Step 3: If the player can play a number card, then s/he does so.

Step 4: If the player has no matching cards except a Wild card, s/he plays the Wild. The player will then declare the color to be the one s/he has the most cards of (the color is random if the player has the same number of card in two or more colors).

NOTE: In either step 2 or step 3, if the player has many possible cards to play, s/he will play the one which results in the color s/he has the most of.

With these steps in mind, let's start building the PlayMatchingCard() methods.

Matching a Wild

If the card we need to match is a Wild, we will need to use the property DeclaredColor in the PlayerTurn object. We must play a card of that color.

Here's the code for the Wild card version of PlayMatchingCard() (the logic involved in the method is in the comments):

private PlayerTurn PlayMatchingCard(CardColor color)  
{
    var turn = new PlayerTurn();
    turn.Result = TurnResult.PlayedCard;
    var matching = Hand.Where(x => x.Color == color || x.Color == CardColor.Wild).ToList();

    //We cannot play wild draw four unless there are no other matches.  But if we can play it, we must.
    if (matching.All(x => x.Value == CardValue.DrawFour))
    {
        turn.Card = matching.First();
        turn.DeclaredColor = SelectDominantColor();
        turn.Result = TurnResult.WildCard;
        Hand.Remove(matching.First());

        return turn;
    }

    //Otherwise, we play the card that would cause the most damage to the next player.
    if (matching.Any(x => x.Value == CardValue.DrawTwo))
    {
        turn.Card = matching.First(x => x.Value == CardValue.DrawTwo);
        turn.Result = TurnResult.DrawTwo;
        turn.DeclaredColor = turn.Card.Color;
        Hand.Remove(turn.Card);

        return turn;
    }

    if (matching.Any(x => x.Value == CardValue.Skip))
    {
        turn.Card = matching.First(x => x.Value == CardValue.Skip);
        turn.Result = TurnResult.Skip;
        turn.DeclaredColor = turn.Card.Color;
        Hand.Remove(turn.Card);

        return turn;
    }

    if (matching.Any(x => x.Value == CardValue.Reverse))
    {
        turn.Card = matching.First(x => x.Value == CardValue.Reverse);
        turn.Result = TurnResult.Reversed;
        turn.DeclaredColor = turn.Card.Color;
        Hand.Remove(turn.Card);

        return turn;
    }

    //If we cannot play an "attacking" card, we play any number card
    var matchOnColor = matching.Where(x => x.Color == color);
    if (matchOnColor.Any())
    {
        turn.Card = matchOnColor.First();
        turn.DeclaredColor = turn.Card.Color;
        Hand.Remove(matchOnColor.First());

        return turn;
    }

    //We only play a regular Wild card if we have no other matches
    if (matching.Any(x => x.Value == CardValue.Wild))
    {
        turn.Card = matching.First(x => x.Value == CardValue.Wild);
        turn.DeclaredColor = SelectDominantColor();
        turn.Result = TurnResult.WildCard;
        Hand.Remove(turn.Card);

        return turn;
    }

    //This should never happen
    turn.Result = TurnResult.ForceDraw;
    return turn;
}

Matching a Non-Wild

Now we must consider the situation when the current discard is not a wild card. In my model, the code for this method is very, very similar to the Wild situation, but I couldn't figure out an appropriate way to separate them sanely. Anyway, here's the non-wild version of PlayMatchingCard():

private PlayerTurn PlayMatchingCard(Card currentDiscard)  
{
    var turn = new PlayerTurn();
    turn.Result = TurnResult.PlayedCard;
    var matching = Hand.Where(x => x.Color == currentDiscard.Color || x.Value == currentDiscard.Value || x.Color == CardColor.Wild).ToList();

    //We cannot play wild draw four unless there are no other matches.
    if(matching.All(x => x.Value == CardValue.DrawFour))
    {
        turn.Card = matching.First();
        turn.DeclaredColor = SelectDominantColor();
        turn.Result = TurnResult.WildCard;
        Hand.Remove(matching.First());

        return turn;
    }

    //Otherwise, we play the card that would cause the most damage to the next player.
    if(matching.Any(x=> x.Value == CardValue.DrawTwo))
    {
        turn.Card = matching.First(x => x.Value == CardValue.DrawTwo);
        turn.Result = TurnResult.DrawTwo;
        turn.DeclaredColor = turn.Card.Color;
        Hand.Remove(turn.Card);

        return turn;
    }

    if(matching.Any(x => x.Value == CardValue.Skip))
    {
        turn.Card = matching.First(x => x.Value == CardValue.Skip);
        turn.Result = TurnResult.Skip;
        turn.DeclaredColor = turn.Card.Color;
        Hand.Remove(turn.Card);

        return turn;
    }

    if (matching.Any(x => x.Value == CardValue.Reverse))
    {
        turn.Card = matching.First(x => x.Value == CardValue.Reverse);
        turn.Result = TurnResult.Reversed;
        turn.DeclaredColor = turn.Card.Color;
        Hand.Remove(turn.Card);

        return turn;
    }

    // At this point the player has a choice of sorts
    // Assuming he has a match on color AND a match on value 
    // (with none of the matches being attacking cards), 
    // he can choose which to play.  For this modeling practice, we'll assume 
    // that playing the match with MORE possible matches from his hand 
    // is the better option.

    var matchOnColor = matching.Where(x => x.Color == currentDiscard.Color);
    var matchOnValue = matching.Where(x => x.Value == currentDiscard.Value);
    if(matchOnColor.Any() && matchOnValue.Any())
    {
        var correspondingColor = Hand.Where(x => x.Color == matchOnColor.First().Color);
        var correspondingValue = Hand.Where(x => x.Value == matchOnValue.First().Value);
        if(correspondingColor.Count() >= correspondingValue.Count())
        {
            turn.Card = matchOnColor.First();
            turn.DeclaredColor = turn.Card.Color;
            Hand.Remove(matchOnColor.First());

            return turn;
        }
        else //Match on value
        {
            turn.Card = matchOnValue.First();
            turn.DeclaredColor = turn.Card.Color;
            Hand.Remove(matchOnValue.First());

            return turn;
        }
    }
    else if(matchOnColor.Any()) //Play the match on color
    {
        turn.Card = matchOnColor.First();
        turn.DeclaredColor = turn.Card.Color;
        Hand.Remove(matchOnColor.First());

        return turn;
    }
    else if(matchOnValue.Any()) //Play the match on value
    {
        turn.Card = matchOnValue.First();
        turn.DeclaredColor = turn.Card.Color;
        Hand.Remove(matchOnValue.First());

        return turn;
    }

    //Play regular wilds last.  If a wild becomes our last card, we win on the next turn!
    if (matching.Any(x => x.Value == CardValue.Wild))
    {
        turn.Card = matching.First(x => x.Value == CardValue.Wild);
        turn.DeclaredColor = SelectDominantColor();
        turn.Result = TurnResult.WildCard;
        Hand.Remove(turn.Card);

        return turn;
    }

    //This should never happen
    turn.Result = TurnResult.ForceDraw;
    return turn;
}

Selecting the Dominant Color

In both the Wild and non-Wild versions of this method, we see a call to SelectDominantColor(), which returns the color that appears most often in the current players' hand. Here's that method:

private CardColor SelectDominantColor()  
{
    if (!Hand.Any())
    {
        return CardColor.Wild; //Null case, causes a passthrough in the calling method
    }
    var colors = Hand.GroupBy(x => x.Color).OrderByDescending(x => x.Count());
    return colors.First().First().Color;
}

Drawing a Card

We've now completed implementation Player Logic Steps 2, 3, and 4, so let's move on to Step 5:

Step 5: If the player has no cards to play, s/he draws a single card from the draw pile. If the drawn card can be played, s/he plays that card.

We now need to implement the DrawCard() method defined earlier. This method turns out to be surprisingly simple now that PlayMatchingCard() is already implemented. Here it is:

private PlayerTurn DrawCard(PlayerTurn previousTurn, CardDeck drawPile)  
{
    PlayerTurn turn = new PlayerTurn();
    var drawnCard = drawPile.Draw(1);
    Hand.AddRange(drawnCard);

    if (HasMatch(previousTurn.Card))  //If the drawn card matches the discard, play it
    {
        turn = PlayMatchingCard(previousTurn.Card);
        turn.Result = TurnResult.ForceDrawPlay;
    }
    else
    {
        turn.Result = TurnResult.ForceDraw;
        turn.Card = previousTurn.Card;
    }

    return turn;
}

And with that, there's only one thing left to do: implement the decision tree!

Putting It All Together

Now that the individual player actions are all scripted, the only thing we have left to do is implement a method which will be called by the Game Manager (which we will implement fully in Part 3 of this series) and will make the Player decide what action to take. The method is called PlayTurn() and here it is:

public PlayerTurn PlayTurn(PlayerTurn previousTurn, CardDeck drawPile)  
{
    PlayerTurn turn = new PlayerTurn();
    if (previousTurn.Result == TurnResult.Skip
        || previousTurn.Result == TurnResult.DrawTwo
        || previousTurn.Result == TurnResult.WildDrawFour)
    {
        return ProcessAttack(previousTurn.Card, drawPile);
    }
    else if ((previousTurn.Result == TurnResult.WildCard 
                || previousTurn.Result == TurnResult.Attacked 
                || previousTurn.Result == TurnResult.ForceDraw) 
                && previousTurn.Card.Color == CardColor.Wild
                && HasMatch(previousTurn.DeclaredColor))
    {
        turn = PlayMatchingCard(previousTurn.DeclaredColor);
    }
    else if (HasMatch(previousTurn.Card))
    {
        turn = PlayMatchingCard(previousTurn.Card);
    }
    else //Draw a card and see if it can play
    {
        turn = DrawCard(previousTurn, drawPile);
    }

    DisplayTurn(turn);
    return turn;
}

Summary

Well, would you look at that! We've now completed our Players' behavior (stupid jackasses that they are), and all that's left to do is wire the entire thing together and play some UNO! In the final part of this series, we'll do just that.

Don't forget to check out the GitHub repository for this series.

Happy Coding!