Skip to content

Instantly share code, notes, and snippets.

@fab1an
Created March 11, 2012 16:48
Show Gist options
  • Save fab1an/2017105 to your computer and use it in GitHub Desktop.
Save fab1an/2017105 to your computer and use it in GitHub Desktop.
Minecraft Enchantments simulator
DEBUG = false
ROUNDS = 100000
DUMP_FILE = "stats_enchantment.marshal"
# #
# #
# ##### DATA #
# #
# #
# Combos:
@combos = []
@combos = [:fortune, :efficiency]
@combos = [:fortune, :efficiency]
# Items
@items = {}
@items[:tool] = [:sword,:bow,:pickaxe_shovel_axe]
@items[:armor] = [:helmet,:chestplate_leggins,:boots]
# Enchantability
@enchantabilites = {}
@enchantabilites[:wooden] = {:tool => 15}
@enchantabilites[:leather] = {:armor => 15}
@enchantabilites[:stone] = {:tool => 5}
@enchantabilites[:iron] = {:armor => 9, :tool => 14}
@enchantabilites[:chain] = {:armor => 12}
@enchantabilites[:diamond] = {:armor => 10, :tool => 10}
@enchantabilites[:gold] = {:armor => 25, :tool => 22}
# conflicting enchantments
@conflicts = []
@conflicts << [:protection,:fire_protection,:blast_protection,:projectile_protection]
@conflicts << [:sharpness,:smite,:bane_of_arthropods]
@conflicts << [:silk_touch,:fortune]
# possible enchantments: armor
@enchantments = {}
@enchantments[:protection] = [:helmet,:chestplate_leggins,:boots]
@enchantments[:fire_protection] = [:helmet,:chestplate_leggins,:boots]
@enchantments[:feather_fall] = [:boots]
@enchantments[:blast_protection] = [:helmet,:chestplate_leggins,:boots]
@enchantments[:projectile_protection] =[:helmet,:chestplate_leggins,:boots]
@enchantments[:respiration] = [:helmet]
@enchantments[:aqua_affinity] = [:helmet]
# possible enchantments: sword
@enchantments[:sharpness] = [:sword]
@enchantments[:smite] = [:sword]
@enchantments[:bane_of_arthropods] = [:sword]
@enchantments[:knockback] = [:sword]
@enchantments[:fire_aspect] = [:sword]
@enchantments[:looting] = [:sword]
# epossible enchantments: bow
@enchantments[:power] = [:bow]
@enchantments[:punch] = [:bow]
@enchantments[:flame] = [:bow]
@enchantments[:infinity] = [:bow]
# possible enchantments: tool
@enchantments[:efficiency] = [:pickaxe_shovel_axe]
@enchantments[:silk_touch] = [:pickaxe_shovel_axe]
@enchantments[:unbreaking] = [:pickaxe_shovel_axe]
@enchantments[:fortune] = [:pickaxe_shovel_axe]
# enchantmentlevels: armor
@enchantmentsLevels = {}
@enchantmentsLevels[:protection] = [[1,21],[17,37],[33,53],[49,69],[]]
@enchantmentsLevels[:fire_protection] = [[10,22],[18,30],[26,38],[34,46],[]]
@enchantmentsLevels[:feather_fall] = [[5,15],[11,21],[17,27],[23,33],[]]
@enchantmentsLevels[:blast_protection] = [[5,17],[13,25],[21,33],[29,41],[]]
@enchantmentsLevels[:projectile_protection] = [[3,18],[9,24],[15,30],[21,36],[]]
@enchantmentsLevels[:respiration] = [[10,40],[20,50],[30,60],[],[]]
@enchantmentsLevels[:aqua_affinity] = [[1,41],[],[],[],[]]
# enchantmentlevels: sword
@enchantmentsLevels[:sharpness] = [[1,21],[17,37],[33,53],[49,69],[65,85]]
@enchantmentsLevels[:smite] = [[5,25],[13,33],[21,41],[29,49],[37,57]]
@enchantmentsLevels[:bane_of_arthropods] = [[5,25],[13,33],[21,41],[29,49],[37,57]]
@enchantmentsLevels[:knockback] = [[5,55],[25,75],[],[],[]]
@enchantmentsLevels[:fire_aspect] = [[10,60],[30,80],[],[],[]]
@enchantmentsLevels[:looting] = [[20,70],[32,82],[44,94],[],[]]
# enchantmentlevels: bow
@enchantmentsLevels[:power] = [[1,16],[11,26],[21,36],[31,46],[41,56]]
@enchantmentsLevels[:punch] = [[12,37],[32,57],[],[],[]]
@enchantmentsLevels[:flame] = [[20,50],[],[],[],[]]
@enchantmentsLevels[:infinity] = [[20,50],[],[],[],[]]
# enchantmentlevels: tool
@enchantmentsLevels[:efficiency] = [[1,51],[16,66],[31,81],[46,96],[61,111]]
@enchantmentsLevels[:silk_touch] = [[25,75],[],[],[],[]]
@enchantmentsLevels[:unbreaking] = [[5,55],[15,65],[25,75],[],[]]
@enchantmentsLevels[:fortune] = [[20,70],[32,82],[44,94],[],[]]
# enchantmentsWeights: armor
@enchantmentWeights = {}
@enchantmentWeights[:protection] = 10
@enchantmentWeights[:fire_protection] = 5
@enchantmentWeights[:feather_fall] = 5
@enchantmentWeights[:blast_protection] = 2
@enchantmentWeights[:projectile_protection] = 5
@enchantmentWeights[:respiration] = 2
@enchantmentWeights[:aqua_affinity] = 2
# enchantmentWeights: sword
@enchantmentWeights[:sharpness] = 10
@enchantmentWeights[:smite] = 5
@enchantmentWeights[:bane_of_arthropods] = 5
@enchantmentWeights[:knockback] = 5
@enchantmentWeights[:fire_aspect] = 2
@enchantmentWeights[:looting] = 2
# enchantmentWeights: bow
@enchantmentWeights[:power] = 10
@enchantmentWeights[:punch] = 2
@enchantmentWeights[:flame] = 2
@enchantmentWeights[:infinity] = 1
# enchantmentWeights: tool
@enchantmentWeights[:efficiency] = 10
@enchantmentWeights[:silk_touch] = 1
@enchantmentWeights[:unbreaking] = 5
@enchantmentWeights[:fortune] = 2
# #
# #
# ##### CLASSES #
# #
# #
class Stats
def initialize
@levels = []
end
def add(level, enchantments)
@levels[level - 1] = {} if @levels[level - 1].nil?
names = enchantments.map{ |e| e[:name]}
names.sort {|a,b| a.to_s <=> b.to_s}
oldCount = @levels[level - 1][names];
oldCount = 0 if oldCount.nil?
@levels[level - 1][names] = oldCount + 1
end
def highestChanceOf(enchantmentNames, min)
bestWeightedPercentage = 0
bestLevel = 0
percentAtBestLevel = 0
bestOccurences = 0
heighestPercentage = 0
heighestPercentageLevel = 0
heighestPercentageOccurences = 0
puts "want #{enchantmentNames.inspect} #{min}" if DEBUG
1.upto(50) do |level|
occurences = @levels[level - 1]
occurences = {} if occurences.nil?
occurencesOfCountAtLevel = 0
occurences.each do |eList,occ|
matchingNames = false
if (!enchantmentNames.empty?)
if (enchantmentNames.all? {|name| eList.include? name})
matchingNames = true
end
else
matchingNames = true
end
if (matchingNames && (eList.length >= min))
puts "#{level} matches #{eList.inspect} #{occ}" if DEBUG
occurencesOfCountAtLevel = occurencesOfCountAtLevel + occ
else
puts "no match #{eList.inspect} #{occ}" if DEBUG
end
end
next if occurencesOfCountAtLevel == 0
percent = (100 * occurencesOfCountAtLevel.to_f) / (ROUNDS.to_f)
neededEnchants = (100 / percent)
# weight percentage by dividing it through the xp needed
weightedPercentage = percent / xpForLevel(level)
# weight percentage by diving it through the enchants needed
weightedPercentage = weightedPercentage / neededEnchants
puts "L #{level} percent #{percent} weighted #{weightedPercentage}" if DEBUG
if (weightedPercentage > bestWeightedPercentage)
bestWeightedPercentage = weightedPercentage
percentAtBestLevel = percent
bestLevel = level
bestOccurences = occurencesOfCountAtLevel
end
if (percent > heighestPercentage)
heighestPercentage = percent
heighestPercentageLevel = level
heighestPercentageOccurences = occurencesOfCountAtLevel
end
end
puts "highest %: #{heighestPercentage} @ L #{heighestPercentageLevel} (#{heighestPercentageOccurences}), chosen %: #{percentAtBestLevel} @ L #{bestLevel} (#{bestOccurences})" if DEBUG
{:percentage => percentAtBestLevel, :level => bestLevel}
end
end
# #
# #
# ##### ALGORITHMS #
# #
# #
def xpForLevel(level)
xp = 0
1.upto(level) do |l|
if (level == 1)
xp = xp + 7
else
if (l % 2 == 0)
# even
xp = xp + 7 + l / 2 * 3 + (l / 2 - 1) * 4
else
# odd
xp = xp + 7 + (l - 1) / 2 * 3 + (l - 1) / 2 *4
end
end
end
xp
end
def prettify(enchantment)
return prettyString(enchantment[:name]) + " " + enchantment[:level].to_s
end
def prettyString(s)
s.to_s.downcase.gsub(/_/," ").gsub(/\b[a-z]/) { |a| a.upcase }
end
# two dice
def triangularRandom(limit)
(rand(limit).to_f/2 + rand(limit).to_f/2)
end
def getAllCombinations
# tools,armors
allCombinations = []
@items.keys.each do |type|
# run through all enchantable items
items = @items[type]
items.each do |item|
# valid materials
materials = []
if item == :bow
materials << :wooden
else
materials = @enchantabilites.keys.select {|material| @enchantabilites[material].include?(type)}
end
materials.each do |material|
allCombinations << {:material => material, :item => item}
end
end
end
allCombinations
end
def modifiedEnchantLevel(level,enchantability)
puts "calculating modification for level #{level}, enchantability #{enchantability}" if DEBUG
modifiedLevel = level.to_f + triangularRandom(enchantability+1) + 1
modifier = triangularRandom(0)*0.5 + 0.75
finalLevel = (modifiedLevel * modifier).round
finalLevel
end
@cache = {}
def possibleEnchantments(item, modifiedlevel)
#cachedResult = @cache[{:item => item, :modLevel => modifiedlevel}]
#if !cachedResult.nil?
# cachedResult = Array.new(cachedResult)
#end
matchingEs = @enchantments.keys.select {|key| @enchantments[key].include?(item)}
withLevels = []
matchingEs.each do |e|
level = 5
@enchantmentsLevels[e].reverse.each do |rangeMin,rangeMax|
if !rangeMin.nil?
if (modifiedlevel >= rangeMin && modifiedlevel <= rangeMax)
break
end
end
level = level - 1
end
if (level != 0)
enchantment = {}
enchantment[:name] = e
enchantment[:level] = level
withLevels << enchantment
end
end
#@cache[{:item => item, :modLevel => modifiedlevel}] = Array.new(withLevels)
withLevels
end
def chooseE(enchantments)
pool = []
enchantments.each do |e|
@enchantmentWeights[e[:name]].times {pool << e}
end
pool[rand(pool.size)]
end
def conflictsFor(enchantment_name)
conflicts = @conflicts.select {|c| c.include?(enchantment_name)}
conflicts = conflicts.flatten.uniq - [enchantment_name]
conflicts
end
def enchant(item,enchantability,level)
chosenEnchantments = []
# get modified level
modifiedLevel = modifiedEnchantLevel(level, enchantability)
# get possible enchantments
possibleEs = possibleEnchantments(item, modifiedLevel)
while(!possibleEs.empty?) do
# 1. pick an enchantment
puts "possible Es: #{possibleEs.inspect}" if DEBUG
chosenE = chooseE(possibleEs)
puts "chosen #{chosenE.inspect}" if DEBUG
chosenEnchantments << chosenE
# 2. remove this and conflicting
possibleEs.delete(chosenE)
possibleEs = possibleEs.reject do |e|
conflictsFor(chosenE[:name]).include?(e[:name])
end
puts "after removal of conflicting: #{possibleEs.inspect}" if DEBUG
# 3. divide mod-level in half, rounded down
modifiedLevel = (modifiedLevel / 2.0).floor
# 4. With probability (modified level + 1) / 50, keep going
r = rand()
p = (modifiedLevel + 1)/50.0
break if r > p
puts "could pick another one" if DEBUG
end
chosenEnchantments
end
def enchantAll
allCombinations = getAllCombinations()
puts "calculating #{allCombinations.size} material/item combinations, using #{ROUNDS} runs for each level,"
total = allCombinations.size * 50 * ROUNDS
finished = 0
puts "totaling #{total} enchants:"
lastStr = nil
# per combination[level]: enchantmentNames => count
allStats = {}
allCombinations.each do |c|
# create Stats for material/item combination
allStats[c] = Stats.new
type = @items.keys.find {|t| @items[t].include?(c[:item])}
enchantability = @enchantabilites[c[:material]][type]
puts "* enchanting #{50*ROUNDS} #{prettyString(c[:material])} #{prettyString(c[:item])}, enchantability: #{enchantability}"
1.upto(50).each do |level|
ROUNDS.times do
enchantments = []
# redo value falls out of range for everything
while (enchantments.empty?)
enchantments = enchant(c[:item], enchantability, level)
end
# debug: output enchantment
pretty = enchantments.map {|e| prettify(e)}.sort if DEBUG
puts " * enchanted Level #{level} #{prettyString(c[:material])} #{prettyString(c[:item])}: #{pretty.join(", ")}" if DEBUG
# statistics/counting:
allStats[c].add(level, enchantments)
# result percentage
finished = finished + 1
promille = (1000 * finished.to_f) / total / 10
percentageStr = ("%3.1f" % promille).to_s
if (percentageStr != lastStr)
puts "* #{percentageStr}% done"
lastStr = percentageStr
STDOUT.flush
end
end
end
end
allStats
end
def showStats(stats)
allCombinations = getAllCombinations()
# output statistics:
puts "calculated #{allCombinations.size} material/item combinations, using #{ROUNDS} runs for each level,"
puts "totaling #{ROUNDS * 50 * allCombinations.size} enchants:"
puts ""
# tools,armors
@items.keys.each do |type|
# run through all enchantable items
items = @items[type]
items.each do |item|
# valid materials
materials = []
if item == :bow
materials << :wooden
else
materials = @enchantabilites.keys.select {|material| @enchantabilites[material].include?(type)}
end
materials.sort! {|a,b| a.to_s <=> b.to_s}
puts " * #{prettyString(item)}:"
puts ""
puts " highest chances of getting <count> or more enchantments: (% at / optimal level)"
puts ""
puts "".ljust(39) + materials.map{|m| m.to_s.center(20," ") }.join()
puts "".ljust(39) + "".ljust(20*materials.size,"_")
1.upto(4) do |count|
# map materials to the stats of the combination (1 item, many materials)
temp = materials.map{|m| stats[{:material => m,:item => item}]}
temp = temp.map do |stat|
chance = stat.highestChanceOf([], count)
percentString = "%3.1f" % chance[:percentage] + "% / L "
percentString = percentString.rjust(10)
percentString + ("%2d" % chance[:level]).rjust(2)
end
puts "#{count.to_s} (or more) |".rjust(39) + temp.map{|c| c.center(20, " ")}.join()
end
puts ""
puts ""
puts " highest chances of getting specific enchantments: (% at / optimal level)"
puts ""
puts "".ljust(39) + materials.map{|m| m.to_s.center(20," ") }.join()
puts "".ljust(39) + "".ljust(20*materials.size,"_")
matchingEs = @enchantments.keys.select {|key| @enchantments[key].include?(item)}
matchingEs.sort {|a,b| a.to_s <=> b.to_s}
matchingEs.each do |e|
temp = materials.map{|m| stats[{:material => m,:item => item}]}
temp = temp.map do |stat|
chance = stat.highestChanceOf([e],1)
percentString = "%3.1f" % chance[:percentage] + "% / L "
percentString = percentString.rjust(10)
percentString + ("%2d" % chance[:level]).rjust(2)
end
eString = " " + prettyString(e.to_s)
puts eString.ljust(37) + " |" + temp.map{|c| c.center(20, " ")}.join()
end
puts ""
puts ""
puts ""
end
end
end
stats = nil
if File.exist?(DUMP_FILE)
File.open(DUMP_FILE,'r') do|file|
stats = Marshal.load(file)
end
else
stats = enchantAll()
File.open(DUMP_FILE,'w') do|file|
Marshal.dump(stats, file)
end
end
showStats(stats)
Marshalled Ruby Data for 100.000 runs: http://dl.dropbox.com/u/168982/stats_enchantment.marshal
optimal level is chosen by
1. dividing the percentage through the XP needed for the level
2. dividing the result through the numbers of enchants needed with this percentage to gain a 100% chance. (5 for 22% for example)
calculated 26 material/item combinations, using 100000 runs for each level,
totaling 130000000 enchants:
* Helmet:
highest chances of getting <count> or more enchantments: (% at / optimal level)
chain diamond gold iron leather
____________________________________________________________________________________________________
1 (or more) | 100.0% / L 1 100.0% / L 1 100.0% / L 1 100.0% / L 1 100.0% / L 1
2 (or more) | 9.5% / L 1 8.5% / L 1 16.2% / L 1 7.9% / L 1 10.9% / L 1
3 (or more) | 6.0% / L 26 5.6% / L 25 1.5% / L 1 6.1% / L 28 0.5% / L 1
4 (or more) | 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0
highest chances of getting specific enchantments: (% at / optimal level)
chain diamond gold iron leather
____________________________________________________________________________________________________
Fire Protection | 5.8% / L 1 16.3% / L 6 16.0% / L 1 15.5% / L 6 9.8% / L 1
Blast Protection | 8.5% / L 1 8.6% / L 1 8.1% / L 1 8.4% / L 1 8.5% / L 1
Projectile Protection | 24.4% / L 1 25.5% / L 1 21.3% / L 1 25.9% / L 1 23.4% / L 1
Respiration | 4.1% / L 1 11.9% / L 6 13.3% / L 1 13.1% / L 7 7.0% / L 1
Aqua Affinity | 16.6% / L 1 17.2% / L 1 16.6% / L 1 17.2% / L 1 16.0% / L 1
Protection | 50.3% / L 1 52.3% / L 1 42.2% / L 1 53.4% / L 1 46.8% / L 1
* Chestplate Leggins:
highest chances of getting <count> or more enchantments: (% at / optimal level)
chain diamond gold iron leather
____________________________________________________________________________________________________
1 (or more) | 100.0% / L 1 100.0% / L 1 100.0% / L 1 100.0% / L 1 100.0% / L 1
2 (or more) | 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0
3 (or more) | 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0
4 (or more) | 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0
highest chances of getting specific enchantments: (% at / optimal level)
chain diamond gold iron leather
____________________________________________________________________________________________________
Fire Protection | 6.6% / L 1 16.2% / L 5 18.2% / L 1 17.9% / L 6 11.0% / L 1
Blast Protection | 9.5% / L 1 9.6% / L 1 9.2% / L 1 9.4% / L 1 9.6% / L 1
Projectile Protection | 27.6% / L 1 28.4% / L 1 24.1% / L 1 28.8% / L 1 26.0% / L 1
Protection | 56.3% / L 1 58.4% / L 1 48.5% / L 1 59.8% / L 1 53.4% / L 1
* Boots:
highest chances of getting <count> or more enchantments: (% at / optimal level)
chain diamond gold iron leather
____________________________________________________________________________________________________
1 (or more) | 100.0% / L 1 100.0% / L 1 100.0% / L 1 100.0% / L 1 100.0% / L 1
2 (or more) | 9.0% / L 1 7.9% / L 1 15.8% / L 1 7.0% / L 1 10.4% / L 1
3 (or more) | 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0
4 (or more) | 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0
highest chances of getting specific enchantments: (% at / optimal level)
chain diamond gold iron leather
____________________________________________________________________________________________________
Fire Protection | 5.5% / L 1 15.8% / L 6 15.4% / L 1 15.3% / L 6 9.3% / L 1
Blast Protection | 7.7% / L 1 7.6% / L 1 7.9% / L 1 7.4% / L 1 7.7% / L 1
Projectile Protection | 22.8% / L 1 23.5% / L 1 20.3% / L 1 24.1% / L 1 21.9% / L 1
Feather Fall | 25.8% / L 1 24.7% / L 1 31.4% / L 1 23.6% / L 1 27.4% / L 1
Protection | 47.1% / L 1 49.1% / L 1 40.8% / L 1 50.1% / L 1 44.0% / L 1
* Sword:
highest chances of getting <count> or more enchantments: (% at / optimal level)
diamond gold iron stone wooden
____________________________________________________________________________________________________
1 (or more) | 100.0% / L 1 100.0% / L 1 100.0% / L 1 100.0% / L 1 100.0% / L 1
2 (or more) | 7.6% / L 1 14.5% / L 1 10.2% / L 1 5.8% / L 2 10.7% / L 1
3 (or more) | 17.1% / L 50 1.1% / L 1 18.2% / L 50 15.3% / L 49 18.5% / L 50
4 (or more) | 2.6% / L 50 3.3% / L 49 2.8% / L 49 2.2% / L 49 3.0% / L 50
highest chances of getting specific enchantments: (% at / optimal level)
diamond gold iron stone wooden
____________________________________________________________________________________________________
Bane Of Arthropods | 17.0% / L 1 18.8% / L 1 18.1% / L 1 10.1% / L 1 18.5% / L 1
Knockback | 22.3% / L 1 26.3% / L 1 24.3% / L 1 19.7% / L 2 24.7% / L 1
Fire Aspect | 9.5% / L 6 8.9% / L 1 5.0% / L 1 11.0% / L 9 5.5% / L 1
Looting | 12.4% / L 19 10.4% / L 12 11.9% / L 17 12.4% / L 21 11.6% / L 16
Sharpness | 49.6% / L 1 41.5% / L 1 45.1% / L 1 70.7% / L 1 44.2% / L 1
Smite | 17.1% / L 1 18.8% / L 1 18.2% / L 1 10.0% / L 1 18.3% / L 1
* Bow:
highest chances of getting <count> or more enchantments: (% at / optimal level)
wooden
____________________
1 (or more) | 100.0% / L 1
2 (or more) | 4.3% / L 1
3 (or more) | 8.8% / L 32
4 (or more) | 1.1% / L 35
highest chances of getting specific enchantments: (% at / optimal level)
wooden
____________________
Flame | 20.1% / L 16
Infinity | 11.9% / L 18
Power | 96.1% / L 1
Punch | 8.2% / L 1
* Pickaxe Shovel Axe:
highest chances of getting <count> or more enchantments: (% at / optimal level)
diamond gold iron stone wooden
____________________________________________________________________________________________________
1 (or more) | 100.0% / L 1 100.0% / L 1 100.0% / L 1 100.0% / L 1 100.0% / L 1
2 (or more) | 7.7% / L 1 14.4% / L 1 10.3% / L 1 5.6% / L 2 10.7% / L 1
3 (or more) | 16.9% / L 50 18.5% / L 47 18.1% / L 50 15.6% / L 50 18.3% / L 50
4 (or more) | 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0 0.0% / L 0
highest chances of getting specific enchantments: (% at / optimal level)
diamond gold iron stone wooden
____________________________________________________________________________________________________
Silk Touch | 9.2% / L 25 9.0% / L 20 9.0% / L 23 9.4% / L 27 9.5% / L 24
Fortune | 17.0% / L 18 14.7% / L 12 16.2% / L 16 17.2% / L 20 16.5% / L 16
Unbreaking | 33.5% / L 1 40.9% / L 1 37.1% / L 1 18.7% / L 1 38.0% / L 1
Efficiency | 74.3% / L 1 71.7% / L 1 73.2% / L 1 84.6% / L 1 72.7% / L 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment