smashing times

Damage rolls in Warmachine are calculated similarly to attack rolls (see previous discussion). If we are committing resources to improve our board position, we should have an idea of the likely effectiveness of that commitment. A commonly quoted statistic is expected damage output.

In stats, the expected value is the best guess at what a random process will produce. Luckily rolling dice is rather well defined, so we can calculate the expected outcomes directly. If we add up the total of all possible values divided by the odds (or multiplied by the probability) of getting that value, we have the expected value.

> diceDistr(n = 2)
         2          3          4          5          6          7
0.02777778 0.05555556 0.08333333 0.11111111 0.13888889 0.16666667
         8          9         10         11         12
0.13888889 0.11111111 0.08333333 0.05555556 0.02777778
> sum(diceDistr(n = 2) * seq(from = 2, to = 12))
[1] 7

For dice, the expected value is also the most likely value, since the distribution is symmetrical and bell shaped. This is not the case for all distributions. So rolling damage with two dice, we expect to add 7 to the power (or power and strength).

> sum(diceDistr(n = 3) * seq(from = 3, to = 6 * 3))
[1] 10.5
> sum(diceDistr(n = 4) * seq(from = 4, to = 6 * 4))
[1] 14

For rolling 3 or 4 dice we expect to add 10.5 or 14 to the power. If we wish to calculate the expected value for a given pair of power and armour values, we should take into account that not every roll causes damage. There are no negative damage rolls. The distribution becomes truncated and so its properties change somewhat.

> PAS <- seq_len(22)
> ARM <- seq_len(27)
> # expected damage value
> #
> # pas, power and strength (single numeric)
> # arm, armour value (single numeric)
> # n, number of dice (single numeric
> # D, number of sides of dice (single numeric)
>
> meanEffect <- function(pas, arm, n, D = 6) {
+    
+     distr <- diceDistr(n = n, D = D)
+
+     # the effect density
+     distrE <- pas + as.numeric(names(distr)) - arm
+    
+     # no negative effect
+     distrE[distrE < 0] <- 0
+
+     distrP <- distrE * distr
+
+     # mean effect is sum of effect density
+     meanE <- sum(distrP)
+
+     return(meanE)
+ }
>
> # TEST 1: expected outcome
> test1 <- meanEffect(pas = 12, arm = 13, n = 2)
> test1 == 12 + 7 - 13
[1] TRUE
> # TEST 2: 2 damage boxcars or 1 damage 5+6 or 6+5
> test2 <- meanEffect(pas = 7, arm = 17, n = 2)
> test2 == 2 * 1 / 36 + 1 * 2 / 36
[1] TRUE

Note that this is not exactly the same as adding 7 to the difference. A POW 7 attack still has a chance of causing damage to an ARM 17 target; an 11 or 12 will be successful.

> PAS <- seq_len(22)
> ARM <- seq_len(27)
> # pas by row
> pasMat <- matrix(rep(PAS, times = length(ARM)),
+     nrow = length(PAS))
>
> # arm by column
> armMat <- matrix(rep(ARM, each = length(PAS)),
+     nrow = length(PAS))
>
> # tabulation array
> dArray <- array(c(pasMat, armMat),
+     dim = c(length(PAS), length(ARM), 2))

We can create tables of damage output as for probability of hitting.

> mueTabled2 <- apply(dArray, 1:2, function(x) {
+         meanEffect(pas = x[1], arm = x[2], n = 2) })
> dimnames(mueTabled2) <- list(PAS, ARM)
> round(mueTabled2[6:18, 10:25], 1)
     10   11   12   13   14   15  16  17  18  19  20  21  22  23  24  25
