library("DiagrammeR") #mermaid and other GraphViz tools
library("kableExtra") #quickly make presentable tables
library("tidyverse") #general suite of data management tools
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:
mermaid
graph for now (may take on node color-coding and edge customization at a later time)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
")
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
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)
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 |
Algorithm:
LR
for “left to right”)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?]