Initiate battle simulation

First, a personal note.

Based on the traffic in this blog, and the Reddit up-votes, I understand that the blog is quite appreciated. I’m glad if this is the case, and I would be happy to hear some comments, thoughts, and correction to what I post here. I’ve started to work on a combat manager. It is part of my github repository and you are welcome to download and experiment with it there.

Background.

As a DM, when I plan encounters at home, I’m don’t have any estimation about how easy or hard an encounter is. The DC value, and encounter building guidance, are a way to evaluate that, and are good option. However, a gap remains where I’m not quite sure how deadly the encounter is. In addition, often I don’t know what the effect of extra ability, or terrain feature, that I think to add will have. Sometimes also, several possible tactics for the minions is available, and I wish I could test this before the battle. More about combat tactics as a DM are available in the angry DM blog. The ideal solution is to let the party play the scenario, see how the players handle it, and tweak things. Naturally, this would harm the game purpose, and won’t be enjoyable. Simulating the battle, though, should be quick, and provide some incomplete, though useful, insights about planned encounters. Running a “true” simulation is quite difficult, as there are many aspects to consider such as the various abilities of creatures and players, terrain features, and much more. Trying to plan an entire battle simulation is quite a scaring task. Thus, I rather to take a different approach, where I start with an extremely naive battle simulation, and gradually add elements to it to make it more relevent. My plans for the next (quite few) posts are to start with such naive simulator, and add aspects to it such as healing, distances, and several possible actions. I don’t anticipate I’ll have a complete simulator at the end of this post series, but I hope to have something useful, as well as learn about various aspects of the game mechanics.

The naive model.

The simplest, relevant case I could think of is of two men bashing on each other’s head with a club (or hacking with an ax, it doesn’t really matter). The bear necessities to achieve that, in game mechanics terms, are the AC, HP, and attack damage. The statistics are collected over 10000 simulated battles, in each battle the initiative is rolled separately at its beginning (without any bonus to initiative), and it isn’t change through the battle. Each combatant has one attack per round. Using this model, I can investigate the effect AC, HP, and attack damage have on the chance of survival. Attack bonus are included in the AC definitions thus the AC is actually the effective AC, and are not of significance right now.

The importance of initiative.

To the end of this post, I will consider for two opponents with exactly the same stats; Effective AC 15, HP value of 20, and attack damage of 1d8, it is tempting to say that both combatant have the same chance of killing the other, however, this is not the case. To demonstrate this, I use even a simpler battle system, coin flipping.

Our two men play a game, each of them, on its turn, flips a coin, the first one to have heads wins, what are the odds of each player to win? The first man, has a chance of 1/2 to win on his first flip, and only if he fails, his opponents gets to flip, so he has a chance of 1/4 to win on the first round. For the first man to win on the second round, the second man has to have tails on the first round, which has probability of 1/4, and then have heads in probability of 1/2, multiplying them give us probability of winning which is equal to 1/8, and so forth. Notice that the first player chance of wining at each round is twice as much as the second player, thus, his chance of wining the game is 2/3 and the other man are 1/3. This advantage stems only from the first man going first. Coincidentally, I’ve stumble today on a YouTube Video about a coin toss that explains there is a slight bias in coin toss.

From the simple coin tossing experiment we learn that we should handle the initiative properly, by rolling in each simulation iteration. The effect is not that overwhelming, though, in the example above, the starting combatant has a chance of wining of of about 52%. As the video I’ve linked before explains, this advantage is largely negated by the standard deviation. The chance of wining for both combatants can be calculated exactly, and is shown in the figure below. Changing the AC in this example, enhance the bias to about 60%. As usual, the code will be available at the end of this post.

Chance of winning

Damage distributions.

As the battle progress, the combatant deal damage to each other. In the next figure, I display the damage distribution dealt by a combatant after 5 rounds (red line), 20 rounds (blue line) and 40 rounds (yellow line) assuming you survive that far, that is, without considering a chance of loosing the battle. This chances are still an exact calculation and not the result of simulated battle. The odd shape of the 5 round line is cause by the relatively high chance of 0 HP damage even after 5 rounds (about 25 %) that also slightly affects the shape of the curve of the 20 steps curve.

Damage distribution

We note that as the battle progress, the curve shape spreads to the side, as there are more valid damage “options”, and it is lower, to allow for total probability of 1. The inset depicts the chance of dealing enough damage to one combatant.

The significance of this all.

As I’ve noted, this is a crude model, and it hard to predict upon that model how hard an encounter would be based on that. However, there are two things we can learn by this model alone. First, if you plan a many low level creatures encounter, allowing your player to win the initiative (e.g. allow for surprise round) will give them an extra advantage. The increased influence of going first in competition of easy task, is commonly observed. For example, if you get the chance to be the first to buy stocks of a new company, the stocks are cheap, and the potential gain is high. If this is a booming company, this task will quickly turn harder and the gain would be much less.

Another thing we can do with this model, is to estimate (by changing parameters in the code) by this model how many attacks a single combatant will require to bring it down. And by that, predict how long the battle will be, or how hard the opponent would be. True, this would be a rough number, but I believe that it is not far by more than 25%. I base this assumption solely on gut feeling, and as I’ll progress with my simulator attempts, I’ll learn more about this.

