Get all the ranked trick-takers into a data frame.

trick_df <- run_search(list('advsearch' = '1', 
                            'range[numvoters][min]'='30',
                            'nosubtypes[]'='boardgameexpansion',
                            'propertyids[]'='2009'))
[1] 3
[1] 1
[1] 2
[1] 3
trick_df

Now get XML for all these IDs in a single page.

tricks_xml <- GET('https://boardgamegeek.com',
                     accept_xml(),
                     path = '/xmlapi2/thing',
                     query = list('id' = paste(trick_df$id, collapse = ','),
                                  'stats' = '1'))

Then start picking out the properties we are interested in.

items <- content(tricks_xml) %>% 
  xml_find_all('item')
attrs <- c('yearpublished',
           'minplayers',
           'maxplayers',
           'playingtime',
           'minplaytime',
           'maxplaytime',
           'minage',
           'statistics/ratings/usersrated',
           'statistics/ratings/average',
           'statistics/ratings/bayesaverage',
           'statistics/ratings/stddev',
           'statistics/ratings/owned',
           'statistics/ratings/numweights',
           'statistics/ratings/averageweight',
           'statistics/ratings/ranks/rank[@name="boardgame"]'
           )
trick_data <- map(attrs, ~items %>% 
                    xml_find_all(paste0("//",.x)) %>% 
                    xml_attr('value') %>% 
                    as.numeric()) %>% 
  set_names(str_replace(attrs, 'statistics/ratings/','')) %>% 
  bind_cols(id = trick_df$id, name = trick_df$ordtitle, .) %>%
  rename(rank = `ranks/rank[@name="boardgame"]`)
NAs introduced by coercion

Add some more complex properties

# list columns
# links (already written this for Knizia networks)
add_linkcol <- function(data, link_type){
  xpath <- paste0(".//link[@type=\'boardgame",link_type,"\']")
  data[[link_type]] <- map(items, ~xml_find_all(.x, xpath) %>% 
                              xml_attr('value'))
  
  data
}
link_types <- c('category','mechanic','family','expansion','designer','artist','publisher')
for(type in link_types){
  trick_data <- add_linkcol(trick_data, type)
}
# poll for best number of players (already written this at home)
poll_list <- items %>% xml_find_all('poll[@name="suggested_numplayers"]')
get_poll_df <- function(poll){
    
  numplayers <- poll %>% 
    xml_find_all('.//results') %>%
    xml_attr('numplayers')
      
  quality <- poll %>%
    xml_find_all('.//results/result') %>%
    xml_attr('value')
      
  votes <- poll %>%
    xml_find_all('.//results/result') %>%
    xml_attr('numvotes') %>%
    as.integer()
  
  data_frame(numplayers = rep(numplayers, each = 3), quality, votes)
}
trick_data$poll_votes <- poll_list %>%
    xml_attr('totalvotes') %>%
    as.integer()
trick_data$poll <- map(poll_list, get_poll_df)    

Let’s have a look at most common mechanisms.

trick_data %>% 
  unnest(mechanic) %>% 
  count(mechanic) %>% 
  arrange(desc(n))
package ‘bindrcpp’ was built under R version 3.3.3

And designers:

trick_data %>% 
  unnest(designer) %>% 
  count(designer) %>%
  filter(n > 1) %>%
  arrange(desc(n))

And publishers:

trick_data %>% 
  unnest(publisher) %>% 
  count(publisher) %>%
  filter(n > 5) %>%
  arrange(desc(n))

And artists:

trick_data %>% 
  unnest(artist) %>% 
  count(artist) %>%
  filter(n > 1) %>%
  arrange(desc(n))

And category:

trick_data %>% 
  unnest(category) %>% 
  count(category) %>%
  filter(n > 5) %>%
  arrange(desc(n))

And family:

trick_data %>% 
  unnest(family) %>% 
  count(family) %>%
  filter(n > 1) %>%
  arrange(desc(n))

simple plots - year

trick_data %>% filter(yearpublished > 1980) %>% ggplot(aes(x = yearpublished)) + geom_bar()

