In general, if you're coding in R and considering a for
loop, there's probably a better way to do it. I think this is a case in point. Also, whilst Ha Nguyen's solution is correct for the use case you have in your question, it will be dificult to generalise. This is because your data structure is not tidy. It's not tidy because your column names contain data: the numeric suffices indicate which die roll/player/etc the contents relate to. The code is therefore fragile with respect to changes in the number of players, rolls and rounds you are simulating.
A tidy solutuon would avoid this issue by using a single column to contain the die roll and have additional columns to indicate the round and player to which the die roll relates.
Here's a posible implementation:
library(tidyverse)
simulate <- function(nPlayers=2, nRolls=2, nRounds=12) {
# Generate tidy data
data <- tibble() %>%
expand(
Round=c(1:nRounds),
Player=c(1:nPlayers),
Roll=c(1:nRolls)
) %>%
mutate(Result=ceiling(runif(nrow(.), max=6)))
# Calculate each player's score in each round
totalsByRound <- data %>%
group_by(Round, Player) %>%
summarise(Score=sum(Result), .groups="drop")
# Determine winner for each round
winners <- totalsByRound %>%
group_by(Round) %>%
slice_max(Score) %>%
rename(
Winner=Player,
WinningScore=Score
) %>%
# n() calculates group size with grouped data, so n() > 1 indicates a tie
filter(n() == 1)
# Convert to wide format ease of presentation
totalsByRound <- totalsByRound %>%
pivot_wider(
names_from=Player,
values_from=Score,
names_prefix="Player"
) %>%
left_join(winners, by="Round")
return(totalsByRound)
}
And an example use
# For repoducibility
set.seed(1234)
# Do the simulation
results <- simulate()
results
# A tibble: 12 x 5
Round Player1 Player2 Winner WinningScore
<int> <dbl> <dbl> <int> <dbl>
1 1 5 8 2 8
2 2 10 3 1 10
3 3 8 9 2 9
4 4 8 8 NA NA
5 5 4 4 NA NA
6 6 4 2 1 4
7 7 7 10 2 10
8 8 6 5 1 6
9 9 6 7 2 7
10 10 4 11 2 11
11 11 8 6 1 8
12 12 6 8 2 8
Player scores are summarised easily:
# Summarise results
results %>%
group_by(Winner) %>%
summarise(RoundsWon=n(), .groups="drop")
# A tibble: 3 x 2
Winner RoundsWon
<int> <int>
1 1 4
2 2 6
3 NA 2
You could of course include the summary in the simulate
function, but I'm not sure what your actual use case is, so I left it out.
The code will handle other numbers of players, rounds and rolls just by changing parameter values:
# Demo for different parameters
simulate(nPlayers=4, nRolls=3, nRounds=100)
# A tibble: 100 x 7
Round Player1 Player2 Player3 Player4 Winner WinningScore
<int> <dbl> <dbl> <dbl> <dbl> <int> <dbl>
1 1 8 11 8 13 4 13
2 2 9 8 7 11 4 11
3 3 7 8 13 8 3 13
4 4 11 9 8 6 1 11
5 5 5 10 5 4 2 10
6 6 9 8 14 8 3 14
7 7 17 11 9 12 1 17
8 8 10 13 14 11 3 14
9 9 12 14 6 14 NA NA
10 10 13 11 6 8 1 13
# … with 90 more rows
The tidy solution is more robust than using the for
loop and should be noticably faster for "large" datasets. It's also more compact and, I believe, easier to understand.