This notebook is licensed under the MIT License. If you use the code or data visualization designs contained within this notebook, it would be greatly appreciated if proper attribution is given back to this notebook and/or myself. Thanks! :)
Setup
[30m── [1mAttaching packages[22m ────────────────────────────────── tidyverse 1.2.1 ──[39m
[30m[32m✔[30m [34mggplot2[30m 2.2.1.[31m9000[30m [32m✔[30m [34mpurrr [30m 0.2.4
[32m✔[30m [34mtibble [30m 1.4.2 [32m✔[30m [34mdplyr [30m 0.7.4
[32m✔[30m [34mtidyr [30m 0.8.0 [32m✔[30m [34mstringr[30m 1.3.0
[32m✔[30m [34mreadr [30m 1.1.1 [32m✔[30m [34mforcats[30m 0.3.0 [39m
[30m── [1mConflicts[22m ───────────────────────────────────── tidyverse_conflicts() ──
[31m✖[30m [34mdplyr[30m::[32mfilter()[30m masks [34mstats[30m::filter()
[31m✖[30m [34mdplyr[30m::[32mlag()[30m masks [34mstats[30m::lag()[39m
Attaching package: ‘scales’
The following object is masked from ‘package:purrr’:
discard
The following object is masked from ‘package:readr’:
col_factor
Loading required package: viridisLite
Attaching package: ‘viridis’
The following object is masked from ‘package:scales’:
viridis_pal
# Special thanks to Ewen Gallic for his implementation of a ggplot2 basketball court
# http://egallic.fr/en/drawing-a-basketball-court-with-r/
source("bb_court_college.R")
Attaching package: ‘gridExtra’
The following object is masked from ‘package:dplyr’:
combine
Scale for 'x' is already present. Adding another scale for 'x', which
will replace the existing scale.
Scale for 'y' is already present. Adding another scale for 'y', which
will replace the existing scale.
Scale for 'x' is already present. Adding another scale for 'x', which
will replace the existing scale.
Scale for 'y' is already present. Adding another scale for 'y', which
will replace the existing scale.
Scale for 'y' is already present. Adding another scale for 'y', which
will replace the existing scale.
Scale for 'x' is already present. Adding another scale for 'x', which
will replace the existing scale.
R version 3.4.4 (2018-03-15)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS High Sierra 10.13.3
Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRlapack.dylib
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
attached base packages:
[1] grid stats graphics grDevices utils datasets methods
[8] base
other attached packages:
[1] gtable_0.2.0 gridExtra_2.3 viridis_0.5.0
[4] viridisLite_0.3.0 scales_0.5.0 forcats_0.3.0
[7] stringr_1.3.0 dplyr_0.7.4 purrr_0.2.4
[10] readr_1.1.1 tidyr_0.8.0 tibble_1.4.2
[13] ggplot2_2.2.1.9000 tidyverse_1.2.1
loaded via a namespace (and not attached):
[1] Rcpp_0.12.16 cellranger_1.1.0 pillar_1.2.1 compiler_3.4.4
[5] plyr_1.8.4 bindr_0.1.1 tools_3.4.4 jsonlite_1.5
[9] lubridate_1.7.3 nlme_3.1-131.1 lattice_0.20-35 pkgconfig_2.0.1
[13] rlang_0.2.0 psych_1.7.8 cli_1.0.0 rstudioapi_0.7
[17] yaml_2.1.18 parallel_3.4.4 haven_1.1.1 bindrcpp_0.2
[21] xml2_1.2.0 httr_1.3.1 knitr_1.20 hms_0.4.2
[25] glue_1.2.0 R6_2.2.2 readxl_1.0.0 foreign_0.8-69
[29] modelr_0.1.1 reshape2_1.4.3 magrittr_1.5 rvest_0.3.2
[33] assertthat_0.2.0 mnormt_1.5-5 colorspace_1.3-2 stringi_1.1.7
[37] lazyeval_0.2.1 munsell_0.4.3 broom_0.4.3 crayon_1.3.4
theme_set(theme_minimal(base_size=9, base_family="Source Sans Pro") +
theme(plot.title = element_text(size=8, family="Source Sans Pro Bold", margin=margin(t = -0.1, b = 0.1, unit='cm')),
axis.title.x = element_text(size=8),
axis.title.y = element_text(size=8),
plot.subtitle = element_text(family="Source Sans Pro Semibold", color="#969696", size=6),
plot.caption = element_text(size=6, color="#969696"),
legend.text = element_text(size = 6),
legend.key.width = unit(0.25, unit='cm')))
bb_theme <- theme(
plot.title = element_text(size=10, family="Source Sans Pro Bold", margin=margin(t = -0.1, b = 0.0, unit='cm')),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
panel.grid = element_blank(),
legend.position = 'top',
legend.text = element_text(size = 6),
legend.title = element_text(size = 6),
legend.key.width = unit(1, unit='cm'),
legend.key.height = unit(0.25, unit='cm'),
legend.margin = margin(c(0, 0, -0.4, 0), unit='cm'))
BigQuery:
#standardSQL
SELECT CAST(event_coord_x as int64) as x,
600 - CAST(event_coord_y as int64) as y,
COUNT(*) as attempts,
COUNTIF(points_scored IS NOT NULL) as successes,
AVG(IFNULL(CAST(points_scored as int64), 0)) as avg_points
FROM `bigquery-public-data.ncaa_basketball.mbb_pbp_sr`
WHERE shot_type IS NOT NULL
AND event_coord_x IS NOT NULL
AND event_coord_y IS NOT NULL
AND scheduled_date < '2018-03-15'
GROUP BY x, y
ORDER BY attempts DESC, avg_points DESC
file_path <- "court.csv"
df <- read_csv(file_path, progress=NA)
Parsed with column specification:
cols(
x = col_integer(),
y = col_integer(),
attempts = col_integer(),
successes = col_integer(),
avg_points = col_double()
)
df <- df %>% mutate(
x = rescale(x, to = c(0,94)),
y = rescale(y, to = c(-50,0)),
perc_success = successes/attempts
)
df %>% head()
plot <- P_180 +
stat_summary_2d(data=df, aes(x=x, y=y, z=attempts), alpha=0.8, binwidth=c(25/(2*25), 50/(2*47)), fun=sum) +
scale_fill_viridis(option='inferno', end=1, labels=comma) +
labs(title=sprintf('Heat Map of %s Basketball Shots from NCAA Games', df %>% pull(attempts) %>% sum() %>% comma()),
subtitle='Starting with the 2013-14 season. Via Sportradar data in BigQuery',
fill='# of 2pt/3pt Shot Attempts\nMade From Spot',
caption = "Max Woolf — minimaxir.com") +
bb_theme
ggsave('ncaa_count_attempts_unlog.png', plot, width=6, height=4)
plot <- P_180 +
stat_summary_2d(data=df, aes(x=x, y=y, z=attempts), alpha=0.8, binwidth=c(25/(2*25), 50/(2*47)), fun=sum) +
scale_fill_viridis(option='inferno', end=1, labels=comma, trans='log10') +
labs(title=sprintf('Heat Map of %s Basketball Shots from NCAA Games', df %>% pull(attempts) %>% sum() %>% comma()),
subtitle='Starting with the 2013-14 season. Via Sportradar data in BigQuery',
fill='# of 2pt/3pt Shot Attempts\nMade From Spot',
caption = "Max Woolf — minimaxir.com") +
bb_theme
ggsave('ncaa_count_attempts.png', plot, width=6, height=4)
plot <- P_180 +
stat_summary_2d(data=df, aes(x=x, y=y, z=perc_success), alpha=0.8, binwidth=c(25/(2*25), 50/(2*47)), fun=mean, drop=T) +
scale_fill_viridis(option='viridis', end=1, label=percent) +
labs(title=sprintf('Shot Success of %s Basketball Shots from NCAA Games', df %>% pull(attempts) %>% sum() %>% comma()),
subtitle='Starting with the 2013-14 season. Via Sportradar data in BigQuery',
fill='% of Successful Shots\nMade From Spot',
caption = "Max Woolf — minimaxir.com") +
bb_theme
ggsave('ncaa_count_perc_success.png', plot, width=6, height=4)
plot <- P_180 +
stat_summary_2d(data=df, aes(x=x, y=y, z=avg_points), alpha=0.8, binwidth=c(25/(2*25), 50/(2*47)), fun=mean, drop=T) +
scale_fill_viridis(option='viridis', end=1) +
labs(title=sprintf('Average Points Earned From of %s Basketball Shots from NCAA Games', df %>% pull(attempts) %>% sum() %>% comma()),
subtitle='Starting with the 2013-14 season. Via Sportradar data in BigQuery',
fill='Average Points Earned From Shot Attempts\nMade From Spot',
caption = "Max Woolf — minimaxir.com") +
bb_theme
ggsave('ncaa_count_avg_points.png', plot, width=6, height=4)
Half Court
BigQuery:
#standardSQL
SELECT IF(event_coord_x < 564, CAST(event_coord_x as int64), 1128 - CAST(event_coord_x as int64)) as x,
IF(event_coord_x < 564, 600 - CAST(event_coord_y as int64), CAST(event_coord_y as int64)) as y,
COUNT(*) as attempts,
COUNTIF(points_scored IS NOT NULL) as successes,
AVG(IFNULL(CAST(points_scored as int64), 0)) as avg_points
FROM `bigquery-public-data.ncaa_basketball.mbb_pbp_sr`
WHERE shot_type IS NOT NULL
AND event_coord_x IS NOT NULL
AND event_coord_y IS NOT NULL
AND IF(event_coord_x < 564, 'left', 'right') = team_basket
AND scheduled_date < '2018-03-15'
GROUP BY x, y
ORDER BY attempts DESC, avg_points DESC
file_path_half <- "half_court.csv"
df_half <- read_csv(file_path_half, progress=NA)
Parsed with column specification:
cols(
x = col_integer(),
y = col_integer(),
attempts = col_integer(),
successes = col_integer(),
avg_points = col_double()
)
df_half <- df_half %>% mutate(
x = rescale(x, to = c(0, 47)),
y = rescale(y, to = c(0, 50)),
perc_success = successes/attempts
)
df_half %>% head()
plot <- P_half +
stat_summary_2d(data=df_half, aes(x=y, y=x, z=attempts), alpha=0.8, bins=100, fun=sum) +
scale_fill_viridis(option='inferno', end=1, labels=comma, trans='log10') +
labs(title=sprintf('Heat Map of %s Basketball Shots from NCAA Games', df_half %>% pull(attempts) %>% sum() %>% comma()),
subtitle='Starting with the 2013-14 season. Via Sportradar data in BigQuery',
fill='# of 2pt/3pt Shot Attempts\nMade From Spot',
caption = "Max Woolf — minimaxir.com") +
bb_theme
ggsave('ncaa_count_attempts_half_log.png', plot, width=4, height=4)
Bar Chart of Shot Types
BigQuery:
#standardSQL
SELECT shot_type,
IF(event_coord_x < 564, CAST(event_coord_x as int64), 1128 - CAST(event_coord_x as int64)) as x,
IF(event_coord_x < 564, 600 - CAST(event_coord_y as int64), CAST(event_coord_y as int64)) as y,
COUNT(*) as attempts,
COUNTIF(points_scored IS NOT NULL) as successes,
AVG(IFNULL(CAST(points_scored as int64), 0)) as avg_points
FROM `bigquery-public-data.ncaa_basketball.mbb_pbp_sr`
WHERE shot_type IS NOT NULL
AND event_coord_x IS NOT NULL
AND event_coord_y IS NOT NULL
AND IF(event_coord_x < 564, 'left', 'right') = team_basket
AND scheduled_date < '2018-03-15'
GROUP BY shot_type, x, y
ORDER BY attempts DESC, avg_points DESC
file_path_half_types <- "half_types.csv"
df_half_types <- read_csv(file_path_half_types, progress=NA)
Parsed with column specification:
cols(
shot_type = col_character(),
x = col_integer(),
y = col_integer(),
attempts = col_integer(),
successes = col_integer(),
avg_points = col_double()
)
df_half_types_agg <- df_half_types %>%
group_by(shot_type) %>%
summarize(total_attempts = sum(attempts),
total_successes = sum(successes),
perc_success = total_successes/total_attempts,
avg_points = mean(avg_points)) %>%
ungroup() %>%
mutate(prop_attempts = total_attempts/sum(total_attempts)) %>%
arrange(desc(total_attempts))
df_half_types_agg$shot_type <- factor(df_half_types_agg$shot_type, levels=rev(df_half_types_agg$shot_type))
plot <- ggplot(df_half_types_agg, aes(x=shot_type, y=prop_attempts, fill=shot_type, color=shot_type)) +
geom_bar(stat="identity", color=NA) +
geom_text(aes(label=sprintf('%0.1f%%', prop_attempts*100)), size=3, family="Source Sans Pro Bold", hjust=-0.25) +
coord_flip() +
scale_color_brewer(palette="Set1", guide=F) +
scale_fill_brewer(palette="Set1", guide=F) +
scale_y_continuous(labels=percent, limits=c(0,1)) +
labs(title=sprintf('Proportion of %s Basketball Shots by Type from NCAA Games', df_half_types_agg %>% pull(total_attempts) %>% sum() %>% comma()),
subtitle='Starting with the 2013-14 season. Via Sportradar data in BigQuery',
caption = "Max Woolf — minimaxir.com",
y = "Proportion of Shot Attempts Made") +
theme(axis.title.y = element_blank())
ggsave('ncaa_types_prop_attempts.png', plot, width=4, height=2)
plot <- ggplot(df_half_types_agg, aes(x=shot_type, y=perc_success, fill=shot_type)) +
geom_bar(stat="identity") +
geom_text(aes(label=sprintf('%0.1f%%', perc_success*100)), size=3, color="white", family="Source Sans Pro Bold", hjust=1.25) +
coord_flip() +
scale_fill_brewer(palette="Set1", guide=F) +
scale_y_continuous(labels=percent, limits=c(0,1)) +
labs(title=sprintf('Shot Success of %s Basketball Shots by Type from NCAA Games', df_half_types_agg %>% pull(total_attempts) %>% sum() %>% comma()),
subtitle='Starting with the 2013-14 season. Via Sportradar data in BigQuery',
caption = "Max Woolf — minimaxir.com",
y = "% of Successful Shots Made by Type") +
theme(axis.title.y = element_blank())
ggsave('ncaa_types_perc.png', plot, width=4, height=2)
Half Court, Faceted by Type of Shot
df_half_types <- df_half_types %>% mutate(
x = rescale(x, to = c(0, 47)),
y = rescale(y, to = c(0, 50)),
perc_success = successes/attempts,
shot_type = factor(shot_type, levels=df_half_types_agg$shot_type)
)
df_half_types %>% head()
plot <- P_half +
stat_summary_2d(data=df_half_types, aes(x=y, y=x, z=attempts), alpha=0.8, bins=100, fun=sum) +
scale_fill_viridis(option='inferno', end=1, labels=comma, trans='log10') +
facet_wrap(~ shot_type, ncol=2) +
labs(title=sprintf('Heat Map of %s Basketball Shots from NCAA Games', df_half_types %>% pull(attempts) %>% sum() %>% comma()),
subtitle='Starting with the 2013-14 season. Via Sportradar data in BigQuery',
fill='# of 2pt/3pt Shot Attempts\nMade From Spot',
caption = "Max Woolf — minimaxir.com") +
bb_theme +
theme(plot.title = element_text(size=7))
ggsave('ncaa_count_attempts_half_types_log.png', plot, width=3, height=5)
Half Court, Faceted by 10 Minute Time Buckets.
BigQuery:
#standardSQL
SELECT IF(elapsed_time_sec >= 60*40, 99, CAST(TRUNC((elapsed_time_sec-1)/300) AS int64)) as min_interval,
IF(event_coord_x < 564, CAST(event_coord_x as int64), 1128 - CAST(event_coord_x as int64)) as x,
IF(event_coord_x < 564, 600 - CAST(event_coord_y as int64), CAST(event_coord_y as int64)) as y,
shot_type,
COUNT(*) as attempts,
COUNTIF(points_scored IS NOT NULL) as successes,
AVG(IFNULL(CAST(points_scored as int64), 0)) as avg_points
FROM `bigquery-public-data.ncaa_basketball.mbb_pbp_sr`
WHERE shot_type IS NOT NULL
AND event_coord_x IS NOT NULL
AND event_coord_y IS NOT NULL
AND IF(event_coord_x < 564, 'left', 'right') = team_basket
AND scheduled_date < '2018-03-15'
GROUP BY min_interval, shot_type, x, y
ORDER BY attempts DESC, avg_points DESC
file_path_half_interval <- "half_interval.csv"
df_half_interval <- read_csv(file_path_half_interval, progress=NA)
Parsed with column specification:
cols(
min_interval = col_integer(),
x = col_integer(),
y = col_integer(),
shot_type = col_character(),
attempts = col_integer(),
successes = col_integer(),
avg_points = col_double()
)
df_half_interval %>% head()
interval_levels = c('1st Half\n20:00 — 15:00',
'1st Half\n14:59 — 10:00',
'1st Half\n09:59 — 05:00',
'1st Half\n04:59 — 00:00',
'2nd Half\n20:00 — 15:00',
'2nd Half\n14:59 — 10:00',
'2nd Half\n09:59 — 05:00',
'2nd Half\n04:59 — 00:00',
'OT')
df_half_interval <- df_half_interval %>% mutate(
x = rescale(x, to = c(0, 47)),
y = rescale(y, to = c(0, 50)),
perc_success = successes/attempts,
min_interval = factor(min_interval, labels = interval_levels),
shot_type = factor(shot_type, levels=df_half_types_agg$shot_type)
)
df_half_interval %>% head()
plot <- P_half +
stat_summary_2d(data=df_half_interval, aes(x=y, y=x, z=attempts), alpha=0.8, bins=25, fun=sum) +
scale_fill_viridis(option='inferno', end=1, labels=comma, trans='log10') +
facet_grid(min_interval ~ shot_type) +
labs(title=sprintf('Heat Map of %s Basketball Shots from NCAA Games', df_half_interval %>% pull(attempts) %>% sum() %>% comma()),
subtitle='Facet title represents game time at shot.\nVia Sportradar data in BigQuery',
fill='# of 2pt/3pt Shot Attempts\nMade From Spot',
caption = "Max Woolf — minimaxir.com") +
bb_theme
ggsave('ncaa_count_attempts_half_interval_log.png', plot, width=4, height=7)
Half Court, by Point Delta at Time of Shot
BigQuery:
#standardSQL
SELECT CASE WHEN score_delta < -20 THEN '<-20'
WHEN score_delta >= -20 AND score_delta < -10 THEN '-20 — -11'
WHEN score_delta >= -10 AND score_delta < 0 THEN '-10 — -1'
WHEN score_delta = 0 THEN '0'
WHEN score_delta >= 1 AND score_delta <= 10 THEN '1 — 10'
WHEN score_delta > 10 AND score_delta <= 20 THEN '11 — 20'
WHEN score_delta > 20 THEN '>20'
END
AS score_delta_interval,
shot_type,
IF(event_coord_x < 564, CAST(event_coord_x as int64), 1128 - CAST(event_coord_x as int64)) as x,
IF(event_coord_x < 564, 600 - CAST(event_coord_y as int64), CAST(event_coord_y as int64)) as y,
COUNT(*) as attempts,
COUNTIF(points_scored IS NOT NULL) as successes,
AVG(IFNULL(CAST(points_scored as int64), 0)) as avg_points
FROM (
SELECT *, team_score - (game_score - team_score) as score_delta
FROM (
SELECT event_coord_x, event_coord_y, points_scored, shot_type, team_basket, scheduled_date,
SUM(IFNULL(CAST(points_scored as int64), 0)) OVER (PARTITION BY game_id, team_id ORDER BY timestamp) - IFNULL(CAST(points_scored as int64), 0) as team_score,
SUM(IFNULL(CAST(points_scored as int64), 0)) OVER (PARTITION BY game_id ORDER BY timestamp) - IFNULL(CAST(points_scored as int64), 0) as game_score
FROM `bigquery-public-data.ncaa_basketball.mbb_pbp_sr`
)
)
WHERE shot_type IS NOT NULL
AND event_coord_x IS NOT NULL
AND event_coord_y IS NOT NULL
AND IF(event_coord_x < 564, 'left', 'right') = team_basket
AND scheduled_date < '2018-03-15'
GROUP BY score_delta_interval, shot_type, x, y
ORDER BY attempts DESC, avg_points DESC
file_path_half_score <- "half_score.csv"
df_half_score <- read_csv(file_path_half_score, progress=NA)
Parsed with column specification:
cols(
score_delta_interval = col_character(),
shot_type = col_character(),
x = col_integer(),
y = col_integer(),
attempts = col_integer(),
successes = col_integer(),
avg_points = col_double()
)
score_levels <- c('<-20', '-20 — -11', '-10 — -1', '0', '1 — 10', '11 — 20', '>20')
df_half_score <- df_half_score %>% mutate(
x = rescale(x, to = c(0, 47)),
y = rescale(y, to = c(0, 50)),
perc_success = successes/attempts,
score_delta_interval = factor(score_delta_interval, levels = score_levels),
shot_type = factor(shot_type, levels=df_half_types_agg$shot_type)
)
df_half_score %>% head()
plot <- P_half +
stat_summary_2d(data=df_half_score, aes(x=y, y=x, z=attempts), alpha=0.8, bins=25, fun=sum) +
scale_fill_viridis(option='inferno', end=1, labels=comma, trans='log10') +
facet_grid(score_delta_interval ~ shot_type) +
labs(title=sprintf('Heat Map of %s Basketball Shots from NCAA Games', df_half_score %>% pull(attempts) %>% sum() %>% comma()),
subtitle='Facet title represents team score difference relative to other team before shot.\nVia Sportradar data in BigQuery',
fill='# of 2pt/3pt Shot Attempts\nMade From Spot',
caption = "Max Woolf — minimaxir.com") +
bb_theme
ggsave('ncaa_count_attempts_half_score_log.png', plot, width=4, height=5.5)
Bar Charts of Elapsed Time, Faceted by Shot Type
df_half_interval_agg <- df_half_interval %>%
group_by(min_interval, shot_type) %>%
summarize(total_attempts = sum(attempts),
total_successes = sum(successes),
perc_success = total_successes/total_attempts,
avg_points = mean(avg_points)) %>%
arrange(desc(perc_success)) %>%
ungroup() %>%
group_by(min_interval) %>%
mutate(prop_attempts = total_attempts/sum(total_attempts)) %>%
arrange(desc(total_attempts))
df_half_interval_agg$shot_type <- factor(df_half_interval_agg$shot_type, levels=rev(df_half_types_agg$shot_type))
plot <- ggplot(df_half_interval_agg, aes(x=shot_type, y=perc_success, fill=shot_type)) +
geom_bar(stat="identity") +
geom_text(aes(label=sprintf('%0.1f%%', perc_success*100)), size=2, color="white", family="Source Sans Pro Bold", hjust=1.05) +
coord_flip() +
facet_wrap(~ min_interval) +
scale_fill_brewer(palette="Set1", guide=F) +
scale_y_continuous(labels=percent, limits=c(0,1)) +
labs(title=sprintf('Shot Success of %s Basketball Shots by Type from NCAA Games', df_half_interval_agg %>% pull(total_attempts) %>% sum() %>% comma()),
subtitle='Facet title represents game time at shot.\nVia Sportradar data in BigQuery',
caption = "Max Woolf — minimaxir.com",
y = "% of Successful Shots Made by Type") +
theme(axis.title.y = element_blank())
ggsave('ncaa_types_perc_success_type_elapsed.png', plot, width=4, height=4)
plot <- ggplot(df_half_interval_agg, aes(x=shot_type, y=prop_attempts, fill=shot_type, color=shot_type)) +
geom_bar(stat="identity", color=NA) +
geom_text(aes(label=sprintf('%0.1f%%', prop_attempts*100)), size=2, family="Source Sans Pro Bold", hjust=-0.25) +
coord_flip() +
facet_wrap(~ min_interval) +
scale_color_brewer(palette="Set1", guide=F) +
scale_fill_brewer(palette="Set1", guide=F) +
scale_y_continuous(labels=percent, limits=c(0,1)) +
labs(title=sprintf('Proportion of %s Basketball Shots by Type from NCAA Games', df_half_interval_agg %>% pull(total_attempts) %>% sum() %>% comma()),
subtitle='Facet title represents game time at shot.\nVia Sportradar data in BigQuery',
caption = "Max Woolf — minimaxir.com",
y = "Proportion of Shot Attempts Made") +
theme(axis.title.y = element_blank())
ggsave('ncaa_types_prop_type_elapsed.png', plot, width=4, height=4)
Bar Charts of Score Delta, Faceted by Shot Type
df_half_score_agg <- df_half_score %>%
group_by(score_delta_interval, shot_type) %>%
summarize(total_attempts = sum(attempts),
total_successes = sum(successes),
perc_success = total_successes/total_attempts,
avg_points = mean(avg_points)) %>%
arrange(desc(perc_success)) %>%
ungroup() %>%
group_by(score_delta_interval) %>%
mutate(prop_attempts = total_attempts/sum(total_attempts)) %>%
arrange(desc(total_attempts))
df_half_score_agg$shot_type <- factor(df_half_score_agg$shot_type, levels=rev(df_half_types_agg$shot_type))
plot <- ggplot(df_half_score_agg, aes(x=shot_type, y=perc_success, fill=shot_type)) +
geom_bar(stat="identity") +
geom_text(aes(label=sprintf('%0.1f%%', perc_success*100)), size=2, color="white", family="Source Sans Pro Bold", hjust=1.05) +
coord_flip() +
facet_wrap(~ score_delta_interval) +
scale_fill_brewer(palette="Set1", guide=F) +
scale_y_continuous(labels=percent, limits=c(0,1)) +
labs(title=sprintf('Shot Success of %s Basketball Shots by Type from NCAA Games', df_half_interval_agg %>% pull(total_attempts) %>% sum() %>% comma()),
subtitle='Facet title represents team score difference relative to other team before shot.\nVia Sportradar data in BigQuery',
caption = "Max Woolf — minimaxir.com",
y = "% of Successful Shots Made by Type") +
theme(axis.title.y = element_blank())
ggsave('ncaa_types_perc_success_type_score.png', plot, width=4, height=4)
plot <- ggplot(df_half_score_agg, aes(x=shot_type, y=prop_attempts, fill=shot_type, color=shot_type)) +
geom_bar(stat="identity", color=NA) +
geom_text(aes(label=sprintf('%0.1f%%', prop_attempts*100)), size=2, family="Source Sans Pro Bold", hjust=-0.25) +
coord_flip() +
facet_wrap(~ score_delta_interval) +
scale_color_brewer(palette="Set1", guide=F) +
scale_fill_brewer(palette="Set1", guide=F) +
scale_y_continuous(labels=percent, limits=c(0,1)) +
labs(title=sprintf('Proportion of %s Basketball Shots by Type from NCAA Games', df_half_interval_agg %>% pull(total_attempts) %>% sum() %>% comma()),
subtitle='Facet title represents team score difference relative to other team before shot.\nVia Sportradar data in BigQuery',
caption = "Max Woolf — minimaxir.com",
y = "Proportion of Shot Attempts Made by Type") +
theme(axis.title.y = element_blank())
ggsave('ncaa_types_prop_type_score.png', plot, width=4, height=4)
Bonus Query to Check % of Cross Court Shots
#standardSQL
SELECT is_cross_court,
COUNT(*) as num_baskets
FROM (
SELECT team_basket, shot_type, event_coord_x, event_coord_y, scheduled_date,
IF(event_coord_x < 564, 'left', 'right') != team_basket as is_cross_court
FROM `bigquery-public-data.ncaa_basketball.mbb_pbp_sr`
)
WHERE shot_type IS NOT NULL
AND event_coord_x IS NOT NULL
AND event_coord_y IS NOT NULL
AND scheduled_date < '2018-03-15'
GROUP BY is_cross_court
LICENSE
The MIT License (MIT)
Copyright (c) 2018 Max Woolf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LS0tCnRpdGxlOiAiVmlzdWFsaXppbmcgT25lIE1pbGxpb24gTkNBQSBCYXNrZXRiYWxsIFNob3RzIgphdXRob3I6ICJNYXggV29vbGYgKEBtaW5pbWF4aXIpIgpkYXRlOiAiMjAxOC0wMy0xOSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBtYXRoamF4OiBudWxsCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdGhlbWU6IHNwYWNlbGFiCi0tLQoKVGhpcyBSIE5vdGVib29rIGlzIHRoZSBjb21wbGVtZW50IHRvIG15IGJsb2cgcG9zdCBbVmlzdWFsaXppbmcgT25lIE1pbGxpb24gTkNBQSBCYXNrZXRiYWxsIFNob3RzXShodHRwOi8vbWluaW1heGlyLmNvbS8yMDE4LzAzL2Jhc2tldGJhbGwtc2hvdHMvKS4KClRoaXMgbm90ZWJvb2sgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBJZiB5b3UgdXNlIHRoZSBjb2RlIG9yIGRhdGEgdmlzdWFsaXphdGlvbiBkZXNpZ25zIGNvbnRhaW5lZCB3aXRoaW4gdGhpcyBub3RlYm9vaywgaXQgd291bGQgYmUgZ3JlYXRseSBhcHByZWNpYXRlZCBpZiBwcm9wZXIgYXR0cmlidXRpb24gaXMgZ2l2ZW4gYmFjayB0byB0aGlzIG5vdGVib29rIGFuZC9vciBteXNlbGYuIFRoYW5rcyEgOikKCiMgU2V0dXAKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkodmlyaWRpcykKCiMgU3BlY2lhbCB0aGFua3MgdG8gRXdlbiBHYWxsaWMgZm9yIGhpcyBpbXBsZW1lbnRhdGlvbiBvZiBhIGdncGxvdDIgYmFza2V0YmFsbCBjb3VydAojIGh0dHA6Ly9lZ2FsbGljLmZyL2VuL2RyYXdpbmctYS1iYXNrZXRiYWxsLWNvdXJ0LXdpdGgtci8Kc291cmNlKCJiYl9jb3VydF9jb2xsZWdlLlIiKQoKc2Vzc2lvbkluZm8oKQpgYGAKCmBgYHtyfQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbChiYXNlX3NpemU9OSwgYmFzZV9mYW1pbHk9IlNvdXJjZSBTYW5zIFBybyIpICsKICAgICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTgsIGZhbWlseT0iU291cmNlIFNhbnMgUHJvIEJvbGQiLCBtYXJnaW49bWFyZ2luKHQgPSAtMC4xLCBiID0gMC4xLCB1bml0PSdjbScpKSwKICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgICAgICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGZhbWlseT0iU291cmNlIFNhbnMgUHJvIFNlbWlib2xkIiwgY29sb3I9IiM5Njk2OTYiLCBzaXplPTYpLAogICAgICAgICAgICAgICAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoc2l6ZT02LCBjb2xvcj0iIzk2OTY5NiIpLAogICAgICAgICAgICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5rZXkud2lkdGggPSB1bml0KDAuMjUsIHVuaXQ9J2NtJykpKQoKYmJfdGhlbWUgPC0gdGhlbWUoCiAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTAsIGZhbWlseT0iU291cmNlIFNhbnMgUHJvIEJvbGQiLCBtYXJnaW49bWFyZ2luKHQgPSAtMC4xLCBiID0gMC4wLCB1bml0PSdjbScpKSwKICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gJ3RvcCcsCiAgICAgICAgICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksCiAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLAogICAgICAgICAgICAgICAgbGVnZW5kLmtleS53aWR0aCA9IHVuaXQoMSwgdW5pdD0nY20nKSwKICAgICAgICAgICAgICAgIGxlZ2VuZC5rZXkuaGVpZ2h0ID0gdW5pdCgwLjI1LCB1bml0PSdjbScpLAogICAgICAgICAgICAgICAgbGVnZW5kLm1hcmdpbiA9IG1hcmdpbihjKDAsIDAsIC0wLjQsIDApLCB1bml0PSdjbScpKQpgYGAKCkJpZ1F1ZXJ5OgoKYGBge3NxbCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CiNzdGFuZGFyZFNRTApTRUxFQ1QgQ0FTVChldmVudF9jb29yZF94IGFzIGludDY0KSBhcyB4LAogICAgICAgIDYwMCAtIENBU1QoZXZlbnRfY29vcmRfeSBhcyBpbnQ2NCkgYXMgeSwKICAgICAgICBDT1VOVCgqKSBhcyBhdHRlbXB0cywKICAgICAgICBDT1VOVElGKHBvaW50c19zY29yZWQgSVMgTk9UIE5VTEwpIGFzIHN1Y2Nlc3NlcywKICAgICAgICBBVkcoSUZOVUxMKENBU1QocG9pbnRzX3Njb3JlZCBhcyBpbnQ2NCksIDApKSBhcyBhdmdfcG9pbnRzCkZST00gYGJpZ3F1ZXJ5LXB1YmxpYy1kYXRhLm5jYWFfYmFza2V0YmFsbC5tYmJfcGJwX3NyYApXSEVSRSBzaG90X3R5cGUgSVMgTk9UIE5VTEwKQU5EIGV2ZW50X2Nvb3JkX3ggSVMgTk9UIE5VTEwKQU5EIGV2ZW50X2Nvb3JkX3kgSVMgTk9UIE5VTEwKQU5EIHNjaGVkdWxlZF9kYXRlIDwgJzIwMTgtMDMtMTUnCkdST1VQIEJZIHgsIHkKT1JERVIgQlkgYXR0ZW1wdHMgREVTQywgYXZnX3BvaW50cyBERVNDCmBgYAoKYGBge3J9CmZpbGVfcGF0aCA8LSAiY291cnQuY3N2IgpkZiA8LSByZWFkX2NzdihmaWxlX3BhdGgsIHByb2dyZXNzPU5BKQpkZiAlPiUgaGVhZCgpCmBgYAoKYGBge3J9CmRmIDwtIGRmICU+JSBtdXRhdGUoCiAgeCA9IHJlc2NhbGUoeCwgdG8gPSBjKDAsOTQpKSwKICB5ID0gcmVzY2FsZSh5LCB0byA9IGMoLTUwLDApKSwKICBwZXJjX3N1Y2Nlc3MgPSBzdWNjZXNzZXMvYXR0ZW1wdHMKKQoKZGYgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQpwbG90IDwtIFBfMTgwICsKICAgICAgICAgIHN0YXRfc3VtbWFyeV8yZChkYXRhPWRmLCBhZXMoeD14LCB5PXksIHo9YXR0ZW1wdHMpLCBhbHBoYT0wLjgsIGJpbndpZHRoPWMoMjUvKDIqMjUpLCA1MC8oMio0NykpLCBmdW49c3VtKSArCiAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uPSdpbmZlcm5vJywgZW5kPTEsIGxhYmVscz1jb21tYSkgKwogICAgICAgICAgbGFicyh0aXRsZT1zcHJpbnRmKCdIZWF0IE1hcCBvZiAlcyBCYXNrZXRiYWxsIFNob3RzIGZyb20gTkNBQSBHYW1lcycsIGRmICU+JSBwdWxsKGF0dGVtcHRzKSAlPiUgc3VtKCkgJT4lIGNvbW1hKCkpLAogICAgICAgICAgICAgICAgc3VidGl0bGU9J1N0YXJ0aW5nIHdpdGggdGhlIDIwMTMtMTQgc2Vhc29uLiBWaWEgU3BvcnRyYWRhciBkYXRhIGluIEJpZ1F1ZXJ5JywKICAgICAgICAgICAgICAgZmlsbD0nIyBvZiAycHQvM3B0IFNob3QgQXR0ZW1wdHNcbk1hZGUgRnJvbSBTcG90JywKICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iKSArCiAgICAgICAgICBiYl90aGVtZQoKZ2dzYXZlKCduY2FhX2NvdW50X2F0dGVtcHRzX3VubG9nLnBuZycsIHBsb3QsIHdpZHRoPTYsIGhlaWdodD00KQpgYGAKCiFbXShuY2FhX2NvdW50X2F0dGVtcHRzX3VubG9nLnBuZykKCmBgYHtyfQpwbG90IDwtIFBfMTgwICsKICAgICAgICAgIHN0YXRfc3VtbWFyeV8yZChkYXRhPWRmLCBhZXMoeD14LCB5PXksIHo9YXR0ZW1wdHMpLCBhbHBoYT0wLjgsIGJpbndpZHRoPWMoMjUvKDIqMjUpLCA1MC8oMio0NykpLCBmdW49c3VtKSArCiAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uPSdpbmZlcm5vJywgZW5kPTEsIGxhYmVscz1jb21tYSwgdHJhbnM9J2xvZzEwJykgKwogICAgICAgICAgbGFicyh0aXRsZT1zcHJpbnRmKCdIZWF0IE1hcCBvZiAlcyBCYXNrZXRiYWxsIFNob3RzIGZyb20gTkNBQSBHYW1lcycsIGRmICU+JSBwdWxsKGF0dGVtcHRzKSAlPiUgc3VtKCkgJT4lIGNvbW1hKCkpLAogICAgICAgICAgICAgICAgc3VidGl0bGU9J1N0YXJ0aW5nIHdpdGggdGhlIDIwMTMtMTQgc2Vhc29uLiBWaWEgU3BvcnRyYWRhciBkYXRhIGluIEJpZ1F1ZXJ5JywKICAgICAgICAgICAgICAgZmlsbD0nIyBvZiAycHQvM3B0IFNob3QgQXR0ZW1wdHNcbk1hZGUgRnJvbSBTcG90JywKICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iKSArCiAgICAgICAgICBiYl90aGVtZQoKZ2dzYXZlKCduY2FhX2NvdW50X2F0dGVtcHRzLnBuZycsIHBsb3QsIHdpZHRoPTYsIGhlaWdodD00KQpgYGAKCiFbXShuY2FhX2NvdW50X2F0dGVtcHRzLnBuZykKCmBgYHtyfQpwbG90IDwtIFBfMTgwICsKICAgICAgICAgIHN0YXRfc3VtbWFyeV8yZChkYXRhPWRmLCBhZXMoeD14LCB5PXksIHo9cGVyY19zdWNjZXNzKSwgYWxwaGE9MC44LCBiaW53aWR0aD1jKDI1LygyKjI1KSwgNTAvKDIqNDcpKSwgZnVuPW1lYW4sIGRyb3A9VCkgKwogICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbj0ndmlyaWRpcycsIGVuZD0xLCBsYWJlbD1wZXJjZW50KSArCiAgICAgICAgICBsYWJzKHRpdGxlPXNwcmludGYoJ1Nob3QgU3VjY2VzcyBvZiAlcyBCYXNrZXRiYWxsIFNob3RzIGZyb20gTkNBQSBHYW1lcycsIGRmICU+JSBwdWxsKGF0dGVtcHRzKSAlPiUgc3VtKCkgJT4lIGNvbW1hKCkpLAogICAgICAgICAgICAgICAgc3VidGl0bGU9J1N0YXJ0aW5nIHdpdGggdGhlIDIwMTMtMTQgc2Vhc29uLiBWaWEgU3BvcnRyYWRhciBkYXRhIGluIEJpZ1F1ZXJ5JywKICAgICAgICAgICAgICAgZmlsbD0nJSBvZiBTdWNjZXNzZnVsIFNob3RzXG5NYWRlIEZyb20gU3BvdCcsCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIikgKwogICAgICAgICAgYmJfdGhlbWUKCmdnc2F2ZSgnbmNhYV9jb3VudF9wZXJjX3N1Y2Nlc3MucG5nJywgcGxvdCwgd2lkdGg9NiwgaGVpZ2h0PTQpCmBgYAoKIVtdKG5jYWFfY291bnRfcGVyY19zdWNjZXNzLnBuZykKCmBgYHtyfQpwbG90IDwtIFBfMTgwICsKICAgICAgICAgIHN0YXRfc3VtbWFyeV8yZChkYXRhPWRmLCBhZXMoeD14LCB5PXksIHo9YXZnX3BvaW50cyksIGFscGhhPTAuOCwgYmlud2lkdGg9YygyNS8oMioyNSksIDUwLygyKjQ3KSksIGZ1bj1tZWFuLCBkcm9wPVQpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb249J3ZpcmlkaXMnLCBlbmQ9MSkgKwogICAgICAgICAgbGFicyh0aXRsZT1zcHJpbnRmKCdBdmVyYWdlIFBvaW50cyBFYXJuZWQgRnJvbSBvZiAlcyBCYXNrZXRiYWxsIFNob3RzIGZyb20gTkNBQSBHYW1lcycsIGRmICU+JSBwdWxsKGF0dGVtcHRzKSAlPiUgc3VtKCkgJT4lIGNvbW1hKCkpLAogICAgICAgICAgICAgICAgc3VidGl0bGU9J1N0YXJ0aW5nIHdpdGggdGhlIDIwMTMtMTQgc2Vhc29uLiBWaWEgU3BvcnRyYWRhciBkYXRhIGluIEJpZ1F1ZXJ5JywKICAgICAgICAgICAgICAgZmlsbD0nQXZlcmFnZSBQb2ludHMgRWFybmVkIEZyb20gU2hvdCBBdHRlbXB0c1xuTWFkZSBGcm9tIFNwb3QnLAogICAgICAgICAgICAgICBjYXB0aW9uID0gIk1heCBXb29sZiDigJQgbWluaW1heGlyLmNvbSIpICsKICAgICAgICAgIGJiX3RoZW1lCgpnZ3NhdmUoJ25jYWFfY291bnRfYXZnX3BvaW50cy5wbmcnLCBwbG90LCB3aWR0aD02LCBoZWlnaHQ9NCkKYGBgCgohW10obmNhYV9jb3VudF9hdmdfcG9pbnRzLnBuZykKCiMjIEhhbGYgQ291cnQKCkJpZ1F1ZXJ5OgoKYGBge3NxbCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CiNzdGFuZGFyZFNRTApTRUxFQ1QgSUYoZXZlbnRfY29vcmRfeCA8IDU2NCwgQ0FTVChldmVudF9jb29yZF94IGFzIGludDY0KSwgMTEyOCAtIENBU1QoZXZlbnRfY29vcmRfeCBhcyBpbnQ2NCkpIGFzIHgsCiAgICAgICAgSUYoZXZlbnRfY29vcmRfeCA8IDU2NCwgNjAwIC0gQ0FTVChldmVudF9jb29yZF95IGFzIGludDY0KSwgQ0FTVChldmVudF9jb29yZF95IGFzIGludDY0KSkgYXMgeSwKICAgICAgICBDT1VOVCgqKSBhcyBhdHRlbXB0cywKICAgICAgICBDT1VOVElGKHBvaW50c19zY29yZWQgSVMgTk9UIE5VTEwpIGFzIHN1Y2Nlc3NlcywgCiAgICAgICAgQVZHKElGTlVMTChDQVNUKHBvaW50c19zY29yZWQgYXMgaW50NjQpLCAwKSkgYXMgYXZnX3BvaW50cwpGUk9NIGBiaWdxdWVyeS1wdWJsaWMtZGF0YS5uY2FhX2Jhc2tldGJhbGwubWJiX3BicF9zcmAKV0hFUkUgc2hvdF90eXBlIElTIE5PVCBOVUxMCkFORCBldmVudF9jb29yZF94IElTIE5PVCBOVUxMCkFORCBldmVudF9jb29yZF95IElTIE5PVCBOVUxMCkFORCBJRihldmVudF9jb29yZF94IDwgNTY0LCAnbGVmdCcsICdyaWdodCcpID0gdGVhbV9iYXNrZXQKQU5EIHNjaGVkdWxlZF9kYXRlIDwgJzIwMTgtMDMtMTUnCkdST1VQIEJZIHgsIHkKT1JERVIgQlkgYXR0ZW1wdHMgREVTQywgYXZnX3BvaW50cyBERVNDCmBgYAoKCmBgYHtyfQpmaWxlX3BhdGhfaGFsZiA8LSAiaGFsZl9jb3VydC5jc3YiCmRmX2hhbGYgPC0gcmVhZF9jc3YoZmlsZV9wYXRoX2hhbGYsIHByb2dyZXNzPU5BKQpkZl9oYWxmICU+JSBoZWFkKCkKYGBgCgpgYGB7cn0KZGZfaGFsZiA8LSBkZl9oYWxmICU+JSBtdXRhdGUoCiAgeCA9IHJlc2NhbGUoeCwgdG8gPSBjKDAsIDQ3KSksCiAgeSA9IHJlc2NhbGUoeSwgdG8gPSBjKDAsIDUwKSksCiAgcGVyY19zdWNjZXNzID0gc3VjY2Vzc2VzL2F0dGVtcHRzCikKCmRmX2hhbGYgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQpwbG90IDwtIFBfaGFsZiArCiAgICAgICAgICBzdGF0X3N1bW1hcnlfMmQoZGF0YT1kZl9oYWxmLCBhZXMoeD15LCB5PXgsIHo9YXR0ZW1wdHMpLCBhbHBoYT0wLjgsIGJpbnM9MTAwLCBmdW49c3VtKSArCiAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uPSdpbmZlcm5vJywgZW5kPTEsIGxhYmVscz1jb21tYSwgdHJhbnM9J2xvZzEwJykgKwogICAgICAgICAgbGFicyh0aXRsZT1zcHJpbnRmKCdIZWF0IE1hcCBvZiAlcyBCYXNrZXRiYWxsIFNob3RzIGZyb20gTkNBQSBHYW1lcycsIGRmX2hhbGYgJT4lIHB1bGwoYXR0ZW1wdHMpICU+JSBzdW0oKSAlPiUgY29tbWEoKSksCiAgICAgICAgICAgICAgICBzdWJ0aXRsZT0nU3RhcnRpbmcgd2l0aCB0aGUgMjAxMy0xNCBzZWFzb24uIFZpYSBTcG9ydHJhZGFyIGRhdGEgaW4gQmlnUXVlcnknLAogICAgICAgICAgICAgICBmaWxsPScjIG9mIDJwdC8zcHQgU2hvdCBBdHRlbXB0c1xuTWFkZSBGcm9tIFNwb3QnLAogICAgICAgICAgICAgICBjYXB0aW9uID0gIk1heCBXb29sZiDigJQgbWluaW1heGlyLmNvbSIpICsKICAgICAgICAgIGJiX3RoZW1lCgpnZ3NhdmUoJ25jYWFfY291bnRfYXR0ZW1wdHNfaGFsZl9sb2cucG5nJywgcGxvdCwgd2lkdGg9NCwgaGVpZ2h0PTQpCmBgYAoKIVtdKG5jYWFfY291bnRfYXR0ZW1wdHNfaGFsZl9sb2cucG5nKQoKIyMgQmFyIENoYXJ0IG9mIFNob3QgVHlwZXMKCkJpZ1F1ZXJ5OgoKYGBge3NxbCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CiNzdGFuZGFyZFNRTApTRUxFQ1Qgc2hvdF90eXBlLAogICAgICAgIElGKGV2ZW50X2Nvb3JkX3ggPCA1NjQsIENBU1QoZXZlbnRfY29vcmRfeCBhcyBpbnQ2NCksIDExMjggLSBDQVNUKGV2ZW50X2Nvb3JkX3ggYXMgaW50NjQpKSBhcyB4LAogICAgICAgIElGKGV2ZW50X2Nvb3JkX3ggPCA1NjQsIDYwMCAtIENBU1QoZXZlbnRfY29vcmRfeSBhcyBpbnQ2NCksIENBU1QoZXZlbnRfY29vcmRfeSBhcyBpbnQ2NCkpIGFzIHksCiAgICAgICAgQ09VTlQoKikgYXMgYXR0ZW1wdHMsCiAgICAgICAgQ09VTlRJRihwb2ludHNfc2NvcmVkIElTIE5PVCBOVUxMKSBhcyBzdWNjZXNzZXMsIAogICAgICAgIEFWRyhJRk5VTEwoQ0FTVChwb2ludHNfc2NvcmVkIGFzIGludDY0KSwgMCkpIGFzIGF2Z19wb2ludHMKRlJPTSBgYmlncXVlcnktcHVibGljLWRhdGEubmNhYV9iYXNrZXRiYWxsLm1iYl9wYnBfc3JgCldIRVJFIHNob3RfdHlwZSBJUyBOT1QgTlVMTApBTkQgZXZlbnRfY29vcmRfeCBJUyBOT1QgTlVMTApBTkQgZXZlbnRfY29vcmRfeSBJUyBOT1QgTlVMTApBTkQgSUYoZXZlbnRfY29vcmRfeCA8IDU2NCwgJ2xlZnQnLCAncmlnaHQnKSA9IHRlYW1fYmFza2V0CkFORCBzY2hlZHVsZWRfZGF0ZSA8ICcyMDE4LTAzLTE1JwpHUk9VUCBCWSBzaG90X3R5cGUsIHgsIHkKT1JERVIgQlkgYXR0ZW1wdHMgREVTQywgYXZnX3BvaW50cyBERVNDCmBgYAoKCmBgYHtyfQpmaWxlX3BhdGhfaGFsZl90eXBlcyA8LSAiaGFsZl90eXBlcy5jc3YiCmRmX2hhbGZfdHlwZXMgPC0gcmVhZF9jc3YoZmlsZV9wYXRoX2hhbGZfdHlwZXMsIHByb2dyZXNzPU5BKQpkZl9oYWxmX3R5cGVzICU+JSBoZWFkKCkKYGBgCgpgYGB7cn0KZGZfaGFsZl90eXBlc19hZ2cgPC0gZGZfaGFsZl90eXBlcyAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoc2hvdF90eXBlKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXplKHRvdGFsX2F0dGVtcHRzID0gc3VtKGF0dGVtcHRzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX3N1Y2Nlc3NlcyA9IHN1bShzdWNjZXNzZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGVyY19zdWNjZXNzID0gdG90YWxfc3VjY2Vzc2VzL3RvdGFsX2F0dGVtcHRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZnX3BvaW50cyA9IG1lYW4oYXZnX3BvaW50cykpICU+JQogICAgICAgICAgICAgICAgICAgICAgICB1bmdyb3VwKCkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShwcm9wX2F0dGVtcHRzID0gdG90YWxfYXR0ZW1wdHMvc3VtKHRvdGFsX2F0dGVtcHRzKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGFycmFuZ2UoZGVzYyh0b3RhbF9hdHRlbXB0cykpIAoKZGZfaGFsZl90eXBlc19hZ2ckc2hvdF90eXBlIDwtIGZhY3RvcihkZl9oYWxmX3R5cGVzX2FnZyRzaG90X3R5cGUsIGxldmVscz1yZXYoZGZfaGFsZl90eXBlc19hZ2ckc2hvdF90eXBlKSkKYGBgCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfaGFsZl90eXBlc19hZ2csIGFlcyh4PXNob3RfdHlwZSwgeT1wcm9wX2F0dGVtcHRzLCBmaWxsPXNob3RfdHlwZSwgY29sb3I9c2hvdF90eXBlKSkgKwogICAgICAgICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBjb2xvcj1OQSkgKwogICAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1zcHJpbnRmKCclMC4xZiUlJywgcHJvcF9hdHRlbXB0cyoxMDApKSwgc2l6ZT0zLCBmYW1pbHk9IlNvdXJjZSBTYW5zIFBybyBCb2xkIiwgaGp1c3Q9LTAuMjUpICsKICAgICAgICAgIGNvb3JkX2ZsaXAoKSArCiAgICAgICAgICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIsIGd1aWRlPUYpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiLCBndWlkZT1GKSArCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzPXBlcmNlbnQsIGxpbWl0cz1jKDAsMSkpICsKICAgICAgICAgIGxhYnModGl0bGU9c3ByaW50ZignUHJvcG9ydGlvbiBvZiAlcyBCYXNrZXRiYWxsIFNob3RzIGJ5IFR5cGUgZnJvbSBOQ0FBIEdhbWVzJywgZGZfaGFsZl90eXBlc19hZ2cgJT4lIHB1bGwodG90YWxfYXR0ZW1wdHMpICU+JSBzdW0oKSAlPiUgY29tbWEoKSksCiAgICAgICAgICAgICAgICBzdWJ0aXRsZT0nU3RhcnRpbmcgd2l0aCB0aGUgMjAxMy0xNCBzZWFzb24uIFZpYSBTcG9ydHJhZGFyIGRhdGEgaW4gQmlnUXVlcnknLAogICAgICAgICAgICAgICBjYXB0aW9uID0gIk1heCBXb29sZiDigJQgbWluaW1heGlyLmNvbSIsCiAgICAgICAgICAgICAgIHkgPSAiUHJvcG9ydGlvbiBvZiBTaG90IEF0dGVtcHRzIE1hZGUiKSArCiAgICAgICAgICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpCgpnZ3NhdmUoJ25jYWFfdHlwZXNfcHJvcF9hdHRlbXB0cy5wbmcnLCBwbG90LCB3aWR0aD00LCBoZWlnaHQ9MikKYGBgCgohW10obmNhYV90eXBlc19wcm9wX2F0dGVtcHRzLnBuZykKCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9oYWxmX3R5cGVzX2FnZywgYWVzKHg9c2hvdF90eXBlLCB5PXBlcmNfc3VjY2VzcywgZmlsbD1zaG90X3R5cGUpKSArCiAgICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICAgICAgICAgIGdlb21fdGV4dChhZXMobGFiZWw9c3ByaW50ZignJTAuMWYlJScsIHBlcmNfc3VjY2VzcyoxMDApKSwgc2l6ZT0zLCBjb2xvcj0id2hpdGUiLCBmYW1pbHk9IlNvdXJjZSBTYW5zIFBybyBCb2xkIiwgaGp1c3Q9MS4yNSkgKwogICAgICAgICAgY29vcmRfZmxpcCgpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiLCBndWlkZT1GKSArCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzPXBlcmNlbnQsIGxpbWl0cz1jKDAsMSkpICsKICAgICAgICAgIGxhYnModGl0bGU9c3ByaW50ZignU2hvdCBTdWNjZXNzIG9mICVzIEJhc2tldGJhbGwgU2hvdHMgYnkgVHlwZSBmcm9tIE5DQUEgR2FtZXMnLCBkZl9oYWxmX3R5cGVzX2FnZyAlPiUgcHVsbCh0b3RhbF9hdHRlbXB0cykgJT4lIHN1bSgpICU+JSBjb21tYSgpKSwKICAgICAgICAgICAgICAgIHN1YnRpdGxlPSdTdGFydGluZyB3aXRoIHRoZSAyMDEzLTE0IHNlYXNvbi4gVmlhIFNwb3J0cmFkYXIgZGF0YSBpbiBCaWdRdWVyeScsCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIiwKICAgICAgICAgICAgICAgeSA9ICIlIG9mIFN1Y2Nlc3NmdWwgU2hvdHMgTWFkZSBieSBUeXBlIikgKwogICAgICAgICAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKQoKZ2dzYXZlKCduY2FhX3R5cGVzX3BlcmMucG5nJywgcGxvdCwgd2lkdGg9NCwgaGVpZ2h0PTIpCmBgYAoKIVtdKG5jYWFfdHlwZXNfcGVyYy5wbmcpCgojIyBIYWxmIENvdXJ0LCBGYWNldGVkIGJ5IFR5cGUgb2YgU2hvdAoKYGBge3J9CmRmX2hhbGZfdHlwZXMgPC0gZGZfaGFsZl90eXBlcyAlPiUgbXV0YXRlKAogIHggPSByZXNjYWxlKHgsIHRvID0gYygwLCA0NykpLAogIHkgPSByZXNjYWxlKHksIHRvID0gYygwLCA1MCkpLAogIHBlcmNfc3VjY2VzcyA9IHN1Y2Nlc3Nlcy9hdHRlbXB0cywKICBzaG90X3R5cGUgPSBmYWN0b3Ioc2hvdF90eXBlLCBsZXZlbHM9ZGZfaGFsZl90eXBlc19hZ2ckc2hvdF90eXBlKQopCgpkZl9oYWxmX3R5cGVzICU+JSBoZWFkKCkKYGBgCgoKYGBge3J9CnBsb3QgPC0gUF9oYWxmICsKICAgICAgICAgIHN0YXRfc3VtbWFyeV8yZChkYXRhPWRmX2hhbGZfdHlwZXMsIGFlcyh4PXksIHk9eCwgej1hdHRlbXB0cyksIGFscGhhPTAuOCwgYmlucz0xMDAsIGZ1bj1zdW0pICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb249J2luZmVybm8nLCBlbmQ9MSwgbGFiZWxzPWNvbW1hLCB0cmFucz0nbG9nMTAnKSArCiAgICAgICAgICBmYWNldF93cmFwKH4gc2hvdF90eXBlLCBuY29sPTIpICsKICAgICAgICAgIGxhYnModGl0bGU9c3ByaW50ZignSGVhdCBNYXAgb2YgJXMgQmFza2V0YmFsbCBTaG90cyBmcm9tIE5DQUEgR2FtZXMnLCBkZl9oYWxmX3R5cGVzICU+JSBwdWxsKGF0dGVtcHRzKSAlPiUgc3VtKCkgJT4lIGNvbW1hKCkpLAogICAgICAgICAgICAgICAgc3VidGl0bGU9J1N0YXJ0aW5nIHdpdGggdGhlIDIwMTMtMTQgc2Vhc29uLiBWaWEgU3BvcnRyYWRhciBkYXRhIGluIEJpZ1F1ZXJ5JywKICAgICAgICAgICAgICAgZmlsbD0nIyBvZiAycHQvM3B0IFNob3QgQXR0ZW1wdHNcbk1hZGUgRnJvbSBTcG90JywKICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iKSArCiAgICAgICAgICBiYl90aGVtZSArCiAgICAgICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9NykpCgpnZ3NhdmUoJ25jYWFfY291bnRfYXR0ZW1wdHNfaGFsZl90eXBlc19sb2cucG5nJywgcGxvdCwgd2lkdGg9MywgaGVpZ2h0PTUpCmBgYAoKIVtdKG5jYWFfY291bnRfYXR0ZW1wdHNfaGFsZl90eXBlc19sb2cucG5nKQoKIyMgSGFsZiBDb3VydCwgRmFjZXRlZCBieSAxMCBNaW51dGUgVGltZSBCdWNrZXRzLgoKQmlnUXVlcnk6CgpgYGB7c3FsIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KI3N0YW5kYXJkU1FMClNFTEVDVCBJRihlbGFwc2VkX3RpbWVfc2VjID49IDYwKjQwLCA5OSwgQ0FTVChUUlVOQygoZWxhcHNlZF90aW1lX3NlYy0xKS8zMDApIEFTIGludDY0KSkgYXMgbWluX2ludGVydmFsLAogICAgICAgIElGKGV2ZW50X2Nvb3JkX3ggPCA1NjQsIENBU1QoZXZlbnRfY29vcmRfeCBhcyBpbnQ2NCksIDExMjggLSBDQVNUKGV2ZW50X2Nvb3JkX3ggYXMgaW50NjQpKSBhcyB4LAogICAgICAgIElGKGV2ZW50X2Nvb3JkX3ggPCA1NjQsIDYwMCAtIENBU1QoZXZlbnRfY29vcmRfeSBhcyBpbnQ2NCksIENBU1QoZXZlbnRfY29vcmRfeSBhcyBpbnQ2NCkpIGFzIHksCiAgICAgICAgc2hvdF90eXBlLAogICAgICAgIENPVU5UKCopIGFzIGF0dGVtcHRzLAogICAgICAgIENPVU5USUYocG9pbnRzX3Njb3JlZCBJUyBOT1QgTlVMTCkgYXMgc3VjY2Vzc2VzLCAKICAgICAgICBBVkcoSUZOVUxMKENBU1QocG9pbnRzX3Njb3JlZCBhcyBpbnQ2NCksIDApKSBhcyBhdmdfcG9pbnRzCkZST00gYGJpZ3F1ZXJ5LXB1YmxpYy1kYXRhLm5jYWFfYmFza2V0YmFsbC5tYmJfcGJwX3NyYApXSEVSRSBzaG90X3R5cGUgSVMgTk9UIE5VTEwKQU5EIGV2ZW50X2Nvb3JkX3ggSVMgTk9UIE5VTEwKQU5EIGV2ZW50X2Nvb3JkX3kgSVMgTk9UIE5VTEwKQU5EIElGKGV2ZW50X2Nvb3JkX3ggPCA1NjQsICdsZWZ0JywgJ3JpZ2h0JykgPSB0ZWFtX2Jhc2tldApBTkQgc2NoZWR1bGVkX2RhdGUgPCAnMjAxOC0wMy0xNScKR1JPVVAgQlkgbWluX2ludGVydmFsLCBzaG90X3R5cGUsIHgsIHkKT1JERVIgQlkgYXR0ZW1wdHMgREVTQywgYXZnX3BvaW50cyBERVNDCmBgYAoKYGBge3J9CmZpbGVfcGF0aF9oYWxmX2ludGVydmFsIDwtICJoYWxmX2ludGVydmFsLmNzdiIKZGZfaGFsZl9pbnRlcnZhbCA8LSByZWFkX2NzdihmaWxlX3BhdGhfaGFsZl9pbnRlcnZhbCwgcHJvZ3Jlc3M9TkEpCmRmX2hhbGZfaW50ZXJ2YWwgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQppbnRlcnZhbF9sZXZlbHMgPSBjKCcxc3QgSGFsZlxuMjA6MDAg4oCUIDE1OjAwJywKICAgICAgICAgICAgICAgICAgICAnMXN0IEhhbGZcbjE0OjU5IOKAlCAxMDowMCcsCiAgICAgICAgICAgICAgICAgICAgJzFzdCBIYWxmXG4wOTo1OSDigJQgMDU6MDAnLAogICAgICAgICAgICAgICAgICAgICcxc3QgSGFsZlxuMDQ6NTkg4oCUIDAwOjAwJywKICAgICAgICAgICAgICAgICAgICAnMm5kIEhhbGZcbjIwOjAwIOKAlCAxNTowMCcsCiAgICAgICAgICAgICAgICAgICAgJzJuZCBIYWxmXG4xNDo1OSDigJQgMTA6MDAnLAogICAgICAgICAgICAgICAgICAgICcybmQgSGFsZlxuMDk6NTkg4oCUIDA1OjAwJywKICAgICAgICAgICAgICAgICAgICAnMm5kIEhhbGZcbjA0OjU5IOKAlCAwMDowMCcsCiAgICAgICAgICAgICAgICAgICAgJ09UJykKCgpkZl9oYWxmX2ludGVydmFsIDwtIGRmX2hhbGZfaW50ZXJ2YWwgJT4lIG11dGF0ZSgKICB4ID0gcmVzY2FsZSh4LCB0byA9IGMoMCwgNDcpKSwKICB5ID0gcmVzY2FsZSh5LCB0byA9IGMoMCwgNTApKSwKICBwZXJjX3N1Y2Nlc3MgPSBzdWNjZXNzZXMvYXR0ZW1wdHMsCiAgbWluX2ludGVydmFsID0gZmFjdG9yKG1pbl9pbnRlcnZhbCwgbGFiZWxzID0gaW50ZXJ2YWxfbGV2ZWxzKSwKICBzaG90X3R5cGUgPSBmYWN0b3Ioc2hvdF90eXBlLCBsZXZlbHM9ZGZfaGFsZl90eXBlc19hZ2ckc2hvdF90eXBlKQopCgpkZl9oYWxmX2ludGVydmFsICU+JSBoZWFkKCkKYGBgCgpgYGB7cn0KcGxvdCA8LSBQX2hhbGYgKwogICAgICAgICAgc3RhdF9zdW1tYXJ5XzJkKGRhdGE9ZGZfaGFsZl9pbnRlcnZhbCwgYWVzKHg9eSwgeT14LCB6PWF0dGVtcHRzKSwgYWxwaGE9MC44LCBiaW5zPTI1LCBmdW49c3VtKSArCiAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uPSdpbmZlcm5vJywgZW5kPTEsIGxhYmVscz1jb21tYSwgdHJhbnM9J2xvZzEwJykgKwogICAgICAgICAgZmFjZXRfZ3JpZChtaW5faW50ZXJ2YWwgfiBzaG90X3R5cGUpICsKICAgICAgICAgIGxhYnModGl0bGU9c3ByaW50ZignSGVhdCBNYXAgb2YgJXMgQmFza2V0YmFsbCBTaG90cyBmcm9tIE5DQUEgR2FtZXMnLCBkZl9oYWxmX2ludGVydmFsICU+JSBwdWxsKGF0dGVtcHRzKSAlPiUgc3VtKCkgJT4lIGNvbW1hKCkpLAogICAgICAgICAgICAgICAgc3VidGl0bGU9J0ZhY2V0IHRpdGxlIHJlcHJlc2VudHMgZ2FtZSB0aW1lIGF0IHNob3QuXG5WaWEgU3BvcnRyYWRhciBkYXRhIGluIEJpZ1F1ZXJ5JywKICAgICAgICAgICAgICAgZmlsbD0nIyBvZiAycHQvM3B0IFNob3QgQXR0ZW1wdHNcbk1hZGUgRnJvbSBTcG90JywKICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iKSArCiAgICAgICAgICBiYl90aGVtZQoKZ2dzYXZlKCduY2FhX2NvdW50X2F0dGVtcHRzX2hhbGZfaW50ZXJ2YWxfbG9nLnBuZycsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD03KQpgYGAKCiFbXShuY2FhX2NvdW50X2F0dGVtcHRzX2hhbGZfaW50ZXJ2YWxfbG9nLnBuZykKCiMjIEhhbGYgQ291cnQsIGJ5IFBvaW50IERlbHRhIGF0IFRpbWUgb2YgU2hvdAoKQmlnUXVlcnk6CgpgYGB7c3FsIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KI3N0YW5kYXJkU1FMClNFTEVDVCBDQVNFIFdIRU4gc2NvcmVfZGVsdGEgPCAtMjAgVEhFTiAnPC0yMCcKICAgICAgICAgICAgV0hFTiBzY29yZV9kZWx0YSA+PSAtMjAgQU5EIHNjb3JlX2RlbHRhIDwgLTEwIFRIRU4gJy0yMCDigJQgLTExJwogICAgICAgICAgICBXSEVOIHNjb3JlX2RlbHRhID49IC0xMCBBTkQgc2NvcmVfZGVsdGEgPCAwIFRIRU4gJy0xMCDigJQgLTEnCiAgICAgICAgICAgIFdIRU4gc2NvcmVfZGVsdGEgPSAwIFRIRU4gJzAnCiAgICAgICAgICAgIFdIRU4gc2NvcmVfZGVsdGEgPj0gMSBBTkQgc2NvcmVfZGVsdGEgPD0gMTAgVEhFTiAnMSDigJQgMTAnCiAgICAgICAgICAgIFdIRU4gc2NvcmVfZGVsdGEgPiAxMCBBTkQgc2NvcmVfZGVsdGEgPD0gMjAgVEhFTiAnMTEg4oCUIDIwJwogICAgICAgICAgICBXSEVOIHNjb3JlX2RlbHRhID4gMjAgIFRIRU4gJz4yMCcKICAgICAgICAgICAgRU5ECiAgICAgICAgICAgIEFTIHNjb3JlX2RlbHRhX2ludGVydmFsLAogICAgICAgIHNob3RfdHlwZSwKICAgICAgICBJRihldmVudF9jb29yZF94IDwgNTY0LCBDQVNUKGV2ZW50X2Nvb3JkX3ggYXMgaW50NjQpLCAxMTI4IC0gQ0FTVChldmVudF9jb29yZF94IGFzIGludDY0KSkgYXMgeCwKICAgICAgICBJRihldmVudF9jb29yZF94IDwgNTY0LCA2MDAgLSBDQVNUKGV2ZW50X2Nvb3JkX3kgYXMgaW50NjQpLCBDQVNUKGV2ZW50X2Nvb3JkX3kgYXMgaW50NjQpKSBhcyB5LAogICAgICAgIENPVU5UKCopIGFzIGF0dGVtcHRzLAogICAgICAgIENPVU5USUYocG9pbnRzX3Njb3JlZCBJUyBOT1QgTlVMTCkgYXMgc3VjY2Vzc2VzLCAKICAgICAgICBBVkcoSUZOVUxMKENBU1QocG9pbnRzX3Njb3JlZCBhcyBpbnQ2NCksIDApKSBhcyBhdmdfcG9pbnRzCkZST00gKApTRUxFQ1QgKiwgdGVhbV9zY29yZSAtIChnYW1lX3Njb3JlIC0gdGVhbV9zY29yZSkgYXMgc2NvcmVfZGVsdGEKICBGUk9NICgKICAgIFNFTEVDVCBldmVudF9jb29yZF94LCBldmVudF9jb29yZF95LCBwb2ludHNfc2NvcmVkLCBzaG90X3R5cGUsIHRlYW1fYmFza2V0LCBzY2hlZHVsZWRfZGF0ZSwKICAgIFNVTShJRk5VTEwoQ0FTVChwb2ludHNfc2NvcmVkIGFzIGludDY0KSwgMCkpIE9WRVIgKFBBUlRJVElPTiBCWSBnYW1lX2lkLCB0ZWFtX2lkIE9SREVSIEJZIHRpbWVzdGFtcCkgLSBJRk5VTEwoQ0FTVChwb2ludHNfc2NvcmVkIGFzIGludDY0KSwgMCkgYXMgdGVhbV9zY29yZSwKICAgIFNVTShJRk5VTEwoQ0FTVChwb2ludHNfc2NvcmVkIGFzIGludDY0KSwgMCkpIE9WRVIgKFBBUlRJVElPTiBCWSBnYW1lX2lkIE9SREVSIEJZIHRpbWVzdGFtcCkgLSBJRk5VTEwoQ0FTVChwb2ludHNfc2NvcmVkIGFzIGludDY0KSwgMCkgYXMgZ2FtZV9zY29yZQogICAgRlJPTSBgYmlncXVlcnktcHVibGljLWRhdGEubmNhYV9iYXNrZXRiYWxsLm1iYl9wYnBfc3JgCiAgKQopCldIRVJFIHNob3RfdHlwZSBJUyBOT1QgTlVMTApBTkQgZXZlbnRfY29vcmRfeCBJUyBOT1QgTlVMTApBTkQgZXZlbnRfY29vcmRfeSBJUyBOT1QgTlVMTApBTkQgSUYoZXZlbnRfY29vcmRfeCA8IDU2NCwgJ2xlZnQnLCAncmlnaHQnKSA9IHRlYW1fYmFza2V0CkFORCBzY2hlZHVsZWRfZGF0ZSA8ICcyMDE4LTAzLTE1JwpHUk9VUCBCWSBzY29yZV9kZWx0YV9pbnRlcnZhbCwgc2hvdF90eXBlLCB4LCB5Ck9SREVSIEJZIGF0dGVtcHRzIERFU0MsIGF2Z19wb2ludHMgREVTQwpgYGAKCmBgYHtyfQpmaWxlX3BhdGhfaGFsZl9zY29yZSA8LSAiaGFsZl9zY29yZS5jc3YiCmRmX2hhbGZfc2NvcmUgPC0gcmVhZF9jc3YoZmlsZV9wYXRoX2hhbGZfc2NvcmUsIHByb2dyZXNzPU5BKQpkZl9oYWxmX3Njb3JlICU+JSBoZWFkKCkKYGBgCgpgYGB7cn0Kc2NvcmVfbGV2ZWxzIDwtIGMoJzwtMjAnLCAnLTIwIOKAlCAtMTEnLCAnLTEwIOKAlCAtMScsICcwJywgJzEg4oCUIDEwJywgJzExIOKAlCAyMCcsICc+MjAnKQoKZGZfaGFsZl9zY29yZSA8LSBkZl9oYWxmX3Njb3JlICU+JSBtdXRhdGUoCiAgeCA9IHJlc2NhbGUoeCwgdG8gPSBjKDAsIDQ3KSksCiAgeSA9IHJlc2NhbGUoeSwgdG8gPSBjKDAsIDUwKSksCiAgcGVyY19zdWNjZXNzID0gc3VjY2Vzc2VzL2F0dGVtcHRzLAogIHNjb3JlX2RlbHRhX2ludGVydmFsID0gZmFjdG9yKHNjb3JlX2RlbHRhX2ludGVydmFsLCBsZXZlbHMgPSBzY29yZV9sZXZlbHMpLAogIHNob3RfdHlwZSA9IGZhY3RvcihzaG90X3R5cGUsIGxldmVscz1kZl9oYWxmX3R5cGVzX2FnZyRzaG90X3R5cGUpCikKCmRmX2hhbGZfc2NvcmUgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQpwbG90IDwtIFBfaGFsZiArCiAgICAgICAgICBzdGF0X3N1bW1hcnlfMmQoZGF0YT1kZl9oYWxmX3Njb3JlLCBhZXMoeD15LCB5PXgsIHo9YXR0ZW1wdHMpLCBhbHBoYT0wLjgsIGJpbnM9MjUsIGZ1bj1zdW0pICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb249J2luZmVybm8nLCBlbmQ9MSwgbGFiZWxzPWNvbW1hLCB0cmFucz0nbG9nMTAnKSArCiAgICAgICAgICBmYWNldF9ncmlkKHNjb3JlX2RlbHRhX2ludGVydmFsIH4gc2hvdF90eXBlKSArCiAgICAgICAgICBsYWJzKHRpdGxlPXNwcmludGYoJ0hlYXQgTWFwIG9mICVzIEJhc2tldGJhbGwgU2hvdHMgZnJvbSBOQ0FBIEdhbWVzJywgZGZfaGFsZl9zY29yZSAlPiUgcHVsbChhdHRlbXB0cykgJT4lIHN1bSgpICU+JSBjb21tYSgpKSwKICAgICAgICAgICAgICAgIHN1YnRpdGxlPSdGYWNldCB0aXRsZSByZXByZXNlbnRzIHRlYW0gc2NvcmUgZGlmZmVyZW5jZSByZWxhdGl2ZSB0byBvdGhlciB0ZWFtIGJlZm9yZSBzaG90LlxuVmlhIFNwb3J0cmFkYXIgZGF0YSBpbiBCaWdRdWVyeScsCiAgICAgICAgICAgICAgIGZpbGw9JyMgb2YgMnB0LzNwdCBTaG90IEF0dGVtcHRzXG5NYWRlIEZyb20gU3BvdCcsCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIikgKwogICAgICAgICAgYmJfdGhlbWUKCmdnc2F2ZSgnbmNhYV9jb3VudF9hdHRlbXB0c19oYWxmX3Njb3JlX2xvZy5wbmcnLCBwbG90LCB3aWR0aD00LCBoZWlnaHQ9NS41KQpgYGAKCiFbXShuY2FhX2NvdW50X2F0dGVtcHRzX2hhbGZfc2NvcmVfbG9nLnBuZykKCiMjIEJhciBDaGFydHMgb2YgRWxhcHNlZCBUaW1lLCBGYWNldGVkIGJ5IFNob3QgVHlwZQoKYGBge3J9CmRmX2hhbGZfaW50ZXJ2YWxfYWdnIDwtIGRmX2hhbGZfaW50ZXJ2YWwgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KG1pbl9pbnRlcnZhbCwgc2hvdF90eXBlKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXplKHRvdGFsX2F0dGVtcHRzID0gc3VtKGF0dGVtcHRzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX3N1Y2Nlc3NlcyA9IHN1bShzdWNjZXNzZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGVyY19zdWNjZXNzID0gdG90YWxfc3VjY2Vzc2VzL3RvdGFsX2F0dGVtcHRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZnX3BvaW50cyA9IG1lYW4oYXZnX3BvaW50cykpICU+JQogICAgICAgICAgICAgICAgICAgICAgICBhcnJhbmdlKGRlc2MocGVyY19zdWNjZXNzKSkgICU+JQogICAgICAgICAgICAgICAgICAgICAgICB1bmdyb3VwKCkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KG1pbl9pbnRlcnZhbCkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShwcm9wX2F0dGVtcHRzID0gdG90YWxfYXR0ZW1wdHMvc3VtKHRvdGFsX2F0dGVtcHRzKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGFycmFuZ2UoZGVzYyh0b3RhbF9hdHRlbXB0cykpIAoKZGZfaGFsZl9pbnRlcnZhbF9hZ2ckc2hvdF90eXBlIDwtIGZhY3RvcihkZl9oYWxmX2ludGVydmFsX2FnZyRzaG90X3R5cGUsIGxldmVscz1yZXYoZGZfaGFsZl90eXBlc19hZ2ckc2hvdF90eXBlKSkKYGBgCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfaGFsZl9pbnRlcnZhbF9hZ2csIGFlcyh4PXNob3RfdHlwZSwgeT1wZXJjX3N1Y2Nlc3MsIGZpbGw9c2hvdF90eXBlKSkgKwogICAgICAgICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsPXNwcmludGYoJyUwLjFmJSUnLCBwZXJjX3N1Y2Nlc3MqMTAwKSksIHNpemU9MiwgY29sb3I9IndoaXRlIiwgZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gQm9sZCIsIGhqdXN0PTEuMDUpICsKICAgICAgICAgIGNvb3JkX2ZsaXAoKSArCiAgICAgICAgICBmYWNldF93cmFwKH4gbWluX2ludGVydmFsKSArCiAgICAgICAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQxIiwgZ3VpZGU9RikgKwogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1wZXJjZW50LCBsaW1pdHM9YygwLDEpKSArCiAgICAgICAgICBsYWJzKHRpdGxlPXNwcmludGYoJ1Nob3QgU3VjY2VzcyBvZiAlcyBCYXNrZXRiYWxsIFNob3RzIGJ5IFR5cGUgZnJvbSBOQ0FBIEdhbWVzJywgZGZfaGFsZl9pbnRlcnZhbF9hZ2cgJT4lIHB1bGwodG90YWxfYXR0ZW1wdHMpICU+JSBzdW0oKSAlPiUgY29tbWEoKSksCiAgICAgICAgICAgICAgICBzdWJ0aXRsZT0nRmFjZXQgdGl0bGUgcmVwcmVzZW50cyBnYW1lIHRpbWUgYXQgc2hvdC5cblZpYSBTcG9ydHJhZGFyIGRhdGEgaW4gQmlnUXVlcnknLAogICAgICAgICAgICAgICBjYXB0aW9uID0gIk1heCBXb29sZiDigJQgbWluaW1heGlyLmNvbSIsCiAgICAgICAgICAgICAgIHkgPSAiJSBvZiBTdWNjZXNzZnVsIFNob3RzIE1hZGUgYnkgVHlwZSIpICsKICAgICAgICAgIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSkKCmdnc2F2ZSgnbmNhYV90eXBlc19wZXJjX3N1Y2Nlc3NfdHlwZV9lbGFwc2VkLnBuZycsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD00KQpgYGAKCiFbXShuY2FhX3R5cGVzX3BlcmNfc3VjY2Vzc190eXBlX2VsYXBzZWQucG5nKQoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX2hhbGZfaW50ZXJ2YWxfYWdnLCBhZXMoeD1zaG90X3R5cGUsIHk9cHJvcF9hdHRlbXB0cywgZmlsbD1zaG90X3R5cGUsIGNvbG9yPXNob3RfdHlwZSkpICsKICAgICAgICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgY29sb3I9TkEpICsKICAgICAgICAgIGdlb21fdGV4dChhZXMobGFiZWw9c3ByaW50ZignJTAuMWYlJScsIHByb3BfYXR0ZW1wdHMqMTAwKSksIHNpemU9MiwgZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gQm9sZCIsIGhqdXN0PS0wLjI1KSArCiAgICAgICAgICBjb29yZF9mbGlwKCkgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IG1pbl9pbnRlcnZhbCkgKwogICAgICAgICAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IlNldDEiLCBndWlkZT1GKSArCiAgICAgICAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQxIiwgZ3VpZGU9RikgKwogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1wZXJjZW50LCBsaW1pdHM9YygwLDEpKSArCiAgICAgICAgICBsYWJzKHRpdGxlPXNwcmludGYoJ1Byb3BvcnRpb24gb2YgJXMgQmFza2V0YmFsbCBTaG90cyBieSBUeXBlIGZyb20gTkNBQSBHYW1lcycsIGRmX2hhbGZfaW50ZXJ2YWxfYWdnICU+JSBwdWxsKHRvdGFsX2F0dGVtcHRzKSAlPiUgc3VtKCkgJT4lIGNvbW1hKCkpLAogICAgICAgICAgICAgICAgc3VidGl0bGU9J0ZhY2V0IHRpdGxlIHJlcHJlc2VudHMgZ2FtZSB0aW1lIGF0IHNob3QuXG5WaWEgU3BvcnRyYWRhciBkYXRhIGluIEJpZ1F1ZXJ5JywKICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iLAogICAgICAgICAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgU2hvdCBBdHRlbXB0cyBNYWRlIikgKwogICAgICAgICAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKQoKZ2dzYXZlKCduY2FhX3R5cGVzX3Byb3BfdHlwZV9lbGFwc2VkLnBuZycsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD00KQpgYGAKCiFbXShuY2FhX3R5cGVzX3Byb3BfdHlwZV9lbGFwc2VkLnBuZykKCiMjIEJhciBDaGFydHMgb2YgU2NvcmUgRGVsdGEsIEZhY2V0ZWQgYnkgU2hvdCBUeXBlCgpgYGB7cn0KZGZfaGFsZl9zY29yZV9hZ2cgPC0gZGZfaGFsZl9zY29yZSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoc2NvcmVfZGVsdGFfaW50ZXJ2YWwsIHNob3RfdHlwZSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcml6ZSh0b3RhbF9hdHRlbXB0cyA9IHN1bShhdHRlbXB0cyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9zdWNjZXNzZXMgPSBzdW0oc3VjY2Vzc2VzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfc3VjY2VzcyA9IHRvdGFsX3N1Y2Nlc3Nlcy90b3RhbF9hdHRlbXB0cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2Z19wb2ludHMgPSBtZWFuKGF2Z19wb2ludHMpKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgYXJyYW5nZShkZXNjKHBlcmNfc3VjY2VzcykpICAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgdW5ncm91cCgpICU+JQogICAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieShzY29yZV9kZWx0YV9pbnRlcnZhbCkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShwcm9wX2F0dGVtcHRzID0gdG90YWxfYXR0ZW1wdHMvc3VtKHRvdGFsX2F0dGVtcHRzKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGFycmFuZ2UoZGVzYyh0b3RhbF9hdHRlbXB0cykpIAoKZGZfaGFsZl9zY29yZV9hZ2ckc2hvdF90eXBlIDwtIGZhY3RvcihkZl9oYWxmX3Njb3JlX2FnZyRzaG90X3R5cGUsIGxldmVscz1yZXYoZGZfaGFsZl90eXBlc19hZ2ckc2hvdF90eXBlKSkKYGBgCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfaGFsZl9zY29yZV9hZ2csIGFlcyh4PXNob3RfdHlwZSwgeT1wZXJjX3N1Y2Nlc3MsIGZpbGw9c2hvdF90eXBlKSkgKwogICAgICAgICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsPXNwcmludGYoJyUwLjFmJSUnLCBwZXJjX3N1Y2Nlc3MqMTAwKSksIHNpemU9MiwgY29sb3I9IndoaXRlIiwgZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gQm9sZCIsIGhqdXN0PTEuMDUpICsKICAgICAgICAgIGNvb3JkX2ZsaXAoKSArCiAgICAgICAgICBmYWNldF93cmFwKH4gc2NvcmVfZGVsdGFfaW50ZXJ2YWwpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiLCBndWlkZT1GKSArCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzPXBlcmNlbnQsIGxpbWl0cz1jKDAsMSkpICsKICAgICAgICAgIGxhYnModGl0bGU9c3ByaW50ZignU2hvdCBTdWNjZXNzIG9mICVzIEJhc2tldGJhbGwgU2hvdHMgYnkgVHlwZSBmcm9tIE5DQUEgR2FtZXMnLCBkZl9oYWxmX2ludGVydmFsX2FnZyAlPiUgcHVsbCh0b3RhbF9hdHRlbXB0cykgJT4lIHN1bSgpICU+JSBjb21tYSgpKSwKICAgICAgICAgICAgICAgIHN1YnRpdGxlPSdGYWNldCB0aXRsZSByZXByZXNlbnRzIHRlYW0gc2NvcmUgZGlmZmVyZW5jZSByZWxhdGl2ZSB0byBvdGhlciB0ZWFtIGJlZm9yZSBzaG90LlxuVmlhIFNwb3J0cmFkYXIgZGF0YSBpbiBCaWdRdWVyeScsCiAgICAgICAgICAgICAgIGNhcHRpb24gPSAiTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIiwKICAgICAgICAgICAgICAgeSA9ICIlIG9mIFN1Y2Nlc3NmdWwgU2hvdHMgTWFkZSBieSBUeXBlIikgKwogICAgICAgICAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKQoKZ2dzYXZlKCduY2FhX3R5cGVzX3BlcmNfc3VjY2Vzc190eXBlX3Njb3JlLnBuZycsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD00KQpgYGAKCiFbXShuY2FhX3R5cGVzX3BlcmNfc3VjY2Vzc190eXBlX3Njb3JlLnBuZykKCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9oYWxmX3Njb3JlX2FnZywgYWVzKHg9c2hvdF90eXBlLCB5PXByb3BfYXR0ZW1wdHMsIGZpbGw9c2hvdF90eXBlLCBjb2xvcj1zaG90X3R5cGUpKSArCiAgICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGNvbG9yPU5BKSArCiAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsPXNwcmludGYoJyUwLjFmJSUnLCBwcm9wX2F0dGVtcHRzKjEwMCkpLCBzaXplPTIsIGZhbWlseT0iU291cmNlIFNhbnMgUHJvIEJvbGQiLCBoanVzdD0tMC4yNSkgKwogICAgICAgICAgY29vcmRfZmxpcCgpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBzY29yZV9kZWx0YV9pbnRlcnZhbCkgKwogICAgICAgICAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IlNldDEiLCBndWlkZT1GKSArCiAgICAgICAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQxIiwgZ3VpZGU9RikgKwogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1wZXJjZW50LCBsaW1pdHM9YygwLDEpKSArCiAgICAgICAgICBsYWJzKHRpdGxlPXNwcmludGYoJ1Byb3BvcnRpb24gb2YgJXMgQmFza2V0YmFsbCBTaG90cyBieSBUeXBlIGZyb20gTkNBQSBHYW1lcycsIGRmX2hhbGZfaW50ZXJ2YWxfYWdnICU+JSBwdWxsKHRvdGFsX2F0dGVtcHRzKSAlPiUgc3VtKCkgJT4lIGNvbW1hKCkpLAogICAgICAgICAgICAgICAgc3VidGl0bGU9J0ZhY2V0IHRpdGxlIHJlcHJlc2VudHMgdGVhbSBzY29yZSBkaWZmZXJlbmNlIHJlbGF0aXZlIHRvIG90aGVyIHRlYW0gYmVmb3JlIHNob3QuXG5WaWEgU3BvcnRyYWRhciBkYXRhIGluIEJpZ1F1ZXJ5JywKICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iLAogICAgICAgICAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgU2hvdCBBdHRlbXB0cyBNYWRlIGJ5IFR5cGUiKSArCiAgICAgICAgICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpCgpnZ3NhdmUoJ25jYWFfdHlwZXNfcHJvcF90eXBlX3Njb3JlLnBuZycsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD00KQpgYGAKCiFbXShuY2FhX3R5cGVzX3Byb3BfdHlwZV9zY29yZS5wbmcpCgojIyBCb251cyBRdWVyeSB0byBDaGVjayAlIG9mIENyb3NzIENvdXJ0IFNob3RzCgpgYGB7c3FsIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KI3N0YW5kYXJkU1FMClNFTEVDVCBpc19jcm9zc19jb3VydCwKICAgICAgQ09VTlQoKikgYXMgbnVtX2Jhc2tldHMKRlJPTSAoClNFTEVDVCB0ZWFtX2Jhc2tldCwgc2hvdF90eXBlLCBldmVudF9jb29yZF94LCBldmVudF9jb29yZF95LCBzY2hlZHVsZWRfZGF0ZSwKSUYoZXZlbnRfY29vcmRfeCA8IDU2NCwgJ2xlZnQnLCAncmlnaHQnKSAhPSB0ZWFtX2Jhc2tldCBhcyBpc19jcm9zc19jb3VydApGUk9NIGBiaWdxdWVyeS1wdWJsaWMtZGF0YS5uY2FhX2Jhc2tldGJhbGwubWJiX3BicF9zcmAKKQpXSEVSRSBzaG90X3R5cGUgSVMgTk9UIE5VTEwKQU5EIGV2ZW50X2Nvb3JkX3ggSVMgTk9UIE5VTEwKQU5EIGV2ZW50X2Nvb3JkX3kgSVMgTk9UIE5VTEwKQU5EIHNjaGVkdWxlZF9kYXRlIDwgJzIwMTgtMDMtMTUnCkdST1VQIEJZIGlzX2Nyb3NzX2NvdXJ0CmBgYAoKIyBMSUNFTlNFCgpUaGUgTUlUIExpY2Vuc2UgKE1JVCkKCkNvcHlyaWdodCAoYykgMjAxOCBNYXggV29vbGYKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkgb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwgaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cyB0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsIGNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcyBmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsIGNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUiBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIgTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUgU09GVFdBUkUu