예제 #1
0
def test_rv_create_rv():
    # given
    a = value(6)
    b = value('D6')
    c = value(DiceValue(6))
    # assert
    assert a.max({}) == b.max({}) == c.max({})
예제 #2
0
def compute_potential_wounds(context, potential_hits, towound):
    potential_wounds = [{
        **hit, 'wounds':
        value(nb) + context.get(EXTRA_WOUND_ON_CRIT, 0) * nb_crit,
        'crit_wounds':
        nb_crit,
        'mortal_wounds':
        hit['mortal_wounds'] + context.get(MW_ON_WOUND_CRIT, 0) * nb_crit,
        'proba':
        hit['proba'] * probability_of_wound_and_crit(hit['hits'],
                                                     nb,
                                                     nb_crit,
                                                     towound,
                                                     context,
                                                     crit_hit=hit['crit_hits'])
    } for hit in potential_hits for nb in range(hit['hits'] + 1)
                        for nb_crit in range(nb + 1)]
    potential_wounds = [{
        **wnd,
        'wounds': nb,
        'proba': wnd['proba'] * proba,
    } for wnd in potential_wounds
                        for (nb,
                             proba) in wnd['wounds'].potential_values(context)]
    return potential_wounds
예제 #3
0
 def buff(data):
     enemy_wounds = data.get(ENEMY_WOUNDS, 1)
     if enemy_wounds <= 1:
         return 0
     bravery = data.get(ENEMY_BRAVERY, 7)
     roll_higher = sum([proba for val, proba in value('2D6').potential_values(data) if val > bravery])
     return RandomValue({0: 1 - roll_higher, enemy_wounds - 1: roll_higher})
예제 #4
0
 def __init__(self, base_value) -> None:
     if isinstance(base_value, Roll):
         self.base_value = base_value.base_value
     else:
         self.base_value = value(base_value)
     self.rerolled = 0
     self.rules: List[Callable] = [
     ]  # function take dict, return mod and reroll
     self.mod_ignored = []
예제 #5
0
 def buff(data):
     possible_success = roll.success(data)
     possible_damage = {
         val: proba * possible_success
         for val, proba in value(mortal_wounds).potential_values(data)
     }
     possible_damage[0] = possible_damage.get(0,
                                              0) + 1 - possible_success
     data[MORTAL_WOUNDS_PER_ATTACK] = RandomValue(possible_damage)
예제 #6
0
def test_average_apply_extras_on_context():
    # given

    def bonus(context):
        return context.get('bonus', 0)

    random_value = value(1)
    random_value.rules.append(bonus)
    # assert
    assert random_value.average({'bonus': 21}, mod=20) == 42
 def buff(data):
     # roll a dice (-1 if monster). if is equal to or less than the number of minis in the unit, D3 MW
     mod = 0
     if MONSTER in data.get(ENEMY_KEYWORDS, []):
         mod = -1
     possibilities = {
         val: proba
         for val, proba in value('D6').potential_values(data, mod)
     }
     nb_enemies = data.get(ENEMY_NUMBERS, 1)
     possible_success = sum([
         proba for val, proba in possibilities.items() if val <= nb_enemies
     ])
     possible_damage = {
         val: proba * possible_success
         for val, proba in value('D3').potential_values(data)
     }
     possible_damage[0] = 1 - possible_success
     data[MORTAL_WOUNDS_PER_ATTACK] = RandomValue(possible_damage)
