Twice per week during the NFL season I publish a Substack newsletter called Monte Carlo Football Picks. On Wednesday I publish projections for the Thursday Night game and Friday includes the picks for the rest of the week. If you are interested in the NFL, please subscribe, it’s FREE!

For RSTATS fans I publish my code on RPubs every week. Check it out here.

Use this file to run Monte Carlo Simulations after power ratings are complete.

library(tidyverse)
library(tidyquant)
library(ggimage)
library(ggrepel)
library(knitr)
library(kableExtra)

Load required data files

ratings <- read_csv("Week 06/inputs/power_ratings.csv") 
schedule <- read_csv("Week 06/inputs/schedule.csv")
scores <- read_csv("Week 06/inputs/common_scores.csv", col_types = "n")
df.logos <- read.csv("Week 06/inputs/nfl_logos.csv")

Create function to simulate scores. In between the Thursday Night Game and Sunday games I updated the function to eliminate tie games

mc_sim <- function(home, away, data = ratings, line = 0, n = 100000, hfa = 0, mean = 25){
  
  #pull ratings together for both teams in matchup
  h_off <-ratings %>%
    filter(Team == home & Type == "off_rating") %>%
    select(Team, Type, MC_PR, MC_STD)
  
  h_def <-ratings %>%
    filter(Team == home & Type == "def_rating") %>%
    select(Team, Type, MC_PR, MC_STD)
  
  
  a_off <-ratings %>%
    filter(Team == away & Type == "off_rating") %>%
    select(Team, Type, MC_PR, MC_STD)
  
  a_def <-ratings %>%
    filter(Team == away & Type == "def_rating") %>%
    select(Team, Type, MC_PR, MC_STD)
  
  
  #generate scores
  set.seed(13)
  
  h_score <- round(rnorm(n, mean = h_off$MC_PR, sd = h_off$MC_STD) + 
    hfa +
    rnorm(n, mean = a_def$MC_PR, sd = a_def$MC_STD) - mean, digits = 0)
  
  a_score <- round(rnorm(n, mean = a_off$MC_PR, sd = a_off$MC_STD) +
    rnorm(n, mean = h_def$MC_PR, sd = h_def$MC_STD) - mean, digits = 0)
  
  matchup <<- bind_cols(hm = h_score, aw = a_score) %>%
    mutate(home_pf1 = VLOOKUP(hm, scores, rating, score)) %>%
    mutate(away_pf1 = VLOOKUP(aw, scores, rating, score)) %>%
    mutate(home_pf = if_else(home_pf1 == away_pf1,
                             if_else(h_score >= a_score,
                                     home_pf1 + 1,
                                     home_pf1),
                             home_pf1)) %>%
    mutate(away_pf = if_else(home_pf1 == away_pf1,
                             if_else(a_score > h_score,
                                     away_pf1 +1,
                                     away_pf1),
                             away_pf1)) %>%
    mutate(margin = home_pf - away_pf) %>%
    mutate(cover = if_else(margin + line > 0, 1, 0)) %>%
    mutate(win = if_else(margin > 0, 1, 0)) %>%
    mutate(home_team = home, away_team = away) %>%
    select(home_team, home_pf, away_team, away_pf, margin, cover, win)
    
  win_pct <- sum(matchup$win)/n
  cover_pct <- sum(matchup$cover)/n
  
  tibble(home, away, line, win_pct, cover_pct) %>%
    pivot_longer(cols = win_pct:cover_pct, names_to = "Type",
                 values_to = "Home_Confidence") %>%
    mutate(Away_Confidence = 1 - Home_Confidence)

}

Iterate mc_sim function over schedule.csv to get predictions for all of the Sunday/Monday games

predictions <- pmap_dfr(schedule, mc_sim)

hwp <- predictions %>%
  filter(Type == "win_pct") %>%
  mutate(type = "SU", location = "home") %>%
  select(type, team = home, opponent = away, line, location, confidence = Home_Confidence)

awp <- predictions %>%
  filter(Type == "win_pct") %>%
  mutate(type = "SU", location = "away", line = line * -1) %>%
  select(type, team = away, opponent = home, line, location, confidence = Away_Confidence)

