crafting_table 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.asc +11 -0
- data.tar.gz.asc +11 -0
- data/.gitignore +0 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +41 -0
- data/LICENSE +14 -0
- data/Rakefile +0 -0
- data/crafting_table.gemspec +19 -0
- data/data/items/thermal_expansion.yml +64 -0
- data/data/items/vanilla.yml +1152 -0
- data/data/recipes/thermal_expansion.yml +97 -0
- data/data/recipes/vanilla.yml +25 -0
- data/data/test/items/vanilla.yml +1149 -0
- data/data/test/recipes/vanilla.yml +25 -0
- data/examples/interactive_table.rb +98 -0
- data/features/item_differentiation.feature +18 -0
- data/features/loading_items.feature +10 -0
- data/features/resolving_recipes.feature +26 -0
- data/features/searching_items.feature +83 -0
- data/features/searching_recipes.feature +42 -0
- data/features/step_definitions/item_manager_steps.rb +88 -0
- data/features/step_definitions/item_steps.rb +23 -0
- data/features/step_definitions/recipe_manager_steps.rb +42 -0
- data/features/support/env.rb +29 -0
- data/lib/crafting_table.rb +19 -0
- data/lib/crafting_table/item.rb +61 -0
- data/lib/crafting_table/item_manager.rb +171 -0
- data/lib/crafting_table/recipe.rb +36 -0
- data/lib/crafting_table/recipe_manager.rb +216 -0
- data/lib/crafting_table/search/damage_search.rb +51 -0
- data/lib/crafting_table/search/fuzzy_name_search.rb +39 -0
- data/lib/crafting_table/search/input_search.rb +47 -0
- data/lib/crafting_table/search/item_id_search.rb +51 -0
- data/lib/crafting_table/search/name_search.rb +71 -0
- data/lib/crafting_table/search/output_search.rb +47 -0
- data/lib/crafting_table/search/search_builder.rb +85 -0
- data/spec/crafting_table/item_manager_spec.rb +421 -0
- data/spec/crafting_table/item_spec.rb +67 -0
- data/spec/crafting_table/recipe_manager_spec.rb +353 -0
- data/spec/crafting_table/recipe_spec.rb +25 -0
- data/spec/crafting_table/search/search_builder.rb +105 -0
- data/spec/crafting_table/search/search_spec.rb +383 -0
- data/spec/spec_helper.rb +6 -0
- metadata +145 -0
- metadata.gz.asc +11 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
require_relative '../../lib/crafting_table'
|
4
|
+
|
5
|
+
ITEM_FILE = 'data/test/items/vanilla.yml'
|
6
|
+
RECIPE_FILE = 'data/test/recipes/vanilla.yml'
|
7
|
+
|
8
|
+
def string_to_boolean(string)
|
9
|
+
|
10
|
+
if string.downcase =~ /yes|y/
|
11
|
+
true
|
12
|
+
elsif string.downcase =~ /no|n/
|
13
|
+
false
|
14
|
+
else
|
15
|
+
raise ArgumentError
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def string_to_range_or_integer(string)
|
21
|
+
if string =~ /^[0-9]+$/
|
22
|
+
string.to_i
|
23
|
+
elsif string =~ /^[0-9]+\.\.[0-9]+$/
|
24
|
+
Range.new(*string.split('..').map(&:to_i))
|
25
|
+
else
|
26
|
+
fail "Could not convert #{ string.inspect } to range or integer."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
require_relative 'crafting_table/item_manager'
|
4
|
+
require_relative 'crafting_table/item'
|
5
|
+
|
6
|
+
require_relative 'crafting_table/recipe_manager'
|
7
|
+
require_relative 'crafting_table/recipe'
|
8
|
+
|
9
|
+
require_relative 'crafting_table/search/search_builder'
|
10
|
+
require_relative 'crafting_table/search/name_search'
|
11
|
+
require_relative 'crafting_table/search/fuzzy_name_search'
|
12
|
+
require_relative 'crafting_table/search/damage_search'
|
13
|
+
require_relative 'crafting_table/search/item_id_search'
|
14
|
+
require_relative 'crafting_table/search/input_search'
|
15
|
+
require_relative 'crafting_table/search/output_search'
|
16
|
+
|
17
|
+
module CraftingTable
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
module CraftingTable
|
4
|
+
|
5
|
+
# A class representing a single item.
|
6
|
+
# An item, in this case, can be any block, tool, loot etc.
|
7
|
+
#
|
8
|
+
# @author Michael Senn <morrolan@morrolan.ch>
|
9
|
+
# @since 0.1
|
10
|
+
class Item
|
11
|
+
attr_reader :name, :item_id, :damage_value
|
12
|
+
|
13
|
+
# Create a new Item.
|
14
|
+
#
|
15
|
+
# @example Spruce Wood
|
16
|
+
# item = Item.new('Spruce Wood', 17, 1)
|
17
|
+
#
|
18
|
+
# @example Glass
|
19
|
+
# item = Item.new('Glass', 20, :any)
|
20
|
+
#
|
21
|
+
# @param [String] name The item's name.
|
22
|
+
# @param [Integer, Symbol] damage_value The item's damage / meta value.
|
23
|
+
# @param [Integer] item_id The item's ID.
|
24
|
+
def initialize(name, item_id, damage_value = 0)
|
25
|
+
@name, @item_id, @damage_value = name, item_id, damage_value
|
26
|
+
end
|
27
|
+
|
28
|
+
# Compare two items for equality.
|
29
|
+
#
|
30
|
+
# Two items are considered equal, if their name, item ID and damage value are equal.
|
31
|
+
#
|
32
|
+
# @param [Item] other Item which to compare for equality.
|
33
|
+
# @return [Boolean] Whether the two items are equal.
|
34
|
+
def ==(other)
|
35
|
+
name == other.name && item_id == other.item_id && damage_value == other.damage_value
|
36
|
+
end
|
37
|
+
|
38
|
+
alias_method(:eql?, :==)
|
39
|
+
|
40
|
+
# Return reasonable hash value of this item.
|
41
|
+
#
|
42
|
+
# @since 0.2
|
43
|
+
#
|
44
|
+
# @return [Integer] Hash of item's name, id and damage value.
|
45
|
+
def hash
|
46
|
+
[name, item_id, damage_value].hash
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return a unique identifier of this item.
|
50
|
+
# The identifier is an array, containing its ID and damage value
|
51
|
+
#
|
52
|
+
# @since 0.2
|
53
|
+
#
|
54
|
+
# @return [Array<Integer>] Unique identifier of this item.
|
55
|
+
def identifier
|
56
|
+
[item_id, damage_value]
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module CraftingTable
|
6
|
+
|
7
|
+
# A class which contains items, and allows to search through them.
|
8
|
+
#
|
9
|
+
# @author Michael Senn <morrolan@morrolan.ch>
|
10
|
+
# @since 0.1
|
11
|
+
class ItemManager
|
12
|
+
attr_reader :items
|
13
|
+
|
14
|
+
# Create a new ItemManager
|
15
|
+
#
|
16
|
+
# @param [Array] items Array of items with which to initialize the item manager.
|
17
|
+
def initialize(items = [])
|
18
|
+
@items = items.to_ary
|
19
|
+
end
|
20
|
+
|
21
|
+
# Add a new item to the internal collection.
|
22
|
+
#
|
23
|
+
# @param [Item] item Item which to add to the collection.
|
24
|
+
# @return [void]
|
25
|
+
def add(item)
|
26
|
+
@items << item
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add new items by reading them from a YAML file.
|
30
|
+
#
|
31
|
+
# @param [String] path The path to the file from which to read items from.
|
32
|
+
# @return [void]
|
33
|
+
def add_from_file(path)
|
34
|
+
YAML.load_file(path).each do |hash|
|
35
|
+
add(Item.new(hash['name'], hash['id'], hash['damage']))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Clear the internal collection of items.
|
40
|
+
# @return [void]
|
41
|
+
def clear
|
42
|
+
@items.clear
|
43
|
+
end
|
44
|
+
|
45
|
+
# Find items by their name.
|
46
|
+
#
|
47
|
+
# @deprecated Use {#find} instead
|
48
|
+
#
|
49
|
+
# @example Search using default parameters.
|
50
|
+
# manager.find_by_name('Stone').map(&:name) #=> ['Stone']
|
51
|
+
#
|
52
|
+
# @example Case-insensitive search, non-exact matching.
|
53
|
+
# manager.find_by_name('stone', exact: false, case_sensitive: false).map(&:name) #=> ['Stone', 'Sandstone', '...']
|
54
|
+
#
|
55
|
+
# @param [String] name The name for which to search.
|
56
|
+
# @param [Hash] options Options which influence the search.
|
57
|
+
# @option options [Boolean] :exact (true) Whether to match names exactly.
|
58
|
+
# @option options [Boolean] :case_sensitive (true) Whether to search case-sensitively.
|
59
|
+
# @return [Array<Item>] Collection of items which matched the search condition.
|
60
|
+
def find_by_name(name, options = {})
|
61
|
+
default_options = { exact: true, case_sensitive: true }
|
62
|
+
default_options.update(options)
|
63
|
+
|
64
|
+
if default_options[:case_sensitive]
|
65
|
+
if default_options[:exact]
|
66
|
+
items.select { |item| item.name == name }
|
67
|
+
else
|
68
|
+
items.select { |item| item.name.include? name }
|
69
|
+
end
|
70
|
+
else
|
71
|
+
if default_options[:exact]
|
72
|
+
items.select { |item| item.name.downcase == name.downcase }
|
73
|
+
else
|
74
|
+
items.select { |item| item.name.downcase.include? name.downcase }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Find items by their ID.
|
80
|
+
#
|
81
|
+
# @deprecated Use {#find} instead
|
82
|
+
#
|
83
|
+
# @example Searching for single ID.
|
84
|
+
# manager.find_by_item_id(17).first.name #=> 'Wood'
|
85
|
+
#
|
86
|
+
# @example Searching for range of IDs.
|
87
|
+
# manager.find_by_item_id(14..16).map(&:name) #=> ['Gold Ore', 'Iron Ore', 'Coal Ore']
|
88
|
+
#
|
89
|
+
# @example Searching for collection of IDs.
|
90
|
+
# manager.find_by_item_id([1, 3, 24]).map(&:name) #=> ['Stone', 'Dirt', 'Sandstone']
|
91
|
+
#
|
92
|
+
# @param [#include?, Integer] id A collection of IDs, or a single ID, for which to search.
|
93
|
+
# @return [Array<Item>] Collection of items which matched the search condition.
|
94
|
+
def find_by_item_id(id)
|
95
|
+
if id.respond_to? :include?
|
96
|
+
items.select { |item| id.include? item.item_id }
|
97
|
+
else
|
98
|
+
items.select { |item| item.item_id == id }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Find items.
|
103
|
+
#
|
104
|
+
# @since 0.3
|
105
|
+
#
|
106
|
+
# @example Searching for name and damage value
|
107
|
+
# results = manager.find do |search|
|
108
|
+
# search.name = 'Wood'
|
109
|
+
# search.exact = false
|
110
|
+
# search.damage_value = 3
|
111
|
+
# end
|
112
|
+
# results.first.name #=> 'Jungle Wood'
|
113
|
+
#
|
114
|
+
# @example Searching for range of item IDs and two specific damage values
|
115
|
+
# results = manager.find do |search|
|
116
|
+
# search.item_id = 1..20
|
117
|
+
# search.damage_value = [1, 3]
|
118
|
+
# end
|
119
|
+
# results.map(&:name) #=> ['Spruce Sapling', 'Jungle Sapling', 'Spruce Wood', 'Jungle Wood', 'Spruce Leaves', 'Jungle Leaves']
|
120
|
+
#
|
121
|
+
# @yieldparam builder [SearchBuilder]
|
122
|
+
# An instance of the SearchBuilder class which allows to easily specify
|
123
|
+
# multiple search conditions.
|
124
|
+
#
|
125
|
+
# @return [Array<Item>] Items which matched the search conditions.
|
126
|
+
def find(&block)
|
127
|
+
builder = Search::SearchBuilder.new
|
128
|
+
yield builder
|
129
|
+
|
130
|
+
builder.searches.inject(items) { |items, search| search.apply_to(items) }
|
131
|
+
end
|
132
|
+
|
133
|
+
# Find items by their damage value.
|
134
|
+
#
|
135
|
+
# @deprecated Use {#find} instead
|
136
|
+
#
|
137
|
+
# @example Searching for single damage value.
|
138
|
+
# manager.find_by_damage_value(2).map(&:name) #=> ['Birch Wood', '...']
|
139
|
+
#
|
140
|
+
# @example Searching for range of damage values.
|
141
|
+
# manager.find_by_damage_value(1..2).map(&:name) #=> ['Spruce Wood', 'Birch Wood', '...']
|
142
|
+
#
|
143
|
+
# @example Searching for collection of damage values.
|
144
|
+
# manager.find_by_damage_value([1, 3]).map(&:name) #=> ['Spruce Wood', 'Jungle Wood', '...']
|
145
|
+
#
|
146
|
+
# @param [#include?, Integer] id A collection of damage values, or a single damage value, for which to search.
|
147
|
+
# @return [Array<Item>] Collection of items which matched the search condition.
|
148
|
+
def find_by_damage_value(damage)
|
149
|
+
if damage.is_a?(Symbol) || !damage.respond_to?(:include?)
|
150
|
+
items.select { |item| item.damage_value == damage }
|
151
|
+
else
|
152
|
+
items.select { |item| damage.include? item.damage_value }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Find items by their identifier.
|
157
|
+
#
|
158
|
+
# @see Item#identifier
|
159
|
+
# @since 0.2
|
160
|
+
#
|
161
|
+
# @param [Array<Integer>] identifier Identifier for which to
|
162
|
+
#search.
|
163
|
+
# @return [Array<Item>] Collection of items which matched
|
164
|
+
# the search condition.
|
165
|
+
def find_by_identifier(identifier)
|
166
|
+
items.select { |item| item.identifier == identifier }
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
module CraftingTable
|
4
|
+
|
5
|
+
# A class representing a single recipe.
|
6
|
+
# A recipe has a name, one to n inputs, and one to n outputs.
|
7
|
+
#
|
8
|
+
# @author Michael Senn <morrolan@morrolan.ch>
|
9
|
+
# @since 0.2
|
10
|
+
class Recipe
|
11
|
+
attr_reader :name, :input, :output
|
12
|
+
|
13
|
+
# Create a new recipe.
|
14
|
+
#
|
15
|
+
# @example Torch
|
16
|
+
# recipe = Recipe.new('Torch',
|
17
|
+
# { Item.new('Coal', 263, 0) => 1,
|
18
|
+
# Item.new('Stick', 280, 0) => 1 },
|
19
|
+
# { Item.new('Torch', 50, 0) => 4 })
|
20
|
+
#
|
21
|
+
# @param [String] name The recipe's name
|
22
|
+
# @param [Hash{Item => Integer}] input
|
23
|
+
# A hash of items and their amounts which are required to craft
|
24
|
+
# the recipe.
|
25
|
+
# @param [Hash{Item => Integer}] output
|
26
|
+
# A hash of items and their amounts which you get when crafting
|
27
|
+
# the recipe.
|
28
|
+
def initialize(name, input, output)
|
29
|
+
@name, @input, @output = name, input, output
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
@@ -0,0 +1,216 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
module CraftingTable
|
4
|
+
|
5
|
+
# A class which contains recipes, and allows to search through them.
|
6
|
+
#
|
7
|
+
# @author Michael Senn <morrolan@morrolan.ch>
|
8
|
+
# @since 0.2
|
9
|
+
class RecipeManager
|
10
|
+
attr_reader :recipes
|
11
|
+
attr_reader :item_manager
|
12
|
+
|
13
|
+
# Create a new RecipeManager
|
14
|
+
#
|
15
|
+
# @param [ItemManager] item_manager ItemManager which contains the items
|
16
|
+
# on which this manager's recipes are based.
|
17
|
+
def initialize(item_manager)
|
18
|
+
@item_manager = item_manager
|
19
|
+
@recipes = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# Add a new recipe to the internal collection.
|
23
|
+
#
|
24
|
+
# @param [Recipe] recipe Recipe which to add to the collection.
|
25
|
+
# @return [void]
|
26
|
+
def add(recipe)
|
27
|
+
@recipes << recipe
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add new recipes by reading them from a YAML file.
|
31
|
+
#
|
32
|
+
# @param [String] path The path to the file from which to read
|
33
|
+
# recipes from.
|
34
|
+
# @return [void]
|
35
|
+
def add_from_file(path)
|
36
|
+
YAML.load_file(path).each do |recipe_hash|
|
37
|
+
raw_input = recipe_hash.fetch('input', {})
|
38
|
+
raw_output = recipe_hash.fetch('output', {})
|
39
|
+
name = recipe_hash.fetch('name', 'UNKNOWN')
|
40
|
+
|
41
|
+
input = {}
|
42
|
+
output = {}
|
43
|
+
raw_input.each do |identifier_string, amount|
|
44
|
+
identifier = identifier_string.split(':').map(&:to_i)
|
45
|
+
item = item_manager.find_by_identifier(identifier).first
|
46
|
+
input[item] = amount
|
47
|
+
end
|
48
|
+
|
49
|
+
raw_output.each do |identifier_string, amount|
|
50
|
+
identifier = identifier_string.split(':').map(&:to_i)
|
51
|
+
item = item_manager.find_by_identifier(identifier).first
|
52
|
+
output[item] = amount
|
53
|
+
end
|
54
|
+
|
55
|
+
@recipes << Recipe.new(name, input, output)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# Clear the internal collection of recipes.
|
61
|
+
# @return [void]
|
62
|
+
def clear
|
63
|
+
@recipes.clear
|
64
|
+
end
|
65
|
+
|
66
|
+
# Find recipes.
|
67
|
+
#
|
68
|
+
# @since 0.3
|
69
|
+
#
|
70
|
+
# @example Searching for name.
|
71
|
+
# results = manager.find do |search|
|
72
|
+
# search.name = 'slab'
|
73
|
+
# search.exact = false
|
74
|
+
# search.case_sensitive = false
|
75
|
+
# end
|
76
|
+
# results.map(&:name)
|
77
|
+
# #=> ['Cobblestone slab', 'Wooden slab', 'Stone slab', '...']
|
78
|
+
#
|
79
|
+
# @example Searching for recipes which result in torch(es).
|
80
|
+
# manager.find do |search|
|
81
|
+
# manager.output = Item.new('Torch', 50)
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# @example Searching for recipes which require oak planks.
|
85
|
+
# results = manager.find do |search|
|
86
|
+
# manager.input = Item.new('Oak Wood Planks', 5)
|
87
|
+
# end
|
88
|
+
# results.map(&:name)
|
89
|
+
# #=> ['Sticks', 'Crafting Table', 'Chest', 'Bed', '...']
|
90
|
+
#
|
91
|
+
# @yieldparam builder [SearchBuilder]
|
92
|
+
# An instance of the SearchBuilder class which allows to easily specify
|
93
|
+
# multiple search conditions.
|
94
|
+
#
|
95
|
+
# @return [Array<Recipe>] Recipes which matched the search conditions.
|
96
|
+
def find(&block)
|
97
|
+
builder = Search::SearchBuilder.new
|
98
|
+
yield builder
|
99
|
+
|
100
|
+
builder.searches.inject(recipes) { |recipes, search| search.apply_to(recipes) }
|
101
|
+
end
|
102
|
+
|
103
|
+
# Find recipes by their name.
|
104
|
+
#
|
105
|
+
# @deprecated Use {#find} instead.
|
106
|
+
#
|
107
|
+
# @example Search using default parameters.
|
108
|
+
# manager.find_by_name('Torch').first.input.map(&:name) #=> ['Wooden Planks', 'Coal']
|
109
|
+
#
|
110
|
+
# @example Case-insensitive search, non-exact matching.
|
111
|
+
# manager.find_by_name('slab').map(&:name) #=> ['Cobblestone Slab', 'Wooden slab', 'Stone slab', '...']
|
112
|
+
#
|
113
|
+
# @param [String] name The name for which to search.
|
114
|
+
# @param [Hash] options Options which influence the search.
|
115
|
+
# @option options [Boolean] :exact (true) Whether to match names exactly.
|
116
|
+
# @option options [Boolean] :case_sensitive (true) Whether to search case-sensitively.
|
117
|
+
# @return [Array<Recipe>] Collection of recipes which matched the search condition.
|
118
|
+
def find_by_name(name, options = {})
|
119
|
+
default_options = { exact: true, case_sensitive: true }
|
120
|
+
default_options.update(options)
|
121
|
+
|
122
|
+
if default_options[:case_sensitive]
|
123
|
+
if default_options[:exact]
|
124
|
+
recipes.select { |recipe| recipe.name == name }
|
125
|
+
else
|
126
|
+
recipes.select { |recipe| recipe.name.include? name }
|
127
|
+
end
|
128
|
+
else
|
129
|
+
if default_options[:exact]
|
130
|
+
recipes.select { |recipe| recipe.name.downcase == name.downcase }
|
131
|
+
else
|
132
|
+
recipes.select { |recipe| recipe.name.downcase.include? name.downcase }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Find recipes by their input.
|
138
|
+
#
|
139
|
+
# @deprecated Use {#find} instead.
|
140
|
+
#
|
141
|
+
# @example Searching for recipes which require planks.
|
142
|
+
# manager.find_by_input(Item.new('Oak Wood Planks', 5, 0)).map(&:name)
|
143
|
+
# #=> ['Sticks', 'Crafting Table', 'Chest', 'Bed', '...']
|
144
|
+
#
|
145
|
+
# @param [Item] item Item for which to search.
|
146
|
+
# @return [Array<Recipe>] Collection of recipes which matched the search condition.
|
147
|
+
def find_by_input(item)
|
148
|
+
recipes.select { |recipe| recipe.input.key? item }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Find recipes by their output.
|
152
|
+
#
|
153
|
+
# @deprecated Use {#find} instead.
|
154
|
+
#
|
155
|
+
# @example Searching for recipes which result in Coal.
|
156
|
+
# manager.find_by_output(Item.new('Coal', 263, 0)).map(&:name)
|
157
|
+
# #=> ['Coal']
|
158
|
+
# @return [Array<Recipe>]
|
159
|
+
# Collection of recipes which matched the search condition.
|
160
|
+
def find_by_output(item)
|
161
|
+
recipes.select { |recipe| recipe.output.key? item }
|
162
|
+
end
|
163
|
+
|
164
|
+
# Resolve a recipe into its base components.
|
165
|
+
#
|
166
|
+
# @since 0.3
|
167
|
+
#
|
168
|
+
# @example Components required to craft 50 torches.
|
169
|
+
# torch = recipe_manager.find_by_name('Torch').first
|
170
|
+
# components = recipe_manager.resolve_recipe(recipe_torch, 50)
|
171
|
+
# components.map { |item, amount| item.name => amount }
|
172
|
+
# #=> { "Wood" => 2, "Coal" => 13 }
|
173
|
+
#
|
174
|
+
# @param [Recipe] recipe
|
175
|
+
# The recipe which to resolve to its components.
|
176
|
+
# @param [Integer] amount Desired amount of the recipe.
|
177
|
+
# @return [Hash{Item => Integer}]
|
178
|
+
# A hash mapping the base components
|
179
|
+
# to the required amount of each.
|
180
|
+
def resolve_recipe(recipe, amount)
|
181
|
+
# Todo: Allow to specify arbitrary outputs.
|
182
|
+
# Todo: Fail if the specified output is not part of the recipe.
|
183
|
+
desired_output = recipe.output.keys.first
|
184
|
+
amount_per_iteration = recipe.output[desired_output]
|
185
|
+
# If we get four items per iteration, and want 21 items in
|
186
|
+
# total, we'll need 6 iterations.
|
187
|
+
iterations = (amount.to_f / amount_per_iteration).ceil
|
188
|
+
|
189
|
+
requirements = Hash.new(0)
|
190
|
+
|
191
|
+
recipe.input.each do |input, amount|
|
192
|
+
# Finding potential recipes for the input.
|
193
|
+
recipes_for_input = find_by_output(input)
|
194
|
+
|
195
|
+
# Todo: Allow for other criteria where the recipe should be ignored.
|
196
|
+
if recipes_for_input.empty?
|
197
|
+
requirements[input] += amount * iterations
|
198
|
+
else
|
199
|
+
recipe_for_input = recipes_for_input.first
|
200
|
+
requirements_for_input = resolve_recipe(recipe_for_input,
|
201
|
+
amount * iterations)
|
202
|
+
|
203
|
+
requirements.merge!(requirements_for_input) do |key, old_amount, new_amount|
|
204
|
+
old_amount + new_amount
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
requirements
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|