Results 1 to 12 of 12
-
2013-12-07, 10:14 AM (ISO 8601)
- Join Date
- Feb 2013
Commoner vs. Cat - A Mathematical Analysis
It's one of the fundamental criticisms of 3.5, a sign of the inherent imbalance of the system: in RAW, a tiny housecat can easily kill off your average, medium sized commoner.
But what is a commoner to do in a world where felines can lay them low at a whim? What would be the mathematically logical way for a peasant to deal with Mr.Whiskers refusing to leave their hayloft? I was curious, so I wrote a python program to simulate combat between the two.
Combatants
Commoner with 10 in all stats and no combat relevant feats.
Cat as printed in the Monster Manual.
Weapon Choices
As per RAW, a peasant can be proficient with a single common weapon. There are four different ways to wield the weapon:
{table=head]Weapon Style|Fighting Style
Single Weapon| Close
Double (TWF)| Ranged
[/table]
The commoner will try "Double" with any weapon that is not two handed. However, although there are rules for throwing any weapon, they will only try to throw/fire a weapon with a printed range.
A.I.
If the commoner's weapon has a range, and the commoner is using the "ranged" fighting style, they and the cat will be placed at the maximum distance at which the weapon can be fired with no penalty. Otherwise, both will begin 30ft. apart.
Each character rolls for initiative. The loser is flatfooted until their first action.
If the commoner is using the "ranged" fighting style, they will try to get as many ranged attack before the cat closes the distance. Afterwards they will switch to melee attacks if possible.
For "close" fighting style, the cat and commoner both try to close the distance using charges, running, and five-foot steps. This is followed by full attacks.
Analysis
Each "Battle" consists of one thousand matches of the cat against the commoner with each weapon.
Each "Trial" consists of one "Battle". The following test consists of 10 trials, for a total of 10 000 matches between the cat and the commoner with each weapon.
{table=head]Weapon|Odds of Victory
Light Crossbow(single, ranged)|63%
Light Crossbow(double, ranged)|62%
Spear(single, ranged)|61%
Javelin(single, ranged)|60%
Javelin(double, ranged)|60%
Shortspear(single, ranged)|59%
MorningStar(single)|59%
Heavy Mace(single)|58%
Spear(single)|58%
Longspear(single)|58%
Light Mace(single)|57%
Shortspear(single)|56%
Sickle(single)|56%
Dart(double, ranged)|56%
Dart(single, ranged)|56%
Sling(double, ranged)|56%
Sling(single, ranged)|55%
Quarterstaff(single)|55%
Punching Dagger(single)|53%
Spiked Gauntlet(single)|52%
Gauntlet(single)|49%
Unarmed(single)|49%
Club(single, ranged)|45%
Club(single)|44%
Dagger(single, ranged)|41%
Dagger(single)|41%
Heavy Crossbow(single, ranged)|37%
Light Mace(double)|33%
Heavy Crossbow(double, ranged)|33%
Sickle(double)|33%
Spiked Gauntlet(double)|30%
Punching Dagger(double)|30%
Heavy Mace(double)|30%
MorningStar(double)|29%
Shortspear(double, ranged)|28%
Gauntlet(double)|28%
Shortspear(double)|28%
Quarterstaff(double)|27%
Dagger(double, ranged)|27%
Unarmed(double)|27%
Dagger(double)|23%
Club(double, ranged)|20%
Club(double)|12%
[/table]
From the look of things:
- Our commoner's best bet is to stock up on light crossbows and learn how to throw a spear.
- Dual wielding clubs is tantamount to coating your arteries with tuna
- In fact, dual wielding just about anything is borderline suicide.
The biggest surprise was that yes, when the cat dominates, it dominates hard. But with the right weapon choice the commoner can put up a pretty decent fight.
I'll post the code in the next section if anyone is interested.
-
2013-12-07, 10:17 AM (ISO 8601)
- Join Date
- Oct 2007
- Gender
-
2013-12-07, 10:22 AM (ISO 8601)
- Join Date
- Feb 2013
Re: Commoner vs. Cat - A Mathematical Analysis
Program #1 - Single Combat
This is a simplified version of the program that simulates a single fight per weapon, and prints out each step to help you understand how it works.
Code:import random, copy class WeaponType: """The types of Simple Weapons""" unarmed=0 light=1 oneHanded=2 twoHanded=3 ranged=4 class RearmTimes: """How long a weapon takes to re-arm (or in the case of ranged, reload)""" move=0 moveAoO=1 fullAoO=2 WT = WeaponType() RT = RearmTimes() class Weapon: """Simple Method of storing D&D Weapons""" damage = 1 critical = 2 critRange = 20 type = WT.unarmed range = 0 rearm = RT.move double = False reach = False #All weapons are assumed to be ready to fire/swing/stab usable = True offhandPenalty = WT.oneHanded def __init__(self, damage, critical, critRange=20, type=WT.unarmed, range=0, rearm=RT.move, double=False, reach=False, offhandPenalty = WT.oneHanded): self.damage = damage self.critical = critical self.critRange = critRange self.type = type self.range = range self.rearm = rearm self.double = double self.reach = reach self.offhandPenalty = offhandPenalty class Commoner: """An average, everyday peasant""" strengthBonus = 0 dexterityBonus = 0 attack = 0 health = 4 AC = 10 initiative = 0 charged = False primary = Weapon(3,2) offhand = None def __init__(self, primary, offhand = None): self.primary = primary self.offhand = offhand class Cat: """Just your average housecat""" strengthBonus = -4 dexterityBonus = 2 attack = 4 health = 2 AC = 14 initiative = 0 charged = False claw = Weapon(1, 2, 20, WT.light) bite = Weapon(1, 2, 20, WT.light) weapons = { "Unarmed": Weapon(3, 2, 20, WT.unarmed), "Gauntlet": Weapon(3, 2, 20, WT.unarmed), "Dagger": Weapon(4, 3, 19, WT.light, 10), "Punching Dagger": Weapon(4, 3, 20, WT.light), "Spiked Gauntlet": Weapon(4, 2, 20, WT.light), "Light Mace": Weapon(6, 2, 20, WT.light), "Sickle": Weapon(6, 2, 20, WT.light), "Club": Weapon(6, 2, 20, WT.oneHanded, 10), "Heavy Mace": Weapon(8, 2, 20, WT.oneHanded), "MorningStar": Weapon(8, 2, 20, WT.oneHanded), "Shortspear": Weapon(6, 2, 20, WT.oneHanded, 20), "Longspear": Weapon(8, 3, 20, WT.twoHanded, reach=True), "Quarterstaff": Weapon(6, 2, 20, WT.twoHanded, double=True), "Spear": Weapon(8, 3, 20, WT.twoHanded, 20), "Heavy Crossbow": Weapon(10, 2, 19, WT.ranged, 120, RT.fullAoO), "Light Crossbow": Weapon(8, 2, 19, WT.ranged, 80, RT.moveAoO, offhandPenalty = WT.light), "Dart": Weapon(4, 2, 20, WT.ranged, 20, RT.move, offhandPenalty = WT.light), "Javelin": Weapon(6, 2, 20, WT.ranged, 30), "Sling": Weapon(4, 2, 20, WT.ranged, 50, RT.moveAoO) } class Distance: """ How far the cat and human are from each other """ none = 0 five = 1 charge = 2 long = 3 def rollDamage(attacker, defender, weapon, bonus=0): """Rolls a generic attack roll""" roll = random.randint(1,20) damage = random.randint(1,weapon.damage) print("Rolls: " + str(roll)) if roll == 1: print("Miss!") return() if roll == 20 or roll >= weapon.critRange: roll = random.randint(1,20) if (attacker.attack + roll + bonus) >= defender.AC: defender.health = defender.health - (damage * weapon.critical) print("Critical! " + str(damage * weapon.critical) + " damage") return() defender.health = defender.health - damage print("Hit! " + str(damage) + " damage") return() if(attacker.attack + roll + bonus) >= defender.AC: defender.health = defender.health - damage print("Hit! " + str(damage) + " damage") else: print("Miss!") def charge(attacker, weapon, defender): """One character charges another""" attacker.AC = attacker.AC - 2 attacker.charged = True rollDamage(attacker, defender, weapon, 2) def catFullAttack(cat, commoner): """Cat does full attack against commoner""" rollDamage(cat, commoner, cat.claw) rollDamage(cat, commoner, cat.claw) rollDamage(cat, commoner, cat.bite, -5) def fullAttack(commoner, weaponStyle, cat): """Commoner makes a full attack against the cat""" if not commoner.primary.usable: print("Commoner draws primary weapon") commoner.primary.usable = True rollDamage(commoner, cat, commoner.primary) return() if weaponStyle == "single": print("Commoner Full Attacks!") rollDamage(commoner, cat, commoner.primary) return() if not commoner.offhand.usable: print("Commoner draws offhand weapon") commoner.offhand.usable = True print("Commoner Full Attacks!") if commoner.offhand.type == WT.light or commoner.offhand.type == WT.unarmed: rollDamage(commoner, cat, commoner.primary, -6) rollDamage(commoner, cat, commoner.offhand, -8) else: rollDamage(commoner, cat, commoner.primary, -8) rollDamage(commoner, cat, commoner.offhand, -10) def rangedAttack(commoner, weaponStyle, cat): """Commoner makes a ranged attack against the cat""" #print("Offhand Usable (beg Rang): " + str(commoner.offhand.usable)) if not commoner.primary.usable: #Reloading takes Full Round Action if commoner.primary.rearm == RT.fullAoO: print("Commoner reloads.") commoner.primary.usable = True return() elif commoner.primary.rearm == RT.moveAoO: print("Commoner reloads.") commoner.primary.usable = True elif commoner.primary.rearm == RT.move: print("Commoner draws new weapon") commoner.primary.usable = True print("Commoner fired weapon") if commoner.primary.type == WT.ranged else print("Commoner threw weapon") if weaponStyle == "double": if commoner.offhand.usable: print("Commoner Full Attacks!") if commoner.offhand.offhandPenalty == WT.light: rollDamage(commoner, cat, commoner.primary, -6) rollDamage(commoner, cat, commoner.offhand, -8) else: rollDamage(commoner, cat, commoner.primary, -8) rollDamage(commoner, cat, commoner.offhand, -10) commoner.primary.usable = False commoner.offhand.usable = False return() rollDamage(commoner, cat, commoner.primary) commoner.primary.usable = False def catAttackCommoner(cat, commoner, range): """Decides which attack to use against the commoner""" #First we check if we need to cancel a charge penalty if cat.charged: cat.charged = False cat.AC = cat.AC + 2 if range == Distance.charge: print("Cat Charges!") charge(cat, cat.claw, commoner) return(Distance.none) if range == Distance.long: print("Cat runs to commoner") return(Distance.none) if range == Distance.five: print("Cat Full Attacks!") catFullAttack(cat, commoner) return(Distance.none) if range == Distance.none: print("Cat Full Attacks!") catFullAttack(cat, commoner) return(Distance.none) def commonerAttackCat(commoner, cat, range, weaponStyle, fightingStyle): """Decides which attack to use against the cat""" #First we check if we need to cancel a charge penalty if commoner.charged: commoner.charged = False commoner.AC = commoner.AC + 2 if range == Distance.charge: if fightingStyle == "close": print("Commoner Charges!") charge(commoner, commoner.primary, cat) if commoner.primary.reach: return(Distance.five) else: return(Distance.none) else: rangedAttack(commoner, weaponStyle, cat) return(Distance.charge) if range == Distance.five: if fightingStyle == "close": fullAttack(commoner, weaponStyle, cat) else: rangedAttack(commoner, weaponStyle, cat) return(Distance.five) if range == Distance.long: rangedAttack(commoner, weaponStyle, cat) return(Distance.long) if range == Distance.none: if commoner.primary.reach: print("Commoner Full Attacks!") fullAttack(commoner, weaponStyle, cat) return(Distance.five) if commoner.primary.type == WT.ranged: rangedAttack(commoner, weaponStyle, cat) return(Distance.none) fullAttack(commoner, weaponStyle, cat) return(Distance.none) def printRange(range): """Prints the Current Range""" if range == Distance.long: print("Range: Long") elif range == Distance.charge: print("Range: Charge") elif range == Distance.five: print("Range: Five") elif range == Distance.none: print("Range: None") def battle(weapon, weaponStyle="single", fightingStyle="close"): """Simulates a battle between a cat and human commoner""" cat = Cat() commoner = Commoner(weapon, copy.deepcopy(weapon) if weaponStyle == "double" else None) # Establish how far apart they begin range = Distance.charge if weapon.range > 60: range = Distance.long elif weapon.range > 30: range = Distance.charge elif range == 10: range = Distance.five #Role for initiative cat.initiative = cat.dexterityBonus + random.randint(1,20) commoner.initiative = commoner.dexterityBonus + random.randint(1,20) #First round printRange(range) if cat.initiative >= commoner.initiative: print("Cat Wins Initiative") commoner.AC = commoner.AC - commoner.dexterityBonus range = catAttackCommoner(cat, commoner, range) commoner.AC = commoner.AC + commoner.dexterityBonus else: print("Commoner Wins Initiative") cat.AC = cat.AC - cat.dexterityBonus range = commonerAttackCat(commoner, cat, range, weaponStyle, fightingStyle) cat.AC = cat.AC + cat.dexterityBonus while cat.health > 0 and commoner.health > 0: printRange(range) if cat.initiative < commoner.initiative: range = catAttackCommoner(cat, commoner, range) if commoner.health > 0: range = commonerAttackCat(commoner, cat, range, weaponStyle, fightingStyle) else: range = commonerAttackCat(commoner, cat, range, weaponStyle, fightingStyle) if cat.health > 0: range = catAttackCommoner(cat, commoner, range) return(True if commoner.health > 0 else False) for weapon in sorted(weapons.keys()): #First we check if it is possible to fight with a weapon in this way if not weapons[weapon].type == WT.ranged: print(weapon + "(single) vs. Cat") if battle(weapons[weapon]): print("Commoner Wins\n") else: print("Cat Wins\n") if not weapons[weapon].type == WT.twoHanded or weapons[weapon].double: print(weapon + "(double) vs. Cat") if battle(weapons[weapon], "double"): print("Commoner Wins\n") else: print("Cat Wins\n") if weapons[weapon].range > 0: print(weapon + "(single, ranged) vs. Cat") if battle(weapons[weapon], "single", "ranged"): print("Commoner Wins\n") else: print("Cat Wins\n") if not weapons[weapon].type == WT.twoHanded: print(weapon + "(double, ranged) vs. Cat") if battle(weapons[weapon], "double", "ranged"): print("Commoner Wins\n") else: print("Cat Wins\n") print("---------------------------------\n")
This is the big one. At the beginning of the script you can change the "Battles" variable to change how many battles per trial, and the "Trials" variable for how many trials should be run.
You can also change the "Format". "Console" will print it for the command line, while "GITP" formats it for copy-pasting as a table to these forums.
Code:import random, copy, threading, queue #Battles per Trial Battles = 1000 #How many trials to run Trials = 10 #Threads to run. Generally, "optimum threads" = "number of processors" Threads = 2 #Two options for output: "Console" or "GITP" Format="Console" class WeaponType: """The types of Simple Weapons""" unarmed=0 light=1 oneHanded=2 twoHanded=3 ranged=4 class RearmTimes: """How long a weapon takes to re-arm (or in the case of ranged, reload)""" move=0 moveAoO=1 fullAoO=2 WT = WeaponType() RT = RearmTimes() class Weapon: """Simple Method of storing D&D Weapons""" damage = 1 critical = 2 critRange = 20 type = WT.unarmed range = 0 rearm = RT.move double = False reach = False #All weapons are assumed to be ready to fire/swing/stab usable = True offhandPenalty = WT.oneHanded def __init__(self, damage, critical, critRange=20, type=WT.unarmed, range=0, rearm=RT.move, double=False, reach=False, offhandPenalty = WT.oneHanded): self.damage = damage self.critical = critical self.critRange = critRange self.type = type self.range = range self.rearm = rearm self.double = double self.reach = reach self.offhandPenalty = offhandPenalty class Commoner: """An average, everyday peasant""" strengthBonus = 0 dexterityBonus = 0 attack = 0 health = 4 AC = 10 initiative = 0 charged = False primary = Weapon(3,2) offhand = None def __init__(self, primary, offhand = None): self.primary = primary self.offhand = offhand class Cat: """Just your average housecat""" strengthBonus = -4 dexterityBonus = 2 attack = 4 health = 2 AC = 14 initiative = 0 charged = False claw = Weapon(1, 2, 20, WT.light) bite = Weapon(1, 2, 20, WT.light) weapons = { "Unarmed": Weapon(3, 2, 20, WT.unarmed), "Gauntlet": Weapon(3, 2, 20, WT.unarmed), "Dagger": Weapon(4, 3, 19, WT.light, 10), "Punching Dagger": Weapon(4, 3, 20, WT.light), "Spiked Gauntlet": Weapon(4, 2, 20, WT.light), "Light Mace": Weapon(6, 2, 20, WT.light), "Sickle": Weapon(6, 2, 20, WT.light), "Club": Weapon(6, 2, 20, WT.oneHanded, 10), "Heavy Mace": Weapon(8, 2, 20, WT.oneHanded), "MorningStar": Weapon(8, 2, 20, WT.oneHanded), "Shortspear": Weapon(6, 2, 20, WT.oneHanded, 20), "Longspear": Weapon(8, 3, 20, WT.twoHanded, reach=True), "Quarterstaff": Weapon(6, 2, 20, WT.twoHanded, double=True), "Spear": Weapon(8, 3, 20, WT.twoHanded, 20), "Heavy Crossbow": Weapon(10, 2, 19, WT.ranged, 120, RT.fullAoO), "Light Crossbow": Weapon(8, 2, 19, WT.ranged, 80, RT.moveAoO, offhandPenalty = WT.light), "Dart": Weapon(4, 2, 20, WT.ranged, 20, RT.move, offhandPenalty = WT.light), "Javelin": Weapon(6, 2, 20, WT.ranged, 30), "Sling": Weapon(4, 2, 20, WT.ranged, 50, RT.moveAoO) } class Distance: """ How far the cat and human are from each other """ none = 0 five = 1 charge = 2 long = 3 def rollDamage(attacker, defender, weapon, bonus=0): """Rolls a generic attack roll""" roll = random.randint(1,20) damage = random.randint(1,weapon.damage) if roll == 1: return() if roll == 20 or roll >= weapon.critRange: roll = random.randint(1,20) if (attacker.attack + roll + bonus) >= defender.AC: defender.health = defender.health - (damage * weapon.critical) return() defender.health = defender.health - damage return() if(attacker.attack + roll + bonus) >= defender.AC: defender.health = defender.health - damage def charge(attacker, weapon, defender): """One character charges another""" attacker.AC = attacker.AC - 2 attacker.charged = True rollDamage(attacker, defender, weapon, 2) def catFullAttack(cat, commoner): """Cat does full attack against commoner""" rollDamage(cat, commoner, cat.claw) rollDamage(cat, commoner, cat.claw) rollDamage(cat, commoner, cat.bite, -5) def fullAttack(commoner, weaponStyle, cat): """Commoner makes a full attack against the cat""" if not commoner.primary.usable: commoner.primary.usable = True rollDamage(commoner, cat, commoner.primary) return() if weaponStyle == "single": rollDamage(commoner, cat, commoner.primary) return() if not commoner.offhand.usable: commoner.offhand.usable = True if commoner.offhand.type == WT.light or commoner.offhand.type == WT.unarmed: rollDamage(commoner, cat, commoner.primary, -6) rollDamage(commoner, cat, commoner.offhand, -8) else: rollDamage(commoner, cat, commoner.primary, -8) rollDamage(commoner, cat, commoner.offhand, -10) def rangedAttack(commoner, weaponStyle, cat): """Commoner makes a ranged attack against the cat""" if not commoner.primary.usable: #Reloading takes Full Round Action if commoner.primary.rearm == RT.fullAoO: commoner.primary.usable = True return() elif commoner.primary.rearm == RT.moveAoO: commoner.primary.usable = True elif commoner.primary.rearm == RT.move: commoner.primary.usable = True if weaponStyle == "double": if commoner.offhand.usable: if commoner.offhand.offhandPenalty == WT.light: rollDamage(commoner, cat, commoner.primary, -6) rollDamage(commoner, cat, commoner.offhand, -8) else: rollDamage(commoner, cat, commoner.primary, -8) rollDamage(commoner, cat, commoner.offhand, -10) commoner.primary.usable = False commoner.offhand.usable = False return() rollDamage(commoner, cat, commoner.primary) commoner.primary.usable = False def catAttackCommoner(cat, commoner, range): """Decides which attack to use against the commoner""" #First we check if we need to cancel a charge penalty if cat.charged: cat.charged = False cat.AC = cat.AC + 2 if range == Distance.charge: charge(cat, cat.claw, commoner) return(Distance.none) if range == Distance.long: return(Distance.none) if range == Distance.five: catFullAttack(cat, commoner) return(Distance.none) if range == Distance.none: catFullAttack(cat, commoner) return(Distance.none) def commonerAttackCat(commoner, cat, range, weaponStyle, fightingStyle): """Decides which attack to use against the cat""" #First we check if we need to cancel a charge penalty if commoner.charged: commoner.charged = False commoner.AC = commoner.AC + 2 if range == Distance.charge: if fightingStyle == "close": charge(commoner, commoner.primary, cat) if commoner.primary.reach: return(Distance.five) else: return(Distance.none) else: rangedAttack(commoner, weaponStyle, cat) return(Distance.charge) if range == Distance.five: if fightingStyle == "close": fullAttack(commoner, weaponStyle, cat) else: rangedAttack(commoner, weaponStyle, cat) return(Distance.five) if range == Distance.long: rangedAttack(commoner, weaponStyle, cat) return(Distance.long) if range == Distance.none: if commoner.primary.reach: fullAttack(commoner, weaponStyle, cat) return(Distance.five) if commoner.primary.type == WT.ranged: rangedAttack(commoner, weaponStyle, cat) return(Distance.none) fullAttack(commoner, weaponStyle, cat) return(Distance.none) def battle(weapon, weaponStyle="single", fightingStyle="close"): """Simulates a battle between a cat and human commoner""" cat = Cat() commoner = Commoner(weapon, copy.deepcopy(weapon) if weaponStyle == "double" else None) # Establish how far apart they begin range = Distance.charge if weapon.range > 60: range = Distance.long elif weapon.range > 30: range = Distance.charge elif weapon.range == 10: range = Distance.five #Role for initiative cat.initiative = cat.dexterityBonus + random.randint(1,20) commoner.initiative = commoner.dexterityBonus + random.randint(1,20) #First round if cat.initiative >= commoner.initiative: commoner.AC = commoner.AC - commoner.dexterityBonus range = catAttackCommoner(cat, commoner, range) commoner.AC = commoner.AC + commoner.dexterityBonus else: cat.AC = cat.AC - cat.dexterityBonus range = commonerAttackCat(commoner, cat, range, weaponStyle, fightingStyle) cat.AC = cat.AC + cat.dexterityBonus while cat.health > 0 and commoner.health > 0: if cat.initiative < commoner.initiative: range = catAttackCommoner(cat, commoner, range) if commoner.health > 0: range = commonerAttackCat(commoner, cat, range, weaponStyle, fightingStyle) else: range = commonerAttackCat(commoner, cat, range, weaponStyle, fightingStyle) if cat.health > 0: range = catAttackCommoner(cat, commoner, range) return(True if commoner.health > 0 else False) def multiBattle(weapon, weaponStyle="single", fightingStyle="close"): commonerWins = 0 catWins = 0 for match in range(0, Battles): if battle(weapon, weaponStyle, fightingStyle): commonerWins += 1 else: catWins += 1 #print("Commoner Wins: " + str(commonerWins) + " | Cat Wins " + str(catWins)) return(commonerWins) weaponAndVictory = {} victoryOverTime = {} TrialsCompleted = 0 def trialFinished(): global TrialsCompleted global Trials #print("Values: " + str(TrialsCompleted) + "," + "Trials" + str(Trials)) TrialsCompleted += 1 print("%d%%" % int(TrialsCompleted/Trials * 100)) def war(): #print(threading.currentThread().name) for weapon in weapons.keys(): #First we check if it is possible to fight with a weapon in this way commonerWins = 0 currentWeapon = "" if not weapons[weapon].type == WT.ranged: #print(weapon + "(single) vs. Cat") commonerWins = multiBattle(copy.deepcopy(weapons[weapon])) weaponAndVictory[weapon + "(single)"] = commonerWins if not weapons[weapon].type == WT.twoHanded or weapons[weapon].double: #print(weapon + "(double) vs. Cat") commonerWins = multiBattle(copy.deepcopy(weapons[weapon]), "double") weaponAndVictory[weapon + "(double)"] = commonerWins if weapons[weapon].range > 0: #print(weapon + "(single, ranged) vs. Cat") commonerWins = multiBattle(copy.deepcopy(weapons[weapon]), "single", "ranged") weaponAndVictory[weapon + "(single, ranged)"] = commonerWins if not weapons[weapon].type == WT.twoHanded: #print(weapon + "(double, ranged) vs. Cat") commonerWins = multiBattle(copy.deepcopy(weapons[weapon]), "double", "ranged") weaponAndVictory[weapon + "(double, ranged)"] = commonerWins #print("---------------------------------\n") for scenario in weaponAndVictory.keys(): victoryOverTime[scenario] += weaponAndVictory[scenario] #print(scenario + " - " + str(weaponAndVictory[scenario])) trialFinished() def threadTrials(): while True: war() def printToCommandLine(): """Formats the string for the command line""" for scenario in sorted(victoryOverTime, key=victoryOverTime.get, reverse=True): print("%-32s%d%%" % (scenario, int((victoryOverTime[scenario]/(Trials * Battles)) * 100))) def printToGiantInThePlayground(): """Formats the output for printing on the GITP forums""" #Print table header print("{table=head]Weapon|Odds of Victory") colour = "red" #Print Columns for scenario in sorted(victoryOverTime, key=victoryOverTime.get, reverse=True): if int((victoryOverTime[scenario]/(Trials * Battles)) * 100) >= 50: colour = "green" else: colour = "red" print("%s|[color=%s]%d%%[/color]" % (scenario, colour, int((victoryOverTime[scenario]/(Trials * Battles)) * 100))) #Close Table print("[/table]") #initialize the dictionary for weapon in weapons.keys(): if not weapons[weapon].type == WT.ranged: victoryOverTime[weapon + "(single)"] = 0 if not weapons[weapon].type == WT.twoHanded or weapons[weapon].double: victoryOverTime[weapon + "(double)"] = 0 if weapons[weapon].range > 0: victoryOverTime[weapon + "(single, ranged)"] = 0 if not weapons[weapon].type == WT.twoHanded: victoryOverTime[weapon + "(double, ranged)"] = 0 lock = threading.Lock() print("Prepare For Battle!") for thread in range(Threads): t = threading.Thread(target=threadTrials) t.daemon = True t.start() while TrialsCompleted < Trials: True if Format == "Console": printToCommandLine() elif Format == "GITP": printToGiantInThePlayground()
-
2013-12-07, 10:31 AM (ISO 8601)
- Join Date
- Jan 2012
Re: Commoner vs. Cat - A Mathematical Analysis
-
2013-12-07, 10:42 AM (ISO 8601)
- Join Date
- Mar 2012
- Location
- NJ
- Gender
Re: Commoner vs. Cat - A Mathematical Analysis
All this code blows my mind. since i can't decipher it....was there a way to make sure since the cat is a size category smaller it ended up with the +1 to hit a larger commoner?
and vice versa?
-
2013-12-07, 10:42 AM (ISO 8601)
- Join Date
- Feb 2010
- Location
- Finland
Re: Commoner vs. Cat - A Mathematical Analysis
No reach weapons?
Also, cat has hide/move silently scores the commoner will be hard pressed to match (assuming proper concealment, such as from dim light – the cat has low-light vision). The entry does mention that "[c]ats prefer to sneak up on their prey".
[Edit]: Ah, longspear is there, but it apparently grants no benefit over spear.Last edited by Greenish; 2013-12-07 at 10:46 AM.
Quotes:Praise for avatar may be directed to Derjuin.Spoiler
-
2013-12-07, 10:54 AM (ISO 8601)
- Join Date
- May 2007
- Location
- The Land of Cleves
- Gender
Re: Commoner vs. Cat - A Mathematical Analysis
What happens if the commoner tries to grapple the cat? And are you counting attacks of opportunity when the cat attempts to enter the commoner's space?
Time travels in divers paces with divers persons.
—As You Like It, III:ii:328
Chronos's Unalliterative Skillmonkey Guide
Current Homebrew: 5th edition psionics
-
2013-12-07, 10:56 AM (ISO 8601)
- Join Date
- Feb 2013
Re: Commoner vs. Cat - A Mathematical Analysis
That's actually not too difficult. In the class "Commoner" and "Cat" there is a variable called "Attack" that represents the attack bonus they receive. Right now the Cat's bonus is set to +4 (+2 dex, +2 size), while the commoners is 0.
Originally Posted by Greenish
As for hide/move silently, that is far beyond the scope of the hour or two I had to work on this. Would be neat to try and factor in though.
I barely understand the grapple rules as written, let alone how to encapsulate them in a program. Although this is probably a great chance to learn.
Good catch about the tiny creature/lack of reach. I'll have to update it to allow the commoner an AoO.Last edited by TripleD; 2013-12-07 at 11:01 AM.
-
2013-12-07, 10:57 AM (ISO 8601)
- Join Date
- Feb 2005
- Gender
Re: Commoner vs. Cat - A Mathematical Analysis
I thought the core of the Cat vs. Commoner meme was that the Cat would almost always get a surprise round vs. the commoner because of its stealth scores, letting it open with a free attack, then roll into a full attack next round. Your script doesn't seem to allow for the possibility of the battle starting at a distance of less than 30ft.
EDIT: Ah, so it was just a matter of time to code rather than an intentional/accidental omission. Still very neat, and shows that even in the best circumstances. Mr. Whiskers still has almost a 30% chance to kill a man.Last edited by The Glyphstone; 2013-12-07 at 10:58 AM.
NOW COMPLETE: Let's Play Starcraft II Trilogy:
Hell, It's About Time: Wings of Liberty
Does This Mutation Make Me Look Fat: Heart of the Swarm
My Life For Aiur? I Barely Know 'Er: Legacy of the Void
-
2013-12-07, 11:02 AM (ISO 8601)
- Join Date
- Feb 2010
- Location
- Finland
Re: Commoner vs. Cat - A Mathematical Analysis
The commoner, should she win the initiative, could ready an attack against the cat, thus getting two attacks in before the cat can close to melee. After all, she has more than 2 Int, unlike the cat.
For that matter, the cat is a Tiny creature, with reach of 0 ft. It would therefore have to enter the commoner's square to attack, provoking an AoO. So, reach weapon or no reach weapon, if the commoner wins the initiative, she gets two attacks.
I can imagine it'd be tricky alright.
[Edit]: I can't actually remember (or parse) right now whether entering the opponent's square provokes on it's own. Regardless, since the cat needs to enter the commoner's square, it can't just 5' step from where the commoner hit it with reach weapon, but would actually have to provoke an AoO.Last edited by Greenish; 2013-12-07 at 11:05 AM.
Quotes:Praise for avatar may be directed to Derjuin.Spoiler
-
2013-12-07, 11:02 AM (ISO 8601)
- Join Date
- Feb 2013
Re: Commoner vs. Cat - A Mathematical Analysis
Yep. Although thanks to +2 dex, the cat still catches the commoner flatfooted more often than not.
I thought about that possibility, but I decided against it because the goal is to maximize the wins by the commoner. The cat only has 2hp, so the issue is just being able to beat the cat's 14 AC. In that case I figured the +2 bonus from charging was worth more than an extra attack given by AoO.Last edited by TripleD; 2013-12-07 at 11:09 AM. Reason: Spelling Correction
-
2013-12-07, 11:03 AM (ISO 8601)
- Join Date
- Feb 2005
- Gender
Re: Commoner vs. Cat - A Mathematical Analysis
NOW COMPLETE: Let's Play Starcraft II Trilogy:
Hell, It's About Time: Wings of Liberty
Does This Mutation Make Me Look Fat: Heart of the Swarm
My Life For Aiur? I Barely Know 'Er: Legacy of the Void