tl;dr
Play an interactive version of ‘Potato’—a one-page halfling-themed role-playing game (RPG) by Oliver Darkshire (Twitter, Patreon)—in your R console with the {potato} package.
Potato?
I’ve recently put together a GitHub repo to collect together a bunch of neat games that you can play. The twist? They were built using R.
Yes, R: ‘a FrEe SoFtWaRe EnViRoNmEnT fOr StAtIsTiCaL cOmPuTiNg AnD gRaPhIcS’.
I think R is best suited to either text-based user-input games on the R console, or via a more dedicated interface, like Shiny.1
In this vein, Oliver Darkshire wrote an excellent ‘one-page role-playing game’ called Potato that seems ripe for plucking (well, I guess you ‘pull’ potatoes?) into an R implementation. A simple text interface; updating and tracking variables; clear win conditions. The basic desire to avoid action and simply tend to vegetables.
So… {potato}.
Potato!
You can install the {potato} package from GitHub thanks to {remotes}:
install.packages("remotes") # if not yet installed
remotes::install_github("matt-dray/potato")
You start a game with:2
potato::potato()
--- POTATO ---
A (one-page) RPG by Oliver Darkshire (@deathbybadger)
These and more at https://www.patreon.com/deathbybadger
You are a halfling, just trying to exist.
Meanwhile, the dark lord rampages across the world.
You do not care about this. You are trying to farm potatoes.
Because what could a halfling possibly do about it anyway?
Keep rolling until DESTINY, POTATO or ORC reach 10/10.
- DESTINY: 0/10
- POTATO: 0/10
- ORC: 0/10
- PAY: 1 POTATO to remove 1 ORC
Press [ENTER] to roll...
The console will prompt you for input as you play. It’s basically luck-based die rolls, though you will have the chance to intervene with an option to hurl a certain number of precious potatoes at an orc to make it clear off.
You win when POTATO
reaches 10
. You lose when ORC
reaches 10
. You also… ‘don’t lose’… if DESTINY
reaches 10
.
Please see the one-page RPG that David put together and/or support him on Patreon if you like it or any of the other hilarious one-page RPGs that he’s made.
I cannot stress enough that this is his work and all I’ve done is put it into an obscure format that literally three people might look at for a laugh.
Potato…
I could just leave it there, but I think the interesting thing for R users are the various little methods required to make the ‘game’ function.
To display text to the user in the console, we can use cat()
or message()
. I kinda prefer message()
because the user has more control over it in general, like suppressMessages()
(which does what you think it does).
message("Hello world!")
# Hello world!
There’s a subtlety in presentation too, which is that the two functions return text in different colours.
The game loop itself runs inside a repeat
, which is maybe uncommon for some R users. We’re mostly used to for
or while
loops for iteration with a known set of things to iterate over, whereas repeat
will keep going until we specify a break
.
repeat {
if (keep_going) {
do_something()
}
if (!keep_going) {
break
}
}
You can imagine a scenario where keep_going
is set to TRUE
and some actions happen as a result; and that if it becomes FALSE
then the game loop ends. In {potato}, we make sure to first print the current values of DESTINY
, POTATO
and ORC
so the user sees them before the game continues or ends.
What are DESTINY
, POTATO
and ORC
? Before we initiate that repeat
loop we can specify a bunch of starting values for some important scoring variables. Stylistically, it makes sense to use ALL CAPS for these (that’s how they were written in the original game, after all), but there’s also an old-school rule-of-thumb to specify variables this way in R code so you can more easily spot them in your code.
DESTINY <- 0L
POTATO <- 0L
ORC <- 0L
in addition, we clearly need user input to decide what to do during the game. Most of the time, a user’s hand is forced and they need to roll. But sometimes they have the choice to remove an orc at the cost of one or more potatoes (depending on how the die falls).
This is a logical variable that we can keep track of, i.e. can the user pay (TRUE
or FALSE
)?
can_pay <- FALSE
if (COST <= POTATO) {
can_pay <- TRUE
} else if (COST > POTATO) {
can_pay <- FALSE
}
If the cost to yeet an orc is equal-to or less-than the number of potatoes, we can elect to make the payment. This is expressed in the options provided to the user on the command line.
Given the can_pay
value, the user will get the option to either roll the die (FALSE
):
event <- readline(
"Press [ENTER] to roll... "
)
Or choose to roll the die or make the payment (TRUE
):
event <- readline(
"Press [ENTER] to roll or [p] to pay 1 POTATO to remove 1 ORC... "
)
Both of which require user input that results in a value stored in the event
object. Note that hitting Enter results in an empty string (""
).
Die-roll values pass through a series of if
statements that are activated based on the number rolled. So if you roll 1
or 2
, you’re In the garden...
; if 3 or 4, you’ll get A knock on the door...
; else the potato cost per orc-yeet increases by 1).
A second roll is made automatically when in the garden or when a knock is heard. Here’s what happens if a 1
is rolled when in the garden:
if (rolled_garden == 1L) {
message(
paste(
rolled_garden_msg,
"You happily root about all day in your garden."
)
)
message("- Result: +1 POTATO")
POTATO <- POTATO + 1L
}
Excellent, the POTATO
variable counter is increased by 1 in this case and confirmed to the user in a message()
. The latest DESTINY
, POTATO
and ORC
scores are then printed back to the user at the start of the next repeat
loop.
And then you just… keep potatoing.
Potato.
Once again, you can visit Oliver Darkshire on Twitter as @deathbybadger and support him on Patreon.
You can find the source code for {potato} on GitHub. Issues and pull requests welcome. Just make sure you can afford the charge of one potato to submit.
Session info
## ─ Session info ───────────────────────────────────────────────────────────────
## setting value
## version R version 4.2.0 (2022-04-22)
## os macOS Big Sur/Monterey 10.16
## system x86_64, darwin17.0
## ui X11
## language (EN)
## collate en_GB.UTF-8
## ctype en_GB.UTF-8
## tz Europe/London
## date 2022-12-12
## pandoc 2.18 @ /Applications/RStudio.app/Contents/MacOS/quarto/bin/tools/ (via rmarkdown)
##
## ─ Packages ───────────────────────────────────────────────────────────────────
## package * version date (UTC) lib source
## blogdown 1.9 2022-03-28 [1] CRAN (R 4.2.0)
## bookdown 0.26 2022-04-15 [1] CRAN (R 4.2.0)
## bslib 0.3.1 2021-10-06 [1] CRAN (R 4.2.0)
## cli 3.3.0 2022-04-25 [1] CRAN (R 4.2.0)
## digest 0.6.29 2021-12-01 [1] CRAN (R 4.2.0)
## evaluate 0.15 2022-02-18 [1] CRAN (R 4.2.0)
## fastmap 1.1.0 2021-01-25 [1] CRAN (R 4.2.0)
## htmltools 0.5.2 2021-08-25 [1] CRAN (R 4.2.0)
## jquerylib 0.1.4 2021-04-26 [1] CRAN (R 4.2.0)
## jsonlite 1.8.0 2022-02-22 [1] CRAN (R 4.2.0)
## knitr 1.39 2022-04-26 [1] CRAN (R 4.2.0)
## magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.2.0)
## potato * 0.1.0 2022-09-13 [1] Github (matt-dray/potato@2eb37ea)
## R6 2.5.1 2021-08-19 [1] CRAN (R 4.2.0)
## rlang 1.0.2 2022-03-04 [1] CRAN (R 4.2.0)
## rmarkdown 2.14 2022-04-25 [1] CRAN (R 4.2.0)
## rstudioapi 0.14 2022-08-22 [1] CRAN (R 4.2.0)
## sass 0.4.1 2022-03-23 [1] CRAN (R 4.2.0)
## sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.2.0)
## stringi 1.7.6 2021-11-29 [1] CRAN (R 4.2.0)
## stringr 1.4.0 2019-02-10 [1] CRAN (R 4.2.0)
## xfun 0.30 2022-03-02 [1] CRAN (R 4.2.0)
## yaml 2.3.5 2022-02-21 [1] CRAN (R 4.2.0)
##
## [1] /Library/Frameworks/R.framework/Versions/4.2/Resources/library
##
## ──────────────────────────────────────────────────────────────────────────────
Yeah, but have you seen the mind-boggling extent to which Mike Cheng (AKA mikefc, AKA coolbutuseless, AKA R legend) has gone to turn R into a proper game engine?↩︎
Potayto, potahto, let’s call the whole thing off.↩︎