Game Development Journal 4: Representing the Flower Cards in Code

One of the things that drives me completely crazy about reading programming books is that I feel like there is a huge base of knowledge that no one ever talks about.

Over the weekend at Indie DevStock, Simon Allardice spoke a bit about how a lot of teaching materials are terrible because they bombard you with a bunch of facts. If you’re watching an instructional video about Swift the presenter will start showing you every single little menu in Xcode because it’s there and by the time you get to code you’re already irreparably lost and don’t really care anymore.

I see this too and have been guilty of it myself. Whenever WWDC comes around, I and other conference speakers obsessively look for whatever is going to be the new hot thing from the new version of iOS that we can pitch a talk on. We latch onto one thing and learn just enough about it that we can present a talk on it. We come up with convoluted reasons to use the new shiny object so that we can use it as an example in our talk. We go in and talk knowledgeably about this new thing and people come to see us and because we know more than they do (which is usually not a whole lot) and think we’re like experts or geniuses or something.

I almost never come out of a talk or a tutorial thinking about how this new API/tool/whatever is going to completely change my workflow. I will think it’s cool and will try to find reasons to use it, but I almost never have an organic reason for doing so.

There is almost always an easier way to do something than the way we choose. We choose overly complex solutions for things because we feel pressure to show off how smart we are. We write unreadable Swift code because having it look too imperative makes us worry we’re going to look like noobs and not be on the cool functional train.

My attempt to save us from ourselves is going to be contain in this series of blog posts. I am going to present part of an issue I need to solve in order to implement my game. I am going to go over several possible solutions and then explain why I chose the solution I did.

Sometimes I will be comparing an implementation in just plain Swift to using a framework provided by Apple. I hope to make all of this available on GitHub.

I would like to open up our conversations in programming from just “What is the new cool thing out” to “How can using this solve my problems?”

Choosing a Starting Point

The first problem is figuring out where to start. My initial inclination is to think about the user interface because that is the thing that most of us see and interact with, but that is the last thing that should be implemented.

I read a book a while back called Systemantics: How Systems Work and Especially How They Fail. At the first startup I worked at, they had implemented several systems to try and facilitate communication among all the employees about deadlines and so forth. We had about a dozen employees at that point, but we had multiple layers of systems. In order to update anything, you had to update it in about five different places. The engineers would look at the issue tracker in the Git repository and the managers would look at the Post-it notes on the SCRUM board. Their solution to this problem was to add another layer of systems on top of everything else.

All of these systems we put into place work against us and make us slower. It’s really important to only implement the most simple system you can that will fulfill your needs. Yes, once you read the size of a place like Apple you must have systems in place or else nothing will get done. But if you’re a small team of people, you don’t need a system as complex as one that Apple needs. You’ll spend all of your time maintaining the system and nothing will get done.

Another aspect of this book talked about how to build a system. This works for application architecture as well as for teams. The best way to implement a system is to build it out. You have to figure out what your most basic component is and get that up and running first. Once that is stable and tested and you feel good about it, then you can build out from there.

Since the UI is not the foundation upon which you should build your application, it is not the right place to start.

The most vital component to my card game are the cards. Having a card object in place and correct is necessary for me to build out the board representation, game logic, and UI.

So I am starting out building my card objects.

Creating My Flower Deck Structures

Next I need to really think about what my card objects need to do and what pieces of information they need to be able to carry and convey.

Godori does not use a traditional Western deck. Western card decks were outlawed in Japan, and so they developed their own deck that is the basis of many card games that originated in Asia. There are a number of differences between Asian flower decks and traditional Western decks:

  • 48 cards instead of 52
  • 12 suits of four cards instead of four suits of 13 cards
  • Not numbered
  • No court cards (King, Queen, etc…)
  • Each card must be one of four types: bright, ribbon, animal, or junk

Traditional decks are somewhat easy because most of the cards are numbered one through ten. In most Western games, court cards count as ten, so it’s relatively easy to assign values to the cards.

Flower cards are slightly trickier. You need to keep track of not only what suit a card is, but what type it is.

The 48 cards in a flower deck have the following composition: five brights, nine animals, ten ribbons, and twenty-four junk cards. Every suit has at least one junk card and at least two special cards. These are pre-determined and you can use a lookup chart to see what suits have what composition of cards, so this is not going to be a random or automated task.

