Start Date: 2 Sept 2025

Report Date: 30 September 2025

Source: Psi Chi R

To acknowledge National Video Game Day which is September 12th, this month’s dataset is about video game sales.

The contest will run until September 30th, latest time zone possible. The entry form is available here and the prompts are below.

knitr::opts_chunk$set(echo = T,message = F,warning = F)

#setwd("C:/Users/alanh/Documents/R/Psi_Chi_R")

rm(list=ls())

setwd("~/R/Psi_Chi_R")

library(tidyverse)

#commenting out code but not for text
if (FALSE) {

}

#total for bottom row

sum_rows = function(x) {
  x = as.data.frame(x) # Ensure x is a data frame
  sums = sapply(x,function(col) if (is.numeric(col)) sum(col, na.rm = T) else NA)
  sums = as.data.frame(t(sums)) # Convert to data frame
  names(sums) = names(x) # Retain column names
  rbind(x, sums) # Bind the sum row to the original data frame
}

# right column for total
sum_cols = function(x) {
  x$Total = rowSums(x[sapply(x, is.numeric)], na.rm = T)
  x
}

options(scipen=999) # disable scientific notation

#dollar format function
dollars = function(x) {
  paste0("$",format(x,big.mark= ",",scientific=F))
}

desc_stats = function(x){
  c(min = min(x,na.rm=T),
    median = median(x,na.rm=T),
    max = max(x,na.rm=T),
    mean = mean(x,na.rm=T),
    sd = sd(x,na.rm=T))
}

Clean and EDA

data=read_csv('data.csv',show_col_types = F)

# data = readxl::read_excel('C:/Users/alanh/Downloads/2025Apr_data.xlsx')

names(data) = make.names(colnames(data))

#SmartEDA::ExpData(data,type=2) %>% arrange(desc(Per_of_Missing))

skimr::skim(data)
Data summary
Name data
Number of rows 64016
Number of columns 13
_______________________
Column type frequency:
character 7
numeric 6
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
title 0 1.00 1 147 0 39798 0
console 0 1.00 2 6 0 81 0
genre 0 1.00 3 16 0 20 0
publisher 0 1.00 2 49 0 3383 0
developer 17 1.00 2 68 0 8862 0
release_date 7051 0.89 8 10 0 7922 0
last_update 46137 0.28 8 10 0 1545 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
critic_score 57338 0.10 7.22 1.46 1 6.40 7.50 8.30 10.00 ▁▁▃▇▅
total_sales 45094 0.30 0.35 0.81 0 0.03 0.12 0.34 20.32 ▇▁▁▁▁
na_sales 51379 0.20 0.26 0.49 0 0.05 0.12 0.28 9.76 ▇▁▁▁▁
jp_sales 57290 0.11 0.10 0.17 0 0.02 0.04 0.12 2.13 ▇▁▁▁▁
pal_sales 51192 0.20 0.15 0.39 0 0.01 0.04 0.14 9.85 ▇▁▁▁▁
other_sales 48888 0.24 0.04 0.13 0 0.00 0.01 0.03 3.12 ▇▁▁▁▁

Data set:

Level 1: Cleaning and Preparation

Remove rows that are missing a value for ‘critic_score’

Re-format the release_date and last_update columns to only contain the year

data1 = data |> 
  filter(!is.na(critic_score)) |> 
  mutate(release_date2 = year(mdy(release_date)),
         last_update2 = year(mdy(last_update))) |> 
  select(release_date2,last_update2,critic_score,everything())

Level 2: Descriptives

Create a table that shows the average critic ratings (‘critic_score’) and total sales (‘total_sales’) for each genre (‘genre’)

ACR_tab = data1 |> 
  group_by(genre) |> 
  summarise(critic_ratings_avg = mean(critic_score,na.rm=T),
            total_sales_avg = mean(total_sales,na.rm=T))

