Play Pokémon's Safari Zone in R

Screenshot of Rstudio showing only the console pane with green text and background, showing text that describes an encounter with a Pokemon.

An original Nintendo Game Boy playing Pokémon… if you squint.

tl;dr

I created the R package {safar6}, which contains an R6-class object to simulate a simplified, text-based version of the Safari Zone sub-area from Pokémon Blue.

I also made the ‘gamelad’ RStudio theme to mimic a pukey-green original Game Boy. Pair with a blocky monospace font like VT323 for that 8-bit experience.

Kangaskhan you believe it?

Did you know you can play games in R?

I particularly like the text adventures The Secret of Landusia by Peter Prevos and Castle of R by Giora Simchoni

The latter uses object-oriented programming (OOP) for handling game elements, thanks to the {R6} package. So, a room in the castle is an R6-class object with specific fields (variables), like whether the door is $open, and methods (functions) like $openDoor() that can change the door state.

This is interesting because R is better known for being a function- rather than an object-oriented language. You can learn more about OOP in R from Hadley Wickham’s Advanced R book and more about the R6-class from the {R6} website.

A Chansey to learn

I wrote a post about using {R6} to simulate an Automatic Bell Dispenser (an ATM, basically) from Nintendo’s Animal Crossing: New Horizons (2020). Fields include $savings and methods include $withdraw(), for example.

Obviously my next step was try and get to grips with {R6} for games, but I wanted to start small. The original Pokémon1 games were effectively text adventures with some random-number generation and simple calculations going on in the background. Would it be possible to simulate some aspects of it?

Let’s start small. The Safari Zone is a sub-area of the main game and the mechanics here are simpler: you’re only allowed 500 steps, you have to use a special type of ball to capture wild Pokémon (of which you only have 30) and you can’t reduce a wild Pokémon’s health (hit points, HP).

A composite screenshot of the Safari Zone from the original Pokemon game.

‘Would you like to join the hunt?’ Via Bulbapedia

So I went ahead and wrote an R6-class object to mimic the Safari Zone and bundled it in the {safar6} R package.2

A quick Tauros of the game

You can install the package from GitHub. Loading the package provides a reminder of how to play.

remotes::install_github("matt-dray/safar6")

library(safar6)
# {safar6}
# Start game: x <- safari_zone$new()
# Take a step: x$step()

Basically, the package contains an R6-class object SafariZone, which you initialise and assign like x <- safar6::safari_zone$new(). This starts a routine with some text from the game and some interactive elements. Type a value and hit enter when prompted.

x <- safari_zone$new()
# ------------------------
# First, what is your name?
# NEW NAME (1)
# BLUE (2)
# GARY (3)
# JOHN (4)
# ------------------------
# Select 1, 2, 3 or 4:
1
# Your name: 
THEW
# Welcome to the SAFARI ZONE!
# For just P500, you can catch all the
# Pokemon you want in the park!
# Would you like to join the hunt?
# ------------------------
# MONEY: P500
# YES (1) or NO (2)
# ------------------------
# Select 1 or 2: 
1
# That'll be P500 please!
# ------------------------
# MONEY: P0
# ------------------------
# We only use a special POKe BALL here.
# THEW received 30 SAFARI BALLs!
# We'll call you on the PA when you run out of time or SAFARI BALLs!

You can then x$step() through the Safari Zone. This method does a lot of the hard work, since it contains all the logic required for a wild Pokémon encounter. There’s an encounter rate built in, so you’ll likely need to x$step() a number of times before you find a Pokémon.

x$step()
# 499/500

Each step is treated as though you’re walking through the tall grass, which is where you find wild Pokémon. There’s a weighted chance of encountering certain Pokémon at certain levels, but each wild Pokémon also has (hidden) randomised individual variation in its stats (HP, speed, etc) that impact your ability to catch it.

