Skip to content

Commit

Permalink
add experience part 1
Browse files Browse the repository at this point in the history
    - add 'experience' to farmhand state
    - add 'useLegacyLevelingSystem' to farmhand state and settings
      screen
    - update `levelAchieved` to be more flexible about how level is
      calculated based on which leveling system is being used
    - update all calls to `levelAchieved`
    - update utils unit tests for levelAchieved to test both systems
    - add 'EXPERIENCE' feature flag
  • Loading branch information
lstebner committed Aug 27, 2023
1 parent d37d410 commit d96a158
Show file tree
Hide file tree
Showing 21 changed files with 141 additions and 85 deletions.
1 change: 1 addition & 0 deletions .env.development.local
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
REACT_APP_API_ROOT=http://localhost:3001/
REACT_APP_ENABLE_KEGS=true
REACT_APP_ENABLE_EXPERIENCE=true
5 changes: 3 additions & 2 deletions src/components/Farmhand/Farmhand.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ import {
computeMarketPositions,
createNewField,
doesMenuObstructStage,
farmProductsSold,
generateCow,
getAvailableShopInventory,
getItemCurrentValue,
Expand Down Expand Up @@ -381,7 +380,7 @@ export default class Farmhand extends FarmhandReducers {

get levelEntitlements() {
return getLevelEntitlements(
levelAchieved(farmProductsSold(this.state.itemsSold))
levelAchieved({ itemsSold: this.state.itemsSold })
)
}

Expand Down Expand Up @@ -425,6 +424,7 @@ export default class Farmhand extends FarmhandReducers {
cowTradeTimeoutId: -1,
cropsHarvested: {},
dayCount: 0,
experience: 0,
farmName: 'Unnamed',
field: createNewField(),
fieldMode: OBSERVE,
Expand Down Expand Up @@ -497,6 +497,7 @@ export default class Farmhand extends FarmhandReducers {
[toolType.WATERING_CAN]: toolLevel.DEFAULT,
},
useAlternateEndDayButtonPosition: false,
useLegacyLevelingSystem: true,
valueAdjustments: {},
version: process.env.REACT_APP_VERSION ?? '',
}
Expand Down
9 changes: 2 additions & 7 deletions src/components/Field/Field.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@ import Plot from '../Plot'
import QuickSelect from '../QuickSelect'
import { fieldMode } from '../../enums'
import tools from '../../data/tools'
import {
doesInventorySpaceRemain,
farmProductsSold,
levelAchieved,
nullArray,
} from '../../utils'
import { doesInventorySpaceRemain, levelAchieved, nullArray } from '../../utils'
import { getLevelEntitlements } from '../../utils/getLevelEntitlements'

import './Field.sass'
Expand Down Expand Up @@ -74,7 +69,7 @@ export const isInHoverRange = ({
switch (fieldMode) {
case SET_SPRINKLER:
hoveredPlotRangeSizeToRender = getLevelEntitlements(
levelAchieved(farmProductsSold(itemsSold))
levelAchieved({ itemsSold })
).sprinklerRange

break
Expand Down
2 changes: 1 addition & 1 deletion src/components/Navigation/Navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export const Navigation = ({
viewList,

totalFarmProductsSold = farmProductsSold(itemsSold),
currentLevel = levelAchieved(totalFarmProductsSold),
currentLevel = levelAchieved({ itemsSold }),
levelPercent = scaleNumber(
totalFarmProductsSold,
farmProductSalesVolumeNeededForLevel(currentLevel),
Expand Down
12 changes: 2 additions & 10 deletions src/components/OnlinePeersView/OnlinePeer/OnlinePeer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ import CardHeader from '@material-ui/core/CardHeader'
import CowCard from '../../CowCard'

import { moneyString } from '../../../utils/moneyString'
import {
getPlayerName,
farmProductsSold,
integerString,
levelAchieved,
} from '../../../utils'
import { getPlayerName, integerString, levelAchieved } from '../../../utils'

import './OnlinePeer.sass'

Expand All @@ -28,10 +23,7 @@ const OnlinePeer = ({
subheader: (
<div>
<p>Day: {integerString(dayCount)}</p>
<p>
Level:{' '}
{integerString(levelAchieved(farmProductsSold(itemsSold)))}
</p>
<p>Level: {integerString(levelAchieved({ itemsSold }))}</p>
<p>Money: {moneyString(money)}</p>
</div>
),
Expand Down
4 changes: 2 additions & 2 deletions src/components/OnlinePeersView/OnlinePeersView.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { array, number, object, string } from 'prop-types'

import BailOutErrorBoundary from '../BailOutErrorBoundary'

import { getPlayerName, farmProductsSold, levelAchieved } from '../../utils'
import { getPlayerName, levelAchieved } from '../../utils'
import FarmhandContext from '../Farmhand/Farmhand.context'

import CowCard from '../CowCard'
Expand Down Expand Up @@ -58,7 +58,7 @@ const OnlinePeersView = ({
{sortBy(populatedPeers, [
peerId =>
// Use negative value to reverse sort order
-levelAchieved(farmProductsSold(peers[peerId].itemsSold || 0)),
-levelAchieved({ itemsSold: peers[peerId].itemsSold || 0 }),
]).map(peerId => (
<BailOutErrorBoundary {...{ key: peerId }}>
<OnlinePeer {...{ peer: peers[peerId] }} />
Expand Down
13 changes: 13 additions & 0 deletions src/components/SettingsView/SettingsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const SettingsView = ({
handleShowHomeScreenChange,
showNotifications,
useAlternateEndDayButtonPosition,
useLegacyLevelingSystem,
showHomeScreen,
}) => {
const [isClearDataDialogOpen, setIsClearDataDialogOpen] = useState(false)
Expand Down Expand Up @@ -99,6 +100,17 @@ const SettingsView = ({
}
label="Display custom names for cows received from other players"
/>
<FormControlLabel
control={
<Switch
color="primary"
checked={useLegacyLevelingSystem}
onChange={() => console.log('todo: save change')}
name="use-legacy-leveling-system"
/>
}
label="Use legacy leveling system (items sold)"
/>
</FormGroup>
</FormControl>

Expand Down Expand Up @@ -210,6 +222,7 @@ SettingsView.propTypes = {
showHomeScreen: bool.isRequired,
showNotifications: bool.isRequired,
useAlternateEndDayButtonPosition: bool.isRequired,
useLegacyLevelingSystem: bool,
}

export default function Consumer(props) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/StatsView/StatsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const StatsView = ({
todaysRevenue,

totalFarmProductsSold = farmProductsSold(itemsSold),
currentLevel = levelAchieved(totalFarmProductsSold),
currentLevel = levelAchieved({ itemsSold }),
}) => (
<div className="StatsView">
<TableContainer {...{ component: ElevatedPaper }}>
Expand Down
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export const PERSISTED_STATE_KEYS = [
'cowsTraded',
'cropsHarvested',
'dayCount',
'experience',
'farmName',
'field',
'historicalDailyLosses',
Expand Down
12 changes: 12 additions & 0 deletions src/game-logic/reducers/addExperience.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { processLevelUp } from './processLevelUp'

export const addExperience = (state, amount) => {
const newExperience = state.experience + amount

state = processLevelUp(state, state.oldLevel)

return {
...state,
experience: newExperience,
}
}
3 changes: 1 addition & 2 deletions src/game-logic/reducers/generatePriceEvents.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
farmProductsSold,
filterItemIdsToSeeds,
getPriceEventForCrop,
getRandomUnlockedCrop,
Expand Down Expand Up @@ -29,7 +28,7 @@ export const generatePriceEvents = state => {
// less-than check.
if (random() < PRICE_EVENT_CHANCE) {
const { items: unlockedItems } = getLevelEntitlements(
levelAchieved(farmProductsSold(state.itemsSold))
levelAchieved({ itemsSold: state.itemsSold })
)

const cropItem = getRandomUnlockedCrop(
Expand Down
5 changes: 2 additions & 3 deletions src/game-logic/reducers/processLevelUp.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { levels } from '../../data/levels'
import {
farmProductsSold,
getRandomLevelUpReward,
getRandomLevelUpRewardQuantity,
levelAchieved,
Expand All @@ -20,7 +19,7 @@ import { showNotification } from './showNotification'
*/
export const processLevelUp = (state, oldLevel) => {
const { itemsSold, selectedItemId } = state
const newLevel = levelAchieved(farmProductsSold(itemsSold))
const newLevel = levelAchieved({ itemsSold })

// Loop backwards so that the notifications appear in descending order.
for (let i = newLevel; i > oldLevel; i--) {
Expand Down Expand Up @@ -48,7 +47,7 @@ export const processLevelUp = (state, oldLevel) => {
selectedItemId === SPRINKLER_ITEM_ID
) {
const { sprinklerRange } = getLevelEntitlements(
levelAchieved(farmProductsSold(itemsSold))
levelAchieved({ itemsSold })
)

if (sprinklerRange > state.hoveredPlotRangeSize) {
Expand Down
11 changes: 2 additions & 9 deletions src/game-logic/reducers/processSprinklers.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { itemType } from '../../enums'
import {
farmProductsSold,
getPlotContentType,
getRangeCoords,
levelAchieved,
} from '../../utils'
import { getPlotContentType, getRangeCoords, levelAchieved } from '../../utils'
import { getLevelEntitlements } from '../../utils/getLevelEntitlements'

import { setWasWatered } from './helpers'
Expand All @@ -19,9 +14,7 @@ export const processSprinklers = state => {
const crops = new Map()
let modifiedField = [...field]

const { sprinklerRange } = getLevelEntitlements(
levelAchieved(farmProductsSold(itemsSold))
)
const { sprinklerRange } = getLevelEntitlements(levelAchieved({ itemsSold }))

field.forEach((row, plotY) => {
row.forEach((plot, plotX) => {
Expand Down
3 changes: 1 addition & 2 deletions src/game-logic/reducers/sellItem.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { itemsMap } from '../../data/maps'
import {
castToMoney,
farmProductsSold,
getAdjustedItemValue,
getResaleValue,
getSalePriceMultiplier,
Expand Down Expand Up @@ -39,7 +38,7 @@ export const sellItem = (state, { id }, howMany = 1) => {
money: initialMoney,
valueAdjustments,
} = state
const oldLevel = levelAchieved(farmProductsSold(itemsSold))
const oldLevel = levelAchieved({ itemsSold })
let { loanBalance } = state

const adjustedItemValue = isItemSoldInShop(item)
Expand Down
3 changes: 1 addition & 2 deletions src/game-logic/reducers/sellKeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import { itemsMap } from '../../data/maps'
import {
castToMoney,
farmProductsSold,
getSalePriceMultiplier,
levelAchieved,
moneyTotal,
Expand Down Expand Up @@ -38,7 +37,7 @@ export const sellKeg = (state, keg) => {
itemsSold,
money: initialMoney,
} = state
const oldLevel = levelAchieved(farmProductsSold(itemsSold))
const oldLevel = levelAchieved({ itemsSold })

let { loanBalance } = state
let saleValue = 0
Expand Down
17 changes: 17 additions & 0 deletions src/utils/farmProductsSold.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { itemsMap } from '../data/maps'

import { memoize } from './memoize'
import { isItemAFarmProduct } from './isItemAFarmProduct'

export const farmProductsSold = memoize(
/**
* @param {Record<string, number>} itemsSold
* @returns {number}
*/
itemsSold =>
Object.entries(itemsSold).reduce(
(sum, [itemId, numberSold]) =>
sum + (isItemAFarmProduct(itemsMap[itemId]) ? numberSold : 0),
0
)
)
42 changes: 4 additions & 38 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,24 +289,6 @@ export const getPlotContentType = ({ itemId }) =>
export const doesPlotContainCrop = plot =>
plot !== null && getPlotContentType(plot) === itemType.CROP

/**
* @param {farmhand.item} item
* @returns {boolean}
*/
export const isItemAGrownCrop = item =>
Boolean(item.type === itemType.CROP && !item.growsInto)

/**
* @param {farmhand.item} item
* @returns {boolean}
*/
export const isItemAFarmProduct = item =>
Boolean(
isItemAGrownCrop(item) ||
item.type === itemType.MILK ||
item.type === itemType.CRAFTED_ITEM
)

export const getLifeStageRange = memoize((
/** @type {farmhand.cropTimetable} */ cropTimetable
) =>
Expand Down Expand Up @@ -791,26 +773,6 @@ export const findCowById = memoize(
(cowInventory, id) => cowInventory.find(cow => id === cow.id)
)

export const farmProductsSold = memoize(
/**
* @param {Record<string, number>} itemsSold
* @returns {number}
*/
itemsSold =>
Object.entries(itemsSold).reduce(
(sum, [itemId, numberSold]) =>
sum + (isItemAFarmProduct(itemsMap[itemId]) ? numberSold : 0),
0
)
)

/**
* @param {number} farmProductsSold
* @returns {number}
*/
export const levelAchieved = farmProductsSold =>
Math.floor(Math.sqrt(farmProductsSold) / 10) + 1

/**
* @param {number} targetLevel
* @returns {number}
Expand Down Expand Up @@ -1202,3 +1164,7 @@ export const isOctober = () => new Date().getMonth() === 9
export const isDecember = () => new Date().getMonth() === 11

export { default as totalIngredientsInRecipe } from './totalIngredientsInRecipe'
export { farmProductsSold } from './farmProductsSold'
export { isItemAFarmProduct } from './isItemAFarmProduct'
export { isItemAGrownCrop } from './isItemAGrownCrop'
export { levelAchieved } from './levelAchieved'
41 changes: 35 additions & 6 deletions src/utils/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -768,12 +768,41 @@ describe('farmProductsSold', () => {
})

describe('levelAchieved', () => {
test('calculates achieved level', () => {
expect(levelAchieved(0)).toEqual(1)
expect(levelAchieved(100)).toEqual(2)
expect(levelAchieved(150)).toEqual(2)
expect(levelAchieved(400)).toEqual(3)
expect(levelAchieved(980100)).toEqual(100)
const cases = [
[1, 0],
[2, 100],
[2, 150],
[3, 400],
[100, 980100],
]

describe('with legacy system', () => {
test.each(cases)(
`returns level %p for %p items sold`,
(expectedLevel, numItemsSold) => {
const level = levelAchieved({
itemsSold: { carrot: numItemsSold },
useLegacyLevelingSystem: true,
})

expect(level).toEqual(expectedLevel)
}
)
})

describe('with experience system', () => {
test.each(cases)(
`returns level %p for %p experience`,
(expectedLevel, experience) => {
const level = levelAchieved({
experience,
features: { EXPERIENCE: true },
useLegacyLevelingSystem: false,
})

expect(level).toEqual(expectedLevel)
}
)
})
})

Expand Down
Loading

0 comments on commit d96a158

Please sign in to comment.