# How do I model an iterated series of combat rounds in Anydice?

Nemesis fights are straightforward:

1. Choose pools of yang/yin,
2. Roll Nd6 vs. Skill,
3. Deal Yang hits to opponent(s),
4. Block dealt hits with Yin or Chi,
5. First to -1 Chi loses,
6. Players win ties.

Pursuant to an earlier question, I am attempting to model this sequence in Anydice. For simplicity, I skip over the yang/yin steps, and model the players as a single large pool of Chi.

Ideally, this Anydice plot shall display the likelihood that players possessing PCHI chi win a fight against a Nemesis possessing NCHI chi.

The recursive "fight" function:

function: fight PCHI:n chi vs NCHI:n chi {
HITS: 5d(1d6<=2)
TAKES: 5d(1d6<=2)
PLAYERWIN: HITS >= TAKES

if PLAYERWIN {
if NCHI = 0 {
result: 1
}
result: [fight PCHI chi vs NCHI-1 chi]
}

if X = 0 {
result: 0
}
result: [fight PCHI-1 chi vs NCHI chi]

}

output [fight 5 chi vs 5 chi]


At present, Anydice produces the following error:

calculation error

Boolean values can only be numbers, but you provided "d{?}".
Depending on what you want, you might need to create a function.


I believe this indicates that PLAYERWIN is a range of rolls. All well and good, but at present I'm not sure how to structure the function such that it recurs with different arguments based on whether players did indeed deal equal or greater hits.

• What happens when a player rolls 2 yang/2 yin and a nemesis rolls 2 yang/2 yin? Is chi depleted on either side, or is that a "harmless" round? (It's unclear what is meant by a "tie". Is that only to resolve the final round if both the player's and nemesis' chi is negative?) Further what happens when a player rolls 2 yang/3 yin and a nemesis rolls 2 yang/2 yin? (Note that the player's blocks exceed the nemesis' hits. Are those extra blocks worth anything, or do they just evaporate?) Commented Jul 8, 2022 at 11:57
• @posita A tie means that both parties hit -1 Chi at the same time. Extra blocks are wasted. Commented Jul 8, 2022 at 12:24
• I'm confused. My understanding in the player's 2 yang/2 yin vs. the nemesis' 2 yang/2 yin scenario was that the player's yin would block the nemesis' yang and vice versa resulting in zero chi loss for either side that round. Is that not the case? Commented Jul 8, 2022 at 15:26
• That is also true! However, the word "tie" in the question refers to a combat tie, not a round tie. Perhaps a "draw" is a better term. Commented Jul 8, 2022 at 16:21

Somewhat inspired by our discussion in @HighDiceRoller's answer, I adapted my former work-up for multi-round combats in Risus using dyce¹ and put together an interactive widget using Jupyter that might help. One limitation is that it only allows configuring pool allocations for the entire combat, not per-round:

That being said, after playing around with it and assuming I understand the mechanic (and got my math and my widgets wired up right), I'm not sure it's ever a good strategy to dedicate any dice to defense. (Others more careful and observant than me are encouraged to chime in on the dubiousness of my conclusion.)

anydyce² is used to to generate "burst" graphs. You can play around with it in your browser: [source]

If you speak Python, the substantive implementation can be found in nemesis.py. Be warned that it uses recursion within nested functions, which probably makes it harder to wrap one's head around. But it's pretty performant compared to AnyDice, mostly because of memoization (i.e., use of Python's @functools.cache decorator).

¹ dyce is my Python dice probability library.

² anydyce is my visualization layer for dyce meant as a rough stand-in for AnyDice.

### AnyDice and control flow

The trouble here is that AnyDice's if statement is deterministic only. You can't have the program take a separate if branch depending on the outcome of a roll (or more precisely, have it take both branches and weight the outcomes according the to the probability of each branch.)

AFAIK AnyDice has exactly one mechanism for non-deterministic control flow, which is providing a die argument to a function parameter of type n or s, in which case the function will be evaluated for all possible sums or sequences respectively. For example, you can use this to write a conditional operator.

Then your code above could be written as something like this:

function: if CONDITION:n then A else B {
if CONDITION { result: A } else { result: B }
}

function: fight PCHI:n chi vs NCHI:n chi {
if PCHI = 0 { result: 0 }
if NCHI = 0 { result: 1 }
HITS: 5d(1d6<=2)
TAKES: 5d(1d6<=2)
PLAYERWIN: HITS >= TAKES

result: [if PLAYERWIN then [fight PCHI chi vs NCHI-1 chi] else [fight PCHI-1 chi vs NCHI chi]]
}

output [fight 5 chi vs 5 chi]


### Problems

However, there are a few problems. First, this code times out for large starting Chi. Second, even with the listed simplifications

For simplicity, I skip over the yang/yin steps, and model the players as a single large pool of Chi.

there are still additional factors not accounted for:

• It's not that the side that won fewer hits loses 1 Chi. Both sides lose Chi equal to the number of hits they take, which could be on one, both, or neither side, and could be multiple Chi lost.
• There are additional tiebreaker rules for when both sides run out of Chi at the same time.

I may try for a more complete script if nobody else gets to it first.

• Good catch on the additional factors; I had forgotten about that simplification. A naive implementation of "subtract the difference in hits" is timing out and/or maxing out the call depth, alas! I am guessing a new implementation would require a new question? Commented Jul 5, 2022 at 12:53
• Note also that "a more complete script" would also help answer my other question. Commented Jul 5, 2022 at 13:23
• This feels really similar to my write-up on Modeling Multi-Round Combats in Risus for dyce (my Python dice-modeling library). (Not an AnyDice solution, though.) Commented Jul 8, 2022 at 2:46
• … that is until I read the source material. I think this will be difficult to model accurately, since it sounds like participants can choose how many dice to allocate to their own yang/yin pools each round. I don't know how much that can affect outcomes. In other words, I don't know how useful/accurate the OP's simplification to Our Chi vs. Their Chi is. Commented Jul 8, 2022 at 3:35
• I think even the strategic case would be computationally feasible: backwards induction for one player; minimax if the Nemesis also plays strategically on their own turn; solving a zero-sum game via linear programming if both the player and the Nemesis go simultaneously. With the recommended low single-digit Chi values the number of subgames will not be too large. Of course, actually writing out such a program is another matter entirely. Commented Jul 8, 2022 at 7:02