예제 #8
0
def compute_potential_hits(context, potential_attacks, tohit):
    potential_hits = [{
        **att, 'hits':
        value(nb) * context.get(NUMBER_OF_HITS, 1) +
        context.get(EXTRA_HIT_ON_CRIT, 0) * nb_crit,
        'crit_hits':
        nb_crit,
        'second_attacks':
        nb * context.get(EXTRA_ATTACK_ON_HIT, 0),
        'proba':
        att['proba'] * probability_of_hit_and_crit(att['attacks'], nb, nb_crit,
                                                   tohit, context)
    } for att in potential_attacks for nb in range(att['attacks'] + 1)
                      for nb_crit in range(nb + 1)]
    potential_hits = [{
        **att, 'hits':
        att['hits'] + value(nb) + context.get(EXTRA_HIT_ON_CRIT, 0) * nb_crit,
        'crit_hits':
        nb_crit + att['crit_hits'],
        'proba':
        att['proba'] * probability_of_hit_and_crit(att['second_attacks'], nb,
                                                   nb_crit, tohit, context)
    } for att in potential_hits for nb in range(att['second_attacks'] + 1)
                      for nb_crit in range(nb + 1)]
    potential_hits = [{
        **hit,
        'hits':
        nb,
        'mortal_wounds':
        hit['mortal_wounds'] +
        context.get(MW_ON_HIT_CRIT, 0) * hit['crit_hits'],
        'proba':
        hit['proba'] * proba,
    } for hit in potential_hits
                      for (nb, proba) in hit['hits'].potential_values(context)]
    if context.get(STOP_ON_CRIT_HIT, False):
        potential_hits = [{
            **hit,
            'hits': hit['hits'] - hit['crit_hits'],
            'crit_hits': 0,
        } for hit in potential_hits]
    return potential_hits
예제 #9
0
    def __init__(
            self,
            name: str,
            weapons: Union[List[Weapon]],
            move: Union[int, str, Value, Dict[int, Union[int, str, Value]]],
            save: int,
            bravery: int,
            wounds: int,
            min_size: int,
            base: Base,
            rules: List[Rule],
            keywords: List[str],
            cast=0,
            unbind=0,
            named=False,
            max_size=None,
    ):
        self.name = name
        self.weapons = weapons
        self.move = value(move)
        self.save = Roll(save)
        self.extra_save = Roll(7)
        self.bravery = bravery
        self.wounds = wounds
        self.min_size = min_size
        self.size = min_size
        self.max_size = max_size
        if self.max_size is None:
            self.max_size = min_size if 'MONSTER' in keywords or 'HERO' in keywords else min_size
        self.base = base
        self.keywords = keywords
        keywords.append(self.name.upper())
        self.named = named
        self.can_fly = False
        self.run_distance = value('D6')
        self.charge_range = value('2D6')
        self.can_run_and_charge = False
        self.special_users = []
        self.casting_value = value('2D6')
        self.unbinding_value = value('2D6')
        self.morale_roll = value('D6')
        self.notes = []

        self.spells_per_turn = value(cast)
        self.unbind_per_turn = value(unbind)
        self.spells: List[Spell] = [ARCANE_BOLT, MAGIC_SHIELD]
        self.command_abilities: List[CommandAbility] = []

        self.rules = rules
        for r in self.rules:
            r.apply(self)
예제 #10
0
 def buff(data):
     # roll a dice against you opponent
     # if rolled higher, deal the difference as mortal wounds instead of regular wound
     # if rolled lower, no damage
     data[MW_ON_WOUND_CRIT] = RandomValue({
         5: 1 / 36,
         4: 2 / 36,
         3: 3 / 36,
         2: 4 / 36,
         1: 5 / 36,
         0: 21 / 36
     })
     data[EXTRA_DAMAGE_ON_CRIT_WOUND] = value(-1)
예제 #11
0
    def __init__(
        self,
        name: str,
        range_: Union[int, str, Value, Dict[int, Union[int, str, Value]]],
        attacks: Union[int, str, Value, Dict[int, Union[int, str, Value]]],
        tohit,
        towound,
        rend,
        damage: Union[int, str, Value, Dict[int, Union[int, str, Value]]],
        rules: List[Rule],
    ) -> None:
        self.name = name
        self.range = value(range_)
        self.attacks = value(attacks)
        self.tohit = Roll(tohit)
        self.towound = Roll(towound)
        self.rend = value(rend)
        self.damage = value(damage)

        self.attack_rules: List[Callable] = []

        self.rules = rules
        for r in rules:
            r.apply(self)
예제 #12
0
def test_max_d6_is_6():
    # given
    random_value = value('D6')
    # assert
    assert random_value.max({}, mod=-1) == 5
예제 #13
0
def test_average_1_is_1():
    # given
    random_value = value(1)
    # assert
    assert random_value.average({}, mod=1) == 2
예제 #14
0
def test_max_1_is_1():
    # given
    random_value = value(1)
    # assert
    assert random_value.max({}, mod=-1) == 0
예제 #15
0
def test_average_d6_is_35():
    # given
    random_value = value('D6')
    # assert
    assert random_value.average({}, mod=1) == 4.5
