Add code to build crafting trees
This commit is contained in:
@@ -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'
|
||||||
@@ -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)->
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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'
|
||||||
|
|||||||
Reference in New Issue
Block a user