Christmas Coding Challenge – Uno

During this Christmas break I decided to have a go at implementing the card game Uno in Python. It’s a fun and simple game for all ages, and the rules are easy to pick up and follow – but there’s quite a challenge in implementing the rules in a program! I spent a few hours over a few evenings working on it and now have a completed version, and wanted to share some of the interesting bits of the code.

Uno

Uno is played with a bespoke card deck (not a normal deck of playing cards). Uno cards have a colour and a card type (a number or a symbol). There are four regular colour cards, and there are special cards which are black.

Each player is dealt a hand of 7 cards, and they take turns to play a card, trying to become the first player to get rid of all their cards. A player can only play a card if it has the same colour or card type as the last played card. If they cannot play a regular card but have a special black card, this can be played instead. Some cards have consequences, such as the next player having to pick up extra cards, players skipping turns or reversing order of play.

Unit tests

I used a test-driven development (TDD) approach to further my progress in building up the game from simple isolated card logic all the way to simulating gameplay in a multi-player game. I used pytest for its ability to verify exceptions were raised, but otherwise simply used Python’s built-in assert statement.

An Uno Card

I started by writing some tests to check errors were thrown if you tried to create an invalid card, then proceeded to creating valid cards. The card was implemented as an UnoCard class, whose objects stored a colour and a card type.

Card rules

As well as validation on init, I added a method to determine whether a given card was playable on another card. The card playing rules are quite straightforward: you can place a card o

The deck

I started building up an UnoGame class, where a deck of UnoCard instances was created. The deck needed to contain a complete set of Uno cards, which I determined from this image on Wikipedia:

So it turns out there are digit cards 0-9 (and then 1-9 repeated) for each of the four colours, and two of each special card in each colour. There are also four of each of the two types of black cards.

I started by creating lists defining the colours and card types, and initially created the deck by combining these lists in a number of for-loops. However, I later refactored this to use product, repeat and chain from itertools in the standard library, which made it much neater:

color_cards = product(COLORS, COLOR_CARD_TYPES)
black_cards = product(repeat('black', 4), BLACK_CARD_TYPES)
all_cards = chain(color_cards, black_cards)
deck = [UnoCard(color, card_type) for color, card_type in all_cards]

The UnoGame’s deck was then simply a list containing UnoCard objects.

Gameplay

In order for there to be a game, there needed to be players. I made UnoGame initialise with a given number of players. This meant a new UnoPlayer class. Each player would be dealt a hand of 7 cards from the shuffled deck. But first, I wrote tests and made sure that an UnoPlayer object could be created with 7 cards, and couldn’t be created without them.

Reverse

In order for gameplay to take place, I needed to work out how I would designate a player as being the currently active player (whose turn it is). My first thought was itertools.cycle – a handy tool for infinitely iterating over an iterable object like a list, and just starting back at the beginning of the list once all items have been exhausted. However, Uno has a reverse card, meaning the order of play can be reversed at any moment.

I thought it through, and decided to design a new ReversibleCycle class to implement something like a cycle which could be reversed, as specified by the Uno rules. It took me quite a while to figure this out exactly, and get all my tests passing, but I was quite happy with the implementation I ended up with. A tricky part was the edge-case of what happens if the first card drawn (not played by a player – just the starting card) is a reverse card. According to the rules, play will start with the player to the right of what would have been the first player (i.e. the last player). Otherwise, in normal circumstances, the first player will play first, naturally.

I implemented the class as an iterator, so you could loop over it or manually next() it. Example usage of my ReversibleCycle class:

>>> rc = ReversibleCycle(range(3))
>>> next(rc)
0
>>> next(rc)
1
>>> rc.reverse()
>>> next(rc)
0
>>> next(rc)
2

I wrote tests of the ReversibleCycle in an isolated fashion. It works with any iterable, and I simply created an attribute within my UnoGame class which referred to an instance of ReversibleCycle, where the iterable was a list of UnoPlayer objects. I used a property to make it easy to look up which was the current player at any given moment.

More gameplay

Back to testing the gameplay, now I had a working model for cycling through players, with the ability to reverse order of play once a reverse card was played, I tested that players could play cards only when it was their turn, and that they could only play valid cards. I then tested that players playing cards would cause the correct consequences (e.g. the next player picks up 2, the new current player is the right one if a skip/reverse/etc. card is played, and so on).

The winner

I began testing gameplay by allowing the deck not to be shuffled, so that I had a predictable, testable order of cards. Looking back, I should have used a random seed instead (thanks for the tip, Dave). I took this game all the way to a player being designated the winner and the game ending.

Automated play

I then proceeded to write some code, similar to my test code for the gameplay, which would automate play for all players, and end at some point declaring one of the players the winner. There was nothing clever about the way the players played – no strategy – I just determined whether they had any playable cards, and made them play the first playable one in their hand.

AI