hcp <- predictions %>%
  filter(Type == "cover_pct") %>%
  mutate(type = "ATS", location = "home") %>%
  select(type, team = home, opponent = away, line, location, confidence = Home_Confidence)

acp <- predictions %>%
  filter(Type == "cover_pct") %>%
  mutate(type = "ATS", location = "away", line = line * -1) %>%
  select(type, team = away, opponent = home, line, location, confidence = Away_Confidence) 

predictions2 <- bind_rows(hwp, awp, hcp, acp) %>%
  pivot_wider(names_from = type, values_from = confidence)

write_csv(predictions2, file = "Week 06/predictions.csv")

predictions2

Create predictions plot

plot_data <- predictions2 %>%
  left_join(df.logos) %>%
  filter(ATS >= 0.5)
Joining, by = "team"
ggplot(plot_data, aes(ATS, SU)) +
  geom_image(aes(image = url), size = 0.05) +
  xlab("Probability of Covering ATS") +
  ylab("Probability of Winning Game") +
  theme_minimal() +
  labs(title = "Week 6 Predictions", subtitle = "Against the Spread & Straight Up") +
  geom_hline(aes(yintercept = .50), lty = 2, col = "red", alpha = 0.5) +
  geom_vline(aes(xintercept = .50), lty = 2, col = "red", alpha = 0.5) +
  xlim(c(.5,.7)) + ylim(c(.25,.75))

Generate Power Rankings Plot

ratings2 <- ratings %>% left_join(df.logos, by = c("Team" = "team")) %>%
  select(Team, Type, MC_PR, url) %>%
  pivot_wider(names_from = Type, values_from = MC_PR)

ggplot(ratings2, aes(x = off_rating, def_rating)) + 
  geom_image(aes(image = url), size = 0.04) + 
  xlab("Offense PR") + 
  ylab("Defense PR") + 
  theme_minimal() + 
  labs(title = "Team Power Ratings", subtitle = "2021 Week 5") + 
  geom_hline(aes(yintercept = 25), lty = 2, col = "red", alpha = 0.5)+ 
  geom_vline(aes(xintercept = 25), lty = 2, col = "red", alpha = 0.5) + 
  xlim(c(16, 34)) + ylim(c(30, 20))

Game of the Week - Generate plots for one game per week, similar to the TNF game sent out on Wednesdays.

mc_sim(home = "Baltimore Ravens", away = "Los Angeles Chargers", line = -2.5, hfa = 0.25)

MOV Distribution

ggplot(matchup, aes(x = margin)) +
  geom_histogram(binwidth = 1, color = "black", fill = "white") +
  geom_vline(aes(xintercept = mean(margin)), color = "blue", linetype = "dashed", size = 1) +
  geom_vline(aes(xintercept = 2.5), color = "red", linetype = "dashed", size = 1) +
  ggtitle("LA Chargers at Baltimore Ravens \n Distribution of Projected Margin of Victory") +
  xlab("Ravens score minus Chargers score \n blue line = average margin \n red line = betting line") + ylab("Count") +
  scale_x_continuous(breaks = c(-40, -35, -25, -20, -14, -10, -7, -3, 0, 3, 7, 10, 14, 20, 25, 35, 40)) +
  theme_classic() +
  theme_linedraw() +
  theme(
    plot.title = element_text(color="red", size=14, face="bold.italic"),
    axis.title.x = element_text(color="blue", size=14, face="bold"),
    axis.title.y = element_text(color="#993333", size=14, face="bold"),
    panel.grid.major = element_blank(),
          panel.grid.minor = element_blank()
)

Top 10 Scores

score_n <- matchup %>% count(home_pf, away_pf) %>% arrange(desc(n)) %>%
  rename("Home" = home_pf, "Away" = away_pf, "Count" = n)

