Skip to content

Commit

Permalink
Merge pull request #27 from gw2efficiency/add-owned-item-value
Browse files Browse the repository at this point in the history
Add value to owned items if filter is true and fix tests.
  • Loading branch information
queicherius authored Nov 14, 2024
2 parents d9926db + c7791d8 commit a3cade1
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 3 deletions.
70 changes: 67 additions & 3 deletions src/cheapestTree.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
import { calculateTreeQuantity } from './calculateTreeQuantity'
import { calculateTreePrices } from './calculateTreePrices'
import { calculateTreeCraftFlags } from './calculateTreeCraftFlags'
import { RecipeTree, RecipeTreeWithCraftFlags } from './types'
import { NestedRecipe } from '@gw2efficiency/recipe-nesting'
import { RecipeTree, RecipeTreeWithCraftFlags, RecipeTreeWithPrices } from './types'
import {
NestedRecipe,
BasicCurrencyComponent,
BasicItemComponent,
} from '@gw2efficiency/recipe-nesting'

export function cheapestTree(
amount: number,
tree: NestedRecipe,
itemPrices: Record<string, number>,
availableItems: Record<string, number> = {},
forceBuyItems: Array<number> = []
forceBuyItems: Array<number> = [],
valueOwnItems = false
): RecipeTreeWithCraftFlags {
// calculateTreeQuantity already checks for craft flags, so we can set them here when valuing owned items
if (valueOwnItems) {
const treeWithQuantityWithoutAvailableItems = calculateTreeQuantity(
amount,
tree as RecipeTree,
{}
)
const treeWithPriceWithoutAvailableItems = calculateTreePrices(
treeWithQuantityWithoutAvailableItems,
itemPrices
)
const cheaperToBuyItemIds = getCheaperToBuyItemIds(treeWithPriceWithoutAvailableItems)
disableCraftForItemIds(tree, cheaperToBuyItemIds)
}
// Adjust the tree total and used quantities
const treeWithQuantity = calculateTreeQuantity(amount, tree as RecipeTree, availableItems)

Expand All @@ -32,3 +51,48 @@ export function cheapestTree(
// Recalculate the correct tree price
return calculateTreePrices(treeWithQuantityPostFlags, itemPrices)
}

function getCheaperToBuyItemIds(
tree: RecipeTreeWithPrices,
ids: Array<number> = []
): Array<number> {
// If buying the item costs less than the possible profit made by selling its craft components (with TP tax), we should buy instead of crafting
if (
typeof tree.craftDecisionPrice === 'number' &&
typeof tree.buyPrice === 'number' &&
tree.buyPrice < tree.craftDecisionPrice * 0.85
) {
if (!ids.includes(tree.id)) {
ids.push(tree.id)
}
}

if (tree.components && Array.isArray(tree.components)) {
tree.components.forEach((component) => getCheaperToBuyItemIds(component, ids))
}

return ids
}

type NestedRecipeAndBasicComponentWithCraftFlag = (
| NestedRecipe
| BasicItemComponent
| BasicCurrencyComponent
) & {
craft?: boolean
}

function disableCraftForItemIds(
tree: NestedRecipeAndBasicComponentWithCraftFlag,
ids: Array<number>
) {
if (ids.includes(tree.id)) {
tree.craft = false
}

if ('components' in tree && Array.isArray(tree.components)) {
tree.components.forEach((component) => {
disableCraftForItemIds(component as NestedRecipeAndBasicComponentWithCraftFlag, ids)
})
}
}
74 changes: 74 additions & 0 deletions tests/__snapshots__/cheapestTree.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,77 @@ Object {
"usedQuantity": 2,
}
`;

exports[`cheapestTree can calculate the cheapest tree correctly with value own items 1`] = `
Object {
"buyPrice": 10,
"buyPriceEach": 10,
"components": Array [
Object {
"buyPrice": 100,
"buyPriceEach": 100,
"components": Array [
Object {
"buyPrice": 1000,
"buyPriceEach": 500,
"components": undefined,
"craft": false,
"craftDecisionPrice": 1000,
"craftResultPrice": 1000,
"decisionPrice": 1000,
"id": 4,
"output": 1,
"quantity": 2,
"totalQuantity": 2,
"type": "Item",
"usedQuantity": 2,
},
],
"craft": false,
"craftDecisionPrice": 1000,
"craftPrice": 1000,
"craftResultPrice": 100,
"decisionPrice": 100,
"disciplines": Array [
"Amoursmith",
],
"id": 3,
"min_rating": 200,
"multipleRecipeCount": 1,
"output": 1,
"prerequisites": Array [
Object {
"id": 123,
"type": "Recipe",
},
],
"quantity": 5,
"totalQuantity": 5,
"type": "Recipe",
"usedQuantity": 1,
},
],
"craft": true,
"craftDecisionPrice": 100,
"craftPrice": 100,
"craftResultPrice": 100,
"decisionPrice": 100,
"disciplines": Array [
"Amoursmith",
],
"id": 1,
"min_rating": 300,
"multipleRecipeCount": 1,
"output": 1,
"prerequisites": Array [
Object {
"id": 124,
"type": "Recipe",
},
],
"quantity": 1,
"totalQuantity": 1,
"type": "Recipe",
"usedQuantity": 1,
}
`;
67 changes: 67 additions & 0 deletions tests/cheapestTree.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import { NestedRecipe } from '@gw2efficiency/recipe-nesting'
import { cheapestTree } from '../src'
import { RecipeTreeWithCraftFlags } from '../src/types'
import { clone } from '@devoxa/flocky'
import { JSONValue } from '@devoxa/flocky/dist/typeHelpers'

type SimplifiedTree = {
id: number
craft: boolean
components?: Array<SimplifiedTree>
}

function simplifyTree(tree: RecipeTreeWithCraftFlags): SimplifiedTree {
return {
id: tree.id,
craft: tree.craft,
components: tree.components?.map((x) => simplifyTree(x)),
}
}

function cloneRecipe(recipe: NestedRecipe): NestedRecipe {
return clone(recipe as unknown as JSONValue) as unknown as NestedRecipe
}

describe('cheapestTree', () => {
it('can calculate the cheapest tree correctly', () => {
Expand Down Expand Up @@ -130,4 +151,50 @@ describe('cheapestTree', () => {
const calculatedTree = cheapestTree(2, recipeTree, prices, {}, [3])
expect(calculatedTree).toMatchSnapshot()
})

it('can calculate the cheapest tree correctly with value own items', () => {
const recipeTree: NestedRecipe = {
id: 1,
type: 'Recipe',
prerequisites: [{ type: 'Recipe', id: 124 }],
min_rating: 300,
disciplines: ['Amoursmith'],
quantity: 1,
output: 1,
multipleRecipeCount: 1,
components: [
{
id: 3,
type: 'Recipe',
prerequisites: [{ type: 'Recipe', id: 123 }],
output: 1,
min_rating: 200,
disciplines: ['Amoursmith'],
quantity: 5,
components: [{ id: 4, type: 'Item', quantity: 2 }],
multipleRecipeCount: 1,
},
],
}

const prices = { 1: 10, 3: 100, 4: 500 }
const availableItems = { 3: 4, 4: 100 }

const recipeTreeA = cloneRecipe(recipeTree)
const calculatedTreeNoOwn = cheapestTree(1, recipeTreeA, prices)
const simpleTreeNoOwn = simplifyTree(calculatedTreeNoOwn)

// Should make the same decisions as if we had no own materials
const recipeTreeB = cloneRecipe(recipeTree)
const calculatedTree = cheapestTree(1, recipeTreeB, prices, availableItems, [], true)
const simpleTree = simplifyTree(calculatedTree)
expect(calculatedTree).toMatchSnapshot()
expect(simpleTree).toEqual(simpleTreeNoOwn)

// Should value own materials as "free" and make other decisions
const recipeTreeC = cloneRecipe(recipeTree)
const calculatedTreeNoValue = cheapestTree(1, recipeTreeC, prices, availableItems, [], false)
const simpleTreeNoValue = simplifyTree(calculatedTreeNoValue)
expect(simpleTree).not.toEqual(simpleTreeNoValue)
})
})

0 comments on commit a3cade1

Please sign in to comment.