6   3.1  2.3  1.6  1.0  0.6  0.3 0.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
7   4.0  3.1  2.3  1.6  1.0  0.6 0.3 0.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
8   5.0  4.0  3.1  2.3  1.6  1.0 0.6 0.3 0.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0
9   6.0  5.0  4.0  3.1  2.3  1.6 1.0 0.6 0.3 0.1 0.0 0.0 0.0 0.0 0.0 0.0
10  7.0  6.0  5.0  4.0  3.1  2.3 1.6 1.0 0.6 0.3 0.1 0.0 0.0 0.0 0.0 0.0
11  8.0  7.0  6.0  5.0  4.0  3.1 2.3 1.6 1.0 0.6 0.3 0.1 0.0 0.0 0.0 0.0
12  9.0  8.0  7.0  6.0  5.0  4.0 3.1 2.3 1.6 1.0 0.6 0.3 0.1 0.0 0.0 0.0
13 10.0  9.0  8.0  7.0  6.0  5.0 4.0 3.1 2.3 1.6 1.0 0.6 0.3 0.1 0.0 0.0
14 11.0 10.0  9.0  8.0  7.0  6.0 5.0 4.0 3.1 2.3 1.6 1.0 0.6 0.3 0.1 0.0
15 12.0 11.0 10.0  9.0  8.0  7.0 6.0 5.0 4.0 3.1 2.3 1.6 1.0 0.6 0.3 0.1
16 13.0 12.0 11.0 10.0  9.0  8.0 7.0 6.0 5.0 4.0 3.1 2.3 1.6 1.0 0.6 0.3
17 14.0 13.0 12.0 11.0 10.0  9.0 8.0 7.0 6.0 5.0 4.0 3.1 2.3 1.6 1.0 0.6
18 15.0 14.0 13.0 12.0 11.0 10.0 9.0 8.0 7.0 6.0 5.0 4.0 3.1 2.3 1.6 1.0
> require(colorspace)
Loading required package: colorspace
> image(x = PAS, y = ARM, z = mueTabled2,
+     col = heat_hcl(40, c = 80,
+         l = (range(mueTabled2) + 15) * 2.5, power = 0.8))
>
> text(seq(4, 19, by = 3),
+     seq(19, 4, by = -3),
+         labels = round(mueTabled2[seq(4, 19, by = 3),
+             seq(19, 4, by = -3)][as.logical(diag(6))]))

Image

Likewise, for boosted damage rolls, we can calculate the damage expected. I scaled the plots so that the same tone corresponds to the same value as in the plot above.

> mueTabled3 <- apply(dArray, 1:2, function(x) {
+         meanEffect(pas = x[1], arm = x[2], n = 3) })
> dimnames(mueTabled3) <- list(PAS, ARM)
> round(mueTabled3[6:18, 10:25], 1)
     10   11   12   13   14   15   16   17   18  19  20  21  22  23  24  25
6   6.5  5.5  4.6  3.7  2.8  2.1  1.5  1.0  0.6 0.3 0.2 0.1 0.0 0.0 0.0 0.0
7   7.5  6.5  5.5  4.6  3.7  2.8  2.1  1.5  1.0 0.6 0.3 0.2 0.1 0.0 0.0 0.0
8   8.5  7.5  6.5  5.5  4.6  3.7  2.8  2.1  1.5 1.0 0.6 0.3 0.2 0.1 0.0 0.0
9   9.5  8.5  7.5  6.5  5.5  4.6  3.7  2.8  2.1 1.5 1.0 0.6 0.3 0.2 0.1 0.0
10 10.5  9.5  8.5  7.5  6.5  5.5  4.6  3.7  2.8 2.1 1.5 1.0 0.6 0.3 0.2 0.1
11 11.5 10.5  9.5  8.5  7.5  6.5  5.5  4.6  3.7 2.8 2.1 1.5 1.0 0.6 0.3 0.2
12 12.5 11.5 10.5  9.5  8.5  7.5  6.5  5.5  4.6 3.7 2.8 2.1 1.5 1.0 0.6 0.3
13 13.5 12.5 11.5 10.5  9.5  8.5  7.5  6.5  5.5 4.6 3.7 2.8 2.1 1.5 1.0 0.6
14 14.5 13.5 12.5 11.5 10.5  9.5  8.5  7.5  6.5 5.5 4.6 3.7 2.8 2.1 1.5 1.0
15 15.5 14.5 13.5 12.5 11.5 10.5  9.5  8.5  7.5 6.5 5.5 4.6 3.7 2.8 2.1 1.5
16 16.5 15.5 14.5 13.5 12.5 11.5 10.5  9.5  8.5 7.5 6.5 5.5 4.6 3.7 2.8 2.1
17 17.5 16.5 15.5 14.5 13.5 12.5 11.5 10.5  9.5 8.5 7.5 6.5 5.5 4.6 3.7 2.8
18 18.5 17.5 16.5 15.5 14.5 13.5 12.5 11.5 10.5 9.5 8.5 7.5 6.5 5.5 4.6 3.7
> # colours are scaled for equivalent tones between images
> image(x = PAS, y = ARM, z = mueTabled3,
+     col = heat_hcl(40, c = 80,
+         l = (range(mueTabled3) + 15) * 2.5, power = 0.8))
>
> text(seq(4, 19, by = 3),
+     seq(19, 4, by = -3),
+         labels = round(mueTabled3[seq(4, 19, by = 3),
+             seq(19, 4, by = -3)][as.logical(diag(6))]))

Image

So when we’re breaking stuff, we can have a good idea of how much damage we’re likely to cause. But beware; you’ll only achieve the expected value or more half of the time (okay 58% of the time). The rest of the time you’ll be rolling less.

Advertisements

One thought on “smashing times

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s