Enums

The first thing that I notice when looking over these requirements is that there are two pieces of information that a card must know about itself: its suit and what type of card it is. Both of these pieces of information have a limited number of options. There are twelve possible suits and only four possible types. This seems like a really good place to create a couple of enums.

enum CardType {
    case Bright
    case Ribbon
    case Animal
    case Junk
}

enum CardSuit {
    case January
    case February
    case March
    case April
    case May
    case June
    case July
    case August
    case September
    case October
    case November
    case December
}

This sets up the way to track what special characteristics a card has that we care about for the game mechanics. Now we need to apply those to our actual Card data structure.

Class vs Struct

This is part of the whole value vs reference debate. Initially, I thought that I needed to make this a class, but I realized I was remembering it wrong.

When you’re dealing with something where there will only be one thing, you use a class. For example, if you were going to describe a camera where there is only going to be one instance of that camera and it would be passed by reference through out the application, it should be a class. But in this case, I am creating multiple instances of my cards that are all going to be passed by value, so it should be a struct.

The struct should always be the default data structure you should use unless you have a really compelling reason to make something a class.

Each card needs to have two values associated with it: a suit and a type.

Knowing all of this, it’s now quite simple to put together the card structure:

struct Card {
    let cardSuit:CardSuit
    let cardType:CardType
    
    init(suit:CardSuit, type:CardType) {
        cardSuit = suit
        cardType = type
    }
}

Now that I have everything that I need to create a card, I now have to figure out how to create a deck of them.

Data Types: Representing A Deck

This is the most complicated part to represent from this initial foray into development. There are a lot of data structures that can contain a bunch of Card instances, but there are questions about what the best one is and additionally, what is the optimal way to populate that data structure with the card representations.

There are several collection types in Swift: Arrays, dictionaries, and sets.

Initially, I thought about making this into an array. That is kind of the default data structure when you have a lot of something that needs to be collected together.

A set would also work as well. Initially, the order of the cards in the deck really doesn’t matter, you just need to have a collection of all the cards. But I still thought there was a better way.

One issue I have with this game is that every card needs a unique identifier. I need to have an easy way to hook up each card to its graphical representation.

I thought about creating a name property for the card in the Card struct, but I realized that I could create this collection as a dictionary. I can assign a string name to each card as a key to access it rather than having to access its name property within the instance.

There are also several cards within the deck that have special rules associated with them. Rather than creating a bunch of optional properties within each card, it’s a lot easier for me conceptualize just using each card’s name.

The point I am trying to make is that there isn’t a right or a wrong answer for how to pick the collection type for the deck. I see many tutorials where people default to an array because that’s the first thing that comes to mind. There is no explanation about how the programmer decided to implement the programming logic and it seems like magic how the person figured it out. I want more people to think through what problem they are trying to solve rather than rote copying of code without any thought or context.

I have also spent a few days thinking through if there is any way to populate the deck using loops and conditionals. I was trying to figure out if there was a way to do this like you would if you were programming FizzBuzz, but the deck was not mathematically generated.

Initially I was going to create a function to generate the deck, but since the cards are not going to change, I realized that making the deck as a constant was a better way to proceed. It’s still a crap load of code, but it only needs to be done once and will not ever need to be changed.

I have, for the time being, set every card manually. I wanted to give each card a unique name and ensure that the cards are created properly.

