library("DiagrammeR") #mermaid and other GraphViz tools
library("kableExtra") #quickly make presentable tables
library("tidyverse")  #general suite of data management tools

Overview

In this project, I wish to make a flowchart of the missions in the Cyberpunk 2077 video game. There will be some spoilers for the game.

Sources

Design choices:

Previous Example

In a previous venture, I made a mermaid graph from the diagrammeR package that describes how to make a “complete breakfast” in Stardew Valley.

mermaid("
graph LR

A{chicken} --> B(egg) 
B --> C[fried egg]

D{cow} --> E(milk)

F(potato) --> G[hashbrowns]
H(oil) --> G

B --> J[pancakes]
I(wheat flour) --> J

C --> K[complete breakfast]
J --> K
E --> K
G --> K
")

Here I wanted to test out whether or not I could simply define the nodes first (with their shapes) before defining the edges.

mermaid("
graph LR

A{chicken} 
B(egg) 
C[fried egg]
D{cow}
E(milk)
F(potato)
G[hashbrowns]
H(oil)
I(wheat flour)
J[pancakes]
K[complete breakfast]

A --> B
B --> C

D --> E

F --> G
H --> G

B --> J
I --> J

C --> K
J --> K
E --> K
G --> K
")

Data

I copied the information from the PowerPyx walkthrough and used the Eurogamer mission list—along with some of my own design choices—to create a spreadlist of the missions along with each mission’s prerequisites.

mission_df <- read_csv("cyberpunk_missions.csv")
## 
## -- Column specification --------------------------------------------------------
## cols(
##   mission = col_character(),
##   prerequisites = col_character(),
##   isMainMission = col_logical()
## )
head(mission_df)
## # A tibble: 6 x 3
##   mission                prerequisites                           isMainMission
##   <chr>                  <chr>                                   <lgl>        
## 1 The Streetkid          <NA>                                    TRUE         
## 2 The Nomad              <NA>                                    TRUE         
## 3 The Corpo-Rat          <NA>                                    TRUE         
## 4 Practice Makes Perfect The Streetkid, The Nomad, The Corpo-Rat TRUE         
## 5 The Rescue             Practice Makes Perfect                  TRUE         
## 6 The Ripperdoc          The Rescue                              TRUE

Node Identification

Here I wish to create simple names to identify the nodes for later use in clean code for GraphViz.

mission_numbers <- seq(1:nrow(mission_df))
node_id <- paste0("node", mission_numbers)

mission_df <- cbind(mission_df, node_id)

The Main Story

For a slightly smaller data set, we will practice the code and computations on a subset of the data: namely the main missions in Cyberpunk 2077.

mission_df %>%
  filter(isMainMission) %>%
  select(node_id, mission, prerequisites) %>%
  kbl() %>%
  kable_styling()
node_id mission prerequisites
node1 The Streetkid NA
node2 The Nomad NA
node3 The Corpo-Rat NA
node4 Practice Makes Perfect The Streetkid, The Nomad, The Corpo-Rat
node5 The Rescue Practice Makes Perfect
node6 The Ripperdoc The Rescue
node7 The Ride The Ripperdoc
node8 The Pickup The Ride
node9 The Information The Ride
node10 The Heist The Pickup, The Information
node11 Love Like Fire The Heist
node12 Playing For Time Love Like Fire
node13 Automatic Love Playing For Time
node14 The Space In Between Automatic Love
node15 Disasterpiece The Space In Between
node16 Double Life Disasterpiece, Love Rollercoaster
node17 Down On The Street Playing For Time
node18 Gimme Danger Down On The Street
node19 M’Ap Tann Pelen Double Life
node20 I Walk The Line M’Ap Tann Pelen
node21 Never Fade Away I Walk The Line
node22 Transmission Never Fade Away
node23 Ghost Town Playing For Time
node24 Lightning Breaks Ghost Town
node25 Life During Wartime Lightning Breaks
node26 Play It Safe Gimme Danger, Life During Wartime
node27 Search and Destroy Play It Safe
node28 Tapeworm Search and Destroy
node29 Nocturne Op55N1 Tapeworm, Queen Of The Highway, Blistering Love
node30 Last Caress Nocturne Op55N1
node31 We Gotta Life Together Nocturne Op55N1, Queen Of The Highway
node32 For Whom the Bell Tolls Nocturne Op55N1, Blistering Love
node33 Totalimmortal Last Caress
node34 Forward to Death We Gotta Life Together
node35 Changes For Whom the Bell Tolls
node36 Where is My Mind Totalimmortal
node37 Belly of the Beast Forward to Death
node38 Knockin’ on Heaven’s Door Changes
node49 Blistering Love Chippin<U+0092> In
node54 Chippin<U+0092> In Tapeworm
node80 I<U+0092>ll Fly Away Riders on the Storm
node85 Love Rollercoaster Playing For Time
node95 Queen Of The Highway I<U+0092>ll Fly Away, With a Little Help From My Friends
node98 Riders on the Storm Life During Wartime
node130 With a Little Help From My Friends Riders on the Storm

Mermaid Graph

Algorithm:

  • start a character string with the type of graph (e.g. LR for “left to right”)
  • add a line for each node (along with node shape)
  • add a line for each edge
main_story_df <- mission_df %>% filter(isMainMission)

# initialization
graph_viz_code <- "graph LR\n"

# create nodes
for(i in 1:nrow(main_story_df)){
  this_line <- paste0( main_story_df$node_id[i],
        "[",
        main_story_df$mission[i],
        "]")
  graph_viz_code <- paste(graph_viz_code, this_line, sep = "\n")
}

# create edges
graph_viz_code <- paste(graph_viz_code, " ", sep = "\n") #vertical space in output
for(i in 1:nrow(main_story_df)){
  if(!is.na(main_story_df$prerequisites[i])){
    these_prerequisites <- str_trim( str_split(main_story_df$prerequisites[i], ",")[[1]] )
    num_prerequisites   <- length(these_prerequisites)
    
    for(j in 1:num_prerequisites){
      from_node <- main_story_df$node_id[ which( main_story_df$mission == these_prerequisites[j] ) ]
      to_node   <- main_story_df$node_id[ which( main_story_df$mission == main_story_df$mission[i] ) ]
      this_edge <- paste(from_node, "-->", to_node)
      graph_viz_code <- paste(graph_viz_code, this_edge, sep = "\n")
    }
  }
  
}

write_lines(graph_viz_code, "main_story.txt")

For now, I simply copy-and-pasted the contents of that text file into the mermaid function below.

mermaid("graph LR

node1[The Streetkid]
node2[The Nomad]
node3[The Corpo-Rat]
node4[Practice Makes Perfect]
node5[The Rescue]
node6[The Ripperdoc]
node7[The Ride]
node8[The Pickup]
node9[The Information]
node10[The Heist]
node11[Love Like Fire]
node12[Playing For Time]
node13[Automatic Love]
node14[The Space In Between]
node15[Disasterpiece]
node16[Double Life]
node17[Down On The Street]
node18[Gimme Danger]
node19[M'Ap Tann Pelen]
node20[I Walk The Line]
node21[Never Fade Away]
node22[Transmission]
node23[Ghost Town]
node24[Lightning Breaks]
node25[Life During Wartime]
node26[Play It Safe]
node27[Search and Destroy]
node28[Tapeworm]
node29[Nocturne Op55N1]
node30[Last Caress]
node31[We Gotta Life Together]
node32[For Whom the Bell Tolls]
node33[Totalimmortal]
node34[Forward to Death]
node35[Changes]
node36[Where is My Mind]
node37[Belly of the Beast]
node38[Knockin' on Heaven's Door]
node49[Blistering Love]
node54[Chippin’ In]
node80[I’ll Fly Away]
node85[Love Rollercoaster]
node95[Queen Of The Highway]
node98[Riders on the Storm]
node130[With a Little Help From My Friends]
 
node1 --> node4
node2 --> node4
node3 --> node4
node4 --> node5
node5 --> node6
node6 --> node7
node7 --> node8
node7 --> node9
node8 --> node10
node9 --> node10
node10 --> node11
node11 --> node12
node12 --> node13
node13 --> node14
node14 --> node15
node15 --> node16
node85 --> node16
node12 --> node17
node17 --> node18
node16 --> node19
node19 --> node20
node20 --> node21
node21 --> node22
node12 --> node23
node23 --> node24
node24 --> node25
node18 --> node26
node25 --> node26
node26 --> node27
node27 --> node28
node28 --> node29
node95 --> node29
node49 --> node29
node29 --> node30
node29 --> node31
node95 --> node31
node29 --> node32
node49 --> node32
node30 --> node33
node31 --> node34
node32 --> node35
node33 --> node36
node34 --> node37
node35 --> node38
node54 --> node49
node28 --> node54
node98 --> node80
node12 --> node85
node80 --> node95
node130 --> node95
node25 --> node98
node98 --> node130
")

[incomplete as the edges in this larger example were not made?]