The code.

I’ve used a simple code for the data displayed here. To calculate the damage and win probabilities, I’ve calculated the damage probabilities of each round, and used it to expand to the next round. From what I understand, there is a closed form expression for the damage of each round, however, even a 1d3 damage prediction yields an expression which is quite hard to gain any intuition to gain insight on, and I believe it is not in fit to the general spirit of this blog. As all the post placed here thus far, the code is developed and run in python, on an opensuse machine, with numpy and pyplot available.

import numpy as np
import matplotlib.pyplot as plt
import operator


#Creating the damage probability list
dlist = {0:{0:1}} #Before we start, the probability of no damage is 1
reslist = {}

i =1

while i < 150:

    dlist[i] = {} #Adding new round entry to the damage list

    for d in range (0,9): #0 is for the case of a miss, otherwise 1d8
        oi = i -1 #Previous round number
        if d == 0:
            p = 0.75 #chance of miss
        else:
            p =0.25/8#chance of damage that is equal to a specific value

        for od in dlist[oi]: #Calculating probabilities
            op = dlist[oi][od] #The probability for a specific damage in previous round
            nd = od + d #a possible total damage value in this round
            #Note that a single value of nd may appear more than once
            nP = p*op #Probability for this damage

            #storing
            if not nd in dlist[i]:
                dlist[i][nd] = 0
            dlist[i][nd] += nP

            if not nd in reslist:
                reslist[nd] = 0
            reslist[nd] += nP

    i += 1


#Calculating the chance of win in each round
r = 0
#probabilities, will be update as we progress
first_pwin = 0
second_pwin = 0
Yfirst = []
Ysecond = []

while r in dlist: #go through the damage list

    #FIRST COMBATANT
    phere = 1- second_pwin  #chance of reaching this round
    pnow = 0 #chance of winning this round
    for d in dlist[r]:
        if d >= 20:
            pnow += dlist[r][d] #if the opponent is killed
    Yfirst.append(pnow*phere)
    first_pwin = pnow*phere

    #SECOND COMBATANT
    phere = 1- first_pwin
    pnow = 0
    for d in dlist[r]:
        if d >= 20:
            pnow += dlist[r][d]
    Ysecond.append(pnow*phere)
    second_pwin = pnow*phere
    r += 1





Yfirst = np.array(Yfirst)
Ysecond = np.array(Ysecond)
Ydelta = Yfirst - Ysecond

#plotting

figWin = plt.figure()
ax = figWin.add_axes([0.1,0.2,0.8,0.7])
ax2 = figWin.add_axes([0.45,0.3,0.4,0.4])
ax.plot(Yfirst,color='red',label='first')
ax.plot(Ysecond,color='blue',label='second')
ax2.plot(Ydelta)

ax2.set_yticks([0,0.015,0.03])
ax2.set_xlim(0,100)

ax.set_xlim(0,100)

ax.set_xlabel('Number of rounds')
ax.set_ylabel(r'$P(win)$')

ax2.set_xlabel('Number of rounds')
ax2.set_ylabel(r'$Delta P$')
ax.legend(loc=2)


figWin.text(0.1,0.05,r'''
Chance of win of the first and second combatant in a battle where both have
AC=16,HP=20, and damage of 1d8. Inset, the differnce between the chances.''')

figWin.savefig('/tmp/winchance.pdf')
figWin.savefig('/tmp/winchance.png')


fig = plt.figure()
ax = fig.add_axes([0.1,0.25,0.8,0.7])
ax2 = fig.add_axes([0.45,0.5,0.4,0.4])


clist = ['red','blue','orange']
ln = 0
for nroll in [5,20,40]:
    X = []
    Y = []
    for x in sorted(dlist[nroll].keys()):
        X.append(x)
        y = dlist[nroll][x]
        if nroll == 1:
            print x,y
        Y.append(y)
        ax.plot(X,Y,color=clist[ln])
    ln += 1

ax.text(9,0.08,'5 rounds',color='red')
ax.text(19,0.042,'20 rounds',color='blue')
ax.text(40,0.032,'40 rounds',color='orange')


X20 = []
Y20 = []

showHalf = True
for r in dlist:
    X20.append(r)
    y = 0
    for d in dlist[r]:
        if d > 20:
            y += dlist[r][d]
    if y >= 0.5 and showHalf:
        showHalf = False
        ax2.plot([r,r],[0,1],color='gray')
    Y20.append(y)
ax2.plot(X20,Y20,color='black')


ax.set_xlabel('Total applied damage')
ax.set_ylabel(r'$P$')
ax.set_xlim(0,100)

ax2.set_xlabel('Number of staps')
ax2.set_ylabel(r'$P(HP geq 20)$')
ax2.set_xlim(0,80)

fig.text(0.1,0.04,
'''Probability of dealing specific damage after fixed number, of rounds (noted
next to each curve). Inset, the probability of dealing more, than 20 hit points
of total damage as a function of the roudn number. The first round at wich $P>0.5$
is equal to 19, and marked by the vetical gray line.''')

fig.savefig('/tmp/damage_dist.pdf')
fig.savefig('/tmp/damage_dist.png')

One thought on “Initiate battle simulation

  1. Pingback: Continue the initiation. | Roleplaying Scripts

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 )

Connecting to %s