print(ACR_tab,print=Inf)
## # A tibble: 20 × 3
##    genre            critic_ratings_avg total_sales_avg
##    <chr>                         <dbl>           <dbl>
##  1 Action                         6.83           0.784
##  2 Action-Adventure               7.68           1.00 
##  3 Adventure                      7.34           0.572
##  4 Board Game                     5.3            0.3  
##  5 Education                      7.75           0.305
##  6 Fighting                       7.39           0.632
##  7 MMO                            7.78           0.29 
##  8 Misc                           6.88           0.727
##  9 Music                          7.99           0.836
## 10 Party                          5.97           0.375
## 11 Platform                       7.31           0.500
## 12 Puzzle                         7.20           0.283
## 13 Racing                         7.19           0.746
## 14 Role-Playing                   7.47           0.545
## 15 Sandbox                        9.2            1.89 
## 16 Shooter                        7.28           1.18 
## 17 Simulation                     6.91           0.659
## 18 Sports                         7.29           0.902
## 19 Strategy                       7.43           0.268
## 20 Visual Novel                   8.5            0.03

Create a table that shows the average total sales (‘total_sales’) for each developer (‘developer’)

ATS_tab = data1 |> 
  group_by(developer) |> 
  summarise(total_sales_avg = mean(total_sales,na.rm=T))

print(ATS_tab)
## # A tibble: 1,576 × 2
##    developer                total_sales_avg
##    <chr>                              <dbl>
##  1 .theprodukkt                      NaN   
##  2 10tacle Studios AG                  0.16
##  3 11 bit studios                    NaN   
##  4 1C Company                        NaN   
##  5 1C: Maddox Games                    0.05
##  6 1C:Ino-Co                         NaN   
##  7 1st Playable Productions            3.14
##  8 2015, Inc.                          0   
##  9 2D Boy                              0.03
## 10 2K Australia / 2K Boston            2.83
## # ℹ 1,566 more rows

Level 3: Research Questions

What is each developer’s (‘developer’) most sold game in North America (‘na_sales’)?

data1 |> 
  group_by(developer,title) |> 
  summarise(most_sold_game = max(na_sales,na.rm = T)) |> 
  arrange(desc(most_sold_game)) |> 
  distinct(developer,.keep_all = T)

What was the most highly sold game of 2018?

data1 |> 
  filter(release_date2==2018) |> 
  group_by(title) |> 
  summarise(highly_sold_game = max(total_sales,na.rm = T)) |> 
  arrange(desc(highly_sold_game))
# Red Dead Redemption 2

Is there a relationship between public perception (‘critic_score’) and the number of video games sold (‘total_sales’)?

rel_list = list('critic_score','total_sales')

for (i in rel_list){
  x=sum(is.na(data1[[i]]))
  print(paste0(i,":"," ",x))
}
## [1] "critic_score: 0"
## [1] "total_sales: 2552"
data2 = data1 |> 
  filter(!is.na(total_sales))
for (i in rel_list){
  x=shapiro.test(data2[[i]])
  print(i)
  print(x)
}
## [1] "critic_score"
## 
##  Shapiro-Wilk normality test
## 
## data:  data2[[i]]
## W = 0.95407, p-value < 0.00000000000000022
## 
## [1] "total_sales"
## 
##  Shapiro-Wilk normality test
## 
## data:  data2[[i]]
## W = 0.47624, p-value < 0.00000000000000022
for (i in rel_list){
  hist(data2[[i]],main=i,xlab=i,probability = T)
  lines(density(data1[[i]],na.rm = T),lwd=2,col='darkred')
}

if (FALSE) {
hist(data1$total_sales,probability = T)
lines(density(data1$total_sales))

hist(data1$critic_score,probability = T)
lines(density(data1$critic_score))
}
cor_test_list = list('s','k')

for (i in cor_test_list){
  x= cor.test(data2$critic_score,data2$total_sales,method = i)
  print(x)
}
## 
##  Spearman's rank correlation rho
## 
## data:  data2$critic_score and data2$total_sales
## S = 7752046553, p-value < 0.00000000000000022
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
##      rho 
## 0.337814 
## 
## 
##  Kendall's rank correlation tau
## 
## data:  data2$critic_score and data2$total_sales
## z = 22.401, p-value < 0.00000000000000022
## alternative hypothesis: true tau is not equal to 0
## sample estimates:
##       tau 
## 0.2364818
#yes, the Spearman rank shows a 