x$step()
# 498/500
x$step()
# 497/500
# Wild VENONAT L22 appeared!
# ------------------------
# BALLx30 (1)     BAIT (2)
# THROW ROCK (3)  RUN (4)
# ------------------------

During an encounter you can throw rocks to raise the catch chance (but you’ll also increase the flee chance) and throw bait to reduce the chance of fleeing (but that’ll also decrease the catch chance). The Pokémon will be angry or eating for one to five turns.

# Wild VENONAT L22 appeared!
# ------------------------
# BALLx30 (1)     BAIT (2)
# THROW ROCK (3)  RUN (4)
# ------------------------
# Select 1, 2, 3 or 4: 
3
# THEW threw a ROCK.
# Wild VENONAT is angry!

You can also throw a ball to attempt a capture. Success is determined by several factors, like the Pokémon’s HP, its level and its catch rate (possibly modified by rocks and bait). It may also run away given factors like its speed.

# THEW threw a ROCK.
# Wild VENONAT is angry!
# ------------------------
# BALLx30 (1)     BAIT (2)
# THROW ROCK (3)  RUN (4)
# ------------------------
# Select 1, 2, 3 or 4: 
1
# THEW used SAFARI BALL!
# Wobble...
# Darn! The POKeMON broke free!

You may want to change your strategy. More rocks, or some bait? While it’s still angry, you could take advantage of its heightened catch rate by throwing another ball.

# Wild VENONAT is angry!
# ------------------------
# BALLx29 (1)     BAIT (2)
# THROW ROCK (3)  RUN (4)
# ------------------------
# Select 1, 2, 3 or 4: 
1
# THEW used SAFARI BALL!
# Wobble... Wobble... Wobble...
# All right!
# VENONAT was caught!

You can choose to give your ‘captured friend’ a nickname.

# ------------------------
# Do you want to give a nickname to VENONAT?
# YES (1) or NO (2)
# ------------------------
# Select 1 or 2:
1
# Nickname: 
Tajiri
# Tajiri was transferred to BILL's PC!

Try to catch as many as you can before you run out of steps or balls. You can x$pause() the game at any point to see your remaining stats and you can check out x$bills_pc to see what you’ve captured3.

x$pause()
# 497/500
# BALLx28
# BILL's PC: 1
x$bills_pc
#   nickname species level
# 1   Tajiri VENONAT    22

When the game is over, you’ll see an endscreen with your results.

# ------------------------
# PA: Ding-dong!
# Time's up!
# PA: Your SAFARI GAME is over!
# Did you get a good haul?
# Come again!
# ------------------------
# Result: 1 transferred to BILL's PC
#   nickname species level
# 1   Tajiri VENONAT    22

The Safari Zone in the original game was pretty tricky. The Pokémon were flighty and it was especially hard to trap rare encounters like Chansey, Pinsir and Scyther.

The most captures I’ve made on a playthrough of {safar6} is three (!), so use that as a yardstick.

Exeggcute-ing the class

I tried to keep things simple, so there’s a number of omissions compared to the original game. For example, there’s no visuals or sounds; I’ve simulated only the ‘Center’ hub area of the Safari Zone; you walk around as though you’re always in tall grass; you can’t fish or use different rod types; you’re restricted to the catch rates and Pokémon identities of the Blue game (not Red or Yellow, which are different).

On the flipside, I tried to maintain some subtle true-to-the-original elements. For example, you’ll be prompted to enter your name; you can nickname your Pokémon; there’s ‘wobble logic’ for deciding how many times the ball should shake before a capture; and the majority of the text is as it appears in the game.

In particular, I’ve tried to keep the various hidden and non-hidden Pokémon stats and calculations true to Pokémon Blue. For example, I built in:

  • original encounter rates, both for the Safari Zone and the wild Pokémon in it
  • wild Pokémon base statistics and calculation of randomised individual values
  • catch rates based on factors like ball type and HP, and any modifications during the encounter
  • tracking of ‘eating’ and ‘anger’ statuses and the effects on catch rates
  • the calculation for whether a wild Pokémon will flee

