This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
dom6:fearbattleenchantments [2024/12/18 14:06] isitaris |
dom6:fearbattleenchantments [2025/04/19 20:50] (current) isitaris |
||
---|---|---|---|
Line 13: | Line 13: | ||
* A frighten effect reduces the morale of the target by 1, to a maximum of 5. If the target is alone in its squad like a commander, it will rout unless it succeeds on a morale check that we will call standard routing morale check (it is failed if the target fails an opposed DRN check of 5 + morale vs 14 + morale malus; ties are successes). This also affects squads if it is small enough, but this will not be modelled for now as it only affects very small squad with numbers below 5. | * A frighten effect reduces the morale of the target by 1, to a maximum of 5. If the target is alone in its squad like a commander, it will rout unless it succeeds on a morale check that we will call standard routing morale check (it is failed if the target fails an opposed DRN check of 5 + morale vs 14 + morale malus; ties are successes). This also affects squads if it is small enough, but this will not be modelled for now as it only affects very small squad with numbers below 5. | ||
* Wailing Winds hits with full fear effects on the condition that the hit unit fails a morale check that we will call moraleNegateMoraleCheck (it is failed if an open-ended 3d6 roll falls strictly below the unit current morale). | * Wailing Winds hits with full fear effects on the condition that the hit unit fails a morale check that we will call moraleNegateMoraleCheck (it is failed if an open-ended 3d6 roll falls strictly below the unit current morale). | ||
- | * A full fear effect reduces the morale of the target by 1, to a maximum of 5 (this was changed recently in a dom 6 patch, it was previously 10). The full fear target will rout unless it succeeds on a morale check that we will call individual morale check (it is failed if the target fails an opposed DRN check of morale vs 6 + morale malus; ties are successes). If the full fear target did not fail the previous check and is alone in its squad like a commander, it will rout unless it succeeds on an standard routing morale check. If the target is in a squad, morale problems are involved (see [[user: | + | * A full fear effect reduces the morale of the target by 1, to a maximum of 5 (this was changed recently in a dom 6 patch, it was previously 10). The full fear target will rout unless it succeeds on a morale check that we will call individual morale check (it is failed if the target fails an opposed DRN check of morale vs 6 + morale malus; ties are successes). If the full fear target did not fail the previous check and is alone in its squad like a commander, it will rout unless it succeeds on an standard routing morale check (as said before, such a check is failed if the target fails an opposed DRN check of 5 + morale vs 14 + morale malus; ties are successes). If the target is in a squad, morale problems are involved (see [[user: |
* A single standard routing morale check can be done every round. A single individual routing morale check can be done every round. | * A single standard routing morale check can be done every round. A single individual routing morale check can be done every round. | ||
* The morale malus decays in the following way: every round, there is a 50% chance that the malus decreases by 1. | * The morale malus decays in the following way: every round, there is a 50% chance that the malus decreases by 1. | ||
Line 19: | Line 19: | ||
+ | [[https:// | ||
+ | [[https:// | ||
+ | {{ : | ||
- | ====== Monte Carlo simulation code ====== | ||
+ | ====== A few figures of interest ====== | ||
- | ``` | + | {{ : |
- | import matplotlib.pyplot as plt | + | |
- | import matplotlib.colors | + | |
- | import random as rand | + | |
- | import numpy as np | + | |
- | import math | + | |
- | import exploding_dice as DRN | + | |
- | from matplotlib.lines import Line2D | + | |
- | import os | + | |
- | import matplotlib.ticker as mtick | + | |
- | def ceildiv(a, b): | + | this should be made a png |
- | return -(a // -b) | + | |
+ | What to show: | ||
- | # to-do list: | + | commanders |
- | # - do fear aura I guess as well, assuming line vs line | + | * commanders |
- | # - add modelling of the loss of the bonus of 4 to morale check (i would need to make sure that routed units count as killed units) | + | |
- | # - add modeeling of the effect of blood rain on squad that go below size 5 | + | |
- | # - try and calculate variance and show it in plots | + | |
- | # - try and add back a simulation | + | |
- | # - make it 50/50 which of wailing winds or blood rain goes first | + | |
- | # - DONE: check whether blood rain can rout commanders by itself; answer: yes | + | |
- | # - DONE: check what happens if morale malus is above unit's morale; answer: looks like negative morale after maluses are taken into account | + | |
+ | units | ||
+ | * density 3, squad size 50(or 100?), vs wWinds at various morales | ||
+ | * same thing with wWinds+bRain | ||
+ | * a few examples for 4 morales of bRain vs bRain+Wwinds vs wwinds, if possible resized to fit in the same size as previous plot | ||
- | # graphics suggestions: | + | |
- | # - DONE: use % instead of proba for easier access by people | + | * same thing for wWinds+bRain, showing that there' |
- | # - DONE: alternate half and full lines | + | |
- | + | ||
- | + | ||
- | # globals effects (values gotten by asking Loggy): | + | |
- | # wailing winds: 3% of battlefield every 320 ticks, full fear effect if fail weird morale check (resists == openended 3d6 < current morale) so max penalty -10 | + | |
- | # blood rain 3% of battlefield every 320 ticks, frighten effect, max penalty -5, won't cause rout by itself for the squad scenario, but will for lone commanders | + | |
- | wailingHitRate = 3 | + | |
- | rainHitRate = 3 | + | |
- | fullFearMalusLimit = 5 # maximum morale malus from wailing winds | + | |
- | frightenMalusLimit = 5 # maximum morale malus from blood rain | + | |
- | + | ||
- | DOM5_wailingHitRate = 5 | + | |
- | + | ||
- | + | ||
- | def moraleDomAverage(squadSize, | + | |
- | totalMorale = 0 | + | |
- | for unitID in range(squadSize): | + | |
- | totalMorale += unitMorale - moraleMalus[unitID] | + | |
- | return ((squadSize // 2) + totalMorale) // squadSize # | + | |
- | + | ||
- | def standardRoutingMoraleCheckFails_commanders(unitMorale, | + | |
- | # note: the value of 5 is in fact the morale bonus 5*squadSize/ | + | |
- | if (5 + unitMorale + DRN.DRN() < 14 + moraleMalus + DRN.DRN()): | + | |
- | return True | + | |
- | else: | + | |
- | return False | + | |
- | + | ||
- | def standardRoutingMoraleCheckFails_fullFear_squad(averageMorale, | + | |
- | # note: the value 4 on the left-hand side is a bonus that becomes 0 if the squad becomes too beaten up (exact conditions in https:// | + | |
- | # loss of this bonus is not modelled here | + | |
- | if (4 + averageMorale + (squadSize // 2 + squadSize*5) // squadSize + DRN.DRN() < 14 + DRN.DRN()): | + | |
- | return True | + | |
- | else: | + | |
- | return False | + | |
- | + | ||
- | # def standardRoutingMoraleCheckFails_fullFear_squad(averageMorale, | + | |
- | # if (4 + unitMorale + 5 + DRN.DRN() < 14 + DRN.DRN()): | + | |
- | # | + | |
- | # | + | |
- | # | + | |
- | + | ||
- | def routingMoraleCheckFails_individualCheck(unitMorale, | + | |
- | if (unitMorale + DRN.DRN() < 6 + moraleMalus + DRN.DRN()): | + | |
- | return True | + | |
- | else: | + | |
- | return False | + | |
- | + | ||
- | def moraleNegateCheckFails(unitMorale, | + | |
- | if (DRN.DRN_triple() < max(unitMorale - moraleMalus, | + | |
- | return False | + | |
- | else: | + | |
- | return True | + | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | def wailingBattleSim_commanders(unitMorale = 10, bloodRainActive = 0, wailingWindsActive = 1, debug = 0): | + | |
- | tickCount = 0 | + | |
- | roundCountCurrent = 0 | + | |
- | roundCountNew = 0 | + | |
- | moraleCheckCount = 0 # one morale check per turn maximum - monitors this | + | |
- | moraleCheckCount_easier = 0 # one morale check per turn maximum - monitors this | + | |
- | unitHasRouted = False | + | |
- | moraleMalus = 0 | + | |
- | while unitHasRouted == False: | + | |
- | tickCount += 320 | + | |
- | roundCountNew = tickCount // 7500 | + | |
- | + | ||
- | # Blood Rain effect | + | |
- | if bloodRainActive == 1: | + | |
- | if rand.randrange(100) < rainHitRate: | + | |
- | if moraleMalus < frightenMalusLimit: | + | |
- | moraleMalus += 1 | + | |
- | if moraleCheckCount == 0 and standardRoutingMoraleCheckFails_commanders(unitMorale, | + | |
- | unitHasRouted = True | + | |
- | moraleCheckCount = 1 | + | |
- | + | ||
- | # Wailing Winds effect | + | |
- | if wailingWindsActive == 1: | + | |
- | if rand.randrange(100) < wailingHitRate: | + | |
- | if moraleNegateCheckFails(unitMorale, | + | |
- | if moraleMalus < fullFearMalusLimit: | + | |
- | moraleMalus += 1 | + | |
- | if moraleCheckCount == 0 and standardRoutingMoraleCheckFails_commanders(unitMorale, | + | |
- | unitHasRouted = True | + | |
- | moraleCheckCount = 1 | + | |
- | if moraleCheckCount_easier == 0 and routingMoraleCheckFails_individualCheck(unitMorale, | + | |
- | unitHasRouted = True | + | |
- | moraleCheckCount_easier = 1 | + | |
- | + | ||
- | # new round, chance | + | |
- | if roundCountNew > roundCountCurrent: | + | |
- | if rand.randrange(100) < 50: # 50% chance of the morale malus being reduced by 1 every round | + | |
- | moraleMalus = max(moraleMalus - 1, 0) | + | |
- | moraleCheckCount = 0 | + | |
- | moraleCheckCount_easier = 0 | + | |
- | if debug == 1: | + | |
- | print(moraleMalus) | + | |
- | roundCountCurrent = tickCount // 7500 | + | |
- | + | ||
- | # end of battle, unit hasn't routed yet (this is dom5 number, gotta check new dom6 end of turn stuff) | + | |
- | if roundCountCurrent > 100: # turn 100: battle enchantents end | + | |
- | unitHasRouted = True | + | |
- | + | ||
- | roundCountCurrent = 130 #debug value | + | |
- | return roundCountCurrent | + | |
- | + | ||
- | def wailingBattleSim_squad(unitMorale = 10, bloodRainActive = 0, wailingWindsActive = 1, squadSize = 1, unitDensity = 1, debug = 0): | + | |
- | tickCount = 0 | + | |
- | roundCountCurrent = 0 | + | |
- | roundCountNew = 0 | + | |
- | unitHasRouted = np.zeros(squadSize, | + | |
- | moraleMalus = np.zeros(squadSize) | + | |
- | moraleCheckCount = 0 # one morale check per unit per turn maximum - monitors this ; this one is shared by whole squad | + | |
- | moraleCheckCount_easier = np.zeros(squadSize) # one morale check per unit per turn maximum - monitors this | + | |
- | unitMoraleProblems = np.zeros(squadSize) # see morale problems in https:// | + | |
- | squadSquareSize = ceildiv(squadSize, | + | |
- | unitRoutRound = np.zeros(squadSize) # turn at which the unit routed | + | |
- | while not np.all(unitHasRouted): | + | |
- | tickCount += 320 | + | |
- | roundCountNew = tickCount // 7500 | + | |
- | + | ||
- | | + | |
- | # Blood Rain effect if active | + | |
- | if bloodRainActive == 1: | + | |
- | if rand.randrange(100) < rainHitRate: | + | |
- | for j in range(unitDensity): | + | |
- | unitID = squadSquare_i*unitDensity + j | + | |
- | if moraleMalus[unitID] < frightenMalusLimit: | + | |
- | moraleMalus[unitID] += 1 | + | |
- | + | ||
- | # Wailing Winds effect | + | |
- | if wailingWindsActive == 1: | + | |
- | if rand.randrange(100) < wailingHitRate: | + | |
- | for j in range(unitDensity): | + | |
- | unitID = squadSquare_i*unitDensity + j | + | |
- | if moraleNegateCheckFails(unitMorale, | + | |
- | if moraleMalus[unitID] < fullFearMalusLimit: | + | |
- | moraleMalus[unitID] += 1 | + | |
- | if moraleCheckCount_easier[unitID] == 0 and unitHasRouted[unitID] == False and routingMoraleCheckFails_individualCheck(unitMorale, | + | |
- | unitHasRouted[unitID] = True | + | |
- | unitRoutRound[unitID] = roundCountNew | + | |
- | if debug == 1: | + | |
- | print(" | + | |
- | + | ||
- | moraleCheckCount_easier[unitID] = 1 | + | |
- | + | ||
- | unitMoraleProblems[: | + | |
- | unitMoraleProblems[unitID] += 1000 | + | |
- | + | ||
- | if np.max(unitMoraleProblems) >= 10000: # or 5 cases depending on surviving number of squad members; issue is those cases look at whether the unit is alive, not routed. So it's hard to check in a sim without fighting; maybe ignore for now | + | |
- | if moraleCheckCount == 0 and unitHasRouted[unitID] == False and standardRoutingMoraleCheckFails_fullFear_squad(moraleDomAverage(squadSize, | + | |
- | unitHasRouted[: | + | |
- | unitRoutRound[: | + | |
- | if debug == 1: | + | |
- | print(" | + | |
- | moraleCheckCount = 1 | + | |
- | + | ||
- | # new round, chance of morale malus to decay, and wailing winds will be able to try a rout one more time | + | |
- | if roundCountNew > roundCountCurrent: | + | |
- | for unitID in range(squadSize): | + | |
- | if rand.randrange(100) < 50: # 50% chance of the morale malus being reduced by 1 every round | + | |
- | moraleMalus[unitID] = max(moraleMalus[unitID] - 1, 0) | + | |
- | moraleCheckCount = 0 | + | |
- | moraleCheckCount_easier[: | + | |
- | unitMoraleProblems[: | + | |
- | if debug == 1: | + | |
- | print(moraleMalus) | + | |
- | roundCountCurrent = tickCount // 7500 | + | |
- | # end of battle, unit hasn't routed yet (this is dom5 number, gotta check new dom6 end of turn stuff) | + | |
- | if roundCountCurrent > 100: # turn 100: battle enchantents end | + | |
- | roundCountCurrent = 130 #debug value | + | |
- | # unitHasRouted = np.ones(squadSize, | + | |
- | for unitID in range(squadSize): | + | |
- | if unitHasRouted[unitID] == False: | + | |
- | unitHasRouted[unitID] = True | + | |
- | unitRoutRound[unitID] = roundCountCurrent | + | |
- | if debug == 1: | + | |
- | print(" | + | |
- | return unitRoutRound | + | |
- | + | ||
- | def FearMoraleMalusSim(unitMorale = 10, bloodRainActive = 0, wailingWindsActive = 1, debug = 0): | + | |
- | battleHasEnded = False | + | |
- | tickCount = 0 | + | |
- | roundCountCurrent = 0 | + | |
- | roundCountNew = 0 | + | |
- | moraleMalus = 0 | + | |
- | + | ||
- | nRounds = 100 | + | |
- | moraleMalusArray = np.zeros(nRounds) | + | |
- | moraleMalusArray[0] = 0 | + | |
- | while not battleHasEnded: | + | |
- | tickCount += 320 | + | |
- | roundCountNew = tickCount // 7500 | + | |
- | + | ||
- | # Blood Rain effect if active | + | |
- | if bloodRainActive == 1: | + | |
- | if rand.randrange(100) < rainHitRate: | + | |
- | if moraleMalus < frightenMalusLimit: | + | |
- | moraleMalus += 1 | + | |
- | + | ||
- | # Wailing Winds effect | + | |
- | if wailingWindsActive == 1: | + | |
- | if rand.randrange(100) < wailingHitRate: | + | |
- | if moraleNegateCheckFails(unitMorale, | + | |
- | if moraleMalus < fullFearMalusLimit: | + | |
- | moraleMalus += 1 | + | |
- | + | ||
- | # new round, chance of morale malus to decay, and wailing winds will be able to try a rout one more time | + | |
- | if roundCountNew > roundCountCurrent: | + | |
- | moraleMalusArray[roundCountNew] = moraleMalus | + | |
- | if rand.randrange(100) < 50: # 50% chance of the morale malus being reduced by 1 every round | + | |
- | moraleMalus = max(moraleMalus - 1, 0) | + | |
- | if debug == 1: | + | |
- | print(moraleMalus) | + | |
- | roundCountCurrent = tickCount // 7500 | + | |
- | # end of battle, unit hasn't routed yet (this is dom5 number, gotta check new dom6 end of turn stuff) | + | |
- | if roundCountCurrent > 98: # turn 100: battle enchantents end | + | |
- | roundCountCurrent = 130 #debug value | + | |
- | battleHasEnded = True | + | |
- | if debug == 1: | + | |
- | print(" | + | |
- | return moraleMalusArray | + | |
- | + | ||
- | # FearMoraleMalusSim(10, | + | |
- | + | ||
- | + | ||
- | # did I actually finish those DOM5 versions? to be checked | + | |
- | def DOM5_wailingBattleSim_commanders(unitMorale = 10, bloodRainActive = 0, debug = 0): | + | |
- | tickCount = 0 | + | |
- | roundCountCurrent = 0 | + | |
- | roundCountNew = 0 | + | |
- | moraleCheckCount = 0 # one morale check per turn maximum - monitors this | + | |
- | moraleCheckCount_easier = 0 # one morale check per turn maximum - monitors this | + | |
- | unitHasRouted = False | + | |
- | moraleMalus = 0 | + | |
- | while unitHasRouted == False: | + | |
- | tickCount += 320 | + | |
- | roundCountNew = tickCount // 7500 | + | |
- | + | ||
- | # Blood Rain effect if active | + | |
- | # if wailingWindsActive == 1: for DOM5 if wailing winds isn't active there is no rout check, so there would be no need for a simulation | + | |
- | if rand.randrange(100) < DOM5_wailingHitRate: | + | |
- | if moraleMalus < frightenMalusLimit: | + | |
- | moraleMalus += 1 | + | |
- | if moraleCheckCount == 0 and standardRoutingMoraleCheckFails_commanders(unitMorale, | + | |
- | unitHasRouted = True | + | |
- | moraleCheckCount = 1 | + | |
- | + | ||
- | # new round, chance of morale malus to decay, and wailing winds will be able to try a rout one more time | + | |
- | if roundCountNew > roundCountCurrent: | + | |
- | moraleMalus = moraleMalus // 2 | + | |
- | moraleCheckCount = 0 | + | |
- | if debug == 1: | + | |
- | print(moraleMalus) | + | |
- | roundCountCurrent = tickCount // 7500 | + | |
- | + | ||
- | # end of battle, unit hasn't routed yet (this is dom5 number, gotta check new dom6 end of turn stuff) | + | |
- | if roundCountCurrent > 100: # turn 100: battle enchantents end | + | |
- | unitHasRouted = True | + | |
- | + | ||
- | roundCountCurrent = 130 #debug value | + | |
- | return roundCountCurrent | + | |
- | + | ||
- | # did I actually finish those DOM5 versions? to be checked | + | |
- | def DOM5_wailingBattleSim_squad(unitMorale = 10, bloodRainActive = 0, squadSize = 1, unitDensity = 1, debug = 0): | + | |
- | tickCount = 0 | + | |
- | roundCountCurrent = 0 | + | |
- | roundCountNew = 0 | + | |
- | unitHasRouted = np.zeros(squadSize, | + | |
- | moraleMalus = np.zeros(squadSize) | + | |
- | moraleCheckCount = 0 # one morale check per unit per turn maximum - monitors this ; this one is shared by whole squad | + | |
- | moraleCheckCount_easier = np.zeros(squadSize) # one morale check per unit per turn maximum - monitors this | + | |
- | unitMoraleProblems = np.zeros(squadSize) # see morale problems in https:// | + | |
- | squadSquareSize = ceildiv(squadSize, | + | |
- | unitRoutRound = np.zeros(squadSize) # turn at which the unit routed | + | |
- | while not np.all(unitHasRouted): | + | |
- | tickCount += 320 | + | |
- | roundCountNew = tickCount // 7500 | + | |
- | + | ||
- | for squadSquare_i in range(squadSquareSize): | + | |
- | # Wailing Winds effect | + | |
- | # if wailingWindsActive == 1: for DOM5 if wailing winds isn't active there is no rout check, so there would be no need for a simulation | + | |
- | if rand.randrange(100) < DOM5_wailingHitRate: | + | |
- | for j in range(unitDensity): | + | |
- | unitID = squadSquare_i*unitDensity + j | + | |
- | # if moraleNegateCheckFails(unitMorale, | + | |
- | if moraleMalus[unitID] < frightenMalusLimit: | + | |
- | moraleMalus[unitID] += 1 | + | |
- | + | ||
- | # in DOM5 there is no individual rout | + | |
- | # if moraleCheckCount_easier[unitID] == 0 and unitHasRouted[unitID] == False and routingMoraleCheckFails_individualCheck(unitMorale, | + | |
- | # | + | |
- | # | + | |
- | # if debug == 1: | + | |
- | # | + | |
- | + | ||
- | moraleCheckCount_easier[unitID] = 1 | + | |
- | + | ||
- | unitMoraleProblems[: | + | |
- | unitMoraleProblems[unitID] += 1000 | + | |
- | + | ||
- | if np.max(unitMoraleProblems) >= 10000: # or 5 cases depending on surviving number of squad members; issue is those cases look at whether the unit is alive, not routed. So it's hard to check in a sim without fighting; maybe ignore for now | + | |
- | if moraleCheckCount == 0 and unitHasRouted[unitID] == False and standardRoutingMoraleCheckFails_fullFear_squad(moraleDomAverage(squadSize, | + | |
- | unitHasRouted[: | + | |
- | unitRoutRound[: | + | |
- | if debug == 1: | + | |
- | print(" | + | |
- | moraleCheckCount = 1 | + | |
- | + | ||
- | # new round, chance of morale malus to decay, and wailing winds will be able to try a rout one more time | + | |
- | if roundCountNew > roundCountCurrent: | + | |
- | for unitID in range(squadSize): | + | |
- | moraleMalus[unitID] = moraleMalus[unitID] // 2 | + | |
- | moraleCheckCount = 0 | + | |
- | moraleCheckCount_easier[: | + | |
- | unitMoraleProblems[: | + | |
- | if debug == 1: | + | |
- | print(moraleMalus) | + | |
- | roundCountCurrent = tickCount // 7500 | + | |
- | # end of battle, unit hasn't routed yet (this is dom5 number, gotta check new dom6 end of turn stuff) | + | |
- | if roundCountCurrent > 100: # turn 100: battle enchantents end | + | |
- | roundCountCurrent = 130 #debug value | + | |
- | # unitHasRouted = np.ones(squadSize, | + | |
- | for unitID in range(squadSize): | + | |
- | if unitHasRouted[unitID] == False: | + | |
- | unitHasRouted[unitID] = True | + | |
- | unitRoutRound[unitID] = roundCountCurrent | + | |
- | if debug == 1: | + | |
- | print(" | + | |
- | return unitRoutRound | + | |
- | + | ||
- | + | ||
- | + | ||
- | def wailingExpectedRout_commanders(unitMorale = 10, bloodRainActive = 0, wailingWindsActive = 1, nIteration = 1000): | + | |
- | roundExpectedRout = 0 | + | |
- | for i in range(nIteration): | + | |
- | if (i % 5000 == 0): | + | |
- | print(" | + | |
- | routRound = wailingBattleSim_commanders(unitMorale, | + | |
- | roundExpectedRout+=routRound | + | |
- | return 1./ | + | |
- | + | ||
- | def makeWailingRoutDistrib_commanders(unitMorale = 10, bloodRainActive = 0, wailingWindsActive = 1, nIteration = 1000, isDOM5 = 0): | + | |
- | routingTime = [] | + | |
- | for i in range(nIteration): | + | |
- | if (i % 5000 == 0): | + | |
- | print(" | + | |
- | if (isDOM5 == 0): | + | |
- | routingTime = routingTime + [wailingBattleSim_commanders(unitMorale, | + | |
- | else: | + | |
- | routingTime = routingTime + [DOM5_wailingBattleSim_commanders(unitMorale, | + | |
- | routingTime = np.array(routingTime) | + | |
- | return routingTime | + | |
- | + | ||
- | def makeWailingRoutDistrib_squad(unitMorale = 10, bloodRainActive = 0, wailingWindsActive = 1, squadSize = 1, unitDensity = 1, nIteration = 1000, isDOM5 = 0): | + | |
- | nRounds = 100 | + | |
- | routedRatio = np.zeros(nRounds) | + | |
- | for i in range(nIteration): | + | |
- | if (i % 5000 == 0): | + | |
- | print(" | + | |
- | if (isDOM5 == 0): | + | |
- | unitRoutRound = np.array(wailingBattleSim_squad(unitMorale, | + | |
- | else: | + | |
- | unitRoutRound = np.array(DOM5_wailingBattleSim_squad(unitMorale, | + | |
- | for round in range(nRounds): | + | |
- | routedRatio[round] += (unitRoutRound <= round).sum() * 1./ | + | |
- | routedRatio = routedRatio * 1./ | + | |
- | return routedRatio | + | |
- | + | ||
- | def makeFearMoraleMalusAverageDistrib(unitMorale = 10, bloodRainActive = 0, wailingWindsActive = 1, nIteration = 1000): | + | |
- | nRounds = 100 | + | |
- | moraleMalusAverage = np.zeros(nRounds) | + | |
- | for i in range(nIteration): | + | |
- | if (i % 5000 == 0): | + | |
- | print(" | + | |
- | moraleMalusAverage += np.array(FearMoraleMalusSim(unitMorale, | + | |
- | moraleMalusAverage = moraleMalusAverage * 1./ | + | |
- | return moraleMalusAverage | + | |
- | # makeFearMoraleMalusAverageDistrib(10, | + | |
- | + | ||
- | def plotHistograms_commanders(nDistribs, | + | |
- | + | ||
- | nBins = 140 | + | |
- | fig, ax = plt.subplots() | + | |
- | color1 = ' | + | |
- | ax.set_xlabel(' | + | |
- | ax.set_ylabel(' | + | |
- | + | ||
- | if option == " | + | |
- | colourMap = plt.get_cmap(" | + | |
- | else: | + | |
- | colourMap = plt.get_cmap(" | + | |
- | + | ||
- | for i in range(nDistribs): | + | |
- | if option == " | + | |
- | ax.hist(routingTimeDistrib_collection[i], | + | |
- | else: | + | |
- | ax.hist(routingTimeDistrib_collection[i], | + | |
- | + | ||
- | + | ||
- | # Create new legend handles but use the colors from the existing ones | + | |
- | handles, labels = ax.get_legend_handles_labels() | + | |
- | if len(handles) != nDistribs: | + | |
- | raise ValueError(' | + | |
- | if option == " | + | |
- | new_handles = [Line2D([], [], c=handles[i].get_edgecolor(), | + | |
- | else: | + | |
- | new_handles = [Line2D([], [], c=handles[i].get_edgecolor()) for i in range(nDistribs)] | + | |
- | + | ||
- | # plt.legend(handles=new_handles, | + | |
- | + | ||
- | ax.legend(handles=new_handles, | + | |
- | ax.label_outer() | + | |
- | + | ||
- | plt.xlim(xmin=0, | + | |
- | plt.ylim(ymin=0, | + | |
- | plt.gca().yaxis.set_major_formatter(mtick.PercentFormatter(xmax=1.0)) # transforms y-axis in % format | + | |
- | + | ||
- | plt.title(pltTitle) | + | |
- | + | ||
- | fig.tight_layout() # otherwise the right y-label is slightly clipped | + | |
- | fig.tight_layout() # for some reason gotta call it twice for the top title to not be cropped | + | |
- | + | ||
- | + | ||
- | # x-axis: major ticks every 20, minor ticks every 5 | + | |
- | major_ticks_x = np.arange(0, | + | |
- | minor_ticks_x = np.arange(0, | + | |
- | # y-axis: major ticks every 0.2, minor ticks every 0.05 | + | |
- | major_ticks_y = np.arange(0, | + | |
- | minor_ticks_y = np.arange(0, | + | |
- | + | ||
- | ax.set_xticks(major_ticks_x) | + | |
- | ax.set_xticks(minor_ticks_x, | + | |
- | ax.set_yticks(major_ticks_y) | + | |
- | ax.set_yticks(minor_ticks_y, | + | |
- | + | ||
- | ax.grid(which=' | + | |
- | + | ||
- | ax.grid(which=' | + | |
- | ax.grid(which=' | + | |
- | + | ||
- | # save figure | + | |
- | script_dir = os.path.dirname(__file__) | + | |
- | results_dir = os.path.join(script_dir, | + | |
- | + | ||
- | if not os.path.isdir(results_dir): | + | |
- | os.makedirs(results_dir) | + | |
- | + | ||
- | if option == " | + | |
- | plt.show() | + | |
- | plt.savefig(results_dir+pdfName+' | + | |
- | + | ||
- | def plotHistograms_squad(nDistribs, | + | |
- | + | ||
- | fig, ax = plt.subplots() | + | |
- | color1 = ' | + | |
- | ax.set_xlabel(' | + | |
- | ax.set_ylabel(' | + | |
- | + | ||
- | if option == " | + | |
- | colourMap = plt.get_cmap(" | + | |
- | else: | + | |
- | colourMap = plt.get_cmap(" | + | |
- | + | ||
- | nRounds = 100 # x-axis bin size | + | |
- | for i in range(nDistribs): | + | |
- | if option == " | + | |
- | ax.plot(np.array(range(nRounds)), | + | |
- | else: | + | |
- | ax.plot(np.array(range(nRounds)), | + | |
- | + | ||
- | # Create new legend handles but use the colors from the existing ones | + | |
- | handles, labels = ax.get_legend_handles_labels() | + | |
- | if len(handles) != nDistribs: | + | |
- | raise ValueError(' | + | |
- | if option == " | + | |
- | new_handles = [Line2D([], [], c=handles[i].get_color(), | + | |
- | else: | + | |
- | new_handles = [Line2D([], [], c=handles[i].get_color()) for i in range(nDistribs)] | + | |
- | + | ||
- | ax.legend(handles=new_handles, | + | |
- | ax.label_outer() | + | |
- | + | ||
- | plt.xlim(xmin=0, | + | |
- | plt.ylim(ymin=0, | + | |
- | plt.gca().yaxis.set_major_formatter(mtick.PercentFormatter(xmax=1.0)) # transforms y-axis in % format | + | |
- | + | ||
- | plt.title(pltTitle) | + | |
- | + | ||
- | fig.tight_layout() # otherwise the right y-label is slightly clipped | + | |
- | fig.tight_layout() # for some reason gotta call it twice for the top title to not be cropped | + | |
- | + | ||
- | + | ||
- | # setting up the grid and the ticks on the axes: | + | |
- | + | ||
- | # x-axis: major ticks every 20, minor ticks every 5 | + | |
- | major_ticks_x = np.arange(0, | + | |
- | minor_ticks_x = np.arange(0, | + | |
- | # y-axis: major ticks every 0.2, minor ticks every 0.05 | + | |
- | major_ticks_y = np.arange(0, | + | |
- | minor_ticks_y = np.arange(0, | + | |
- | + | ||
- | ax.set_xticks(major_ticks_x) | + | |
- | ax.set_xticks(minor_ticks_x, | + | |
- | ax.set_yticks(major_ticks_y) | + | |
- | ax.set_yticks(minor_ticks_y, | + | |
- | + | ||
- | ax.grid(which=' | + | |
- | + | ||
- | ax.grid(which=' | + | |
- | ax.grid(which=' | + | |
- | + | ||
- | # save figure in ./ | + | |
- | script_dir = os.path.dirname(__file__) | + | |
- | results_dir = os.path.join(script_dir, | + | |
- | + | ||
- | if not os.path.isdir(results_dir): | + | |
- | os.makedirs(results_dir) | + | |
- | + | ||
- | if option == " | + | |
- | plt.show() | + | |
- | plt.savefig(results_dir+pdfName+' | + | |
- | + | ||
- | def plotHistograms_fearMoraleMalus(nDistribs, | + | |
- | + | ||
- | nBins = 140 | + | |
- | fig, ax = plt.subplots() | + | |
- | color1 = ' | + | |
- | ax.set_xlabel(' | + | |
- | ax.set_ylabel(' | + | |
- | + | ||
- | if option == " | + | |
- | colourMap = plt.get_cmap(" | + | |
- | else: | + | |
- | colourMap = plt.get_cmap(" | + | |
- | + | ||
- | xRounds = np.arange(100) | + | |
- | for i in range(nDistribs): | + | |
- | if option == " | + | |
- | # ax.hist(fearMoraleMalusDistrib_collection[i], | + | |
- | ax.plot(xRounds, | + | |
- | else: | + | |
- | # ax.hist(fearMoraleMalusDistrib_collection[i], | + | |
- | ax.plot(xRounds, | + | |
- | + | ||
- | # Create new legend handles but use the colors from the existing ones | + | |
- | handles, labels = ax.get_legend_handles_labels() | + | |
- | if len(handles) != nDistribs: | + | |
- | raise ValueError(' | + | |
- | if option == " | + | |
- | new_handles = [Line2D([], [], c=handles[i].get_color(), | + | |
- | else: | + | |
- | new_handles = [Line2D([], [], c=handles[i].get_color()) for i in range(nDistribs)] | + | |
- | + | ||
- | # plt.legend(handles=new_handles, | + | |
- | + | ||
- | ax.legend(handles=new_handles, | + | |
- | ax.label_outer() | + | |
- | + | ||
- | plt.xlim(xmin=0, | + | |
- | plt.ylim(ymin=0, | + | |
- | # plt.gca().yaxis.set_major_formatter(mtick.PercentFormatter(xmax=1.0)) # transforms y-axis in % format | + | |
- | + | ||
- | plt.title(pltTitle) | + | |
- | + | ||
- | fig.tight_layout() # otherwise the right y-label is slightly clipped | + | |
- | fig.tight_layout() # for some reason gotta call it twice for the top title to not be cropped | + | |
- | + | ||
- | + | ||
- | # x-axis: major ticks every 20, minor ticks every 5 | + | |
- | major_ticks_x = np.arange(0, | + | |
- | minor_ticks_x = np.arange(0, | + | |
- | # # y-axis: major ticks every 0.2, minor ticks every 0.05 | + | |
- | # major_ticks_y = np.arange(0, | + | |
- | # minor_ticks_y = np.arange(0, | + | |
- | # y-axis: major ticks every 0.2, minor ticks every 0.05 | + | |
- | major_ticks_y = np.arange(0, | + | |
- | minor_ticks_y = np.arange(0, | + | |
- | + | ||
- | ax.set_xticks(major_ticks_x) | + | |
- | ax.set_xticks(minor_ticks_x, | + | |
- | ax.set_yticks(major_ticks_y) | + | |
- | ax.set_yticks(minor_ticks_y, | + | |
- | + | ||
- | ax.grid(which=' | + | |
- | + | ||
- | ax.grid(which=' | + | |
- | ax.grid(which=' | + | |
- | + | ||
- | # save figure in ./ | + | |
- | script_dir = os.path.dirname(__file__) | + | |
- | results_dir = os.path.join(script_dir, | + | |
- | + | ||
- | if not os.path.isdir(results_dir): | + | |
- | os.makedirs(results_dir) | + | |
- | + | ||
- | if option == " | + | |
- | plt.show() | + | |
- | plt.savefig(results_dir+pdfName+' | + | |
- | + | ||
- | def plot_varyingMorale_setEnchantments_commandersRout(): | + | |
- | unitMorale_collection = range(10, | + | |
- | nIteration=100000 | + | |
- | + | ||
- | nDistribs = len(unitMorale_collection) | + | |
- | + | ||
- | | + | |
- | for wailingWindsActive in [0,1]: | + | |
- | if (bloodRainActive, | + | |
- | print(" | + | |
- | bloodRain = "" | + | |
- | if bloodRainActive == 1: | + | |
- | bloodRain = " | + | |
- | if bloodRainActive == 0: | + | |
- | bloodRain = " | + | |
- | wailingWinds = "" | + | |
- | if wailingWindsActive == 1: | + | |
- | wailingWinds = " | + | |
- | if wailingWindsActive == 0: | + | |
- | wailingWinds = " | + | |
- | + | ||
- | routingTimeDistrib_collection = [] | + | |
- | collectionLegend = [] | + | |
- | for unitMorale in unitMorale_collection: | + | |
- | print(" | + | |
- | routingTimeDistrib_collection += [makeWailingRoutDistrib_commanders(unitMorale, | + | |
- | collectionLegend += [" | + | |
- | + | ||
- | wailingStatus = " | + | |
- | rainStatus = " | + | |
- | + | ||
- | pltTitle = " | + | |
- | pdfName = " | + | |
- | + | ||
- | plotHistograms_commanders(nDistribs, | + | |
- | + | ||
- | def plot_setMorale_varyingEnchantments_commandersRout(): | + | |
- | unitMorale_collection = range(10, | + | |
- | # unitMorale_collection = [15] | + | |
- | nIteration=100000 | + | |
- | + | ||
- | for unitMorale in unitMorale_collection: | + | |
- | print(" | + | |
- | # for wailingWindsActive in [0,1]: | + | |
- | # if (bloodRainActive, | + | |
- | + | ||
- | nDistribs = 3 # wailing, | + | |
- | routingTimeDistrib_collection = [] | + | |
- | collectionLegend = [] | + | |
- | + | ||
- | for bloodRainActive in [0,1]: | + | |
- | for wailingWindsActive in [0,1]: | + | |
- | if (bloodRainActive, | + | |
- | print(" | + | |
- | routingTimeDistrib_collection += [makeWailingRoutDistrib_commanders(unitMorale, | + | |
- | wailingStatus = " | + | |
- | rainStatus = " | + | |
- | collectionLegend += [" | + | |
- | + | ||
- | + | ||
- | pltTitle = " | + | |
- | pdfName = " | + | |
- | + | ||
- | plotHistograms_commanders(nDistribs, | + | |
- | + | ||
- | def plot_setMorale_varyingEnchantments_squadRout(): | + | |
- | unitMorale_collection = range(10, | + | |
- | # unitMorale_collection = range(16, | + | |
- | nIteration=100000 | + | |
- | + | ||
- | squadSize = 10 | + | |
- | unitDensity = 2 | + | |
- | + | ||
- | for unitMorale in unitMorale_collection: | + | |
- | print(" | + | |
- | + | ||
- | nDistribs = 2 # wailing, | + | |
- | routedRatioDistrib_collection = [] | + | |
- | collectionLegend = [] | + | |
- | + | ||
- | for bloodRainActive in [0,1]: | + | |
- | for wailingWindsActive in [0,1]: | + | |
- | if (bloodRainActive, | + | |
- | print(" | + | |
- | routedRatioDistrib_collection += [makeWailingRoutDistrib_squad(unitMorale, | + | |
- | wailingStatus = " | + | |
- | rainStatus = " | + | |
- | collectionLegend += [" | + | |
- | + | ||
- | + | ||
- | pltTitle = "squad morale " + str(unitMorale) | + | |
- | pdfName = " | + | |
- | + | ||
- | plotHistograms_squad(nDistribs, | + | |
- | + | ||
- | def plot_varyingMorale_setEnchantments_squadRout(): | + | |
- | unitMorale_collection = range(10, | + | |
- | nIteration=100000 | + | |
- | + | ||
- | squadSize = 10 | + | |
- | unitDensity = 2 | + | |
- | + | ||
- | nDistribs = len(unitMorale_collection) | + | |
- | + | ||
- | for bloodRainActive in [0,1]: | + | |
- | for wailingWindsActive in [0,1]: | + | |
- | if (bloodRainActive, | + | |
- | print(" | + | |
- | bloodRain = "" | + | |
- | if bloodRainActive == 1: | + | |
- | bloodRain = " | + | |
- | if bloodRainActive == 0: | + | |
- | bloodRain = " | + | |
- | wailingWinds = "" | + | |
- | if wailingWindsActive == 1: | + | |
- | wailingWinds = " | + | |
- | if wailingWindsActive == 0: | + | |
- | wailingWinds = " | + | |
- | + | ||
- | routedRatioDistrib_collection = [] | + | |
- | collectionLegend = [] | + | |
- | + | ||
- | for unitMorale in unitMorale_collection: | + | |
- | print(" | + | |
- | routedRatioDistrib_collection += [makeWailingRoutDistrib_squad(unitMorale, | + | |
- | collectionLegend += [" | + | |
- | + | ||
- | wailingStatus = " | + | |
- | rainStatus = " | + | |
- | + | ||
- | pltTitle = "squad - BloodRain" | + | |
- | pdfName = " | + | |
- | + | ||
- | plotHistograms_squad(nDistribs, | + | |
- | + | ||
- | def plot_varyingMorale_setEnchantments_moraleMalus(): | + | |
- | unitMorale_collection = range(10, | + | |
- | nIteration=10000 | + | |
- | + | ||
- | nDistribs = len(unitMorale_collection) | + | |
- | + | ||
- | for bloodRainActive in [0,1]: | + | |
- | for wailingWindsActive in [0,1]: | + | |
- | if (bloodRainActive, | + | |
- | print(" | + | |
- | bloodRain = "" | + | |
- | if bloodRainActive == 1: | + | |
- | bloodRain = " | + | |
- | if bloodRainActive == 0: | + | |
- | bloodRain = " | + | |
- | wailingWinds = "" | + | |
- | if wailingWindsActive == 1: | + | |
- | wailingWinds = " | + | |
- | if wailingWindsActive == 0: | + | |
- | wailingWinds = " | + | |
- | + | ||
- | fearMoraleMalusDistrib_collection = [] | + | |
- | collectionLegend = [] | + | |
- | for unitMorale in unitMorale_collection: | + | |
- | print(" | + | |
- | fearMoraleMalusDistrib_collection += [makeFearMoraleMalusAverageDistrib(unitMorale, | + | |
- | collectionLegend += [" | + | |
- | + | ||
- | wailingStatus = " | + | |
- | rainStatus = " | + | |
- | + | ||
- | pltTitle = " | + | |
- | pdfName = " | + | |
- | + | ||
- | plotHistograms_fearMoraleMalus(nDistribs, | + | |
- | + | ||
- | def plot_setMorale_varyingEnchantments_moraleMalus(): | + | |
- | unitMorale_collection = range(10, | + | |
- | # unitMorale_collection = [15] | + | |
- | nIteration=10000 | + | |
- | + | ||
- | for unitMorale in unitMorale_collection: | + | |
- | print(" | + | |
- | # for wailingWindsActive in [0,1]: | + | |
- | # if (bloodRainActive, | + | |
- | + | ||
- | nDistribs = 3 # wailing, | + | |
- | fearMoraleMalusDistrib_collection = [] | + | |
- | collectionLegend = [] | + | |
- | + | ||
- | for bloodRainActive in [0,1]: | + | |
- | for wailingWindsActive in [0,1]: | + | |
- | if (bloodRainActive, | + | |
- | print(" | + | |
- | fearMoraleMalusDistrib_collection += [makeFearMoraleMalusAverageDistrib(unitMorale, | + | |
- | wailingStatus = " | + | |
- | rainStatus = " | + | |
- | collectionLegend += [" | + | |
- | + | ||
- | + | ||
- | pltTitle = " | + | |
- | pdfName = " | + | |
- | + | ||
- | plotHistograms_fearMoraleMalus(nDistribs, | + | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | def plot_all_commanders(): | + | |
- | plot_varyingMorale_setEnchantments_commandersRout() | + | |
- | plot_setMorale_varyingEnchantments_commandersRout() | + | |
- | + | ||
- | + | ||
- | + | ||
- | def plot_test(): | + | |
- | unitMorale_collection = [10, | + | |
- | nIteration=100000 | + | |
- | + | ||
- | # # commanders | + | |
- | # for unitMorale in unitMorale_collection: | + | |
- | # # for wailingWindsActive in [0,1]: | + | |
- | # # | + | |
- | + | ||
- | # | + | |
- | # | + | |
- | # | + | |
- | + | ||
- | # for bloodRainActive in [0,1]: | + | |
- | # for wailingWindsActive in [0,1]: | + | |
- | # if (bloodRainActive, | + | |
- | # | + | |
- | # | + | |
- | # | + | |
- | # | + | |
- | + | ||
- | + | ||
- | # | + | |
- | # | + | |
- | + | ||
- | # | + | |
- | + | ||
- | + | ||
- | # squad | + | |
- | for unitMorale in unitMorale_collection: | + | |
- | print(" | + | |
- | # for wailingWindsActive in [0,1]: | + | |
- | # if (bloodRainActive, | + | |
- | + | ||
- | nDistribs = 2 # wailing, | + | |
- | routedRatioDistrib_collection = [] | + | |
- | collectionLegend = [] | + | |
- | squadSize = 10 | + | |
- | unitDensity = 2 | + | |
- | + | ||
- | for bloodRainActive in [0,1]: | + | |
- | for wailingWindsActive in [0,1]: | + | |
- | if (bloodRainActive, | + | |
- | print(" | + | |
- | routedRatioDistrib_collection += [makeWailingRoutDistrib_squad(unitMorale, | + | |
- | wailingStatus = " | + | |
- | rainStatus = " | + | |
- | collectionLegend += [" | + | |
- | + | ||
- | + | ||
- | pltTitle = "squad morale " + str(unitMorale) | + | |
- | pdfName = " | + | |
- | + | ||
- | plotHistograms_squad(nDistribs, | + | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | # for units: | + | |
- | # proba full squad gone | + | |
- | # proba one unit gone | + | |
- | # | + | |
- | + | ||
- | + | ||
- | # legacy error bars attempt | + | |
- | + | ||
- | # SumSquareDev = 0 | + | |
- | # E = wailingExpectedRout(unitMorale, | + | |
- | + | ||
- | # n, bins, patches = plt.hist(RoutingTime, | + | |
- | + | ||
- | # y,binEdges = np.histogram(RoutingTime, | + | |
- | # bincenters = 0.5*(binEdges[1: | + | |
- | # sigma = np.sqrt(y) # wrong error bar, check out https:// | + | |
- | # width = 0.0 | + | |
- | # # plt.bar(bincenters, | + | |
- | # plt.errorbar( | + | |
- | # | + | |
- | # y, | + | |
- | # yerr = y**0.5, | + | |
- | # | + | |
- | # | + | |
- | # | + | |
- | # ) | + | |
- | ``` | + | |
+ | let people check github for density 1 and 2 |