@jeremyckahn/farmhand
Version:
A farming game
258 lines (223 loc) • 7.57 kB
text/typescript
import { testState } from '../../test-utils/index.js'
import { toolLevel, toolType } from '../../enums.js'
import { INFINITE_STORAGE_LIMIT } from '../../constants.js'
import { chopForestPlot } from './chopForestPlot.js'
vitest.mock('../../data/items.js')
const toolLevelsWithAxe = (level: farmhand.toolLevel) => ({
[toolType.AXE]: level,
[toolType.HOE]: toolLevel.DEFAULT,
[toolType.SCYTHE]: toolLevel.DEFAULT,
[toolType.SHOVEL]: toolLevel.DEFAULT,
[toolType.WATERING_CAN]: toolLevel.DEFAULT,
})
// sample-tree-1's treeTimeline is [1, 2] (GROWN from daysOld 3) with a
// lifespan of 100 (DEAD from daysOld 103), and fruitTimeline is [1, 1]
// (fruit GROWN from daysSinceLastHarvest 2).
describe('chopForestPlot', () => {
beforeEach(() => {
vitest.spyOn(Math, 'random').mockReturnValue(0)
})
describe('empty plot', () => {
test('no-ops', () => {
const inputState = testState({
forest: [[null]],
inventoryLimit: INFINITE_STORAGE_LIMIT,
toolLevels: toolLevelsWithAxe(toolLevel.DEFAULT),
})
const state = chopForestPlot(inputState, 0, 0)
expect(state).toEqual(inputState)
})
})
describe('axe is unavailable', () => {
test('no-ops', () => {
const inputState = testState({
forest: [
[{ itemId: 'sample-tree-1', daysOld: 3, daysSinceLastHarvest: 2 }],
],
inventory: [],
inventoryLimit: INFINITE_STORAGE_LIMIT,
toolLevels: toolLevelsWithAxe(toolLevel.UNAVAILABLE),
})
const state = chopForestPlot(inputState, 0, 0)
expect(state).toEqual(inputState)
})
})
describe('non-tree plot content', () => {
test('no-ops', () => {
const inputState = testState({
forest: [[{ forageableId: 'mushroom', daysOld: 0 }]],
inventoryLimit: INFINITE_STORAGE_LIMIT,
toolLevels: toolLevelsWithAxe(toolLevel.DEFAULT),
})
const state = chopForestPlot(inputState, 0, 0)
expect(state).toEqual(inputState)
})
})
describe('inventory is full', () => {
test('no-ops', () => {
const inputState = testState({
forest: [
[{ itemId: 'sample-tree-1', daysOld: 3, daysSinceLastHarvest: 2 }],
],
inventory: [],
inventoryLimit: 0,
toolLevels: toolLevelsWithAxe(toolLevel.DEFAULT),
})
const state = chopForestPlot(inputState, 0, 0)
expect(state).toEqual(inputState)
})
})
describe('grown tree with ripe fruit', () => {
test('harvests the fruit as a bonus, adds wood, and clears the plot', () => {
const { forest, inventory } = chopForestPlot(
testState({
forest: [
[
{
itemId: 'sample-tree-1',
daysOld: 3,
daysSinceLastHarvest: 2,
},
],
],
inventory: [],
inventoryLimit: INFINITE_STORAGE_LIMIT,
toolLevels: toolLevelsWithAxe(toolLevel.DEFAULT),
}),
0,
0
)
expect(inventory).toEqual([
{ id: 'sample-tree-1', quantity: 1 },
{ id: 'wood', quantity: 1 },
])
expect(forest[0][0]).toBeNull()
})
})
describe('grown tree, fruit not yet ripe', () => {
test('adds wood without any fruit, and clears the plot', () => {
const { forest, inventory } = chopForestPlot(
testState({
forest: [
[{ itemId: 'sample-tree-1', daysOld: 3, daysSinceLastHarvest: 0 }],
],
inventory: [],
inventoryLimit: INFINITE_STORAGE_LIMIT,
toolLevels: toolLevelsWithAxe(toolLevel.DEFAULT),
}),
0,
0
)
expect(inventory).toEqual([{ id: 'wood', quantity: 1 }])
expect(forest[0][0]).toBeNull()
})
})
describe('immature tree', () => {
test('yields a halved wood range and no fruit', () => {
vitest.mocked(Math.random).mockReturnValue(0.999)
const { forest, inventory } = chopForestPlot(
testState({
forest: [
[{ itemId: 'sample-tree-1', daysOld: 0, daysSinceLastHarvest: 0 }],
],
inventory: [],
inventoryLimit: INFINITE_STORAGE_LIMIT,
// DEFAULT axe tier's full range is [1, 2], so an immature tree's
// halved range is [1, 1].
toolLevels: toolLevelsWithAxe(toolLevel.DEFAULT),
}),
0,
0
)
expect(inventory).toEqual([{ id: 'wood', quantity: 1 }])
expect(forest[0][0]).toBeNull()
})
})
describe('dead tree', () => {
test('yields the full (not halved) wood range and no fruit bonus, even with ripe-looking fruit', () => {
const { forest, inventory } = chopForestPlot(
testState({
forest: [
[
{
itemId: 'sample-tree-1',
// Past growthDuration + lifespan (3 + 100 = 103) - dead.
daysOld: 103,
// Would read as ripe (GROWN) fruit if the tree were alive.
daysSinceLastHarvest: 2,
},
],
],
inventory: [],
inventoryLimit: INFINITE_STORAGE_LIMIT,
// DEFAULT axe tier's full range is [1, 2]; a dead tree gets the
// full range, not the halved immature-tree range of [1, 1].
toolLevels: toolLevelsWithAxe(toolLevel.DEFAULT),
}),
0,
0
)
expect(inventory).toEqual([{ id: 'wood', quantity: 1 }])
expect(forest[0][0]).toBeNull()
})
test('rolls the full range up to its maximum, distinguishing it from a halved range', () => {
vitest.mocked(Math.random).mockReturnValue(0.999)
const { inventory } = chopForestPlot(
testState({
forest: [
[
{
itemId: 'sample-tree-1',
daysOld: 103,
daysSinceLastHarvest: 0,
},
],
],
inventory: [],
inventoryLimit: INFINITE_STORAGE_LIMIT,
toolLevels: toolLevelsWithAxe(toolLevel.GOLD),
}),
0,
0
)
// GOLD tier's full range is [8, 10]; a halved range would cap at 5.
expect(inventory).toEqual([{ id: 'wood', quantity: 10 }])
})
})
describe('wood yield range per axe tier', () => {
test('rolls the minimum of the tier range when random() is 0', () => {
vitest.mocked(Math.random).mockReturnValue(0)
const { inventory } = chopForestPlot(
testState({
forest: [
[{ itemId: 'sample-tree-1', daysOld: 3, daysSinceLastHarvest: 0 }],
],
inventory: [],
inventoryLimit: INFINITE_STORAGE_LIMIT,
toolLevels: toolLevelsWithAxe(toolLevel.GOLD),
}),
0,
0
)
// GOLD tier's range is [8, 10].
expect(inventory).toEqual([{ id: 'wood', quantity: 8 }])
})
test('rolls close to the maximum of the tier range when random() is just under 1', () => {
vitest.mocked(Math.random).mockReturnValue(0.999)
const { inventory } = chopForestPlot(
testState({
forest: [
[{ itemId: 'sample-tree-1', daysOld: 3, daysSinceLastHarvest: 0 }],
],
inventory: [],
inventoryLimit: INFINITE_STORAGE_LIMIT,
toolLevels: toolLevelsWithAxe(toolLevel.GOLD),
}),
0,
0
)
// GOLD tier's range is [8, 10].
expect(inventory).toEqual([{ id: 'wood', quantity: 10 }])
})
})
})