Add plan builder and tests
This commit is contained in:
@@ -5,10 +5,19 @@ Copyright (c) 2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
|
_ = require 'underscore'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
module.exports = class CraftingNode
|
module.exports = class CraftingNode
|
||||||
|
|
||||||
|
@::TYPES =
|
||||||
|
INVENTORY: 0
|
||||||
|
ITEM: 1
|
||||||
|
RECIPE: 2
|
||||||
|
|
||||||
|
@::TYPE = null
|
||||||
|
|
||||||
constructor: (options={})->
|
constructor: (options={})->
|
||||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||||
|
|
||||||
@@ -16,6 +25,8 @@ module.exports = class CraftingNode
|
|||||||
|
|
||||||
@_children = []
|
@_children = []
|
||||||
@_complete = null
|
@_complete = null
|
||||||
|
@_id = _.uniqueId 'node_'
|
||||||
|
@_rotations = 0
|
||||||
@_valid = null
|
@_valid = null
|
||||||
|
|
||||||
# Public Methods ###############################################################################
|
# Public Methods ###############################################################################
|
||||||
@@ -25,6 +36,7 @@ module.exports = class CraftingNode
|
|||||||
|
|
||||||
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
|
||||||
@@ -47,6 +59,10 @@ module.exports = class CraftingNode
|
|||||||
leave = visitor['onLeaveOtherNode']
|
leave = visitor['onLeaveOtherNode']
|
||||||
if leave? then leave.call visitor, this
|
if leave? then leave.call visitor, this
|
||||||
|
|
||||||
|
rotateChildren: ->
|
||||||
|
@_children.push @_children.shift()
|
||||||
|
@_rotations += 1
|
||||||
|
|
||||||
# Property Methods #############################################################################
|
# Property Methods #############################################################################
|
||||||
|
|
||||||
getChildren: ->
|
getChildren: ->
|
||||||
@@ -66,6 +82,12 @@ module.exports = class CraftingNode
|
|||||||
maxDepth = Math.max maxDepth, child.getDepth() + 1
|
maxDepth = Math.max maxDepth, child.getDepth() + 1
|
||||||
return maxDepth
|
return maxDepth
|
||||||
|
|
||||||
|
getId: ->
|
||||||
|
return @_id
|
||||||
|
|
||||||
|
getRotations: ->
|
||||||
|
return @_rotations
|
||||||
|
|
||||||
getSize: ->
|
getSize: ->
|
||||||
size = 1
|
size = 1
|
||||||
for child in @children
|
for child in @children
|
||||||
@@ -85,7 +107,10 @@ module.exports = class CraftingNode
|
|||||||
complete: { get:@prototype.isComplete }
|
complete: { get:@prototype.isComplete }
|
||||||
completeText: { get:@prototype.getCompleteText }
|
completeText: { get:@prototype.getCompleteText }
|
||||||
depth: { get:@prototype.getDepth }
|
depth: { get:@prototype.getDepth }
|
||||||
|
id: { get:@prototype.getId }
|
||||||
|
rotations: { get:@prototype.getRotations }
|
||||||
size: { get:@prototype.getSize }
|
size: { get:@prototype.getSize }
|
||||||
|
valid: { get:@prototype.isValid }
|
||||||
|
|
||||||
# Virtual Methods ##############################################################################
|
# Virtual Methods ##############################################################################
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
###
|
||||||
|
Crafting Guide - crafting_plan.coffee
|
||||||
|
|
||||||
|
Copyright (c) 2015 by Redwood Labs
|
||||||
|
All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
module.exports = class CraftingPlan
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
@@ -14,6 +14,7 @@ module.exports = class InventoryNode extends CraftingNode
|
|||||||
|
|
||||||
@::ENTER_METHOD = 'onEnterInventoryNode'
|
@::ENTER_METHOD = 'onEnterInventoryNode'
|
||||||
@::LEAVE_METHOD = 'onLeaveInventoryNode'
|
@::LEAVE_METHOD = 'onLeaveInventoryNode'
|
||||||
|
@::TYPE = CraftingNode::TYPES.INVENTORY
|
||||||
|
|
||||||
constructor: (options={})->
|
constructor: (options={})->
|
||||||
if not options.inventory? then throw new Error 'options.inventory is required'
|
if not options.inventory? then throw new Error 'options.inventory is required'
|
||||||
@@ -38,6 +39,7 @@ module.exports = class InventoryNode extends CraftingNode
|
|||||||
_checkValidity: ->
|
_checkValidity: ->
|
||||||
for child in @children
|
for child in @children
|
||||||
return false unless child.isValid
|
return false unless child.isValid
|
||||||
|
return true
|
||||||
|
|
||||||
# Object Overrides #############################################################################
|
# Object Overrides #############################################################################
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ module.exports = class ItemNode extends CraftingNode
|
|||||||
|
|
||||||
@::ENTER_METHOD = 'onEnterItemNode'
|
@::ENTER_METHOD = 'onEnterItemNode'
|
||||||
@::LEAVE_METHOD = 'onLeaveItemNode'
|
@::LEAVE_METHOD = 'onLeaveItemNode'
|
||||||
|
@::TYPE = CraftingNode::TYPES.ITEM
|
||||||
|
|
||||||
constructor: (options={})->
|
constructor: (options={})->
|
||||||
if not options.item? then throw new Error 'options.item is required'
|
if not options.item? then throw new Error 'options.item is required'
|
||||||
@@ -58,8 +59,10 @@ module.exports = class ItemNode extends CraftingNode
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
_checkValidity: ->
|
_checkValidity: ->
|
||||||
|
return true unless @children.length > 0
|
||||||
for child in @children
|
for child in @children
|
||||||
return true if child.isValid
|
return true if child.isValid
|
||||||
|
return false
|
||||||
|
|
||||||
# Object Overrides #############################################################################
|
# Object Overrides #############################################################################
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
###
|
||||||
|
Crafting Plan - plan_builder.coffee
|
||||||
|
|
||||||
|
Copyright (c) 2015 by Redwood Labs
|
||||||
|
All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
CraftingNode = require './crafting_node'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
module.exports = class PlanBuilder
|
||||||
|
|
||||||
|
constructor: (rootNode)->
|
||||||
|
if not rootNode? then throw new Error 'rootNode is required'
|
||||||
|
|
||||||
|
@_choiceNodes = []
|
||||||
|
@_complete = false
|
||||||
|
@_plans = []
|
||||||
|
@_rootNode = rootNode
|
||||||
|
|
||||||
|
@_isolateChoiceNodes()
|
||||||
|
|
||||||
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
|
producePlans: (maxPlans=null)->
|
||||||
|
maxPlans = if maxPlans then @plans.length + maxPlans else Number.MAX_VALUE
|
||||||
|
|
||||||
|
while @plans.length < maxPlans and not @complete
|
||||||
|
plan = @_captureCurrentPlan()
|
||||||
|
@plans.push plan if plan.length > 0
|
||||||
|
@_incrementChoiceNodes()
|
||||||
|
|
||||||
|
return @_plans
|
||||||
|
|
||||||
|
# Property Methods #############################################################################
|
||||||
|
|
||||||
|
isComplete: ->
|
||||||
|
return @_complete
|
||||||
|
|
||||||
|
getPlans: ->
|
||||||
|
return @_plans
|
||||||
|
|
||||||
|
Object.defineProperties @prototype,
|
||||||
|
complete: { get:@prototype.isComplete }
|
||||||
|
plans: { get:@prototype.getPlans }
|
||||||
|
|
||||||
|
# Private Methods ##############################################################################
|
||||||
|
|
||||||
|
_captureCurrentPlan: ->
|
||||||
|
toVisit = [@_rootNode]
|
||||||
|
plan = []
|
||||||
|
|
||||||
|
while toVisit.length > 0
|
||||||
|
node = toVisit.shift()
|
||||||
|
|
||||||
|
if node.TYPE is CraftingNode::TYPES.INVENTORY
|
||||||
|
toVisit.push(c) for c in node.children
|
||||||
|
else if node.TYPE is CraftingNode::TYPES.ITEM
|
||||||
|
toVisit.push node.children[0] if node.children.length > 0
|
||||||
|
else if node.TYPE is CraftingNode::TYPES.RECIPE
|
||||||
|
plan.push node.recipe
|
||||||
|
toVisit.push(c) for c in node.children
|
||||||
|
|
||||||
|
return plan.reverse()
|
||||||
|
|
||||||
|
_incrementChoiceNodes: ->
|
||||||
|
index = @_choiceNodes.length - 1
|
||||||
|
while true
|
||||||
|
if index is -1
|
||||||
|
@_complete = true
|
||||||
|
return
|
||||||
|
|
||||||
|
node = @_choiceNodes[index]
|
||||||
|
node.rotateChildren()
|
||||||
|
|
||||||
|
return unless node.rotations % node.children.length is 0
|
||||||
|
index -= 1
|
||||||
|
|
||||||
|
_isolateChoiceNodes: ->
|
||||||
|
@_rootNode.acceptVisitor
|
||||||
|
onEnterItemNode: (node)=>
|
||||||
|
if node.children.length > 1
|
||||||
|
@_choiceNodes.push node
|
||||||
@@ -14,6 +14,7 @@ module.exports = class RecipeNode extends CraftingNode
|
|||||||
|
|
||||||
@::ENTER_METHOD = 'onEnterRecipeNode'
|
@::ENTER_METHOD = 'onEnterRecipeNode'
|
||||||
@::LEAVE_METHOD = 'onLeaveRecipeNode'
|
@::LEAVE_METHOD = 'onLeaveRecipeNode'
|
||||||
|
@::TYPE = CraftingNode::TYPES.RECIPE
|
||||||
|
|
||||||
constructor: (options={})->
|
constructor: (options={})->
|
||||||
if not options.recipe? then throw new Error 'options.recipe is required'
|
if not options.recipe? then throw new Error 'options.recipe is required'
|
||||||
|
|||||||
@@ -31,6 +31,27 @@ MOD_VERSION_FILE =
|
|||||||
|
|
||||||
item: Cobblestone
|
item: Cobblestone
|
||||||
|
|
||||||
|
item: Copper Block
|
||||||
|
recipe:
|
||||||
|
input: Copper Ingot
|
||||||
|
pattern: 000 000 000
|
||||||
|
tools: Crafting Table
|
||||||
|
|
||||||
|
item: Copper Ingot
|
||||||
|
recipe:
|
||||||
|
input: Copper Ore, Coal
|
||||||
|
pattern: .0. ... .1.
|
||||||
|
tools: Furnace
|
||||||
|
recipe:
|
||||||
|
input: Copper Ore, Charcoal
|
||||||
|
pattern: .0. ... .1.
|
||||||
|
tools: Furnace
|
||||||
|
recipe:
|
||||||
|
input: Copper Block
|
||||||
|
pattern: ... .0. ...
|
||||||
|
|
||||||
|
item: Copper Ore
|
||||||
|
|
||||||
item: Furnace
|
item: Furnace
|
||||||
recipe:
|
recipe:
|
||||||
input: Cobblestone
|
input: Cobblestone
|
||||||
|
|||||||
@@ -61,9 +61,11 @@ describe 'GraphBuilder.coffee', ->
|
|||||||
it 'an item with multiple inputs', ->
|
it 'an item with multiple inputs', ->
|
||||||
runSingleItemTreeBuildingTest 'test__lever', 8, 9
|
runSingleItemTreeBuildingTest 'test__lever', 8, 9
|
||||||
|
|
||||||
it 'can make a tree for an item with multiple recipes', ->
|
it 'an item with multiple recipes', ->
|
||||||
runSingleItemTreeBuildingTest 'test__iron_ingot', 6, 11
|
runSingleItemTreeBuildingTest 'test__iron_ingot', 6, 11
|
||||||
|
|
||||||
it 'can make a tree for an item with multiple inputs and multiple recipes', ->
|
it 'an item with multiple inputs and multiple recipes', ->
|
||||||
runSingleItemTreeBuildingTest 'test__iron_sword', 8, 18
|
runSingleItemTreeBuildingTest 'test__iron_sword', 8, 18
|
||||||
logger.debug builder.toString()
|
|
||||||
|
it 'an item with one recursive recipe', ->
|
||||||
|
runSingleItemTreeBuildingTest 'test__copper_ingot', 10, 24
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
###
|
||||||
|
Crafting Guide - plan_builder.test.coffee
|
||||||
|
|
||||||
|
Copyright (c) 2015 by Redwood Labs
|
||||||
|
All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
fixtures = require './fixtures'
|
||||||
|
PlanBuilder = require '../../src/coffee/models/crafting/plan_builder'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
describe 'plan_builder.coffee', ->
|
||||||
|
|
||||||
|
printPlan = (plan)->
|
||||||
|
return ((s.slug.replace(/^.*>.*>/, '') for s in plan)).join ' > '
|
||||||
|
|
||||||
|
it 'generates no plans for a gatherable item', ->
|
||||||
|
builder = new PlanBuilder fixtures.makeTree 'test__oak_wood'
|
||||||
|
plans = builder.producePlans 100
|
||||||
|
|
||||||
|
plans.length.should.equal 0
|
||||||
|
builder.complete.should.be.true
|
||||||
|
|
||||||
|
it 'can find a multi-step plan', ->
|
||||||
|
builder = new PlanBuilder fixtures.makeTree 'test__lever'
|
||||||
|
plans = builder.producePlans 100
|
||||||
|
|
||||||
|
printPlan(plans[0]).should.equal '4 test__oak_planks > 4 test__stick > test__lever'
|
||||||
|
plans.length.should.equal 1
|
||||||
|
builder.complete.should.be.true
|
||||||
|
|
||||||
|
it 'can find multiple plans', ->
|
||||||
|
builder = new PlanBuilder fixtures.makeTree 'test__iron_ingot'
|
||||||
|
plans = builder.producePlans 100
|
||||||
|
|
||||||
|
printPlan(plans[0]).should.equal 'test__charcoal > test__iron_ingot'
|
||||||
|
printPlan(plans[1]).should.equal 'test__iron_ingot'
|
||||||
Reference in New Issue
Block a user