craftingrecipes
Version:
recipes for crafting items with inventory and itempile
304 lines (245 loc) • 8.34 kB
JavaScript
'use strict';
class CraftingThesaurus {
constructor() {
this.map = {};
global.CraftingThesaurus_instance = this; // TODO: desingletonify?
}
registerName(lookupName, item) {
if (lookupName.indexOf('.') === -1) {
throw new Error(`craftingrecipes registerName(${lookupName}): name is in invalid format, should be 'shape.material'`);
}
if (this.map[lookupName] === undefined) {
this.map[lookupName] = [];
}
this.map[lookupName].push(item);
}
matchesName(lookupName, itemPile) {
if (lookupName === undefined && itemPile === undefined) return true; // nothing matches nothing
if (itemPile === undefined) return false;
if (itemPile.item === lookupName) return true; // direct name match
// known alias?
const a = this.map[lookupName];
if (a === undefined) return false;
return a.indexOf(itemPile.item) != -1;
}
}
class Recipe {
computeOutput(inventory) {
return undefined;
}
matches(inventory) {
console.log('matches?',inventory);
return this.computeOutput(inventory) !== undefined;
}
craft(inventory) {
return undefined;
}
}
class AmorphousRecipe extends Recipe {
constructor(ingredients, output) {
super();
this.ingredients = ingredients;
this.output = output;
}
// if itemPile is in pendingIngredients, remove it
removeIngredient(itemPile, pendingIngredients) {
for (let i = 0; i < pendingIngredients.length; ++i) {
const testIngredient = pendingIngredients[i];
if (global.CraftingThesaurus_instance.matchesName(testIngredient, itemPile)) {
pendingIngredients.splice(i, 1);
console.log('matchesName',testIngredient,itemPile);
return true;
}
}
return false;
}
findMatchingSlots(inventory) {
const pendingIngredients = this.ingredients.slice();
const foundIndices = [];
for (let i = 0; i < inventory.size(); ++i) {
const itemPile = inventory.get(i)
if (itemPile === undefined) continue;
console.log('findMatchingSlots',itemPile,pendingIngredients);
if (!this.removeIngredient(itemPile, pendingIngredients)) {
console.log('FOUND SOMETHING WE DIDNT WANT!');
// found something we didn't want
return undefined;
}
foundIndices.push(i);
}
if (pendingIngredients.length !== 0) {
// didn't find everything
return undefined;
}
return foundIndices;
}
computeOutput(inventory) {
if (this.findMatchingSlots(inventory) !== undefined) {
return this.output.clone();
}
return undefined;
}
craft(inventory) {
const slots = this.findMatchingSlots(inventory);
if (!slots) return undefined;
for (let slot of slots) {
inventory.takeAt(slot, 1); // TODO: check return
}
return this.output.clone();
}
}
class PositionalRecipe extends Recipe {
// TODO: accept patternStrArray, ingredientMap (['AB''], {A:.., B:..})
constructor(ingredientMatrix, output) {
super();
this.ingredientMatrix = ingredientMatrix;
this.output = output;
}
findMatchingSlots(inputInventory) {
console.log('findMatchingSlots',inputInventory);
// inventory input ingredients must match this.ingredientMatrix at same (relative) positions
const foundIndices = [];
const a = PositionalRecipe.tighten(inputInventory);
const inventory = a[0];
const shiftRow = a[1];
const shiftColumn = a[2];
if (inventory.height !== this.ingredientMatrix.length || inventory.width !== this.ingredientMatrix[0].length) {
console.log('fms wrong dimensions',
inventory.height, this.ingredientMatrix.length,
inventory.width, this.ingredientMatrix[0].length,
inventory, this.ingredientMatrix);
return undefined;
}
for (let i = 0; i < this.ingredientMatrix.length; ++i) {
const row = this.ingredientMatrix[i];
for (let j = 0; j < row.length; ++j) {
const expectedName = row[j];
const index = j + i * inventory.width;
const actualPile = inventory.get(index);
if (!global.CraftingThesaurus_instance.matchesName(expectedName, actualPile)) {
console.log('fms not matching',expectedName,actualPile);
return undefined;
}
// add original index from inputInventory for removing ingredients
const unshiftedIndex = (j + shiftColumn) + (i + shiftRow) * inputInventory.width;
foundIndices.push(unshiftedIndex);
}
}
console.log('fms foundIndices',foundIndices);
return foundIndices;
}
// tighten the bounds around an inventory, shrinking it from the edges if possible
static tighten(inventory) {
// iterate from each side, computing occupied bounds
let x;
for (x = 0; x < inventory.width; ++x) {
let isOccupied = false;
for (let y = 0; y < inventory.height; ++y) {
if (inventory.get(y + x * inventory.width) !== undefined) {
isOccupied = true;
}
}
if (isOccupied) break;
}
const firstRow = x;
for (x = inventory.width - 1; x >= 0; --x) {
let isOccupied = false;
for (let y = 0; y < inventory.height; ++y) {
if (inventory.get(y + x * inventory.width) !== undefined) {
isOccupied = true;
}
}
if (isOccupied) break;
}
const lastRow = x;
let y;
for (y = 0; y < inventory.height; ++y) {
let isOccupied = false;
for (let x = 0; x < inventory.width; ++x) {
if (inventory.get(y + x * inventory.width) !== undefined) {
isOccupied = true;
}
}
if (isOccupied) break;
}
const firstColumn = y;
for (y = inventory.height - 1; y >= 0; --y) {
let isOccupied = false;
for (let x = 0; x < inventory.width; ++x) {
if (inventory.get(y + x * inventory.width) !== undefined) {
isOccupied = true;
}
}
if (isOccupied) break;
}
const lastColumn = y;
//console.log('firstRow',firstRow,'lastRow',lastRow);
//console.log('firstColumn',firstColumn,'lastColumn',lastColumn);
// copy to new smaller-or-equal inventory
let newWidth = lastColumn - firstColumn + 1;
let newHeight = lastRow - firstRow + 1;
if (newWidth < 0 || newHeight < 0) {
// completely empty inventory - but cannot shrink to 0x0 (dimensions must be
// positive in inventory >=0.1.2), so go with the next best thing (1x1)
newWidth = newHeight = 1;
}
//console.log('new ',newWidth,newHeight);
const newInventory = new inventory.constructor(newWidth, newHeight);
for (let oldX = firstRow; oldX <= lastRow; ++oldX) {
for (let oldY = firstColumn; oldY <= lastColumn; ++oldY) {
const pile = inventory.get(oldY + oldX * inventory.width);
const newX = oldX - firstRow;
const newY = oldY - firstColumn;
newInventory.set(newY + newX * newInventory.width, pile);
}
}
return [newInventory, firstRow, firstColumn];
}
computeOutput(inventory) {
console.log('computeOutput',inventory);
if (this.findMatchingSlots(inventory) !== undefined) {
return this.output.clone();
}
return undefined;
}
// TODO: refactor with AmorphousRecipe
craft(inventory) {
const slots = this.findMatchingSlots(inventory);
if (!slots) return undefined;
for (let slot of slots) {
inventory.takeAt(slot, 1); // TODO: check return
}
return this.output.clone();
}
}
class RecipeList {
constructor() {
this.recipes = [];
}
register(recipe) {
this.recipes.push(recipe);
}
find(inventory) {
for (let recipe of this.recipes) {
if (recipe.computeOutput(inventory) !== undefined) {
return recipe;
}
}
return undefined;
}
craft(inventory) {
for (let recipe of this.recipes) {
const output = recipe.craft(inventory);
if (output) return output;
}
return undefined;
}
}
module.exports = (game, opts) => {
throw new Error('craftingrecipes plugin (plugin only) replaced by voxel-recipes');
};
module.exports.Recipe = Recipe;
module.exports.AmorphousRecipe = AmorphousRecipe;
module.exports.PositionalRecipe = PositionalRecipe;
module.exports.CraftingThesaurus = CraftingThesaurus;
module.exports.RecipeList = RecipeList;