Fix bugs with new crafting algo

This commit is contained in:
Andrew Miner
2015-11-10 20:08:03 -08:00
parent 4e95ab998f
commit ff398b4a2f
23 changed files with 255 additions and 78 deletions
+2 -2
View File
@@ -95,8 +95,8 @@ GitHub.file.itemDescription.fileName = _.template "item.cg"
GitHub.file.itemDescription.path = _.template "/data/<%= modSlug %>/items/<%= itemSlug %>"
exports.Limits = Limits = {}
Limits.maximumGraphSize = 10000
Limits.maximumPlanCount = 10000
Limits.maximumGraphSize = 5000
Limits.maximumPlanCount = 5000
exports.Login = Login = {}
Login.authorizeUrl = _.template "https://github.com/login/oauth/authorize" +
@@ -5,15 +5,16 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
AdsenseController = require './adsense_controller'
CraftPage = require '../models/craft_page'
InventoryController = require './inventory_controller'
PageController = require './page_controller'
StepController = require './step_controller'
_ = require 'underscore'
{Event} = require '../constants'
{Text} = require '../constants'
{Url} = require '../constants'
_ = require 'underscore'
AdsenseController = require './adsense_controller'
CraftPage = require '../models/craft_page'
CraftsmanWorkingController = require './craftsman_working_controller'
{Event} = require '../constants'
InventoryController = require './inventory_controller'
PageController = require './page_controller'
StepController = require './step_controller'
{Text} = require '../constants'
{Url} = require '../constants'
########################################################################################################################
@@ -32,15 +33,13 @@ module.exports = class CraftPageController extends PageController
@modPack = options.modPack
@storage = options.storage
@model.craftsman.on Event.change, => @tryRefresh()
# Event Methods ################################################################################
onHaveInventoryChanged: ->
@storage.store 'crafting-plan:have', @model.craftsman.have.unparse()
onMoveNeedToHave: (itemSlug)->
quantity = @model.craftsman.need.quantityOf itemSlug
quantity = @needInventoryController.model.quantityOf itemSlug
@model.craftsman.have.add itemSlug, quantity
@onHaveInventoryChanged()
@@ -106,12 +105,16 @@ module.exports = class CraftPageController extends PageController
firstButtonType: 'up'
@needInventoryController.on Event.button.first, (c, s)=> @onMoveNeedToHave(s)
@workingSectionController = @addChild CraftsmanWorkingController, '.view__craftsman_working',
model: @model.craftsman
@$instructionsSection = @$('.instructions.section')
@$makeSection = @$('.make.section')
@$toolsSection = @$('.tools.section')
@$ingredientsSection = @$('.ingredients.section')
@$stepsSection = @$('.steps.section')
@$stepsContainer = @$('.steps.section .panel')
@$workingSection = @$('.view__craftsman_working')
super
@@ -139,14 +142,23 @@ module.exports = class CraftPageController extends PageController
return not @model.craftsman.want.hasAtLeast controller.model.outputItemSlug
_refreshSectionVisibility: ->
return unless @_rendered
if @model.craftsman.want.isEmpty
for $el in [@$toolsSection, @$ingredientsSection, @$stepsSection]
@hide $el
@show @$instructionsSection
@hide @$workingSection
else if not @model.craftsman.complete
for $el in [@$toolsSection, @$ingredientsSection, @$stepsSection]
@hide $el
@hide @$instructionsSection
@show @$workingSection
else
for $el in [@$toolsSection, @$ingredientsSection, @$stepsSection]
@show $el
@hide @$instructionsSection
@hide @$workingSection
_refreshSteps: ->
steps = @model.craftsman.plan?.steps or []
@@ -0,0 +1,54 @@
###
Crafting Guide - crafting_grid_controller.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
Craftsman = require '../models/crafting/craftsman'
{Event} = require '../constants'
########################################################################################################################
module.exports = class CraftsmanWorkingController extends BaseController
constructor: (options={})->
if not options.model then throw new Error 'options.model is required'
options.templateName = 'craftsman_working'
super options
@model.on Event.change, => @_refreshStatusText()
# BaseController Methods #######################################################################
onDidRender: ->
@$message = @$('.message p')
@$count = @$('.count p')
super
refresh: ->
@_refreshStatusText()
super
# Private Methods ##############################################################################
_refreshStatusText: ->
return unless @$message? and @$count?
switch @model.stage
when Craftsman::STAGE.WAITING
@$message.html 'Preparing crafting calculation...'
@$count.html ''
when Craftsman::STAGE.GRAPHING
@$message.html 'Researching recipes...'
@$count.html "Found #{@model.stageCount} recipes so far..."
when Craftsman::STAGE.PLANNING
@$message.html 'Figuring out possible crafting plans...'
@$count.html "Found #{@model.stageCount} possibilities so far..."
when Craftsman::STAGE.ANALYZING
@$message.html 'Looking for the best plan...'
@$count.html "Finished checking #{@model.stageCount} so far..."
when Craftsman::STAGE.COMPLETE
@$message.html 'All done!'
@$count.html ''
@@ -72,7 +72,7 @@ module.exports = class FullRecipeController extends BaseController
if @model?
for stack in @model.input
inputs.add stack.itemSlug, stack.quantity
inputs.add stack.itemSlug, @model.getQuantityRequired stack.itemSlug
_refreshOutputs: ->
@@ -81,7 +81,7 @@ module.exports = class FullRecipeController extends BaseController
if @model?
for stack in @model.output
outputs.add stack.itemSlug, stack.quantity
outputs.add stack.itemSlug, @model.getQuantityProduced stack.itemSlug
_refreshTools: ->
@$toolContainer.empty()
+2 -1
View File
@@ -23,7 +23,8 @@ module.exports = class CraftPage extends BaseModel
@modPack.on Event.change, => @_consumeParams()
@on Event.change + ':params', => @_consumeParams()
@craftsman.on Event.change, => @trigger Event.change, this
@craftsman.on Event.change + ':stage', => @trigger Event.change, this
@craftsman.on Event.change + ':complete', => @trigger Event.change, this
# Private Methods ##############################################################################
@@ -12,11 +12,13 @@ SimpleInventory = require '../simple_inventory'
module.exports = class CraftingPlan
constructor: (steps, want, modPack)->
constructor: (modPack, want, have, steps)->
if not modPack? then throw new Error 'modPack is required'
if not steps? then throw new Error 'steps is required'
if not want? then throw new Error 'want is required'
if not have? then throw new Error 'have is required'
if not steps? then throw new Error 'steps is required'
@_have = have
@_made = null
@_modPack = modPack
@_need = null
@@ -31,9 +33,11 @@ module.exports = class CraftingPlan
computeRequired: ->
@_need = new SimpleInventory modPack:@_modPack
@_made = new SimpleInventory modPack:@_modPack
@_need.addInventory @_want
@_made = new SimpleInventory modPack:@_modPack
@_made.addInventory @_have
for i in [@_steps.length-1..0] by -1
step = @_steps[i]
step.multiplier = 0
@@ -47,6 +51,8 @@ module.exports = class CraftingPlan
@_executeStep step
@_made.addInventory @_want
@_pruneEmptySteps()
@_numberSteps()
hasRawScore: (name)->
return @_rawScores[name]?
@@ -69,6 +75,8 @@ module.exports = class CraftingPlan
# Property Methods #############################################################################
Object.defineProperties @prototype,
have:
get: -> @_have
length:
get: -> @steps.length
made:
@@ -87,6 +95,10 @@ module.exports = class CraftingPlan
@_want.each (stack)->
result.push " #{stack}"
result.push "When you already have:"
@_have.each (stack)->
result.push " #{stack}"
result.push "Start with:"
@_need.each (stack)->
result.push " #{stack}"
@@ -140,3 +152,12 @@ module.exports = class CraftingPlan
_numberSteps: ->
for step, i in @_steps
step.number = i + 1
_pruneEmptySteps: ->
index = 0
while index < @_steps.length
step = @_steps[index]
if step.multiplier is 0
@_steps.splice index, 1
else
index++
+17 -18
View File
@@ -18,11 +18,11 @@ w = require 'when'
module.exports = class Craftsman extends BaseModel
@::ANALYZE_STEP_INCREMENT = 100
@::ANALYZE_STEP_INCREMENT = 29
@::GRAPH_STEP_INCREMENT = 10
@::GRAPH_STEP_INCREMENT = 59
@::PLAN_STEP_INCREMENT = 50
@::PLAN_STEP_INCREMENT = 39
@::STAGE =
WAITING: 'waiting'
@@ -43,32 +43,29 @@ module.exports = class Craftsman extends BaseModel
reset = _.throttle (=> @reset()), 100
@_have = new Inventory
@_have = new Inventory modPack:@_modPack
@_have.on Event.change, reset
@_want = new Inventory
@_want = new Inventory modPack:@_modPack
@_want.on Event.change, reset
@on Event.change + ':paused', reset
@on Event.change + ':stage', => logger.info "Craftsman has started #{@stage}..."
@on 'scheduleNextWork', => @_scheduleNextWork()
@reset()
# Public Methods ###############################################################################
work: ->
return if @_want.isEmpty
logger.verbose => "stage: #{@stage}(#{@stageCount}) working..."
if not @_graphBuilder?
want = new Inventory {modPack:@_modPack}, clone:@_want
want.localize()
@_want.localize()
@_have.localize()
have = new Inventory {modPack:@_modPack}, clone:@_have
have.localize()
logger.info -> "Craftsman starting to build #{@_want} from #{@_have}"
logger.info -> "Craftsman starting to build #{want} from #{have}"
@_graphBuilder = new GraphBuilder modPack:@_modPack, want:want, have:have
@_graphBuilder = new GraphBuilder modPack:@_modPack, want:@_want, have:@_have
@stage = @STAGE.GRAPHING
@stageCount = 0
else if not @_graphBuilder.complete
@@ -77,7 +74,7 @@ module.exports = class Craftsman extends BaseModel
else if not @_planBuilder?
logger.debug => "Craftsman finished computing graph:\n#{@_graphBuilder.rootNode}"
@_planBuilder = new PlanBuilder @_graphBuilder.rootNode, @_modPack, want:@_graphBuilder.want
@_planBuilder = new PlanBuilder @_graphBuilder.rootNode, @_modPack, have:@_have, want:@_want
@stage = @STAGE.PLANNING
@stageCount = 0
else if not @_planBuilder.complete
@@ -96,11 +93,13 @@ module.exports = class Craftsman extends BaseModel
@_planEvaluator.findBestPlan PlanEvaluator::CRITERIA.LEAST_MATERIALS
]
@_plans[0].computeRequired()
logger.info => "Craftsman has finished with plans: #{(p.toString() for p in @_plans).join('\n')}"
@trigger Event.change + ':complete', this
@trigger Event.change, this
@stage = @STAGE.COMPLETE
@stageCount = 0
@_scheduleNextWork()
logger.info => "Craftsman has finished with plans: #{(p.toString() for p in @_plans).join('\n')}"
@trigger Event.change, this
@trigger 'scheduleNextWork'
return @complete
reset: ->
@@ -28,7 +28,7 @@ module.exports = class InventoryNode extends CraftingNode
@inventory.each (stack)=>
item = @modPack.findItem stack.itemSlug
if not item? then throw new Error "Could not find an item for slug: #{stack.itemSlug}"
result.push new ItemNode modPack:@modPack, item:item
result.push new ItemNode modPack:@modPack, item:item, ignoreGatherable:true
return result
_checkCompleteness: ->
+8 -4
View File
@@ -17,22 +17,26 @@ module.exports = class ItemNode extends CraftingNode
@::TYPE = CraftingNode::TYPES.ITEM
constructor: (options={})->
if not options.item?
throw new Error 'options.item is required'
if not options.item? then throw new Error 'options.item is required'
super options
@item = options.item
@_ignoreGatherable = options.ignoreGatherable ?= false
@_recipes = null
# Property Methods #############################################################################
getRecipes: ->
if not @_recipes?
@_recipes = @modPack.findRecipes @item.slug
@_recipes = @modPack.findRecipes @item.slug, forCrafting:true, onlyPrimary:true
if not @_recipes
@_recipes = @modPack.findRecipes @item.slug, forCrafting:true
return @_recipes or []
isGatherable: ->
return true if @item.isGatherable
if not @_ignoreGatherable
return true if @item.isGatherable
return true if @getRecipes().length is 0
return false
@@ -17,8 +17,10 @@ module.exports = class PlanBuilder
constructor: (rootNode, modPack, options={})->
if not rootNode? then throw new Error 'rootNode is required'
if not modPack? then throw new Error 'modPack is required'
@want = options.want
@have = options.have
@_choiceNodes = []
@_complete = false
@@ -53,6 +55,10 @@ module.exports = class PlanBuilder
complete:
get: -> @_complete
have:
get: -> @_have
set: (have)-> @_have = have or new Inventory
maxPlanCount:
get: -> @_maxPlanCount
set: (value)-> @_maxPlanCount = value
@@ -95,7 +101,7 @@ module.exports = class PlanBuilder
seenRecipes[recipeSlug] = true
steps.push new CraftingStep node.recipe, @_modPack
plan = new CraftingPlan steps, @_want, @_modPack
plan = new CraftingPlan @_modPack, @_want, @_have, steps
return plan
_incrementChoiceNodes: ->
+19 -1
View File
@@ -6,7 +6,9 @@ All rights reserved.
###
CraftingNode = require './crafting_node'
# ItemNode = require './item_node' # don't include here, causes a cycle
Item = require '../item'
# ItemNode = require './item_node' # don't include here, causes a cycle
ItemSlug = require '../item_slug'
########################################################################################################################
@@ -29,6 +31,9 @@ module.exports = class RecipeNode extends CraftingNode
for stack in @recipe.input
item = @modPack.findItem stack.itemSlug
if not item?
name = @modPack.findName stack.itemSlug
item = new Item name:name, slug:stack.itemSlug, gatherable:true
result.push new ItemNode modPack:@modPack, item:item
return result
@@ -39,6 +44,7 @@ module.exports = class RecipeNode extends CraftingNode
_checkValidity: ->
return false if @_isRepeatedRecipe()
return false if @_requiresToolBeingMade()
for child in @children
return false unless child.valid
@@ -55,6 +61,18 @@ module.exports = class RecipeNode extends CraftingNode
return false
_requiresToolBeingMade: ->
for toolStack in @recipe.tools
toolSlug = toolStack.itemSlug
nextParent = @parent
while nextParent?
if ItemSlug.equal toolSlug, nextParent.item?.slug
return true
nextParent = nextParent.parent
return false
# Object Overrides ############################################################################
toString: (options={})->
+3
View File
@@ -35,6 +35,8 @@ module.exports = class ItemSlug
return 0
@equal: (a, b)->
return true if not a? and not b?
return false unless a? and b?
return false unless a.mod is b.mod
return false unless a.item is b.item
return true
@@ -56,6 +58,7 @@ module.exports = class ItemSlug
return ItemSlug.compare this, that
matches: (slug, options={exact:false})->
return false unless slug?
return false unless typeof(slug.matches) is 'function'
if slug.isQualified and this.isQualified
+2
View File
@@ -139,6 +139,7 @@ module.exports = class ModVersion extends BaseModel
findRecipes: (itemSlug, result=[], options={})->
options.onlyPrimary ?= false
options.forCrafting ?= false
primaryRecipes = []
otherRecipes = []
@@ -146,6 +147,7 @@ module.exports = class ModVersion extends BaseModel
for recipe in _.values @_recipes
continue unless recipe.isConditionSatisfied()
continue unless recipe.hasAllTools()
continue if options.forCrafting and recipe.ignoreDuringCrafting
if recipe.itemSlug.matches itemSlug
primaryRecipes.push recipe
@@ -64,11 +64,11 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
@_recipeData = null
_command_ignoreDuringCrafting: (value)->
if not @_itemData? then throw new Error 'cannot declare "ignoreDuringCraft" before "item"'
if @_itemData.ignoreDuringCrafting? then throw new Error 'duplicate declaration of "ignoreDuringCraft"'
if not (value in ['yes', 'no']) then throw new Error 'ignoreDuringCraft must be either "yes" or "no"'
if not @_recipeData? then throw new Error 'cannot declare "ignoreDuringCrafting" before "recipe"'
if @_recipeData.ignoreDuringCrafting? then throw new Error 'duplicate declaration of "ignoreDuringCrafting"'
if not (value in ['yes', 'no']) then throw new Error 'ignoreDuringCrafting must be either "yes" or "no"'
@_itemData.ignoreDuringCrafting = (value is 'yes')
@_recipeData.ignoreDuringCrafting = (value is 'yes')
_command_input: (inputNames...)->
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
@@ -182,11 +182,12 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
if not recipeData.pattern? then throw new Error 'the "pattern" declaration is required'
recipe = new Recipe
condition: recipeData.condition
input: @_buildStackList modVersion, recipeData.input, recipeData.pattern
output: @_buildStackList modVersion, recipeData.output
pattern: recipeData.pattern
tools: @_buildStackList modVersion, recipeData.tools
condition: recipeData.condition
ignoreDuringCrafting: recipeData.ignoreDuringCrafting
input: @_buildStackList modVersion, recipeData.input, recipeData.pattern
output: @_buildStackList modVersion, recipeData.output
pattern: recipeData.pattern
tools: @_buildStackList modVersion, recipeData.tools
modVersion.addRecipe recipe
return recipe
@@ -311,6 +312,7 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
.push 'extras: '
.call => @_unparseStackList builder, extraOutputs
.line()
.onlyIf recipe.ignoreDuringCrafting, => builder.line 'ignoreDuringCrafting: yes'
.push 'input: '
.loop inputNames
.line()
+5 -4
View File
@@ -29,10 +29,11 @@ module.exports = class Recipe extends BaseModel
attributes.pattern = @_parsePattern attributes.pattern
attributes.condition ?= null
attributes.modVersion ?= null
attributes.tools ?= []
options.logEvents ?= false
attributes.condition ?= null
attributes.ignoreDuringCrafting ?= false
attributes.modVersion ?= null
attributes.tools ?= []
options.logEvents ?= false
super attributes, options
@_computeQuantities attributes.pattern
+4 -2
View File
@@ -108,8 +108,10 @@ module.exports = class SimpleInventory
stack.quantity -= quantity
if stack.quantity is 0
delete @_stacks[itemSlug]
@_itemSlugs = (s for s in @_itemSlugs when not ItemSlug.equal(s, itemSlug))
for currentItemSlug, index in @_itemSlugs
if ItemSlug.equal itemSlug, currentItemSlug
@_itemSlugs.splice index, 1
break
return this
+1 -10
View File
@@ -16,16 +16,7 @@
.panel
.view__inventory.large.editable
//- .tools.section.hideable.hidden
//- h2 Tools
//- .panel
//- .inUse
//- h3 In Use
//- .view__inventory.large
//-
//- .options
//- h3 More Options
//- .view__inventory.large
.view__craftsman_working.section
.ingredients.section.hideable.hidden
h2 Ingredients
+15
View File
@@ -0,0 +1,15 @@
//-
//- Crafting Guide - item_group.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- All rights reserved.
//-
.view__item_group.section.hideable.hidden
.content
.waiting
img(src='/images/wait.gif')
.message
p
.count
p
+35
View File
@@ -0,0 +1,35 @@
/*
Crafting Table - crafting_table.scss
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
*/
.view__craftsman_working .content {
align-items: center;
background: $color-white;
box-shadow: none;
display: flex;
flex-direction: column;
padding: 3em;
.waiting {
margin-bottom: 3em;
}
.message {
margin-bottom: 1em;
p {
font-family: $font-family-header;
font-size: $font-size-large;
}
}
.count {
p {
font-family: $font-family-header;
font-size: $font-size-normal;
}
}
}
+3 -2
View File
@@ -8,8 +8,9 @@ All rights reserved.
@import 'adsense';
@import 'browse_page';
@import 'crafting_grid';
@import 'craft_page';
@import 'crafting_table';
@import 'craft_page';
@import 'craftsman_working';
@import 'feedback';
@import 'full_recipe';
@import 'home_page';
@@ -27,8 +28,8 @@ All rights reserved.
@import 'mod_pack';
@import 'mod_page';
@import 'mod_selector';
@import 'stack';
@import 'slot';
@import 'stack';
@import 'step';
@import 'tutorial';
@import 'video';