Add Multiblock model, parser, and tests.
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
###
|
||||||
|
Crafting Guide - multiblock.coffee
|
||||||
|
|
||||||
|
Copyright (c) 2014-2015 by Redwood Labs
|
||||||
|
All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
BaseModel = require './base_model'
|
||||||
|
SimpleInventory = require './simple_inventory'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
module.exports = class Multiblock extends BaseModel
|
||||||
|
|
||||||
|
constructor: (attributes={}, options={})->
|
||||||
|
if not attributes.input? then throw new Error 'attributes.input is required'
|
||||||
|
if not attributes.layers? then throw new Error 'attributes.layers is required'
|
||||||
|
if attributes.layers.length < 1 then throw new Error 'attributes.layers.length must be >= 1'
|
||||||
|
super attributes, options
|
||||||
|
|
||||||
|
@_analyzePattern()
|
||||||
|
|
||||||
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
|
getStackAt: (x, y, z)->
|
||||||
|
value = @_stackCache[y]?[z]?[x]
|
||||||
|
return null if value is undefined
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Property Methods #############################################################################
|
||||||
|
|
||||||
|
Object.defineProperties @prototype,
|
||||||
|
depth:
|
||||||
|
get: -> @_depth
|
||||||
|
|
||||||
|
height:
|
||||||
|
get: -> @_height
|
||||||
|
|
||||||
|
width:
|
||||||
|
get: -> @_width
|
||||||
|
|
||||||
|
# Private Methods ##############################################################################
|
||||||
|
|
||||||
|
_analyzeLayer: (layer, stackCacheLayer)->
|
||||||
|
rows = layer.split ' '
|
||||||
|
@_depth = Math.max @_depth, rows.length
|
||||||
|
|
||||||
|
for row in rows
|
||||||
|
@_width = Math.max @_width, row.length
|
||||||
|
stackCacheRow = []
|
||||||
|
stackCacheLayer.push stackCacheRow
|
||||||
|
@_analyzeRow row, stackCacheRow
|
||||||
|
|
||||||
|
_analyzePattern: ->
|
||||||
|
@_depth = @_height = @_width = 0
|
||||||
|
@_inventory = new SimpleInventory
|
||||||
|
@_stackCache = []
|
||||||
|
|
||||||
|
for layer in @layers
|
||||||
|
stackCacheLayer = []
|
||||||
|
@_stackCache.push stackCacheLayer
|
||||||
|
@_analyzeLayer layer, stackCacheLayer
|
||||||
|
|
||||||
|
@_height = @layers.length
|
||||||
|
|
||||||
|
_analyzeRow: (row, stackCacheRow)->
|
||||||
|
for cell, x in row.split ''
|
||||||
|
index = parseInt cell
|
||||||
|
stack = null
|
||||||
|
|
||||||
|
if not _.isNaN index
|
||||||
|
stack = @input[index]
|
||||||
|
@_inventory.add stack.itemSlug, stack.quantity
|
||||||
|
|
||||||
|
stackCacheRow.push stack
|
||||||
@@ -5,13 +5,14 @@ Copyright (c) 2014-2015 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
|
_ = require 'underscore'
|
||||||
CommandParserVersionBase = require './command_parser_version_base'
|
CommandParserVersionBase = require './command_parser_version_base'
|
||||||
Item = require '../item'
|
Item = require '../item'
|
||||||
ItemSlug = require '../item_slug'
|
ItemSlug = require '../item_slug'
|
||||||
ModVersion = require '../mod_version'
|
ModVersion = require '../mod_version'
|
||||||
|
Multiblock = require '../multiblock'
|
||||||
Recipe = require '../recipe'
|
Recipe = require '../recipe'
|
||||||
Stack = require '../simple_stack'
|
Stack = require '../simple_stack'
|
||||||
_ = require 'underscore'
|
|
||||||
{StringBuilder} = require 'crafting-guide-common'
|
{StringBuilder} = require 'crafting-guide-common'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
@@ -70,12 +71,29 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
|
|||||||
|
|
||||||
@_recipeData.ignoreDuringCrafting = (value is 'yes')
|
@_recipeData.ignoreDuringCrafting = (value is 'yes')
|
||||||
|
|
||||||
_command_input: (inputNames...)->
|
_command_input: (stackDescriptions...)->
|
||||||
if not @_recipeData? then throw new Error 'cannot declare "input" before "recipe"'
|
activeData = if @_recipeData? then @_recipeData else @_multiblockData
|
||||||
if @_recipeData.input.length isnt 0 then throw new Error 'duplicate declaration of "input"'
|
|
||||||
|
|
||||||
for name in inputNames
|
if not activeData? then throw new Error 'cannot declare "input" before "recipe" or "multiblock"'
|
||||||
@_recipeData.input.push @_parseStack name
|
if activeData.input.length isnt 0 then throw new Error 'duplicate declaration of "input"'
|
||||||
|
|
||||||
|
for stackDescription in stackDescriptions
|
||||||
|
activeData.input.push @_parseStack stackDescription
|
||||||
|
|
||||||
|
_command_layer: (layerText)->
|
||||||
|
if not @_multiblockData? then throw new Error 'cannot declare "layer" before "multiblock"'
|
||||||
|
if not layerText? then throw new Error 'cannot have an empty layer'
|
||||||
|
if layerText.length is 0 then throw new Error 'cannot have an empty layer'
|
||||||
|
|
||||||
|
@_multiblockData.layers.push layerText
|
||||||
|
|
||||||
|
_command_multiblock: ->
|
||||||
|
if not @_itemData? then throw new Error 'cannot declare "multiblock" before "item"'
|
||||||
|
if @_itemData.multiblockData? then throw new Error 'duplicate declaration of "multiblock"'
|
||||||
|
|
||||||
|
@_recipeData = null
|
||||||
|
@_multiblockData = input:[], layers:[], line:@_lineNumber
|
||||||
|
@_itemData.multiblockData = @_multiblockData
|
||||||
|
|
||||||
_command_onlyIf: (condition)->
|
_command_onlyIf: (condition)->
|
||||||
words = condition.split ' '
|
words = condition.split ' '
|
||||||
@@ -112,6 +130,7 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
|
|||||||
_command_recipe: ->
|
_command_recipe: ->
|
||||||
if not @_itemData? then throw new Error 'cannot delcare "recipe" before "item"'
|
if not @_itemData? then throw new Error 'cannot delcare "recipe" before "item"'
|
||||||
|
|
||||||
|
@_multiblockData = null
|
||||||
@_recipeData = line:@_lineNumber, input:[], output:[{quantity:1, name:@_itemData.name}], tools:[]
|
@_recipeData = line:@_lineNumber, input:[], output:[{quantity:1, name:@_itemData.name}], tools:[]
|
||||||
@_itemData.recipes ?= []
|
@_itemData.recipes ?= []
|
||||||
@_itemData.recipes.push @_recipeData
|
@_itemData.recipes.push @_recipeData
|
||||||
@@ -171,11 +190,23 @@ module.exports = class ModVersionParserV1 extends CommandParserVersionBase
|
|||||||
itemData.slug = ItemSlug.slugify itemData.name
|
itemData.slug = ItemSlug.slugify itemData.name
|
||||||
modVersion.registerName itemData.slug, itemData.name
|
modVersion.registerName itemData.slug, itemData.name
|
||||||
|
|
||||||
|
if itemData.multiblockData
|
||||||
|
item.multiblock = @_handleErrors @_buildMultiblock, modVersion, item, itemData.multiblockData
|
||||||
|
|
||||||
for recipeData in itemData.recipes
|
for recipeData in itemData.recipes
|
||||||
@_handleErrors @_buildRecipe, modVersion, itemData, recipeData
|
@_handleErrors @_buildRecipe, modVersion, itemData, recipeData
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
_buildMultiblock: (modVersion, item, multiblockData)->
|
||||||
|
@_lineNumber = multiblockData.line
|
||||||
|
if multiblockData.layers.length is 0 then throw new Error '"multiblock" requires at least one "layer"'
|
||||||
|
if multiblockData.input.length is 0 then throw new Error '"multiblock" requires at least one "input"'
|
||||||
|
|
||||||
|
input = @_buildStackList modVersion, multiblockData.input
|
||||||
|
multiblock = new Multiblock input:input, layers:multiblockData.layers
|
||||||
|
return multiblock
|
||||||
|
|
||||||
_buildRecipe: (modVersion, itemData, recipeData)->
|
_buildRecipe: (modVersion, itemData, recipeData)->
|
||||||
@_lineNumber = recipeData.line
|
@_lineNumber = recipeData.line
|
||||||
if recipeData.input.length is 0 then throw new Error 'the "input" declaration is required'
|
if recipeData.input.length is 0 then throw new Error 'the "input" declaration is required'
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
###
|
||||||
|
Crafting Guide - multiblock.test.coffee
|
||||||
|
|
||||||
|
Copyright (c) 2015 by Redwood Labs
|
||||||
|
All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
ItemSlug = require '../src/coffee/models/item_slug'
|
||||||
|
Mod = require '../src/coffee/models/multiblock'
|
||||||
|
Multiblock = require '../src/coffee/models/multiblock'
|
||||||
|
Stack = require '../src/coffee/models/stack'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
describe 'multiblock.coffee', ->
|
||||||
|
|
||||||
|
describe 'with a single block type', ->
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
@input = [ new Stack itemSlug:ItemSlug.slugify('cobblestone'), quantity:1 ]
|
||||||
|
|
||||||
|
it 'correctly parses a 1x1x1 cube', ->
|
||||||
|
model = new Multiblock input:@input, layers:['0']
|
||||||
|
model.depth.should.equal 1
|
||||||
|
model.height.should.equal 1
|
||||||
|
model.width.should.equal 1
|
||||||
|
model.getStackAt(0, 0, 0).toString().should.equal '1 cobblestone'
|
||||||
|
|
||||||
|
it 'correctly parses a solid 3x3x3 cube', ->
|
||||||
|
model = new Multiblock input:@input, layers:['000 000 000', '000 000 000', '000 000 000']
|
||||||
|
model.depth.should.equal 3
|
||||||
|
model.height.should.equal 3
|
||||||
|
model.width.should.equal 3
|
||||||
|
|
||||||
|
for x in [0..2]
|
||||||
|
for y in [0..2]
|
||||||
|
for z in [0..2]
|
||||||
|
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
|
||||||
|
|
||||||
|
it 'correctly parses a hollow 3x3x3 cube', ->
|
||||||
|
model = new Multiblock input:@input, layers:['000 000 000', '000 0.0 000', '000 000 000']
|
||||||
|
model.depth.should.equal 3
|
||||||
|
model.height.should.equal 3
|
||||||
|
model.width.should.equal 3
|
||||||
|
|
||||||
|
for x in [0..2]
|
||||||
|
for y in [0..2]
|
||||||
|
for z in [0..2]
|
||||||
|
if x isnt 1 or y isnt 1 or z isnt 1
|
||||||
|
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
|
||||||
|
else
|
||||||
|
expect(model.getStackAt(x, y, z)).to.be.null
|
||||||
|
|
||||||
|
it 'correctly parses a 3x2x3 pyramid', ->
|
||||||
|
model = new Multiblock input:@input, layers:['000 000 000', '.. .0 ..']
|
||||||
|
model.depth.should.equal 3
|
||||||
|
model.height.should.equal 2
|
||||||
|
model.width.should.equal 3
|
||||||
|
|
||||||
|
for y in [0..1]
|
||||||
|
for z in [0..2]
|
||||||
|
for x in [0..2]
|
||||||
|
if y is 1 and (x isnt 1 or z isnt 1)
|
||||||
|
expect(model.getStackAt(x, y, z)).to.be.null
|
||||||
|
else
|
||||||
|
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
|
||||||
|
|
||||||
|
describe 'with multiple block types', ->
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
@input = [
|
||||||
|
new Stack itemSlug:ItemSlug.slugify('cobblestone'), quantity:1
|
||||||
|
new Stack itemSlug:ItemSlug.slugify('stone'), quantity:1
|
||||||
|
new Stack itemSlug:ItemSlug.slugify('oak wood'), quantity:1
|
||||||
|
]
|
||||||
|
|
||||||
|
it 'correctly parses a 1x3x1 column of different types', ->
|
||||||
|
model = new Multiblock input:@input, layers:['0', '1', '2']
|
||||||
|
model.depth.should.equal 1
|
||||||
|
model.height.should.equal 3
|
||||||
|
model.width.should.equal 1
|
||||||
|
|
||||||
|
model.getStackAt(0, 0, 0).toString().should.equal '1 cobblestone'
|
||||||
|
model.getStackAt(0, 1, 0).toString().should.equal '1 stone'
|
||||||
|
model.getStackAt(0, 2, 0).toString().should.equal '1 oak_wood'
|
||||||
@@ -63,6 +63,42 @@ describe 'mod_version_parser_v1.coffee', ->
|
|||||||
func = -> parser.parse 'gatherable: yes; item: Alpha Bravo; gatherable: yes'
|
func = -> parser.parse 'gatherable: yes; item: Alpha Bravo; gatherable: yes'
|
||||||
expect(func).to.throw Error, '"gatherable" before "item"'
|
expect(func).to.throw Error, '"gatherable" before "item"'
|
||||||
|
|
||||||
|
describe 'Multiblock', ->
|
||||||
|
|
||||||
|
it 'adds a "multiblock" when present', ->
|
||||||
|
modVersion = parser.parse 'item: Alpha; multiblock:; input:Bravo; layer: 0'
|
||||||
|
item = modVersion.findItemByName 'Alpha'
|
||||||
|
item.multiblock.height.should.equal 1
|
||||||
|
|
||||||
|
it 'requires "item" be declared before "multiblock"', ->
|
||||||
|
func = -> parser.parse "multiblock:"
|
||||||
|
expect(func).to.throw Error, '"multiblock" before "item"'
|
||||||
|
|
||||||
|
it 'prohibits multiple "multiblock" commands per item"', ->
|
||||||
|
func = -> parser.parse "item: Alpha; multiblock:; multiblock:"
|
||||||
|
expect(func).to.throw Error, 'duplicate'
|
||||||
|
|
||||||
|
it 'prohibits multiblocks with no inputs', ->
|
||||||
|
func = -> parser.parse "item: Alpha; multiblock:; layer: 000"
|
||||||
|
expect(func).to.throw Error, 'at least one "input"'
|
||||||
|
|
||||||
|
describe 'layer', ->
|
||||||
|
|
||||||
|
it 'allows multiple "layer" commands', ->
|
||||||
|
modVersion = parser.parse 'item: Alpha; multiblock:; input:Bravo, Charlie; layer: 01 10; layer: 10 01'
|
||||||
|
item = modVersion.findItemByName 'Alpha'
|
||||||
|
item.multiblock.depth.should.equal 2
|
||||||
|
item.multiblock.height.should.equal 2
|
||||||
|
item.multiblock.width.should.equal 2
|
||||||
|
|
||||||
|
it 'prohibits empty layers', ->
|
||||||
|
func = -> parser.parse 'item: Alpha; multiblock:; input: Bravo; layer:; layer: 00 00'
|
||||||
|
expect(func).to.throw Error, 'empty layer'
|
||||||
|
|
||||||
|
it 'prohibits multiblocks with no layers', ->
|
||||||
|
func = -> parser.parse "item: Alpha; multiblock:; input: Bravo"
|
||||||
|
expect(func).to.throw Error, 'at least one "layer"'
|
||||||
|
|
||||||
describe 'Recipe', ->
|
describe 'Recipe', ->
|
||||||
|
|
||||||
beforeEach -> baseText = 'item: Charlie; '
|
beforeEach -> baseText = 'item: Charlie; '
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ global._ = require 'underscore'
|
|||||||
global.Backbone = require 'backbone'
|
global.Backbone = require 'backbone'
|
||||||
global.assert = chai.assert
|
global.assert = chai.assert
|
||||||
global.expect = chai.expect
|
global.expect = chai.expect
|
||||||
|
global.inspect = require('util').inspect
|
||||||
global.should = chai.should()
|
global.should = chai.should()
|
||||||
global.sinon = require 'sinon'
|
global.sinon = require 'sinon'
|
||||||
global.util = require 'util'
|
global.util = require 'util'
|
||||||
|
|||||||
Reference in New Issue
Block a user