Course:CPSC312-2024/Hanabi

From UBC Wiki

What is the problem?

It's co-op game night in the cabin in the woods, and you've invited a bunch of your friends over to play Hanabi… but, oh my! All of the cards have suddenly gone missing, what a tragedy!

Never fear, there's another way to play and save the day! With the power of a ✨shiny✨ new language (Prolog), and the trusty computer in the corner of the cabin, we can create a pass-and-play implementation of Hanabi, so that you and your friends can enjoy co-op game night together once more!

What is the something extra?

One of the classic features of board games is the little 60 second hour glass that comes with many games, and we didn't want our game to lose that sense of time and stakes! In Hanabi you have 3 Misfires (lives) before the game is over, so adding in an element of a timer is one way to take lives and force the players to act. No more thinking for forever, lollygagging, nor shouting at someone to take their turn (because you're bored). In this cooperative game, if you can't think on the spot, you will lose!

To do this, we implemented a clock system with threading. When a player's turn starts, a new thread is created that will act as a timer, and it prints multiple warnings to the console: 30 seconds left, 10 seconds left, and "time's up". When the player takes their action, our code attempts to kill the thread: if the player ran out of time (thread already ran to competition), then this fails the thread kill, so the player takes a Misfire as a result.

What did we learn from doing this?

The implementation of the "something extra" was actually quite enjoyable! While it was still an implementation of threads and clocks, it was one of the better languages we could've used when it came to the libraries available to do it. The biggest problem came from the lack of Prolog documentation. We would check the wiki for specific functions, and the wiki page quite often just said 'this function exists, here is a really small blurb on what it does', and that was it. We would try and look at examples of a function that has 2 parameters, and could not for the life of us figure out what we were looking for in those 2 parameters. The actual usage of threading and time was fairly happy and intuitive, it was just a matter of figuring out what these functions took as parameters.

Doing basic math in Prolog was funny! In one of our functions, we had to add 5 numbers, so we had to create Count1/2/3/4/5 in our implementation and add them one by one (i.e. Count3 is Number3 + Count2, Count4 is Number4 + Count3...). It was ugly compared to what you would do in other languages, which would just be 1 + 2 + 3...

We also had some trouble handling user input! We initially used the read predicate to get user input, which meant that we had to wrap it in a catch block to handle errors, which ended up getting quite messy, since entering that block would cause our code to jump around in game-breaking ways. We ended up fixing this by using the read_term predicate instead, and setting its syntax error handler to a mode that would soft-fail when given bad input, but allow the user to try again right afterwards. This allowed us to safely reject bad input while not breaking our delicate gameplay sequence!

Along a similar vein, we also had some trouble with back-and-forth recursion in our frontend. At one point in development, we had options that allowed the player to go back to previous menus and choose different options (if they wanted to), but we quickly discovered that a lot of action sequences involving returning to a previous menu broke our gameplay sequence, so we ended up removing that option for simplicity.
While it did mean that players would have less room for error, this actually ended up working out for the better implementation-wise, since it made adding timers to each menu a lot simpler. It also eliminated a lot of complex recursion in our code, making it a lot tidier and nicer to read.

Work division

We all worked on a live share together for most of the whole project: most of the backend gameplay utilities were set up by Nate and Nanjou, the dynamic data and timer were set up by Trevor, and the frontend was done by Trevor and Nanjou. That being said, we all had our hands on everything, and throughout the whole time we had the power of friendship!

Links to code etc.

https://github.students.cs.ubc.ca/tglenn01/CPSC312HanabiGame