Course:CPSC312-2024/foryUno

From UBC Wiki

Authors: Nate Sternberg, Nanjou Olsen, Trevor Glennon

What is the problem?

For many years, people (*clears throat* Hasbro) have disagreed about about the rules of Uno, causing the same people to play a different game every time they play! But never fear, we are here to settle the debate! With Haskell, we have the power to change the world: by making an Uno implementation where you can play the game against some number of bot players the way YOU want to play.

2peBc9w.png
(original tweet by @realUNOgame deleted, image retrieved from r/TrashTaste)

Why foryUno matters

Do you love cabin-in-the-woods-core? Do you dream of disconnecting from the world with nothing but your coffee, a nice blanket, and your GHCi compiler? Do you have no internet and no friends, but an urge to play your favourite childhood game of Uno, reminiscing on youthful innocence before the 9-5 grind slowly crushed your soul?

Well... foryUno is a personalized game of Uno, just for you! You get to choose the rules that you want to play with, and can play whenever and wherever. There's nothing needed other than yourself and your computer!

What is the "something extra?"

We added a rule editor! The Uno Twitter account insists on one very particular ruleset, so we needed to seize the means of production. As a result, our game offers multiple configuration options:

  • Choosing how many decks to have in play for ultimate +4 chaos
  • The infamous 0 and 7 rules to swap hands with an opponent, or rotate everyone's hands around!
    (imagine me doing the game conference and pose on a black background)
  • +2 and +4 stacking to build massive card draw chains and ruin someone's day

In foryUno, the tools are yours, and the limits are endless.

What did we learn from doing this?

It was quite easy to create a solid foundation and code structure for out game using Haskell. All 3 of us really like having pretty code, so this made the project experience really fun after setting everything up. Using Haskell allowed us to build a game state that intuitively made sense, and made our game flow logic quite effective.

Our main struggles in implementing this game came from trying to handle things Haskell wasn't necessarily designed for: time and randomness. We also struggled a lot with technical debt, due to how our development cycle played out.

  • Time: We tried to implemented the ability to have a timed Uno call similar to a reaction time mini-game, but importing the appropriate time modules, as well as preventing time calls from being lazily evaluated, was frustrating. The Time systems available to us were deprecated and impossible to import without ✨witchcraft Linux powers✨, and Haskell's innate Time systems were all lazily evaluating the time, resulting in net times equaling zero.
    As a result, after multiple hours of debugging, we ended up cutting this feature from our final game. If we were to revisit this project, however, we would give this another college try.
  • Randomness: In our game state, we have a deck that we wanted to be randomly shuffled. While getting a randomization algorithm to work wasn't the most impossible task, setting the seed for it was awkward, since randomization in Haskell frequently comes from IO, which itself can be a bit of a hassle to work with. At one point during development, we considered doing something like asking the player "What is your favourite number above 5000? :)" as a way of setting a seed. However, that felt unnatural, so we also tried to follow the common seed setting convention of using the current time! But I just spent a paragraph explaining how that went… :)
    In the end, we ended up getting the IO randomization to work with the System.Random package! We initialized the randomizer in one of our starting IO methods, then passed the randomizer directly into our state, so we didn't have to access IO every time we needed it.
    As a side effect: you might be required to install the random package in order for your code to compile. If you have cabal installed, this can be done by typing cabal install --lib random in a terminal window.
  • Technical debt: The more we implemented in our project, the bigger our game's State grew. It essentially acted as a large chunk of memory that was kept in a field, and would be passed along to various functions in our game.
    Our Aura and Rules types are prime examples of things that significantly affected our State type: they weren't part of our initial specification of State, but as we started implementing more parts of our game, we realized that we needed them in order to accurately track the game state and handle the game's logic. So, when we added them, we had to refactor every function that used a State in order to accommodate these additions to the State type.

We also had some other cool ideas that we didn't get to implement, such as mic-activated Uno calls, colouring console text to match the colours of cards, and using Unicode art to display cards. While these would have been nice features to add, Haskell isn't really built to handle these types of things on its own, and we also didn't ended up having the time to implement them.

Work division

We had a 1-4 hour call every day (starting from 8 days before today ^_^), where we worked on the game code together using a live-shared code editor.

Links to code

CPSC312UnoGame on GitHub Enterprise

Post Script:

Trevor: I wrote this with little sleep and was cackling the whole time. Made sure to include an accurate description and specific examples we ran into, I hope you had fun and laughed ^_^

Nanjou: …and then I edited this to make sure things flowed nicely, and to add in a few other things that weren't already mentioned. I hope you enjoyed our jokes and had fun too!