Magic in R with {scryr}

Magic in R with {scryr}

2022-01-07 · This page is made of stone

scryr is a lightweight wrapper around Scryfall, an amazing (and free!) Magic: The Gathering API. With scryr you can ingest card data as tidy data frames, allowing for frictionless integration with tidyverse pipelines.

Currently there are 2 main endpoints: cards and sets. The other 4 endpoints (bulk_data, catalogs, rulings, and symbols) are mostly auxiliary to the main ones.

1# Install scryr
2install.packages("scryr")
3
4# Start scryr and helpers
5library(dplyr)
6library(scryr)

Cards #

This endpoints is, by far, the most complex. Make sure to read scry_cards()' full documentation before diving in! For the curious, more relevant information about cards can be found in vignette("syntax") (Query Syntax) and vignette("layouts") (Layouts and Faces).

The most important function here is scry_cards(). It returns a data frame of card data given a query:

 1# Legendary vampires
 2vampires <- scry_cards("t:vampire t:legend")
 3
 4# There are many, many columns
 5print(vampires)
 6#> # A tibble: 48 × 70
 7#>    id         name     set   lang  colors color_identity mana_cost   cmc
 8#>    <chr>      <chr>    <chr> <chr> <list> <list>         <chr>     <dbl>
 9#>  1 913dd06f-… Anje Fa… c19   en    <chr … <chr [2]>      {1}{B}{R}     3
10#>  2 1bfac4ab-… Anje, M… vow   en    <chr … <chr [2]>      {2}{B}{R}     4
11#>  3 b8630ae1-… Anowon,… voc   en    <chr … <chr [1]>      {3}{B}{B}     5
12#>  4 bca84fc4-… Anowon,… znc   en    <chr … <chr [2]>      {2}{U}{B}     4
13#>  5 e811f37a-… Arvad t… dom   en    <chr … <chr [2]>      {3}{W}{B}     5
14#>  6 213ad4ba-… Ascenda… hop   en    <chr … <chr [1]>      {4}{B}{B}     6
15#>  7 e5fb44d7-… Baron S… me1   en    <chr … <chr [1]>      {5}{B}{B…     8
16#>  8 487e843f-… Crovax … tpr   en    <chr … <chr [1]>      {2}{B}{B}     4
17#>  9 2c58ce5e-… Drana, … c17   en    <chr … <chr [1]>      {3}{B}{B}     5
18#> 10 31d0c37f-… Drana, … jmp   en    <chr … <chr [1]>      {1}{B}{B}     3
19#> # … with 38 more rows, and 62 more variables: oracle_text <chr>,
20#> #   power <chr>, toughness <chr>, type_line <chr>, edhrec_rank <int>,
21#> #   keywords <list>, layout <chr>, legalities <list>, oversized <lgl>,
22#> #   reserved <lgl>, oracle_id <chr>, mtgo_id <int>,
23#> #   multiverse_ids <list>, tcgplayer_id <int>, cardmarket_id <int>,
24#> #   uri <chr>, scryfall_uri <chr>, rulings_uri <chr>,
25#> #   prints_search_uri <chr>, artist <chr>, artist_ids <list>, …

Note that many columns are list-columns with deeply nested information inside. This is a result of Scryfall’s data model and is the reason why scryr needs tibbles to work. But don’t be alarmed! It’s all pretty consistent.

 1# Get Anje's related cards
 2vampires %>%
 3  filter(name == "Anje, Maid of Dishonor") %>%
 4  pull(all_parts)
 5#> [[1]]
 6#> # A tibble: 2 × 6
 7#>   object       id         component  name     type_line   uri
 8#>   <chr>        <chr>      <chr>      <chr>    <chr>       <chr>
 9#> 1 related_card 1bfac4ab-… combo_pie… Anje, M… Legendary … https://api.s…
