Add code to build crafting trees

This commit is contained in:
Andrew Miner
2015-08-25 21:15:08 -07:00
parent 4ff2915d85
commit ea82518b0d
8 changed files with 492 additions and 2 deletions
@@ -0,0 +1,99 @@
###
Crafting Guide - crafting_node.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
########################################################################################################################
module.exports = class CraftingNode
constructor: (options={})->
if not options.modPack? then throw new Error 'options.modPack is required'
@modPack = options.modPack
@_children = []
@_complete = null
@_valid = null
# Public Methods ###############################################################################
expand: (queue=[])->
return queue if @children.length > 0
for child in @_createChildren()
child.parent = this
@_children.push child
queue.push child
return queue
getNode: (path)->
return null unless _.isArray(path) and path.length > 0
child = @children[path[0]]
return null unless child?
return child.getPath path[1..]
# 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
getSize: ->
size = 1
for child in @children
size += child.size
return size
getNodeType: ->
return @_nodeType
isValid: ->
return @_valid if @_valid?
valid = @_checkValidity()
@_valid = false if not valid
return valid
Object.defineProperties @prototype,
children: { get:@prototype.getChildren }
complete: { get:@prototype.isComplete }
completeText: { get:@prototype.getCompleteText }
depth: { get:@prototype.getDepth }
size: { get:@prototype.getSize }
# Virtual Methods ##############################################################################
# Subclasses must override this method to create such children as are appropriate for that kind of node. The new
# nodes should be appended to the given array.
_createChildren: (result)->
throw new Error "#{@constructor.name} must override the _createChildren method"
# Subclasses must override this method to indicate whether the node has at least one "complete" subtree rooted with
# itself. Completeness may be defined differently by different subclasses.
_checkCompleteness: ->
throw new Error "#{@constructor.name} must override the _checkCompletness method"
# Subclasses must override this method to determine whether the subtree represented by a node should still be
# explored for having a useful crafting plan.
_checkValidity: ->
throw new Error "#{@constructor.name} must override the _checkValidity method"
@@ -0,0 +1,72 @@
###
Crafting Guide - graph_builder.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
Inventory = require '../inventory'
InventoryNode = require './inventory_node'
########################################################################################################################
module.exports = class GraphBuilder
constructor: (options={})->
if not options.modPack? then throw new Error 'options.modPack is required'
@modPack = options.modPack
@_wanted = options.wanted ?= new Inventory
@_wanted.on 'change', => @reset()
@reset()
# Public Methods ###############################################################################
expandGraph: (steps=null)->
maxSteps = if steps? then @_stepCount + steps else Number.MAX_VALUE
@_queue ?= []
while true
break if @_queue.length is 0
break if @_stepCount >= maxSteps
node = @_queue.shift()
break unless node?
node.expand @_queue
@_stepCount += 1
reset: ->
@_rootNode = new InventoryNode modPack:@modPack, inventory:@wanted
@_queue = [@_rootNode]
@_stepCount = 0
# 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,
complete: { get:@prototype.isComplete }
rootNode: { get:@prototype.getRootNode }
stepCount: { get:@prototype.getStepCount }
wanted: { get:@prototype.getWanted }
# Object Overrides ############################################################################
toString: (indent='')->
"Build Tree\n#{@_rootNode.toString(indent + ' ')}"
@@ -0,0 +1,47 @@
###
Crafting Guide - crafting_plan_node.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
CraftingNode = require './crafting_node'
ItemNode = require './item_node'
########################################################################################################################
module.exports = class InventoryNode extends CraftingNode
constructor: (options={})->
if not options.inventory? then throw new Error 'options.inventory is required'
super options
@inventory = options.inventory
# CraftingNode Overrides #######################################################################
_createChildren: (result=[])->
@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
return result
_checkCompleteness: ->
for child in @children
return false unless child.isComplete
return true
_checkValidity: ->
for child in @children
return false unless child.isValid
# Object Overrides #############################################################################
toString: (indent='')->
completeText = if @complete then 'complete' else 'incomplete'
parts = ["#{indent}#{@completeText} InventoryNode for #{@inventory}"]
nextIndent = indent + ' '
for child in @children
parts.push child.toString nextIndent
return parts.join '\n'
@@ -0,0 +1,69 @@
###
Crafting Guide - item_node.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
CraftingNode = require './crafting_node'
RecipeNode = require './recipe_node'
########################################################################################################################
module.exports = class ItemNode extends CraftingNode
constructor: (options={})->
if not options.item? then throw new Error 'options.item is required'
super options
@item = options.item
@_recipes = null
# Property Methods #############################################################################
getRecipes: ->
if not @_recipes?
@_recipes = @modPack.findRecipes @item.slug
return @_recipes or []
isGatherable: ->
return true if @item.isGatherable
return true unless @getRecipes().length > 0
return false
Object.defineProperties @prototype,
gatherable: { get:@prototype.isGatherable }
recipes: { get:@prototype.getRecipes }
# CraftingNode Overrides #######################################################################
_createChildren: (result=[])->
recipes = @getRecipes()
return [] unless recipes.length > 0
for recipe in recipes
result.push new RecipeNode modPack:@modPack, recipe:recipe
return result
_checkCompleteness: ->
return true if @gatherable
return false unless @children?
for child in @children
return true if child.isComplete
return false
_checkValidity: ->
for child in @children
return true if child.isValid
# Object Overrides #############################################################################
toString: (indent)->
completeText = if @complete then 'complete' else 'incomplete'
parts = ["#{indent}#{@completeText} ItemNode for #{@item.name}"]
nextIndent = indent + ' '
for child in @children
parts.push child.toString nextIndent
return parts.join '\n'
@@ -0,0 +1,61 @@
###
Crafting Guide - recipe_node.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
CraftingNode = require './crafting_node'
# ItemNode = require './item_node' # don't include here, causes a cycle
########################################################################################################################
module.exports = class RecipeNode extends CraftingNode
constructor: (options={})->
if not options.recipe? then throw new Error 'options.recipe is required'
super options
@recipe = options.recipe
# CraftingNode Overrides #######################################################################
_createChildren: (result=[])->
ItemNode = require './item_node' # include here to avoid a cycle
for stack in @recipe.input
item = @modPack.findItem stack.itemSlug
result.push new ItemNode modPack:@modPack, item:item
return result
_checkCompleteness: ->
for child in @children
return false unless child.isComplete
return true
_checkValidity: ->
for child in @children
return false unless child.isValid
return false if @_isRepeatedRecipe()
return true
# Private Methods ##############################################################################
_isRepeatedRecipe: ->
nextParent = @parent
while nextParent?
return true if nextParent.recipe is @recipe
nextParent = nextParent.parent
return false
# Object Overrides ############################################################################
toString: (indent)->
completeText = if @complete then 'complete' else 'incomplete'
parts = ["#{indent}#{@completeText} RecipeNode for #{@recipe.slug}"]
nextIndent = indent + ' '
for child in @children
parts.push child.toString nextIndent
return parts.join '\n'
+1 -1
View File
@@ -170,7 +170,7 @@ module.exports = class Inventory extends BaseModel
# Object Overrides ############################################################################# # Object Overrides #############################################################################
toString: -> toString: ->
result = [@constructor.name, " (", @cid, ") { items: ["] result = [@constructor.name, " (", @cid, ") {items: ["]
needsDelimiter = false needsDelimiter = false
@each (stack)-> @each (stack)->
+142
View File
@@ -0,0 +1,142 @@
###
Crafting Guide - crafting_node.test.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
GraphBuilder = require '../../src/coffee/models/crafting/graph_builder'
ItemSlug = require '../../src/coffee/models/item_slug'
Mod = require '../../src/coffee/models/mod'
ModPack = require '../../src/coffee/models/mod_pack'
ModVersion = require '../../src/coffee/models/mod_version'
########################################################################################################################
SAMPLE_MOD_VERSION_TEXT = """
schema: 1
item: Charcoal
recipe:
input: 8 Oak Wood, Coal
pattern: .0. ... .1.
item: Crafting Table
recipe:
input: Oak Planks
pattern: .00 .00 ...
item: Coal
item: Cobblestone
item: Furnace
recipe:
input: Cobblestone
pattern: 000 0.0 000
tools: Crafting Table
item: Iron Ore
item: Iron Ingot
recipe:
input: 8 Iron Ore, Charcoal
pattern: .0. ... .1.
tools: Furnace
recipe:
input: 8 Iron Ore, Coal
pattern: .0. ... .1.
tools: Furnace
item: Iron Sword
recipe:
input: Iron Ingot, Stick
pattern: .0. .0. .1.
tools: Crafting Table
item: Lever
recipe:
input: Stick, Cobblestone
pattern: .0. .1. ...
item: Oak Planks
recipe:
input: Oak Wood
pattern: ... .0. ...
quantity: 4
item: Oak Wood
item: Stick
recipe:
input: Oak Planks
pattern: .0. .0. ...
quantity: 4
"""
builder = mod = modPack = modVersion = null
########################################################################################################################
describe.only 'GraphBuilder.coffee', ->
beforeEach ->
modPack = new ModPack
mod = new Mod name:'Test', slug:'test'
modPack.addMod mod
modVersion = new ModVersion modSlug:'test', version:'0.0'
modVersion.parse SAMPLE_MOD_VERSION_TEXT
mod.addModVersion modVersion
builder = new GraphBuilder modPack:modPack
describe 'expand', ->
it 'can work a few steps at a time', ->
builder.wanted.add ItemSlug.slugify 'test__iron_sword'
builder.expandGraph 9
builder.rootNode.depth.should.equal 6
builder.rootNode.size.should.equal 13
builder.complete.should.be.false
builder.expandGraph 9
builder.rootNode.depth.should.equal 8
builder.rootNode.size.should.equal 18
builder.complete.should.be.true
it 'works properly with an empty inventory', ->
builder.expandGraph 100
builder.rootNode.depth.should.equal 1
builder.rootNode.size.should.equal 1
builder.complete.should.be.true
describe 'can build a tree for', ->
runSingleItemTreeBuildingTest = (slug, depth, size)->
builder.wanted.add ItemSlug.slugify slug
builder.expandGraph 100
builder.rootNode.depth.should.equal depth
builder.rootNode.size.should.equal size
builder.complete.should.be.true
it 'a gatherable item', ->
runSingleItemTreeBuildingTest 'test__oak_wood', 2, 2
it 'a single-step item', ->
runSingleItemTreeBuildingTest 'test__crafting_table', 6, 6
it 'an item with multiple inputs', ->
runSingleItemTreeBuildingTest 'test__lever', 8, 9
it 'can make a tree for 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', ->
runSingleItemTreeBuildingTest 'test__iron_sword', 8, 18
logger.debug builder.toString()
+1 -1
View File
@@ -21,7 +21,7 @@ global.util = require 'util'
global.w = require 'when' global.w = require 'when'
{Logger} = require 'crafting-guide-common' {Logger} = require 'crafting-guide-common'
global.logger = new Logger level:Logger.FATAL global.logger = new Logger level:Logger.DEBUG
require '../src/coffee/polyfill' require '../src/coffee/polyfill'
require '../src/coffee/underscore_mixins' require '../src/coffee/underscore_mixins'