minimum players

trick_data %>% ggplot(aes(x = minplayers)) + geom_bar()

maximum players

trick_data %>% filter(maxplayers < 12) %>% ggplot(aes(x = maxplayers)) + geom_bar()

Best with…

best_data <- trick_data %>%
  filter(poll_votes >= 5) %>%
  mutate(poll_freq = map(poll, ~group_by(.,numplayers) %>%
                           mutate(freq = votes / sum(votes)) %>%
                           ungroup()),
         best = map(poll_freq, ~filter(.,quality == 'Best') %>% 
                          filter(freq > 0.5) %>% 
                          pull(numplayers) %>% as.integer()),
         nbest = map_int(best, length))
best_data %>%  
  filter(nbest == 1) %>%
  mutate(best = as.integer(best)) %>%
  arrange(best, rank) %>%
  select(name, best)

Range of best with:

best_data %>%  
  count(nbest)

Best with (single count):

best_data %>%  
  filter(nbest == 1) %>%
  mutate(best = as.integer(best)) %>% 
  count(best)
best_data %>% 
  unnest(best) %>%
  count(best) 
best_data %>% 
  unnest(best) %>%
  filter(best == 5) %>%
  arrange(rank) %>%
  select(name, best) 

Trickster nominees:

trick_data %>% 
  filter(yearpublished < 2009, 
                      map_lgl(designer, ~ length(.) > 0),
                      map_lgl(designer, ~ !('(Uncredited)' %in% .)),
                      map_lgl(family, ~ !('Climbing Games' %in% .)),
                      !is.na(rank)) %>% 
  arrange(desc(average)) %>%
  select(name, rank) 

By year:

trick_data %>% 
  filter(map_lgl(designer, ~ length(.) > 0),
         map_lgl(designer, ~ !('(Uncredited)' %in% .)),
         map_lgl(family, ~ !('Climbing Games' %in% .)),
         !is.na(rank)) %>%
  group_by(yearpublished) %>%
  top_n(-1, rank) %>%
  arrange(yearpublished) %>%
  select(yearpublished, name)