10#> 2 related_card a6f374bc-… token      Blood    Token Arti… https://api.s…
11
12# Get Anje's color identity
13vampires %>%
14  filter(name == "Anje Falkenrath") %>%
15  pull(color_identity)
16#> [[1]]
17#> [1] "B" "R"

There are also “singular” functions, that is, functions that return one card instead of many. They are scry_card() and its siblings, all of them methods that find a card given some sort of identifier.

 1# Using an ID
 2scry_card("913dd06f-ed2f-4128-9c9d-9cd0d8a55425")$name
 3#> [1] "Anje Falkenrath"
 4
 5# Using a name
 6scry_card_name("Anje Falkenrath")$name
 7#> [1] "Anje Falkenrath"
 8
 9# Using a collector number and a set
10scry_card_number(37, "c19")$name
11#> [1] "Anje Falkenrath"
12
13# Just get a random vampire commander
14scry_card_random("t:vampire t:legend")$name
15#> [1] "Vito, Thorn of the Dusk Rose"

If you’re unsure of exactly what card you’re looking for, don’t worry. Scryfall also has an endpoint that tries to autocomplete the name of a card and scryr makes it available so that you don’t have to ever leave R to look for a card.

1# There she is
2autocomplete_name("falken")[12]
3#> [1] "Anje Falkenrath"

Sets #

The other main endpoint retrieves information about sets. There are also many list-columns but, again, they are all handled consistently; following in the footsteps of cards, sets also has a “plural” function and a “singular” function. Note that scry_cards() is the only “plural” method that can filter results with its q argument.

 1# Get all sets
 2scry_sets()
 3#> # A tibble: 721 × 19
 4#>    id     code  name  uri   scryfall_uri search_uri released_at set_type
 5#>    <chr>  <chr> <chr> <chr> <chr>        <chr>      <date>      <chr>
 6#>  1 a6012… tunf  Unfi… http… https://scr… https://a… 2022-04-01  token
 7#>  2 b314f… unf   Unfi… http… https://scr… https://a… 2022-04-01  funny
 8#>  3 b11b2… pw22  Wiza… http… https://scr… https://a… 2022-03-05  promo
 9#>  4 5bd03… tnec  Neon… http… https://scr… https://a… 2022-02-18  token
10#>  5 8bb11… tneo  Kami… http… https://scr… https://a… 2022-02-18  token
11#>  6 5b4d9… nec   Neon… http… https://scr… https://a… 2022-02-18  command…
12#>  7 59a20… neo   Kami… http… https://scr… https://a… 2022-02-18  expansi…
13#>  8 78a7f… cc2   Comm… http… https://scr… https://a… 2022-01-28  command…
14#>  9 5c163… dbl   Inni… http… https://scr… https://a… 2022-01-28  draft_i…
15#> 10 8a673… y22   Alch… http… https://scr… https://a… 2021-12-09  expansi…
16#> # … with 711 more rows, and 11 more variables: card_count <int>,
17#> #   parent_set_code <chr>, digital <lgl>, nonfoil_only <lgl>,
18#> #   foil_only <lgl>, icon_svg_uri <chr>, tcgplayer_id <int>,
19#> #   mtgo_code <chr>, arena_code <chr>, block_code <chr>, block <chr>
20
21# Get a single set with an ID
22scry_set("vow")
23#> # A tibble: 1 × 19
24#>   id    code  name  mtgo_code arena_code tcgplayer_id uri   scryfall_uri
25#>   <chr> <chr> <chr> <chr>     <chr>             <int> <chr> <chr>
26#> 1 8144… vow   Inni… vow       vow                2862 http… https://scr…
27#> # … with 11 more variables: search_uri <chr>, released_at <date>,
28#> #   set_type <chr>, card_count <int>, printed_size <int>,
29#> #   digital <lgl>, nonfoil_only <lgl>, foil_only <lgl>,
30#> #   block_code <chr>, block <chr>, icon_svg_uri <chr>

Other Endpoints #

