Add CraftingPlan to compute resource requirements

This commit is contained in:
Andrew Miner
2015-09-27 13:57:21 -07:00
parent de04c79d17
commit 74e95e614b
16 changed files with 280 additions and 434 deletions
@@ -71,7 +71,7 @@ module.exports = class FullRecipeController extends BaseController
inputs.clear() inputs.clear()
if @model? if @model?
@model.eachInputStack (stack)-> for stack in @model.input
inputs.add stack.itemSlug, stack.quantity inputs.add stack.itemSlug, stack.quantity
+42 -48
View File
@@ -32,13 +32,13 @@ module.exports = class CraftingNode
# Public Methods ############################################################################### # Public Methods ###############################################################################
expand: (queue=[])-> expand: (queue=[])->
return unless @valid
return queue if @children.length > 0 return queue if @children.length > 0
for child in @_createChildren() for child in @_createChildren()
child.parent = this child.parent = this
if child.valid @_children.push child
@_children.push child queue.push child
queue.push child
return queue return queue
acceptVisitor: (visitor)-> acceptVisitor: (visitor)->
@@ -65,52 +65,46 @@ module.exports = class CraftingNode
# Property Methods ############################################################################# # Property Methods #############################################################################
getChildren: ->
return @_children
isComplete: ->
if not @_complete?
@_complete = @_checkCompleteness()
return @_complete
getCompleteText: ->
return if @complete then "" else ""
getDepth: ->
maxDepth = 1
for child in @children
maxDepth = Math.max maxDepth, child.getDepth() + 1
return maxDepth
getId: ->
return @_id
getRotations: ->
return @_rotations
getSize: ->
size = 1
for child in @children
size += child.size
return size
isValid: ->
return @_valid if @_valid?
valid = @_checkValidity()
@_valid = false if not valid
return valid
Object.defineProperties @prototype, Object.defineProperties @prototype,
children: { get:@prototype.getChildren }
complete: { get:@prototype.isComplete } children:
completeText: { get:@prototype.getCompleteText } get: -> return @_children
depth: { get:@prototype.getDepth }
id: { get:@prototype.getId } complete:
rotations: { get:@prototype.getRotations } get: ->
size: { get:@prototype.getSize } if not @_complete?
valid: { get:@prototype.isValid } @_complete = @_checkCompleteness()
return @_complete
completeText:
get: -> if @complete then "◼︎" else "◻︎"
depth:
get: ->
maxDepth = 1
for child in @children
maxDepth = Math.max maxDepth, child.depth + 1
return maxDepth
id:
get: -> return @_id
rotations:
get: -> return @_rotations
size:
get: ->
size = 1
for child in @children
size += child.size
return size
valid:
get: -> @_checkValidity()
validText:
get: -> if @valid then "" else ""
# Virtual Methods ############################################################################## # Virtual Methods ##############################################################################
+39 -30
View File
@@ -39,12 +39,24 @@ module.exports = class CraftingPlan
# Object Overrides ############################################################################# # Object Overrides #############################################################################
toString: -> toString: ->
return "#{@constructor.name}{ result = ["To Make:"]
wanted:#{@wanted}, @_wanted.each (stack)->
required:#{@required}, result.push " #{stack}"
steps:[#{(step.toString() for step in @steps).join(',')}],
produced:#{@produced} result.push "Start with:"
}" @_required.each (stack)->
result.push " #{stack}"
result.push "Use these recipes:"
for step in @_steps
result.push " #{step}"
result.push "To produce:"
@_produced.each (stack)->
result.push " #{stack}"
return result.join '\n'
# Private Methods ############################################################################## # Private Methods ##############################################################################
@@ -52,35 +64,32 @@ module.exports = class CraftingPlan
@_required.addInventory @_wanted @_required.addInventory @_wanted
for i in [@_steps.length-1..0] by -1 for i in [@_steps.length-1..0] by -1
recipe = @_steps[i] step = @_steps[i]
for stack in recipe.output for stack in step.recipe.output
while @_required.quantityOf(stack.itemSlug) > 0 while @_required.quantityOf(stack.itemSlug) > 0
@_executeRecipe recipe @_executeStep step
@_produced.addInventory @_wanted @_produced.addInventory @_wanted
_executeRecipe: (recipe)-> _executeStep: (step)->
#console.log "executing: #{recipe}" step.repeat += 1
recipe = step.recipe
for stack in recipe.input for stack in recipe.input
@_use stack available = @_produced.quantityOf stack.itemSlug
required = recipe.getQuantityRequired stack.itemSlug
consumed = Math.min required, available
deficit = required - consumed
@_produced.remove stack.itemSlug, consumed
@_required.add stack.itemSlug, deficit
for stack in recipe.output for stack in recipe.output
@_produce stack deficit = @_required.quantityOf stack.itemSlug
created = recipe.getQuantityProduced stack.itemSlug
replenished = Math.min deficit, created
surplus = created - replenished
_produce: (stack)-> @_produced.add stack.itemSlug, surplus
deficit = @_required.quantityOf stack.itemSlug @_required.remove stack.itemSlug, replenished
replenished = Math.min deficit, stack.quantity
surplus = stack.quantity - replenished
#console.log "producing #{stack.itemSlug}, surplus: #{surplus}, replenished:#{replenished}"
@_produced.add stack.itemSlug, surplus
@_required.remove stack.itemSlug, replenished
_use: (stack)->
available = @_produced.quantityOf stack.itemSlug
consumed = Math.min stack.quantity, available
deficit = stack.quantity - consumed
#console.log "using #{stack.itemSlug}, consumed: #{consumed}, deficit:#{deficit}"
@_produced.remove stack.itemSlug, consumed
@_required.add stack.itemSlug, deficit
@@ -0,0 +1,29 @@
###
Crafting Guide - crafting_step.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
########################################################################################################################
module.exports = class CraftingStep
constructor: (recipe, repeat=0)->
if not recipe? then throw new Error 'recipe is required'
if repeat < 0 then throw new Error 'repeat must be at least 1'
@_recipe = recipe
@repeat = repeat
# Property Methods #############################################################################
Object.defineProperties @prototype,
recipe:
get: -> @_recipe
# Object Overrides #############################################################################
toString: ->
return "#{@repeat}x #{@recipe.slug}"
+16 -19
View File
@@ -44,26 +44,23 @@ module.exports = class GraphBuilder
# Property Methods ############################################################################# # Property Methods #############################################################################
isComplete: ->
return false unless @_rootNode?
return false unless @_queue?
return false unless @_queue.length is 0
return true
getRootNode: ->
return @_rootNode
getStepCount: ->
return @_stepCount
getWanted: ->
return @_wanted
Object.defineProperties @prototype, Object.defineProperties @prototype,
complete: { get:@prototype.isComplete }
rootNode: { get:@prototype.getRootNode } complete:
stepCount: { get:@prototype.getStepCount } get: ->
wanted: { get:@prototype.getWanted } return false unless @_rootNode?
return false unless @_queue?
return false unless @_queue.length is 0
return true
rootNode:
get: -> @_rootNode
stepCount:
get: -> @_stepCount
wanted:
get: -> @_wanted
# Object Overrides ############################################################################ # Object Overrides ############################################################################
@@ -33,12 +33,12 @@ module.exports = class InventoryNode extends CraftingNode
_checkCompleteness: -> _checkCompleteness: ->
for child in @children for child in @children
return false unless child.isComplete return false unless child.complete
return true return true
_checkValidity: -> _checkValidity: ->
for child in @children for child in @children
return false unless child.isValid return false unless child.valid
return true return true
# Object Overrides ############################################################################# # Object Overrides #############################################################################
@@ -47,8 +47,7 @@ module.exports = class InventoryNode extends CraftingNode
options.indent ?= '' options.indent ?= ''
options.recursive ?= true options.recursive ?= true
completeText = if @complete then 'complete' else 'incomplete' parts = ["#{options.indent}#{@completeText} #{@validText} InventoryNode for #{@inventory}"]
parts = ["#{options.indent}#{@completeText} InventoryNode for #{@inventory}"]
nextIndent = options.indent + ' ' nextIndent = options.indent + ' '
if options.recursive if options.recursive
for child in @children for child in @children
+7 -5
View File
@@ -46,7 +46,10 @@ module.exports = class ItemNode extends CraftingNode
return [] unless recipes.length > 0 return [] unless recipes.length > 0
for recipe in recipes for recipe in recipes
result.push new RecipeNode modPack:@modPack, recipe:recipe child = new RecipeNode modPack:@modPack, recipe:recipe
child.parent = this
if child.valid
result.push child
return result return result
@@ -55,13 +58,13 @@ module.exports = class ItemNode extends CraftingNode
return false unless @children? return false unless @children?
for child in @children for child in @children
return true if child.isComplete return true if child.complete
return false return false
_checkValidity: -> _checkValidity: ->
return true unless @children.length > 0 return true unless @children.length > 0
for child in @children for child in @children
return true if child.isValid return true if child.valid
return false return false
# Object Overrides ############################################################################# # Object Overrides #############################################################################
@@ -70,8 +73,7 @@ module.exports = class ItemNode extends CraftingNode
options.indent ?= '' options.indent ?= ''
options.recursive ?= true options.recursive ?= true
completeText = if @complete then 'complete' else 'incomplete' parts = ["#{options.indent}#{@completeText} #{@validText} ItemNode for #{@item.name}"]
parts = ["#{options.indent}#{@completeText} ItemNode for #{@item.name}"]
nextIndent = options.indent + ' ' nextIndent = options.indent + ' '
if options.recursive if options.recursive
for child in @children for child in @children
+35 -20
View File
@@ -7,6 +7,7 @@ All rights reserved.
CraftingNode = require './crafting_node' CraftingNode = require './crafting_node'
CraftingPlan = require './crafting_plan' CraftingPlan = require './crafting_plan'
CraftingStep = require './crafting_step'
Inventory = require '../inventory' Inventory = require '../inventory'
######################################################################################################################## ########################################################################################################################
@@ -30,37 +31,34 @@ module.exports = class PlanBuilder
producePlans: (maxPlans=null)-> producePlans: (maxPlans=null)->
maxPlans = if maxPlans then @plans.length + maxPlans else Number.MAX_VALUE maxPlans = if maxPlans then @plans.length + maxPlans else Number.MAX_VALUE
while @plans.length is 0 or (@plans.length < maxPlans and not @complete) while not @complete and (@plans.length < maxPlans)
plan = @_captureCurrentPlan() plan = @_captureCurrentPlan()
@plans.push plan if plan?
@plans.push plan
@_incrementChoiceNodes() @_incrementChoiceNodes()
return @_plans return @_plans
# Property Methods ############################################################################# # Property Methods #############################################################################
isComplete: ->
return @_complete
getPlans: ->
return @_plans
getWanted: ->
return @_wanted
setWanted: (wanted)->
@_wanted = wanted or new Inventory
Object.defineProperties @prototype, Object.defineProperties @prototype,
complete: { get:@prototype.isComplete }
plans: { get:@prototype.getPlans } complete:
wanted: { get:@prototype.getWanted, set:@prototype.setWanted } get: -> @_complete
plans:
get: -> @_plans
wanted:
get: -> @_wanted
set: (wanted)-> @_wanted = wanted or new Inventory
# Private Methods ############################################################################## # Private Methods ##############################################################################
_captureCurrentPlan: -> _captureCurrentPlan: ->
toVisit = [@_rootNode] toVisit = [@_rootNode]
steps = [] stepNodes = []
while toVisit.length > 0 while toVisit.length > 0
node = toVisit.shift() node = toVisit.shift()
@@ -70,14 +68,31 @@ module.exports = class PlanBuilder
else if node.TYPE is CraftingNode::TYPES.ITEM else if node.TYPE is CraftingNode::TYPES.ITEM
toVisit.push node.children[0] if node.children.length > 0 toVisit.push node.children[0] if node.children.length > 0
else if node.TYPE is CraftingNode::TYPES.RECIPE else if node.TYPE is CraftingNode::TYPES.RECIPE
steps.push node.recipe stepNodes.push node
toVisit.push(c) for c in node.children toVisit.push(c) for c in node.children
steps.reverse() steps = []
seenRecipes = {}
index = stepNodes.length - 1
while index >= 0
node = stepNodes[index]
index -= 1
return null unless node.valid and node.complete
recipeSlug = node.recipe.slug
continue if seenRecipes[recipeSlug]?
seenRecipes[recipeSlug] = true
steps.push new CraftingStep node.recipe
plan = new CraftingPlan steps, @_wanted plan = new CraftingPlan steps, @_wanted
return plan return plan
_incrementChoiceNodes: -> _incrementChoiceNodes: ->
if @_choiceNodes.length is 0
@_complete = true
return
index = @_choiceNodes.length - 1 index = @_choiceNodes.length - 1
while true while true
if index is -1 if index is -1
@@ -34,14 +34,15 @@ module.exports = class RecipeNode extends CraftingNode
_checkCompleteness: -> _checkCompleteness: ->
for child in @children for child in @children
return false unless child.isComplete return false unless child.complete
return true return true
_checkValidity: -> _checkValidity: ->
for child in @children
return false unless child.isValid
return false if @_isRepeatedRecipe() return false if @_isRepeatedRecipe()
for child in @children
return false unless child.valid
return true return true
# Private Methods ############################################################################## # Private Methods ##############################################################################
@@ -60,8 +61,7 @@ module.exports = class RecipeNode extends CraftingNode
options.indent ?= '' options.indent ?= ''
options.recursive ?= true options.recursive ?= true
completeText = if @complete then 'complete' else 'incomplete' parts = ["#{options.indent}#{@completeText} #{@validText} RecipeNode for #{@recipe.slug}"]
parts = ["#{options.indent}#{@completeText} RecipeNode for #{@recipe.slug}"]
nextIndent = options.indent + ' ' nextIndent = options.indent + ' '
if options.recursive if options.recursive
for child in @children for child in @children
-233
View File
@@ -1,233 +0,0 @@
###
Crafting Guide - crafting_plan.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
Inventory = require './inventory'
ItemSlug = require './item_slug'
Step = require './step'
_ = require 'underscore'
{Event} = require '../constants'
########################################################################################################################
module.exports = class CraftingPlan extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.modPack then throw new Error 'modPack is required'
attributes.includingTools ?= false
super attributes, options
@have = new Inventory modPack:@modPack
@want = new Inventory modPack:@modPack
@need = new Inventory modPack:@modPack
@result = new Inventory modPack:@modPack
recraft = _.debounce (=> @craft()), 100
for inventory in [@have, @want]
inventory.on Event.change, recraft
@on Event.change + ':includingTools', recraft
@clear()
# Public Methods ###############################################################################
clear: (options={})->
@steps = []
@need.clear()
@result.clear()
@trigger 'change', this
return this
craft: ->
toolsMessage = if @includingTools then ' (including tools)' else ''
haveMessage = if @have.isEmpty then '' else " starting with #{@have.unparse()}"
logger.info => "crafting #{@want.unparse()}#{toolsMessage}#{haveMessage}"
@clear()
@have.localize()
@want.localize()
@result.addInventory @have
@steps = {}
@want.each (stack)=>
@_findSteps stack.itemSlug, {}, ignoreGatherable:true
item = @modPack.findItem stack.itemSlug
@need.add item.slug, stack.quantity
@steps = (step for recipeSlug, step of @steps)
@_resolveNeeds()
@_removeExtraSteps()
@result.addInventory @want
@need.trigger 'change', @need
@result.trigger 'change', @result
@trigger 'change', this
removeUncraftableItems: ->
toRemove = []
@want.each (stack)=>
item = @modPack.findItem stack.itemSlug
if not item? then toRemove.push stack.itemSlug
for itemSlug in toRemove
@want.remove itemSlug
# Event Methods ################################################################################
onIncludingToolsChanged: ->
@storage.setItem 'includingTools', "#{@includingTools}"
@craft()
# Object Overrides #############################################################################
toString: ->
return "#{@constructor.name} {
have:#{@have},
want:#{@want},
need:#{@need},
result:#{@result},
steps:#{@steps}
}"
# Private Methods ##############################################################################
_findRecipes: (item)->
recipes = @modPack.findRecipes item.slug
return null unless recipes? and recipes.length > 0
recipeOptions = []
for recipe in recipes
if recipe.tools.length is 0
recipeOptions.push recipe:recipe, missingTools:0
else
missingTools = []
for tool in recipe.tools
if not @have.hasAtLeast tool.itemSlug
missingTools.push tool
recipeOptions.push recipe:recipe, missingTools:missingTools.length
recipeOptions.sort (a, b)->
return 0 if a.missingTools is b.missingTools
return if a.missingTools < b.missingTools then -1 else +1
return (option.recipe for option in recipeOptions)
_findSteps: (itemSlug, parentSteps={})->
item = @modPack.findItem itemSlug
return unless item?
return unless item.isCraftable
ignoreGatherable = @want.hasAtLeast itemSlug, 1
if (not item.isGatherable) or ignoreGatherable
recipes = @_findRecipes item
recipes ?= []
if parentSteps[item.slug]?
logger.verbose -> "found cycle at #{item.slug}"
throw new Error 'invalid recipe path'
parentSteps[item.slug] = item
logger.verbose -> "exploring: #{item.slug}"
logger.indent()
currentSteps = _.clone @steps
foundValidRecipe = false
for i in [0...recipes.length] by 1
recipe = recipes[i]
logger.verbose -> "trying recipe #{i+1} of #{recipes.length}: #{recipe.slug}"
if @steps[recipe.slug]?
logger.verbose -> "already accepted this recipe"
foundValidRecipe = true
break
try
if @includingTools
for toolStack in recipe.tools
if not @_hasStep toolStack.itemSlug
@_findSteps toolStack.itemSlug, parentSteps
for inputStack in recipe.input
@_findSteps inputStack.itemSlug, parentSteps
logger.verbose -> "adding step for: #{recipe.slug}"
@steps[recipe.slug] = new Step outputItemSlug:item.slug, recipe:recipe
foundValidRecipe = true
break
catch error
logger.verbose -> "recipe didn't work out: #{recipe.slug}"
if error.message isnt 'invalid recipe path' then throw error
@steps = _.clone currentSteps
delete parentSteps[item.slug]
logger.outdent()
if not (foundValidRecipe or item.isGatherable)
logger.verbose -> "could not find a valid recipe for #{item.slug}"
throw new Error 'invalid recipe path'
_hasStep: (itemSlug)->
for recipeSlug, step of @steps
return true if step.recipe.produces itemSlug
return false
_qualifyItemSlug: (itemSlug)->
item = @modPack.findItem itemSlug
return item.slug if item?
return itemSlug
_removeExtraSteps: ->
result = []
number = 1
for step in @steps
if step.multiplier > 0
result.push step
step.number = number
number += 1
@steps = result
_resolveNeeds: ->
for i in [@steps.length-1..0] by -1
step = @steps[i]
step.number = i + 1
recipe = step.recipe
outputQuantity = recipe.getQuantityProducedOf step.outputItemSlug
step.multiplier = Math.ceil(@need.quantityOf(step.outputItemSlug) / outputQuantity)
if @includingTools
recipe.eachToolStack (stack)=>
itemSlug = @_qualifyItemSlug stack.itemSlug
available = @result.quantityOf(itemSlug) + @need.quantityOf(itemSlug)
needed = Math.max 0, stack.quantity - available
@need.add itemSlug, needed
@result.add itemSlug, needed
recipe.eachInputStack (stack)=>
itemSlug = @_qualifyItemSlug stack.itemSlug
needed = step.multiplier * stack.quantity
consumed = Math.min needed, @result.quantityOf itemSlug
remaining = needed - consumed
@result.remove itemSlug, consumed
@need.add itemSlug, remaining
recipe.eachOutputStack (stack)=>
itemSlug = @_qualifyItemSlug stack.itemSlug
created = stack.quantity * step.multiplier
consumed = Math.min created, @need.quantityOf itemSlug
remaining = created - consumed
@result.add itemSlug, remaining
@need.remove itemSlug, consumed
+23 -49
View File
@@ -6,6 +6,7 @@ All rights reserved.
### ###
BaseModel = require './base_model' BaseModel = require './base_model'
ItemSlug = require './item_slug'
Stack = require './stack' Stack = require './stack'
{Event} = require '../constants' {Event} = require '../constants'
{StringBuilder} = require 'crafting-guide-common' {StringBuilder} = require 'crafting-guide-common'
@@ -46,50 +47,15 @@ module.exports = class Recipe extends BaseModel
return -1 if aValue return -1 if aValue
return +1 if bValue return +1 if bValue
aValue = a.getQuantityProducedOf itemSlug aValue = a.getQuantityProduced itemSlug
bValue = b.getQuantityProducedOf itemSlug bValue = b.getQuantityProduced itemSlug
if aValue isnt bValue if aValue isnt bValue
return if aValue > bValue then -1 else +1 return if aValue > bValue then -1 else +1
aValue = a.getOutputCount()
bValue = b.getOutputCount()
if aValue isnt bValue
return if aValue > bValue then -1 else +1
aValue = a.tools.length
bValue = b.tools.length
if aValue isnt bValue
return if aValue < bValue then -1 else +1
aValue = a.getInputCount()
bValue = b.getInputCount()
if aValue isnt bValue
return if aValue < bValue then -1 else +1
return 0 return 0
# Public Methods ############################################################################### # Public Methods ###############################################################################
eachInputStack: (callback)->
for i in [0...@pattern.length]
stack = @getStackAtSlot(i)
continue unless stack?
callback stack
eachOutputStack: (callback)->
for stack in @output
callback stack
eachToolStack: (callback)->
for stack in @tools
callback stack
getInputCount: ->
result = 0
for stack in @input
result += stack.quantity
return result
getStackAtSlot: (patternSlot)-> getStackAtSlot: (patternSlot)->
trueIndex = 0:0, 1:1, 2:2, 3:4, 4:5, 5:6, 6:8, 7:9, 8:10 trueIndex = 0:0, 1:1, 2:2, 3:4, 4:5, 5:6, 6:8, 7:9, 8:10
patternDigit = @pattern[trueIndex[patternSlot]] patternDigit = @pattern[trueIndex[patternSlot]]
@@ -101,13 +67,7 @@ module.exports = class Recipe extends BaseModel
return stack return stack
getOutputCount: -> getQuantityProduced: (itemSlug)->
result = 0
for stack in @output
result += stack.quantity
return result
getQuantityProducedOf: (itemSlug)->
total = 0 total = 0
for stack in @output for stack in @output
if stack.itemSlug.matches itemSlug if stack.itemSlug.matches itemSlug
@@ -115,6 +75,16 @@ module.exports = class Recipe extends BaseModel
return total return total
getQuantityRequired: (itemSlug)->
total = 0
for i in [0...@pattern.length]
stack = @getStackAtSlot i
continue unless stack?
if ItemSlug.equal stack.itemSlug, itemSlug
total += stack.quantity
return total
hasAllTools: (modPack)-> hasAllTools: (modPack)->
modPack ?= @modVersion?.mod?.modPack modPack ?= @modVersion?.mod?.modPack
return true unless modPack return true unless modPack
@@ -179,7 +149,7 @@ module.exports = class Recipe extends BaseModel
if not @_slug? if not @_slug?
builder = new StringBuilder builder = new StringBuilder
delimiterNeeded = false delimiterNeeded = false
@eachInputStack (stack)-> for stack in @input
if delimiterNeeded then builder.push ',' if delimiterNeeded then builder.push ','
delimiterNeeded = true delimiterNeeded = true
@@ -187,12 +157,12 @@ module.exports = class Recipe extends BaseModel
builder.push stack.itemSlug.qualified builder.push stack.itemSlug.qualified
builder.push '>' builder.push '>'
@eachToolStack (stack)-> for stack in @tools
builder.push stack.itemSlug.qualified builder.push stack.itemSlug.qualified
builder.push '>' builder.push '>'
delimiterNeeded = false delimiterNeeded = false
@eachOutputStack (stack)-> for stack in @output
if delimiterNeeded then builder.push ',' if delimiterNeeded then builder.push ','
delimiterNeeded = true delimiterNeeded = true
@@ -215,7 +185,9 @@ module.exports = class Recipe extends BaseModel
needsDelimiter = false needsDelimiter = false
for stack in @input for stack in @input
if needsDelimiter then result.push ', ' if needsDelimiter then result.push ', '
result.push stack.toString() result.push @getQuantityRequired stack.itemSlug
result.push ' '
result.push stack.itemSlug
needsDelimiter = true needsDelimiter = true
result.push ']' result.push ']'
@@ -223,7 +195,9 @@ module.exports = class Recipe extends BaseModel
needsDelimiter = false needsDelimiter = false
for stack in @output for stack in @output
if needsDelimiter then result.push ', ' if needsDelimiter then result.push ', '
result.push stack.toString() result.push @getQuantityProduced stack.itemSlug
result.push ' '
result.push stack.itemSlug
needsDelimiter = true needsDelimiter = true
result.push ']' result.push ']'
+1 -1
View File
@@ -31,5 +31,5 @@ module.exports = class Step extends BaseModel
_computeInventory: -> _computeInventory: ->
@inventory.clear() @inventory.clear()
@recipe.eachInputStack (stack)=> for stack in @recipe.input
@inventory.add stack.itemSlug, stack.quantity * @multiplier @inventory.add stack.itemSlug, stack.quantity * @multiplier
+51 -5
View File
@@ -7,6 +7,7 @@ All rights reserved.
CraftingPlan = require '../../src/coffee/models/crafting/crafting_plan' CraftingPlan = require '../../src/coffee/models/crafting/crafting_plan'
fixtures = require './fixtures' fixtures = require './fixtures'
ItemSlug = require '../../src/coffee/models/item_slug'
######################################################################################################################## ########################################################################################################################
@@ -20,30 +21,75 @@ describe 'crafting_plan.coffee', ->
plan.required.unparse().should.equal 'coal' plan.required.unparse().should.equal 'coal'
plan.produced.unparse().should.equal 'coal' plan.produced.unparse().should.equal 'coal'
it 'can compute a single item with a single step', -> it 'can compute a single item with one single step plan', ->
plans = fixtures.makePlans [1, 'test__charcoal'] plans = fixtures.makePlans [1, 'test__charcoal']
plans.length.should.equal 1 plans.length.should.equal 1
plan = plans[0] plan = plans[0]
plan.required.unparse().should.equal 'coal:8.oak_wood' plan.required.unparse().should.equal 'coal:8.oak_wood'
plan.produced.unparse().should.equal '8.charcoal' plan.produced.unparse().should.equal '8.charcoal'
(s.toString() for s in plan.steps).should.eql ['1x 8 test__oak_wood,test__coal>>8 test__charcoal']
it 'can compute a large quantity of a single item with a single step', -> it 'can compute a large quantity of a single item with one single step plan', ->
plans = fixtures.makePlans [15, 'test__charcoal'] plans = fixtures.makePlans [15, 'test__charcoal']
plans.length.should.equal 1 plans.length.should.equal 1
plan = plans[0] plan = plans[0]
plan.required.unparse().should.equal '2.coal:16.oak_wood' plan.required.unparse().should.equal '2.coal:16.oak_wood'
plan.produced.unparse().should.equal '16.charcoal' plan.produced.unparse().should.equal '16.charcoal'
(s.toString() for s in plan.steps).should.eql ['2x 8 test__oak_wood,test__coal>>8 test__charcoal']
it 'can compute a single item with multiple steps', -> it 'can compute a single item with multiple plans', ->
plans = fixtures.makePlans [1, 'test__iron_ingot'] plans = fixtures.makePlans [1, 'test__iron_ingot']
plans.length.should.equal 2 plans.length.should.equal 2
plan = plans[0] plan = plans[0]
plan.required.unparse().should.equal 'coal:8.iron_ore:8.oak_wood' plan.required.unparse().should.equal 'coal:8.iron_ore:8.oak_wood'
plan.produced.unparse().should.equal '7.charcoal:iron_ingot' plan.produced.unparse().should.equal '7.charcoal:8.iron_ingot'
(s.toString() for s in plan.steps).should.eql [
'1x 8 test__oak_wood,test__coal>>8 test__charcoal'
'1x 8 test__iron_ore,test__charcoal>test__furnace>8 test__iron_ingot'
]
plan = plans[1] plan = plans[1]
plan.required.unparse().should.equal 'coal:8.iron_ore' plan.required.unparse().should.equal 'coal:8.iron_ore'
plan.produced.unparse().should.equal 'iron_ingot' plan.produced.unparse().should.equal '8.iron_ingot'
(s.toString() for s in plan.steps).should.eql [
'1x 8 test__iron_ore,test__coal>test__furnace>8 test__iron_ingot'
]
it 'can compute multiple items with multiple plans', ->
plans = fixtures.makePlans [1, 'test__copper_block'], [1, 'test__iron_sword']
plans.length.should.equal 4
for plan in plans
plan.required.unparse().should.match /16.copper_ore.*8.iron_ore/
plan.produced.unparse().should.match /copper_block.*:iron_sword/
plan = plans[0]
plan.required.unparse().should.match /3.coal.*9.oak_wood/
plan.produced.unparse().should.match /7.charcoal/
"#{plan.steps[3]}".should.equal '1x 8 test__iron_ore,test__charcoal>test__furnace>8 test__iron_ingot'
"#{plan.steps[4]}".should.equal '2x 8 test__copper_ore,test__coal>test__furnace>8 test__copper_ingot'
plan.steps.length.should.equal 7
plan = plans[1]
plan.required.unparse().should.match /3.coal.*:oak_wood/
plan.produced.unparse().should.not.match /charcoal/
"#{plan.steps[2]}".should.equal '1x 8 test__iron_ore,test__coal>test__furnace>8 test__iron_ingot'
"#{plan.steps[3]}".should.equal '2x 8 test__copper_ore,test__coal>test__furnace>8 test__copper_ingot'
plan.steps.length.should.equal 6
plan = plans[2]
plan.required.unparse().should.match /^coal.*9.oak_wood/
plan.produced.unparse().should.match /5.charcoal/
"#{plan.steps[3]}".should.equal '1x 8 test__iron_ore,test__charcoal>test__furnace>8 test__iron_ingot'
"#{plan.steps[4]}".should.equal '2x 8 test__copper_ore,test__charcoal>test__furnace>8 test__copper_ingot'
plan.steps.length.should.equal 7
plan = plans[3]
plan.required.unparse().should.match /2.coal.*9.oak_wood/
plan.produced.unparse().should.match /6.charcoal/
"#{plan.steps[3]}".should.equal '1x 8 test__iron_ore,test__coal>test__furnace>8 test__iron_ingot'
"#{plan.steps[4]}".should.equal '2x 8 test__copper_ore,test__charcoal>test__furnace>8 test__copper_ingot'
plan.steps.length.should.equal 7
+7 -2
View File
@@ -41,16 +41,19 @@ MOD_VERSION_FILE =
item: Copper Ingot item: Copper Ingot
recipe: recipe:
input: Copper Ore, Coal input: 8 Copper Ore, Coal
pattern: .0. ... .1. pattern: .0. ... .1.
quantity: 8
tools: Furnace tools: Furnace
recipe: recipe:
input: Copper Ore, Charcoal input: 8 Copper Ore, Charcoal
pattern: .0. ... .1. pattern: .0. ... .1.
quantity: 8
tools: Furnace tools: Furnace
recipe: recipe:
input: Copper Block input: Copper Block
pattern: ... .0. ... pattern: ... .0. ...
quantity: 9
item: Copper Ore item: Copper Ore
@@ -66,10 +69,12 @@ MOD_VERSION_FILE =
recipe: recipe:
input: 8 Iron Ore, Charcoal input: 8 Iron Ore, Charcoal
pattern: .0. ... .1. pattern: .0. ... .1.
quantity: 8
tools: Furnace tools: Furnace
recipe: recipe:
input: 8 Iron Ore, Coal input: 8 Iron Ore, Coal
pattern: .0. ... .1. pattern: .0. ... .1.
quantity: 8
tools: Furnace tools: Furnace
item: Iron Sword item: Iron Sword
+15 -11
View File
@@ -13,27 +13,31 @@ PlanBuilder = require '../../src/coffee/models/crafting/plan_builder'
describe 'plan_builder.coffee', -> describe 'plan_builder.coffee', ->
printPlan = (plan)-> printPlan = (plan)->
return ((s.slug.replace(/^.*>.*>/, '') for s in plan.steps;;)).join ' > ' return '' unless plan?
return ((s.recipe.slug.replace(/^.*>.*>/, '') for s in plan.steps;;)).join ' > '
it 'generates an empty plan for a gatherable item', -> it 'generates an empty plan for a gatherable item', ->
builder = new PlanBuilder fixtures.makeTree 'test__oak_wood' plans = fixtures.makePlans [1, 'test__oak_wood']
plans = builder.producePlans 100
plans.length.should.equal 1 plans.length.should.equal 1
plans[0].length.should.equal 0 plans[0].length.should.equal 0
builder.complete.should.be.true
it 'can find a multi-step plan', -> it 'can find a multi-step plan', ->
builder = new PlanBuilder fixtures.makeTree 'test__lever' plans = fixtures.makePlans [1, 'test__lever']
plans = builder.producePlans 100
printPlan(plans[0]).should.equal '4 test__oak_planks > 4 test__stick > test__lever' printPlan(plans[0]).should.equal '4 test__oak_planks > 4 test__stick > test__lever'
plans.length.should.equal 1 plans.length.should.equal 1
builder.complete.should.be.true
it 'can find multiple plans', -> it 'can find multiple plans', ->
builder = new PlanBuilder fixtures.makeTree 'test__iron_ingot' plans = fixtures.makePlans [1, 'test__iron_ingot']
plans = builder.producePlans 100
printPlan(plans[0]).should.equal '8 test__charcoal > test__iron_ingot' printPlan(plans[0]).should.equal '8 test__charcoal > 8 test__iron_ingot'
printPlan(plans[1]).should.equal 'test__iron_ingot' printPlan(plans[1]).should.equal '8 test__iron_ingot'
plans.length.should.equal 2
it 'ignores invalid plans', ->
plans = fixtures.makePlans [1, 'test__copper_block']
printPlan(plans[0]).should.equal '8 test__copper_ingot > test__copper_block'
printPlan(plans[1]).should.equal '8 test__charcoal > 8 test__copper_ingot > test__copper_block'
plans.length.should.equal 2
@@ -91,6 +91,11 @@ describe 'mod_version_parser_v1.coffee', ->
modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...' modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...'
(s.item for s in modVersion._slugs).should.eql ['charlie', 'delta', 'echo', 'foxtrot'] (s.item for s in modVersion._slugs).should.eql ['charlie', 'delta', 'echo', 'foxtrot']
it 'correctly handles recipes which use the same input multiple times', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha; pattern:.0..0....'
recipe = _.values(modVersion._recipes)[0]
recipe.getQuantityRequired(ItemSlug.slugify('alpha')).should.equal 2
it 'allows a quantity for each input', -> it 'allows a quantity for each input', ->
modVersion = parser.parse baseText + 'recipe:; input: 12 Delta, 3 Echo; pattern:... 0.1 ...' modVersion = parser.parse baseText + 'recipe:; input: 12 Delta, 3 Echo; pattern:... 0.1 ...'
recipe = _.values(modVersion._recipes)[0] recipe = _.values(modVersion._recipes)[0]