Wednesday, March 8, 2017

Using Functional Recursion To Create A Self-Playing Version Of 6 Nimmt!

This experiment seeks to fly in the face of the single responsibility principle.  Not because I should, but because I can.  Call me Icarus, if you wish, but my wings are molded from stronger material than wax.  ANYWAYS, nothing in this article is indicative of good coding practices, or is even particularly elegant or efficient in its design.  I do think it gives some interesting perspective on the value recursive functions bring to the programmatic table however, so let's use that as my defense mechanism against the pundits.

What you are about to see is the entire game of 6 Nimmt!, a wonderfully German card game designed by Wolfgang Kramer, defined within a single function.  Set up, tear down, and all of the mechanics will be entirely contained within one function.  Furthermore, unlike my previous posts this one actually runs!  But first we're gonna need some classes and structs and what have you to get us started.

Let's start with the most rudimentary element of any card game, the cards:

Here we have a struct with three properties: number, bulls, and playedBy.  Number is the numeric value of the card, bulls are how many points the card is worth (points are BAD in this game!), and playedBy will help us keep track of which player uses the card in the game.

Next we need a deck to store these cards:

Pretty simple.  An array to store our cards and a function to generate our deck and shuffle it up.  Each card gets assigned a value between 1 and 104, and playedBy is nil for now.  Bulls are a computed property so we don't have to worry about that one at all.

Now we need some players to deal these cards to!

Our Player class has three properties: name, hand, and score.  All pretty self-explanatory.  We also got a neat little function for getting a Player their starting hand in there.

Lastly, we need a class for our game itself.

An array of Players, an array of Card arrays (representing the table where we're playing the game), a roundCounter to keep track of which round of the game we're in, and a simple bool to make sure we don't keep repeatedly setting up a game we're already playing.

Great, we're good to go.  If you don't know how to play 6 Nimmt! already the following function will make very little sense to you.  Too bad, brush up on your European card game history, bro.

Here's what we start with.  A pitiful, disgusting, empty, useless function.  Let's cram it with some functionality.

Our function starts innocently enough with a conditional checking if the game needs to be set up.  If it does, it creates a deck, five players, deals them ten cards each, and starts off each of the four card rows that our game is actually played on.  It ends by telling our game that it doesn't need to be set up anymore, remember we're going to be going through this game over and over again so we don't want to set up a new game every time a new round begins.

Take note that Deck.createDeck() and player.createHand() are the only external function calls in the entire game.  I decided to include those as methods because otherwise this screen shot looked like complete shit.

Next we create an empty array for all the cards players will be playing this round.  Note that it's totally safe to start it as empty because we want a brand new array for each round of the game we go through.  After that each player randomly chooses a card from their hand to play (proper A.I. is for another article, dude), then the cards are sorted from lowest to highest.  This is done because in 6 Nimmt! players take their turns in ascending card value order.

Now, we must find out which row each player's card will be placed in.

We start by calculating the difference between the player's card and the last card in each row.  If this creates a negative number we are setting the difference to a number higher than the highest possible value difference between cards to make the our conditionals much easier to implement.  

Now, to understand the following game logic, keep in mind the following rules of 6 Nimmt!
  1. A player's card value must be higher than the value of the rightmost card in the row they are placing it.
  2. If their card value is higher than the value of the rightmost card in multiple rows they must place it next to the card which has the least difference.
  3. If the row a card must go into already has five cards in it, the player scores all the Bulls in that row and replaces the row with their card.  Remember, players don't want Bulls!
  4. If their card value is lower than the rightmost card values in all four rows they can choose which row they want to score and replace with their card.
Cool, so we already have our player card and rightmost card value differences so lets start playing the game!

This is what happens if a player's card value is lower than all four rows rightmost card value.  The  player scores the row worth the least amount of Bulls and replaces it with their card.

These four conditional statements check to see which row the player's card should go in, then to see if that row already has five cards in it.  If it does, the player must score the row and replace it with their card.  If it doesn't, the player simply adds their card to the row. 

That's actually the entire game!  We're already done.  Not too crazy, right?  The only thing left to do is check to see who won:

This block of code only triggers if we just completed the tenth and final round of the game.  It sorts the players by their scores, prints some stuff to the console, and determines who the winner is.  After that it wipes clean all of our structs and class properties to prepare for another game.  There's only one small thing left to do, and that's make this bad boy recursive!

BAM, son.  We are done.  I could've made the game automatically play another game after finishing the clean up process, but infinite recursion is a terrifying thing and not to be trifled with.  Therefore the only thing left to do is call this functions as many times as we can handle and play 6 Nimmt! over and over and over again.  

Wow, what a journey.  What a world we live in where such great people can meet and bond over dozens of games of 6 Nimmt! each lasting about a second.  I am truly grateful that I was able to make such a meeting of minds happen inside magical the world of Swift.

Until next time!

No comments:

Post a Comment