예제 #16
0
def cloak_of_feathers(u: Unit):
    fly(u)
    u.move = value(14)
    u.save = Roll(4)
예제 #17
0
 def morale_grade(self, context: dict):
     if HERO in self.keywords or MONSTER in self.keywords:
         return self.bravery
     mod = value('D6').average(context) - self.morale_roll.average(context)
     return self.bravery + mod
예제 #18
0
 def rule_func(u: Unit):
     value(range_)
     pass
예제 #19
0
def charge_at_3d6(u: Unit):
    u.charge_range = value('3D6')
예제 #20
0
    'Kairos Fateweaver', [
        [Weapon('Staff of Tomorrow', 3, 2, 4, {11: 2, 5: 3, 0: 4}, -1, 2, []),
         Weapon('Beaks and Talons', 1, 5, 4, 3, -1, 2, [])],
    ], {11: 10, 8: 9, 5: 8, 2: 7, 0: 6}, 4, 10, 14, 1, monster_base, rules=[
        FLIGHT,
        Rule('Mastery of Magic', mastery_of_magic),
        Rule('Oracle of Eternity', can_reroll_x_dice_during_game(1)),
        Spell('Gift of Change', 8, None),
    ], keywords=[CHAOS, DAEMON, TZEENTCH, WIZARD, HERO, MONSTER, 'LORD OF CHANGE'], cast=2, unbind=2, named=True))


def arcane_tome(u: Unit):
    u.casting_value = u.casting_value + OncePerGame('D6')


sky_sharks = Rule('Sky-sharks', extra_damage_on_keyword(value('D3') - 1, MONSTER))

TZEENTCH_WS.append(Warscroll(
    'Herald of Tzeentch on Burning Chariot', [
        [Weapon('Staff of Change', 2, 1, 4, 3, -1, 'D3', []),
         Weapon('Wake of Fire', 'move across', 1, 7, 7, 0, 0, [Rule('', deal_x_mortal_wound_on_roll('D3', Roll(4)))]),
         Weapon('Screamer`s Lamprey Bites', 1, 6, 4, 3, 0, 1, [sky_sharks])],
        [Weapon('Ritual Dagger', 1, 2, 4, 4, 0, 1, []),
         Weapon('Wake of Fire', 'move across', 1, 7, 7, 0, 0, [Rule('', deal_x_mortal_wound_on_roll('D3', Roll(4)))]),
         Weapon('Screamer`s Lamprey Bites', 1, 6, 4, 3, 0, 1, [sky_sharks])],
    ], 14, 5, 10, 8, 1, monster_base, rules=[
        FLIGHT,
        Rule('Arcane Tome', arcane_tome),
        Spell('Tzeentch`s Firestorm', 9, None),
    ], keywords=[CHAOS, DAEMON, HORROR, TZEENTCH, WIZARD, HERO, 'HERALD ON CHARIOT'], cast=1, unbind=1))
예제 #21
0
 def buff(data):
     data[NUMBER_OF_HITS] = value(hits)
예제 #22
0
 def buff(data):
     if keyword in data.get(ENEMY_KEYWORDS, []):
         return value(extra_damage)
     return 0
예제 #23
0
 def buff(data):
     if data.get(CHARGING, False):
         data[MORTAL_WOUNDS_PER_ATTACK] = value(mortal_wounds)
예제 #24
0
 def buff(data):
     data[MW_ON_HIT_CRIT] = value(mortal_wounds)
     data[STOP_ON_CRIT_HIT] = True
예제 #25
0
 def hellfire(data):
     data[MW_IF_DAMAGE] = value('D3') * RandomValue({1: 0.5, 0: 0.5})
예제 #26
0
 def buff(data):
     data[EXTRA_DAMAGE_ON_CRIT_WOUND] = value('D6') - 1
예제 #27
0
 def buff(data):
     if data.get(CHARGING, False):
         return value(extra_attacks)
     return 0
예제 #28
0
 def rule_func(u: Unit):
     value(range_)
     u.notes.append(
         f'Spell stealer ({round(value(chances).average({}) * value(tries_per_turn).average({}), 1)})'
     )
예제 #29
0
 def buff(data):
     data[EXTRA_HIT_ON_CRIT] = value(amount) - 1
