Added CraftingTable* components and UI
This commit is contained in:
+6
-3
@@ -98,9 +98,9 @@ module.exports = (grunt)->
|
|||||||
images:
|
images:
|
||||||
files: ['./src/images/**/*']
|
files: ['./src/images/**/*']
|
||||||
tasks: ['copy:images']
|
tasks: ['copy:images']
|
||||||
browserify:
|
source:
|
||||||
files: ['./src/scripts/**/*.coffee', './test/**/*.coffee']
|
files: ['./src/scripts/**/*.coffee']
|
||||||
tasks: ['browserify']
|
tasks: ['browserify:main']
|
||||||
pages:
|
pages:
|
||||||
files: ['./src/pages/**/*.jade']
|
files: ['./src/pages/**/*.jade']
|
||||||
tasks: ['jade:pages']
|
tasks: ['jade:pages']
|
||||||
@@ -110,6 +110,9 @@ module.exports = (grunt)->
|
|||||||
sass:
|
sass:
|
||||||
files: ['./src/css/**/*.scss']
|
files: ['./src/css/**/*.scss']
|
||||||
tasks: ['sass', 'copy:styles']
|
tasks: ['sass', 'copy:styles']
|
||||||
|
tests:
|
||||||
|
files: ['./src/scripts/**/*.coffee', './test/**/*.coffee']
|
||||||
|
tasks: ['browserify:test']
|
||||||
|
|
||||||
grunt.registerTask 'default', 'build'
|
grunt.registerTask 'default', 'build'
|
||||||
|
|
||||||
|
|||||||
+59
-18
@@ -8,17 +8,6 @@ All rights reserved.
|
|||||||
@import 'mixins';
|
@import 'mixins';
|
||||||
@import 'reset';
|
@import 'reset';
|
||||||
|
|
||||||
// Constants ///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
$color-background: #AAC9FF;
|
|
||||||
$color-bedrock: #0A0A0A;
|
|
||||||
$color-content: rgba(230, 230, 230, 0.90);
|
|
||||||
$color-brick-dark: #7A7A7A;
|
|
||||||
$color-brick-light: #D9D9D9;
|
|
||||||
$color-footer: #3D1D09;
|
|
||||||
$color-footer-text: white;
|
|
||||||
$color-dirt: #906647;
|
|
||||||
|
|
||||||
// Fonts ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
// Fonts ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -28,6 +17,29 @@ $color-dirt: #906647;
|
|||||||
src: url('/fonts/press_start.ttf') format('truetype');
|
src: url('/fonts/press_start.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constants ///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
$color-background: #AAC9FF;
|
||||||
|
$color-bedrock: #0A0A0A;
|
||||||
|
$color-brick-dark: #7A7A7A;
|
||||||
|
$color-brick-light: #D9D9D9;
|
||||||
|
$color-content: rgba(230, 230, 230, 0.90);
|
||||||
|
$color-dirt: #906647;
|
||||||
|
$color-footer: #3D1D09;
|
||||||
|
$color-footer-text: white;
|
||||||
|
$color-gray-faint: #F2F2F2;
|
||||||
|
$color-gray-light: #CCCCCC;
|
||||||
|
$color-table-background: white;
|
||||||
|
|
||||||
|
$font-family-header: 'PressStart';
|
||||||
|
$font-family-normal: sans-serif;
|
||||||
|
$font-family-input: monospace;
|
||||||
|
|
||||||
|
$font-size-small: 1.1em;
|
||||||
|
$font-size-normal: 1.3em;
|
||||||
|
$font-size-large: 2.0em;
|
||||||
|
$font-size-x-large: 3.0em;
|
||||||
|
|
||||||
// Common //////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
// Common //////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -47,8 +59,8 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: PressStart;
|
font-family: $font-family-header;
|
||||||
font-size: 3em;
|
font-size: $font-size-x-large;
|
||||||
position: relative; top: -0.5em;
|
position: relative; top: -0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,18 +71,40 @@ h2 {
|
|||||||
* { display: inline; }
|
* { display: inline; }
|
||||||
|
|
||||||
img {
|
img {
|
||||||
position: relative; height: 6em; width: 6em;
|
position: relative; height: 4em; width: 4em;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: PressStart;
|
font-family: $font-family-header;
|
||||||
font-size: 2em;
|
font-size: $font-size-large;
|
||||||
position: relative; top: -0.25em;
|
position: relative; top: -0.25em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-family: $font-family-normal;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
p { font-size: $font-size-normal; }
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: $font-family-normal;
|
||||||
|
font-size: $font-size-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-family: $font-family-input;
|
||||||
|
font-size: $font-size-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-family: $font-family-normal;
|
||||||
|
font-size: $font-size-normal;
|
||||||
|
}
|
||||||
|
|
||||||
.centered {
|
.centered {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -84,6 +118,13 @@ h2 {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page > div {
|
||||||
|
position: relative; width: 100%;
|
||||||
|
padding: 2em 2em 0 2em;
|
||||||
|
|
||||||
|
&:first-child { padding-top: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
// Views ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
// Views ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
.view__footer {
|
.view__footer {
|
||||||
@@ -111,7 +152,7 @@ h2 {
|
|||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-size: 1.25em;
|
font-size: $font-size-small;
|
||||||
padding-bottom: 1.0em;
|
padding-bottom: 1.0em;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
@@ -123,7 +164,7 @@ h2 {
|
|||||||
padding-bottom: 1.0em;
|
padding-bottom: 1.0em;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: PressStart;
|
font-family: $font-family-header;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,dialog,div,dl,dt,em,embed,fieldset,figcaption,figure,font,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,meter,nav,object,ol,output,p,pre,progress,q,rp,rt,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video,xmp {
|
a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,dialog,div,dl,dt,em,embed,fieldset,figcaption,figure,font,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,meter,nav,object,ol,output,p,pre,progress,q,rp,rt,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,textarea,tfoot,th,thead,time,tr,tt,u,ul,var,video,xmp {
|
||||||
border: 0;
|
border: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
Crafting Table - crafting_table.scss
|
||||||
|
|
||||||
|
Copyright (c) 2014 by Redwood Labs
|
||||||
|
All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.view__crafting_table {
|
||||||
|
|
||||||
|
.recipe_selector {
|
||||||
|
background: $color-table-background;
|
||||||
|
padding: 1em;
|
||||||
|
position: relative; width: 100%;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
|
||||||
|
&:first-child { margin-left: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] + label { margin-left: 0; }
|
||||||
|
|
||||||
|
input[name="name"] { width: 60%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
background: white;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
td { padding: 0 1em 1em 1em; width: 33%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea, ul {
|
||||||
|
border: 0.1em solid $color-gray-light;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 6em;
|
||||||
|
padding: 0.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,4 +5,5 @@ Copyright (c) 2014 by Redwood Labs
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import 'crafting_table';
|
||||||
@import 'recipe_catalog';
|
@import 'recipe_catalog';
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ All rights reserved.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.view__recipe_catalog {
|
.view__recipe_catalog {
|
||||||
position: relative; width: 100%;
|
|
||||||
padding: 0 2em;
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@include center-vertical;
|
@include center-vertical;
|
||||||
position: absolute; right: 1em;
|
position: absolute; right: 1em;
|
||||||
@@ -19,12 +16,12 @@ All rights reserved.
|
|||||||
table {
|
table {
|
||||||
background: white;
|
background: white;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 98%;
|
width: 100%;
|
||||||
|
|
||||||
td {
|
td {
|
||||||
height: 3em; padding: 0 1em; position: relative; vertical-align: middle;
|
position: relative; height: 2 * $font-size-normal;
|
||||||
|
padding: 0 1em;
|
||||||
p { font-size: 1.5em; }
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
td:nth-child(1) { width: 30%; }
|
td:nth-child(1) { width: 30%; }
|
||||||
|
|||||||
@@ -30,4 +30,5 @@
|
|||||||
.centered
|
.centered
|
||||||
iframe(src="http://ghbtns.com/github-btn.html?user=andrewminer&repo=crafting-guide&type=fork&size=large",
|
iframe(src="http://ghbtns.com/github-btn.html?user=andrewminer&repo=crafting-guide&type=fork&size=large",
|
||||||
allowtransparency="true", frameborder="0", scrolling="0", width="100", height="32")
|
allowtransparency="true", frameborder="0", scrolling="0", width="100", height="32")
|
||||||
.divider.bottom
|
a(href="/test.html")
|
||||||
|
.divider.bottom
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
.view__header
|
.view__header
|
||||||
h1
|
h1
|
||||||
img(src="images/workbench_front.png")
|
a(href="/")
|
||||||
|
img(src="images/workbench_front.png")
|
||||||
p Crafting Guide
|
p Crafting Guide
|
||||||
.divider.bottom
|
.divider.bottom
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ Opacity.hidden = 1e-6
|
|||||||
Opacity.shown = 1
|
Opacity.shown = 1
|
||||||
|
|
||||||
exports.Event = Event = {}
|
exports.Event = Event = {}
|
||||||
Event.change = 'change'
|
Event.add = 'add' # collection, item...
|
||||||
|
Event.change = 'change' # model
|
||||||
Event.load = {}
|
Event.load = {}
|
||||||
Event.load.started = 'book:load:started' # controller, url
|
Event.load.started = 'load:started' # controller, url
|
||||||
Event.load.succeeded = 'book:load:succeeded' # controller, book
|
Event.load.succeeded = 'load:succeeded' # controller, book
|
||||||
Event.load.failed = 'book:load:failed' # controller, error message
|
Event.load.failed = 'load:failed' # controller, error message
|
||||||
Event.load.finished = 'book:load:finished' # controller
|
Event.load.finished = 'load:finished' # controller
|
||||||
|
Event.remove = 'remove' # collection, item...
|
||||||
|
|
||||||
exports.DefaultBookUrls = DefaultBookUrls = [
|
exports.DefaultBookUrls = DefaultBookUrls = [
|
||||||
'/data/vanilla.json'
|
'/data/vanilla.json'
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ module.exports = class BaseController extends Backbone.View
|
|||||||
@_parent = options.parent
|
@_parent = options.parent
|
||||||
@_children = []
|
@_children = []
|
||||||
|
|
||||||
|
Object.defineProperty this, 'rendered', get:-> return @_rendered
|
||||||
@_loadTemplate options.templateName
|
@_loadTemplate options.templateName
|
||||||
super options
|
super options
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
###
|
||||||
|
# Crafting Guide - crafting_table_controller.coffee
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 by Redwood Labs
|
||||||
|
# All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
BaseController = require './base_controller'
|
||||||
|
InventoryParser = require '../models/inventory_parser'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
module.exports = class CraftingTableController extends BaseController
|
||||||
|
|
||||||
|
constructor: (options={})->
|
||||||
|
if not options.model? then throw new Error 'options.model is required'
|
||||||
|
options.templateName = 'crafting_table'
|
||||||
|
super options
|
||||||
|
|
||||||
|
@_parser = new InventoryParser
|
||||||
|
@_name = null
|
||||||
|
@_quantity = null
|
||||||
|
@_includingTools = null
|
||||||
|
|
||||||
|
# Event Methods ################################################################################
|
||||||
|
|
||||||
|
onHaveFieldChanged: ->
|
||||||
|
return unless @rendered
|
||||||
|
|
||||||
|
@model.have.clear()
|
||||||
|
@_parser.parse @$haveField.val(), @model.have
|
||||||
|
@_recalculate()
|
||||||
|
|
||||||
|
onIncludingToolsFieldChanged: ->
|
||||||
|
return unless @rendered
|
||||||
|
@_includingTools = @$('input[name="including_tools"]:checked').length > 0
|
||||||
|
@_recalculate()
|
||||||
|
|
||||||
|
onNameFieldChanged: ->
|
||||||
|
return unless @rendered
|
||||||
|
@_name = @$nameField.val()
|
||||||
|
@_recalculate()
|
||||||
|
|
||||||
|
onQuantityFieldChanged: ->
|
||||||
|
return unless @rendered
|
||||||
|
@_quantity = parseInt @$quantityField.find(":selected").val()
|
||||||
|
@_recalculate()
|
||||||
|
|
||||||
|
# BaseController Overrides #####################################################################
|
||||||
|
|
||||||
|
onDidRender: ->
|
||||||
|
@$haveField = @$('textarea[name="have"]')
|
||||||
|
@$includingToolsField = @$('input[name="including_tools"]')
|
||||||
|
@$nameField = @$('input[name="name"]')
|
||||||
|
@$quantityField = @$('select[name="quantity"]')
|
||||||
|
|
||||||
|
super
|
||||||
|
|
||||||
|
@onHaveFieldChanged()
|
||||||
|
@onIncludingToolsFieldChanged()
|
||||||
|
@onNameFieldChanged()
|
||||||
|
@onQuantityFieldChanged()
|
||||||
|
|
||||||
|
# Backbone.View Overrides ######################################################################
|
||||||
|
|
||||||
|
events:
|
||||||
|
'input textarea[name="have"]': 'onHaveFieldChanged'
|
||||||
|
'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
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
###
|
###
|
||||||
|
|
||||||
BaseController = require './base_controller'
|
BaseController = require './base_controller'
|
||||||
|
CraftingTableController = require './crafting_table_controller'
|
||||||
LandingPage = require '../models/landing_page'
|
LandingPage = require '../models/landing_page'
|
||||||
RecipeCatalogController = require './recipe_catalog_controller'
|
RecipeCatalogController = require './recipe_catalog_controller'
|
||||||
|
|
||||||
@@ -21,5 +22,6 @@ module.exports = class LandingPageController extends BaseController
|
|||||||
# BaseController Overrides #####################################################################
|
# BaseController Overrides #####################################################################
|
||||||
|
|
||||||
onDidRender: ->
|
onDidRender: ->
|
||||||
@recipeBooksController = @addChild RecipeCatalogController, '.view__recipe_catalog', model:@model.catalog
|
@catalogController = @addChild RecipeCatalogController, '.view__recipe_catalog', model:@model.catalog
|
||||||
|
@tableController = @addChild CraftingTableController, '.view__crafting_table', model:@model.table
|
||||||
super
|
super
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ module.exports = class CraftingGuideRouter extends Backbone.Router
|
|||||||
controller.render()
|
controller.render()
|
||||||
|
|
||||||
$pageContent = $('.page')
|
$pageContent = $('.page')
|
||||||
$pageContent.empty()
|
controller.$el.addClass 'page'
|
||||||
$pageContent.append controller.$el
|
$pageContent.replaceWith controller.$el
|
||||||
|
|
||||||
controller.$el.fadeIn showDuration, ->
|
controller.$el.fadeIn showDuration, ->
|
||||||
controller.onDidShow()
|
controller.onDidShow()
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
###
|
||||||
|
# Crafting Guide - crafting_table.coffee
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 by Redwood Labs
|
||||||
|
# All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
BaseModel = require './base_model'
|
||||||
|
Inventory = require './inventory'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
module.exports = class CraftingTable extends BaseModel
|
||||||
|
|
||||||
|
constructor: (attributes={}, options={})->
|
||||||
|
if not attributes.catalog? then throw new Error "attributes.catalog is required"
|
||||||
|
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}"
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
###
|
||||||
|
# Crafting Guide - inventory.coffee
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 by Redwood Labs
|
||||||
|
# All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
BaseModel = require './base_model'
|
||||||
|
{Event} = require '../constants'
|
||||||
|
Item = require './item'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
module.exports = class Inventory extends BaseModel
|
||||||
|
|
||||||
|
constructor: (attributes={}, options={})->
|
||||||
|
super attributes, options
|
||||||
|
@clear()
|
||||||
|
|
||||||
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
|
add: (name, quantity=1)->
|
||||||
|
item = @_items[name]
|
||||||
|
if not item?
|
||||||
|
item = new Item name:name, quantity:quantity
|
||||||
|
@_items[name] = item
|
||||||
|
@_names.push name
|
||||||
|
else
|
||||||
|
item.quantity += quantity
|
||||||
|
|
||||||
|
@trigger Event.add, this, name, quantity
|
||||||
|
@trigger Event.change, this
|
||||||
|
return this
|
||||||
|
|
||||||
|
clear: ->
|
||||||
|
@_items = {}
|
||||||
|
@_names = []
|
||||||
|
|
||||||
|
each: (onItem)->
|
||||||
|
for name in @_names
|
||||||
|
item = @_items[name]
|
||||||
|
onItem item
|
||||||
|
|
||||||
|
hasAtLeast: (name, quantity=1)->
|
||||||
|
if quantity is 0 then return true
|
||||||
|
|
||||||
|
item = @_items[name]
|
||||||
|
return false unless item?
|
||||||
|
return item.quantity >= quantity
|
||||||
|
|
||||||
|
remove: (name, quantity=1)->
|
||||||
|
item = @_items[name]
|
||||||
|
if not item? then throw new Error "cannot remove #{name} since it is not in this inventory"
|
||||||
|
if item.quantity < quantity
|
||||||
|
throw new Error "cannot remove #{quantity} #{name} because there is only #{item.quantity} in this inventory"
|
||||||
|
|
||||||
|
item.quantity -= quantity
|
||||||
|
if item.quantity is 0
|
||||||
|
delete @_items[name]
|
||||||
|
@_names = _(@_names).without name
|
||||||
|
|
||||||
|
@trigger Event.remove, this, name, quantity
|
||||||
|
@trigger Event.change, this
|
||||||
|
return this
|
||||||
|
|
||||||
|
# Object Overrides #############################################################################
|
||||||
|
|
||||||
|
toString: ->
|
||||||
|
result = [@constructor.name, " (", @cid, ") { items: ["]
|
||||||
|
|
||||||
|
needsDelimiter = false
|
||||||
|
for name, item of @_items
|
||||||
|
if needsDelimiter then result.push ', '
|
||||||
|
result.push item.toString()
|
||||||
|
needsDelimiter = true
|
||||||
|
result.push ']'
|
||||||
|
|
||||||
|
result.push '}'
|
||||||
|
return result.join ''
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
###
|
||||||
|
# Crafting Guide - inventory_parser.coffee
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 by Redwood Labs
|
||||||
|
# All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
Inventory = require './inventory'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
module.exports = class InventoryParser
|
||||||
|
|
||||||
|
@ITEM_REGEX = /^([0-9]+)(.*)$/
|
||||||
|
|
||||||
|
parse: (data, inventory=null)->
|
||||||
|
inventory ?= new Inventory
|
||||||
|
return inventory if not data? or data.length is 0
|
||||||
|
|
||||||
|
lines = data.split '\n'
|
||||||
|
for line in lines
|
||||||
|
match = InventoryParser.ITEM_REGEX.exec line
|
||||||
|
name = if match? then match[2].trim() else line
|
||||||
|
quantity = if match? then parseInt(match[1]) else 1
|
||||||
|
|
||||||
|
if name.length > 0
|
||||||
|
inventory.add name, quantity
|
||||||
|
|
||||||
|
return inventory
|
||||||
@@ -11,9 +11,47 @@ BaseModel = require './base_model'
|
|||||||
|
|
||||||
module.exports = class Item extends BaseModel
|
module.exports = class Item extends BaseModel
|
||||||
|
|
||||||
|
@DEFAULT_STACK_SIZE = 64
|
||||||
|
|
||||||
constructor: (attributes={}, options={})->
|
constructor: (attributes={}, options={})->
|
||||||
attributes.name ?= ''
|
attributes.name ?= ''
|
||||||
attributes.quantity ?= 1
|
attributes.quantity ?= 1
|
||||||
|
attributes.stackSize ?= Item.DEFAULT_STACK_SIZE
|
||||||
super attributes, options
|
super attributes, options
|
||||||
|
|
||||||
# Public Methods ###############################################################################
|
Object.defineProperty this, 'stackQuantity', get:@getStackQuantity
|
||||||
|
|
||||||
|
# Public Methods ###############################################################################
|
||||||
|
|
||||||
|
canMerge: (item)->
|
||||||
|
return item.name is @name
|
||||||
|
|
||||||
|
merge: (item)->
|
||||||
|
if not @canMerge(item) then throw new Error "cannot merge #{item} into #{this}"
|
||||||
|
@quantity += item.quantity
|
||||||
|
|
||||||
|
toFormatHash: ->
|
||||||
|
return result =
|
||||||
|
name: @name
|
||||||
|
quantity: @quantity
|
||||||
|
stackQuantity: @stackQuantity
|
||||||
|
|
||||||
|
# Property Methods #############################################################################
|
||||||
|
|
||||||
|
getStackQuantity: ->
|
||||||
|
count = 0
|
||||||
|
extra = @quantity
|
||||||
|
while extra > @stackSize
|
||||||
|
extra -= @stackSize
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
return count:count extra:extra
|
||||||
|
|
||||||
|
# Object Overrides #############################################################################
|
||||||
|
|
||||||
|
toString: ->
|
||||||
|
result = "#{@constructor.name} (#{@cid}) { name:\"#{@name}\", quantity:#{@quantity}"
|
||||||
|
if @stackSize isnt Item.DEFAULT_STACK_SIZE
|
||||||
|
result += ", stackSize:#{@stackSize}"
|
||||||
|
result += ' }'
|
||||||
|
return result
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
BaseModel = require './base_model'
|
BaseModel = require './base_model'
|
||||||
|
CraftingTable = require './crafting_table'
|
||||||
RecipeCatalog = require './recipe_catalog'
|
RecipeCatalog = require './recipe_catalog'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
@@ -14,4 +15,5 @@ module.exports = class LandingPage extends BaseModel
|
|||||||
|
|
||||||
constructor: (attributes={}, options={})->
|
constructor: (attributes={}, options={})->
|
||||||
attributes.catalog ?= new RecipeCatalog
|
attributes.catalog ?= new RecipeCatalog
|
||||||
|
attributes.table ?= new CraftingTable catalog:attributes.catalog
|
||||||
super attributes, options
|
super attributes, options
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
###
|
|
||||||
# Crafting Guide - v1.coffee
|
|
||||||
#
|
|
||||||
# Copyright (c) 2014 by Redwood Labs
|
|
||||||
# All rights reserved.
|
|
||||||
###
|
|
||||||
|
|
||||||
Item = require '../item'
|
|
||||||
Recipe = require '../recipe'
|
|
||||||
RecipeBook = require '../recipe_book'
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
|
|
||||||
module.exports = class V1
|
|
||||||
|
|
||||||
constructor: ->
|
|
||||||
@_errorLocation = 'the header information'
|
|
||||||
|
|
||||||
parse: (data)->
|
|
||||||
return @_parseRecipeBook data
|
|
||||||
|
|
||||||
# Private Methods ##############################################################################
|
|
||||||
|
|
||||||
_parseRecipeBook: (data)->
|
|
||||||
if not data? then throw new Error 'recipe book data is missing'
|
|
||||||
if not data.version? then throw new Error 'version is required'
|
|
||||||
if not data.mod_name? then throw new Error 'mod_name is required'
|
|
||||||
if not data.mod_version? then throw new Error 'mod_version is required'
|
|
||||||
if not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
|
|
||||||
|
|
||||||
book = new RecipeBook version:data.version, modName:data.mod_name, modVersion:data.mod_version
|
|
||||||
book.description = data.description or ''
|
|
||||||
|
|
||||||
for index in [0...data.recipes.length]
|
|
||||||
@_errorLocation = "recipe #{index + 1}"
|
|
||||||
recipeData = data.recipes[index]
|
|
||||||
book.recipes.push @_parseRecipe recipeData
|
|
||||||
|
|
||||||
return book
|
|
||||||
|
|
||||||
_parseRecipe: (data, options={})->
|
|
||||||
if not data? then throw new Error "recipe data is missing for #{@_errorLocation}"
|
|
||||||
if not data.output? then throw new Error "#{@_errorLocation} is missing output"
|
|
||||||
if not data.input? then throw new Error "#{@_errorLocation} is missing input"
|
|
||||||
|
|
||||||
output = @_parseItemList data.output, field:'output', canBeEmpty:false
|
|
||||||
@_errorLocation = "recipe for output[0].name"
|
|
||||||
|
|
||||||
data.tools ?= []
|
|
||||||
input = @_parseItemList data.input, field:'input', canBeEmpty:false
|
|
||||||
tools = @_parseItemList data.tools, field:'tools', canBeEmpty:true
|
|
||||||
|
|
||||||
return new Recipe input:input, output:output, tools:tools
|
|
||||||
|
|
||||||
_parseItemList: (data, options={})->
|
|
||||||
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
|
|
||||||
|
|
||||||
if not _.isArray(data) then data = [data]
|
|
||||||
if data.length is 0 and not options.canBeEmpty
|
|
||||||
throw new Error "#{options.field} for #{@_errorLocation} cannot be empty"
|
|
||||||
|
|
||||||
result = []
|
|
||||||
for index in [0...data.length]
|
|
||||||
itemData = data[index]
|
|
||||||
result.push @_parseItem itemData, field:options.field, index:index
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
_parseItem: (data, options={})->
|
|
||||||
errorBase = "#{options.field} element #{options.index} for #{@_errorLocation}"
|
|
||||||
if not data? then throw new Error "#{errorBase} is missing"
|
|
||||||
|
|
||||||
if _.isString(data) then data = [1, data]
|
|
||||||
if not _.isArray(data) then throw new Error "#{errorBase} must be an array"
|
|
||||||
|
|
||||||
if data.length is 1 then data.unshift 1
|
|
||||||
if data.length isnt 2 then throw new Error "#{errorBase} must have at least one element"
|
|
||||||
if not _.isNumber(data[0]) then throw new Error "#{errorBase} must start with a number"
|
|
||||||
|
|
||||||
return new Item quantity:data[0], name:data[1]
|
|
||||||
@@ -12,6 +12,42 @@ BaseModel = require './base_model'
|
|||||||
module.exports = class Recipe extends BaseModel
|
module.exports = class Recipe extends BaseModel
|
||||||
|
|
||||||
constructor: (attributes={}, options={})->
|
constructor: (attributes={}, options={})->
|
||||||
|
if not attributes.output? then throw new Error "attributes.output is required"
|
||||||
|
if not attributes.input? then throw new Error "attributes.input is required"
|
||||||
|
attributes.tools ?= []
|
||||||
super attributes, options
|
super attributes, options
|
||||||
|
|
||||||
Object.defineProperty this, 'name', get:-> @output[0].name
|
Object.defineProperty this, 'name', get:-> @output[0].name
|
||||||
|
|
||||||
|
# Object Overrides #############################################################################
|
||||||
|
|
||||||
|
toString: ->
|
||||||
|
result = [@constructor.name, " (", @cid, ") { name:", @name]
|
||||||
|
|
||||||
|
result.push ", input:["
|
||||||
|
needsDelimiter = false
|
||||||
|
for inputItem in @input
|
||||||
|
if needsDelimiter then result.push ', '
|
||||||
|
result.push inputItem.toString()
|
||||||
|
needsDelimiter = false
|
||||||
|
result.push ']'
|
||||||
|
|
||||||
|
result.push ", output:["
|
||||||
|
needsDelimiter = false
|
||||||
|
for outputItem in @output
|
||||||
|
if needsDelimiter then result.push ', '
|
||||||
|
result.push outputItem.toString()
|
||||||
|
needsDelimiter = false
|
||||||
|
result.push ']'
|
||||||
|
|
||||||
|
if @tools.length > 0
|
||||||
|
result.push ", tools:["
|
||||||
|
needsDelimiter = false
|
||||||
|
for tool in @tools
|
||||||
|
if needsDelimiter then result.push ', '
|
||||||
|
result.push tool.toString()
|
||||||
|
needsDelimiter = false
|
||||||
|
result.push ']'
|
||||||
|
|
||||||
|
result.push '}'
|
||||||
|
return result.join ''
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
###
|
###
|
||||||
|
|
||||||
V1 = require './parser_versions/v1'
|
Item = require './item'
|
||||||
|
Recipe = require './recipe'
|
||||||
|
RecipeBook = require './recipe_book'
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
|
|
||||||
@@ -24,3 +26,73 @@ module.exports = class RecipeBookParser
|
|||||||
throw new Error "cannot parse version #{data.version} recipe books"
|
throw new Error "cannot parse version #{data.version} recipe books"
|
||||||
|
|
||||||
return parser.parse data
|
return parser.parse data
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
module.exports.V1 = class V1
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
@_errorLocation = 'the header information'
|
||||||
|
|
||||||
|
parse: (data)->
|
||||||
|
return @_parseRecipeBook data
|
||||||
|
|
||||||
|
# Private Methods ##############################################################################
|
||||||
|
|
||||||
|
_parseRecipeBook: (data)->
|
||||||
|
if not data? then throw new Error 'recipe book data is missing'
|
||||||
|
if not data.version? then throw new Error 'version is required'
|
||||||
|
if not data.mod_name? then throw new Error 'mod_name is required'
|
||||||
|
if not data.mod_version? then throw new Error 'mod_version is required'
|
||||||
|
if not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
|
||||||
|
|
||||||
|
book = new RecipeBook version:data.version, modName:data.mod_name, modVersion:data.mod_version
|
||||||
|
book.description = data.description or ''
|
||||||
|
|
||||||
|
for index in [0...data.recipes.length]
|
||||||
|
@_errorLocation = "recipe #{index + 1}"
|
||||||
|
recipeData = data.recipes[index]
|
||||||
|
book.recipes.push @_parseRecipe recipeData
|
||||||
|
|
||||||
|
return book
|
||||||
|
|
||||||
|
_parseRecipe: (data, options={})->
|
||||||
|
if not data? then throw new Error "recipe data is missing for #{@_errorLocation}"
|
||||||
|
if not data.output? then throw new Error "#{@_errorLocation} is missing output"
|
||||||
|
if not data.input? then throw new Error "#{@_errorLocation} is missing input"
|
||||||
|
|
||||||
|
output = @_parseItemList data.output, field:'output', canBeEmpty:false
|
||||||
|
@_errorLocation = "recipe for output[0].name"
|
||||||
|
|
||||||
|
data.tools ?= []
|
||||||
|
input = @_parseItemList data.input, field:'input', canBeEmpty:false
|
||||||
|
tools = @_parseItemList data.tools, field:'tools', canBeEmpty:true
|
||||||
|
|
||||||
|
return new Recipe input:input, output:output, tools:tools
|
||||||
|
|
||||||
|
_parseItemList: (data, options={})->
|
||||||
|
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
|
||||||
|
|
||||||
|
if not _.isArray(data) then data = [data]
|
||||||
|
if data.length is 0 and not options.canBeEmpty
|
||||||
|
throw new Error "#{options.field} for #{@_errorLocation} cannot be empty"
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for index in [0...data.length]
|
||||||
|
itemData = data[index]
|
||||||
|
result.push @_parseItem itemData, field:options.field, index:index
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
_parseItem: (data, options={})->
|
||||||
|
errorBase = "#{options.field} element #{options.index} for #{@_errorLocation}"
|
||||||
|
if not data? then throw new Error "#{errorBase} is missing"
|
||||||
|
|
||||||
|
if _.isString(data) then data = [1, data]
|
||||||
|
if not _.isArray(data) then throw new Error "#{errorBase} must be an array"
|
||||||
|
|
||||||
|
if data.length is 1 then data.unshift 1
|
||||||
|
if data.length isnt 2 then throw new Error "#{errorBase} must have at least one element"
|
||||||
|
if not _.isNumber(data[0]) then throw new Error "#{errorBase} must start with a number"
|
||||||
|
|
||||||
|
return new Item quantity:data[0], name:data[1]
|
||||||
|
|||||||
+10
-1
@@ -2,13 +2,22 @@ var jade = jade || require('jade').runtime;
|
|||||||
|
|
||||||
this["JST"] = this["JST"] || {};
|
this["JST"] = this["JST"] || {};
|
||||||
|
|
||||||
|
this["JST"]["crafting_table"] = function template(locals) {
|
||||||
|
var buf = [];
|
||||||
|
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("");
|
||||||
|
};
|
||||||
|
|
||||||
this["JST"]["landing_page"] = function template(locals) {
|
this["JST"]["landing_page"] = function template(locals) {
|
||||||
var buf = [];
|
var buf = [];
|
||||||
var jade_mixins = {};
|
var jade_mixins = {};
|
||||||
var jade_interp;
|
var jade_interp;
|
||||||
|
|
||||||
var jade_indent = [];
|
var jade_indent = [];
|
||||||
buf.push("\n<div class=\"view__landing_page\">\n <div class=\"view__recipe_catalog\"></div>\n <div class=\"view__crafter\"></div>\n</div>");;return buf.join("");
|
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("");
|
||||||
};
|
};
|
||||||
|
|
||||||
this["JST"]["recipe_book"] = function template(locals) {
|
this["JST"]["recipe_book"] = function template(locals) {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
//-
|
||||||
|
//- Crafting Guide - crafting_table.jade
|
||||||
|
//-
|
||||||
|
//- Copyright (c) 2014 by Redwood Labs
|
||||||
|
//- All rights reserved.
|
||||||
|
//-
|
||||||
|
|
||||||
|
.view__crafting_table
|
||||||
|
h2
|
||||||
|
img(src="/images/workbench_top.png")
|
||||||
|
p Crafting Table
|
||||||
|
|
||||||
|
.recipe_selector
|
||||||
|
p I want to make
|
||||||
|
select(name="quantity")
|
||||||
|
option(value="1") 1
|
||||||
|
option(value="2") 2
|
||||||
|
option(value="4") 4
|
||||||
|
option(value="8") 8
|
||||||
|
option(value="16") 16
|
||||||
|
option(value="64") 64
|
||||||
|
input(name="name", placeholder="enter an item name")
|
||||||
|
label
|
||||||
|
input(name="including_tools", type="checkbox")
|
||||||
|
| including tools
|
||||||
|
|
||||||
|
table
|
||||||
|
tr
|
||||||
|
td.have
|
||||||
|
h3: p You have...
|
||||||
|
textarea(name="have", placeholder="enter what you have, for example:\n\n3 wool\n4 iron ingot")
|
||||||
|
td.make
|
||||||
|
h3: p You'll make...
|
||||||
|
ul
|
||||||
|
td.get
|
||||||
|
h3: p You'll get...
|
||||||
|
ul
|
||||||
@@ -7,4 +7,4 @@
|
|||||||
|
|
||||||
.view__landing_page
|
.view__landing_page
|
||||||
.view__recipe_catalog
|
.view__recipe_catalog
|
||||||
.view__crafter
|
.view__crafting_table
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
###
|
||||||
|
# Crafting Guide - event_recorder.coffee
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 by Redwood Labs
|
||||||
|
# All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
util = require 'util'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
module.exports = class EventRecorder
|
||||||
|
|
||||||
|
constructor: (model)->
|
||||||
|
if not model? then throw new Error 'model is required'
|
||||||
|
|
||||||
|
@model = model
|
||||||
|
@events = []
|
||||||
|
|
||||||
|
Object.defineProperty this, 'names', get:-> e.event for e in @events
|
||||||
|
|
||||||
|
@model.on 'all', (event, model, args...)=>
|
||||||
|
logger.verbose "#{model?.constructor?.name}(#{model?.cid}) emitted #{event}
|
||||||
|
with args: #{util.inspect(args)}"
|
||||||
|
@events.push id:model?.cid, event:event, args:args
|
||||||
|
|
||||||
|
reset: ->
|
||||||
|
@events = []
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
###
|
||||||
|
# Crafting Guide - inventory.test.coffee
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 by Redwood Labs
|
||||||
|
# All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
{Event} = require '../src/scripts/constants'
|
||||||
|
EventRecorder = require './event_recorder'
|
||||||
|
Inventory = require '../src/scripts/models/inventory'
|
||||||
|
Item = require '../src/scripts/models/item'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
inventory = null
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
describe 'Inventory', ->
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
inventory = new Inventory
|
||||||
|
inventory.add 'wool', 4
|
||||||
|
inventory.add 'string', 20
|
||||||
|
inventory.add 'boat'
|
||||||
|
|
||||||
|
describe 'add', ->
|
||||||
|
|
||||||
|
it 'can add to an empty inventory', ->
|
||||||
|
inventory.add 'iron ingot', 4
|
||||||
|
item = inventory._items['iron ingot']
|
||||||
|
item.constructor.name.should.equal 'Item'
|
||||||
|
item.name.should.equal 'iron ingot'
|
||||||
|
item.quantity.should.equal 4
|
||||||
|
|
||||||
|
it 'can augment quantity of existing items', ->
|
||||||
|
inventory.add 'wool', 2
|
||||||
|
inventory._items.wool.quantity.should.equal 6
|
||||||
|
|
||||||
|
it 'can add zero quantity', ->
|
||||||
|
inventory.add 'wool', 0
|
||||||
|
inventory._items.wool.quantity.should.equal 4
|
||||||
|
|
||||||
|
it 'emits the proper events', ->
|
||||||
|
events = new EventRecorder inventory
|
||||||
|
inventory.add 'iron ingot', 10
|
||||||
|
events.names.should.eql [Event.add, Event.change]
|
||||||
|
|
||||||
|
describe 'each', ->
|
||||||
|
|
||||||
|
it 'works with an empty inventory', ->
|
||||||
|
inventory = new Inventory
|
||||||
|
result = []
|
||||||
|
inventory.each (item)-> result.push item.name
|
||||||
|
result.should.eql []
|
||||||
|
|
||||||
|
it 'works when items have only been added', ->
|
||||||
|
result = []
|
||||||
|
inventory.each (item)-> result.push item.name
|
||||||
|
result.should.eql ['wool', 'string', 'boat']
|
||||||
|
|
||||||
|
it 'works when items have been augmented', ->
|
||||||
|
inventory.add 'iron ingot'
|
||||||
|
inventory.add 'boat'
|
||||||
|
inventory.add 'wool', 2
|
||||||
|
|
||||||
|
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']
|
||||||
|
|
||||||
|
describe 'hasAtLeast', ->
|
||||||
|
|
||||||
|
it 'works when the item is completely absent', ->
|
||||||
|
answer = inventory.hasAtLeast 'chicken', 1
|
||||||
|
answer.should.be.false
|
||||||
|
|
||||||
|
it 'always returns true for zero quantity', ->
|
||||||
|
inventory.hasAtLeast('chicken', 0).should.be.true
|
||||||
|
inventory.hasAtLeast('wool', 0).should.be.true
|
||||||
|
|
||||||
|
it 'works for a quantity above 1', ->
|
||||||
|
inventory.hasAtLeast('wool', 3).should.be.true
|
||||||
|
inventory.hasAtLeast('wool', 4).should.be.true
|
||||||
|
inventory.hasAtLeast('wool', 5).should.be.false
|
||||||
|
|
||||||
|
describe 'remove', ->
|
||||||
|
|
||||||
|
it 'throws when the item is absent', ->
|
||||||
|
expect(-> inventory.remove('chicken')).to.throw Error,
|
||||||
|
'cannot remove chicken since it is not in this inventory'
|
||||||
|
|
||||||
|
it 'throws when the item has insufficient quantity', ->
|
||||||
|
expect(-> inventory.remove('wool', 10)).to.throw Error,
|
||||||
|
'cannot remove 10 wool because there is only 4 in this inventory'
|
||||||
|
|
||||||
|
it 'removes a single item by default', ->
|
||||||
|
inventory.remove 'wool'
|
||||||
|
inventory._items.wool.quantity.should.equal 3
|
||||||
|
|
||||||
|
it 'removes a quantity above 1', ->
|
||||||
|
inventory.remove 'wool', 3
|
||||||
|
inventory._items.wool.quantity.should.equal 1
|
||||||
|
|
||||||
|
it 'emits the proper events', ->
|
||||||
|
events = new EventRecorder inventory
|
||||||
|
inventory.remove 'wool'
|
||||||
|
events.names.should.eql [Event.remove, Event.change]
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
###
|
||||||
|
# Crafting Guide - inventory_parser.test.coffee
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 by Redwood Labs
|
||||||
|
# All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
Inventory = require '../src/scripts/models/inventory'
|
||||||
|
InventoryParser = require '../src/scripts/models/inventory_parser'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
parser = null
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
describe 'InventoryParser', ->
|
||||||
|
|
||||||
|
beforeEach -> parser = new InventoryParser
|
||||||
|
|
||||||
|
it 'returns an empty Inventory for an empty string', ->
|
||||||
|
result = parser.parse ''
|
||||||
|
result._names.length.should.equal 0
|
||||||
|
|
||||||
|
it 'can parse a single item without quantity', ->
|
||||||
|
result = parser.parse 'wool'
|
||||||
|
result._items.wool.quantity.should.equal 1
|
||||||
|
|
||||||
|
it 'can parse a single item with quantity', ->
|
||||||
|
result = parser.parse '4 wool'
|
||||||
|
result._items.wool.quantity.should.equal 4
|
||||||
|
|
||||||
|
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']
|
||||||
|
|
||||||
|
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']
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
###
|
|
||||||
# Crafting Guide - v1.test.coffee
|
|
||||||
#
|
|
||||||
# Copyright (c) 2014 by Redwood Labs
|
|
||||||
# All rights reserved.
|
|
||||||
###
|
|
||||||
|
|
||||||
V1 = require '../../src/scripts/models/parser_versions/v1'
|
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
|
|
||||||
parser = null
|
|
||||||
|
|
||||||
########################################################################################################################
|
|
||||||
|
|
||||||
describe 'RecipeBookParser.V1', ->
|
|
||||||
|
|
||||||
before -> parser = new V1
|
|
||||||
|
|
||||||
describe '_parseRecipeBook', ->
|
|
||||||
|
|
||||||
it 'requires a mod_name', ->
|
|
||||||
data = version:1, mod_version:'1.0', recipes:[]
|
|
||||||
expect(-> parser._parseRecipeBook data).to.throw Error, 'mod_name is required'
|
|
||||||
|
|
||||||
it 'requires a mod_version', ->
|
|
||||||
data = version:1, mod_name:'Empty', recipes:[]
|
|
||||||
expect(-> parser._parseRecipeBook data).to.throw Error, 'mod_version is required'
|
|
||||||
|
|
||||||
it 'can parse an empty recipe book', ->
|
|
||||||
data =
|
|
||||||
version: 1
|
|
||||||
mod_name: 'Empty'
|
|
||||||
mod_version: '1.0'
|
|
||||||
recipes: []
|
|
||||||
book = parser._parseRecipeBook data
|
|
||||||
book.modName.should.equal 'Empty'
|
|
||||||
book.modVersion.should.equal '1.0'
|
|
||||||
|
|
||||||
it 'can parse a non-empty recipe book', ->
|
|
||||||
data =
|
|
||||||
version: 1
|
|
||||||
mod_name: 'Minecraft'
|
|
||||||
mod_version: '1.7.10'
|
|
||||||
recipes: [
|
|
||||||
{ input:'sugar cane', output:'sugar' }
|
|
||||||
{ input:[[3, 'wool'], [3, 'planks']], tools:'crafting table', output:'bed' }
|
|
||||||
]
|
|
||||||
book = parser._parseRecipeBook data
|
|
||||||
book.modName.should.equal 'Minecraft'
|
|
||||||
book.modVersion.should.equal '1.7.10'
|
|
||||||
(r.name for r in book.recipes).sort().should.eql ['bed', 'sugar']
|
|
||||||
|
|
||||||
describe '_parseRecipe', ->
|
|
||||||
|
|
||||||
it 'requires output to be defined', ->
|
|
||||||
parser._errorLocation = 'boat'
|
|
||||||
expect(-> parser._parseRecipe input:'wool').to.throw Error, 'boat is missing output'
|
|
||||||
|
|
||||||
it 'requires input to be defined', ->
|
|
||||||
parser._errorLocation = 'boat'
|
|
||||||
expect(-> parser._parseRecipe output:'wool').to.throw Error, 'boat is missing input'
|
|
||||||
|
|
||||||
it 'can parse a regular recipe', ->
|
|
||||||
data =
|
|
||||||
output: 'bed'
|
|
||||||
input: [[3, 'planks'], [3, 'wool']]
|
|
||||||
tools: 'crafting table'
|
|
||||||
recipe = parser._parseRecipe data
|
|
||||||
(i.name for i in recipe.output).should.eql ['bed']
|
|
||||||
(i.name for i in recipe.input).sort().should.eql ['planks', 'wool']
|
|
||||||
(i.name for i in recipe.tools).should.eql ['crafting table']
|
|
||||||
|
|
||||||
it 'can parse a recipe without tools', ->
|
|
||||||
recipe = parser._parseRecipe output:'sugar', input:'sugar cane'
|
|
||||||
(i.name for i in recipe.output).should.eql ['sugar']
|
|
||||||
(i.name for i in recipe.input).sort().should.eql ['sugar cane']
|
|
||||||
(i.name for i in recipe.tools).should.eql []
|
|
||||||
|
|
||||||
describe '_parseItemList', ->
|
|
||||||
|
|
||||||
it 'can promote a single item to a list', ->
|
|
||||||
list = parser._parseItemList 'boat'
|
|
||||||
(i.name for i in list).should.eql ['boat']
|
|
||||||
|
|
||||||
it 'can require a list to be non-empty', ->
|
|
||||||
parser._errorLocation = 'boat'
|
|
||||||
options = field:'output', canBeEmpty:false
|
|
||||||
expect(-> parser._parseItemList [], options).to.throw Error, 'output for boat cannot be empty'
|
|
||||||
|
|
||||||
it 'can allow an empty list', ->
|
|
||||||
list = parser._parseItemList [], canBeEmpty:true
|
|
||||||
list.length.should.equal 0
|
|
||||||
|
|
||||||
it 'can parse a non-empty list', ->
|
|
||||||
list = parser._parseItemList [[3, 'plank'], [3, 'wool']]
|
|
||||||
(i.name for i in list).sort().should.eql ['plank', 'wool']
|
|
||||||
|
|
||||||
describe '_parseItem', ->
|
|
||||||
|
|
||||||
it 'requires the array to have at least one element', ->
|
|
||||||
parser._errorLocation = 'boat'
|
|
||||||
options = index:1, field:'output'
|
|
||||||
expect(-> parser._parseItem([], options)).to.throw Error,
|
|
||||||
"output element 1 for boat must have at least one element"
|
|
||||||
|
|
||||||
it 'can fill in a missing number', ->
|
|
||||||
item = parser._parseItem 'boat'
|
|
||||||
item.name.should.equal 'boat'
|
|
||||||
item.quantity.should.equal 1
|
|
||||||
|
|
||||||
item2 = parser._parseItem ['boat']
|
|
||||||
item2.name.should.equal 'boat'
|
|
||||||
item2.quantity.should.equal 1
|
|
||||||
|
|
||||||
it 'requires the data to start with a number', ->
|
|
||||||
parser._errorLocation = 'boat'
|
|
||||||
options = index:1, field:'output'
|
|
||||||
expect(-> parser._parseItem(['2', 'book'], options)).to.throw Error,
|
|
||||||
"output element 1 for boat must start with a number"
|
|
||||||
|
|
||||||
it 'can parse a basic item', ->
|
|
||||||
item = parser._parseItem [2, 'book']
|
|
||||||
item.constructor.name.should.equal 'Item'
|
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
###
|
||||||
|
# Crafting Guide - v1.test.coffee
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 by Redwood Labs
|
||||||
|
# All rights reserved.
|
||||||
|
###
|
||||||
|
|
||||||
|
RecipeBookParser = require '../src/scripts/models/recipe_book_parser'
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
parser = null
|
||||||
|
|
||||||
|
########################################################################################################################
|
||||||
|
|
||||||
|
describe 'RecipeBookParser', ->
|
||||||
|
|
||||||
|
describe "V1", ->
|
||||||
|
|
||||||
|
before -> parser = new RecipeBookParser.V1
|
||||||
|
|
||||||
|
describe '_parseRecipeBook', ->
|
||||||
|
|
||||||
|
it 'requires a mod_name', ->
|
||||||
|
data = version:1, mod_version:'1.0', recipes:[]
|
||||||
|
expect(-> parser._parseRecipeBook data).to.throw Error, 'mod_name is required'
|
||||||
|
|
||||||
|
it 'requires a mod_version', ->
|
||||||
|
data = version:1, mod_name:'Empty', recipes:[]
|
||||||
|
expect(-> parser._parseRecipeBook data).to.throw Error, 'mod_version is required'
|
||||||
|
|
||||||
|
it 'can parse an empty recipe book', ->
|
||||||
|
data =
|
||||||
|
version: 1
|
||||||
|
mod_name: 'Empty'
|
||||||
|
mod_version: '1.0'
|
||||||
|
recipes: []
|
||||||
|
book = parser._parseRecipeBook data
|
||||||
|
book.modName.should.equal 'Empty'
|
||||||
|
book.modVersion.should.equal '1.0'
|
||||||
|
|
||||||
|
it 'can parse a non-empty recipe book', ->
|
||||||
|
data =
|
||||||
|
version: 1
|
||||||
|
mod_name: 'Minecraft'
|
||||||
|
mod_version: '1.7.10'
|
||||||
|
recipes: [
|
||||||
|
{ input:'sugar cane', output:'sugar' }
|
||||||
|
{ input:[[3, 'wool'], [3, 'planks']], tools:'crafting table', output:'bed' }
|
||||||
|
]
|
||||||
|
book = parser._parseRecipeBook data
|
||||||
|
book.modName.should.equal 'Minecraft'
|
||||||
|
book.modVersion.should.equal '1.7.10'
|
||||||
|
(r.name for r in book.recipes).sort().should.eql ['bed', 'sugar']
|
||||||
|
|
||||||
|
describe '_parseRecipe', ->
|
||||||
|
|
||||||
|
it 'requires output to be defined', ->
|
||||||
|
parser._errorLocation = 'boat'
|
||||||
|
expect(-> parser._parseRecipe input:'wool').to.throw Error, 'boat is missing output'
|
||||||
|
|
||||||
|
it 'requires input to be defined', ->
|
||||||
|
parser._errorLocation = 'boat'
|
||||||
|
expect(-> parser._parseRecipe output:'wool').to.throw Error, 'boat is missing input'
|
||||||
|
|
||||||
|
it 'can parse a regular recipe', ->
|
||||||
|
data =
|
||||||
|
output: 'bed'
|
||||||
|
input: [[3, 'planks'], [3, 'wool']]
|
||||||
|
tools: 'crafting table'
|
||||||
|
recipe = parser._parseRecipe data
|
||||||
|
(i.name for i in recipe.output).should.eql ['bed']
|
||||||
|
(i.name for i in recipe.input).sort().should.eql ['planks', 'wool']
|
||||||
|
(i.name for i in recipe.tools).should.eql ['crafting table']
|
||||||
|
|
||||||
|
it 'can parse a recipe without tools', ->
|
||||||
|
recipe = parser._parseRecipe output:'sugar', input:'sugar cane'
|
||||||
|
(i.name for i in recipe.output).should.eql ['sugar']
|
||||||
|
(i.name for i in recipe.input).sort().should.eql ['sugar cane']
|
||||||
|
(i.name for i in recipe.tools).should.eql []
|
||||||
|
|
||||||
|
describe '_parseItemList', ->
|
||||||
|
|
||||||
|
it 'can promote a single item to a list', ->
|
||||||
|
list = parser._parseItemList 'boat'
|
||||||
|
(i.name for i in list).should.eql ['boat']
|
||||||
|
|
||||||
|
it 'can require a list to be non-empty', ->
|
||||||
|
parser._errorLocation = 'boat'
|
||||||
|
options = field:'output', canBeEmpty:false
|
||||||
|
expect(-> parser._parseItemList [], options).to.throw Error, 'output for boat cannot be empty'
|
||||||
|
|
||||||
|
it 'can allow an empty list', ->
|
||||||
|
list = parser._parseItemList [], canBeEmpty:true
|
||||||
|
list.length.should.equal 0
|
||||||
|
|
||||||
|
it 'can parse a non-empty list', ->
|
||||||
|
list = parser._parseItemList [[3, 'plank'], [3, 'wool']]
|
||||||
|
(i.name for i in list).sort().should.eql ['plank', 'wool']
|
||||||
|
|
||||||
|
describe '_parseItem', ->
|
||||||
|
|
||||||
|
it 'requires the array to have at least one element', ->
|
||||||
|
parser._errorLocation = 'boat'
|
||||||
|
options = index:1, field:'output'
|
||||||
|
expect(-> parser._parseItem([], options)).to.throw Error,
|
||||||
|
"output element 1 for boat must have at least one element"
|
||||||
|
|
||||||
|
it 'can fill in a missing number', ->
|
||||||
|
item = parser._parseItem 'boat'
|
||||||
|
item.name.should.equal 'boat'
|
||||||
|
item.quantity.should.equal 1
|
||||||
|
|
||||||
|
item2 = parser._parseItem ['boat']
|
||||||
|
item2.name.should.equal 'boat'
|
||||||
|
item2.quantity.should.equal 1
|
||||||
|
|
||||||
|
it 'requires the data to start with a number', ->
|
||||||
|
parser._errorLocation = 'boat'
|
||||||
|
options = index:1, field:'output'
|
||||||
|
expect(-> parser._parseItem(['2', 'book'], options)).to.throw Error,
|
||||||
|
"output element 1 for boat must start with a number"
|
||||||
|
|
||||||
|
it 'can parse a basic item', ->
|
||||||
|
item = parser._parseItem [2, 'book']
|
||||||
|
item.constructor.name.should.equal 'Item'
|
||||||
+6
-1
@@ -17,11 +17,16 @@ global.assert = chai.assert
|
|||||||
global.expect = chai.expect
|
global.expect = chai.expect
|
||||||
global.should = chai.should()
|
global.should = chai.should()
|
||||||
|
|
||||||
|
Logger = require '../src/scripts/logger'
|
||||||
|
global.logger = new Logger level:Logger.TRACE
|
||||||
|
|
||||||
# Test Registry ########################################################################################################
|
# Test Registry ########################################################################################################
|
||||||
|
|
||||||
mocha.setup 'bdd'
|
mocha.setup 'bdd'
|
||||||
|
|
||||||
require './parser_versions/v1.test'
|
require './inventory.test'
|
||||||
|
require './inventory_parser.test'
|
||||||
|
require './recipe_book_parser.test'
|
||||||
|
|
||||||
mocha.checkLeaks()
|
mocha.checkLeaks()
|
||||||
mocha.run()
|
mocha.run()
|
||||||
|
|||||||
Reference in New Issue
Block a user