league stats list behavior
This commit is contained in:
parent
48718c51de
commit
e2b750048d
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
134
src/ts/app/components/PokemonExplorer/LeagueStatsList.tsx
Normal file
134
src/ts/app/components/PokemonExplorer/LeagueStatsList.tsx
Normal 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>;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
[
|
||||
export const LevelMultipliers = [
|
||||
0.094,
|
||||
0.135137432,
|
||||
0.16639787,
|
||||
@ -78,4 +78,4 @@
|
||||
0.78463697,
|
||||
0.787473578,
|
||||
0.79030001
|
||||
]
|
||||
];
|
||||
15
src/ts/app/utils/calculator.ts
Normal file
15
src/ts/app/utils/calculator.ts
Normal 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;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user