This commit is contained in:
Andrew Miner
2015-10-31 13:21:14 -07:00
parent 74e95e614b
commit 4e95ab998f
28 changed files with 948 additions and 471 deletions
+4
View File
@@ -94,6 +94,10 @@ GitHub.file.itemDescription = {}
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
exports.Login = Login = {}
Login.authorizeUrl = _.template "https://github.com/login/oauth/authorize" +
"?client_id=<%= clientId %>&scope=public_repo&state=<%= state %>"
@@ -32,45 +32,39 @@ module.exports = class CraftPageController extends PageController
@modPack = options.modPack
@storage = options.storage
@model.plan.on Event.change, => @tryRefresh()
@model.craftsman.on Event.change, => @tryRefresh()
# Event Methods ################################################################################
onCraftTool: (itemSlug)->
# TODO: implement this
onHaveInventoryChanged: ->
@storage.store 'crafting-plan:have', @model.plan.have.unparse()
@storage.store 'crafting-plan:have', @model.craftsman.have.unparse()
onMoveNeedToHave: (itemSlug)->
quantity = @model.plan.need.quantityOf itemSlug
@model.plan.have.add itemSlug, quantity
quantity = @model.craftsman.need.quantityOf itemSlug
@model.craftsman.have.add itemSlug, quantity
@onHaveInventoryChanged()
onRemoveFromHaveInventory: (itemSlug)->
@model.plan.have.remove itemSlug
@model.craftsman.have.remove itemSlug
@onHaveInventoryChanged()
onRemoveFromWant: (itemSlug)->
@model.plan.want.remove itemSlug
@model.craftsman.want.remove itemSlug
@onWantInventoryChange()
onStopUsingTool: (itemSlug)->
# TODO: implement this
onStepComplete: (stepController)->
step = stepController.model
for stack in step.recipe.output
@model.plan.have.add stack.itemSlug, stack.quantity * step.multiplier
@model.craftsman.have.add stack.itemSlug, stack.quantity * step.multiplier
@onHaveInventoryChanged()
onWantInventoryChange: ->
text = @model.plan.want.unparse()
text = @model.craftsman.want.unparse()
url = Url.crafting inventoryText:text
router.navigate url
if @model.plan.want.isEmpty
@model.plan.have.clear()
if @model.craftsman.want.isEmpty
@model.craftsman.have.clear()
@onHaveInventoryChanged()
# PageController Overrides #####################################################################
@@ -89,24 +83,15 @@ module.exports = class CraftPageController extends PageController
@wantInventoryController = @addChild InventoryController, '.want .view__inventory',
imageLoader: @imageLoader
isAcceptable: (item)=> item.isCraftable
model: @model.plan.want
model: @model.craftsman.want
modPack: @modPack
firstButtonType: 'remove'
@wantInventoryController.on Event.button.first, (c, s)=> @onRemoveFromWant(s)
@wantInventoryController.on Event.change, (c)=> @onWantInventoryChange()
# @toolsInUseController = @addChild InventoryController, '.tools .view__inventory',
# imageLoader: @imageLoader
# model: @model.plan.toolsInUse
# modPack: @modPack
# firstButtonType: 'down'
# secondButtonType: 'up'
# @toolsInUseController.on Event.button.first, (c, s)=> @onStopUsingTool(s)
# @toolsInUseController.of Event.button.second, (c, s)=> @onCraftTool(s)
@haveInventoryController = @addChild InventoryController, '.have .view__inventory',
imageLoader: @imageLoader
model: @model.plan.have
model: @model.craftsman.have
modPack: @modPack
firstButtonType: 'down'
@haveInventoryController.on Event.button.first, (c, s)=>
@@ -116,7 +101,7 @@ module.exports = class CraftPageController extends PageController
@needInventoryController = @addChild InventoryController, '.need .view__inventory',
editable: false
imageLoader: @imageLoader
model: @model.plan.need
model: null
modPack: @modPack
firstButtonType: 'up'
@needInventoryController.on Event.button.first, (c, s)=> @onMoveNeedToHave(s)
@@ -131,11 +116,12 @@ module.exports = class CraftPageController extends PageController
super
onWillRender: ->
@model.plan.have.clear()
@model.plan.have.parse @storage.load('crafting-plan:have')
@model.craftsman.have.clear()
@model.craftsman.have.parse @storage.load('crafting-plan:have')
super
refresh: ->
@needInventoryController.model = @model.craftsman.plan?.need
@_refreshSectionVisibility()
@_refreshSteps()
@adsenseController.fillAdPositions()
@@ -150,10 +136,10 @@ module.exports = class CraftPageController extends PageController
# Private Methods ################################################################################
_isStepCompletable: (controller)->
return not @model.plan.want.hasAtLeast controller.model.outputItemSlug
return not @model.craftsman.want.hasAtLeast controller.model.outputItemSlug
_refreshSectionVisibility: ->
if @model.plan.want.isEmpty
if @model.craftsman.want.isEmpty
for $el in [@$toolsSection, @$ingredientsSection, @$stepsSection]
@hide $el
@show @$instructionsSection
@@ -163,10 +149,11 @@ module.exports = class CraftPageController extends PageController
@hide @$instructionsSection
_refreshSteps: ->
steps = @model.craftsman.plan?.steps or []
@_stepControllers ?= []
index = 0
for step in @model.plan.steps
for step in steps
controller = @_stepControllers[index]
if not controller?
controller = new StepController
@@ -1,95 +0,0 @@
###
Crafting Guide - crafting_table_controller.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
ImageLoader = require './image_loader'
MinimalRecipeController = require './minimal_recipe_controller'
_ = require 'underscore'
{Duration} = require '../constants'
{Event} = require '../constants'
########################################################################################################################
module.exports = class CraftingTableController extends BaseController
constructor: (options={})->
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
if not options.model? then throw new Error 'options.model is required'
if not options.modPack? then throw new Error 'options.modPack is required'
options.templateName = 'crafting_table'
super options
@imageLoader = options.imageLoader
@modPack = options.modPack
# Event Methods ################################################################################
onNextClicked: ->
@model.stepIndex += 1
onPrevClicked: ->
@model.stepIndex -= 1
onReportProblem: ->
itemList = @model.plan.want.unparse()
toolsMessage = if @model.plan.includingTools then '(including tools)' else ''
message = "When I was on step #{@model.stepIndex + 1} of making:
\n\n#{itemList}#{toolsMessage}\n\nI noticed that...\n"
global.feedbackController.enterFeedback message
# BaseController Overrides #####################################################################
onDidRender: ->
@recipeController = @addChild MinimalRecipeController, '.view__minimal_recipe',
imageLoader: @imageLoader
modPack: @modPack
@$next = @$('.next')
@$prev = @$('.prev')
@$problemControl = @$('.problem')
@$title = @$('h2 p')
@$tool = @$('.tool p')
@$multiplier = $('<p class="multiplier"></p>')
@$('.output').append @$multiplier
@defaultTitle = @$title.html()
super
refresh: ->
@$prev.removeClass 'enabled'
@$next.removeClass 'enabled'
if @model.hasSteps
if @model.hasPrevStep then @$prev.addClass 'enabled'
if @model.hasNextStep then @$next.addClass 'enabled'
@$title.html "Step #{@model.stepIndex + 1} of #{@model.stepCount}"
else
@$title.html @defaultTitle
currentStep = @model.currentStep
@recipeController.model = currentStep?.recipe
if currentStep?.multiplier > 1
@$multiplier.html "×#{currentStep.multiplier}"
else
@$multiplier.html ''
if not (@model.hasSteps and global.feedbackController?)
@$problemControl.addClass 'hidden'
else
@$problemControl.removeClass 'hidden'
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click .next': 'onNextClicked'
'click .prev': 'onPrevClicked'
'click .problem a': 'onReportProblem'
@@ -33,7 +33,6 @@ module.exports = class InventoryController extends BaseController
constructor: (options={})->
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
if not options.model? then throw new Error 'options.model is required'
if not options.modPack? then throw new Error 'options.modPack is required'
@imageLoader = options.imageLoader
@@ -57,6 +56,8 @@ module.exports = class InventoryController extends BaseController
# Event Methods ################################################################################
onClearButtonClicked: ->
return unless @model?
@model.clear()
@trigger Event.clear, this
@trigger Event.change, this
@@ -65,6 +66,8 @@ module.exports = class InventoryController extends BaseController
@trigger Event.button.first, this, stackController?.model?.itemSlug
onItemChosen: (itemSlug)->
return unless @model?
@model.add itemSlug, 1
@trigger Event.add, this, itemSlug
@trigger Event.change, this
@@ -95,11 +98,12 @@ module.exports = class InventoryController extends BaseController
@$icon.attr 'src', @icon
@$title.html @title
@$clearButton.disabled = @model.isEmpty
@_refreshStacks()
if @model.isEmpty
@$clearButton.disabled = @model?.isEmpty
if not @model or @model.isEmpty
@show @$emptyPlaceholder
else
@hide @$emptyPlaceholder
@@ -120,27 +124,28 @@ module.exports = class InventoryController extends BaseController
@_stackControllers ?= []
index = 0
@model.each (stack)=>
controller = @_stackControllers[index]
if not controller?
controller = new StackController
editable: @editable
firstButtonType: @firstButtonType
imageLoader: @imageLoader
model: stack
modPack: @modPack
secondButtonType: @secondButtonType
shouldEnableButton: @shouldEnableButton
controller.on Event.change, => @trigger Event.change, this
controller.on Event.button.first, (c)=> @onFirstButtonClicked(c)
controller.on Event.button.second, (c)=> @onSecondButtonClicked(c)
controller.render()
if @model?
@model.each (stack)=>
controller = @_stackControllers[index]
if not controller?
controller = new StackController
editable: @editable
firstButtonType: @firstButtonType
imageLoader: @imageLoader
model: stack
modPack: @modPack
secondButtonType: @secondButtonType
shouldEnableButton: @shouldEnableButton
controller.on Event.change, => @trigger Event.change, this
controller.on Event.button.first, (c)=> @onFirstButtonClicked(c)
controller.on Event.button.second, (c)=> @onSecondButtonClicked(c)
controller.render()
@_stackControllers.push controller
@$itemContainer.append controller.$el
else
controller.model = stack
index += 1
@_stackControllers.push controller
@$itemContainer.append controller.$el
else
controller.model = stack
index += 1
while @_stackControllers.length > index
@_stackControllers.pop().remove()
@@ -5,11 +5,11 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
BaseController = require './base_controller'
{Event} = require '../constants'
InventoryController = require './inventory_controller'
MinimalRecipeController = require './minimal_recipe_controller'
_ = require 'underscore'
{Event} = require '../constants'
########################################################################################################################
@@ -56,7 +56,7 @@ module.exports = class StepController extends BaseController
return super
refresh: ->
itemDisplay = @modPack.findItemDisplay @model.outputItemSlug
itemDisplay = @modPack.findItemDisplay @model.recipe.output[0].itemSlug
@$header.html "#{@model.number}. #{itemDisplay.itemName}"
@inventoryController.model = @model.inventory
+11 -13
View File
@@ -5,34 +5,32 @@ Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
CraftingPlan = require './crafting_plan'
CraftingTable = require './crafting_table'
{Event} = require '../constants'
Inventory = require './inventory'
ModPack = require './mod_pack'
BaseModel = require './base_model'
Craftsman = require './crafting/craftsman'
{Event} = require '../constants'
Inventory = require './inventory'
ModPack = require './mod_pack'
########################################################################################################################
module.exports = class CraftPage extends BaseModel
constructor: (attributes={}, options={})->
attributes.modPack ?= new ModPack
attributes.params ?= null
attributes.plan ?= new CraftingPlan modPack:attributes.modPack
attributes.table ?= new CraftingTable plan:attributes.plan
if not attributes.modPack then throw new Error 'attributes.modPack is required'
attributes.params ?= null
attributes.craftsman ?= new Craftsman attributes.modPack
super attributes, options
@modPack.on Event.change, => @_consumeParams()
@on Event.change + ':params', => @_consumeParams()
@plan.on Event.change, => @trigger Event.change, this
@craftsman.on Event.change, => @trigger Event.change, this
# Private Methods ##############################################################################
_consumeParams: ->
return unless @params?
@plan.want.clear()
@craftsman.want.clear()
if not @params.inventoryText?
@params = null
else
@@ -42,7 +40,7 @@ module.exports = class CraftPage extends BaseModel
inventory.each (stack)=>
item = @modPack.findItem stack.itemSlug, enableAsNeeded:true
return unless item? and item.isCraftable
@plan.want.add stack.itemSlug, stack.quantity
@craftsman.want.add stack.itemSlug, stack.quantity
inventory.remove stack.itemSlug
if inventory.isEmpty then @params = null
+84 -37
View File
@@ -5,46 +5,90 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
Inventory = require '../inventory'
_ = require '../../underscore_mixins'
SimpleInventory = require '../simple_inventory'
########################################################################################################################
module.exports = class CraftingPlan
constructor: (steps, wanted)->
constructor: (steps, want, modPack)->
if not modPack? then throw new Error 'modPack is required'
if not steps? then throw new Error 'steps is required'
if not wanted? then throw new Error 'wanted is required'
if not want? then throw new Error 'want is required'
@_produced = new Inventory
@_required = new Inventory
@_steps = steps
@_wanted = wanted
@_made = null
@_modPack = modPack
@_need = null
@_rawScores = {}
@_scores = {}
@_steps = steps
@_want = want
@_computeRequired()
@_numberSteps()
# Public Methods ###############################################################################
computeRequired: ->
@_need = new SimpleInventory modPack:@_modPack
@_made = new SimpleInventory modPack:@_modPack
@_need.addInventory @_want
for i in [@_steps.length-1..0] by -1
step = @_steps[i]
step.multiplier = 0
for stack in step.recipe.output
if not stack?
throw new Error 'stack should not be null here'
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
while @_need.quantityOf(qualifiedSlug) > 0
@_executeStep step
@_made.addInventory @_want
hasRawScore: (name)->
return @_rawScores[name]?
getRawScore: (name)->
return @_rawScores[name]
setRawScore: (name, rawScore)->
@_rawScores[name] = rawScore
hasScore: (name)->
return @_scores[name]?
getScore: (name)->
return @_scores[name]
setScore: (name, score)->
@_scores[name] = score
# Property Methods #############################################################################
Object.defineProperties @prototype,
length:
get: -> @steps.length
produced:
get: -> @_produced
required:
get: -> @_required
made:
get: -> @_made
need:
get: -> @_need
steps:
get: -> @_steps
wanted:
get: -> @_wanted
want:
get: -> @_want
# Object Overrides #############################################################################
toString: ->
result = ["To Make:"]
@_wanted.each (stack)->
@_want.each (stack)->
result.push " #{stack}"
result.push "Start with:"
@_required.each (stack)->
@_need.each (stack)->
result.push " #{stack}"
result.push "Use these recipes:"
@@ -52,44 +96,47 @@ module.exports = class CraftingPlan
result.push " #{step}"
result.push "To produce:"
@_produced.each (stack)->
@_made.each (stack)->
result.push " #{stack}"
if _.keys(@_rawScores).length > 0
result.push "Scores:"
for criteria, score of @_rawScores
if @_scores[criteria]?
result.push " #{criteria}: #{score} (#{@_scores[criteria]})"
else
result.push " #{criteria}: #{score}"
return result.join '\n'
# Private Methods ##############################################################################
_computeRequired: ->
@_required.addInventory @_wanted
for i in [@_steps.length-1..0] by -1
step = @_steps[i]
for stack in step.recipe.output
while @_required.quantityOf(stack.itemSlug) > 0
@_executeStep step
@_produced.addInventory @_wanted
_executeStep: (step)->
step.repeat += 1
step.multiplier += 1
recipe = step.recipe
for stack in recipe.input
available = @_produced.quantityOf stack.itemSlug
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
available = @_made.quantityOf qualifiedSlug
required = recipe.getQuantityRequired stack.itemSlug
consumed = Math.min required, available
deficit = required - consumed
@_produced.remove stack.itemSlug, consumed
@_required.add stack.itemSlug, deficit
@_made.remove qualifiedSlug, consumed
@_need.add qualifiedSlug, deficit
for stack in recipe.output
deficit = @_required.quantityOf stack.itemSlug
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
deficit = @_need.quantityOf qualifiedSlug
created = recipe.getQuantityProduced stack.itemSlug
replenished = Math.min deficit, created
surplus = created - replenished
@_produced.add stack.itemSlug, surplus
@_required.remove stack.itemSlug, replenished
@_made.add qualifiedSlug, surplus
@_need.remove qualifiedSlug, replenished
_numberSteps: ->
for step, i in @_steps
step.number = i + 1
@@ -5,25 +5,55 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
SimpleInventory = require '../simple_inventory'
########################################################################################################################
module.exports = class CraftingStep
constructor: (recipe, repeat=0)->
constructor: (recipe, modPack, multiplier=0)->
if not recipe? then throw new Error 'recipe is required'
if repeat < 0 then throw new Error 'repeat must be at least 1'
if not modPack? then throw new Error 'modPack is required'
if multiplier < 0 then throw new Error 'multiplier must be at least 1'
@_recipe = recipe
@repeat = repeat
@number = null
@_inventory = null
@_modPack = modPack
@_multiplier = multiplier
@_recipe = recipe
# Property Methods #############################################################################
Object.defineProperties @prototype,
inventory:
get: ->
if not @_inventory?
@_refreshInventory()
return @_inventory
recipe:
get: -> @_recipe
multiplier:
get: -> @_multiplier
set: (newMultiplier)->
@_multiplier = newMultiplier
@_refreshInventory() if @_inventory?
slug:
get: -> "#{@multiplier}x #{@_recipe.slug}"
# Object Overrides #############################################################################
toString: ->
return "#{@repeat}x #{@recipe.slug}"
return @slug
# Private Methods ##############################################################################
_refreshInventory: ->
@_inventory = new SimpleInventory
for stack in @_recipe.input
qualifiedSlug = @_modPack.qualifySlug stack.itemSlug
@_inventory.add qualifiedSlug, @multiplier * @_recipe.getQuantityRequired stack.itemSlug
+140
View File
@@ -0,0 +1,140 @@
###
Crafting Guide - craftsman.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
BaseModel = require '../base_model'
{Event} = require '../../constants'
GraphBuilder = require './graph_builder'
Inventory = require '../inventory'
PlanBuilder = require './plan_builder'
PlanEvaluator = require './plan_evaluator'
w = require 'when'
########################################################################################################################
module.exports = class Craftsman extends BaseModel
@::ANALYZE_STEP_INCREMENT = 100
@::GRAPH_STEP_INCREMENT = 10
@::PLAN_STEP_INCREMENT = 50
@::STAGE =
WAITING: 'waiting'
GRAPHING: 'examining recipes'
PLANNING: 'computing plans'
ANALYZING: 'analyzing plans'
COMPLETE: 'complete'
constructor: (modPack)->
if not modPack? then throw new Error 'modPack is required'
attributes =
paused: false
stage: @STAGE.WAITING
stageCount: 0
super attributes, {}
@_modPack = modPack
reset = _.throttle (=> @reset()), 100
@_have = new Inventory
@_have.on Event.change, reset
@_want = new Inventory
@_want.on Event.change, reset
@on Event.change + ':paused', reset
@on Event.change + ':stage', => logger.info "Craftsman has started #{@stage}..."
@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()
have = new Inventory {modPack:@_modPack}, clone:@_have
have.localize()
logger.info -> "Craftsman starting to build #{want} from #{have}"
@_graphBuilder = new GraphBuilder modPack:@_modPack, want:want, have:have
@stage = @STAGE.GRAPHING
@stageCount = 0
else if not @_graphBuilder.complete
@_graphBuilder.expandGraph @GRAPH_STEP_INCREMENT
@stageCount = @_graphBuilder.stepCount
else if not @_planBuilder?
logger.debug => "Craftsman finished computing graph:\n#{@_graphBuilder.rootNode}"
@_planBuilder = new PlanBuilder @_graphBuilder.rootNode, @_modPack, want:@_graphBuilder.want
@stage = @STAGE.PLANNING
@stageCount = 0
else if not @_planBuilder.complete
@_planBuilder.producePlans @PLAN_STEP_INCREMENT
@stageCount = @_planBuilder.plans.length
else if not @_planEvaluator?
@_planEvaluator = new PlanEvaluator @_planBuilder.plans
@stage = @STAGE.ANALYZING
@stageCount = 0
else if not @_planEvaluator.complete
@_planEvaluator.scorePlans @ANALYZE_STEP_INCREMENT
@stageCount = @_planEvaluator.lastScored
else
@_plans = [
@_planEvaluator.findBestPlan PlanEvaluator::CRITERIA.FEWEST_STEPS
@_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
@_scheduleNextWork()
return @complete
reset: ->
@_graphBuilder = null
@_planBuilder = null
@_planEvaluator = null
@_plans = null
@stage = @STAGE.WAITING
@stageCount = 0
@_scheduleNextWork()
# Property Methods #############################################################################
Object.defineProperties @prototype,
complete:
get: -> @_plans?
have:
get: -> @_have
plan:
get: -> @_plans?[0]
want:
get: -> @_want
# Private Methods ##############################################################################
_scheduleNextWork: ->
return if @paused
return if @want.isEmpty
return if @complete
_.defer => @work()
@@ -7,6 +7,7 @@ All rights reserved.
Inventory = require '../inventory'
InventoryNode = require './inventory_node'
{Limits} = require '../../constants'
########################################################################################################################
@@ -14,12 +15,15 @@ module.exports = class GraphBuilder
constructor: (options={})->
if not options.modPack? then throw new Error 'options.modPack is required'
if not options.want? then throw new Error 'options.want is required'
@modPack = options.modPack
@_wanted = options.wanted ?= new Inventory
@_maximumGraphSize = Limits.maximumGraphSize
@_modPack = options.modPack
@_stepCount = 0
@_want = options.want
@_wanted.on 'change', => @reset()
@reset()
@_rootNode = new InventoryNode modPack:@_modPack, inventory:@_want
@_queue = [@_rootNode]
# Public Methods ###############################################################################
@@ -38,9 +42,6 @@ module.exports = class GraphBuilder
@_stepCount += 1
reset: ->
@_rootNode = new InventoryNode modPack:@modPack, inventory:@wanted
@_queue = [@_rootNode]
@_stepCount = 0
# Property Methods #############################################################################
@@ -48,6 +49,7 @@ module.exports = class GraphBuilder
complete:
get: ->
return true if @_stepCount > @_maximumGraphSize
return false unless @_rootNode?
return false unless @_queue?
return false unless @_queue.length is 0
@@ -59,8 +61,8 @@ module.exports = class GraphBuilder
stepCount:
get: -> @_stepCount
wanted:
get: -> @_wanted
want:
get: -> @_want
# Object Overrides ############################################################################
+16 -3
View File
@@ -17,7 +17,8 @@ module.exports = class ItemNode extends CraftingNode
@::TYPE = CraftingNode::TYPES.ITEM
constructor: (options={})->
if not options.item? then throw new Error 'options.item is required'
if not options.item?
throw new Error 'options.item is required'
super options
@item = options.item
@@ -32,7 +33,7 @@ module.exports = class ItemNode extends CraftingNode
isGatherable: ->
return true if @item.isGatherable
return true unless @getRecipes().length > 0
return true if @getRecipes().length is 0
return false
Object.defineProperties @prototype,
@@ -43,7 +44,7 @@ module.exports = class ItemNode extends CraftingNode
_createChildren: (result=[])->
recipes = @getRecipes()
return [] unless recipes.length > 0
return [] if @gatherable
for recipe in recipes
child = new RecipeNode modPack:@modPack, recipe:recipe
@@ -62,7 +63,9 @@ module.exports = class ItemNode extends CraftingNode
return false
_checkValidity: ->
return false if @_isRepeatedItem()
return true unless @children.length > 0
for child in @children
return true if child.valid
return false
@@ -79,3 +82,13 @@ module.exports = class ItemNode extends CraftingNode
for child in @children
parts.push child.toString indent:nextIndent
return parts.join '\n'
# Private Methods ##############################################################################
_isRepeatedItem: ->
nextParent = @parent
while nextParent?
return true if nextParent.item is @item
nextParent = nextParent.parent
return false
+21 -11
View File
@@ -9,20 +9,23 @@ CraftingNode = require './crafting_node'
CraftingPlan = require './crafting_plan'
CraftingStep = require './crafting_step'
Inventory = require '../inventory'
{Limits} = require '../../constants'
########################################################################################################################
module.exports = class PlanBuilder
constructor: (rootNode, options={})->
constructor: (rootNode, modPack, options={})->
if not rootNode? then throw new Error 'rootNode is required'
@wanted = options.wanted
@want = options.want
@_choiceNodes = []
@_complete = false
@_plans = []
@_rootNode = rootNode
@_choiceNodes = []
@_complete = false
@_maxPlanCount = Limits.maximumPlanCount
@_modPack = modPack
@_plans = []
@_rootNode = rootNode
@_isolateChoiceNodes()
@@ -31,6 +34,9 @@ module.exports = class PlanBuilder
producePlans: (maxPlans=null)->
maxPlans = if maxPlans then @plans.length + maxPlans else Number.MAX_VALUE
if @plans.length >= @_maxPlanCount
@_complete = true
while not @complete and (@plans.length < maxPlans)
plan = @_captureCurrentPlan()
if plan?
@@ -47,12 +53,16 @@ module.exports = class PlanBuilder
complete:
get: -> @_complete
maxPlanCount:
get: -> @_maxPlanCount
set: (value)-> @_maxPlanCount = value
plans:
get: -> @_plans
wanted:
get: -> @_wanted
set: (wanted)-> @_wanted = wanted or new Inventory
want:
get: -> @_want
set: (want)-> @_want = want or new Inventory
# Private Methods ##############################################################################
@@ -83,9 +93,9 @@ module.exports = class PlanBuilder
continue if seenRecipes[recipeSlug]?
seenRecipes[recipeSlug] = true
steps.push new CraftingStep node.recipe
steps.push new CraftingStep node.recipe, @_modPack
plan = new CraftingPlan steps, @_wanted
plan = new CraftingPlan steps, @_want, @_modPack
return plan
_incrementChoiceNodes: ->
@@ -0,0 +1,98 @@
###
Crafting Guide - plan_evaluator.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
########################################################################################################################
module.exports = class PlanEvaluator
@::CRITERIA =
FEWEST_STEPS: 'fewest steps'
LEAST_MATERIALS: 'least materials'
constructor: (plans)->
if not plans? then throw new Error 'plans is required'
@_plans = plans
@_lastScored = 0
@_normalized = false
# Public Methods ###############################################################################
findBestPlan: (mainCriteria)->
criteriaList = [mainCriteria].concat (criteria for key, criteria of @CRITERIA when criteria isnt mainCriteria)
@_plans.sort (a, b)->
for criteria in criteriaList
scoreA = a.getScore criteria
scoreB = b.getScore criteria
if scoreA isnt scoreB
return if scoreA > scoreB then -1 else +1
return 0
return @_plans[0]
scorePlans: (count)->
count ?= @_plans.length
return if @complete
maxPlanIndex = Math.min @_lastScored + count, @_plans.length - 1
for i in [@_lastScored..maxPlanIndex]
@_plans[i].computeRequired()
@_scorePlan @_plans[i]
@_lastScored = i
if @complete then @_normalizeScores()
return @complete
# Property Methods #############################################################################
Object.defineProperties @prototype,
complete:
get: -> @_lastScored is @_plans.length - 1
lastScored:
get: -> @_lastScored
# Private Methods ##############################################################################
_computeFewestStepsScore: (plan)->
total = 0
for step in plan.steps
total += step.multiplier
plan.setRawScore @CRITERIA.FEWEST_STEPS, total
_computeLeastMaterialsScore: (plan)->
total = 0
plan.need.each (stack)->
total += stack.quantity
plan.setRawScore @CRITERIA.LEAST_MATERIALS, total
_normalizeScores: ->
for key, criteria of @CRITERIA
maxScore = 0
minScore = Number.MAX_VALUE
for plan in @_plans
continue unless plan.hasRawScore criteria
maxScore = Math.max maxScore, plan.getRawScore(criteria)
minScore = Math.min minScore, plan.getRawScore(criteria)
adjustedMaxScore = maxScore - minScore
for plan in @_plans
continue unless plan.hasRawScore criteria
if adjustedMaxScore > 0
adjustedRawScore = plan.getRawScore(criteria) - minScore
plan.setScore criteria, 1.0 - (1.0 * adjustedRawScore / adjustedMaxScore)
else
plan.setScore criteria, 1.0
_scorePlan: (plan)->
@_computeFewestStepsScore plan
@_computeLeastMaterialsScore plan
-72
View File
@@ -1,72 +0,0 @@
###
Crafting Guide - crafting_table.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
{Event} = require '../constants'
########################################################################################################################
module.exports = class CraftingTable extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.plan? then throw new Error "attributes.plan is required"
super attributes, options
@plan.on 'change', => @reset()
@_stepIndex = 0
Object.defineProperties this, {
currentStep: { get:@getCurrentStep }
hasNextStep: { get:@hasNextStep }
hasPrevStep: { get:@hasPrevStep }
hasSteps: { get:@hasSteps }
stepCount: { get:@getStepCount }
stepIndex: { get:@getStepIndex, set:@setStepIndex }
}
# Public Methods ###############################################################################
reset: ->
@stepIndex = 0
return this
# Property Methods #############################################################################
hasNextStep: ->
return @_stepIndex + 1 < @plan.steps.length
hasPrevStep: ->
return @_stepIndex > 0
hasSteps: ->
return @plan.steps.length > 0
getCurrentStep: ->
return @plan.steps[@_stepIndex]
getStepIndex: ->
return @_stepIndex
setStepIndex: (newStepIndex)->
oldStepIndex = @_stepIndex
newStepIndex = Math.max 0, Math.min @plan.steps.length - 1, newStepIndex
@_stepIndex = newStepIndex
@trigger Event.change + ':stepIndex', this, oldStepIndex, newStepIndex
@trigger Event.change, this
return this
getStepCount: ->
return 0 unless @plan.steps?
return @plan.steps.length
# Object Overrides #############################################################################
toString: ->
return "#{@constructor.name} (#{@cid}) { plan:#{@plan}, step:#{@_stepIndex} }"
+5 -3
View File
@@ -21,6 +21,9 @@ module.exports = class Inventory extends BaseModel
attributes.modPack ?= null
@clear()
if options.clone?
@addInventory options.clone
# Class Methods ################################################################################
@Delimiters =
@@ -100,8 +103,8 @@ module.exports = class Inventory extends BaseModel
quantityOf: (itemSlug)->
stack = @_stacks[itemSlug]
return 0 unless stack?
return stack.quantity
return stack.quantity if stack
return 0
remove: (itemSlug, quantity=null)->
stack = @_stacks[itemSlug]
@@ -172,7 +175,6 @@ module.exports = class Inventory extends BaseModel
total += stack.quantity
return total
Object.defineProperties @prototype,
isEmpty: { get:@prototype.getIsEmpty }
totalQuantity: { get:@prototype.getTotalQuantity }
+9 -8
View File
@@ -21,15 +21,16 @@ module.exports = class Item extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.name? then throw new Error 'attributes.name is required'
attributes.description ?= null
attributes.group ?= Item.Group.Other
attributes.isGatherable ?= false
attributes.modVersion ?= null
attributes.officialUrl ?= null
attributes.slug ?= ItemSlug.slugify attributes.name
attributes.videos ?= []
attributes.description ?= null
attributes.group ?= Item.Group.Other
attributes.ignoreDuringCrafting ?= false
attributes.isGatherable ?= false
attributes.modVersion ?= null
attributes.officialUrl ?= null
attributes.slug ?= ItemSlug.slugify attributes.name
attributes.videos ?= []
options.logEvents ?= false
options.logEvents ?= false
super attributes, options
@on Event.change + ':modVersion', =>
+5 -6
View File
@@ -5,11 +5,10 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
CraftingPlan = require './crafting_plan'
_ = require 'underscore'
{Event} = require '../constants'
{Url} = require '../constants'
_ = require 'underscore'
BaseModel = require './base_model'
{Event} = require '../constants'
{Url} = require '../constants'
########################################################################################################################
@@ -57,4 +56,4 @@ module.exports = class ItemPage extends BaseModel
result = _.values result
return null unless result.length > 0
return result.sort (a, b)-> a.compareTo b
return result.sort (a, b)-> a.compareTo b
+32 -7
View File
@@ -20,24 +20,35 @@ module.exports = class ModPack extends BaseModel
super attributes, options
@_mods = []
@_cache = {}
@on Event.change, => @_cache = {}
# Public Methods ###############################################################################
findItem: (itemSlug, options={})->
options.includeDisabled ?= false
key = "#{itemSlug}-#{options.includeDisabled}"
@_cache.itemBySlug ?= {}
item = @_cache.itemBySlug[key]
return item if item?
if itemSlug.isQualified
mod = @getMod itemSlug.mod
if mod?
item = mod.findItem itemSlug, options
return item if item?
for mod in @_mods
continue unless mod.enabled or options.includeDisabled
item = mod.findItem itemSlug, options
return item if item?
if not item?
for mod in @_mods
continue unless mod.enabled or options.includeDisabled
item = mod.findItem itemSlug, options
break if item?
return null
if item?
@_cache.itemBySlug[key] = item
return item
findItemByName: (name, options={})->
options.enableAsNeeded ?= false
@@ -82,10 +93,16 @@ module.exports = class ModPack extends BaseModel
return null
findRecipes: (itemSlug, result=[], options={})->
findRecipes: (itemSlug, options={})->
options.alwaysFromOwningMod ?= false
return null unless itemSlug?
key = "#{itemSlug}-#{options.alwaysFromOwningMod}"
@_cache.recipesBySlug ?= {}
result = @_cache.recipesBySlug[key]
return result if result?
result = []
for mod in @_mods
if not mod.enabled
owningMod = itemSlug.isQualified and (itemSlug.mod is mod.slug)
@@ -93,8 +110,16 @@ module.exports = class ModPack extends BaseModel
mod.findRecipes itemSlug, result, options
@_cache.recipesBySlug[key] = result
return if result.length > 0 then result else null
qualifySlug: (itemSlug)->
return itemSlug if itemSlug.isQualified
item = @findItem itemSlug
return item.slug if item?
return itemSlug
# Property Methods #############################################################################
addMod: (mod)->
@@ -63,6 +63,13 @@ 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"'
@_itemData.ignoreDuringCrafting = (value is 'yes')
_command_input: (inputNames...)->
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
if @_recipeData.input.length isnt 0 then throw new Error 'duplicate declaration of "input"'
@@ -148,11 +155,16 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
_buildItem: (modVersion, itemData)->
@_lineNumber = itemData.line
itemData.gatherable ?= false
itemData.recipes ?= []
itemData.gatherable ?= false
itemData.ignoreDuringCrafting ?= false
itemData.recipes ?= []
if itemData.type is 'new'
item = new Item name:itemData.name, isGatherable:itemData.gatherable, group:itemData.group
item = new Item
name: itemData.name,
ignoreDuringCrafting: itemData.ignoreDuringCrafting,
isGatherable: itemData.gatherable,
group: itemData.group
modVersion.addItem item
itemData.slug = item.slug
else
+55 -36
View File
@@ -35,7 +35,10 @@ module.exports = class Recipe extends BaseModel
options.logEvents ?= false
super attributes, options
@_computeQuantities attributes.pattern
@on Event.change + ':modVersion', => @_slug = null
@on Event.change + ':pattern', => @_patternCache = null
# Class Methods ################################################################################
@@ -77,24 +80,22 @@ module.exports = class Recipe extends BaseModel
getQuantityRequired: (itemSlug)->
total = 0
for i in [0...@pattern.length]
stack = @getStackAtSlot i
continue unless stack?
for stack, index in @input
if ItemSlug.equal stack.itemSlug, itemSlug
total += stack.quantity
total += @_quantities[index] * stack.quantity
return total
hasAllTools: (modPack)->
modPack ?= @modVersion?.mod?.modPack
return true unless modPack
return true unless modPack?
for stack in @tools
return false unless modPack.findItem stack.itemSlug
return true
isConditionSatisfied: (modPack)->
return true unless @condition
return true unless @condition?
modPack ?= @modVersion?.mod?.modPack
result = false
@@ -145,36 +146,38 @@ module.exports = class Recipe extends BaseModel
# Property Methods #############################################################################
getSlug: ->
if not @_slug?
builder = new StringBuilder
delimiterNeeded = false
for stack in @input
if delimiterNeeded then builder.push ','
delimiterNeeded = true
if stack.quantity > 1 then builder.push stack.quantity, ' '
builder.push stack.itemSlug.qualified
builder.push '>'
for stack in @tools
builder.push stack.itemSlug.qualified
builder.push '>'
delimiterNeeded = false
for stack in @output
if delimiterNeeded then builder.push ','
delimiterNeeded = true
if stack.quantity > 1 then builder.push stack.quantity, ' '
builder.push stack.itemSlug.qualified
@_slug = builder.toString()
return @_slug
Object.defineProperties @prototype,
slug: {get:@prototype.getSlug}
slug:
get: ->
if not @_slug?
builder = new StringBuilder
delimiterNeeded = false
for stack in @input
if delimiterNeeded then builder.push ','
delimiterNeeded = true
if stack.quantity > 1 then builder.push stack.quantity, ' '
builder.push stack.itemSlug.qualified
builder.push '>'
builder.push @pattern
builder.push '>'
for stack in @tools
builder.push stack.itemSlug.qualified
builder.push '>'
delimiterNeeded = false
for stack in @output
if delimiterNeeded then builder.push ','
delimiterNeeded = true
if stack.quantity > 1 then builder.push stack.quantity, ' '
builder.push stack.itemSlug.qualified
@_slug = builder.toString()
return @_slug
# Object Overrides #############################################################################
@@ -215,6 +218,22 @@ module.exports = class Recipe extends BaseModel
# Private Methods ##############################################################################
_computeQuantities: (pattern)->
quantityMap = {}
for c in pattern.split ''
continue if c is '.'
continue if c is ' '
if quantityMap[c]?
quantityMap[c] += 1
else
quantityMap[c] = 1
@_quantities = []
for i in [0...@input.length]
@_quantities.push quantityMap["#{i}"]
_parsePattern: (pattern)->
return unless pattern?
-26
View File
@@ -1,26 +0,0 @@
###
Crafting Guide - recipe_step.coffee
Copyright (c) 2014 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
########################################################################################################################
module.exports = class RecipeStep extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.recipe? then throw new Error 'attributes.recipe is required'
attributes.multiple ?= 1
super attributes, options
@recipe.on 'change', =>
@trigger 'change:recipe'
@trigger 'change'
# Public Methods ###############################################################################
getItemAt: (index)->
return @recipe.getItemAt index
+200
View File
@@ -0,0 +1,200 @@
###
Crafting Guide - inventory.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
ItemSlug = require './item_slug'
SimpleStack = require './simple_stack'
_ = require 'underscore'
########################################################################################################################
module.exports = class SimpleInventory
constructor: (attributes={}, options={})->
@clear()
if options.modPack?
@modPack = options.modPack
if options.clone?
@addInventory options.clone
# Class Methods ################################################################################
@Delimiters =
Item: '.'
Stack: ':'
# Public Methods ###############################################################################
add: (itemSlug, quantity=1)->
return this unless quantity > 0
@_add itemSlug, quantity
return this
addInventory: (inventory)->
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
return this
clear: (options={})->
@_stacks = {}
@_itemSlugs = []
clone: ->
inventory = new Inventory
inventory.addInventory this
return inventory
each: (callback)->
for itemSlug in @_itemSlugs
callback @_stacks[itemSlug]
getSlugs: ->
return @_itemSlugs[..]
hasAtLeast: (itemSlug, quantity=1)->
if quantity is 0 then return true
stack = @_stacks[itemSlug]
return false unless stack?
return stack.quantity >= quantity
localize: ->
if not @modPack? then throw new Error 'localize requires @modPack'
newSlugs = []
for itemSlug in @_itemSlugs
stack = @_stacks[itemSlug]
qualifiedSlug = @modPack.findItem(itemSlug)?.slug
if qualifiedSlug?
delete @_stacks[itemSlug]
newSlugs.push qualifiedSlug
@_stacks[qualifiedSlug] = stack
stack.itemSlug = qualifiedSlug
else
newSlugs.push itemSlug
@_itemSlugs = newSlugs
@_sort()
pop: ->
itemSlug = @_itemSlugs.pop()
return null unless itemSlug?
stack = @_stacks[itemSlug]
delete @_stacks[itemSlug]
return stack
quantityOf: (itemSlug)->
stack = @_stacks[itemSlug]
return stack.quantity if stack
return 0
remove: (itemSlug, quantity=null)->
stack = @_stacks[itemSlug]
return this unless stack?
quantity ?= stack.quantity
return this unless quantity > 0
if stack.quantity < quantity
throw new Error "cannot remove #{quantity}: only #{stack.quantity} #{itemSlug} in this inventory"
stack.quantity -= quantity
if stack.quantity is 0
delete @_stacks[itemSlug]
@_itemSlugs = (s for s in @_itemSlugs when not ItemSlug.equal(s, itemSlug))
return this
# Parsing Methods ##############################################################################
parse: (data)->
return this if not data? or data.length is 0
stacks = data.split Inventory.Delimiters.Stack
for stackText in stacks
stackParts = stackText.split Inventory.Delimiters.Item
if stackParts.length is 2
quantity = parseInt stackParts[0], 10
itemSlug = ItemSlug.slugify stackParts[1]
else if stackParts.length is 1
quantity = 1
itemSlug = ItemSlug.slugify stackParts[0]
else
throw new Error "expected #{stackText} to have 0 or 1 parts"
if itemSlug.qualified.length > 0
@add itemSlug, quantity
return this
unparse: (options={})->
parts = []
@each (stack)=>
slugText = stack.itemSlug.item
if @modPack?
item = @modPack.findItem ItemSlug.slugify slugText
if item? and item.slug.qualified isnt stack.itemSlug.qualified
slugText = stack.itemSlug.qualified
if stack.quantity is 1
parts.push slugText
else
parts.push "#{stack.quantity}#{SimpleInventory.Delimiters.Item}#{slugText}"
return parts.join SimpleInventory.Delimiters.Stack
# Property Methods #############################################################################
getIsEmpty: ->
return @_itemSlugs.length is 0
getTotalQuantity: ->
total = 0
@each (stack)->
total += stack.quantity
return total
Object.defineProperties @prototype,
isEmpty: { get:@prototype.getIsEmpty }
totalQuantity: { get:@prototype.getTotalQuantity }
# Object Overrides #############################################################################
toString: ->
result = [@constructor.name, " (", @cid, ") {items: ["]
needsDelimiter = false
@each (stack)->
if needsDelimiter then result.push ', '
result.push stack.toString()
needsDelimiter = true
result.push ']'
result.push '}'
return result.join ''
# Private Methods ##############################################################################
_add: (itemSlug, quantity=1)->
return unless itemSlug?
return unless quantity > 0
stack = @_stacks[itemSlug]
if not stack?
stack = new SimpleStack itemSlug:itemSlug, quantity:quantity
@_stacks[itemSlug] = stack
@_itemSlugs.push itemSlug
@_sort()
else
stack.quantity += quantity
_sort: ->
@_itemSlugs.sort (a, b)-> ItemSlug.compare a, b
-35
View File
@@ -1,35 +0,0 @@
###
Crafting Guide - step.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
Inventory = require './inventory'
{Event} = require '../constants'
########################################################################################################################
module.exports = class Step extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.outputItemSlug? then throw new Error 'attributes.outputItemSlug is required'
if not attributes.recipe? then throw new Error 'attributes.recipe is required'
attributes.inventory = new Inventory
attributes.number ?= 1
attributes.outputItemSlug ?= null
attributes.multiplier ?= 1
attributes.recipe ?= null
super attributes, options
@_computeInventory()
@on Event.change + ':multiplier', => @_computeInventory()
# Private Methods ##############################################################################
_computeInventory: ->
@inventory.clear()
for stack in @recipe.input
@inventory.add stack.itemSlug, stack.quantity * @multiplier