league stats list behavior

This commit is contained in:
Jeff Colombo 2019-01-19 23:43:36 -05:00
parent 48718c51de
commit e2b750048d
5 changed files with 208 additions and 28 deletions

View File

@ -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<number, Array<IStats>>;
interface IStatsDistribution {
@ -19,11 +21,10 @@ const maxCpByLeague : IMaxCpByLeague = {
ultra: 2500
};
const cpMultipliers : Array<number> = 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);
}
});
});

View File

@ -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<IStats>;
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<ILeagueStatsListProps, IState> {
private listRef : React.RefObject<FixedSizeList>;
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 (
<div id="league-pokemon-select-list" style={ { height: '400px' } }>
<Measure
bounds={ true }
onResize={ onResize }
>
{
({ measureRef }) => (
<div ref={ measureRef } style={ { height: '100%' } }>
<FixedSizeList
ref={ this.listRef }
height={ height }
itemCount={ this.props.leagueStatsList.length }
itemSize={ 35 }
width={ width }
>
{ this.rowFactory.bind(this) }
</FixedSizeList>
</div>
)
}
</Measure>
</div>
);
}
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 <div key={ index } style={ style } className={ css } onClick={ onClick }>{ `${ Grade[stats.speciesGrade] } ${ stats.cp } ${ stats.level } ${ stats.ivHp } ${ stats.ivAtk } ${ stats.ivDef }` }</div>;
}
}

View File

@ -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<IPokemonExplorerProps, ISta
private readonly MIN_IV = 0;
private readonly MAX_IV = 15;
private onChangeHp : (event : React.ChangeEvent<HTMLInputElement>) => void;
private onChangeAtk : (event : React.ChangeEvent<HTMLInputElement>) => void;
private onChangeDef : (event : React.ChangeEvent<HTMLInputElement>) => void;
private handleChangeHp : (event : React.ChangeEvent<HTMLInputElement>) => void;
private handleChangeAtk : (event : React.ChangeEvent<HTMLInputElement>) => void;
private handleChangeDef : (event : React.ChangeEvent<HTMLInputElement>) => void;
constructor(props : IPokemonExplorerProps) {
super(props);
@ -37,9 +40,9 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
}
};
this.onChangeHp = this.onChangeIvFactory('hp');
this.onChangeAtk = this.onChangeIvFactory('atk');
this.onChangeDef = this.onChangeIvFactory('def');
this.handleChangeHp = this.handleChangeIvFactory('hp');
this.handleChangeAtk = this.handleChangeIvFactory('atk');
this.handleChangeDef = this.handleChangeIvFactory('def');
}
public render() {
@ -70,7 +73,7 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
placeholderDef = '' + rankedPokemon.ivDef;
// a full spec'd pokemon has been entered
} else if (individualValueLevel !== null &&
} else if (individualValueLevel !== null && typeof individualValueLevel === 'number' &&
individualValues.hp !== null &&
individualValues.atk !== null &&
individualValues.def !== null
@ -89,7 +92,20 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
// we don't have the data for this terrible mon
if (rankedPokemon === null) {
rankedPokemon = leaguePokemon.pvp[league][leaguePokemon.pvp[league].length - 1];
rankedPokemon = {
cp: calculateCp(leaguePokemon.stats, individualValueLevel, individualValues.hp, individualValues.atk, individualValues.def),
level: individualValueLevel,
ivHp: individualValues.hp,
ivAtk: individualValues.atk,
ivDef: individualValues.def,
hp: calculateStatAtLevel(individualValueLevel, leaguePokemon.stats.baseStamina, individualValues.hp),
atk: calculateStatAtLevel(individualValueLevel, leaguePokemon.stats.baseAttack, individualValues.atk),
def: calculateStatAtLevel(individualValueLevel, leaguePokemon.stats.baseDefense, individualValues.def),
total: 0,
speciesGrade: Grade.F,
metaGrade: Grade.F,
};
rankedPokemon.total = rankedPokemon.hp + rankedPokemon.atk + rankedPokemon.def;
}
}
@ -122,8 +138,8 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
min={ this.MIN_LEVEL }
max={ this.MAX_LEVEL }
step={ 0.5 }
onChange={ this.onChangeLevel }
value={ individualValueLevel || '' }
onChange={ this.handleChangeLevel }
value={ individualValueLevel !== null ? individualValueLevel : '' }
placeholder={ placeholderLevel }
/>
</div>
@ -136,8 +152,8 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
min={ this.MIN_IV }
max={ this.MAX_IV }
maxLength={ 2 }
onChange={ this.onChangeHp }
value={ individualValues.hp || '' }
onChange={ this.handleChangeHp }
value={ individualValues.hp !== null ? individualValues.hp : '' }
placeholder={ placeholderHp }
/> HP
</label>
@ -148,8 +164,8 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
min={ this.MIN_IV }
max={ this.MAX_IV }
maxLength={ 2 }
onChange={ this.onChangeAtk }
value={ individualValues.atk || '' }
onChange={ this.handleChangeAtk }
value={ individualValues.atk !== null ? individualValues.atk : '' }
placeholder={ placeholderAtk }
/> ATK
</label>
@ -160,8 +176,8 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
min={ this.MIN_IV }
max={ this.MAX_IV }
maxLength={ 2 }
onChange={ this.onChangeDef }
value={ individualValues.def || '' }
onChange={ this.handleChangeDef }
value={ individualValues.def !== null ? individualValues.def : '' }
placeholder={ placeholderDef }
/> DEF
</label>
@ -174,6 +190,11 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
<div className="league-pokemon-stat"><span>{ rankedAtk }</span> ATK</div>
<div className="league-pokemon-stat"><span>{ rankedDef }</span> DEF</div>
</div>
<LeagueStatsList
activeIndividualValues={ individualValues }
leagueStatsList={ leaguePokemon.pvp[league] }
handleActivateLeagueStats={ this.handleActivateLeagueStats }
/>
</div>
);
}
@ -189,7 +210,7 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
return prefix + dex;
}
private readonly onChangeLevel = (event : React.ChangeEvent<HTMLInputElement>) => {
private readonly handleChangeLevel = (event : React.ChangeEvent<HTMLInputElement>) => {
const raw = event.currentTarget.value;
const value = parseFloat(raw);
@ -203,7 +224,7 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
}
}
private readonly onChangeIvFactory = (type : IndividualValueKey) => {
private readonly handleChangeIvFactory = (type : IndividualValueKey) => {
return (event : React.ChangeEvent<HTMLInputElement>) => {
const raw = event.currentTarget.value;
const value = parseInt(raw, 10);
@ -214,4 +235,13 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
}
};
}
private readonly handleActivateLeagueStats = (stats : IStats) => {
const { handleChangeIndividualValue } = this.props;
handleChangeIndividualValue('level', stats.level);
handleChangeIndividualValue('hp', stats.ivHp);
handleChangeIndividualValue('atk', stats.ivAtk);
handleChangeIndividualValue('def', stats.ivDef);
}
}

View File

@ -1,4 +1,4 @@
[
export const LevelMultipliers = [
0.094,
0.135137432,
0.16639787,
@ -78,4 +78,4 @@
0.78463697,
0.787473578,
0.79030001
]
];

View File

@ -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;
};