Add plan builder and tests

This commit is contained in:
Andrew Miner
2015-09-19 09:31:14 -07:00
parent f6b71c9464
commit 6ea97ac3aa
9 changed files with 198 additions and 10 deletions
@@ -5,10 +5,19 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
########################################################################################################################
module.exports = class CraftingNode
@::TYPES =
INVENTORY: 0
ITEM: 1
RECIPE: 2
@::TYPE = null
constructor: (options={})->
if not options.modPack? then throw new Error 'options.modPack is required'
@@ -16,6 +25,8 @@ module.exports = class CraftingNode
@_children = []
@_complete = null
@_id = _.uniqueId 'node_'
@_rotations = 0
@_valid = null
# Public Methods ###############################################################################
@@ -25,6 +36,7 @@ module.exports = class CraftingNode
for child in @_createChildren()
child.parent = this
if child.valid
@_children.push child
queue.push child
return queue
@@ -47,6 +59,10 @@ module.exports = class CraftingNode
leave = visitor['onLeaveOtherNode']
if leave? then leave.call visitor, this
rotateChildren: ->
@_children.push @_children.shift()
@_rotations += 1
# Property Methods #############################################################################
getChildren: ->
@@ -66,6 +82,12 @@ module.exports = class CraftingNode
maxDepth = Math.max maxDepth, child.getDepth() + 1
return maxDepth
getId: ->
return @_id
getRotations: ->
return @_rotations
getSize: ->
size = 1
for child in @children
@@ -85,7 +107,10 @@ module.exports = class CraftingNode
complete: { get:@prototype.isComplete }
completeText: { get:@prototype.getCompleteText }
depth: { get:@prototype.getDepth }
id: { get:@prototype.getId }
rotations: { get:@prototype.getRotations }
size: { get:@prototype.getSize }
valid: { get:@prototype.isValid }
# 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'
@::LEAVE_METHOD = 'onLeaveInventoryNode'
@::TYPE = CraftingNode::TYPES.INVENTORY
constructor: (options={})->
if not options.inventory? then throw new Error 'options.inventory is required'
@@ -38,6 +39,7 @@ module.exports = class InventoryNode extends CraftingNode
_checkValidity: ->
for child in @children
return false unless child.isValid
return true
# Object Overrides #############################################################################
@@ -14,6 +14,7 @@ module.exports = class ItemNode extends CraftingNode
@::ENTER_METHOD = 'onEnterItemNode'
@::LEAVE_METHOD = 'onLeaveItemNode'
@::TYPE = CraftingNode::TYPES.ITEM
constructor: (options={})->
if not options.item? then throw new Error 'options.item is required'
@@ -58,8 +59,10 @@ module.exports = class ItemNode extends CraftingNode
return false
_checkValidity: ->
return true unless @children.length > 0
for child in @children
return true if child.isValid
return false
# 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'
@::LEAVE_METHOD = 'onLeaveRecipeNode'
@::TYPE = CraftingNode::TYPES.RECIPE
constructor: (options={})->
if not options.recipe? then throw new Error 'options.recipe is required'
+21
View File
@@ -31,6 +31,27 @@ MOD_VERSION_FILE =
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
recipe:
input: Cobblestone
+5 -3
View File
@@ -61,9 +61,11 @@ describe 'GraphBuilder.coffee', ->
it 'an item with multiple inputs', ->
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
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
logger.debug builder.toString()
it 'an item with one recursive recipe', ->
runSingleItemTreeBuildingTest 'test__copper_ingot', 10, 24
+38
View File
@@ -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'