McNathanson Posted July 15, 2015 Report Share Posted July 15, 2015 Hey folks I thought those of you interested in Kings of War might have fun playing around with the combat sim I wrote. It's pretty basic and doesn't have a lot of special rules support (just Crushing Blow and Thunderous Charge for now, no Phalanx, etc.). But it will show you a Unit 1 vs Unit 2 combat results distribution, including how many rounds before the loser routed, which is useful info. I'm attaching the code in a .zip and also pasting below if you prefer to just copy/paste into a .py file yourself. Enjoy!Nathan kow-combat-sim-v0.1.zip # Kings of War Combat Simulator version 0.1 # (c) Nathan Heldt-Sheller July 2015 # # Usage: # 1) Download and install Python 2.7.x # 2) From a command prompt execute the script: # python kow-combat-sim-v0.1.py # 3) Choose the KoW Combat Sim option (enter for default) # 4) Choose a number of Trials to run (up to 99999) # 5) Look at the distribution printout to see how many Trials each # unit won, and which round they won in. # # NOTE: Before running a lot of trials, you will want to disable the printouts # except the stats print. Change the "debugmsgs = True" on line ~299 (top of # main() function) to "debugmsgs = False" to disable all the debug prints. # # NOTE: There are a lot of special rules not yet supported... only Crushing Blow # and Thunderous Charge are supported currently. # # NOTE: This may have bugs, I wrote it in just one sitting. It's also my first use # of Python so I'm sure it's ugly code to a real Python programmer... sorry ;) # # NOTE: To change the units that are fighting just edit the info in the script # below (search for "example unit stats"). # # There's also a tool for rolling sequential handfuls of D6 just in case you # want to play around with it. # # Thanks, # Nathan import random import os STEADY = "steady" DISORDERED = "disordered" WAVERED = "wavered" ROUTED = "routed" def rolld6countsuccesses(num_rolls, min_success_val, debugmsgs): # debugmsgs = False if debugmsgs: print "\trolling %d dice looking for %d+..." % \ (num_rolls, min_success_val) success_count = 0 for i in range(num_rolls): die = random.randint(1, 6) if(die >= min_success_val) : success_count += 1 # if debugmsgs: print "die = %d, success_count = %d" % (die, success_count) if debugmsgs: print "\t... %d successes." % success_count return success_count def roll2d6countsuccesses(num_rolls, min_success_val, debugmsgs): # debugmsgs = False success_count = 0 for i in range(num_rolls) : die1 = random.randint(1, 6) die2 = random.randint(1, 6) if(die1 + die2) >= min_success_val : success_count += 1 if(debugmsgs): print "die1 = %d, die2 = %d, success_count = %d" % \ (die1, die2, success_count) return success_count def prompt_for_int(prompt, min_input, max_input, default, debugmsgs): # debugmsgs = True # check args if(default < min_input or default > max_input): raise ValueError(\ "bad args, default %d not within min/max range of %d - %d" % \ (default, min_input, max_input)) # prompt user x = raw_input(prompt + " [%d]: " % default) # if user enters '' assign default if x == '': try: retval = int(default) except: raise ValueError("bad args, default not an int") # else assign the input to retval if its an int else: try: retval = int(x) # check range if(retval < min_input or retval > max_input): raise ValueError("bad input, value out of range") except: if x == "q": raise ValueError("'q' => quit...") else: raise ValueError("bad input: not 'q', and not an int") return retval def d6rollersim(dice, debugmsgs): # debugmsgs = True trials = 1 rolls = 10 value_needed = 4 successes = 2 # start with 2 so that the starting default rolls is 2 if dice == 1: print "Rolling 1 die at a time." else: print "Rolling %d dice at a time." % dice while True : try: # dice = prompt_for_int("Roll 1 or 2 dice at a time?", 1, 2, dice, debugmsgs) rolls = prompt_for_int("Number of rolls to attempt?", 0, 9999, successes, debugmsgs) value_needed = prompt_for_int("Roll needed for success?", 1, 12, value_needed, debugmsgs) trials = prompt_for_int("Trials to average?", 1, 9999, trials, debugmsgs) except ValueError as e: print e #"prompt_for_int() raised an exception '%s'; breaking..." % (e) break print "Starting %d trials, rolling %d dice needing %d or better" \ % (trials, rolls, value_needed) successes = 0 for i in range(trials): # successes += rolld6countsuccesses(rolls, value_needed, debugmsgs) if(dice == 1): successes += rolld6countsuccesses(rolls, value_needed, debugmsgs) elif(dice == 2): successes += roll2d6countsuccesses(rolls, value_needed, debugmsgs) else: print("Unsupported number of dice, goodbye!") break successes /= trials; print "\tAverage successes in {0} trials = {1} out of {2}".format\ (trials, successes, rolls) def kowfightcombat(tohit, towound, attacks, debugmsgs): # debugmsgs = True if debugmsgs: print "\tattacking %d times needing %d to hit and %d to wound" \ % (attacks, tohit, towound) hits = rolld6countsuccesses(attacks, tohit, debugmsgs) wounds = rolld6countsuccesses(hits, towound, debugmsgs) return wounds def kowcalculatewounds(attacker, defender, melee, defense, attacks, cb, tc, nerve_state, debugmsgs): # debugmsgs = True tohit = max(1, melee[attacker]) if debugmsgs: print "Attacker = Unit %d; Defender = Unit %d" % \ (attacker + 1, defender + 1) if nerve_state[attacker] == STEADY: if debugmsgs: print "\tapplying thunderous_charge[%d]" % tc[attacker] towound = max(1, defense[defender] - cb[attacker] - tc[attacker]) else: towound = max(1, defense[defender] - cb[attacker]) wounds_dealt = kowfightcombat(tohit, towound, attacks[attacker], debugmsgs) if debugmsgs: print "\tAttacker's charge dealt %d wounds" % wounds_dealt return wounds_dealt def kownervetest(waver, route, dmg, debugmsgs): # debugmsgs = True nerve_roll = random.randint(1, 6) + random.randint(1, 6) if nerve_roll == 12: if debugmsgs: print "\tnerve roll %d (%d total): we are doomed!" % \ (nerve_roll, nerve_roll + dmg) return WAVERED if nerve_roll == 2: if debugmsgs: print "\tnerve roll %d (%d total): hold your ground!" % \ (nerve_roll, nerve_roll + dmg) return STEADY if (dmg + nerve_roll) >= route: if debugmsgs: print "\tnerve roll %d (%d total) => " % \ (nerve_roll, nerve_roll + dmg) + ROUTED return ROUTED if (dmg + nerve_roll) >= waver: if debugmsgs: print "\tnerve roll %d (%d total) => " % \ (nerve_roll, nerve_roll + dmg) + WAVERED return WAVERED if debugmsgs: print "\tnerve roll %d (%d total) => " % \ (nerve_roll, nerve_roll + dmg) + STEADY return STEADY def kowdocharge(attacker, defender, melee, defense, attacks, cb, tc, dmg, nerve_state, waver, route, debugmsgs): # debugmsgs = True if debugmsgs: print # blank line to separate each charge wounds_dealt = kowcalculatewounds(attacker, defender, melee, defense, attacks, cb, tc, nerve_state, debugmsgs) dmg[defender] += wounds_dealt if wounds_dealt > 0: nerve_test = kownervetest(waver[defender], route[defender], dmg[defender], debugmsgs) if nerve_test == STEADY: nerve_state[defender] = DISORDERED elif nerve_test == WAVERED: nerve_state[defender] = WAVERED elif nerve_test == ROUTED: nerve_state[defender] = ROUTED else: nerve_state[defender] = STEADY if debugmsgs: print "Unit %d: dmg = %d; nerve state = " % (defender + 1, dmg[defender]) + nerve_state[defender] def kowprintsimstats(trials, stats, max_rounds, debugmsgs): debugmsgs = False # the debugmsgs for this function are confusing; disable unit_wins = [] for i in range(max_rounds +1): unit_wins.append([0,0]) for i in range(trials): if debugmsgs: print stats[i] final_combat_round = stats[i][0] if debugmsgs: print "\nTrial %d went %d rounds" % (i, final_combat_round) if(stats[i][1] == ROUTED): unit_wins[final_combat_round][1] += 1 else: unit_wins[final_combat_round][0] += 1 print "\nKoW Combat Simulation Results" print "(Distribution of Wins by Combat Round and Unit):" print ' {0:<15s} {1:^15s} {2:<15s}'.format("Rounds\n Fought", "Unit 1 win", "Unit 2 win") for i in range(1,max_rounds+1): combat_round = i unitAwins = unit_wins[i][0] unitBwins = unit_wins[i][1] print ' {0:<15n} {1:<15n} {2:<15n}'.format(combat_round, unitAwins, unitBwins) print def kowcombatsim(trials, debugmsgs): # debugmsgs = True try: trials = prompt_for_int("Trials", 1, 99999, trials, debugmsgs) except ValueError: trials = 1 print "KoW combat using %d trials:" % trials # example unit stats for 10 Knights vs 40 Shield Wall # copy/paste these numbers to create a different matchup # note that the left value is always the first attacker, and the right # value is the first defender melee = [3,4] # melee value for [Unit 1, Unit 2] defense = [5,4] # etc attacks = [16,25] waver = [14,20] route = [16,22] crushing_blow = [0,0] thunderous_charge = [2,0] damage = [0,0] # for recording damage taken... leave at zero unless you # want to start the Trial with a damaged unit # start the trials stats = [] max_rounds = 1 for i in range(trials): combat_round = 0 nerve_state = [STEADY, STEADY] damage = [0,0] while True: # for human readability, round '1' is the first round combat_round += 1 if debugmsgs: print "\nTRIAL %d - COMBAT ROUND %d" % (i+1, combat_round) # UNIT 1's TURN attacker = 0 defender = 1 # if not wavered, 0 charges 1 if nerve_state[attacker] != WAVERED: kowdocharge(attacker, defender, melee, defense, attacks, crushing_blow, thunderous_charge, damage, nerve_state, waver, route, debugmsgs) if nerve_state[defender] == ROUTED: break # if no charge, reset defender nerve else: nerve_state[defender] = STEADY # UNIT 2's TURN attacker = 1 defender = 0 # if not wavered, 1 countercharges 0 if nerve_state[attacker] != WAVERED: kowdocharge(attacker, defender, melee, defense, attacks, crushing_blow, thunderous_charge, damage, nerve_state, waver, route, debugmsgs) if nerve_state[defender] == ROUTED: break # if no charge, reset defender nerve else: nerve_state[defender] = STEADY # trial over, record stats if combat_round > max_rounds: max_rounds = combat_round trial_results = [combat_round, nerve_state[0], damage[0], nerve_state[1], damage[1]] if debugmsgs: print "Recording Trial Results:", trial_results stats.append(trial_results) # simulation complete, print stats kowprintsimstats(trials, stats, max_rounds, debugmsgs) def main(): # debugmsgs = False # use these "top level" vars to set for the whole script debugmsgs = True # use these "top level" vars to set for the whole script # NOTE: disable HERE to run a lot of Trials without printing forever! os.system('clear') print "Welcome to Nathan's KoW combat sim v0.1; enter 'q' at any prompt to quit." mode = 1 while mode > 0: print "1) Simulate a KoW combat" print "2) Roll handfuls of D6" try: mode = prompt_for_int("Choose a mode, or 'q' to quit", 1, 2, 1, debugmsgs) except ValueError as e: print e mode = 0 # exit if mode == 1: trials = 1 kowcombatsim(trials, debugmsgs) elif mode == 2: d6rollersim(1, debugmsgs) print "quitting, goodbye!" if __name__ == "__main__": main() Quote Link to comment Share on other sites More sharing options...
Steel Angel Posted July 15, 2015 Report Share Posted July 15, 2015 Blink blink!? Hmmmm do you have one in Layman terms? Quote Link to comment Share on other sites More sharing options...
McNathanson Posted July 15, 2015 Author Report Share Posted July 15, 2015 It's really just as easy as running the script (see the "# Usage:" comment at the top). You can Google "installing Python 2.7" and "running a Python script on Windows" and you'll get what you need. After starting the script (step #2 in the Usage instructions), press enter a bunch and you'll see a KoW combat get simulated :) NtK Quote Link to comment Share on other sites More sharing options...
Sammy Posted July 15, 2015 Report Share Posted July 15, 2015 The instructions are at the beginning, you'll need to download Python to run the code. It would be awesome if it were compiled into a sweet UI though, and you append functionality for additional ability variables as they become available. It looks neat though, thanks for putting it together! Quote Link to comment Share on other sites More sharing options...
McNathanson Posted July 15, 2015 Author Report Share Posted July 15, 2015 Thanks Sammy! I think if and when I spend more time on it, it will be to add support for other abilities, and then maybe take .xml input for unit stats. Honestly I'm not sure I'll continue in Python though, I'm not finding it significantly simpler than the same code would be in Java which I'm more familiar with (and which can easily be wrapped in an Andriod UI). But if I instead take the time to write up an HTML interface, I'll keep the backing code in Python. All depends I guess on whether I get into KoW... I'm still waiting for my full copy of the rules to playtest! Quote Link to comment Share on other sites More sharing options...
McNathanson Posted July 15, 2015 Author Report Share Posted July 15, 2015 Updated to fix kownervetest function; see text in above post. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.