예제 #30
0
    def attack_round(self, context, users=1):
        my_context = copy(context)
        for r in self.attack_rules:
            r(my_context)

        potential_attacks = {}
        potential_hits = {}
        potential_wounds = {}
        potential_unsaved = {}
        potential_damage = {}
        cleaned_damage = []
        try:
            potential_attacks = [{
                'attacks':
                nb * users,
                'proba':
                proba,
                'mortal_wounds':
                my_context.get(MORTAL_WOUNDS, value(0)) +
                my_context.get(MORTAL_WOUNDS_PER_ATTACK, 0) * nb * users,
            } for (nb, proba) in self.attacks.potential_values(my_context)]
            assert abs(sum([att['proba']
                            for att in potential_attacks]) - 1) <= pow(0.1, 5)
            potential_attacks = cleaned_dict_list(potential_attacks,
                                                  ['attacks', 'mortal_wounds'])

            potential_hits = compute_potential_hits(my_context,
                                                    potential_attacks,
                                                    self.tohit)
            assert abs(sum([hit['proba']
                            for hit in potential_hits]) - 1) <= pow(0.1, 5)
            potential_hits = cleaned_dict_list(
                potential_hits, ['hits', 'crit_hits', 'mortal_wounds'])

            potential_wounds = compute_potential_wounds(
                my_context, potential_hits, self.towound)
            assert abs(sum([wnd['proba']
                            for wnd in potential_wounds]) - 1) <= pow(0.1, 5)
            potential_wounds = cleaned_dict_list(
                potential_wounds, ['wounds', 'crit_wounds', 'mortal_wounds'])

            potential_unsaved = [
                {
                    **wnd, 'unsaved':
                    nb,
                    'unsaved_crit_wound':
                    nb_crit,
                    'proba':
                    wnd['proba'] * probability_of_save_fail(
                        wnd['wounds'],
                        nb,
                        nb_crit,
                        my_context[ENEMY_SAVE],
                        my_context,
                        rend=self.rend.average(my_context),
                        crit_wnd=wnd['crit_wounds'])
                } for wnd in potential_wounds
                for nb in range(wnd['wounds'] + 1) for nb_crit in range(
                    max(0, wnd['crit_wounds'] - (wnd['wounds'] - nb)),
                    min(wnd['crit_wounds'], nb) + 1)
            ]
            assert abs(
                sum([unsvd['proba']
                     for unsvd in potential_unsaved]) - 1) <= pow(0.1, 5)
            potential_unsaved = cleaned_dict_list(
                potential_unsaved,
                ['unsaved', 'unsaved_crit_wound', 'mortal_wounds'])

            potential_damage = compute_potential_damage(
                self.damage, my_context, potential_unsaved)
            assert abs(sum([dmg['proba']
                            for dmg in potential_damage]) - 1) <= pow(0.1, 5)

            potential_full_damage = [{
                **dmg,
                'damage': dmg['damage'] + nb,
                'mortal_wounds': nb,
                'proba': dmg['proba'] * proba,
            } for dmg in potential_damage for (
                nb, proba) in dmg['mortal_wounds'].potential_values(my_context)
                                     ]
            potential_full_damage = cleaned_dict_list(potential_full_damage,
                                                      ['damage'])

            cleaned_damage = [{
                'damage':
                pick * (1 + context.get(MW_ON_DAMAGE, 0)),
                'proba':
                sum([
                    dmg['proba'] for dmg in potential_full_damage
                    if dmg['damage'] == pick
                ])
            } for pick in set(dmg['damage'] for dmg in potential_full_damage)]
            # raise AssertionError  # testing
        except AssertionError:
            info = {
                'potential_attacks': potential_attacks,
                'potential_hits': potential_hits,
                'potential_wounds': potential_wounds,
                'potential_unsaved': potential_unsaved,
                'potential_damage': potential_damage,
                'cleaned_damage': cleaned_damage,
            }
            print(self.name)
            for k, potent in info.items():
                print(f'- {k}:')
                for e in potent:
                    print(
                        str({k: str(v)
                             for k, v in e.items()
                             }).replace("'", "").replace("\"", ""))
                sum_proba = sum(e['proba'] for e in potent)
                print(f'   total={sum_proba}')
            average = sum(
                d.get('damage') * d.get('proba') for d in cleaned_damage)
            print(f'AVERAGE: {average}')

        return cleaned_damage