Angband Forums > Sil A newbie's way of choosing which upgrade to take
 Register FAQ Members List Calendar Search Today's Posts Mark Forums Read

 March 18, 2018, 03:08 #1 CrabMan Rookie   Join Date: Mar 2018 Posts: 7 A newbie's way of choosing which upgrade to take I am a new player of Sil 1.3, have not went deeper than 400' yet, I've only had 5 games so far. I wrote a small program which I use to choose what equipment to use, how to spend exp, etc. The program simulates a character trying to hit another characted with a melee attack - it rolls attack roll, evasion roll, damage roll, defense roll, checks whethere there was a critical hit, calculates the result damage and repeats this for 40000 times to calculate average resulting value. Here it is: Code: ```# this is Python 3 from collections import Counter, namedtuple from functools import partial import re import itertools from random import randint from typing import Callable, Any, Dict, List, TypeVar T = TypeVar("T") Character = namedtuple("Character", [ "melee_score", "damage", "evasion_score", "defense", "critical_overflow" ]) # critical_overflow is how much melee_roll-evasion_roll must be to get # a critical hit; by default this is 7 plus weapon weight def d(dices: int, sides: int) -> int: """Roll `dices` d `sides` and return the result.""" return sum(randint(1, sides) for i in range(dices)) def parse_dices(s: str) -> List[List[int]]: """Takes a string like "1d4 2d7 3d8" and returns a list of pairs like [[1, 4], [2, 7], [3, 8]]""" matches = re.findall(r"(\d+)d(\d+)", s) if not matches: raise ValueError() return [[int(dices), int(sides)] for (dices, sides) in matches] def roll_dices(s: str) -> int: """Takes a string like "1d4 2d7 3d8 1d1" and returns sum of those rolls.""" dices = parse_dices(s) return sum(d(*pair) for pair in dices) def roll_combat_damage(attacker: Character, defender: Character) -> int: """Simulates `attacker` trying to hit `defender`: checks whether the attack connected, then calculates damage. Returns the resulting damage. If the attack didn't connect, returns 0.""" hit = attacker.melee_score + roll_dices("1d20") \ - (defender.evasion_score + roll_dices("1d20")) if hit <= 0: return 0 [[attack_dices, attack_sides]] = parse_dices(attacker.damage) attack_dices += int(hit // (attacker.critical_overflow)) damage_roll = d(attack_dices, attack_sides) defense_roll = roll_dices(defender.defense) return max(damage_roll - defense_roll, 0) def calc_distribution(func: Callable[[], T]) -> Dict[T, float]: """Calls `func` many times, counts how many times each value was returned, returns probabilities (sum of them equals 1). Func must be a function that takes no arguments.""" counter = Counter() iterations = 40000 for i in range(iterations): counter[func()] += 1 probabilities = { damage: count/iterations for (damage, count) in counter.items() } return probabilities def calc_expected_damage(attacker: Character, defender: Character) -> float: """Calculates expected damage when `attacker` tries to hit `defender`.""" distribution = calc_distribution( partial(roll_combat_damage, attacker, defender) ) return sum(damage * prob for (damage, prob) in distribution.items()) def damage_per_damage(hero: Character, enemy: Character) -> None: our_dmg = calc_expected_damage(hero, enemy) their_dmg = calc_expected_damage(enemy, hero) print( """Expected damage vs enemy per attack: {0}, Expected damage from enemy per attack: {1}, If both attack with the same frequency, then expected damage vs enemy per point of damage from them: {2}""" .format(our_dmg, their_dmg, our_dmg/their_dmg) ) # some monsters easterling_archer = Character(9, "1d7", 9, "2d4", 7) easterling_warrior = Character(7, "2d8", 5, "3d4", 7) distended_spider = Character(7, "2d11", 7, "1d1", 12) # poisons warg = Character(9, "2d7", 10, "1d4", 15) mountain_troll = Character(6, "4d5", 3, "2d4", 9) twisted_bat = Character(15, "2d5", 17, "1d4", 8) grave_wight = Character(11, "2d9", 7, "3d4", 8) # is actually immune to crits # behind the wight there was a small treasure-like thing - a fucking potion # prolly not worth it barrow_wight = Character(13, "2d9", 8, "3d4", 9) # doesnt pursue, DRAINS STATS # tries to disarm using his whip orc_captain = Character(10, "2d8", 7, "3d4", 9) # disarms too? othrod = Character(15, "2d9", 9, "4d4", 7)``` Let's say that my character has (+10, "2d9") attack with a weapon that weighs 3lb, [+12, 5-15] defense (actually 5-15 is 1d4 1d6 1d2 1d1 1d2), and I have neither power, nor finesse. My typical enemy is easterling warrior. I have some exp left and I am choosing between taking power, finesse or putting another point in evasion. I will do this in python REPL: Code: ```In [19]: me_power = Character(10, "2d10", 12, "1d4 1d6 1d2 1d1 1d2", 8+3) In [20]: me_finesse = Character(10, "2d9", 12, "1d4 1d6 1d2 1d1 1d2", 6+3) In [21]: me_evasion = Character(10, "2d9", 13, "1d4 1d6 1d2 1d1 1d2",7+3) In [22]: damage_per_damage(me_power, easterling_warrior) Expected damage vs enemy per attack: 4.274275, Expected damage from enemy per attack: 0.5650249999999999, If both attack with the same frequency, then expected damage vs enemy per point of damage from them: 7.564753771956995 In [23]: damage_per_damage(me_finesse, easterling_warrior) Expected damage vs enemy per attack: 4.080675000000002, Expected damage from enemy per attack: 0.5656999999999999, If both attack with the same frequency, then expected damage vs enemy per point of damage from them: 7.213496552943261 In [24]: damage_per_damage(me_evasion, easterling_warrior) Expected damage vs enemy per attack: 3.7949249999999997, Expected damage from enemy per attack: 0.47392500000000004, If both attack with the same frequency, then expected damage vs enemy per point of damage from them: 8.007437885741414``` So evasion seems the best. However to level evasion I need to spend 900 exp, while power or finesse costs only 500. So I will also calculate my current dmg per dmg: Code: ```In [25]: me_currently = Character(10, "2d9", 12, "1d4 1d6 1d2 1d1 1d2",7+3) In [26]: damage_per_damage(me_currently, easterling_warrior) Expected damage vs enemy per attack: 3.7500250000000004, Expected damage from enemy per attack: 0.56525, If both attack with the same frequency, then expected damage vs enemy per point of damage from them: 6.6342768686421945``` Let's see, I can spend 500 exp to make my character 7.56/6.63=1.14 times more powerful or I can spend 900 exp to make my character 8.00/6.63=1.2 times more powerful which is equivalent (like compound interest) to 1.2^(5/9)=1.11 times for 500 exp. By this method I decide that taking power is better. If I have weapon of Gondolin, I would create a Character object, increase its number of sides by 1, and use it for estimates. To calculate dmg per dmg for two weapon fighting I would calculate dmg per dmg for the main hand and for the 2nd hand separately and add them. For some other skills this algorithm is difficult or impossible to apply. Now come some notes. People on this forum seem to think that finesse is good and power is bad. Every time I compared them that was not the case, in fact power seems very good every time. Also I learned that [0, 1d1] is usually better than [-1, 1d2]. But defense per evasion lost than this is usually good. Battle axes are good. Curved sword is almost always better than short sword. Hand-and-a-half weapon + shield is usually better than wielding it with 2 hands. Mountain trolls are unlike other creatures - their damage is so high that you are better off having high expected damage and evasion against them than defense. Feel free to give feedback, to tell me why my algorithm is bad, etc.
 March 19, 2018, 03:14 #3 wobbly Veteran   Join Date: May 2012 Location: Adelaide, Australia Posts: 1,991 Re hauberks: I use them. I mean a plain hauberk is just bad but fine hauberks or specials can be good. If you're comparing late game high protection characters spend a lot less on evasion which is more pts spent elsewhere.
 March 19, 2018, 03:20 #4 CrabMan Rookie   Join Date: Mar 2018 Posts: 7 Yes, I didn't know how monster criticals work, thank you.