let deck:[String:Card] = ["JanBright": Card(suit: .January, type: .Bright),
                          "JanRibbon": Card(suit: .January, type: .Ribbon),
                          "JanJunk1": Card(suit: .January, type: .Junk),
                          "JanJunk2": Card(suit: .January, type: .Junk),
                          "FebBird": Card(suit: .February, type: .Animal),
                          "FebRibbon": Card(suit: .February, type: .Ribbon),
                          "FebJunk1": Card(suit: .February, type: .Junk),
                          "FebJunk2": Card(suit: .February, type: .Junk),
                          "MarchBright": Card(suit: .March, type: .Bright),
                          "MarchRibbom": Card(suit: .March, type: .Ribbon),
                          "MarchJunk1": Card(suit: .March, type: .Junk),
                          "MarchJunk2": Card(suit: .March, type: .Junk),
                          "AprilBird": Card(suit: .April, type: .Animal),
                          "AprilRibbon": Card(suit: .April, type: .Ribbon),
                          "AprilJunk1": Card(suit: .April, type: .Junk),
                          "AprilJunk2": Card(suit: .April, type: .Junk),
                          "MayAnimal": Card(suit: .May, type: .Animal),
                          "MayRibbon": Card(suit: .May, type: .Ribbon),
                          "MayJunk1": Card(suit: .May, type: .Junk),
                          "MayJunk2": Card(suit: .May, type: .Junk),
                          "JuneAnimal": Card(suit: .June, type: .Animal),
                          "JuneRibbon": Card(suit: .June, type:.Ribbon),
                          "JuneJunk1": Card(suit: .June, type: .Junk),
                          "JuneJunk2": Card(suit: .June, type: .Junk),
                          "JulyAnimal": Card(suit: .July, type: .Animal),
                          "JulyRibbon": Card(suit: .July, type: .Ribbon),
                          "JulyJunk1": Card(suit: .July, type: .Junk),
                          "JulyJunk2": Card(suit: .July, type: .Junk),
                          "AugBright": Card(suit: .August, type: .Bright),
                          "AugBird": Card(suit: .August, type: .Animal),
                          "AugJunk1": Card(suit: .August, type: .Junk),
                          "AugJunk2": Card(suit: .August, type: .Junk),
                          "SepAnimal": Card(suit: .September, type: .Animal),
                          "SepRibbon": Card(suit: .September, type: .Ribbon),
                          "SepJunk1": Card(suit: .September, type: .Junk),
                          "SepJunk2": Card(suit: .September, type: .Junk),
                          "OctAnimal": Card(suit: .October, type: .Animal),
                          "OctRibbon": Card(suit: .October, type: .Ribbon),
                          "OctJunk1": Card(suit: .October, type: .Junk),
                          "OctJunk2": Card(suit: .October, type: .Junk),
                          "NovBright": Card(suit: .November, type: .Bright),
                          "NovAnimal": Card(suit: .November, type: .Animal),
                          "NovRibbon": Card(suit: .November, type: .Ribbon),
                          "NovJunkDouble": Card(suit: .November, type: .Junk),
                          "DecBright": Card(suit: .December, type:.Bright),
                          "DecJunkDouble": Card(suit: .December, type: .Junk),
                          "DecJunk1": Card(suit: .December, type: .Junk),
                          "DecJunk2": Card(suit: .December, type: .Junk),]

There are a few special rules in Godori where certain cards, such as the ones with birds on them, are worth a special bonus if you collect all of them. I did not want to put a bunch of optional properties on the card class for instances that affect only a few cards. I am tagging those cards in their name to try and handle those edge cases in the programming logic rather than hard coding it as a parameter. There are other edge cases I have not dealt with yet and I will go back and deal with that later.

This is part of the problem I see when people talk about the promise of Swift. Swift is really good at removing lines of code, but only in situations where you have certain kinds of programming logic.

If you are parsing a file or processing some kind of information or a few other situations, Swift’s pattern matching and protocols is fantastic and allows you to remove a lot of lines of code.

However, if you are interacting with the Cocoa frameworks or setting up something with a lot of customization, such as the user interface, there are a limited number of things you can pull out and automate.

Since the type of card in each suit does not have a pattern that I can detect, I don’t think I can automate this with loops and conditionals in any way that will be clear later.

I am planning to go through this project and refactor it periodically, so it’s possible I could figure out a better way to automate the deck generation than to manually create each card and add it to the collection. Until then, I am entering each card.

Wrap Up

I have pushed this playground to GitHub. I know that right now it doesn’t look like much, but big things come from small beginnings. This is the foundation upon which everything else will be built. I fully expect to expand on this and refactor it as I figure out better ways to do things.

Things don’t have to be perfect when you first create them. You should be going back and looking over code you wrote before and looking to see if you can make it better based on what you’ve learned since you wrote it.

I hope to post at least one blog post a week on what I have done on this project. I hope to make steady progress on this application and for people to be able to see it come together.

Thanks for reading.