For this project, I will use R to extract player information from a text file containing chess tournament results. The final dataset will include each player’s name, state, total points, pre-tournament rating, and average pre-tournament rating of their opponents. I will then export the completed dataframe as a CSV file.
I loaded the original text file into R as separate lines. I will use the repeated structure of the file to identify the player records and extract the required information.
tournament_lines <- readLines(
"tournamentinfo.txt",
warn = FALSE
)
length(tournament_lines)
## [1] 196
head(tournament_lines, 10)
## [1] "-----------------------------------------------------------------------------------------"
## [2] " Pair | Player Name |Total|Round|Round|Round|Round|Round|Round|Round| "
## [3] " Num | USCF ID / Rtg (Pre->Post) | Pts | 1 | 2 | 3 | 4 | 5 | 6 | 7 | "
## [4] "-----------------------------------------------------------------------------------------"
## [5] " 1 | GARY HUA |6.0 |W 39|W 21|W 18|W 14|W 7|D 12|D 4|"
## [6] " ON | 15445895 / R: 1794 ->1817 |N:2 |W |B |W |B |W |B |W |"
## [7] "-----------------------------------------------------------------------------------------"
## [8] " 2 | DAKSHESH DARURI |6.0 |W 63|W 58|L 4|W 17|W 16|W 20|W 7|"
## [9] " MI | 14598900 / R: 1553 ->1663 |N:2 |B |W |B |W |B |W |B |"
## [10] "-----------------------------------------------------------------------------------------"
Each player’s information appears across two lines. The first line contains the player’s number, name, points, and opponents. The second line contains the player’s state and pre-tournament rating. I identified these two types of lines using their repeated patterns.
player_line_numbers <- grep(
"^\\s*[0-9]+\\s*\\|",
tournament_lines
)
rating_line_numbers <- grep(
"^\\s*[A-Z]{2}\\s*\\|",
tournament_lines
)
player_lines <- tournament_lines[player_line_numbers]
rating_lines <- tournament_lines[rating_line_numbers]
length(player_lines)
## [1] 64
length(rating_lines)
## [1] 64
I separated each record at the vertical bars in the text file. From these fields, I extracted each player’s tournament number, name, total points, state, and pre-tournament rating.
split_record <- function(record) {
trimws(strsplit(record, "|", fixed = TRUE)[[1]])
}
player_fields <- lapply(player_lines, split_record)
rating_fields <- lapply(rating_lines, split_record)
pair_number <- as.integer(
sapply(player_fields, function(record) record[1])
)
player_name <- sapply(
player_fields,
function(record) record[2]
)
total_points <- as.numeric(
sapply(player_fields, function(record) record[3])
)
player_state <- sapply(
rating_fields,
function(record) record[1]
)
rating_text <- sapply(
rating_fields,
function(record) record[2]
)
pre_rating <- as.integer(
sub(".*R:\\s*([0-9]+).*", "\\1", rating_text)
)
player_information <- data.frame(
pair_number,
player_name,
player_state,
total_points,
pre_rating,
stringsAsFactors = FALSE
)
head(player_information)
## pair_number player_name player_state total_points pre_rating
## 1 1 GARY HUA ON 6.0 1794
## 2 2 DAKSHESH DARURI MI 6.0 1553
## 3 3 ADITYA BAJAJ MI 6.0 1384
## 4 4 PATRICK H SCHILLING MI 5.5 1716
## 5 5 HANSHI ZUO MI 5.5 1655
## 6 6 HANSEN SONG OH 5.0 1686
Each completed round contains a result—win, loss, or draw—followed by the opponent’s player number. Entries such as byes, half-point byes, and unplayed rounds do not identify an opponent, so I excluded them.
round_results <- lapply(
player_fields,
function(record) record[4:10]
)
extract_opponents <- function(rounds) {
completed_games <- grepl(
"^[WLD]\\s*[0-9]+",
rounds
)
opponent_ids <- sub(
"^[WLD]\\s*([0-9]+).*$",
"\\1",
rounds[completed_games]
)
as.integer(opponent_ids)
}
opponent_numbers <- lapply(
round_results,
extract_opponents
)
opponent_numbers[[1]]
## [1] 39 21 18 14 7 12 4
I matched each opponent number with that opponent’s pre-tournament rating. I then calculated the mean of those ratings for every player and rounded the result to the nearest whole number, following the assignment example.
average_opponent_pre_rating <- sapply(
opponent_numbers,
function(opponent_ids) {
opponent_ratings <- pre_rating[
match(opponent_ids, pair_number)
]
round(mean(opponent_ratings))
}
)
chess_results <- data.frame(
player_name,
player_state,
total_points,
pre_rating,
average_opponent_pre_rating,
stringsAsFactors = FALSE
)
head(chess_results)
## player_name player_state total_points pre_rating
## 1 GARY HUA ON 6.0 1794
## 2 DAKSHESH DARURI MI 6.0 1553
## 3 ADITYA BAJAJ MI 6.0 1384
## 4 PATRICK H SCHILLING MI 5.5 1716
## 5 HANSHI ZUO MI 5.5 1655
## 6 HANSEN SONG OH 5.0 1686
## average_opponent_pre_rating
## 1 1605
## 2 1469
## 3 1564
## 4 1574
## 5 1501
## 6 1519
The final dataframe contains the five required fields for all 64 players. I checked the number of rows and searched for missing values before exporting the results.
nrow(chess_results)
## [1] 64
colSums(is.na(chess_results))
## player_name player_state
## 0 0
## total_points pre_rating
## 0 0
## average_opponent_pre_rating
## 0
knitr::kable(
chess_results,
caption = "Chess Tournament Player Results"
)
| player_name | player_state | total_points | pre_rating | average_opponent_pre_rating |
|---|---|---|---|---|
| GARY HUA | ON | 6.0 | 1794 | 1605 |
| DAKSHESH DARURI | MI | 6.0 | 1553 | 1469 |
| ADITYA BAJAJ | MI | 6.0 | 1384 | 1564 |
| PATRICK H SCHILLING | MI | 5.5 | 1716 | 1574 |
| HANSHI ZUO | MI | 5.5 | 1655 | 1501 |
| HANSEN SONG | OH | 5.0 | 1686 | 1519 |
| GARY DEE SWATHELL | MI | 5.0 | 1649 | 1372 |
| EZEKIEL HOUGHTON | MI | 5.0 | 1641 | 1468 |
| STEFANO LEE | ON | 5.0 | 1411 | 1523 |
| ANVIT RAO | MI | 5.0 | 1365 | 1554 |
| CAMERON WILLIAM MC LEMAN | MI | 4.5 | 1712 | 1468 |
| KENNETH J TACK | MI | 4.5 | 1663 | 1506 |
| TORRANCE HENRY JR | MI | 4.5 | 1666 | 1498 |
| BRADLEY SHAW | MI | 4.5 | 1610 | 1515 |
| ZACHARY JAMES HOUGHTON | MI | 4.5 | 1220 | 1484 |
| MIKE NIKITIN | MI | 4.0 | 1604 | 1386 |
| RONALD GRZEGORCZYK | MI | 4.0 | 1629 | 1499 |
| DAVID SUNDEEN | MI | 4.0 | 1600 | 1480 |
| DIPANKAR ROY | MI | 4.0 | 1564 | 1426 |
| JASON ZHENG | MI | 4.0 | 1595 | 1411 |
| DINH DANG BUI | ON | 4.0 | 1563 | 1470 |
| EUGENE L MCCLURE | MI | 4.0 | 1555 | 1300 |
| ALAN BUI | ON | 4.0 | 1363 | 1214 |
| MICHAEL R ALDRICH | MI | 4.0 | 1229 | 1357 |
| LOREN SCHWIEBERT | MI | 3.5 | 1745 | 1363 |
| MAX ZHU | ON | 3.5 | 1579 | 1507 |
| GAURAV GIDWANI | MI | 3.5 | 1552 | 1222 |
| SOFIA ADINA STANESCU-BELLU | MI | 3.5 | 1507 | 1522 |
| CHIEDOZIE OKORIE | MI | 3.5 | 1602 | 1314 |
| GEORGE AVERY JONES | ON | 3.5 | 1522 | 1144 |
| RISHI SHETTY | MI | 3.5 | 1494 | 1260 |
| JOSHUA PHILIP MATHEWS | ON | 3.5 | 1441 | 1379 |
| JADE GE | MI | 3.5 | 1449 | 1277 |
| MICHAEL JEFFERY THOMAS | MI | 3.5 | 1399 | 1375 |
| JOSHUA DAVID LEE | MI | 3.5 | 1438 | 1150 |
| SIDDHARTH JHA | MI | 3.5 | 1355 | 1388 |
| AMIYATOSH PWNANANDAM | MI | 3.5 | 980 | 1385 |
| BRIAN LIU | MI | 3.0 | 1423 | 1539 |
| JOEL R HENDON | MI | 3.0 | 1436 | 1430 |
| FOREST ZHANG | MI | 3.0 | 1348 | 1391 |
| KYLE WILLIAM MURPHY | MI | 3.0 | 1403 | 1248 |
| JARED GE | MI | 3.0 | 1332 | 1150 |
| ROBERT GLEN VASEY | MI | 3.0 | 1283 | 1107 |
| JUSTIN D SCHILLING | MI | 3.0 | 1199 | 1327 |
| DEREK YAN | MI | 3.0 | 1242 | 1152 |
| JACOB ALEXANDER LAVALLEY | MI | 3.0 | 377 | 1358 |
| ERIC WRIGHT | MI | 2.5 | 1362 | 1392 |
| DANIEL KHAIN | MI | 2.5 | 1382 | 1356 |
| MICHAEL J MARTIN | MI | 2.5 | 1291 | 1286 |
| SHIVAM JHA | MI | 2.5 | 1056 | 1296 |
| TEJAS AYYAGARI | MI | 2.5 | 1011 | 1356 |
| ETHAN GUO | MI | 2.5 | 935 | 1495 |
| JOSE C YBARRA | MI | 2.0 | 1393 | 1345 |
| LARRY HODGE | MI | 2.0 | 1270 | 1206 |
| ALEX KONG | MI | 2.0 | 1186 | 1406 |
| MARISA RICCI | MI | 2.0 | 1153 | 1414 |
| MICHAEL LU | MI | 2.0 | 1092 | 1363 |
| VIRAJ MOHILE | MI | 2.0 | 917 | 1391 |
| SEAN M MC CORMICK | MI | 2.0 | 853 | 1319 |
| JULIA SHEN | MI | 1.5 | 967 | 1330 |
| JEZZEL FARKAS | ON | 1.5 | 955 | 1327 |
| ASHWIN BALAJI | MI | 1.0 | 1530 | 1186 |
| THOMAS JOSEPH HOSMER | MI | 1.0 | 1175 | 1350 |
| BEN LI | MI | 1.0 | 1163 | 1263 |
I exported the completed dataframe as a CSV file so it can be used in another program or imported into a database.
write.csv(
chess_results,
"chess_tournament_results.csv",
row.names = FALSE
)
file.exists("chess_tournament_results.csv")
## [1] TRUE
I successfully extracted the required information for all 64 chess players from the original tournament text file. I used each player’s round results to identify their opponents and matched those opponent numbers with their pre-tournament ratings. The final dataframe contains each player’s name, state, total points, pre-rating, and average opponent pre-rating. I also exported the completed results as a CSV file that can be imported into a database or used for further analysis.
I used ChatGPT to help develop parts of the R code, understand how to extract information from the structured text file, and check the final calculations.
OpenAI. (2026). ChatGPT (GPT-5) [Large language model]. https://chatgpt.com. Accessed September 26, 2026.