The Spearman’s showed a significant positive correlation between critic score and total sales, rho = 0.34, p < .001. This is consistent with Kendall’s (tau = 0.24, p < .001), indicating that higher critic scores are correlated with greater sales.

Level 4: Visualization

Graph the relationship between public perception (‘critic_score’) and the number of video games sold (‘total_sales’)

data2 |> 
  ggplot(aes(x=critic_score,y=total_sales,col=total_sales))+
  geom_point()+
  theme_bw()+
  geom_jitter()+
  #geom_smooth()+
  labs(title='In the Eyes of the Beholder')+
  theme(plot.title = element_text(hjust = .5))+
  guides(col='none')

Graph the total number of video games sold by EA Sports (‘publisher’) over time

data3 = data2 |> 
  filter(grepl('ea sports',publisher,ignore.case = T)) |> 
  mutate(PUBLISHER_2 = case_when(publisher == 'EA Sports BIG' ~ 'EA Sports',TRUE ~ publisher))

data3 |> 
  group_by(release_date2) |> 
  summarise(HEADCOUNT =n()) |> 
  ggplot(aes(x=release_date2,y=HEADCOUNT))+
  geom_line(col='darkgreen',lwd=1)+
  theme_bw()+
  labs(x='Release Year',y='Games Sold',title='The EA Way by EA Games',subtitle='(the way of microtransactions to sell, sell, sell!)') +
  geom_text(aes(label = HEADCOUNT,vjust = -.5,hjust = 2),size = 3.5,col='darkred')+
  theme(plot.subtitle = element_text(hjust = .5),plot.title = element_text(hjust = .5))

data3 |> 
  ggplot(aes(x=release_date2))+
  geom_bar(fill='darkgreen',col='gold')+
  theme_bw()+
  labs(x='Release Year',y='Games Sold',title='The EA Way by EA Games',subtitle='(the way of microtransactions to sell, sell, sell!)') +
  geom_text(stat='count',aes(label = after_stat(count)),vjust = -0.5,size = 3,col='darkred')+
  theme(plot.subtitle = element_text(hjust = .5),plot.title = element_text(hjust = .5))

data4 = data3 |> 
  select(contains('sale')) |> 
  na.omit()

cor_frame = cor(data4) |> as.data.frame()

cor_frame
cor_frame2 = cor_frame |> 
  rownames_to_column('var1') |> 
  pivot_longer(-var1,names_to = 'var2',values_to = 'corr')

cor_frame2 |> 
  ggplot(aes(x=var1,y=var2,fill=corr))+
  geom_tile()+scale_fill_gradient2(
    high = "darkred", low = "lightblue", mid = "gray",midpoint = 0, limit = c(-1, 1), space = "Lab",name = "Correlation") +
  theme_bw() +
  labs(title='Sales Matrix',y='',x='')+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1),
        plot.title = element_text(hjust = .5))+
  geom_text(aes(label = round(corr, 2)))