There’s no guarantee I’ve got these things completely right, but the gameplay appears similar to the original, so I think it’s close enough.

Disen-Tangela-ing game mechanics

Information about game mechanics and values were relatively tricky to come by. The following resources were really important:

  • Bulbapedia is the Bible of Pokémon and hosts various stats and formulae
  • The Cave of Dragonflies has some excellent breakdowns of game mechanics, particularly in capture and Safari Zone logic
  • the Pokémon Slots website is a convenient lookup for base encounter rates for wild Pokémon by area
  • the pret/pokered GitHub repo contains a disassembly of the games, where you can see the raw game mechanics and stats

Don’t Marowak living creatures

Obviously this is for fun and learning. Play at your own risk. Feel free to report any bugs (as in code problems, not bug-type Pokémon) as GitHub issues.

And do not, I repeat, do not throw rocks at animals IRL.


Session info
## ─ Session info ───────────────────────────────────────────────────────────────
##  setting  value                       
##  version  R version 4.0.2 (2020-06-22)
##  os       macOS  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     2021-01-10                  
## 
## ─ Packages ───────────────────────────────────────────────────────────────────
##  package     * version date       lib source                           
##  assertthat    0.2.1   2019-03-21 [1] CRAN (R 4.0.0)                   
##  blogdown      0.21.76 2021-01-05 [1] Github (rstudio/blogdown@f5c7f89)
##  bookdown      0.21    2020-10-13 [1] CRAN (R 4.0.2)                   
##  cli           2.2.0   2020-11-20 [1] CRAN (R 4.0.2)                   
##  crayon        1.3.4   2017-09-16 [1] CRAN (R 4.0.0)                   
##  digest        0.6.27  2020-10-24 [1] CRAN (R 4.0.2)                   
##  evaluate      0.14    2019-05-28 [1] CRAN (R 4.0.0)                   
##  fansi         0.4.1   2020-01-08 [1] CRAN (R 4.0.0)                   
##  glue          1.4.2   2020-08-27 [1] CRAN (R 4.0.2)                   
##  htmltools     0.5.0   2020-06-16 [1] CRAN (R 4.0.2)                   
##  knitr         1.30    2020-09-22 [1] CRAN (R 4.0.2)                   
##  magrittr      2.0.1   2020-11-17 [1] CRAN (R 4.0.2)                   
##  rlang         0.4.10  2020-12-30 [1] CRAN (R 4.0.2)                   
##  rmarkdown     2.6     2020-12-14 [1] CRAN (R 4.0.2)                   
##  safar6      * 0.1.0   2021-01-10 [1] Github (matt-dray/safar6@94907a9)
##  sessioninfo   1.1.1   2018-11-05 [1] CRAN (R 4.0.0)                   
##  stringi       1.5.3   2020-09-09 [1] CRAN (R 4.0.2)                   
##  stringr       1.4.0   2019-02-10 [1] CRAN (R 4.0.0)                   
##  withr         2.3.0   2020-09-22 [1] CRAN (R 4.0.2)                   
##  xfun          0.19    2020-10-30 [1] CRAN (R 4.0.2)                   
##  yaml          2.2.1   2020-02-01 [1] CRAN (R 4.0.0)                   
## 
## [1] /Library/Frameworks/R.framework/Versions/4.0/Resources/library

  1. The first generation of Pokémon games were developed for the Nintendo Game Boy by Game Freak and published by Nintendo. Pokémon as a property is owned by The Pokémon Company.↩︎

  2. You know, like ‘safari’ and ‘R6’ mashed together?↩︎

  3. ‘Bill’s PC’ is the original game’s in-game Pokémon-storage system. Yes, they’re stored on a computer. In particular, Bill’s computer. Don’t think about it too hard.↩︎