max level button

This commit is contained in:
Jeff Colombo 2019-02-02 11:38:34 -05:00
parent 44cf0e349c
commit e43c7edbf2
13 changed files with 126 additions and 24 deletions

View File

@ -3,7 +3,7 @@ import PokemonDescription from 'pokemongo-json-pokedex/output/locales/en-US/poke
import Pokemon from 'pokemongo-json-pokedex/output/pokemon.json'; import Pokemon from 'pokemongo-json-pokedex/output/pokemon.json';
import { LevelMultipliers } from 'app/models/LevelMultipliers'; import { LevelMultipliers } from 'app/models/LevelMultipliers';
import { Grade, ILeaguePokemon, IPokemon, IStats, League, PokemonIds, Type } from 'app/models/Pokemon'; import { Grade, ILeaguePokemon, IPokemon, IStats, League, PokemonId, Type } from 'app/models/Pokemon';
type ICpAndTotalFound = Record<number, Array<IStats>>; type ICpAndTotalFound = Record<number, Array<IStats>>;
interface IStatsDistribution { interface IStatsDistribution {
@ -65,9 +65,9 @@ Pokemon.forEach((mon) => {
const baseAtk = mon.stats.baseAttack; const baseAtk = mon.stats.baseAttack;
const baseDef = mon.stats.baseDefense; const baseDef = mon.stats.baseDefense;
const baseHp = mon.stats.baseStamina; const baseHp = mon.stats.baseStamina;
const pokemonDescription = typeof PokemonDescription[mon.id as PokemonIds] !== 'undefined' ? PokemonDescription[mon.id as PokemonIds] : {}; const pokemonDescription = typeof PokemonDescription[mon.id as PokemonId] !== 'undefined' ? PokemonDescription[mon.id as PokemonId] : {};
const pokemon : ILeaguePokemon = { const pokemon : ILeaguePokemon = {
id: mon.id, id: mon.id as PokemonId,
name: pokemonDescription.name || name || 'MissingNo.', name: pokemonDescription.name || name || 'MissingNo.',
category: pokemonDescription.category || '', category: pokemonDescription.category || '',
form, form,

View File

@ -36,6 +36,13 @@ body {
color: $main-font-primary-color; color: $main-font-primary-color;
} }
button.nes-btn:disabled,
button.nes-btn:disabled:hover {
background-color: $main-font-secondary-color;
color: $main-hover-color;
box-shadow: none;
}
a.active:not([href]):not([tabindex]) { a.active:not([href]):not([tabindex]) {
color: $main-active-font-color; color: $main-active-font-color;
} }
@ -89,5 +96,10 @@ a.list-item {
.nes-input, .nes-input,
.nes-textarea { .nes-textarea {
background-color: $main-background-color; background-color: $main-background-color;
outline-color: $main-font-secondary-color;
box-shadow: 0 4px $main-border-color, 0 -4px $main-border-color, 4px 0 $main-border-color, -4px 0 $main-border-color; box-shadow: 0 4px $main-border-color, 0 -4px $main-border-color, 4px 0 $main-border-color, -4px 0 $main-border-color;
&::placeholder {
color: $main-font-secondary-color;
}
} }

View File

@ -58,6 +58,7 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps> {
leaguePokemon={ leaguePokemon } leaguePokemon={ leaguePokemon }
individualValues={ individualValues } individualValues={ individualValues }
handleChangeIndividualValue={ this.handleChangeIndividualValue } handleChangeIndividualValue={ this.handleChangeIndividualValue }
handleMaximizeLevel={ this.handleMaximizeLevel }
/> />
} }
</div> </div>
@ -108,6 +109,10 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps> {
break; break;
} }
} }
private readonly handleMaximizeLevel = () => {
this.props.dispatch(ActionsPokemonExplorer.maximizeLevel());
}
} }
const mapStateToProps = (state : ReturnType<typeof appReducers>) : PokemonAppProps => { const mapStateToProps = (state : ReturnType<typeof appReducers>) : PokemonAppProps => {

View File

@ -4,12 +4,13 @@ import { FixedSizeList } from 'react-window';
import classNames from 'classnames'; import classNames from 'classnames';
import { Grade, IStats } from 'app/models/Pokemon'; import { Grade, IStats, PokemonId } from 'app/models/Pokemon';
import { IIndividualValues } from './types'; import { IIndividualValues } from './types';
import * as styles from './styles/LeagueStatsList.scss'; import * as styles from './styles/LeagueStatsList.scss';
export interface ILeagueStatsListProps { export interface ILeagueStatsListProps {
activePokemonId : PokemonId;
activeIndividualValues : IIndividualValues; activeIndividualValues : IIndividualValues;
leagueStatsList : Array<IStats>; leagueStatsList : Array<IStats>;
@ -48,7 +49,12 @@ export class LeagueStatsList extends React.Component<ILeagueStatsListProps, ISta
public componentWillReceiveProps(nextProps : ILeagueStatsListProps) { public componentWillReceiveProps(nextProps : ILeagueStatsListProps) {
const activeIvs = nextProps.activeIndividualValues; const activeIvs = nextProps.activeIndividualValues;
if (activeIvs.level !== null && activeIvs.hp !== null && activeIvs.atk !== null && activeIvs.def !== null) { if (nextProps.activePokemonId !== this.props.activePokemonId) {
this.setState({ activeIndex: -1 });
if (this.listRef.current !== null) {
this.listRef.current.scrollToItem(0);
}
} else if (activeIvs.level !== null && activeIvs.hp !== null && activeIvs.atk !== null && activeIvs.def !== null) {
let activeIndex = this.state.activeIndex; let activeIndex = this.state.activeIndex;
if (activeIndex === -1) { if (activeIndex === -1) {
// there is no current index, wich means the user is entering in the IVs by hand // there is no current index, wich means the user is entering in the IVs by hand

View File

@ -18,6 +18,7 @@ export interface IPokemonExplorerProps {
individualValues : IIndividualValues; individualValues : IIndividualValues;
handleChangeIndividualValue : (stat : IndividualValueKey, value : number | null) => void; handleChangeIndividualValue : (stat : IndividualValueKey, value : number | null) => void;
handleMaximizeLevel : () => void;
} }
interface IState { interface IState {
@ -160,6 +161,10 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
'nes-input', 'nes-input',
styles.ivInput styles.ivInput
); );
const inputTextLevelCss = classNames(
inputTextCss,
styles.levelInput
);
const leaugeRankCss = classNames( const leaugeRankCss = classNames(
styles.leaguePokemonRank, styles.leaguePokemonRank,
containerCss, containerCss,
@ -263,7 +268,7 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
name="level" name="level"
type="number" type="number"
id={ idIvLevelInput } id={ idIvLevelInput }
className={ inputTextCss } className={ inputTextLevelCss }
min={ this.MIN_LEVEL } min={ this.MIN_LEVEL }
max={ this.MAX_LEVEL } max={ this.MAX_LEVEL }
step={ 0.5 } step={ 0.5 }
@ -275,9 +280,10 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
<button <button
type="button" type="button"
className={ maxButtonCss } className={ maxButtonCss }
disabled={ false } disabled={ individualValues.hp === null || individualValues.atk === null || individualValues.def === null }
onClick={ this.handleClickMaximizeLevel }
> >
MAX Lv MAX LEAGUE Lv
</button> </button>
</div> </div>
</section> </section>
@ -290,6 +296,7 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
</section> </section>
</div> </div>
<LeagueStatsList <LeagueStatsList
activePokemonId={ leaguePokemon.id }
activeIndividualValues={ individualValues } activeIndividualValues={ individualValues }
leagueStatsList={ leaguePokemon.pvp[league] } leagueStatsList={ leaguePokemon.pvp[league] }
handleActivateLeagueStats={ this.handleActivateLeagueStats } handleActivateLeagueStats={ this.handleActivateLeagueStats }
@ -312,6 +319,10 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
} }
} }
private readonly handleClickMaximizeLevel = () => {
this.props.handleMaximizeLevel();
}
private readonly handleChangeIvFactory = (type : IndividualValueKey) => { private readonly handleChangeIvFactory = (type : IndividualValueKey) => {
return (event : React.ChangeEvent<HTMLInputElement>) => { return (event : React.ChangeEvent<HTMLInputElement>) => {
const raw = event.currentTarget.value; const raw = event.currentTarget.value;

View File

@ -1,7 +1,10 @@
import { action } from 'typesafe-actions'; import { action } from 'typesafe-actions';
import { ThunkResult } from 'app/types';
import { PokemonExplorerActionTypes } from './types'; import { PokemonExplorerActionTypes } from './types';
import { calculateMaxLevelForLeague } from 'app/utils/calculator';
import { ILeaguePokemon } from 'app/models/Pokemon'; import { ILeaguePokemon } from 'app/models/Pokemon';
export const setIsLoading = (isLoading : boolean) => action(PokemonExplorerActionTypes.SET_IS_LOADING, { isLoading }); export const setIsLoading = (isLoading : boolean) => action(PokemonExplorerActionTypes.SET_IS_LOADING, { isLoading });
@ -15,3 +18,35 @@ export const setIvHp = (hp : number | null) => action(PokemonExplorerActionTypes
export const setIvAtk = (atk : number | null) => action(PokemonExplorerActionTypes.SET_IV_ATK, { atk }); export const setIvAtk = (atk : number | null) => action(PokemonExplorerActionTypes.SET_IV_ATK, { atk });
export const setIvDef = (def : number | null) => action(PokemonExplorerActionTypes.SET_IV_DEF, { def }); export const setIvDef = (def : number | null) => action(PokemonExplorerActionTypes.SET_IV_DEF, { def });
export const maximizeLevel = (
) : ThunkResult<Promise<void>> => {
return async (dispatch, getState, extraArguments) => {
const pokemonExplorerState = getState().pokemonExplorerState;
const {
hp,
atk,
def,
} = pokemonExplorerState.individualValues;
if (pokemonExplorerState.leaguePokemon !== null) {
const pokemonLeagueValues = pokemonExplorerState.leaguePokemon.pvp[pokemonExplorerState.league];
const statsSet = pokemonLeagueValues.some((stats) => {
if (((hp === null) || (stats.ivHp === hp)) &&
((atk === null) || (stats.ivAtk === atk)) &&
((def === null) || (stats.ivDef === def))
) {
dispatch(setIvHp(stats.ivHp));
dispatch(setIvAtk(stats.ivAtk));
dispatch(setIvDef(stats.ivDef));
dispatch(setIvLevel(stats.level));
return true;
}
return false;
});
if (!statsSet && hp !== null && atk !== null && def !== null) {
dispatch(setIvLevel(calculateMaxLevelForLeague(pokemonExplorerState.leaguePokemon.stats, hp, atk, def, pokemonExplorerState.league)));
}
}
};
};

View File

@ -12,6 +12,7 @@ export const initialState : IPokemonExplorerState = {
atk: null, atk: null,
def: null, def: null,
}, },
league: 'great',
}; };
const reduceSetIsLoading = ( const reduceSetIsLoading = (

View File

@ -244,16 +244,24 @@
} }
:global(.nes-field.is-inline) .ivInput { :global(.nes-field.is-inline) .ivInput {
flex-grow: 0; width: 4.25em;
width: 3.75rem; padding-left: 0.7em;
padding-left: 0.5rem; padding-right: 0.5em;
padding-right: 0.5rem;
&.levelInput {
width: 6.5em;
}
} }
:global(.nes-field.is-inline).fieldRow { :global(.nes-field.is-inline).fieldRow {
justify-content: space-evenly; justify-content: space-between;
label { label {
flex-grow: 0; flex-grow: 0;
margin-left: 1em;
&:first-child {
margin-left: 0;
}
} }
} }

View File

@ -5,6 +5,7 @@ export const fieldRow: string;
export const formHeader: string; export const formHeader: string;
export const ivInput: string; export const ivInput: string;
export const leaguePokemonRank: string; export const leaguePokemonRank: string;
export const levelInput: string;
export const pokemonBaseStats: string; export const pokemonBaseStats: string;
export const pokemonInfoLeftColumn: string; export const pokemonInfoLeftColumn: string;
export const pokemonInfoRightColumn: string; export const pokemonInfoRightColumn: string;

View File

@ -1,4 +1,4 @@
import { ILeaguePokemon } from 'app/models/Pokemon'; import { ILeaguePokemon, League } from 'app/models/Pokemon';
export type IndividualValueKey = 'level' | 'hp' | 'atk' | 'def'; export type IndividualValueKey = 'level' | 'hp' | 'atk' | 'def';
export interface IIndividualValues { export interface IIndividualValues {
@ -12,6 +12,7 @@ export interface IPokemonExplorerState {
isLoading : boolean; isLoading : boolean;
leaguePokemon : ILeaguePokemon | null; leaguePokemon : ILeaguePokemon | null;
individualValues : IIndividualValues; individualValues : IIndividualValues;
league : League;
} }
export const PokemonExplorerActionTypes = { export const PokemonExplorerActionTypes = {

View File

@ -33,7 +33,7 @@ interface IRowFactory {
} }
export class PokemonSelectList extends React.Component<IPokemonSelectListProps, IState> { export class PokemonSelectList extends React.Component<IPokemonSelectListProps, IState> {
private listRef : VariableSizeList | null = null; private listRef : React.RefObject<VariableSizeList>;
constructor(props : IPokemonSelectListProps) { constructor(props : IPokemonSelectListProps) {
super(props); super(props);
@ -44,6 +44,8 @@ export class PokemonSelectList extends React.Component<IPokemonSelectListProps,
height: -1, height: -1,
} }
}; };
this.listRef = React.createRef();
} }
public render() { public render() {
@ -93,7 +95,7 @@ export class PokemonSelectList extends React.Component<IPokemonSelectListProps,
({ measureRef }) => ( ({ measureRef }) => (
<div ref={ measureRef }> <div ref={ measureRef }>
<VariableSizeList <VariableSizeList
ref={ this.setListRef } ref={ this.listRef }
height={ height } height={ height }
itemKey={ this.getListItemKey } itemKey={ this.getListItemKey }
itemCount={ listLength } itemCount={ listLength }
@ -119,8 +121,6 @@ export class PokemonSelectList extends React.Component<IPokemonSelectListProps,
); );
} }
private readonly setListRef = (element : VariableSizeList) => this.listRef = element;
private readonly getListItemKey = (index : number) => { private readonly getListItemKey = (index : number) => {
return index + this.props.pokemonList[index].id; return index + this.props.pokemonList[index].id;
} }
@ -166,8 +166,8 @@ export class PokemonSelectList extends React.Component<IPokemonSelectListProps,
private readonly handleChangeFilter = (event : React.ChangeEvent<HTMLInputElement>) => { private readonly handleChangeFilter = (event : React.ChangeEvent<HTMLInputElement>) => {
this.props.handleChangeFilter(event.currentTarget.value) this.props.handleChangeFilter(event.currentTarget.value)
.then(() => { .then(() => {
if (this.listRef !== null) { if (this.listRef.current !== null) {
this.listRef.resetAfterIndex(0, true); this.listRef.current.resetAfterIndex(0, true);
} }
}); });
} }

View File

@ -15,7 +15,7 @@ export interface IBaseStats {
export type Type = 'bug' | 'dark' | 'dragon' | 'electric' | 'fairy' | 'fighting' | 'fire' | 'flying' | 'ghost' | 'grass' | 'ground' | 'ice' | 'normal' | 'poison' | 'psychic' | 'rock' | 'steel' | 'water'; export type Type = 'bug' | 'dark' | 'dragon' | 'electric' | 'fairy' | 'fighting' | 'fire' | 'flying' | 'ghost' | 'grass' | 'ground' | 'ice' | 'normal' | 'poison' | 'psychic' | 'rock' | 'steel' | 'water';
export interface IPokemon { export interface IPokemon {
id : string; id : PokemonId;
name : string; name : string;
category : string; category : string;
form : string | null; form : string | null;
@ -49,7 +49,7 @@ export interface IStats {
metaGrade : Grade; metaGrade : Grade;
} }
export type PokemonIds = 'BULBASAUR' | 'IVYSAUR' | 'VENUSAUR' | 'CHARMANDER' | export type PokemonId = 'BULBASAUR' | 'IVYSAUR' | 'VENUSAUR' | 'CHARMANDER' |
'CHARMELEON' | 'CHARIZARD' | 'SQUIRTLE' | 'WARTORTLE' | 'BLASTOISE' | 'CHARMELEON' | 'CHARIZARD' | 'SQUIRTLE' | 'WARTORTLE' | 'BLASTOISE' |
'CATERPIE' | 'METAPOD' | 'BUTTERFREE' | 'WEEDLE' | 'KAKUNA' | 'BEEDRILL' | 'CATERPIE' | 'METAPOD' | 'BUTTERFREE' | 'WEEDLE' | 'KAKUNA' | 'BEEDRILL' |
'PIDGEY' | 'PIDGEOTTO' | 'PIDGEOT' | 'RATTATA' | 'RATTATA_ALOLA' | 'RATICATE' | 'PIDGEY' | 'PIDGEOTTO' | 'PIDGEOT' | 'RATTATA' | 'RATTATA_ALOLA' | 'RATICATE' |

View File

@ -1,8 +1,15 @@
import { LevelMultipliers } from 'app/models/LevelMultipliers'; import { LevelMultipliers } from 'app/models/LevelMultipliers';
import { IBaseStats } from 'app/models/Pokemon'; import { IBaseStats, League } from 'app/models/Pokemon';
const leagueMaxCp = {
great: 1500,
ultra: 1500
};
const calculateStatsFormula = (baseStats : IBaseStats, ivHp : number, ivAtk : number, ivDef : number) => Math.sqrt(baseStats.baseStamina + ivHp) * Math.sqrt(baseStats.baseDefense + ivDef) * (baseStats.baseAttack + ivAtk);
export const calculateCp = (baseStats : IBaseStats, level : number, ivHp : number, ivAtk : number, ivDef : number) => { 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 statsFormula = calculateStatsFormula(baseStats, ivHp, ivAtk, ivDef);
const levelMultiplier = LevelMultipliers[(level - 1) * 2]; const levelMultiplier = LevelMultipliers[(level - 1) * 2];
const cp = Math.floor((statsFormula * Math.pow(levelMultiplier, 2)) / 10); const cp = Math.floor((statsFormula * Math.pow(levelMultiplier, 2)) / 10);
return cp; return cp;
@ -13,3 +20,18 @@ export const calculateStatAtLevel = (level : number, baseStatValue : number, ivS
const calculatedStat = Math.floor((baseStatValue + ivStat) * levelMultiplier); const calculatedStat = Math.floor((baseStatValue + ivStat) * levelMultiplier);
return calculatedStat; return calculatedStat;
}; };
export const calculateMaxLevelForLeague = (baseStats : IBaseStats, ivHp : number, ivAtk : number, ivDef : number, league : League) => {
const maxCp = leagueMaxCp[league];
const statsFormula = calculateStatsFormula(baseStats, ivHp, ivAtk, ivDef);
let level = 1;
for (let i = LevelMultipliers.length - 1; i >= 0; i--) {
const levelMultiplier = LevelMultipliers[i];
const cp = Math.floor((statsFormula * Math.pow(levelMultiplier, 2)) / 10);
if (cp <= maxCp) {
level = (i / 2) + 1;
break;
}
}
return level;
};