LS0tDQp0aXRsZTogIlRyaWNrdGFrZXIgYW5hbHlzaXMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoaHR0cikNCmxpYnJhcnkocnZlc3QpDQpsaWJyYXJ5KHN0cmluZ3IpDQoNCnJ1bl9zZWFyY2ggPC0gZnVuY3Rpb24ocXVlcnkpew0KICAjZ2V0IEhUTUwgcGFnZSBvZiBmaXJzdCBwYWdlIG9mIHNlYXJjaA0KICBzZWFyY2hfaHRtbCA8LSBHRVQoJ2h0dHBzOi8vYm9hcmRnYW1lZ2Vlay5jb20nLA0KICAgICAgICAgICAgICAgICAgICAgYWNjZXB0X3htbCgpLCANCiAgICAgICAgICAgICAgICAgICAgIHBhdGggPSAnL3NlYXJjaC9ib2FyZGdhbWUvcGFnZS8xJywNCiAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5ID0gcXVlcnkpDQoNCiAgI2dldCBudW1iZXIgb2YgcGFnZXMNCiAgbnBhZ2VzIDwtIHNlYXJjaF9odG1sICU+JQ0KICAgIHJlYWRfaHRtbCAlPiUNCiAgICBodG1sX25vZGVzKCcuZnIgYScpICU+JQ0KICAgIGxlbmd0aCgpDQoNCiAgaWYobnBhZ2VzID09IDApeyAjIG9ubHkgb25lIHBhZ2Ugc28gbm8gbGlua3MNCiAgICBucGFnZXMgPC0gMQ0KICB9IGVsc2UgaWYobnBhZ2VzID09IDYpeyAjIGNvbGxhcHNlZCBsaW5rIGxpc3Qgc28gbmVlZCB0byBleHRyYWN0IGxhc3Qgb25lDQogICAgbnBhZ2VzIDwtIHNlYXJjaF9odG1sICU+JQ0KICAgICAgcmVhZF9odG1sICU+JQ0KICAgICAgaHRtbF9ub2RlcygnLmZyIGEnKSAlPiUNCiAgICAgIGxhc3QgJT4lDQogICAgICBodG1sX2F0dHIoJ2hyZWYnKSAlPiUNCiAgICAgIHN0cl9yZXBsYWNlKCcvc2VhcmNoL2JvYXJkZ2FtZS9wYWdlLycsICcnKSAlPiUNCiAgICAgIHN0cl9yZXBsYWNlKCdcXD8uKicsICcnKQ0KICB9DQogIHByaW50KG5wYWdlcykNCiAgI2dldCBvbmUgcGFnZSBvZiBqc29uIHNlYXJjaCByZXN1bHRzDQogIGdldF9qc29uX3BhZ2UgPC0gZnVuY3Rpb24ocGFnZW51bSwgcXVlcnkpew0KICAgIFN5cy5zbGVlcCgyKQ0KICAgIHByaW50KHBhZ2VudW0pDQogICAgcGFnZSA8LSBHRVQoJ2h0dHBzOi8vYm9hcmRnYW1lZ2Vlay5jb20nLA0KICAgICAgICBhY2NlcHRfanNvbigpLCANCiAgICAgICAgcGF0aCA9IHBhc3RlMCgnL3NlYXJjaC9ib2FyZGdhbWUvcGFnZS8nLCBwYWdlbnVtKSwNCiAgICAgICAgcXVlcnkgPSBxdWVyeSkNCg0KICAgICNtYWtlIHN1cmUgaXQgd29ya2VkDQogICAgaWYoaGVhZGVycyhwYWdlKSRgY29udGVudC10eXBlYCA9PSAiYXBwbGljYXRpb24vanNvbjsgY2hhcnNldD11dGYtOCIpew0KICAgICAgcGFnZQ0KICAgIH0gZWxzZSB7DQogICAgICBnZXRfanNvbl9wYWdlKHBhZ2VudW0sIHF1ZXJ5KQ0KICAgIH0NCiAgfQ0KICANCiAgI2dldCBhbGwgcGFnZXMgb2YganNvbiBzZWFyY2ggcmVzdWx0cyBhbmQgdGhlbiBjb252ZXJ0IHRvIGRmDQogIHNlYXJjaF9qc29uIDwtIG1hcCgxOm5wYWdlcywgZ2V0X2pzb25fcGFnZSwgcXVlcnkpDQogIG1hcF9kZihzZWFyY2hfanNvbiwgY29udGVudCkgJT4lIG1hcF9kZihiaW5kX3Jvd3MpDQp9DQpgYGANCg0KR2V0IGFsbCB0aGUgcmFua2VkIHRyaWNrLXRha2VycyBpbnRvIGEgZGF0YSBmcmFtZS4NCg0KYGBge3J9DQp0cmlja19kZiA8LSBydW5fc2VhcmNoKGxpc3QoJ2FkdnNlYXJjaCcgPSAnMScsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICdyYW5nZVtudW12b3RlcnNdW21pbl0nPSczMCcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ25vc3VidHlwZXNbXSc9J2JvYXJkZ2FtZWV4cGFuc2lvbicsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3Byb3BlcnR5aWRzW10nPScyMDA5JykpDQoNCg0KdHJpY2tfZGYNCg0KYGBgDQoNCk5vdyBnZXQgWE1MIGZvciBhbGwgdGhlc2UgSURzIGluIGEgc2luZ2xlIHBhZ2UuDQoNCmBgYHtyfQ0KdHJpY2tzX3htbCA8LSBHRVQoJ2h0dHBzOi8vYm9hcmRnYW1lZ2Vlay5jb20nLA0KICAgICAgICAgICAgICAgICAgICAgYWNjZXB0X3htbCgpLA0KICAgICAgICAgICAgICAgICAgICAgcGF0aCA9ICcveG1sYXBpMi90aGluZycsDQogICAgICAgICAgICAgICAgICAgICBxdWVyeSA9IGxpc3QoJ2lkJyA9IHBhc3RlKHRyaWNrX2RmJGlkLCBjb2xsYXBzZSA9ICcsJyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3N0YXRzJyA9ICcxJykpDQoNCmBgYA0KDQpUaGVuIHN0YXJ0IHBpY2tpbmcgb3V0IHRoZSBwcm9wZXJ0aWVzIHdlIGFyZSBpbnRlcmVzdGVkIGluLg0KDQpgYGB7cn0NCg0KaXRlbXMgPC0gY29udGVudCh0cmlja3NfeG1sKSAlPiUgDQogIHhtbF9maW5kX2FsbCgnaXRlbScpDQoNCmF0dHJzIDwtIGMoJ3llYXJwdWJsaXNoZWQnLA0KICAgICAgICAgICAnbWlucGxheWVycycsDQogICAgICAgICAgICdtYXhwbGF5ZXJzJywNCiAgICAgICAgICAgJ3BsYXlpbmd0aW1lJywNCiAgICAgICAgICAgJ21pbnBsYXl0aW1lJywNCiAgICAgICAgICAgJ21heHBsYXl0aW1lJywNCiAgICAgICAgICAgJ21pbmFnZScsDQogICAgICAgICAgICdzdGF0aXN0aWNzL3JhdGluZ3MvdXNlcnNyYXRlZCcsDQogICAgICAgICAgICdzdGF0aXN0aWNzL3JhdGluZ3MvYXZlcmFnZScsDQogICAgICAgICAgICdzdGF0aXN0aWNzL3JhdGluZ3MvYmF5ZXNhdmVyYWdlJywNCiAgICAgICAgICAgJ3N0YXRpc3RpY3MvcmF0aW5ncy9zdGRkZXYnLA0KICAgICAgICAgICAnc3RhdGlzdGljcy9yYXRpbmdzL293bmVkJywNCiAgICAgICAgICAgJ3N0YXRpc3RpY3MvcmF0aW5ncy9udW13ZWlnaHRzJywNCiAgICAgICAgICAgJ3N0YXRpc3RpY3MvcmF0aW5ncy9hdmVyYWdld2VpZ2h0JywNCiAgICAgICAgICAgJ3N0YXRpc3RpY3MvcmF0aW5ncy9yYW5rcy9yYW5rW0BuYW1lPSJib2FyZGdhbWUiXScNCiAgICAgICAgICAgKQ0KDQp0cmlja19kYXRhIDwtIG1hcChhdHRycywgfml0ZW1zICU+JSANCiAgICAgICAgICAgICAgICAgICAgeG1sX2ZpbmRfYWxsKHBhc3RlMCgiLy8iLC54KSkgJT4lIA0KICAgICAgICAgICAgICAgICAgICB4bWxfYXR0cigndmFsdWUnKSAlPiUgDQogICAgICAgICAgICAgICAgICAgIGFzLm51bWVyaWMoKSkgJT4lIA0KICBzZXRfbmFtZXMoc3RyX3JlcGxhY2UoYXR0cnMsICdzdGF0aXN0aWNzL3JhdGluZ3MvJywnJykpICU+JSANCiAgYmluZF9jb2xzKGlkID0gdHJpY2tfZGYkaWQsIG5hbWUgPSB0cmlja19kZiRvcmR0aXRsZSwgLikgJT4lDQogIHJlbmFtZShyYW5rID0gYHJhbmtzL3JhbmtbQG5hbWU9ImJvYXJkZ2FtZSJdYCkNCg0KYGBgDQoNCkFkZCBzb21lIG1vcmUgY29tcGxleCBwcm9wZXJ0aWVzDQoNCmBgYHtyfQ0KIyBsaXN0IGNvbHVtbnMNCg0KIyBsaW5rcyAoYWxyZWFkeSB3cml0dGVuIHRoaXMgZm9yIEtuaXppYSBuZXR3b3JrcykNCmFkZF9saW5rY29sIDwtIGZ1bmN0aW9uKGRhdGEsIGxpbmtfdHlwZSl7DQogIHhwYXRoIDwtIHBhc3RlMCgiLi8vbGlua1tAdHlwZT1cJ2JvYXJkZ2FtZSIsbGlua190eXBlLCJcJ10iKQ0KDQogIGRhdGFbW2xpbmtfdHlwZV1dIDwtIG1hcChpdGVtcywgfnhtbF9maW5kX2FsbCgueCwgeHBhdGgpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHhtbF9hdHRyKCd2YWx1ZScpKQ0KICANCiAgZGF0YQ0KfQ0KDQpsaW5rX3R5cGVzIDwtIGMoJ2NhdGVnb3J5JywnbWVjaGFuaWMnLCdmYW1pbHknLCdleHBhbnNpb24nLCdkZXNpZ25lcicsJ2FydGlzdCcsJ3B1Ymxpc2hlcicpDQoNCmZvcih0eXBlIGluIGxpbmtfdHlwZXMpew0KICB0cmlja19kYXRhIDwtIGFkZF9saW5rY29sKHRyaWNrX2RhdGEsIHR5cGUpDQp9DQoNCiMgcG9sbCBmb3IgYmVzdCBudW1iZXIgb2YgcGxheWVycyAoYWxyZWFkeSB3cml0dGVuIHRoaXMgYXQgaG9tZSkNCg0KDQpwb2xsX2xpc3QgPC0gaXRlbXMgJT4lIHhtbF9maW5kX2FsbCgncG9sbFtAbmFtZT0ic3VnZ2VzdGVkX251bXBsYXllcnMiXScpDQoNCg0KDQpnZXRfcG9sbF9kZiA8LSBmdW5jdGlvbihwb2xsKXsNCiAgICANCiAgbnVtcGxheWVycyA8LSBwb2xsICU+JSANCiAgICB4bWxfZmluZF9hbGwoJy4vL3Jlc3VsdHMnKSAlPiUNCiAgICB4bWxfYXR0cignbnVtcGxheWVycycpDQogICAgICANCiAgcXVhbGl0eSA8LSBwb2xsICU+JQ0KICAgIHhtbF9maW5kX2FsbCgnLi8vcmVzdWx0cy9yZXN1bHQnKSAlPiUNCiAgICB4bWxfYXR0cigndmFsdWUnKQ0KICAgICAgDQogIHZvdGVzIDwtIHBvbGwgJT4lDQogICAgeG1sX2ZpbmRfYWxsKCcuLy9yZXN1bHRzL3Jlc3VsdCcpICU+JQ0KICAgIHhtbF9hdHRyKCdudW12b3RlcycpICU+JQ0KICAgIGFzLmludGVnZXIoKQ0KICANCiAgZGF0YV9mcmFtZShudW1wbGF5ZXJzID0gcmVwKG51bXBsYXllcnMsIGVhY2ggPSAzKSwgcXVhbGl0eSwgdm90ZXMpDQp9DQoNCnRyaWNrX2RhdGEkcG9sbF92b3RlcyA8LSBwb2xsX2xpc3QgJT4lDQogICAgeG1sX2F0dHIoJ3RvdGFsdm90ZXMnKSAlPiUNCiAgICBhcy5pbnRlZ2VyKCkNCg0KdHJpY2tfZGF0YSRwb2xsIDwtIG1hcChwb2xsX2xpc3QsIGdldF9wb2xsX2RmKSAgICANCg0KYGBgDQoNCkxldCdzIGhhdmUgYSBsb29rIGF0IG1vc3QgY29tbW9uIG1lY2hhbmlzbXMuDQoNCmBgYHtyfQ0KdHJpY2tfZGF0YSAlPiUgDQogIHVubmVzdChtZWNoYW5pYykgJT4lIA0KICBjb3VudChtZWNoYW5pYykgJT4lIA0KICBhcnJhbmdlKGRlc2MobikpDQoNCmBgYA0KDQpBbmQgZGVzaWduZXJzOg0KDQpgYGB7cn0NCnRyaWNrX2RhdGEgJT4lIA0KICB1bm5lc3QoZGVzaWduZXIpICU+JSANCiAgY291bnQoZGVzaWduZXIpICU+JQ0KICBmaWx0ZXIobiA+IDEpICU+JQ0KICBhcnJhbmdlKGRlc2MobikpDQoNCmBgYA0KDQpBbmQgcHVibGlzaGVyczoNCg0KYGBge3J9DQp0cmlja19kYXRhICU+JSANCiAgdW5uZXN0KHB1Ymxpc2hlcikgJT4lIA0KICBjb3VudChwdWJsaXNoZXIpICU+JQ0KICBmaWx0ZXIobiA+IDUpICU+JQ0KICBhcnJhbmdlKGRlc2MobikpDQoNCmBgYA0KDQpBbmQgYXJ0aXN0czoNCg0KYGBge3J9DQp0cmlja19kYXRhICU+JSANCiAgdW5uZXN0KGFydGlzdCkgJT4lIA0KICBjb3VudChhcnRpc3QpICU+JQ0KICBmaWx0ZXIobiA+IDEpICU+JQ0KICBhcnJhbmdlKGRlc2MobikpDQoNCmBgYA0KDQpBbmQgY2F0ZWdvcnk6DQoNCmBgYHtyfQ0KdHJpY2tfZGF0YSAlPiUgDQogIHVubmVzdChjYXRlZ29yeSkgJT4lIA0KICBjb3VudChjYXRlZ29yeSkgJT4lDQogIGZpbHRlcihuID4gNSkgJT4lDQogIGFycmFuZ2UoZGVzYyhuKSkNCg0KYGBgDQoNCkFuZCBmYW1pbHk6DQoNCmBgYHtyfQ0KdHJpY2tfZGF0YSAlPiUgDQogIHVubmVzdChmYW1pbHkpICU+JSANCiAgY291bnQoZmFtaWx5KSAlPiUNCiAgZmlsdGVyKG4gPiAxKSAlPiUNCiAgYXJyYW5nZShkZXNjKG4pKQ0KDQpgYGANCg0Kc2ltcGxlIHBsb3RzIC0geWVhcg0KDQpgYGB7cn0NCnRyaWNrX2RhdGEgJT4lIGZpbHRlcih5ZWFycHVibGlzaGVkID4gMTk4MCkgJT4lIGdncGxvdChhZXMoeCA9IHllYXJwdWJsaXNoZWQpKSArIGdlb21fYmFyKCkNCmBgYA0KDQptaW5pbXVtIHBsYXllcnMNCg0KYGBge3J9DQp0cmlja19kYXRhICU+JSBnZ3Bsb3QoYWVzKHggPSBtaW5wbGF5ZXJzKSkgKyBnZW9tX2JhcigpDQpgYGANCg0KbWF4aW11bSBwbGF5ZXJzDQoNCmBgYHtyfQ0KdHJpY2tfZGF0YSAlPiUgZmlsdGVyKG1heHBsYXllcnMgPCAxMikgJT4lIGdncGxvdChhZXMoeCA9IG1heHBsYXllcnMpKSArIGdlb21fYmFyKCkNCmBgYA0KDQpCZXN0IHdpdGguLi4NCg0KYGBge3J9DQpiZXN0X2RhdGEgPC0gdHJpY2tfZGF0YSAlPiUNCiAgZmlsdGVyKHBvbGxfdm90ZXMgPj0gNSkgJT4lDQogIG11dGF0ZShwb2xsX2ZyZXEgPSBtYXAocG9sbCwgfmdyb3VwX2J5KC4sbnVtcGxheWVycykgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUoZnJlcSA9IHZvdGVzIC8gc3VtKHZvdGVzKSkgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICB1bmdyb3VwKCkpLA0KICAgICAgICAgYmVzdCA9IG1hcChwb2xsX2ZyZXEsIH5maWx0ZXIoLixxdWFsaXR5ID09ICdCZXN0JykgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoZnJlcSA+IDAuNSkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBwdWxsKG51bXBsYXllcnMpICU+JSBhcy5pbnRlZ2VyKCkpLA0KICAgICAgICAgbmJlc3QgPSBtYXBfaW50KGJlc3QsIGxlbmd0aCkpDQoNCmJlc3RfZGF0YSAlPiUgIA0KICBmaWx0ZXIobmJlc3QgPT0gMSkgJT4lDQogIG11dGF0ZShiZXN0ID0gYXMuaW50ZWdlcihiZXN0KSkgJT4lDQogIGFycmFuZ2UoYmVzdCwgcmFuaykgJT4lDQogIHNlbGVjdChuYW1lLCBiZXN0KQ0KYGBgDQoNClJhbmdlIG9mIGJlc3Qgd2l0aDoNCg0KYGBge3J9DQpiZXN0X2RhdGEgJT4lICANCiAgY291bnQobmJlc3QpDQpgYGANCg0KQmVzdCB3aXRoIChzaW5nbGUgY291bnQpOg0KDQpgYGB7cn0NCmJlc3RfZGF0YSAlPiUgIA0KICBmaWx0ZXIobmJlc3QgPT0gMSkgJT4lDQogIG11dGF0ZShiZXN0ID0gYXMuaW50ZWdlcihiZXN0KSkgJT4lIA0KICBjb3VudChiZXN0KQ0KYGBgDQoNCg0KYGBge3J9DQpiZXN0X2RhdGEgJT4lIA0KICB1bm5lc3QoYmVzdCkgJT4lDQogIGNvdW50KGJlc3QpIA0KDQpgYGANCg0KYGBge3J9DQpiZXN0X2RhdGEgJT4lIA0KICB1bm5lc3QoYmVzdCkgJT4lDQogIGZpbHRlcihiZXN0ID09IDUpICU+JQ0KICBhcnJhbmdlKHJhbmspICU+JQ0KICBzZWxlY3QobmFtZSwgYmVzdCkgDQpgYGANCg0KVHJpY2tzdGVyIG5vbWluZWVzOg0KDQpgYGB7cn0NCnRyaWNrX2RhdGEgJT4lIA0KICBmaWx0ZXIoeWVhcnB1Ymxpc2hlZCA8IDIwMDksIA0KICAgICAgICAgICAgICAgICAgICAgIG1hcF9sZ2woZGVzaWduZXIsIH4gbGVuZ3RoKC4pID4gMCksDQogICAgICAgICAgICAgICAgICAgICAgbWFwX2xnbChkZXNpZ25lciwgfiAhKCcoVW5jcmVkaXRlZCknICVpbiUgLikpLA0KICAgICAgICAgICAgICAgICAgICAgIG1hcF9sZ2woZmFtaWx5LCB+ICEoJ0NsaW1iaW5nIEdhbWVzJyAlaW4lIC4pKSwNCiAgICAgICAgICAgICAgICAgICAgICAhaXMubmEocmFuaykpICU+JSANCiAgYXJyYW5nZShkZXNjKGF2ZXJhZ2UpKSAlPiUNCiAgc2VsZWN0KG5hbWUsIHJhbmspIA0KYGBgDQoNCkJ5IHllYXI6DQoNCmBgYHtyfQ0KdHJpY2tfZGF0YSAlPiUgDQogIGZpbHRlcihtYXBfbGdsKGRlc2lnbmVyLCB+IGxlbmd0aCguKSA+IDApLA0KICAgICAgICAgbWFwX2xnbChkZXNpZ25lciwgfiAhKCcoVW5jcmVkaXRlZCknICVpbiUgLikpLA0KICAgICAgICAgbWFwX2xnbChmYW1pbHksIH4gISgnQ2xpbWJpbmcgR2FtZXMnICVpbiUgLikpLA0KICAgICAgICAgIWlzLm5hKHJhbmspKSAlPiUNCiAgZ3JvdXBfYnkoeWVhcnB1Ymxpc2hlZCkgJT4lDQogIHRvcF9uKC0xLCByYW5rKSAlPiUNCiAgYXJyYW5nZSh5ZWFycHVibGlzaGVkKSAlPiUNCiAgc2VsZWN0KHllYXJwdWJsaXNoZWQsIG5hbWUpDQoNCmBgYA==