From f89de8abd83ba85e969de23df86a709ddbb4cbd7 Mon Sep 17 00:00:00 2001 From: Andrew Miner Date: Mon, 2 Sep 2013 12:24:08 -0700 Subject: [PATCH] [#13, #23] Add support for alternate recipes * Added support for determining all the possibly ways to create a given item * Added full unit test support --- html/crafting.js | 355 ------ html/css/main.css | 11 +- html/css/qunit-1.12.0.css | 244 ++++ html/data/vanilla.json | 10 +- html/index.html | 12 +- html/script/crafting.js | 683 +++++++++++ html/script/jquery.js | 167 +++ html/{ => script}/purl.js | 0 html/script/qunit-1.12.0.js | 2212 +++++++++++++++++++++++++++++++++++ html/script/test.js | 746 ++++++++++++ html/test.html | 19 + 11 files changed, 4092 insertions(+), 367 deletions(-) delete mode 100644 html/crafting.js create mode 100644 html/css/qunit-1.12.0.css create mode 100644 html/script/crafting.js create mode 100644 html/script/jquery.js rename html/{ => script}/purl.js (100%) create mode 100644 html/script/qunit-1.12.0.js create mode 100644 html/script/test.js create mode 100644 html/test.html 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 @@ - - + + + + + + + + + +