From e2b750048d2c87f287a7dde0c24980e7a368519a Mon Sep 17 00:00:00 2001 From: Jeff Colombo Date: Sat, 19 Jan 2019 23:43:36 -0500 Subject: [PATCH] league stats list behavior --- generatePokemonData.ts | 17 +-- .../PokemonExplorer/LeagueStatsList.tsx | 134 ++++++++++++++++++ .../PokemonExplorer/PokemonExplorer.tsx | 66 ++++++--- .../app/models/LevelMultipliers.ts} | 4 +- src/ts/app/utils/calculator.ts | 15 ++ 5 files changed, 208 insertions(+), 28 deletions(-) create mode 100644 src/ts/app/components/PokemonExplorer/LeagueStatsList.tsx rename src/{db/cpMultipliers.json => ts/app/models/LevelMultipliers.ts} (97%) create mode 100644 src/ts/app/utils/calculator.ts diff --git a/generatePokemonData.ts b/generatePokemonData.ts index 8031bba..07a6fd3 100644 --- a/generatePokemonData.ts +++ b/generatePokemonData.ts @@ -1,6 +1,8 @@ import * as fs from 'fs'; import Pokemon from 'pokemongo-json-pokedex/output/pokemon.json'; -import { ILeaguePokemon, IPokemon, IStats, League, Grade } from 'app/models/Pokemon'; + +import { LevelMultipliers } from 'app/models/LevelMultipliers'; +import { Grade, ILeaguePokemon, IPokemon, IStats, League } from 'app/models/Pokemon'; type ICpAndTotalFound = Record>; interface IStatsDistribution { @@ -19,11 +21,10 @@ const maxCpByLeague : IMaxCpByLeague = { ultra: 2500 }; -const cpMultipliers : Array = JSON.parse(fs.readFileSync('src/db/cpMultipliers.json', 'utf8')); const getClosestCpMultiplierIndex = (value : number) => { let i; - for (i = 0; i < cpMultipliers.length; i++) { - if (value < cpMultipliers[i]) { + for (i = 0; i < LevelMultipliers.length; i++) { + if (value < LevelMultipliers[i]) { break; } } @@ -100,7 +101,7 @@ Pokemon.forEach((mon) => { const league = key as League; const maxCp = maxCpByLeague[league]; const maxLeagueLevelMultiplierIndex = getClosestCpMultiplierIndex(Math.sqrt((maxCp * 10) / cpMultiplier)); - const maxLeagueLevelMultiplier = cpMultipliers[maxLeagueLevelMultiplierIndex]; + const maxLeagueLevelMultiplier = LevelMultipliers[maxLeagueLevelMultiplierIndex]; const maxLeagueCp = ~~((cpMultiplier * Math.pow(maxLeagueLevelMultiplier, 2)) / 10); const maxLeagueLevel = (maxLeagueLevelMultiplierIndex + 2) / 2; pokemonWithIvs = { @@ -130,7 +131,7 @@ Pokemon.forEach((mon) => { Object.keys(pokemon.pvp).forEach((key) => { const league = key as League; - const orderedCombinedStats = Object.keys(combinedStatsDistribution[league]).map(cpTotal => parseInt(cpTotal)).sort(); + const orderedCombinedStats = Object.keys(combinedStatsDistribution[league]).map((cpTotal) => parseInt(cpTotal, 10)).sort(); const len = orderedCombinedStats.length - 1; const offset = orderedCombinedStats[1]; const max = orderedCombinedStats[len] - offset; // index 0 is always `Grade.S` @@ -177,7 +178,7 @@ Pokemon.forEach((mon) => { fs.writeFile(outPath + mon.id + '.json', JSON.stringify(pokemon), (err) => { if (err) { /* tslint:disable-next-line:no-console */ - return console.log(mon.name, err); + return console.error(mon.name, err); } }); }); @@ -205,7 +206,7 @@ fs.mkdir(outPath, {recursive: true}, () => { fs.writeFile(outPath + 'order.json', JSON.stringify(pokemonOrder), (err) => { if (err) { /* tslint:disable-next-line:no-console */ - return console.log('order', err); + return console.error('order', err); } }); }); diff --git a/src/ts/app/components/PokemonExplorer/LeagueStatsList.tsx b/src/ts/app/components/PokemonExplorer/LeagueStatsList.tsx new file mode 100644 index 0000000..f44c0ce --- /dev/null +++ b/src/ts/app/components/PokemonExplorer/LeagueStatsList.tsx @@ -0,0 +1,134 @@ +import React from 'react'; +import { ContentRect, default as Measure } from 'react-measure'; +import { FixedSizeList } from 'react-window'; + +import classNames from 'classnames'; + +import { Grade, IStats } from 'app/models/Pokemon'; +import { IIndividualValues } from './types'; + +export interface ILeagueStatsListProps { + activeIndividualValues : IIndividualValues; + leagueStatsList : Array; + + handleActivateLeagueStats : (stats : IStats) => void; +} + +interface IState { + activeIndex : number; + dimensions : { + width : number; + height : number; + }; +} + +interface IRowFactory { + index : number; + style : React.CSSProperties; +} + +export class LeagueStatsList extends React.Component { + private listRef : React.RefObject; + + constructor(props : ILeagueStatsListProps) { + super(props); + + this.state = { + activeIndex: -1, + dimensions: { + width: -1, + height: -1, + } + }; + + this.listRef = React.createRef(); + } + + public componentWillReceiveProps(nextProps : ILeagueStatsListProps) { + const activeIvs = nextProps.activeIndividualValues; + if (activeIvs.level !== null && activeIvs.hp !== null && activeIvs.atk !== null && activeIvs.def !== null) { + let activeIndex = this.state.activeIndex; + if (activeIndex === -1) { + // there is no current index, wich means the user is entering in the IVs by hand + // now we will look through the list to see if we can find a match + this.props.leagueStatsList.some((stats, index) => { + if (activeIvs.hp === stats.ivHp && + activeIvs.atk === stats.ivAtk && + activeIvs.def === stats.ivDef + ) { + this.setState({ activeIndex: index }); + activeIndex = index; + return true; + } + return false; + }); + } + + if (activeIndex > -1) { + const stateStats = this.props.leagueStatsList[activeIndex]; + if (activeIvs.hp === stateStats.ivHp && activeIvs.atk === stateStats.ivAtk && activeIvs.def === stateStats.ivDef) { + // the current IVs belong to the stats at the active index, so scroll to active stats + if (this.listRef.current !== null) { + this.listRef.current.scrollToItem(activeIndex, 'center'); + } + } else { + // the current IVs have changed since we last scrolled to the active stats, so unset the active index + this.setState({ activeIndex: -1 }); + } + } + } else { + this.setState({ activeIndex: -1 }); + } + } + + public render() { + const { width, height } = this.state.dimensions; + const onResize = (contentRect : ContentRect) => { + if (typeof contentRect.bounds !== 'undefined') { + this.setState({ dimensions: contentRect.bounds }); + } + }; + + return ( +
+ + { + ({ measureRef }) => ( +
+ + { this.rowFactory.bind(this) } + +
+ ) + } +
+
+ ); + } + + private rowFactory({ index, style } : IRowFactory) { + const activeIvs = this.props.activeIndividualValues; + const stats = this.props.leagueStatsList[index]; + const css = classNames({ + active: activeIvs.level === stats.level && + activeIvs.hp === stats.ivHp && + activeIvs.atk === stats.ivAtk && + activeIvs.def === stats.ivDef, + highlight: this.state.activeIndex === index, + }); + const onClick = () => { + this.props.handleActivateLeagueStats(stats); + this.setState({ activeIndex: index }); + }; + return
{ `${ Grade[stats.speciesGrade] } ${ stats.cp } ${ stats.level } ${ stats.ivHp } ${ stats.ivAtk } ${ stats.ivDef }` }
; + } +} diff --git a/src/ts/app/components/PokemonExplorer/PokemonExplorer.tsx b/src/ts/app/components/PokemonExplorer/PokemonExplorer.tsx index 96c7fc1..eede277 100644 --- a/src/ts/app/components/PokemonExplorer/PokemonExplorer.tsx +++ b/src/ts/app/components/PokemonExplorer/PokemonExplorer.tsx @@ -1,9 +1,12 @@ import React from 'react'; import { Grade, ILeaguePokemon, IStats } from 'app/models/Pokemon'; +import { calculateCp, calculateStatAtLevel } from 'app/utils/calculator'; import { IIndividualValues, IndividualValueKey } from './types'; +import { LeagueStatsList } from './LeagueStatsList'; + export interface IPokemonExplorerProps { isLoading : boolean; leaguePokemon : ILeaguePokemon; @@ -24,9 +27,9 @@ export class PokemonExplorer extends React.Component) => void; - private onChangeAtk : (event : React.ChangeEvent) => void; - private onChangeDef : (event : React.ChangeEvent) => void; + private handleChangeHp : (event : React.ChangeEvent) => void; + private handleChangeAtk : (event : React.ChangeEvent) => void; + private handleChangeDef : (event : React.ChangeEvent) => void; constructor(props : IPokemonExplorerProps) { super(props); @@ -37,9 +40,9 @@ export class PokemonExplorer extends React.Component @@ -136,8 +152,8 @@ export class PokemonExplorer extends React.Component HP @@ -148,8 +164,8 @@ export class PokemonExplorer extends React.Component ATK @@ -160,8 +176,8 @@ export class PokemonExplorer extends React.Component DEF @@ -174,6 +190,11 @@ export class PokemonExplorer extends React.Component{ rankedAtk } ATK
{ rankedDef } DEF
+ ); } @@ -189,7 +210,7 @@ export class PokemonExplorer extends React.Component) => { + private readonly handleChangeLevel = (event : React.ChangeEvent) => { const raw = event.currentTarget.value; const value = parseFloat(raw); @@ -203,7 +224,7 @@ export class PokemonExplorer extends React.Component { + private readonly handleChangeIvFactory = (type : IndividualValueKey) => { return (event : React.ChangeEvent) => { const raw = event.currentTarget.value; const value = parseInt(raw, 10); @@ -214,4 +235,13 @@ export class PokemonExplorer extends React.Component { + const { handleChangeIndividualValue } = this.props; + + handleChangeIndividualValue('level', stats.level); + handleChangeIndividualValue('hp', stats.ivHp); + handleChangeIndividualValue('atk', stats.ivAtk); + handleChangeIndividualValue('def', stats.ivDef); + } } diff --git a/src/db/cpMultipliers.json b/src/ts/app/models/LevelMultipliers.ts similarity index 97% rename from src/db/cpMultipliers.json rename to src/ts/app/models/LevelMultipliers.ts index 73b1546..854590b 100644 --- a/src/db/cpMultipliers.json +++ b/src/ts/app/models/LevelMultipliers.ts @@ -1,4 +1,4 @@ -[ +export const LevelMultipliers = [ 0.094, 0.135137432, 0.16639787, @@ -78,4 +78,4 @@ 0.78463697, 0.787473578, 0.79030001 -] \ No newline at end of file +]; diff --git a/src/ts/app/utils/calculator.ts b/src/ts/app/utils/calculator.ts new file mode 100644 index 0000000..886429c --- /dev/null +++ b/src/ts/app/utils/calculator.ts @@ -0,0 +1,15 @@ +import { LevelMultipliers } from 'app/models/LevelMultipliers'; +import { IBaseStats } from 'app/models/Pokemon'; + +export const calculateCp = (baseStats : IBaseStats, level : number, ivHp : number, ivAtk : number, ivDef : number) => { + const statsFormula = Math.sqrt(baseStats.baseStamina + ivHp) * Math.sqrt(baseStats.baseDefense + ivDef) * (baseStats.baseAttack + ivAtk); + const levelMultiplier = LevelMultipliers[(level - 1) * 2]; + const cp = Math.floor((statsFormula * Math.pow(levelMultiplier, 2)) / 10); + return cp; +}; + +export const calculateStatAtLevel = (level : number, baseStatValue : number, ivStat : number) => { + const levelMultiplier = LevelMultipliers[(level - 1) * 2]; + const calculatedStat = Math.floor((baseStatValue + ivStat) * levelMultiplier); + return calculatedStat; +};