Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A script for running scenarios against Adventures in Middle-earth Embarkation and Journey rules; See http://takeonrules.com/2018/12/19/diving-further-into-embarkation/
#!/usr/bin/env ruby -wKU
ITERATIONS = 100_000
TERRAINS = %i(easy moderate hard severe daunting)
Guide = Struct.new(:survival_proficiency, :wisdom_modifier, :label) do
def to_s; label; end
end
GUIDES = [
Guide.new(0,-1, "naive level 1"),
Guide.new(2,2, "trained level 1"),
Guide.new(4,3, "focused level 1"),
Guide.new(3,2, "trained level 5"),
Guide.new(6,4, "focused level 5"),
Guide.new(4,2, "trained level 9"),
Guide.new(8,5, "focused level 9"),
Guide.new(5,2, "trained level 13"),
Guide.new(10,5, "focused level 13")
]
# This script runs ITERATIONS number of scenarios for each combination
# of TERRAINS and GUIDES. For each combination of terrain, guide, and number
# between 1 and 12, it outputs the count of Embarkation results for the
# given number and the count of Journey results for the given number.
# Note those numbers are the respective rolls of the Embarkation roll
# and the Journey roll, and are somewhat independent of each other.
#
# See https://takeonrules.com/2018/12/19/diving-further-into-embarkation/ for
# further discussion, and details into the
TERRAINS_PERIL_RATING = {
easy: 1,
moderate: 2,
hard: 3,
severe: 4,
daunting: 5
}
TERRAINS_DC_RATING = {
easy: 7,
moderate: 12,
hard: 17,
severe: 22,
daunting: 27
}
# For the given rolled Embarkation result, modify the
# journey rolls by the value
EMBARKATION_JOURNEY_MODIFICATION = {
1 => 2,
3 => 1,
10 => 1,
12 => 2
}
# The name of the Journey events from Adventures in Middle-earth
JOURNEY_TABLE = {
1 => "1) A Chance Encounter",
2 => "2) Of Herbs and Stewed Rabbit",
3 => "3) An Obstacle",
4 => "4) In Need of Help",
5 => "5) Agents of the Enemy",
6 => "6) The Wonders of Middle-earth",
7 => "7) A Hunt",
8 => "8) A Fine Spot for a Camp",
9 => "9) A Lingering Memory of Times Long Past",
10 => "10) A Place Touched by the Shadow",
11 => "11) The Enemy is Abroad",
12 => "12) Many Meetings? Fly you Fools!"
}
# The name of the Embarkation results from Adventures in Middle-earth
EMBARKATION_TABLE = {
1 => "1) Dark Signs and Evil Portents",
2 => "2) A Fell and Foreboding Start",
3 => "3) The Keen Eyes of the Enemy",
4 => "4) The Wearisome Toil of Many Leagues",
5 => "5) Foul Weather",
6 => "6) Meagre Supplies and Poor Meals",
7 => "7) Feasts Fit for the Kings of Ancient Times",
8 => "8) Fine Weather",
9 => "9) Paths Both Swift and True",
10 => "10) Hidden from the Shadow",
11 => "11) With Hopeful Hearts and Clear Purpose",
12 => "12) From Auspicious Beginnings"
}
# Given a guide and terrain, determine the Embarkation
# rolled value for rules as written
# Returns an Integer between 1 and 12
def embarkation_role_raw(guide:, terrain:)
roll = rand(12) + 1
peril_rating = TERRAINS_PERIL_RATING.fetch(terrain)
survival = guide.survival_proficiency
wisdom = guide.wisdom_modifier / 2
returning_value = roll + survival + wisdom - peril_rating
returning_value = 12 if returning_value > 12
returning_value = 1 if returning_value < 1
return returning_value
end
# Given a guide and terrain, determine the Embarkation
# rolled value for the alternate
# Returns an Integer between 1 and 12
def embarkation_role_alternate_one(guide:, terrain:)
roll = rand(20) + 1
survival = guide.survival_proficiency
wisdom = guide.wisdom_modifier
dc = TERRAINS_DC_RATING.fetch(terrain)
if roll + survival + wisdom >= dc
# If the guide succeeds; d6+6
return rand(6) + 7
else
# If the guide fails, d6
return rand(6) + 1
end
end
# Given the Embarkation result, return a random Journey result
def journey_results(embarkation_result)
roll = rand(12) + 1
modification = EMBARKATION_JOURNEY_MODIFICATION.fetch(embarkation_result, 0)
key = roll + modification
key = 12 if key > 12
key = 1 if key < 1 # Guard condition
return key
end
accumulations = []
AGGREGATE_RESULT_PARAMETERS = [
:terrain,
:guide,
:embarkation_roll,
:embarkation_raw,
:embarkation_alternate,
:journey_roll,
:journey_raw,
:journey_alternate
]
# Responsible for generating output of the relevant parameters
AggregateResult = Struct.new(*AGGREGATE_RESULT_PARAMETERS) do
def to_header_row
AGGREGATE_RESULT_PARAMETERS.map(&:to_s).join("\t")
end
def to_row
AGGREGATE_RESULT_PARAMETERS.map {|param| public_send(param) }.join("\t")
end
end
# Run the scenarios
TERRAINS.each do |terrain|
GUIDES.each do |guide|
journey_results_raw = {}
journey_results_alternate = {}
embarkation_rolls_raw = {}
embarkation_rolls_alternate = {}
(1..ITERATIONS).each do |iteration|
embarkation_raw = embarkation_role_raw(guide: guide, terrain: terrain)
journey_raw = journey_results(embarkation_raw)
journey_results_raw[journey_raw] ||= 0
journey_results_raw[journey_raw] += 1
embarkation_rolls_raw[embarkation_raw] ||= 0
embarkation_rolls_raw[embarkation_raw] += 1
embarkation_alternate_one = embarkation_role_alternate_one(guide: guide, terrain: terrain)
journey_alternate_one = journey_results(embarkation_alternate_one)
journey_results_alternate[journey_alternate_one] ||= 0
journey_results_alternate[journey_alternate_one] += 1
embarkation_rolls_alternate[embarkation_alternate_one] ||= 0
embarkation_rolls_alternate[embarkation_alternate_one] += 1
end
(1..12).each do |index|
accumulations << AggregateResult.new(
terrain,
guide,
EMBARKATION_TABLE.fetch(index),
embarkation_rolls_raw.fetch(index,0),
embarkation_rolls_alternate.fetch(index, 0),
JOURNEY_TABLE.fetch(index),
journey_results_raw.fetch(index, 0),
journey_results_alternate.fetch(index, 0)
)
end
end
end
# Output the results of those scenarios
accumulations.each_with_index do |acc, i|
puts acc.to_header_row if i == 0
puts acc.to_row
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.