prepare for league switching

This commit is contained in:
Jeff Colombo 2019-02-05 22:56:35 -05:00
parent 0abda4387f
commit a639613694
21 changed files with 386 additions and 244 deletions

View File

@ -2,17 +2,16 @@ import * as fs from 'fs';
import PokemonDescription from 'pokemongo-json-pokedex/output/locales/en-US/pokemon.json';
import Pokemon from 'pokemongo-json-pokedex/output/pokemon.json';
import { ILeaguePokemon, IMaxCpByLeague, League, MaxCpByLeague } from 'app/models/League';
import { LevelMultipliers } from 'app/models/LevelMultipliers';
import { Grade, IBaseStatsRank, ILeaguePokemon, IMaxStats, IPokemon, IStats, League, PokemonId, Type } from 'app/models/Pokemon';
import { Grade, IBaseStatsRank, IMaxStats, IPokemon, IStats, PokemonId, Type } from 'app/models/Pokemon';
type ICpAndTotalFound = Record<number, Array<IStats>>;
interface IStatsDistribution {
great : ICpAndTotalFound;
ultra : ICpAndTotalFound;
}
interface IMaxCpByLeague {
great : number;
ultra : number;
master : ICpAndTotalFound;
custom : ICpAndTotalFound;
}
interface ICalculateRelativeStats {
id : string;
@ -21,11 +20,6 @@ interface ICalculateRelativeStats {
const outPath = './dist/db/';
const maxCpByLeague : IMaxCpByLeague = {
great: 1500,
ultra: 2500
};
const maxPossibleStats : IMaxStats = {
baseStamina: 0,
baseAttack: 0,
@ -140,6 +134,8 @@ Pokemon.forEach((mon) => {
pvp: {
great: [],
ultra: [],
master: [],
custom: [],
},
};
@ -183,6 +179,8 @@ Pokemon.forEach((mon) => {
const combinedStatsDistribution : IStatsDistribution = {
great: {},
ultra: {},
master: {},
custom: {},
};
for (let ivHp = 15; ivHp >= 0; ivHp--) {
for (let ivAtk = 15; ivAtk >= 0; ivAtk--) {
@ -190,9 +188,9 @@ Pokemon.forEach((mon) => {
let pokemonWithIvs : IStats;
const cpMultiplier = (baseAtk + ivAtk) * Math.sqrt(baseDef + ivDef) * Math.sqrt(baseHp + ivHp);
Object.keys(maxCpByLeague).forEach((key) => {
Object.keys(MaxCpByLeague).forEach((key) => {
const league = key as League;
const maxCp = maxCpByLeague[league];
const maxCp = MaxCpByLeague[league];
const maxLeagueLevelMultiplierIndex = getClosestCpMultiplierIndex(Math.sqrt((maxCp * 10) / cpMultiplier));
const maxLeagueLevelMultiplier = LevelMultipliers[maxLeagueLevelMultiplierIndex];
const maxLeagueCp = Math.floor((cpMultiplier * Math.pow(maxLeagueLevelMultiplier, 2)) / 10);

View File

@ -46,40 +46,124 @@ $main-border-color: $gray-scale-4;
$main-hover-color: darken($main-background-color, 5%);
$great-league-color: #4d6ed0;
$ultra-league-color: #5e30a3;
$master-league-color: #475b5e;
$custom-league-color: #f00;
$league-colors: (
great: (
primary: $great-league-color,
border: $nes-dark,
hover: lighten($great-league-color, 10),
// active: #355aca,
blur: lighten($great-league-color, 30),
blur-border: lighten($nes-dark, 10),
shadow: #1a3ea7,
contrast: #fff
),
ultra: (
primary: $ultra-league-color,
border: $nes-dark,
hover: lighten($ultra-league-color, 10),
// active: #50298b,
blur: lighten($ultra-league-color, 30),
blur-border: lighten($nes-dark, 10),
shadow: #342085,
contrast: #fff
),
master: (
primary: $master-league-color,
border: $nes-dark,
hover: lighten($master-league-color, 10),
// active: #3a4a4d,
blur: lighten($master-league-color, 30),
blur-border: lighten($nes-dark, 10),
shadow: #2f4947,
contrast: #fff
),
custom: (
primary: $custom-league-color,
border: $nes-dark,
hover: lighten($custom-league-color, 10),
blur: lighten($custom-league-color, 30),
blur-border: lighten($nes-dark, 10),
shadow: darkred,
contrast: #fff
)
);
// https://bulbapedia.bulbagarden.net/wiki/Category:Type_color_templates
$normal-primary: #a8a878;
$normal-contrast: #fff;
$fire-primary: #f08030;
$fire-contrast: #fff;
$fighting-primary: #c03028;
$fighting-contrast: #fff;
$water-primary: #6890f0;
$water-contrast: #fff;
$flying-primary: #a890f0;
$flying-contrast: #fff;
$grass-primary: #78c850;
$grass-contrast: #fff;
$poison-primary: #a040a0;
$poison-contrast: #fff;
$electric-primary: #f8d030;
$electric-contrast: #fff;
$ground-primary: #e0c068;
$ground-contrast: #fff;
$psychic-primary: #f85888;
$psychic-contrast: #fff;
$rock-primary: #b8a038;
$rock-contrast: #fff;
$ice-primary: #98d8d8;
$ice-contrast: #fff;
$bug-primary: #a8b820;
$bug-contrast: #fff;
$dragon-primary: #6f35fc;
$dragon-contrast: #fff;
$ghost-primary: #705898;
$ghost-contrast: #fff;
$dark-primary: #705848;
$dark-contrast: #fff;
$steel-primary: #b8b8d0;
$steel-contrast: #fff;
$fairy-primary: #ee99ac;
$fairy-contrast: #fff;
$type-colors: (
normal: (
primary: #a8a878,
contrast: #fff
),
fire: (
primary: #f08030,
contrast: #fff
),
fighting: (
primary: #c03028,
contrast: #fff
),
water: (
primary: #6890f0,
contrast: #fff
),
flying: (
primary: #a890f0,
contrast: #fff
),
grass: (
primary: #78c850,
contrast: #fff
),
poison: (
primary: #a040a0,
contrast: #fff
),
electric: (
primary: #f8d030,
contrast: #fff
),
ground: (
primary: #e0c068,
contrast: #fff
),
psychic: (
primary: #f85888,
contrast: #fff
),
rock: (
primary: #b8a038,
contrast: #fff
),
ice: (
primary: #98d8d8,
contrast: #fff
),
bug: (
primary: #a8b820,
contrast: #fff
),
dragon: (
primary: #6f35fc,
contrast: #fff
),
ghost: (
primary: #705898,
contrast: #fff
),
dark: (
primary: #705848,
contrast: #fff
),
steel: (
primary: #b8b8d0,
contrast: #fff
),
fairy: (
primary: #ee99ac,
contrast: #fff
)
);

View File

@ -1,7 +1,8 @@
import { AjaxUtils } from 'api/AjaxUtils';
import { IConfig } from 'app/models/Config';
import { ILeaguePokemon, IPokemon, League } from 'app/models/Pokemon';
import { ILeaguePokemon, League } from 'app/models/League';
import { IPokemon } from 'app/models/Pokemon';
interface IPokemonJSON extends IPokemon {}
interface ILeaguePokemonJSON extends ILeaguePokemon {}

View File

@ -1,6 +1,8 @@
import React from 'react';
import { connect } from 'react-redux';
import { League } from 'app/models/League';
import { appReducers } from './index';
import * as ActionsPokemonExplorer from './components/PokemonExplorer/actions';
@ -43,6 +45,7 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps> {
filterTerm,
} = this.props.pokemonSelectListState;
const {
league,
individualValues,
leaguePokemon,
} = this.props.pokemonExplorerState;
@ -60,10 +63,12 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps> {
{ leaguePokemon !== null &&
<PokemonExplorer
isLoading={ this.props.pokemonExplorerState.isLoading }
activeLeague={ league }
leaguePokemon={ leaguePokemon }
individualValues={ individualValues }
handleChangeIndividualValue={ this.handleChangeIndividualValue }
handleMaximizeLevel={ this.handleMaximizeLevel }
handleChangeLeague={ this.handleChangeLeague }
/>
}
</div>
@ -118,6 +123,10 @@ class PokemonApp extends React.Component<IConnectedPokemonAppProps> {
private readonly handleMaximizeLevel = () => {
this.props.dispatch(ActionsPokemonExplorer.maximizeLevel());
}
private readonly handleChangeLeague = (league : League) => {
this.props.dispatch(ActionsPokemonExplorer.setActiveLeague(league));
}
}
const mapStateToProps = (state : ReturnType<typeof appReducers>) : PokemonAppProps => {

View File

@ -0,0 +1,54 @@
import React from 'react';
import classNames from 'classnames';
import { League, LeagueLabels } from 'app/models/League';
import * as styles from './styles/LeagueSelector.scss';
export interface ILeagueSelectorProps {
activeLeague : League;
handleLeagueSelect : (league : League) => void;
}
export class LeagueSelector extends React.Component<ILeagueSelectorProps, object> {
public render() {
const { activeLeague } = this.props;
const css = classNames(
);
return (
<div className={ styles.wrapper }>
{
LeagueLabels.map((league, index) => {
const buttonCss = classNames(
'nes-btn',
{
[ styles.greatLeagueButton ]: league.id === 'great',
[ styles.ultraLeagueButton ]: league.id === 'ultra',
[ styles.masterLeagueButton ]: league.id === 'master',
[ styles.customLeagueButton ]: league.id === 'custom',
[ styles.active ]: activeLeague === league.id,
}
);
return <button
key={ league.id + index }
className={ buttonCss }
onClick={ this.handleLeagueSelectFactory(league.id) }
>
{ league.label }
</button>;
})
}
</div>
);
}
private readonly handleLeagueSelectFactory = (league : League) => {
return () => {
this.props.handleLeagueSelect(league);
};
}
}

View File

@ -2,12 +2,14 @@ import React from 'react';
import classNames from 'classnames';
import { Grade, ILeaguePokemon, IMaxStats, IStats, } from 'app/models/Pokemon';
import { ILeaguePokemon, League } from 'app/models/League';
import { Grade, IStats, } from 'app/models/Pokemon';
import { calculateCp, calculateStatAtLevel } from 'app/utils/calculator';
import { formatDexNumber } from 'app/utils/formatter';
import { IIndividualValues, IndividualValueKey } from './types';
import { LeagueSelector } from './LeagueSelector';
import { LeagueStatsList } from './LeagueStatsList';
import { StatDisplay } from './StatDisplay';
@ -15,11 +17,13 @@ import * as styles from './styles/PokemonExplorer.scss';
export interface IPokemonExplorerProps {
isLoading : boolean;
activeLeague : League;
leaguePokemon : ILeaguePokemon;
individualValues : IIndividualValues;
handleChangeIndividualValue : (stat : IndividualValueKey, value : number | null) => void;
handleMaximizeLevel : () => void;
handleChangeLeague : (league : League) => void;
}
interface IState {
@ -54,6 +58,7 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
public render() {
const {
activeLeague,
individualValues,
leaguePokemon
} = this.props;
@ -236,6 +241,10 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
</section>
</div>
</div>
<LeagueSelector
activeLeague={ activeLeague }
handleLeagueSelect={ this.handleLeagueSelect }
/>
<section className={ formContainerCss }>
<h5 className={ containerTitleCss }>IVs</h5>
<div className={ inlineFieldCss }>
@ -379,4 +388,8 @@ export class PokemonExplorer extends React.Component<IPokemonExplorerProps, ISta
handleChangeIndividualValue('atk', stats.ivAtk);
handleChangeIndividualValue('def', stats.ivDef);
}
private readonly handleLeagueSelect = (league : League) => {
this.props.handleChangeLeague(league);
}
}

View File

@ -2,7 +2,7 @@ import React from 'react';
import classNames from 'classnames';
import * as styles from './styles/PokemonExplorer.scss';
import * as styles from './styles/StatDisplay.scss';
export interface IStartDisplayProps {
statLabel : string;

View File

@ -5,7 +5,8 @@ import { PokemonExplorerActionTypes } from './types';
import { calculateMaxLevelForLeague } from 'app/utils/calculator';
import { ILeaguePokemon, IMaxStats } from 'app/models/Pokemon';
import { ILeaguePokemon, League } from 'app/models/League';
import { IMaxStats } from 'app/models/Pokemon';
export const setIsLoading = (isLoading : boolean) => action(PokemonExplorerActionTypes.SET_IS_LOADING, { isLoading });
@ -21,6 +22,8 @@ export const setIvAtk = (atk : number | null) => action(PokemonExplorerActionTyp
export const setIvDef = (def : number | null) => action(PokemonExplorerActionTypes.SET_IV_DEF, { def });
export const setActiveLeague = (league : League) => action(PokemonExplorerActionTypes.SET_ACTIVE_LEAGUE, { league });
export const fetchConfig = (
) : ThunkResult<Promise<void>> => {
return async (dispatch, getState, extraArguments) => {

View File

@ -91,6 +91,14 @@ const reduceSetIvDef = (
}
});
const reduceSetActiveLeague = (
state : IPokemonExplorerState,
action : ReturnType<typeof Actions.setActiveLeague>
) : IPokemonExplorerState => ({
...state,
league: action.payload.league
});
export const PokemonExplorerReducers : Reducer<IPokemonExplorerState> = (
state : IPokemonExplorerState = initialState,
action,
@ -110,6 +118,8 @@ export const PokemonExplorerReducers : Reducer<IPokemonExplorerState> = (
return reduceSetIvAtk(state, action as ReturnType<typeof Actions.setIvAtk>);
case PokemonExplorerActionTypes.SET_IV_DEF:
return reduceSetIvDef(state, action as ReturnType<typeof Actions.setIvDef>);
case PokemonExplorerActionTypes.SET_ACTIVE_LEAGUE:
return reduceSetActiveLeague(state, action as ReturnType<typeof Actions.setActiveLeague>);
default:
return state;
}

View File

@ -0,0 +1,76 @@
@import 'styles/Variables.scss';
@each $league, $colors in $league-colors {
$blur: map-get($colors, 'blur');
$hover: map-get($colors, 'hover');
$primary: map-get($colors, 'primary');
$shadow: map-get($colors, 'shadow');
$border: map-get($colors, 'border');
$blur-border: map-get($colors, 'blur-border');
$contrast: map-get($colors, 'contrast');
:global(.nes-btn).#{$league}LeagueButton {
color: $contrast;
background-color: $blur;
box-shadow: none;
// box-shadow: inset -4px -4px darken(saturate(adjust-hue($primary, -12deg), 7), 9);
// box-shadow: inset -4px -4px darken(saturate($primary, 14), 18);
&::before,
&::after {
border-color: $blur-border;
}
&:hover,
&:focus {
background-color: $hover;
box-shadow: inset -4px -4px $shadow;
&:active {
box-shadow: inset 4px 4px $shadow;
}
&::before,
&::after {
border-color: $border;
}
}
&.active {
background-color: $primary;
box-shadow: inset -4px -4px $shadow;
&:hover,
&:focus {
box-shadow: inset -6px -6px $shadow;
}
&:active {
box-shadow: inset 4px 4px $shadow;
}
&::before,
&::after {
border-color: $border;
}
}
}
}
// TODO: above won't generate an scss file automatically...
/* stylelint-disable block-no-empty */
.greatLeagueButton,
.ultraLeagueButton,
.masterLeagueButton,
.customLeagueButton {
font-size: 1em;
}
/* stylelint-enable block-no-empty */
.wrapper {
font-size: 0.8em;
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
}

View File

@ -0,0 +1,8 @@
// This file is automatically generated.
// Please do not change this file!
export const active: string;
export const customLeagueButton: string;
export const greatLeagueButton: string;
export const masterLeagueButton: string;
export const ultraLeagueButton: string;
export const wrapper: string;

View File

@ -76,165 +76,17 @@
box-shadow: 0 -4px $shadow-color, 0 -8px, 4px 0 $shadow-color, 4px -4px, 8px 0, 0 4px $shadow-color, 0 8px, -4px 0 $shadow-color, -4px 4px, -8px 0, -4px -4px, 4px 4px;
}
&:global(.normal) {
color: $normal-contrast;
background-color: $normal-primary;
@each $type, $colors in $type-colors {
$primary: map-get($colors, 'primary');
$contrast: map-get($colors, 'contrast');
&::after {
@include rounded-box-shadow($normal-primary);
}
}
&:global(.#{$type}) {
color: $contrast;
background-color: $primary;
&:global(.fire) {
color: $fire-contrast;
background-color: $fire-primary;
&::after {
@include rounded-box-shadow($fire-primary);
}
}
&:global(.fighting) {
color: $fighting-contrast;
background-color: $fighting-primary;
&::after {
@include rounded-box-shadow($fighting-primary);
}
}
&:global(.water) {
color: $water-contrast;
background-color: $water-primary;
&::after {
@include rounded-box-shadow($water-primary);
}
}
&:global(.flying) {
color: $flying-contrast;
background-color: $flying-primary;
&::after {
@include rounded-box-shadow($flying-primary);
}
}
&:global(.grass) {
color: $grass-contrast;
background-color: $grass-primary;
&::after {
@include rounded-box-shadow($grass-primary);
}
}
&:global(.poison) {
color: $poison-contrast;
background-color: $poison-primary;
&::after {
@include rounded-box-shadow($poison-primary);
}
}
&:global(.electric) {
color: $electric-contrast;
background-color: $electric-primary;
&::after {
@include rounded-box-shadow($electric-primary);
}
}
&:global(.ground) {
color: $ground-contrast;
background-color: $ground-primary;
&::after {
@include rounded-box-shadow($ground-primary);
}
}
&:global(.psychic) {
color: $psychic-contrast;
background-color: $psychic-primary;
&::after {
@include rounded-box-shadow($psychic-primary);
}
}
&:global(.rock) {
color: $rock-contrast;
background-color: $rock-primary;
&::after {
@include rounded-box-shadow($rock-primary);
}
}
&:global(.ice) {
color: $ice-contrast;
background-color: $ice-primary;
&::after {
@include rounded-box-shadow($ice-primary);
}
}
&:global(.bug) {
color: $bug-contrast;
background-color: $bug-primary;
&::after {
@include rounded-box-shadow($bug-primary);
}
}
&:global(.dragon) {
color: $dragon-contrast;
background-color: $dragon-primary;
&::after {
@include rounded-box-shadow($dragon-primary);
}
}
&:global(.ghost) {
color: $ghost-contrast;
background-color: $ghost-primary;
&::after {
@include rounded-box-shadow($ghost-primary);
}
}
&:global(.dark) {
color: $dark-contrast;
background-color: $dark-primary;
&::after {
@include rounded-box-shadow($dark-primary);
}
}
&:global(.steel) {
color: $steel-contrast;
background-color: $steel-primary;
&::after {
@include rounded-box-shadow($steel-primary);
}
}
&:global(.fairy) {
color: $fairy-contrast;
background-color: $fairy-primary;
&::after {
@include rounded-box-shadow($fairy-primary);
&::after {
@include rounded-box-shadow($primary);
}
}
}
}
@ -254,23 +106,6 @@
align-self: stretch;
}
.baseStatRow {
display: flex;
align-items: center;
& > * {
flex-shrink: 0;
}
& > progress {
flex-shrink: 1;
margin-left: 1em;
width: 5em;
height: 0.5em;
padding: 2px;
}
}
:global(.nes-field.is-inline) .ivInput {
width: 4.25em;
padding-left: 0.7em;

View File

@ -1,6 +1,5 @@
// This file is automatically generated.
// Please do not change this file!
export const baseStatRow: string;
export const dexHeader: string;
export const fieldRow: string;
export const formHeader: string;

View File

@ -0,0 +1,16 @@
.baseStatRow {
display: flex;
align-items: center;
& > * {
flex-shrink: 0;
}
& > progress {
flex-shrink: 1;
margin-left: 1em;
width: 5em;
height: 0.5em;
padding: 2px;
}
}

View File

@ -0,0 +1,3 @@
// This file is automatically generated.
// Please do not change this file!
export const baseStatRow: string;

View File

@ -1,4 +1,5 @@
import { ILeaguePokemon, IMaxStats, League } from 'app/models/Pokemon';
import { ILeaguePokemon, League } from 'app/models/League';
import { IMaxStats } from 'app/models/Pokemon';
export type IndividualValueKey = 'level' | 'hp' | 'atk' | 'def';
export interface IIndividualValues {
@ -24,4 +25,5 @@ export const PokemonExplorerActionTypes = {
SET_IV_HP: 'POKEMON_EXPLORER/SET_IV_HP',
SET_IV_ATK: 'POKEMON_EXPLORER/SET_IV_ATK',
SET_IV_DEF: 'POKEMON_EXPLORER/SET_IV_DEF',
SET_ACTIVE_LEAGUE: 'POKEMON_EXPLORER/SET_ACTIVE_LEAGUE',
};

View File

@ -1,6 +1,7 @@
import { action } from 'typesafe-actions';
import { ILeaguePokemon, IPokemon } from 'app/models/Pokemon';
import { ILeaguePokemon } from 'app/models/League';
import { IPokemon } from 'app/models/Pokemon';
import { ThunkResult } from 'app/types';
import { PokemonSelectListActionTypes } from './types';

View File

@ -1,4 +1,5 @@
import { ILeaguePokemon, IPokemon } from 'app/models/Pokemon';
import { ILeaguePokemon } from 'app/models/League';
import { IPokemon } from 'app/models/Pokemon';
export interface IPokemonSelectListState {
isLoading : boolean;

View File

@ -0,0 +1,40 @@
import { IPokemon, IStats } from './Pokemon';
export type League = 'great' | 'ultra' | 'master' | 'custom';
export interface IMaxCpByLeague {
great : number;
ultra : number;
master : number;
custom : number;
}
export const MaxCpByLeague : IMaxCpByLeague = {
great: 1500,
ultra: 2500,
master: Infinity,
custom: Infinity,
};
export const LeagueLabels : Array<{ id : League, label : string }> = [{
id: 'great',
label: 'Great'
}, {
id: 'ultra',
label: 'Ultra'
}, {
id: 'master',
label: 'Master'
}, {
id: 'custom',
label: 'Custom'
}];
export interface ILeaguePokemon extends IPokemon {
pvp : {
great : Array<IStats>;
ultra : Array<IStats>;
master : Array<IStats>;
custom : Array<IStats>;
};
}

View File

@ -38,13 +38,6 @@ export interface IPokemon {
stats : IBaseStats;
statsRank : IBaseStatsRank;
}
export type League = 'great' | 'ultra';
export interface ILeaguePokemon extends IPokemon {
pvp : {
great : Array<IStats>;
ultra : Array<IStats>;
};
}
export interface IStats {
cp : number;

View File

@ -1,10 +1,6 @@
import { League, MaxCpByLeague } from 'app/models/League';
import { LevelMultipliers } from 'app/models/LevelMultipliers';
import { IBaseStats, League } from 'app/models/Pokemon';
const leagueMaxCp = {
great: 1500,
ultra: 1500
};
import { IBaseStats } from 'app/models/Pokemon';
const calculateStatsFormula = (baseStats : IBaseStats, ivHp : number, ivAtk : number, ivDef : number) => Math.sqrt(baseStats.baseStamina + ivHp) * Math.sqrt(baseStats.baseDefense + ivDef) * (baseStats.baseAttack + ivAtk);
@ -22,7 +18,7 @@ export const calculateStatAtLevel = (level : number, baseStatValue : number, ivS
};
export const calculateMaxLevelForLeague = (baseStats : IBaseStats, ivHp : number, ivAtk : number, ivDef : number, league : League) => {
const maxCp = leagueMaxCp[league];
const maxCp = MaxCpByLeague[league];
const statsFormula = calculateStatsFormula(baseStats, ivHp, ivAtk, ivDef);
let level = 1;
for (let i = LevelMultipliers.length - 1; i >= 0; i--) {