LS0tDQp0aXRsZTogIlBzaSBDaGkgUiAtIFNlcCAyMDI1Ig0KI2RhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBsaWdodGJveDogdHJ1ZQ0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIGFsd2F5c19hbGxvd19odG1sOiB5ZXMNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIGFuY2hvcl9zZWN0aW9uczogVFJVRQ0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpTdGFydCBEYXRlOiAyIFNlcHQgMjAyNQ0KDQpSZXBvcnQgRGF0ZTogYHIgZm9ybWF0KFN5cy5EYXRlKCksICclZCAlQiAlWScpYA0KDQpbU291cmNlOiBQc2kgQ2hpIFJdKGh0dHBzOi8vb3NmLmlvL2N2OWJ0L3dpa2kvaG9tZS8pDQoNClRvIGFja25vd2xlZGdlIE5hdGlvbmFsIFZpZGVvIEdhbWUgRGF5IHdoaWNoIGlzIFNlcHRlbWJlciAxMnRoLCB0aGlzIG1vbnRoJ3MgZGF0YXNldCBpcyBhYm91dCB2aWRlbyBnYW1lIHNhbGVzLg0KDQpUaGUgY29udGVzdCB3aWxsIHJ1biB1bnRpbCBTZXB0ZW1iZXIgMzB0aCwgbGF0ZXN0IHRpbWUgem9uZSBwb3NzaWJsZS4gVGhlIGVudHJ5IGZvcm0gaXMgYXZhaWxhYmxlIGhlcmUgYW5kIHRoZSBwcm9tcHRzIGFyZSBiZWxvdy4NCg0KDQpgYGB7ciB3YXJuaW5nPUYsbWVzc2FnZT1GfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBULG1lc3NhZ2UgPSBGLHdhcm5pbmcgPSBGKQ0KDQojc2V0d2QoIkM6L1VzZXJzL2FsYW5oL0RvY3VtZW50cy9SL1BzaV9DaGlfUiIpDQoNCnJtKGxpc3Q9bHMoKSkNCg0Kc2V0d2QoIn4vUi9Qc2lfQ2hpX1IiKQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KI2NvbW1lbnRpbmcgb3V0IGNvZGUgYnV0IG5vdCBmb3IgdGV4dA0KaWYgKEZBTFNFKSB7DQoNCn0NCg0KI3RvdGFsIGZvciBib3R0b20gcm93DQoNCnN1bV9yb3dzID0gZnVuY3Rpb24oeCkgew0KICB4ID0gYXMuZGF0YS5mcmFtZSh4KSAjIEVuc3VyZSB4IGlzIGEgZGF0YSBmcmFtZQ0KICBzdW1zID0gc2FwcGx5KHgsZnVuY3Rpb24oY29sKSBpZiAoaXMubnVtZXJpYyhjb2wpKSBzdW0oY29sLCBuYS5ybSA9IFQpIGVsc2UgTkEpDQogIHN1bXMgPSBhcy5kYXRhLmZyYW1lKHQoc3VtcykpICMgQ29udmVydCB0byBkYXRhIGZyYW1lDQogIG5hbWVzKHN1bXMpID0gbmFtZXMoeCkgIyBSZXRhaW4gY29sdW1uIG5hbWVzDQogIHJiaW5kKHgsIHN1bXMpICMgQmluZCB0aGUgc3VtIHJvdyB0byB0aGUgb3JpZ2luYWwgZGF0YSBmcmFtZQ0KfQ0KDQojIHJpZ2h0IGNvbHVtbiBmb3IgdG90YWwNCnN1bV9jb2xzID0gZnVuY3Rpb24oeCkgew0KICB4JFRvdGFsID0gcm93U3Vtcyh4W3NhcHBseSh4LCBpcy5udW1lcmljKV0sIG5hLnJtID0gVCkNCiAgeA0KfQ0KDQpvcHRpb25zKHNjaXBlbj05OTkpICMgZGlzYWJsZSBzY2llbnRpZmljIG5vdGF0aW9uDQoNCiNkb2xsYXIgZm9ybWF0IGZ1bmN0aW9uDQpkb2xsYXJzID0gZnVuY3Rpb24oeCkgew0KICBwYXN0ZTAoIiQiLGZvcm1hdCh4LGJpZy5tYXJrPSAiLCIsc2NpZW50aWZpYz1GKSkNCn0NCg0KZGVzY19zdGF0cyA9IGZ1bmN0aW9uKHgpew0KICBjKG1pbiA9IG1pbih4LG5hLnJtPVQpLA0KICAgIG1lZGlhbiA9IG1lZGlhbih4LG5hLnJtPVQpLA0KICAgIG1heCA9IG1heCh4LG5hLnJtPVQpLA0KICAgIG1lYW4gPSBtZWFuKHgsbmEucm09VCksDQogICAgc2QgPSBzZCh4LG5hLnJtPVQpKQ0KfQ0KYGBgDQoNCiMjIENsZWFuIGFuZCBFREENCg0KYGBge3J9DQpkYXRhPXJlYWRfY3N2KCdkYXRhLmNzdicsc2hvd19jb2xfdHlwZXMgPSBGKQ0KDQojIGRhdGEgPSByZWFkeGw6OnJlYWRfZXhjZWwoJ0M6L1VzZXJzL2FsYW5oL0Rvd25sb2Fkcy8yMDI1QXByX2RhdGEueGxzeCcpDQoNCm5hbWVzKGRhdGEpID0gbWFrZS5uYW1lcyhjb2xuYW1lcyhkYXRhKSkNCg0KI1NtYXJ0RURBOjpFeHBEYXRhKGRhdGEsdHlwZT0yKSAlPiUgYXJyYW5nZShkZXNjKFBlcl9vZl9NaXNzaW5nKSkNCg0Kc2tpbXI6OnNraW0oZGF0YSkNCmBgYA0KDQojIyMgRGF0YSBzZXQ6DQoNCmBgYHtyIGVjaG89Rn0NCmRvd25sb2FkdGhpczo6ZG93bmxvYWRfdGhpcyhkYXRhLG91dHB1dF9uYW1lID0gJ2RhdGFzZXQnLG91dHB1dF9leHRlbnNpb24gPSAnLnhsc3gnKQ0KYGBgDQoNCg0KIyMgTGV2ZWwgMTogQ2xlYW5pbmcgYW5kIFByZXBhcmF0aW9uDQoNCiMjIyBSZW1vdmUgcm93cyB0aGF0IGFyZSBtaXNzaW5nIGEgdmFsdWUgZm9yIOKAmGNyaXRpY19zY29yZeKAmQ0KDQojIyMgUmUtZm9ybWF0IHRoZSByZWxlYXNlX2RhdGUgYW5kIGxhc3RfdXBkYXRlIGNvbHVtbnMgdG8gb25seSBjb250YWluIHRoZSB5ZWFyDQoNCmBgYHtyfQ0KZGF0YTEgPSBkYXRhIHw+IA0KICBmaWx0ZXIoIWlzLm5hKGNyaXRpY19zY29yZSkpIHw+IA0KICBtdXRhdGUocmVsZWFzZV9kYXRlMiA9IHllYXIobWR5KHJlbGVhc2VfZGF0ZSkpLA0KICAgICAgICAgbGFzdF91cGRhdGUyID0geWVhcihtZHkobGFzdF91cGRhdGUpKSkgfD4gDQogIHNlbGVjdChyZWxlYXNlX2RhdGUyLGxhc3RfdXBkYXRlMixjcml0aWNfc2NvcmUsZXZlcnl0aGluZygpKQ0KYGBgDQoNCiMjIExldmVsIDI6IERlc2NyaXB0aXZlcw0KDQojIyMgQ3JlYXRlIGEgdGFibGUgdGhhdCBzaG93cyB0aGUgYXZlcmFnZSBjcml0aWMgcmF0aW5ncyAo4oCYY3JpdGljX3Njb3Jl4oCZKSBhbmQgdG90YWwgc2FsZXMgKOKAmHRvdGFsX3NhbGVz4oCZKSBmb3IgZWFjaCBnZW5yZSAo4oCYZ2VucmXigJkpDQoNCmBgYHtyfQ0KQUNSX3RhYiA9IGRhdGExIHw+IA0KICBncm91cF9ieShnZW5yZSkgfD4gDQogIHN1bW1hcmlzZShjcml0aWNfcmF0aW5nc19hdmcgPSBtZWFuKGNyaXRpY19zY29yZSxuYS5ybT1UKSwNCiAgICAgICAgICAgIHRvdGFsX3NhbGVzX2F2ZyA9IG1lYW4odG90YWxfc2FsZXMsbmEucm09VCkpDQoNCnByaW50KEFDUl90YWIscHJpbnQ9SW5mKQ0KYGBgDQoNCiMjIENyZWF0ZSBhIHRhYmxlIHRoYXQgc2hvd3MgdGhlIGF2ZXJhZ2UgdG90YWwgc2FsZXMgKOKAmHRvdGFsX3NhbGVz4oCZKSBmb3IgZWFjaCBkZXZlbG9wZXIgKOKAmGRldmVsb3BlcuKAmSkNCg0KYGBge3J9DQpBVFNfdGFiID0gZGF0YTEgfD4gDQogIGdyb3VwX2J5KGRldmVsb3BlcikgfD4gDQogIHN1bW1hcmlzZSh0b3RhbF9zYWxlc19hdmcgPSBtZWFuKHRvdGFsX3NhbGVzLG5hLnJtPVQpKQ0KDQpwcmludChBVFNfdGFiKQ0KYGBgDQoNCg0KIyMgTGV2ZWwgMzogUmVzZWFyY2ggUXVlc3Rpb25zDQoNCiMjIyBXaGF0IGlzIGVhY2ggZGV2ZWxvcGVy4oCZcyAo4oCYZGV2ZWxvcGVy4oCZKSBtb3N0IHNvbGQgZ2FtZSBpbiBOb3J0aCBBbWVyaWNhICjigJhuYV9zYWxlc+KAmSk/DQoNCmBgYHtyfQ0KZGF0YTEgfD4gDQogIGdyb3VwX2J5KGRldmVsb3Blcix0aXRsZSkgfD4gDQogIHN1bW1hcmlzZShtb3N0X3NvbGRfZ2FtZSA9IG1heChuYV9zYWxlcyxuYS5ybSA9IFQpKSB8PiANCiAgYXJyYW5nZShkZXNjKG1vc3Rfc29sZF9nYW1lKSkgfD4gDQogIGRpc3RpbmN0KGRldmVsb3Blciwua2VlcF9hbGwgPSBUKQ0KYGBgDQoNCiMjIyBXaGF0IHdhcyB0aGUgbW9zdCBoaWdobHkgc29sZCBnYW1lIG9mIDIwMTg/DQoNCmBgYHtyfQ0KZGF0YTEgfD4gDQogIGZpbHRlcihyZWxlYXNlX2RhdGUyPT0yMDE4KSB8PiANCiAgZ3JvdXBfYnkodGl0bGUpIHw+IA0KICBzdW1tYXJpc2UoaGlnaGx5X3NvbGRfZ2FtZSA9IG1heCh0b3RhbF9zYWxlcyxuYS5ybSA9IFQpKSB8PiANCiAgYXJyYW5nZShkZXNjKGhpZ2hseV9zb2xkX2dhbWUpKQ0KDQojIFJlZCBEZWFkIFJlZGVtcHRpb24gMg0KYGBgDQoNCiMjIyBJcyB0aGVyZSBhIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHB1YmxpYyBwZXJjZXB0aW9uICjigJhjcml0aWNfc2NvcmXigJkpIGFuZCB0aGUgbnVtYmVyIG9mIHZpZGVvIGdhbWVzIHNvbGQgKOKAmHRvdGFsX3NhbGVz4oCZKT8NCg0KYGBge3J9DQpyZWxfbGlzdCA9IGxpc3QoJ2NyaXRpY19zY29yZScsJ3RvdGFsX3NhbGVzJykNCg0KZm9yIChpIGluIHJlbF9saXN0KXsNCiAgeD1zdW0oaXMubmEoZGF0YTFbW2ldXSkpDQogIHByaW50KHBhc3RlMChpLCI6IiwiICIseCkpDQp9DQpgYGANCg0KYGBge3J9DQpkYXRhMiA9IGRhdGExIHw+IA0KICBmaWx0ZXIoIWlzLm5hKHRvdGFsX3NhbGVzKSkNCmBgYA0KDQpgYGB7cn0NCmZvciAoaSBpbiByZWxfbGlzdCl7DQogIHg9c2hhcGlyby50ZXN0KGRhdGEyW1tpXV0pDQogIHByaW50KGkpDQogIHByaW50KHgpDQp9DQpgYGANCg0KYGBge3J9DQpmb3IgKGkgaW4gcmVsX2xpc3Qpew0KICBoaXN0KGRhdGEyW1tpXV0sbWFpbj1pLHhsYWI9aSxwcm9iYWJpbGl0eSA9IFQpDQogIGxpbmVzKGRlbnNpdHkoZGF0YTFbW2ldXSxuYS5ybSA9IFQpLGx3ZD0yLGNvbD0nZGFya3JlZCcpDQp9DQoNCg0KaWYgKEZBTFNFKSB7DQpoaXN0KGRhdGExJHRvdGFsX3NhbGVzLHByb2JhYmlsaXR5ID0gVCkNCmxpbmVzKGRlbnNpdHkoZGF0YTEkdG90YWxfc2FsZXMpKQ0KDQpoaXN0KGRhdGExJGNyaXRpY19zY29yZSxwcm9iYWJpbGl0eSA9IFQpDQpsaW5lcyhkZW5zaXR5KGRhdGExJGNyaXRpY19zY29yZSkpDQp9DQpgYGANCg0KYGBge3J9DQpjb3JfdGVzdF9saXN0ID0gbGlzdCgncycsJ2snKQ0KDQpmb3IgKGkgaW4gY29yX3Rlc3RfbGlzdCl7DQogIHg9IGNvci50ZXN0KGRhdGEyJGNyaXRpY19zY29yZSxkYXRhMiR0b3RhbF9zYWxlcyxtZXRob2QgPSBpKQ0KICBwcmludCh4KQ0KfQ0KDQojeWVzLCB0aGUgU3BlYXJtYW4gcmFuayBzaG93cyBhIA0KYGBgDQoNClRoZSBTcGVhcm1hbidzIHNob3dlZCBhIHNpZ25pZmljYW50IHBvc2l0aXZlIGNvcnJlbGF0aW9uIGJldHdlZW4gY3JpdGljIHNjb3JlIGFuZCB0b3RhbCBzYWxlcywgcmhvID0gMC4zNCwgcCA8IC4wMDEuIFRoaXMgaXMgY29uc2lzdGVudCB3aXRoIEtlbmRhbGzigJlzICh0YXUgPSAwLjI0LCBwIDwgLjAwMSksIGluZGljYXRpbmcgdGhhdCBoaWdoZXIgY3JpdGljIHNjb3JlcyBhcmUgY29ycmVsYXRlZCB3aXRoIGdyZWF0ZXIgc2FsZXMuDQoNCiMjIExldmVsIDQ6IFZpc3VhbGl6YXRpb24NCg0KIyMjIEdyYXBoIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBwdWJsaWMgcGVyY2VwdGlvbiAo4oCYY3JpdGljX3Njb3Jl4oCZKSBhbmQgdGhlIG51bWJlciBvZiB2aWRlbyBnYW1lcyBzb2xkICjigJh0b3RhbF9zYWxlc+KAmSkNCg0KYGBge3J9DQpkYXRhMiB8PiANCiAgZ2dwbG90KGFlcyh4PWNyaXRpY19zY29yZSx5PXRvdGFsX3NhbGVzLGNvbD10b3RhbF9zYWxlcykpKw0KICBnZW9tX3BvaW50KCkrDQogIHRoZW1lX2J3KCkrDQogIGdlb21faml0dGVyKCkrDQogICNnZW9tX3Ntb290aCgpKw0KICBsYWJzKHRpdGxlPSdJbiB0aGUgRXllcyBvZiB0aGUgQmVob2xkZXInKSsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC41KSkrDQogIGd1aWRlcyhjb2w9J25vbmUnKQ0KYGBgDQoNCiMjIyBHcmFwaCB0aGUgdG90YWwgbnVtYmVyIG9mIHZpZGVvIGdhbWVzIHNvbGQgYnkgRUEgU3BvcnRzICjigJhwdWJsaXNoZXLigJkpIG92ZXIgdGltZQ0KDQpgYGB7cn0NCmRhdGEzID0gZGF0YTIgfD4gDQogIGZpbHRlcihncmVwbCgnZWEgc3BvcnRzJyxwdWJsaXNoZXIsaWdub3JlLmNhc2UgPSBUKSkgfD4gDQogIG11dGF0ZShQVUJMSVNIRVJfMiA9IGNhc2Vfd2hlbihwdWJsaXNoZXIgPT0gJ0VBIFNwb3J0cyBCSUcnIH4gJ0VBIFNwb3J0cycsVFJVRSB+IHB1Ymxpc2hlcikpDQoNCmRhdGEzIHw+IA0KICBncm91cF9ieShyZWxlYXNlX2RhdGUyKSB8PiANCiAgc3VtbWFyaXNlKEhFQURDT1VOVCA9bigpKSB8PiANCiAgZ2dwbG90KGFlcyh4PXJlbGVhc2VfZGF0ZTIseT1IRUFEQ09VTlQpKSsNCiAgZ2VvbV9saW5lKGNvbD0nZGFya2dyZWVuJyxsd2Q9MSkrDQogIHRoZW1lX2J3KCkrDQogIGxhYnMoeD0nUmVsZWFzZSBZZWFyJyx5PSdHYW1lcyBTb2xkJyx0aXRsZT0nVGhlIEVBIFdheSBieSBFQSBHYW1lcycsc3VidGl0bGU9Jyh0aGUgd2F5IG9mIG1pY3JvdHJhbnNhY3Rpb25zIHRvIHNlbGwsIHNlbGwsIHNlbGwhKScpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IEhFQURDT1VOVCx2anVzdCA9IC0uNSxoanVzdCA9IDIpLHNpemUgPSAzLjUsY29sPSdkYXJrcmVkJykrDQogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSkscGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC41KSkNCmBgYA0KDQpgYGB7cn0NCmRhdGEzIHw+IA0KICBnZ3Bsb3QoYWVzKHg9cmVsZWFzZV9kYXRlMikpKw0KICBnZW9tX2JhcihmaWxsPSdkYXJrZ3JlZW4nLGNvbD0nZ29sZCcpKw0KICB0aGVtZV9idygpKw0KICBsYWJzKHg9J1JlbGVhc2UgWWVhcicseT0nR2FtZXMgU29sZCcsdGl0bGU9J1RoZSBFQSBXYXkgYnkgRUEgR2FtZXMnLHN1YnRpdGxlPScodGhlIHdheSBvZiBtaWNyb3RyYW5zYWN0aW9ucyB0byBzZWxsLCBzZWxsLCBzZWxsISknKSArDQogIGdlb21fdGV4dChzdGF0PSdjb3VudCcsYWVzKGxhYmVsID0gYWZ0ZXJfc3RhdChjb3VudCkpLHZqdXN0ID0gLTAuNSxzaXplID0gMyxjb2w9J2RhcmtyZWQnKSsNCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC41KSxwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YTQgPSBkYXRhMyB8PiANCiAgc2VsZWN0KGNvbnRhaW5zKCdzYWxlJykpIHw+IA0KICBuYS5vbWl0KCkNCg0KY29yX2ZyYW1lID0gY29yKGRhdGE0KSB8PiBhcy5kYXRhLmZyYW1lKCkNCg0KY29yX2ZyYW1lDQpgYGANCg0KYGBge3J9DQpjb3JfZnJhbWUyID0gY29yX2ZyYW1lIHw+IA0KICByb3duYW1lc190b19jb2x1bW4oJ3ZhcjEnKSB8PiANCiAgcGl2b3RfbG9uZ2VyKC12YXIxLG5hbWVzX3RvID0gJ3ZhcjInLHZhbHVlc190byA9ICdjb3JyJykNCg0KY29yX2ZyYW1lMiB8PiANCiAgZ2dwbG90KGFlcyh4PXZhcjEseT12YXIyLGZpbGw9Y29ycikpKw0KICBnZW9tX3RpbGUoKStzY2FsZV9maWxsX2dyYWRpZW50MigNCiAgICBoaWdoID0gImRhcmtyZWQiLCBsb3cgPSAibGlnaHRibHVlIiwgbWlkID0gImdyYXkiLG1pZHBvaW50ID0gMCwgbGltaXQgPSBjKC0xLCAxKSwgc3BhY2UgPSAiTGFiIixuYW1lID0gIkNvcnJlbGF0aW9uIikgKw0KICB0aGVtZV9idygpICsNCiAgbGFicyh0aXRsZT0nU2FsZXMgTWF0cml4Jyx5PScnLHg9JycpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0ID0gMSksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSkpKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoY29yciwgMikpKQ0KYGBgDQoNCmBgYHtyIGluY2x1ZGU9Rn0NCiNiZWVwIHdoZW4gZG9uZQ0KaWYgKHJlcXVpcmUoImJlZXByIikpDQogIGJlZXByOjpiZWVwKDIpDQpgYGA=