From 1a84ce2e2878da6abd4f4ef11fe305cd1ac481db Mon Sep 17 00:00:00 2001 From: Andrew Miner Date: Wed, 24 Dec 2014 19:17:37 -0800 Subject: [PATCH] Complete basic crafting functionality --- src/css/main.scss | 1 + src/css/templates/crafting_table.scss | 35 +++++-- src/data/industrial_craft.json | 12 +-- src/data/thermal_expansion.json | 8 +- src/data/vanilla.json | 26 +++--- .../crafting_table_controller.coffee | 48 ++++++---- .../recipe_catalog_controller.coffee | 5 + src/scripts/models/crafting_plan.coffee | 92 +++++++++++++++++++ src/scripts/models/crafting_table.coffee | 42 +++++++-- src/scripts/models/inventory.coffee | 43 ++++++++- src/scripts/models/recipe.coffee | 16 ++++ src/scripts/models/recipe_book.coffee | 2 +- src/scripts/models/recipe_catalog.coffee | 26 ++++-- src/scripts/views.js | 4 +- src/templates/crafting_table.jade | 7 +- src/templates/landing_page.jade | 2 +- test/crafting_plan.test.coffee | 79 ++++++++++++++++ test/inventory.test.coffee | 59 +++++++++--- test/inventory_parser.test.coffee | 9 +- test/test.coffee | 1 + 20 files changed, 427 insertions(+), 90 deletions(-) create mode 100644 src/scripts/models/crafting_plan.coffee create mode 100644 test/crafting_plan.test.coffee diff --git a/src/css/main.scss b/src/css/main.scss index db79a7e50..6e4976574 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -25,6 +25,7 @@ $color-brick-dark: #7A7A7A; $color-brick-light: #D9D9D9; $color-content: rgba(230, 230, 230, 0.90); $color-dirt: #906647; +$color-ice: #8AA7E6; $color-footer: #3D1D09; $color-footer-text: white; $color-gray-faint: #F2F2F2; diff --git a/src/css/templates/crafting_table.scss b/src/css/templates/crafting_table.scss index d97f2e2b9..0c8161125 100644 --- a/src/css/templates/crafting_table.scss +++ b/src/css/templates/crafting_table.scss @@ -7,6 +7,17 @@ All rights reserved. .view__crafting_table { + .quantity { + border-radius: 0.75em; + background: $color-ice; + color: $color-table-background; + display: inline-block; + text-align: center; + min-width: 3em; + margin-right: 0.5em; + padding: 0 0.5em; + } + .recipe_selector { background: $color-table-background; padding: 1em; @@ -28,18 +39,30 @@ All rights reserved. margin-bottom: 0.5em; } - table { - background: white; - width: 100%; + li { + margin: 0.25em 0; + } - td { padding: 0 1em 1em 1em; width: 33%; } + table { background: $color-table-background; width: 100%; } + + td { + border-left: 0.1em solid $color-gray-light; + padding: 0 1em 1em 1em; + width: 25%; + + &:first-child { border-left: 0; } } textarea, ul { - border: 0.1em solid $color-gray-light; + position: relative; + font-size: $font-size-normal; width: 100%; height: 100%; - min-height: 6em; + min-height: 12em; padding: 0.2em; } + + textarea { font-family: $font-family-input; resize: vertical; } + + ul { font-family: $font-family-normal; } } diff --git a/src/data/industrial_craft.json b/src/data/industrial_craft.json index cb131a3d9..c7e895c51 100644 --- a/src/data/industrial_craft.json +++ b/src/data/industrial_craft.json @@ -454,7 +454,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Bronze Ingot"]], - "input": [[1, "Bronze Dust"], [125, "milli-coal"]], + "input": [[1, "Bronze Dust"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Bronze Leggings"]], @@ -502,7 +502,7 @@ "tools": ["Macerator"] }, { "output": [[1, "Copper Ingot"]], - "input": [[1, "Copper Ore"], [125, "milli-coal"]], + "input": [[1, "Copper Ore"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Crop-Matron"]], @@ -682,7 +682,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Refined Iron"]], - "input": [[1, "Iron Ingot"], [125, "milli-coal"]], + "input": [[1, "Iron Ingot"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[7, "Reinforced Glass"]], @@ -690,7 +690,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Rubber"]], - "input": [[1, "Resin"], [125, "milli-coal"]], + "input": [[1, "Resin"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Rubber Boots"]], @@ -706,7 +706,7 @@ "tools": ["Macerator"] }, { "output": [[1, "Silver Ingot"]], - "input": [[1, "Silver Ore"], [125, "milli-coal"]], + "input": [[1, "Silver Ore"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Solar Helmet"]], @@ -726,7 +726,7 @@ "tools": ["Macerator"] }, { "output": [[1, "Tin Ingot"]], - "input": [[1, "Tin Ore"], [125, "milli-coal"]], + "input": [[1, "Tin Ore"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Tree Tap"]], diff --git a/src/data/thermal_expansion.json b/src/data/thermal_expansion.json index ac9a38c5c..c5cd440c4 100644 --- a/src/data/thermal_expansion.json +++ b/src/data/thermal_expansion.json @@ -26,7 +26,7 @@ "tools": [] }, { "output": [[1, "Electrum Ingot"]], - "input": [[1, "Electrum Blend"], [125, "milli-coal"]], + "input": [[1, "Electrum Blend"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Energetic Infuser"]], @@ -86,7 +86,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Invar Ingot"]], - "input": [[1, "Invar Blend"], [125, "milli-coal"]], + "input": [[1, "Invar Blend"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Item Tesseract"]], @@ -94,7 +94,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Lead Ingot"]], - "input": [[1, "Lead Ore"], [125, "milli-coal"]], + "input": [[1, "Lead Ore"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Liquid Tesseract"]], @@ -190,7 +190,7 @@ "tools": ["Pulverizer"] }, { "output": [[1, "Shiny Ingot"]], - "input": [[1, "Shiny Dust"], [125, "milli-coal"]], + "input": [[1, "Shiny Dust"], [1, "{furnace fuel}"]], "tools": ["Crafting Table"] }, { "output": [[1, "Steam Engine"]], diff --git a/src/data/vanilla.json b/src/data/vanilla.json index a0796430f..f991ffe62 100644 --- a/src/data/vanilla.json +++ b/src/data/vanilla.json @@ -10,7 +10,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Baked Potato"]], - "input": [[1, "Potato"], [125, "milli-coal"]], + "input": [[1, "Potato"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Beacon"]], @@ -62,7 +62,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Brick"]], - "input": [[1, "Clay"], [125, "milli-coal"]], + "input": [[1, "Clay"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Bricks"]], @@ -74,7 +74,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Cactus Green"]], - "input": [[1, "Cactus"], [125, "milli-coal"]], + "input": [[1, "Cactus"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Cake"], [1, "Bucket"]], @@ -106,7 +106,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Charcoal"]], - "input": [[1, "Oak Log"], [125, "milli-coal"]], + "input": [[1, "Oak Log"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Chest"]], @@ -126,15 +126,15 @@ "tools": [] }, { "output": [[1, "Cooked Chicken"]], - "input": [[1, "Raw Chicken"], [125, "milli-coal"]], + "input": [[1, "Raw Chicken"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Cooked Fish"]], - "input": [[1, "Raw Fish"], [125, "milli-coal"]], + "input": [[1, "Raw Fish"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Cooked Porkchop"]], - "input": [[1, "Raw Porkchop"], [125, "milli-coal"]], + "input": [[1, "Raw Porkchop"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Cookie"]], @@ -210,7 +210,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Glass"]], - "input": [[1, "Sand"], [125, "milli-coal"]], + "input": [[1, "Sand"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[3, "Glass Bottle"]], @@ -250,7 +250,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Gold Ingot"]], - "input": [[1, "Gold Ore"], [125, "milli-coal"]], + "input": [[1, "Gold Ore"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[9, "Gold Nugget"]], @@ -310,7 +310,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Iron Ingot"]], - "input": [[1, "Iron Ore"], [125, "milli-coal"]], + "input": [[1, "Iron Ore"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Iron Leggings"]], @@ -422,7 +422,7 @@ "tools": [] }, { "output": [[1, "Nether Brick (item)"]], - "input": [[1, "Netherrack"], [125, "milli-coal"]], + "input": [[1, "Netherrack"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Nether Brick (block)"]], @@ -486,7 +486,7 @@ "tools": ["Crafting Table"] }, { "output": [[1, "Steak"]], - "input": [[1, "Raw Beef"], [125, "milli-coal"]], + "input": [[1, "Raw Beef"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[4, "Stick"]], @@ -502,7 +502,7 @@ "tools": [] }, { "output": [[1, "Stone"]], - "input": [[1, "Cobblestone"], [125, "milli-coal"]], + "input": [[1, "Cobblestone"], [1, "{furnace fuel}"]], "tools": ["Furnace"] }, { "output": [[1, "Stone Axe"]], diff --git a/src/scripts/controllers/crafting_table_controller.coffee b/src/scripts/controllers/crafting_table_controller.coffee index 9e05fa0fd..12cd27fc7 100644 --- a/src/scripts/controllers/crafting_table_controller.coffee +++ b/src/scripts/controllers/crafting_table_controller.coffee @@ -17,10 +17,7 @@ module.exports = class CraftingTableController extends BaseController options.templateName = 'crafting_table' super options - @_parser = new InventoryParser - @_name = null - @_quantity = null - @_includingTools = null + @_parser = new InventoryParser # Event Methods ################################################################################ @@ -29,22 +26,22 @@ module.exports = class CraftingTableController extends BaseController @model.have.clear() @_parser.parse @$haveField.val(), @model.have - @_recalculate() + @model.craft() onIncludingToolsFieldChanged: -> return unless @rendered - @_includingTools = @$('input[name="including_tools"]:checked').length > 0 - @_recalculate() + @model.includingTools = @$('input[name="including_tools"]:checked').length > 0 + @model.craft() onNameFieldChanged: -> return unless @rendered - @_name = @$nameField.val() - @_recalculate() + @model.name = @$nameField.val() + @model.craft() onQuantityFieldChanged: -> return unless @rendered - @_quantity = parseInt @$quantityField.find(":selected").val() - @_recalculate() + @model.quantity = parseInt @$quantityField.find(":selected").val() + @model.craft() # BaseController Overrides ##################################################################### @@ -54,13 +51,34 @@ module.exports = class CraftingTableController extends BaseController @$nameField = @$('input[name="name"]') @$quantityField = @$('select[name="quantity"]') + @$needList = @$('td.need ul') + @$makeList = @$('td.make ul') + @$resultList = @$('td.result ul') + super @onHaveFieldChanged() @onIncludingToolsFieldChanged() - @onNameFieldChanged() @onQuantityFieldChanged() + # do last so others are no-op until all is set + @onNameFieldChanged() + + refresh: -> + @$needList.empty() + @$makeList.empty() + @$resultList.empty() + + if @model.plan? + @model.plan.need.each (item)=> + @$needList.append "
  • #{item.quantity} #{item.name}
  • " + @model.plan.make.each (item)=> + @$makeList.append "
  • #{item.quantity} #{item.name}
  • " + @model.plan.result.each (item)=> + @$resultList.append "
  • #{item.quantity} #{item.name}
  • " + + super + # Backbone.View Overrides ###################################################################### events: @@ -68,9 +86,3 @@ module.exports = class CraftingTableController extends BaseController 'input input[name="name"]': 'onNameFieldChanged' 'input select[name="quantity"]': 'onQuantityFieldChanged' 'change input[name="including_tools"]': 'onIncludingToolsFieldChanged' - - # Private Methods ############################################################################## - - _recalculate: -> - return if not @_name? or @_name.length is 0 - @model.craft @_name, @_quantity, @_includingTools diff --git a/src/scripts/controllers/recipe_catalog_controller.coffee b/src/scripts/controllers/recipe_catalog_controller.coffee index 63a80ca44..1c58c512a 100644 --- a/src/scripts/controllers/recipe_catalog_controller.coffee +++ b/src/scripts/controllers/recipe_catalog_controller.coffee @@ -6,6 +6,7 @@ ### BaseController = require './base_controller' +{DefaultBookUrls} = require '../constants' RecipeBookController = require './recipe_book_controller' ######################################################################################################################## @@ -21,6 +22,10 @@ module.exports = class RecipeCatalogController extends BaseController # BaseController Overrides ##################################################################### + onWillRender: -> + if @model.books.length is 0 + @model.loadAllBooks DefaultBookUrls + onDidRender: -> @$table = @$('table') super diff --git a/src/scripts/models/crafting_plan.coffee b/src/scripts/models/crafting_plan.coffee new file mode 100644 index 000000000..261e44f87 --- /dev/null +++ b/src/scripts/models/crafting_plan.coffee @@ -0,0 +1,92 @@ +### +# Crafting Guide - crafting_plan.coffee +# +# Copyright (c) 2014 by Redwood Labs +# All rights reserved. +### + +Inventory = require './inventory' + +######################################################################################################################## + +module.exports = class CraftingPlan + + constructor: (@catalog, includingTools)-> + if not @catalog then throw new Error 'catalog is required' + @includingTools = if includingTools then true else false + @clear() + + # Public Methods ############################################################################### + + clear: -> + @make = new Inventory + @need = new Inventory + @result = new Inventory + @steps = [] + @_expected = new Inventory + @_pending = null + + return this + + craft: (name, quantity=1, have=null)-> + @clear() + @result.addInventory(have) if have? + + @_expected.add name, quantity + @_pending = @_expected.clone() + while not @_pending.isEmpty + @_processPending() + + @steps.reverse() + + # Object Overrides ############################################################################# + + toString: -> + return "#{@constructor.name} { need:#{@need}, make:#{@make}, result:#{@result} }" + + # Private Methods ############################################################################## + + _processPending: -> + targetItem = @_pending.pop() + return unless targetItem? + + recipes = @catalog.findRecipes targetItem.name + return if not recipes.length > 0 + recipe = recipes[0] + + if @includingTools + for tool in recipe.tools + totalExpected = @result.quantityOf(tool.name) + @_expected.quantityOf(tool.name) + if totalExpected < tool.quantity + @_pending.add tool.name, tool.quantity + @_expected.add tool.name, tool.quantity + + while @_totalQuantityOf(targetItem.name) < @_expected.quantityOf(targetItem.name) + @steps.push recipe + + for item in recipe.input + @_processInputItem item + + for item in recipe.output + @_processOutputItem item + + _processInputItem: (item)-> + quantityAvailable = @result.quantityOf item.name + quantityUsed = Math.min quantityAvailable, item.quantity + quantityNeeded = item.quantity - quantityUsed + + @result.remove item.name, quantityUsed + @_pending.add item.name, quantityNeeded + @need.add item.name, quantityNeeded + + _processOutputItem: (item)-> + quantityMissing = @need.quantityOf item.name + quantityUsed = Math.min quantityMissing, item.quantity + quantityLeft = item.quantity - quantityUsed + + @need.remove item.name, quantityUsed + @result.add item.name, quantityLeft + @make.add item.name, item.quantity + + _totalQuantityOf: (name)-> + return @result.quantityOf(name) - @need.quantityOf(name) diff --git a/src/scripts/models/crafting_table.coffee b/src/scripts/models/crafting_table.coffee index f12e97b82..e0fda88b0 100644 --- a/src/scripts/models/crafting_table.coffee +++ b/src/scripts/models/crafting_table.coffee @@ -5,8 +5,10 @@ # All rights reserved. ### -BaseModel = require './base_model' -Inventory = require './inventory' +BaseModel = require './base_model' +CraftingPlan = require './crafting_plan' +{Event} = require '../constants' +Inventory = require './inventory' ######################################################################################################################## @@ -14,14 +16,36 @@ module.exports = class CraftingTable extends BaseModel constructor: (attributes={}, options={})-> if not attributes.catalog? then throw new Error "attributes.catalog is required" + attributes.name ?= null + attributes.quantity ?= 1 + attributes.includingTools ?= false + attributes.have ?= new Inventory + attributes.plan ?= null super attributes, options - @have = new Inventory - @make = new Inventory - @get = new Inventory - # Public Methods ############################################################################### - craft: (name, quantity=1, includingTools=false)-> - toolPhrase = if includingTools then ' includingTools' else '' - logger.verbose "calculating build plan for #{quantity} #{name}#{toolPhrase} with inventory: #{@have}" + craft: -> + if not @name?.length + @plan = null + return + + toolPhrase = if @includingTools then ' includingTools' else '' + logger.verbose "calculating build plan for #{@quantity} #{@name}#{toolPhrase} with inventory: #{@have}" + + plan = new CraftingPlan @catalog, @includingTools + plan.includingTools = @includingTools + plan.craft @name, @quantity, @have + + @plan = plan + + # Object Overrides ############################################################################# + + toString: -> + return "#{@constructor.name} (#{@cid}) { + name:#{@name}, + quantity:#{@quantity}, + includingTools:#{@includingTools}, + have:#{@have}, + plan:#{@plan} + }" diff --git a/src/scripts/models/inventory.coffee b/src/scripts/models/inventory.coffee index ebea29f1d..c7184d752 100644 --- a/src/scripts/models/inventory.coffee +++ b/src/scripts/models/inventory.coffee @@ -17,14 +17,19 @@ module.exports = class Inventory extends BaseModel super attributes, options @clear() + Object.defineProperty this, 'isEmpty', get:-> @_names.length is 0 + # Public Methods ############################################################################### add: (name, quantity=1)-> + return if quantity is 0 + item = @_items[name] if not item? item = new Item name:name, quantity:quantity @_items[name] = item @_names.push name + @_names.sort() else item.quantity += quantity @@ -32,10 +37,19 @@ module.exports = class Inventory extends BaseModel @trigger Event.change, this return this + addInventory: (inventory)-> + inventory.each (item)=> @add item.name, item.quantity + return this + clear: -> @_items = {} @_names = [] + clone: -> + inventory = new Inventory + @each (item)-> inventory.add item.name, item.quantity + return inventory + each: (onItem)-> for name in @_names item = @_items[name] @@ -48,7 +62,25 @@ module.exports = class Inventory extends BaseModel return false unless item? return item.quantity >= quantity + pop: -> + name = @_names.pop() + return null unless name? + + item = @_items[name] + delete @_items[name] + + @trigger Event.remove, this, item.name, item.quantity + @trigger Event.change, this + return item + + quantityOf: (name)-> + item = @_items[name] + return 0 unless item? + return item.quantity + remove: (name, quantity=1)-> + return if quantity is 0 + item = @_items[name] if not item? then throw new Error "cannot remove #{name} since it is not in this inventory" if item.quantity < quantity @@ -63,13 +95,22 @@ module.exports = class Inventory extends BaseModel @trigger Event.change, this return this + toList: -> + result = [] + @each (item)-> + if item.quantity > 1 + result.push [item.quantity, item.name] + else + result.push item.name + return result + # Object Overrides ############################################################################# toString: -> result = [@constructor.name, " (", @cid, ") { items: ["] needsDelimiter = false - for name, item of @_items + @each (item)-> if needsDelimiter then result.push ', ' result.push item.toString() needsDelimiter = true diff --git a/src/scripts/models/recipe.coffee b/src/scripts/models/recipe.coffee index bf0522a0b..c374d4fcf 100644 --- a/src/scripts/models/recipe.coffee +++ b/src/scripts/models/recipe.coffee @@ -19,6 +19,22 @@ module.exports = class Recipe extends BaseModel Object.defineProperty this, 'name', get:-> @output[0].name + # Public Methods ############################################################################### + + make: (inventory, missing)-> + for item in @input + needed = item.quantity + while needed > 0 + if inventory.hasAtLeast item.name + inventory.remove item.name + else + missing.add item.name + + for item in @output + inventory.add item.name, item.quantity + + return this + # Object Overrides ############################################################################# toString: -> diff --git a/src/scripts/models/recipe_book.coffee b/src/scripts/models/recipe_book.coffee index 02c0d7013..ec8594ae7 100644 --- a/src/scripts/models/recipe_book.coffee +++ b/src/scripts/models/recipe_book.coffee @@ -21,7 +21,7 @@ module.exports = class RecipeBook extends BaseModel # Public Methods ############################################################################### - getRecipes: (name)-> + findRecipes: (name)-> result = [] for recipe in @recipes if recipe.name is name diff --git a/src/scripts/models/recipe_catalog.coffee b/src/scripts/models/recipe_catalog.coffee index a9be601f6..7fa2f1164 100644 --- a/src/scripts/models/recipe_catalog.coffee +++ b/src/scripts/models/recipe_catalog.coffee @@ -6,7 +6,6 @@ ### BaseModel = require './base_model' -{DefaultBookUrls} = require '../constants' {Event} = require '../constants' RecipeBookParser = require './recipe_book_parser' @@ -19,11 +18,17 @@ module.exports = class RecipeCatalog extends BaseModel super attributes, options @_parser = new RecipeBookParser - if @books.length is 0 - @loadAllBooks DefaultBookUrls # Public Methods ############################################################################### + findRecipes: (name)-> + result = [] + for book in @books + for recipe in book.findRecipes name + result.push recipe + + return result + loadBook: (url)-> w.promise (resolve, reject)=> @trigger Event.load.started, this, url @@ -35,6 +40,15 @@ module.exports = class RecipeCatalog extends BaseModel error: (xhr, status, error)=> reject @onBookLoadFailed(url, error, status, xhr) + loadBookData: (data)-> + book = @_parser.parse data + @books.push book + @books.sort (a, b)-> + return 0 if a.modName is b.modName + return if a.modName < b.modName then -1 else +1 + + return book + loadAllBooks: (urlList)-> promises = (@loadBook(url) for url in urlList) return w.settle promises @@ -43,11 +57,7 @@ module.exports = class RecipeCatalog extends BaseModel onBookLoaded: (url, data, status, xhr)-> try - book = @_parser.parse data - @books.push book - @books.sort (a, b)-> - return 0 if a.modName is b.modName - return if a.modName < b.modName then -1 else +1 + book = @loadBookData data logger.info "loaded recipe book from #{url}: #{book}" @trigger Event.load.succeeded, this, book diff --git a/src/scripts/views.js b/src/scripts/views.js index 5f4aa6df7..c58fa6bf9 100644 --- a/src/scripts/views.js +++ b/src/scripts/views.js @@ -8,7 +8,7 @@ var jade_mixins = {}; var jade_interp; var jade_indent = []; -buf.push("\n
    \n

    \n

    Crafting Table

    \n

    \n
    \n

    I want to make

    \n \n \n \n
    \n \n \n \n \n \n \n
    \n

    \n

    You have...

    \n

    \n \n
    \n

    \n

    You'll make...

    \n

    \n
      \n
      \n

      \n

      You'll get...

      \n

      \n
        \n
        \n
        ");;return buf.join(""); +buf.push("\n
        \n

        \n

        Crafting Table

        \n

        \n
        \n

        I want to make

        \n \n \n \n
        \n \n \n \n \n \n \n \n
        \n

        \n

        You have...

        \n

        \n \n
        \n

        \n

        You'll need...

        \n

        \n
          \n
          \n

          \n

          You'll make...

          \n

          \n
            \n
            \n

            \n

            You'll get...

            \n

            \n
              \n
              \n
              ");;return buf.join(""); }; this["JST"]["landing_page"] = function template(locals) { @@ -17,7 +17,7 @@ var jade_mixins = {}; var jade_interp; var jade_indent = []; -buf.push("\n
              \n
              \n
              \n
              ");;return buf.join(""); +buf.push("\n
              \n
              \n
              \n
              ");;return buf.join(""); }; this["JST"]["recipe_book"] = function template(locals) { diff --git a/src/templates/crafting_table.jade b/src/templates/crafting_table.jade index 2a56fb8bb..062f75312 100644 --- a/src/templates/crafting_table.jade +++ b/src/templates/crafting_table.jade @@ -28,10 +28,13 @@ tr td.have h3: p You have... - textarea(name="have", placeholder="enter what you have, for example:\n\n3 wool\n4 iron ingot") + textarea(name="have", placeholder="For example:\n\n3 wool\n4 iron ingot") + td.need + h3: p You'll need... + ul td.make h3: p You'll make... ul - td.get + td.result h3: p You'll get... ul diff --git a/src/templates/landing_page.jade b/src/templates/landing_page.jade index 96b52522e..a35634e99 100644 --- a/src/templates/landing_page.jade +++ b/src/templates/landing_page.jade @@ -6,5 +6,5 @@ //- .view__landing_page - .view__recipe_catalog .view__crafting_table + .view__recipe_catalog diff --git a/test/crafting_plan.test.coffee b/test/crafting_plan.test.coffee new file mode 100644 index 000000000..232032866 --- /dev/null +++ b/test/crafting_plan.test.coffee @@ -0,0 +1,79 @@ +### +# Crafting Guide - crafting_plan.test.coffee +# +# Copyright (c) 2014 by Redwood Labs +# All rights reserved. +### + +RecipeCatalog = require '../src/scripts/models/recipe_catalog' +CraftingPlan = require '../src/scripts/models/crafting_plan' + +######################################################################################################################## + +catalog = plan = null + +######################################################################################################################## + +describe 'CraftingPlan', -> + + beforeEach -> + catalog = new RecipeCatalog + catalog.loadBookData { + version: 1 + mod_name: 'Minecraft' + mod_version: '1.7.10' + recipes: [ + { input:'Oak Log', output:[[4, 'Oak Plank']] } + { input:[[2, 'Oak Plank']], output:[[4, 'Stick']] } + { input:[[4, 'Oak Plank']], output:'Crafting Table' } + { input:[[8, 'Cobblestone']], tools:'Crafting Table', output:'Furnace' } + { input:['Iron Ore', '{furnace fuel}'], tools:'Furnace', output:'Iron Ingot' } + { input:[[2, 'Iron Ingot'], 'Stick'], tools:'Crafting Table', output:'Iron Sword' } + ] + } + + plan = new CraftingPlan catalog + + describe 'craft', -> + + describe 'under the simplest conditions', -> + + it 'can craft a single step recipe', -> + plan.craft 'Oak Plank' + plan.need.toList().should.eql ['Oak Log'] + plan.result.toList().should.eql [[4, 'Oak Plank']] + + it 'can craft a multi-step recipe', -> + plan.craft 'Crafting Table' + plan.need.toList().should.eql ['Oak Log'] + plan.result.toList().should.eql ['Crafting Table'] + + it 'can craft a multi-step recipe using tools', -> + plan.craft 'Furnace' + plan.need.toList().should.eql [[8, 'Cobblestone']] + plan.result.toList().should.eql ['Furnace'] + + it 'can craft a multi-step recipe re-using tools', -> + plan.craft 'Iron Sword' + plan.need.toList().should.eql [[2, 'Iron Ore'], 'Oak Log', [2, '{furnace fuel}']] + plan.result.toList().should.eql ['Iron Sword', [2, 'Oak Plank'], [3, 'Stick']] + + describe 'with building tools', -> + + it 'can craft a multi-step recipe using tools', -> + plan.includingTools = true + plan.craft 'Furnace' + plan.need.toList().should.eql [[8, 'Cobblestone'], 'Oak Log'] + plan.result.toList().should.eql ['Crafting Table', 'Furnace'] + + it 'can craft a multi-step recipe re-using tools', -> + plan.includingTools = true + plan.craft 'Iron Sword' + plan.need.toList().should.eql [ + [8, 'Cobblestone'], [2, 'Iron Ore'], [2, 'Oak Log'], [2, '{furnace fuel}'], + ] + plan.result.toList().should.eql [ + 'Crafting Table', 'Furnace', 'Iron Sword', [2, 'Oak Plank'], [3, 'Stick'] + ] + + describe 'using existing inventory', -> diff --git a/test/inventory.test.coffee b/test/inventory.test.coffee index 66ca70247..c53116af1 100644 --- a/test/inventory.test.coffee +++ b/test/inventory.test.coffee @@ -35,17 +35,42 @@ describe 'Inventory', -> it 'can augment quantity of existing items', -> inventory.add 'wool', 2 - inventory._items.wool.quantity.should.equal 6 + inventory.toList().should.eql ['boat', [20, 'string'], [6, 'wool']] it 'can add zero quantity', -> inventory.add 'wool', 0 - inventory._items.wool.quantity.should.equal 4 + inventory.toList().should.eql ['boat', [20, 'string'], [4, 'wool']] it 'emits the proper events', -> events = new EventRecorder inventory inventory.add 'iron ingot', 10 events.names.should.eql [Event.add, Event.change] + describe 'addInventory', -> + + it 'can add to an empty inventory', -> + newInventory = new Inventory + newInventory.addInventory inventory + newInventory._names.should.eql ['boat', 'string', 'wool'] + + it 'can add a mix of new and existing items', -> + newInventory = new Inventory + newInventory.add 'string', 2 + newInventory.addInventory inventory + newInventory.toList().should.eql ['boat', [22, 'string'], [4, 'wool']] + + describe 'clone', -> + + it 'creates an empty inventory from an empty inventory', -> + a = new Inventory + b = a.clone() + b._names.should.eql [] + + it 'faithfully copies an existing inventory', -> + copy = inventory.clone() + copy._names.should.eql ['boat', 'string', 'wool'] + (item.quantity for name, item of copy._items).should.eql [1, 20, 4] + describe 'each', -> it 'works with an empty inventory', -> @@ -57,7 +82,7 @@ describe 'Inventory', -> it 'works when items have only been added', -> result = [] inventory.each (item)-> result.push item.name - result.should.eql ['wool', 'string', 'boat'] + result.should.eql ['boat', 'string', 'wool'] it 'works when items have been augmented', -> inventory.add 'iron ingot' @@ -66,15 +91,7 @@ describe 'Inventory', -> result = [] inventory.each (item)-> result.push item.name - result.should.eql ['wool', 'string', 'boat', 'iron ingot'] - - it 'moves items to the end when removed and re-added', -> - inventory.remove 'wool', 4 - inventory.add 'wool' - - result = [] - inventory.each (item)-> result.push item.name - result.should.eql ['string', 'boat', 'wool'] + result.should.eql ['boat', 'iron ingot', 'string', 'wool'] describe 'hasAtLeast', -> @@ -91,6 +108,24 @@ describe 'Inventory', -> inventory.hasAtLeast('wool', 4).should.be.true inventory.hasAtLeast('wool', 5).should.be.false + describe 'pop', -> + + it 'returns null for an empty inventory', -> + inventory = new Inventory + result = inventory.pop() + expect(result).to.be.null + + it 'completely removes the last item', -> + result = inventory.pop() + result.name.should.equal 'wool' + result.quantity.should.equal 4 + inventory.toList().should.eql ['boat', [20, 'string']] + + it 'triggers the right events', -> + events = new EventRecorder inventory + result = inventory.pop() + events.names.should.eql [Event.remove, Event.change] + describe 'remove', -> it 'throws when the item is absent', -> diff --git a/test/inventory_parser.test.coffee b/test/inventory_parser.test.coffee index 7f2953f37..616cd6c82 100644 --- a/test/inventory_parser.test.coffee +++ b/test/inventory_parser.test.coffee @@ -32,15 +32,10 @@ describe 'InventoryParser', -> it 'can parse multiple mixed-type items', -> result = parser.parse '4 wool\n10 string\nboat\n\n' - result._items.wool.quantity.should.equal 4 - result._items.string.quantity.should.equal 10 - result._items.boat.quantity.should.equal 1 - result._names.should.eql ['wool', 'string', 'boat'] + result.toList().should.eql ['boat', [10, 'string'], [4, 'wool']] it 're-uses the given inventory object', -> inventory = new Inventory inventory.add 'string', 8 result = parser.parse '4 wool', inventory - inventory._items.string.quantity.should.equal 8 - inventory._items.wool.quantity.should.equal 4 - inventory._names.should.eql ['string', 'wool'] + result.toList().should.eql [[8, 'string'], [4, 'wool']] diff --git a/test/test.coffee b/test/test.coffee index 5f25ced17..ab8b84055 100644 --- a/test/test.coffee +++ b/test/test.coffee @@ -24,6 +24,7 @@ global.logger = new Logger level:Logger.TRACE mocha.setup 'bdd' +require './crafting_plan.test' require './inventory.test' require './inventory_parser.test' require './recipe_book_parser.test'