crafting_table 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|