March 19, 2018, 03:59   #5
CrabMan
Rookie

Join Date: Mar 2018
Posts: 7
Quote:
 Originally Posted by Quirk However, there are some factors that aren't being tracked here which are relevant. The first is the cost of being surrounded - you get Evasion penalties based on being surrounded. Each enemy adjacent on the three sides opposite to your attacker brings your Evasion down by -2 and other adjacent enemies bring it down by -1. So from a corridor perspective, if you have three enemies approaching on the left and one on the right, you want to kill the one on the right first, and quickly. Doing more damage counts for more than efficiently outdamaging one foe. This makes all the non-evasion options slightly better along with two handed weapons. I'd need to extend your code and run some simulations to say exactly how this changes things, but the more turns you spend surrounded, the worse you will fare - two enemies are hitting you at effectively +2 instead of one at +0.
Actually, when surrounded you are still equally happy if you increase your expected damage twofold or if you decrease the enemies' expected damage twofold. It's just now you need to calculate expected damage as if the enemies had more melee score (yes, the code can be improved to support that better). Consider the following:

You are with two enemies of the same type in a corridor, one on each side. Let's say on average you deal d_h (imagine _ means subscript) to the monster with your attack (h stands for hero), a single monster on average deals d_m damage per attack, and when you are surrounded in a corridor, their melee score is increased by two, and then a monster deals d_{m2} damage on average. You will fight the first monster for t=h_m/d_h, where h_m? is the monster's hp. So in total the first monster will deal d_{m2} t damage to you, and the second monster will deal (d_{m2}+d_m)t damage - the sum of that is the total amount of health you will lose fighting these two monsters, and it equals D = (2d_{m2}+d_m?)t = (2d_{m2}+d_m) h_m? / d_h. Notice that if you increase d_h twofold, this value will decrease twofold, and the same will happen if you decrease some value between d_m and d_{m2} twofold. And if you increase d_m by some value and decrease d_m and d_{m2} in such way that hero's damage per point of monster's damage increases twofold, the same will happen. The conclusion is that you are equally happy. The same will hold for more enemies, e.g. for 3 enemies the formula will be D = (d_{m4}+2d_{m2}+2d_{m1}+d_m?) h_m? / d_h.

