diff --git a/html/crafting.js b/html/crafting.js
deleted file mode 100644
index 0de10407e..000000000
--- a/html/crafting.js
+++ /dev/null
@@ -1,355 +0,0 @@
-// Constants //////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-var ERROR_DISPLAY_DURATION = 10000; // ms
-var FADE_DURATION = 250; // ms
-var SLIDE_DURATION = 600; // ms
-
-// Global Variables ///////////////////////////////////////////////////////////////////////////////////////////////////
-
-var __inventory = createManifest();
-var __loadingCount = 0;
-var __recipebooks = [];
-var __toolsIncluded = false;
-
-// UI Functions ///////////////////////////////////////////////////////////////////////////////////////////////////////
-
-$(function() {
- $("#recipebook_load_button").click(onRecipeLoadButtonClicked);
- $("#crafting_count").change(onCraftingSelectorChanged);
- $("#tools_included").change(onIncludeToolsChanged);
- $("#crafting_selector").focus(onCraftingSelectorFocused);
- $("#inventory").keyup(onInventoryChanged);
-
- loadRecipebook("data/vanilla.json");
- loadRecipebook("data/buildcraft.json");
- loadRecipebook("data/industrial_craft.json");
-});
-
-function onCraftingSelectorChanged() {
- var recipeName = $("#crafting_selector").val();
- var count = parseInt($("#crafting_count option:selected").val());
- if (count === undefined) return;
-
-
- var recipe = findRecipe(recipeName);
- if (recipe === undefined) {
- $("#crafting_output").slideUp(SLIDE_DURATION);
- } else {
- var inventory = __inventory.copy();
- var craftedItems = createManifest();
- var missingMaterials = createManifest();
- for (var i = 0; i < count; i++) {
- recipe.craft(inventory, craftedItems, missingMaterials);
- if (inventory.contains(count, recipeName)) break;
- }
-
- $("#missing_materials").html(missingMaterials.toHtml());
- $("#crafted_items").html(craftedItems.toHtml());
- $("#leftover_materials").html(inventory.toHtml());
- $("#crafting_output").slideDown(SLIDE_DURATION);
- }
-
- updatePageState(count, recipeName);
-}
-
-function onCraftingSelectorFocused() {
- $("#crafting_selector").autocomplete("search");
-}
-
-function onIncludeToolsChanged() {
- __toolsIncluded = $("#tools_included").is(":checked");
- onCraftingSelectorChanged()
-}
-
-function onInventoryChanged() {
- __inventory.removeAll();
-
- var inventoryText = $("#inventory").val();
- if (inventoryText) {
- var inventoryLines = inventoryText.split("\n");
- for (var i = 0; i < inventoryLines.length; i++) {
- var line = inventoryLines[i].trim();
- var match = /^([0-9]+)(.*)$/.exec(line);
-
- var count = (match) ? parseInt(match[1]) : 1;
- var itemName = (match) ? match[2].trim() : line;
-
- if (itemName.length > 0) {
- __inventory.add(count, itemName);
- }
- }
- }
- onCraftingSelectorChanged();
-}
-
-function onRecipeBookLoaded() {
- if (__loadingCount > 0 || __recipebooks.length === 0) {
- $("#crafting").fadeOut(FADE_DURATION);
- } else if (__loadingCount === 0 && __recipebooks.length > 0) {
- updateCraftingSelector();
- parseUrlParameters();
- $("#crafting").fadeIn(FADE_DURATION);
- }
-}
-
-function onRecipeLoadButtonClicked() {
- $("#reipebook_load_button").attr("disabled", true);
- loadRecipebook($("#recipebook_url").val(), function() {
- $("#recipebook_url").val("");
- $("#reipebook_load_button").removeAttr("disabled");
- });
-}
-
-// UI Helper Functions ////////////////////////////////////////////////////////////////////////////////////////////////
-
-function loadRecipebook(recipebookUrl) {
- __loadingCount++;
- $.ajax({
- url: recipebookUrl,
- dataType: "text",
- success: function(text) {
- try {
- var recipebook = createRecipebook(recipebookUrl, $.parseJSON(text));
- __recipebooks.push(recipebook);
-
- recipebook.asTableRow().insertBefore($("#recipebook_table tr").last());
- } catch (e) {
- $("#load_error").html(e);
- $("#load_error").slideDown(FADE_DURATION).delay(ERROR_DISPLAY_DURATION).slideUp(FADE_DURATION);
- }
- },
- error: function(response, status, error) {
- $("#load_error").html(status + ": " + error);
- $("#load_error").slideDown(FADE_DURATION).delay(ERROR_DISPLAY_DURATION).slideUp(FADE_DURATION);
- },
- complete: function() {
- __loadingCount--;
- onRecipeBookLoaded();
- }
- });
-}
-
-function parseUrlParameters() {
- var quantity = $.url().param('quantity');
- if (quantity !== undefined) {
- $("#crafting_count").val(quantity);
-
- if ($("#crafting_count option:selected").val() === undefined) {
- $("#crafting_count").val(1);
-
- $("#crafting_error").html("Unsupported quantity: " + quantity);
- $("#crafting_error").fadeIn(FADE_DURATION).delay(ERROR_DISPLAY_DURATION).fadeOut(FADE_DURATION);
- }
- }
-
- var recipeName = $.url().param('recipeName');
- if (recipeName !== undefined) {
- $("#crafting_selector").val(recipeName);
-
- if (findRecipe(recipeName) !== undefined) {
- onCraftingSelectorChanged();
- } else if (recipeName.length > 0) {
- $("#crafting_error").html("Cannot find recipe for " + recipeName);
- $("#crafting_error").fadeIn(FADE_DURATION).delay(ERROR_DISPLAY_DURATION).fadeOut(FADE_DURATION);
- }
- }
-}
-
-function updateCraftingSelector() {
- var $selector = $("crafting_selector");
- var names = [];
- $(__recipebooks).each(function(i, recipebook) {
- for (var name in recipebook.recipes) {
- names.push(name);
- }
- });
- names.sort();
-
- $("#crafting_selector").autocomplete({
- source: names, delay: 0, minLength: 0,
- change: onCraftingSelectorChanged,
- close: onCraftingSelectorChanged,
- select: onCraftingSelectorChanged,
- });
-}
-
-function updatePageState(count, recipeName) {
- var newTitle = "Crafting Guide";
- var newLink = window.location.pathname;
- var state = {}
- if (recipeName !== undefined && recipeName.length > 0) {
- newTitle = "Crafting Guide: " + count + " " + recipeName;
- newLink = "?count=" + count + "&recipeName=" + encodeURIComponent(recipeName);
- newLink = newLink.replace(/%20/g, "+");
- state = {count: count, name: recipeName};
- }
-
- document.title = newTitle;
- history.pushState(state, newTitle, newLink);
- ga('send', 'pageview');
-}
-
-// Manifest Object ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-function createManifest() {
- var object = {materials: {}};
-
- object.add = function(count, name) {
- if (object.materials[name] === undefined) {
- object.materials[name] = count;
- } else {
- object.materials[name] = object.materials[name] + count;
- }
- };
-
- object.contains = function(count, name) {
- if (object.materials[name] === undefined) return false;
- return (object.materials[name] >= count);
- };
-
- object.copy = function(count, name) {
- var result = createManifest();
- for (var name in object.materials) {
- result.add(object.materials[name], name);
- }
- return result;
- }
-
- object.remove = function(count, name) {
- if (object.materials[name] !== undefined) {
- if (object.materials[name] <= count) {
- delete object.materials[name]
- } else {
- object.materials[name] -= count;
- }
- }
- };
-
- object.removeAll = function() {
- object.materials = {};
- }
-
- object.toHtml = function() {
- var result = "";
- var count = 0;
- for (var name in object.materials) {
- result += "
| " + object.materials[name] + " | " + name + " |
";
- count += 1
- }
-
- if (count === 0) result = "Nothing";
- return result;
- };
-
- return object;
-}
-
-// Recipe Object //////////////////////////////////////////////////////////////////////////////////////////////////////
-
-function createRecipe(data) {
- var object = {output: [], input: [], tools: data.tools};
-
- $.each(data.output, function(index, value) {
- object.output.push({count: value[0], name: value[1]});
- });
-
- $.each(data.input, function(index, value) {
- object.input.push({count: value[0], name: value[1]});
- });
-
- // Methods ////////////////////////////////////////////
-
- object.craft = function(inventory, craftedItems, missingMaterials) {
- if (inventory === undefined) inventory = createManifest();
- if (craftedItems === undefined) craftedItems = createManifest();
- if (missingMaterials === undefined) missingMaterials = createManifest();
- var toCraftManifest = createManifest();
-
- function drainCraftingManifest(consumeItems) {
- for (var name in toCraftManifest.materials) {
- var count = toCraftManifest.materials[name];
- var itemRecipe = findRecipe(name);
-
- for (var i = 0; i < count; i++) {
- if (inventory.contains(1, name)) {
- if (consumeItems) {
- inventory.remove(1, name);
- }
- } else if (itemRecipe === undefined) {
- missingMaterials.add(1, name);
- if (! consumeItems) {
- inventory.add(1, name);
- }
- } else {
- itemRecipe.craft(inventory, craftedItems, missingMaterials);
- if (consumeItems) {
- inventory.remove(1, name);
- }
- }
- }
- }
- toCraftManifest.removeAll();
- }
-
- if (__toolsIncluded) {
- $(object.tools).each(function(i, tool) {
- if (! inventory.contains(1, tool)) {
- toCraftManifest.add(1, tool);
- }
- });
- drainCraftingManifest(false);
- }
-
- $(object.input).each(function(i, input) {
- for (var j = 0; j < input.count; j++) {
- toCraftManifest.add(1, input.name);
- }
- });
- drainCraftingManifest(true);
-
- $(object.output).each(function(i, output) {
- craftedItems.add(output.count, output.name);
- inventory.add(output.count, output.name);
- });
- };
-
- return object;
-}
-
-// RecipeBook Object //////////////////////////////////////////////////////////////////////////////////////////////////
-
-function createRecipebook(sourceUrl, data) {
- var object = {
- name: data.name,
- description: data.description,
- sourceUrl: sourceUrl,
- recipes: {}
- };
-
- $(data.recipes).each(function(i, data) {
- var recipe = createRecipe(data);
- $(recipe.output).each(function(j, output) {
- object.recipes[output.name] = recipe
- });
- });
-
- object.asTableRow = function() {
- return $(
- "" +
- "| " + object.name + " | " +
- "" + object.description + " | " +
- "
"
- );
- };
-
- return object;
-}
-
-function findRecipe(name) {
- for (var i = 0; i < __recipebooks.length; i++) {
- var recipe = __recipebooks[i].recipes[name];
- if (recipe !== undefined) return recipe;
- }
- return undefined;
-}
-
diff --git a/html/css/main.css b/html/css/main.css
index 98a23d692..10b079f0f 100644
--- a/html/css/main.css
+++ b/html/css/main.css
@@ -31,8 +31,9 @@ p { margin-left: 10px; margin-right: 10px; }
#header { background: url("../images/stone_brick_light.png"); height: 64px; }
#header h1 { font-size: 32px; }
#header img { height: 96px; width: 96px; margin-left: -32px; margin-top: -32px; }
-#recipebook_load_button { margin-left: 5px; width: 60px; }
-#recipebook_table { margin-left: 10px; width: 920px; }
-#recipebook_table td { background: #f8f8f8; border-left: 1px solid #bbb; padding: 10px; }
-#recipebook_url { width: 600px; }
-#recipebooks { margin-top: 32px; margin-bottom: 32px; }
+#recipe_book_load_button { margin-left: 5px; width: 60px; }
+#recipe_book_table { margin-left: 10px; width: 920px; }
+#recipe_book_table td { background: #f8f8f8; border-left: 1px solid #bbb; padding: 10px; }
+#recipe_book_url { width: 600px; }
+#recipe_books { margin-top: 32px; margin-bottom: 32px; }
+
diff --git a/html/css/qunit-1.12.0.css b/html/css/qunit-1.12.0.css
new file mode 100644
index 000000000..7ba3f9a30
--- /dev/null
+++ b/html/css/qunit-1.12.0.css
@@ -0,0 +1,244 @@
+/**
+ * QUnit v1.12.0 - A JavaScript Unit Testing Framework
+ *
+ * http://qunitjs.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699a4;
+ background-color: #0d3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: normal;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ -webkit-border-top-right-radius: 5px;
+ -webkit-border-top-left-radius: 5px;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #fff;
+}
+
+#qunit-testrunner-toolbar label {
+ display: inline-block;
+ padding: 0 .5em 0 .1em;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 0 0.5em 2em;
+ color: #5E740B;
+ background-color: #eee;
+ overflow: hidden;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 0 0.5em 2.5em;
+ background-color: #2b81af;
+ color: #fff;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+#qunit-modulefilter-container {
+ float: right;
+}
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 0.5em 0.4em 2.5em;
+ border-bottom: 1px solid #fff;
+ list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
+ display: none;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #c2ccd1;
+ text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests li .runtime {
+ float: right;
+ font-size: smaller;
+}
+
+.qunit-assert-list {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #fff;
+
+ border-radius: 5px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+
+.qunit-collapsed {
+ display: none;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: .2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ background-color: #e0f2be;
+ color: #374e0c;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ background-color: #ffcaca;
+ color: #500;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: black; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ padding: 5px;
+ background-color: #fff;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #3c510c;
+ background-color: #fff;
+ border-left: 10px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #fff;
+ border-left: 10px solid #EE5757;
+ white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -webkit-border-bottom-right-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+}
+
+#qunit-tests .fail { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: green; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+ padding: 0.5em 0.5em 0.5em 2.5em;
+
+ color: #2b81af;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid white;
+}
+#qunit-testresult .module-name {
+ font-weight: bold;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 1000px;
+ height: 1000px;
+}
diff --git a/html/data/vanilla.json b/html/data/vanilla.json
index fa553d1ac..1d3cfd964 100644
--- a/html/data/vanilla.json
+++ b/html/data/vanilla.json
@@ -26,6 +26,14 @@
"output": [[1, "Bookshelf"]],
"input": [[6, "Oak Plank"], [3, "Book"]],
"tools": ["Crafting Table"]
+ }, {
+ "output": [[1, "Book"]],
+ "input": [[1, "Leather"], [3, "Paper"]],
+ "tools": []
+ }, {
+ "output": [[1, "Bookshelf"]],
+ "input": [[6, "Oak Plank"], [3, "Book"]],
+ "tools": ["Crafting Table"]
}, {
"output": [[1, "Bowl"]],
"input": [[3, "Oak Plank"]],
@@ -51,7 +59,7 @@
"input": [[1, "Cactus"], [125, "milli-coal"]],
"tools": ["Furnace"]
}, {
- "output": [[1, "Cake"]],
+ "output": [[1, "Cake"], [1, "Bucket"]],
"input": [[3, "Milk"], [2, "Sugar"], [3, "Wheat"], [1, "Egg"]],
"tools": ["Crafting Table"]
}, {
diff --git a/html/index.html b/html/index.html
index 8661dd86d..3fc8838c3 100644
--- a/html/index.html
+++ b/html/index.html
@@ -17,14 +17,14 @@
-
+
Recipe Books
-
+
@@ -101,8 +101,8 @@
-
-
+
+
+
+
+
+
+
+