All other endpoints return way less information than the two above. Here is a short demonstration of what else you can do with the rest of scryr:

 1# Get information from a catalog
 2head(scry_catalog("keyword-actions"))
 3#> [1] "Meld"        "Bolster"     "Clash"       "Fateseal"
 4#> [5] "Manifest"    "Monstrosity"
 5
 6# Get rulings for a card
 7scry_ruling("913dd06f-ed2f-4128-9c9d-9cd0d8a55425")
 8#> # A tibble: 1 × 4
 9#>   oracle_id                            source published_at comment
10#>   <chr>                                <chr>  <date>       <chr>
11#> 1 4dab6a96-4376-4aea-983d-406167993214 wotc   2019-08-23   If you disca…
12
13# Get information about symbols
14scry_symbols()
15#> # A tibble: 34 × 11
16#>    symbol colors   cmc loose_variant english           gatherer_alterna…
17#>    <chr>  <list> <dbl> <chr>         <chr>             <list>
18#>  1 {X}    <NULL>   0   X             X generic mana    <chr [2]>
19#>  2 {Y}    <NULL>   0   Y             Y generic mana    <NULL>
20#>  3 {Z}    <NULL>   0   Z             Z generic mana    <NULL>
21#>  4 {0}    <NULL>   0   0             zero mana         <chr [1]>
22#>  5 {½}    <NULL>   0.5 ½             one-half generic… <chr [1]>
23#>  6 {1}    <NULL>   1   1             one generic mana  <chr [1]>
24#>  7 {2}    <NULL>   2   2             two generic mana  <chr [1]>
25#>  8 {3}    <NULL>   3   3             three generic ma… <chr [1]>
26#>  9 {4}    <NULL>   4   4             four generic mana <chr [1]>
27#> 10 {5}    <NULL>   5   5             five generic mana <chr [1]>
28#> # … with 24 more rows, and 5 more variables: transposable <lgl>,
29#> #   represents_mana <lgl>, appears_in_mana_costs <lgl>, funny <lgl>,
30#> #   svg_uri <chr>
31
32# Parse mana costs
33parse_cost("2g2")$cost
34#> [1] "{4}{G}"
35
36# Get names of bulk files
37scry_bulk_files()$name
38#> [1] "Oracle Cards"   "Unique Artwork" "Default Cards"  "All Cards"
39#> [5] "Rulings"
40
41# Download (and parse) bulk rulings
42scry_bulk_file("Rulings")
43#> # A tibble: 44,486 × 4
44#>    oracle_id                            source published_at comment
45#>    <chr>                                <chr>  <date>       <chr>
46#>  1 0004ebd0-dfd6-4276-b4a6-de0003e94237 wotc   2004-10-04   If there ar…
47#>  2 0007c283-5b7a-4c00-9ca1-b455c8dff8c3 wotc   2019-08-23   The "comman…
48#>  3 0007c283-5b7a-4c00-9ca1-b455c8dff8c3 wotc   2019-08-23   Certain car…
49#>  4 0007c283-5b7a-4c00-9ca1-b455c8dff8c3 wotc   2019-08-23   If your com…
50#>  5 000e5d65-96c3-498b-bd01-72b1a1991850 wotc   2004-10-04   The target …
51#>  6 0012bc78-e69d-4a67-a302-e5fe0dfd4407 wotc   2019-05-03   A land norm…
52#>  7 00187de2-bc48-4137-97d8-a9a0fafc76c1 wotc   2019-01-25   You can alw…
53#>  8 00187de2-bc48-4137-97d8-a9a0fafc76c1 wotc   2019-01-25   Pteramander…
54#>  9 00187de2-bc48-4137-97d8-a9a0fafc76c1 wotc   2019-01-25   If a creatu…
55#> 10 001c6369-df13-427d-89df-718d5c09f382 wotc   2009-05-01   Vedalken He…
56#> # … with 44,476 more rows