-1

I'm trying to write a function that combines up to 4 (fair 6 sided) dice rolls to create a specific value (named 'target.mountain') as many times as possible given the numbers shown on the dice.

Then return these values along with any that aren't used in said combination. If the other numbers that aren't used to form the 'target.mountain' can sum to be within the range (5-10) then do so.

So as an example say I roll 4,3,2,5 and my target.mountain value is 9

I would do

4 + 5 -> 9 and as 2 + 3 = 5 my function would return 9, 5

Another example could be

Roll = (2,3,6,4) --> (6 + 3), (4 + 2) --> 9, 6

Once these values have been found then list so it appears like

[1] 9, 5 (example 1)

[1] 9, 6 (example 2)

How do I go about doing this?

If you have ever played the board game 'Mountain Goats' then that may shed some light on how I need the dice to work as I just cannot figure it out!

2 Answers2

1

Let's make the problem a bit harder, say 5 dice.

library(tidyverse)
rolls <- sample(1:6,replace = TRUE, size = 5)
target.mountain <- 7

#Make all possible combinations of the dice:
map_dfr(seq_along(rolls),~ combn(seq_along(rolls),.x,simplify = FALSE) %>%
          map(~tibble(dice = list(.), sum = sum(rolls[.]), rolls = list(rolls[.]),length = length(.)))) %>%
  #filter to only those combinations which equal the target  
  filter(sum == target.mountain) %>%
  #Now make all possible combinations of the sets that equal the target
  {map2(.x = list(.), .y = nrow(.) %>% map(.x = seq(.), .f = combn,x=.,simplify = FALSE) %>% unlist(recursive = FALSE),
        ~.x[unlist(.y),])} %>%
  #Subset to non-overlapping sets
  subset(map_lgl(.,~length(reduce(.x$dice,union))==length(unlist(.x$dice)))) -> part1 

map(part1, as.data.frame)
#[[1]]
#  dice sum rolls length
#1 1, 3   7  3, 4      2
#
#[[2]]
#  dice sum rolls length
#1 4, 5   7  6, 1      2
#
#[[3]]
#     dice sum   rolls length
#1 2, 3, 5   7 2, 4, 1      3
#
#[[4]]
#  dice sum rolls length
#1 1, 3   7  3, 4      2
#2 4, 5   7  6, 1      2

From here you can apply whatever rules you want:

part1 %>% 
  #subset to the largest number of sets
  subset(map_dbl(.,nrow) == max(map_dbl(.,nrow))) %>%
  #subset to the fewest number of total dice
  subset(map_dbl(.,~sum(.x$length)) == min(map_dbl(.,~sum(.x$length)))) %>%
  #if there are still ties, pick the first
  `[[`(1) -> part2

as.data.frame(part2)
#  dice sum rolls length
#1 1, 3   7  3, 4      2
#2 4, 5   7  6, 1      2
Ian Campbell
  • 23,484
  • 14
  • 36
  • 57
  • This is also great, thanks so much for your help. However how do I pull the values from the map so they are in a list format? is it just list()? – KnowsNothingAboutCode Mar 29 '21 at 17:40
  • There is, oddly enough, a function `dplyr::pull`, you could use `part2 %>% pull(rolls)` which will result in a list. – Ian Campbell Mar 29 '21 at 17:42
  • You're a hero, honestly saved me from going insane. Thank you so much! – KnowsNothingAboutCode Mar 29 '21 at 17:46
  • Sorry for the second question but I don't suppose you could help again? Now i know which dice are used to combine to get the target.mountain how do I return() the other dice which are not used along with the target.mountain value in a list? Have been playing round with it for a hour or so now. – KnowsNothingAboutCode Mar 29 '21 at 19:15
  • Keep running into this error and have no idea how to fix it. Error: `.x` is empty, and no `.init` supplied – KnowsNothingAboutCode Mar 29 '21 at 21:09
0

possible solution to the problem

target.mountain = 9
dice <- c(4,3,2,5)

library(tidyverse)

fn <- function(target.mountain, dice){
  fltr <- map(seq_along(dice), ~combn(dice, .x, sum) == target.mountain)
  out <- map(seq_along(dice), ~combn(dice, .x))
  sum_target <- map2(out, fltr, ~.x[, .y]) %>% 
    purrr::discard(.x = ., function(x) length(x) == 0) %>% 
    keep(.x = ., .p = function(x) length(x) == min(lengths(.))) %>% 
    flatten_dbl()
  
  no_sum_target <- dice[!(dice %in% sum_target)]
  result <- toString(c(sum(sum_target), no_sum_target))
  return(result)
}

fn(target.mountain = target.mountain, dice = dice)
#> [1] "9, 3, 2"

Created on 2021-03-29 by the reprex package (v1.0.0)

Yuriy Saraykin
  • 8,390
  • 1
  • 7
  • 14
  • This is brilliant! I honestly have been stuck on this for days. Can't explain how much this is going to help as I had no idea where to go. Now the values which I have to return are only of use if they are in the range (5-10). Now that you've helped me get the values I need is there a way of combining the 'remaining' number (ones that are not == target.mountain) so that they can be of use? Can clarify if that doesn't make sense? – KnowsNothingAboutCode Mar 29 '21 at 17:39
  • look at @Ian Campbell's answer, it is much more versatile – Yuriy Saraykin Mar 29 '21 at 17:45
  • No problem, regardless thank you so much for your help! Means so much. – KnowsNothingAboutCode Mar 29 '21 at 17:49
  • was glad if my answer helped you – Yuriy Saraykin Mar 29 '21 at 17:51