The last thing I did was to take the automated play code and embed it inside an AIUnoGame class, allowing for a single player to be controlled by the user using text input on request. The playable player would be shown their hand each round, and asked which card they would like to play. All actions of other players would be displayed (e.g. “Player 2 plays Red 5”, “Player 4 picks up 2”), and the winner would be declared as before. Again, nothing smart in the AI, but that’s something I may add later.

GUI?

I had wondered when I started whether it would be feasible for me to create this as a GUI – a real visual playable game. I obviously wanted to start from a text-based interface, and get the logic of the game down before I worried about graphics, but again, it’s something I would like to look at next. I wonder if PyGame Zero or guizero would be suitable. Watch this space!

The code

You can find my code on GitHub.

XP Manchester XL – Release Early, Release Often

Yesterday was XP Manchester‘s XL event – a Saturday coding session of pairing, katas and a team bot tournament hosted at the MadLab.

I managed to persuade Kris, a friend I used to work with, to come along – it’s his first experience of this sort of thing, so I’m really glad he got introduced to the world of XP and TDD. Ash was there, Mike brought a friend along who’s also new to agile, and there was a decent crowd of about 23 attendees which was great to say we were expecting snow that afternoon. The day was led by Mark with *ahem* assistance from Jim.

We started by pairing up and solving a decimal to Roman numerals problem using TDD. I did mine with Kris to get him up to speed with what TDD was all about. We used a rather amateur if/elif construct in Python to solve for numbers up to about 20, but it was a good way to get in to the mood and to show Kris the methodology of thinking up a feature, writing a test, writing the code to implement the feature, testing it, refactoring and moving on to the next feature. The emphasis of the day was on ‘release early, release often’ which is a really good attitude for software development. As Facebook founder Mark Zuckerberg said in his letter to the shareholders:

Hackers try to build the best services over the long term by quickly releasing and learning from smaller iterations rather than trying to get everything right all at once. To support this, we have built a testing framework that at any given time can try out thousands of versions of Facebook. We have the words “Done is better than perfect” painted on our walls to remind ourselves to always keep shipping.

The next session was the opposite conversion, Roman Numerals to Decimal, which I managed to do quite well in a pair with Mike’s friend, a PHP dev with no experience of TDD. We did the kata in Python and solved it by looping through the characters from right to left, looking up the value of each character and adding it to the total. If a character of lower value than the previous character was found, its value was subtracted rather than added. I thought this solution to be suitable for any numeral using characters the program knew about. Within the set time we had tested up to 20 but it was failing on 14, which made no sense to me. I tried to debug but found no problems so I left it to inspect later. I got chance to take a look at it later on and with a few handy tips from Michael (assertEquals more useful than assertTrue) I managed to spot the mistake causing it to evaluate 14 wrong. I was right to think the solution was good for all, though, as using the value characters up to M (1000) and adding some more tests for higher numbers in the hundreds and thousands, all tests passed.

The third pair session was the now infamous Ordered Jobs Kata by Martin. I did this kata in Ruby with Michael. Ruby’s not one of my preferred languages like PHP or Python but I do like the chance to work in it for exercises like this (and I’m sure I’ll be doing more of this as I’m now living with a Ruby dev). I’ve had a go at the Ordered Jobs kata once before but didn’t complete it, so it was interesting to have a fresh attempt. We got off to a really slow start when we hit the first sign of difficulty (introduction of dependencies), and took quite a while working out a strategy for holding the input data and making sure dependencies were completed before the other jobs, and realised that although our first solution was technically sound, that the test failed because of the order we asserted in the test. We re-jigged our code to make the test pass and moved on. Speaking to Ash about this later, he pointed out that we’d have been better to specify several smaller pieces of the test separately, i.e. number of jobs in input must match number of jobs in output; all jobs in the input needed to be in the output once; and each of the dependencies individually needed to be listed before their respective dependants. We continued through the next stages and progressed rather well on to stage 6 which we just managed to pass by the end of the session. It turned out we’d managed to get the furthest in the room with that one, despite our slow start.

After lunch we had a bot battle of rock-paper-scissors-waterbomb-dynamite wherein we had to program a ‘player’ to play against a bot on a server built by Jim. It needed to be in C# so each of the .NET developers in the room split off and we joined them to make teams of 3. Kris and I teamed up and our strategy took a while to get going as we had many problems whenever our bot got loaded on to the server we seemed to get disqualified or just fail to take its move. However once all buggy issues resolved we devised a method of logging the opponent’s name and each of its moves. This allowed us to determine what had gone on in each round, what strategies other players (or the pre-programmed bots) were taking, where we won and where we lost. We made a few subtle changes to our attack and added some different approaches when we faced particular known opponents. A fun afternoon which resulted in us coming second to Mike’s team in the final round (although the bots won overall). Interestingly, Mike’s team was the only team running proper tests…

After we left the Madlab we had an extensive drinking session which involved geeky hilarity and many OHs on twitter. Huge thanks to Mark and Jim for making the event happen – and to the Madlab for hosting it.