Course:CPSC312-2024/Randomized Choose Your Own Adventure

From UBC Wiki

What is the problem?

We are creating a randomized version of the classic game choose your own adventure. This game will produce adventure scenarios (in our case, a set of initial scenarios) based on changing probabilities, and randomized chance. The probability of each outcome will depend on your choices, therefore, there is a high probability of outcomes that are a logical consequence of your actions, but there exists small chances of getting totally random, unconnected outcomes. Depending on the choices that the player makes, they will add a certain number of points (each time they make a choice) which will in turn affect the end result that they get.


Something extra

  • Randomized, weighted choices

For our “something extra”, we explored random number generation, and created a randomized algorithm in Haskell, that took in a list and a list of weights for each element in the list, and returned a random element in the list based on the weights (probabilities) given.

At first, we tried to search for Haskell libraries that could do this, but some of these libraries were very outdated, and others were a small part of a much larger library, targeted towards much more complicated uses compared to simply providing functionality for randomization. Because of this, we ended up writing our own algorithm, which taught us a lot about Haskell’s most standard randomized packages like random and MonadRandom.

This algorithm was used in our game to make certain logical outcomes more likely than the completely unrelated ones. We wanted to allow for a player to have a chance of ending up in any random scenario, even when it doesn’t make sense, since we thought this would be funny and allow for surprises. However, in order for the storyline to still mostly make sense, we needed to give the logical outcomes a significantly higher weight than the non-logical ones. Currently, on each turn, the player has a 68% chance of getting a scenario that logically follows their last choice action.


What did we learn from doing this?

We learned that in order to generate random numbers in Haskell using a global generator, you must use IO. Otherwise, you have to use your own random number generator, which does not work well, since your own generator is typically mkStdGen seed, which will return you the same number each time. There are other ways to get around this for working without IO, but these require more workarounds such as passing in a different generator each time you need a random number, and also include unsafe operations, such as using unsafePerformIO. Using IO worked fine, since our program runs in IO, but I can see how working without IO for random numbers would be really difficult and time consuming to do.

Otherwise, we found that since our game was basically a set of scenes/choices, functional programming was useful for progressing through the different choices (we used recursion to keep track of points).  However we did have to be mindful of the arguments of certain functions, depending on what and when we wanted to pass things into each function (i.e. what we wanted to keep track of as our game progressed).

We also looked into creating a TUI, using the library brick, but ran into troubles with installation. We found it really difficult to work with the documentation, because in a lot of cases there were no examples, and when there were, a lot of the demos using brick were outdated, and would not run with the latest version of brick. We learned that since Haskell is not as widely used as some other programming languages, finding good examples and tutorials online was more difficult.

We found that working with random numbers in Haskell was more difficult compared to other programming languages, but Haskell worked really well for implementing “Choose Your Own Adventure,” since each player choice would follow a logical sequential action, which would correspond to another function. This worked really well since Haskell consists of functions, and because all the functions were pure, we didn’t have to worry about mutations, and were more confident that we wrote safe code.


Possible Future extensions (to our game specifically):

  • Adding more interaction (i.e. inventory) that could affect the state of the game
  • Having more random storylines/choices (i.e. more than the 10 we have) or having more intricate storylines (i.e. more related choices that make sense)


Work division

N/A


Links to code etc.

https://github.com/yangchristina/project1-rcyoa