kable(head(score_n, 10)) %>%
  kable_styling(font_size = 24, bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Home Away Count
27 31 1417
31 27 1411
34 27 1214
34 31 1189
27 34 1150
31 34 1139
45 27 1112
27 45 1085
45 31 1076
27 24 1054
NA

Top 10 Margins of Victory (Home score minus Away score)

margin_n <- matchup %>% count(margin) %>% arrange(desc(n)) %>%
  rename("Count" = n)

kable(head(margin_n, 10)) %>%
  kable_styling(font_size = 24, bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
margin Count
7 6845
-7 6600
1 5417
14 4926
-14 4479
3 3983
4 3974
-4 3937
-3 3864
10 3672
LS0tDQp0aXRsZTogIk1vbnRlIENhcmxvIFNpbXVsYXRpb25zOiAyMDIxIE5GTCBXZWVrIDA2Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KVHdpY2UgcGVyIHdlZWsgZHVyaW5nIHRoZSBORkwgc2Vhc29uIEkgcHVibGlzaCBhIFN1YnN0YWNrIG5ld3NsZXR0ZXIgY2FsbGVkIFtNb250ZSBDYXJsbyBGb290YmFsbCBQaWNrc10oaHR0cHM6Ly9tY2ZwLnN1YnN0YWNrLmNvbS8pLiBPbiBXZWRuZXNkYXkgSSBwdWJsaXNoIHByb2plY3Rpb25zIGZvciB0aGUgVGh1cnNkYXkgTmlnaHQgZ2FtZSBhbmQgRnJpZGF5IGluY2x1ZGVzIHRoZSBwaWNrcyBmb3IgdGhlIHJlc3Qgb2YgdGhlIHdlZWsuIElmIHlvdSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgTkZMLCBwbGVhc2UgW3N1YnNjcmliZV0oaHR0cHM6Ly9tY2ZwLnN1YnN0YWNrLmNvbS8pLCBpdCdzIEZSRUUhDQoNCkZvciBSU1RBVFMgZmFucyBJIHB1Ymxpc2ggbXkgY29kZSBvbiBSUHVicyBldmVyeSB3ZWVrLiBbQ2hlY2sgaXQgb3V0IGhlcmVdKGh0dHBzOi8vcnB1YnMuY29tL21yZWdpc3RlcjIpLg0KDQpVc2UgdGhpcyBmaWxlIHRvIHJ1biBNb250ZSBDYXJsbyBTaW11bGF0aW9ucyBhZnRlciBwb3dlciByYXRpbmdzIGFyZSBjb21wbGV0ZS4NCg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeSh0aWR5cXVhbnQpDQpsaWJyYXJ5KGdnaW1hZ2UpDQpsaWJyYXJ5KGdncmVwZWwpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KDQpgYGANCg0KDQoNCkxvYWQgcmVxdWlyZWQgZGF0YSBmaWxlcw0KYGBge3J9DQpyYXRpbmdzIDwtIHJlYWRfY3N2KCJXZWVrIDA2L2lucHV0cy9wb3dlcl9yYXRpbmdzLmNzdiIpIA0Kc2NoZWR1bGUgPC0gcmVhZF9jc3YoIldlZWsgMDYvaW5wdXRzL3NjaGVkdWxlLmNzdiIpDQpzY29yZXMgPC0gcmVhZF9jc3YoIldlZWsgMDYvaW5wdXRzL2NvbW1vbl9zY29yZXMuY3N2IiwgY29sX3R5cGVzID0gIm4iKQ0KZGYubG9nb3MgPC0gcmVhZC5jc3YoIldlZWsgMDYvaW5wdXRzL25mbF9sb2dvcy5jc3YiKQ0KYGBgDQoNCg0KDQpDcmVhdGUgZnVuY3Rpb24gdG8gc2ltdWxhdGUgc2NvcmVzLiBJbiBiZXR3ZWVuIHRoZSBUaHVyc2RheSBOaWdodCBHYW1lIGFuZCBTdW5kYXkgZ2FtZXMgSSB1cGRhdGVkIHRoZSBmdW5jdGlvbiB0byBlbGltaW5hdGUgdGllIGdhbWVzDQoNCmBgYHtyfQ0KbWNfc2ltIDwtIGZ1bmN0aW9uKGhvbWUsIGF3YXksIGRhdGEgPSByYXRpbmdzLCBsaW5lID0gMCwgbiA9IDEwMDAwMCwgaGZhID0gMCwgbWVhbiA9IDI1KXsNCiAgDQogICNwdWxsIHJhdGluZ3MgdG9nZXRoZXIgZm9yIGJvdGggdGVhbXMgaW4gbWF0Y2h1cA0KICBoX29mZiA8LXJhdGluZ3MgJT4lDQogICAgZmlsdGVyKFRlYW0gPT0gaG9tZSAmIFR5cGUgPT0gIm9mZl9yYXRpbmciKSAlPiUNCiAgICBzZWxlY3QoVGVhbSwgVHlwZSwgTUNfUFIsIE1DX1NURCkNCiAgDQogIGhfZGVmIDwtcmF0aW5ncyAlPiUNCiAgICBmaWx0ZXIoVGVhbSA9PSBob21lICYgVHlwZSA9PSAiZGVmX3JhdGluZyIpICU+JQ0KICAgIHNlbGVjdChUZWFtLCBUeXBlLCBNQ19QUiwgTUNfU1REKQ0KICANCiAgDQogIGFfb2ZmIDwtcmF0aW5ncyAlPiUNCiAgICBmaWx0ZXIoVGVhbSA9PSBhd2F5ICYgVHlwZSA9PSAib2ZmX3JhdGluZyIpICU+JQ0KICAgIHNlbGVjdChUZWFtLCBUeXBlLCBNQ19QUiwgTUNfU1REKQ0KICANCiAgYV9kZWYgPC1yYXRpbmdzICU+JQ0KICAgIGZpbHRlcihUZWFtID09IGF3YXkgJiBUeXBlID09ICJkZWZfcmF0aW5nIikgJT4lDQogICAgc2VsZWN0KFRlYW0sIFR5cGUsIE1DX1BSLCBNQ19TVEQpDQogIA0KICANCiAgI2dlbmVyYXRlIHNjb3Jlcw0KICBzZXQuc2VlZCgxMykNCiAgDQogIGhfc2NvcmUgPC0gcm91bmQocm5vcm0obiwgbWVhbiA9IGhfb2ZmJE1DX1BSLCBzZCA9IGhfb2ZmJE1DX1NURCkgKyANCiAgICBoZmEgKw0KICAgIHJub3JtKG4sIG1lYW4gPSBhX2RlZiRNQ19QUiwgc2QgPSBhX2RlZiRNQ19TVEQpIC0gbWVhbiwgZGlnaXRzID0gMCkNCiAgDQogIGFfc2NvcmUgPC0gcm91bmQocm5vcm0obiwgbWVhbiA9IGFfb2ZmJE1DX1BSLCBzZCA9IGFfb2ZmJE1DX1NURCkgKw0KICAgIHJub3JtKG4sIG1lYW4gPSBoX2RlZiRNQ19QUiwgc2QgPSBoX2RlZiRNQ19TVEQpIC0gbWVhbiwgZGlnaXRzID0gMCkNCiAgDQogIG1hdGNodXAgPDwtIGJpbmRfY29scyhobSA9IGhfc2NvcmUsIGF3ID0gYV9zY29yZSkgJT4lDQogICAgbXV0YXRlKGhvbWVfcGYxID0gVkxPT0tVUChobSwgc2NvcmVzLCByYXRpbmcsIHNjb3JlKSkgJT4lDQogICAgbXV0YXRlKGF3YXlfcGYxID0gVkxPT0tVUChhdywgc2NvcmVzLCByYXRpbmcsIHNjb3JlKSkgJT4lDQogICAgbXV0YXRlKGhvbWVfcGYgPSBpZl9lbHNlKGhvbWVfcGYxID09IGF3YXlfcGYxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZl9lbHNlKGhfc2NvcmUgPj0gYV9zY29yZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob21lX3BmMSArIDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG9tZV9wZjEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob21lX3BmMSkpICU+JQ0KICAgIG11dGF0ZShhd2F5X3BmID0gaWZfZWxzZShob21lX3BmMSA9PSBhd2F5X3BmMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZfZWxzZShhX3Njb3JlID4gaF9zY29yZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhd2F5X3BmMSArMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhd2F5X3BmMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF3YXlfcGYxKSkgJT4lDQogICAgbXV0YXRlKG1hcmdpbiA9IGhvbWVfcGYgLSBhd2F5X3BmKSAlPiUNCiAgICBtdXRhdGUoY292ZXIgPSBpZl9lbHNlKG1hcmdpbiArIGxpbmUgPiAwLCAxLCAwKSkgJT4lDQogICAgbXV0YXRlKHdpbiA9IGlmX2Vsc2UobWFyZ2luID4gMCwgMSwgMCkpICU+JQ0KICAgIG11dGF0ZShob21lX3RlYW0gPSBob21lLCBhd2F5X3RlYW0gPSBhd2F5KSAlPiUNCiAgICBzZWxlY3QoaG9tZV90ZWFtLCBob21lX3BmLCBhd2F5X3RlYW0sIGF3YXlfcGYsIG1hcmdpbiwgY292ZXIsIHdpbikNCiAgICANCiAgd2luX3BjdCA8LSBzdW0obWF0Y2h1cCR3aW4pL24NCiAgY292ZXJfcGN0IDwtIHN1bShtYXRjaHVwJGNvdmVyKS9uDQogIA0KICB0aWJibGUoaG9tZSwgYXdheSwgbGluZSwgd2luX3BjdCwgY292ZXJfcGN0KSAlPiUNCiAgICBwaXZvdF9sb25nZXIoY29scyA9IHdpbl9wY3Q6Y292ZXJfcGN0LCBuYW1lc190byA9ICJUeXBlIiwNCiAgICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gIkhvbWVfQ29uZmlkZW5jZSIpICU+JQ0KICAgIG11dGF0ZShBd2F5X0NvbmZpZGVuY2UgPSAxIC0gSG9tZV9Db25maWRlbmNlKQ0KDQp9DQoNCmBgYA0KDQoNCg0KSXRlcmF0ZSBtY19zaW0gZnVuY3Rpb24gb3ZlciBzY2hlZHVsZS5jc3YgdG8gZ2V0IHByZWRpY3Rpb25zIGZvciBhbGwgb2YgdGhlIFN1bmRheS9Nb25kYXkgZ2FtZXMNCmBgYHtyfQ0KcHJlZGljdGlvbnMgPC0gcG1hcF9kZnIoc2NoZWR1bGUsIG1jX3NpbSkNCg0KaHdwIDwtIHByZWRpY3Rpb25zICU+JQ0KICBmaWx0ZXIoVHlwZSA9PSAid2luX3BjdCIpICU+JQ0KICBtdXRhdGUodHlwZSA9ICJTVSIsIGxvY2F0aW9uID0gImhvbWUiKSAlPiUNCiAgc2VsZWN0KHR5cGUsIHRlYW0gPSBob21lLCBvcHBvbmVudCA9IGF3YXksIGxpbmUsIGxvY2F0aW9uLCBjb25maWRlbmNlID0gSG9tZV9Db25maWRlbmNlKQ0KDQphd3AgPC0gcHJlZGljdGlvbnMgJT4lDQogIGZpbHRlcihUeXBlID09ICJ3aW5fcGN0IikgJT4lDQogIG11dGF0ZSh0eXBlID0gIlNVIiwgbG9jYXRpb24gPSAiYXdheSIsIGxpbmUgPSBsaW5lICogLTEpICU+JQ0KICBzZWxlY3QodHlwZSwgdGVhbSA9IGF3YXksIG9wcG9uZW50ID0gaG9tZSwgbGluZSwgbG9jYXRpb24sIGNvbmZpZGVuY2UgPSBBd2F5X0NvbmZpZGVuY2UpDQoNCmhjcCA8LSBwcmVkaWN0aW9ucyAlPiUNCiAgZmlsdGVyKFR5cGUgPT0gImNvdmVyX3BjdCIpICU+JQ0KICBtdXRhdGUodHlwZSA9ICJBVFMiLCBsb2NhdGlvbiA9ICJob21lIikgJT4lDQogIHNlbGVjdCh0eXBlLCB0ZWFtID0gaG9tZSwgb3Bwb25lbnQgPSBhd2F5LCBsaW5lLCBsb2NhdGlvbiwgY29uZmlkZW5jZSA9IEhvbWVfQ29uZmlkZW5jZSkNCg0KYWNwIDwtIHByZWRpY3Rpb25zICU+JQ0KICBmaWx0ZXIoVHlwZSA9PSAiY292ZXJfcGN0IikgJT4lDQogIG11dGF0ZSh0eXBlID0gIkFUUyIsIGxvY2F0aW9uID0gImF3YXkiLCBsaW5lID0gbGluZSAqIC0xKSAlPiUNCiAgc2VsZWN0KHR5cGUsIHRlYW0gPSBhd2F5LCBvcHBvbmVudCA9IGhvbWUsIGxpbmUsIGxvY2F0aW9uLCBjb25maWRlbmNlID0gQXdheV9Db25maWRlbmNlKSANCg0KcHJlZGljdGlvbnMyIDwtIGJpbmRfcm93cyhod3AsIGF3cCwgaGNwLCBhY3ApICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdHlwZSwgdmFsdWVzX2Zyb20gPSBjb25maWRlbmNlKQ0KDQp3cml0ZV9jc3YocHJlZGljdGlvbnMyLCBmaWxlID0gIldlZWsgMDYvcHJlZGljdGlvbnMuY3N2IikNCg0KcHJlZGljdGlvbnMyDQpgYGANCg0KDQoNCkNyZWF0ZSBwcmVkaWN0aW9ucyBwbG90DQpgYGB7cn0NCnBsb3RfZGF0YSA8LSBwcmVkaWN0aW9uczIgJT4lDQogIGxlZnRfam9pbihkZi5sb2dvcykgJT4lDQogIGZpbHRlcihBVFMgPj0gMC41KQ0KDQoNCmdncGxvdChwbG90X2RhdGEsIGFlcyhBVFMsIFNVKSkgKw0KICBnZW9tX2ltYWdlKGFlcyhpbWFnZSA9IHVybCksIHNpemUgPSAwLjA1KSArDQogIHhsYWIoIlByb2JhYmlsaXR5IG9mIENvdmVyaW5nIEFUUyIpICsNCiAgeWxhYigiUHJvYmFiaWxpdHkgb2YgV2lubmluZyBHYW1lIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIldlZWsgNiBQcmVkaWN0aW9ucyIsIHN1YnRpdGxlID0gIkFnYWluc3QgdGhlIFNwcmVhZCAmIFN0cmFpZ2h0IFVwIikgKw0KICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0ID0gLjUwKSwgbHR5ID0gMiwgY29sID0gInJlZCIsIGFscGhhID0gMC41KSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSAuNTApLCBsdHkgPSAyLCBjb2wgPSAicmVkIiwgYWxwaGEgPSAwLjUpICsNCiAgeGxpbShjKC41LC43KSkgKyB5bGltKGMoLjI1LC43NSkpDQpgYGANCg0KDQoNCkdlbmVyYXRlIFBvd2VyIFJhbmtpbmdzIFBsb3QNCmBgYHtyfQ0KcmF0aW5nczIgPC0gcmF0aW5ncyAlPiUgbGVmdF9qb2luKGRmLmxvZ29zLCBieSA9IGMoIlRlYW0iID0gInRlYW0iKSkgJT4lDQogIHNlbGVjdChUZWFtLCBUeXBlLCBNQ19QUiwgdXJsKSAlPiUNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IFR5cGUsIHZhbHVlc19mcm9tID0gTUNfUFIpDQoNCmdncGxvdChyYXRpbmdzMiwgYWVzKHggPSBvZmZfcmF0aW5nLCBkZWZfcmF0aW5nKSkgKyANCiAgZ2VvbV9pbWFnZShhZXMoaW1hZ2UgPSB1cmwpLCBzaXplID0gMC4wNCkgKyANCiAgeGxhYigiT2ZmZW5zZSBQUiIpICsgDQogIHlsYWIoIkRlZmVuc2UgUFIiKSArIA0KICB0aGVtZV9taW5pbWFsKCkgKyANCiAgbGFicyh0aXRsZSA9ICJUZWFtIFBvd2VyIFJhdGluZ3MiLCBzdWJ0aXRsZSA9ICIyMDIxIFdlZWsgNSIpICsgDQogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQgPSAyNSksIGx0eSA9IDIsIGNvbCA9ICJyZWQiLCBhbHBoYSA9IDAuNSkrIA0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gMjUpLCBsdHkgPSAyLCBjb2wgPSAicmVkIiwgYWxwaGEgPSAwLjUpICsgDQogIHhsaW0oYygxNiwgMzQpKSArIHlsaW0oYygzMCwgMjApKQ0KYGBgDQoNCg0KR2FtZSBvZiB0aGUgV2VlayAtIEdlbmVyYXRlIHBsb3RzIGZvciBvbmUgZ2FtZSBwZXIgd2Vlaywgc2ltaWxhciB0byB0aGUgVE5GIGdhbWUgc2VudCBvdXQgb24gV2VkbmVzZGF5cy4NCg0KDQoNCmBgYHtyfQ0KbWNfc2ltKGhvbWUgPSAiQmFsdGltb3JlIFJhdmVucyIsIGF3YXkgPSAiTG9zIEFuZ2VsZXMgQ2hhcmdlcnMiLCBsaW5lID0gLTIuNSwgaGZhID0gMC4yNSkNCmBgYA0KDQoNCk1PViBEaXN0cmlidXRpb24NCmBgYHtyfQ0KZ2dwbG90KG1hdGNodXAsIGFlcyh4ID0gbWFyZ2luKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJ3aGl0ZSIpICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lYW4obWFyZ2luKSksIGNvbG9yID0gImJsdWUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMSkgKw0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gMi41KSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDEpICsNCiAgZ2d0aXRsZSgiTEEgQ2hhcmdlcnMgYXQgQmFsdGltb3JlIFJhdmVucyBcbiBEaXN0cmlidXRpb24gb2YgUHJvamVjdGVkIE1hcmdpbiBvZiBWaWN0b3J5IikgKw0KICB4bGFiKCJSYXZlbnMgc2NvcmUgbWludXMgQ2hhcmdlcnMgc2NvcmUgXG4gYmx1ZSBsaW5lID0gYXZlcmFnZSBtYXJnaW4gXG4gcmVkIGxpbmUgPSBiZXR0aW5nIGxpbmUiKSArIHlsYWIoIkNvdW50IikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gYygtNDAsIC0zNSwgLTI1LCAtMjAsIC0xNCwgLTEwLCAtNywgLTMsIDAsIDMsIDcsIDEwLCAxNCwgMjAsIDI1LCAzNSwgNDApKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lX2xpbmVkcmF3KCkgKw0KICB0aGVtZSgNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG9yPSJyZWQiLCBzaXplPTE0LCBmYWNlPSJib2xkLml0YWxpYyIpLA0KICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChjb2xvcj0iYmx1ZSIsIHNpemU9MTQsIGZhY2U9ImJvbGQiKSwNCiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoY29sb3I9IiM5OTMzMzMiLCBzaXplPTE0LCBmYWNlPSJib2xkIiksDQogICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpDQopDQoNCmBgYA0KDQoNCg0KVG9wIDEwIFNjb3Jlcw0KDQpgYGB7cn0NCnNjb3JlX24gPC0gbWF0Y2h1cCAlPiUgY291bnQoaG9tZV9wZiwgYXdheV9wZikgJT4lIGFycmFuZ2UoZGVzYyhuKSkgJT4lDQogIHJlbmFtZSgiSG9tZSIgPSBob21lX3BmLCAiQXdheSIgPSBhd2F5X3BmLCAiQ291bnQiID0gbikNCg0Ka2FibGUoaGVhZChzY29yZV9uLCAxMCkpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGZvbnRfc2l6ZSA9IDI0LCBib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksDQogICAgICAgICAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFKQ0KDQpgYGANCg0KDQoNClRvcCAxMCBNYXJnaW5zIG9mIFZpY3RvcnkgKEhvbWUgc2NvcmUgbWludXMgQXdheSBzY29yZSkNCg0KYGBge3J9DQptYXJnaW5fbiA8LSBtYXRjaHVwICU+JSBjb3VudChtYXJnaW4pICU+JSBhcnJhbmdlKGRlc2MobikpICU+JQ0KICByZW5hbWUoIkNvdW50IiA9IG4pDQoNCmthYmxlKGhlYWQobWFyZ2luX24sIDEwKSkgJT4lDQogIGthYmxlX3N0eWxpbmcoZm9udF9zaXplID0gMjQsIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwNCiAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gRkFMU0UpDQpgYGANCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg==