1

I am solving a R-simulation qn, when the sum of the 2 dice == the round number (i.e. round 1, round 2...) then add 1 point to the player, else return 0 points. But I am unfamiliar with R for-loop. Appreciate if anyone could help!

Here is my rough plan, but the code doesn't work out well. Eventually, I will want to change the player number and number of iterations easily.

  iteration <- function(iter) {

  n <- 12
  
  # Number of players
  player <- 2
  player.score <- rep(NA, 2)
  player.score
  
  # Roll 1st dice
  Result.1 <- sample(1:6,1)
  
  # Roll 2nd dice
  Result.2 <- sample(1:6, 1)
  
  result <- matrix(0L, ncol=5, nrow=n)
  colnames(result) <- c('Round#', "Result.1", "Result.2", "Total", "Win?")
  
  for(i in 1:player) {
  
  result[, 'Round#'] <- seq(1:n)  

  result[, 'Result.1'] <- sample(Result.1)
  
  result[, 'Result.2'] <- sample(Result.2)
  
  result[, 'Total'] <- rowSums(result[, 2:3])

  result[, 'Win?'] <- ifelse(result[, 'Total'] == result[, 'Round#'], 1, 0)
  result
  
  total.score <- sum(result[, "Win?"])
  player.score[i] <- total.score
  
  }

2 Answers2

1

This might be helpful

set.seed(12)

# Set number of players and number of iterations 
n_player = 3
n_trial = 5

# Empty matrix to store scores of all players 
player_score = matrix(nrow=n_trial, ncol=n_player)

# For each player, run n iterations  
for (p in 1:n_player) {
  for (i in 1:n_trial) {
    dice_1 = sample(1:6,1)
    dice_2 = sample(1:6,1)
    if (dice_1 + dice_2 == i) {
      score = 1
    } else {
      score = 0
    }
    player_score[i, p] = score
  }
}

player_score
     [,1] [,2] [,3]
[1,]    0    0    0
[2,]    0    0    0
[3,]    1    0    0
[4,]    0    0    0
[5,]    0    1    0

total_score = colSums(player_score) 
total_score
[1] 1 1 0
lil_barnacle
  • 168
  • 7
  • Hi, ideally it should return a vector that looks something like this --> 0 0 0 1 0 0 1; where 1 represents the instances where the number is the same, and 0 represents the instances where the number is different. So that I can do a sum of it and compare the score of different players. Sorry for being ambiguous! – ppotatomato Feb 28 '21 at 08:29
1

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.

Limey
  • 10,234
  • 2
  • 12
  • 32
  • Hi Limey, thanks for your help, the outcome looks fantastic and definitely easier to understand than what I have been doing. Just a clarification, what is the purpose of this line of code here? "mutate(Result=ceiling(runif(nrow(.), max=6)))", why is the max set at 6? – ppotatomato Mar 01 '21 at 01:53
  • It serves the same purpuse as your `sample(1:6, 1)`: it generates the dice rolls. `runif` generates random (floating point) numbers from a Uniform(min, max) distribution. min defaults to zero. `nrow(.)` defines how many numbers to generate: one for each row of the current data frame. The `.` means "the current data frame. `ceiling()` converts floating point numbers in the range [0, 6) to integers between 1 and 6. If you think my answer is worthy, please accept it by clicking on the tick at top left. – Limey Mar 01 '21 at 08:15