NickLeylandMedia
NickLeylandMedia

Reputation: 99

Reducing object values based on values pulled from array?

I am building an idle game with JavaScript.

It is a game where you run a bakery.

The resources of the bakery are represented within an object 'mainObj'.

let mainObj = {
  "Money": 100,
  "Resources": {
    "Milk": 10,
    "Eggs": 10,
    "Flour": 10,
    "Water": 10,
    "Sugar": 10
  },
  "Employees": {
    "Bakers": 1,
    "Cooks": 0,
    "Servers": 0,
    "Farmers": 0
  },
  "Inventory": {
    "Cake": 0,
    "Cookies": 0
  }
}

The associated costs to bake an item, such as a cake or cookies, are stored in the array 'bakeInfo'.

let bakeInfo = [{
  "Name": "Cake",
  "Milk": 1,
  "Eggs": 2,
  "Flour": 1
}, {
  "Name": "Cookies",
  "Eggs": 1,
  "Flour": 1
}]

I want to write a function that takes the info from bakeInfo, such as for baking a cake (1 Milk, 2 Eggs, 1 Flour) checks the mainObj for the required ingredients (and throws an error if there isn't enough) and if there is enough of each, it reduces the ingredients in resources by the amount in bakeInfo, and then adds the appropriate item (cake/cookies) to the mainObj inventory.

I've tried a few different ways to go about it, which pretty much entails an individual function for each ingredient type, which seems extremely inefficient to me.

Also, some items that will be baked omit some ingredients (cookies don't require milk). So if the function checked this/only removed required items from the mainObj inventory that would be ideal, in fact, it is necessary.

If someone could point me in the right direction that would be great.

Upvotes: 2

Views: 88

Answers (3)

Mr. Polywhirl
Mr. Polywhirl

Reputation: 48600

With the makeItem function below, you could make cakes until your run out of resources by using a try-catch and throwing an Error when the minimum number of required resources cannot be met.

const player = {
  "Money": 100,
  "Resources": { "Milk": 10, "Eggs": 10, "Flour": 10, "Water": 10, "Sugar": 10 },
  "Employees": { "Bakers": 1, "Cooks": 0, "Servers": 0, "Farmers": 0 },
  "Inventory": { "Cake": 0, "Cookies": 0 }
};

const recipes = [
  { "Name": "Cake", "Milk": 1, "Eggs": 2, "Flour": 1 },
  { "Name": "Cookies", "Eggs": 1, "Flour": 1 }
];

const retrieveRecipe = (name) =>
  recipes.find(({ Name }) => Name === name);

const canMake = (player, recipe) => {
  const found = retrieveRecipe(recipe);
  if (found == null) return false;
  const { Name, ...ingredients } = found;
  const { Resources } = player;
  return Object.entries(ingredients)
    .every(([ name, amount ]) => amount <= Resources[name]);
}

const makeItem = (player, recipe) => {
  if (!canMake(player, recipe)) throw new Error('Insufficient resources');
  const found = retrieveRecipe(recipe);
  const { Name, ...ingredients } = found;
  Object.entries(ingredients).forEach(([name, amount]) => {
    player.Resources[name] -= amount;
  });
  player.Inventory[Name]++;
}

// Make cakes until required resources cannot be met.
try {
  while (true) {
    makeItem(player, 'Cake');
  }
} catch (e) {
  console.log('Ran out of resources!');
}

console.log(player);
.as-console-wrapper { top: 0; max-height: 100% !important; }

Upvotes: 0

TKoL
TKoL

Reputation: 13892

Marko's solution above is for adding one item at a time. However, if you want to check multiple items at a time, and error if there isn't enough ingredients for all of them, this solution might be better:

let mainObj = {
  Money: 100,
  Resources: {
      Milk: 10,
      Eggs: 10,
      Flour: 10,
      Water: 10,
      Sugar: 10
  },
  Employees: {
      Bakers: 1,
      Cooks: 0,
      Servers: 0,
      Farmers: 0
  },
  Inventory: {
      Cake: 0,
      Cookies: 0
  },
}

let bakeInfo = [
  {Name: 'Cake', Milk: 1, Eggs: 2, Flour: 1},
  {Name: 'Cookies', Eggs: 1, Flour: 1} 
]

function bakeOrError(bakeInfo, mainObj) {
  // first make a copy of resources
  const resources = Object.assign({}, mainObj.Resources); 
  // now, iterate over all the bakeInfo and reduce resources
  bakeInfo.forEach(bi => {
    Object.keys(bi)
      .filter(k => k !== 'Name') // don't operate on the Name key, everything else is an ingredient
      .forEach(k => {
        resources[k] -= bi[k];
        if (resources[k] < 0) throw new Error('insufficient resources');
      })
  })

  // if we haven't errored by here, there were enough ingredients to support everything.
  // update the resources object
  mainObj.Resources = resources;
  // then add to inventory
  bakeInfo.forEach(bi => mainObj.Inventory[bi.Name]++);
}

bakeOrError(bakeInfo, mainObj);
console.log(mainObj);

Upvotes: 1

Marko Borković
Marko Borković

Reputation: 1912

This should work:

function reduceResources(info){
  let bakeInfoKeys = Object.keys(info);
  
  // check if there is enough resources
  bakeInfoKeys.forEach(key => {
    if(mainObj.Resources[key] !== undefined){
        if(mainObj.Resources[key] < info[key])
            return false; // return false if there is not enough resources
    }
  });
  
  // reduce the resource amount
  bakeInfoKeys.forEach(key => {
    if(mainObj.Resources[key] !== undefined){
        mainObj.Resources[key] -= info[key];
    }
  });
  
  // add the item to the inventory
  mainObj.Inventory[info['Name']] += 1;
  
  return true;
}

The function will return true if it was successful, and false if the resources are not enough.

You can use it like this:

if(reduceResources(bakeInfo[0]))
    console.log("Success!")
else
    console.log("Fail!")

Upvotes: 0

Related Questions