This does not take into account that the longer you fight, the hungries you will get, the more light source turns you will lose and the more likely some more enemies will accidentally stumble upon you and make your life harder, so increasing damage output by x percent is slightly better than decreasing how much damage you take by the same percentage. Also note that if you are fighting multiple enemies, then you will suffer more hits with enemies having melee score bonus, so you should probably optimize damage per damage for the enemies having melee score +2, +3, or even +4 case.

Last edited by CrabMan; March 19, 2018 at 04:06.

 March 19, 2018, 09:46 #6 Quirk Swordsman   Join Date: Mar 2016 Posts: 286 You're still assuming you start surrounded until the end of the post; sorry if I didn't communicate clearly enough that this is the root of the issue. Assume we have just two enemies in a corridor. You have entered the corridor at full health so the second enemy is reluctant to pursue, until you meet another enemy and it decides it can surround you. It will take two turns to close the gap to do so. We will ignore the surround bonus. Case A: you damage one-third of an enemy's health each turn, and take one-tenth of your health in damage. Case B: you damage one-sixth of an enemy's health each turn, and take one-twentieth of your health in damage. Case A: The first enemy hits you twice, and you hit it twice, before the second enemy closes the gap. The first enemy is now at one-third health. Assuming the enemy is first to move, you are hit from both sides before killing the first enemy. You take two blows from the first enemy, one blow from each enemy while surrounded, and a parting blow before your next action. This reduces you to 50% health. Case B: The first enemy hits you twice, and you hit it twice, before the second enemy closes the gap. The first enemy is now at two-thirds health. Assuming the enemy is first to move, you are hit from both sides multiple times before killing the first enemy. You take two blows from the first enemy, eight blows from both sides while surrounded, and a parting blow before your next action. This reduces you to 45% health. However, in practice, the two blows while surrounded in case A are made at +2, and the eight blows while surrounded in case B are made at +2.
March 19, 2018, 09:47   #7
Quirk
Swordsman

Join Date: Mar 2016
Posts: 286
Quote:
 Originally Posted by wobbly Re hauberks: I use them. I mean a plain hauberk is just bad but fine hauberks or specials can be good. If you're comparing late game high protection characters spend a lot less on evasion which is more pts spent elsewhere.
Yeah - I spent some time playing with the numbers and while basic hauberks are just bad, there's no way to improve them that doesn't lead to fine hauberks being stupidly good.

 March 19, 2018, 10:52 #8 CrabMan Rookie   Join Date: Mar 2018 Posts: 7 Ah, I see what you are talking about. I guess doing (melee) damage is moderately better than evading damage.

 Tags estimator, power

 Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 Thread Tools Display Modes Linear Mode

 Posting Rules You may not post new threads You may not post replies You may not post attachments You may not edit your posts BB code is On Smilies are On [IMG] code is On HTML code is Off Forum Rules
 Forum Jump User Control Panel Private Messages Subscriptions Who's Online Search Forums Forums Home Angband     AAR     Vanilla     Development     ToME     Sil     Variants     Competition The real world     Idle chatter     Oook! Obsolete     v4

 Similar Threads Thread Thread Starter Forum Replies Last Post OOD Town drunk Vanilla 10 September 2, 2016 13:18 Bowman Vanilla 3 December 11, 2015 14:25 Ehrblast Vanilla 13 September 7, 2014 00:20 arturolorioli Vanilla 7 June 14, 2011 19:20 Taha Vanilla 4 August 8, 2009 09:17

All times are GMT +1. The time now is 01:26.