Complete basic crafting functionality

This commit is contained in:
Andrew Miner
2014-12-24 19:17:37 -08:00
parent 3ea6a845c7
commit 1a84ce2e28
20 changed files with 427 additions and 90 deletions
@@ -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 "<li><span class='quantity'>#{item.quantity}</span> #{item.name}</li>"
@model.plan.make.each (item)=>
@$makeList.append "<li><span class='quantity'>#{item.quantity}</span> #{item.name}</li>"
@model.plan.result.each (item)=>
@$resultList.append "<li><span class='quantity'>#{item.quantity}</span> #{item.name}</li>"
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
@@ -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
+92
View File
@@ -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)
+33 -9
View File
@@ -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}
}"
+42 -1
View File
@@ -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
+16
View File
@@ -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: ->
+1 -1
View File
@@ -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
+18 -8
View File
@@ -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
+2 -2
View File
@@ -8,7 +8,7 @@ var jade_mixins = {};
var jade_interp;
var jade_indent = [];
buf.push("\n<div class=\"view__crafting_table\">\n <h2><img src=\"/images/workbench_top.png\"/>\n <p>Crafting Table</p>\n </h2>\n <div class=\"recipe_selector\">\n <p>I want to make</p>\n <select name=\"quantity\">\n <option value=\"1\">1</option>\n <option value=\"2\">2</option>\n <option value=\"4\">4</option>\n <option value=\"8\">8</option>\n <option value=\"16\">16</option>\n <option value=\"64\">64</option>\n </select>\n <input name=\"name\" placeholder=\"enter an item name\"/>\n <label>\n <input name=\"including_tools\" type=\"checkbox\"/>including tools\n </label>\n </div>\n <table>\n <tr>\n <td class=\"have\">\n <h3>\n <p>You have...</p>\n </h3>\n <textarea name=\"have\" placeholder=\"enter what you have, for example:\n\n3 wool\n4 iron ingot\"></textarea>\n </td>\n <td class=\"make\">\n <h3>\n <p>You'll make...</p>\n </h3>\n <ul></ul>\n </td>\n <td class=\"get\">\n <h3>\n <p>You'll get...</p>\n </h3>\n <ul></ul>\n </td>\n </tr>\n </table>\n</div>");;return buf.join("");
buf.push("\n<div class=\"view__crafting_table\">\n <h2><img src=\"/images/workbench_top.png\"/>\n <p>Crafting Table</p>\n </h2>\n <div class=\"recipe_selector\">\n <p>I want to make</p>\n <select name=\"quantity\">\n <option value=\"1\">1</option>\n <option value=\"2\">2</option>\n <option value=\"4\">4</option>\n <option value=\"8\">8</option>\n <option value=\"16\">16</option>\n <option value=\"64\">64</option>\n </select>\n <input name=\"name\" placeholder=\"enter an item name\"/>\n <label>\n <input name=\"including_tools\" type=\"checkbox\"/>including tools\n </label>\n </div>\n <table>\n <tr>\n <td class=\"have\">\n <h3>\n <p>You have...</p>\n </h3>\n <textarea name=\"have\" placeholder=\"For example:\n\n3 wool\n4 iron ingot\"></textarea>\n </td>\n <td class=\"need\">\n <h3>\n <p>You'll need...</p>\n </h3>\n <ul></ul>\n </td>\n <td class=\"make\">\n <h3>\n <p>You'll make...</p>\n </h3>\n <ul></ul>\n </td>\n <td class=\"result\">\n <h3>\n <p>You'll get...</p>\n </h3>\n <ul></ul>\n </td>\n </tr>\n </table>\n</div>");;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<div class=\"view__landing_page\">\n <div class=\"view__recipe_catalog\"></div>\n <div class=\"view__crafting_table\"></div>\n</div>");;return buf.join("");
buf.push("\n<div class=\"view__landing_page\">\n <div class=\"view__crafting_table\"></div>\n <div class=\"view__recipe_catalog\"></div>\n</div>");;return buf.join("");
};
this["JST"]["recipe_book"] = function template(locals) {