The game of Warmachine has a rather nice mechanic for combat. The sum of two (or more) dice are added to the attackers’ melee attack stat (MAT), and these must equal or exceed the defender’s defence stat (DEF). This is great as it means that high and low skill warriors can be represented and quickly calculated without endless references to charts. It also means that average dice are more likely than extreme highs and lows. An exception is that snake-eyes always miss. Damage is applied in a same way. Damage caused by the attack is the sum of two (or more) dice and the power (POW), or power plus strength, less the armour value (ARM) of the defender.

We can work out likely outcomes of combat since we know all possible outcomes of rolling two dice. I created a function to return combinations of dice rolls using the **permutations** function from the package **gtools**. This is a really cool feature of R. Modules of code can just be dumped into your session. I used it to calculate all outcomes on **n** dice, work out all of the totals and count up how many there are of each. This is not the most elegant solution, but it works.

> require(gtools) Loading required package: gtools > # get distribution of dice rolls > # > # calculate the value of each possible option when rolling dice > # n, the number of dice > # D, the number of sides > > diceDistr <- function(n, D = 6) { + + if (length(n) != 1) { + + warning("length n should equal 1, using n[1]") + + n <- n[1] + } + + nvals <- D^n + + vals <- seq.int(from = n, to = nvals) + + combs <- permutations(D, n, v = seq_len(D), + set = FALSE, repeats.allowed = TRUE) + + totals <- apply(combs, 1, sum) + + tabTotals <- c(table(totals)) + + tabFracs <- tabTotals / sum(tabTotals) + + return(tabFracs) + } > # TEST 1: 2D6 > test1 <- diceDistr(n = 2, D = 6) > all(test1 == c(1:6, 5:1) / 36) [1] TRUE

Once we know the probability of each number, we can find the likelihood of a successful attack. The function **cumsum** is useful; it finds the cumulative sum of a numeric vector. We want to know the probability of rolling 7 or greater, so **rev** lets us reverse the order of the vector.

> round(rev(cumsum(rev(diceDistr(n = 2, D = 6))) * 100)) 2 3 4 5 6 7 8 9 10 11 12 100 97 92 83 72 58 42 28 17 8 3

A Steelhead halberdier has MAT 5, Ragman has DEF 14. The Steelhead is hitting on nines, so he has a 28% chance of catching the slippery Thamarite.

Of course snake-eyes is a miss, so I need to adjust how we calculate these probabilities. This small function is one possible implementation.

> # probability that mat + dice is >= def > > probSuccess <- function(mat, def, n) { + + dProb <- diceDistr(n) + + success <- mat + as.numeric(names(dProb)) >= def + + # all 1 always fails + success[1] <- FALSE + + # all 6s always succeeds + success[length(success)] <- TRUE + + prob <- sum(dProb[success]) + return(prob) + } > > # TEST 1 mat 5 vs def 14 == 28% > test1 <- probSuccess(mat = 5, def = 14, n = 2) > round(test1 * 100) == 28 [1] TRUE > > # TEST 2 mat 1 vs def 4 == 99.5% > test2 <- probSuccess(mat = 1, def = 4, n = 3) > test2 == 1 - 1/6^3 [1] TRUE > > # TEST 3 mat 1 vs def 19, 3D6 == 0.5% > test3 <- probSuccess(mat = 1, def = 19, n = 3) > test3 == 1/6^3 [1] TRUE

Using this function we can create a table of probabilities. I created a 3D numeric object called an array. This gave me every combination of MAT and DEF. I used these to pass the pairs of MAT and DEF to probSuccess to create the table. The function apply performs a loop across an object, in this case, summarizing the array.

> MAT <- seq_len(15) > DEF <- seq_len(20) > # rows of mat > matMat <- matrix(rep(MAT, times = length(DEF)), nrow = length(MAT)) > # columns of def > defMat <- matrix(rep(DEF, each = length(MAT)), nrow = length(MAT)) > > # tabulation array > cArray <- array(c(matMat, defMat), + dim = c(length(MAT), length(DEF), 2), + dimnames = list(MAT, DEF, c("MAT", "DEF"))) > probTable2 <- apply(cArray, 1:2, function(x) { + probSuccess(mat = x[1], def = x[2], n = 2) }) > dimnames(probTable2) <- list(MAT, DEF) > round(probTable2[7:14, 5:18] * 100) 5 6 7 8 9 10 11 12 13 14 15 16 17 18 7 97 97 97 97 97 97 92 83 72 58 42 28 17 8 8 97 97 97 97 97 97 97 92 83 72 58 42 28 17 9 97 97 97 97 97 97 97 97 92 83 72 58 42 28 10 97 97 97 97 97 97 97 97 97 92 83 72 58 42 11 97 97 97 97 97 97 97 97 97 97 92 83 72 58 12 97 97 97 97 97 97 97 97 97 97 97 92 83 72 13 97 97 97 97 97 97 97 97 97 97 97 97 92 83 14 97 97 97 97 97 97 97 97 97 97 97 97 97 92 > require(colorspace) Loading required package: colorspace > image(x = MAT, y = DEF, z = probTable2, + col = diverge_hcl(60, c = 100, l = (range(probTable2) + 1) * 45, power = 1)) > > text(seq(2, 12, by = 2), + seq(18, 8, by = -2), + labels = round(probTable2[seq(2, 12, by = 2), + seq(18, 8, by = -2)][as.logical(diag(6))] * 100))

> # get prob of success for each combination, 3 dice > probTable3 <- apply(cArray, 1:2, function(x) { + probSuccess(mat = x[1], def = x[2], n = 3) }) > dimnames(probTable3) <- list(MAT, DEF) > round(probTable3[7:14, 5:18] * 100, 1) 5 6 7 8 9 10 11 12 13 14 15 16 17 18 7 99.5 99.5 99.5 99.5 99.5 99.5 99.5 98.1 95.4 90.7 83.8 74.1 62.5 50.0 8 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 98.1 95.4 90.7 83.8 74.1 62.5 9 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 98.1 95.4 90.7 83.8 74.1 10 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 98.1 95.4 90.7 83.8 11 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 98.1 95.4 90.7 12 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 98.1 95.4 13 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 98.1 14 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 99.5 > > image(x = MAT, y = DEF, z = probTable3, + col = diverge_hcl(60, c = 100, + l = (range(probTable3) + 1) * 45, power = 1)) > > text(seq(2, 12, by = 2), + seq(18, 8, by = -2), + labels = round(probTable3[seq(2, 12, by = 2), + seq(18, 8, by = -2)][as.logical(diag(6))] * 100))

So no problems there. Attack roll probabilities are identical for a given difference between the MAT and DEF. These tables will be useful for working out how combat actions are likely to progress.

Pingback: smashing times | analytical gaming

Pingback: do you wanna be in my gang? | analytical gaming