Skip to content

Commit

Permalink
Fixed constraints, added copy buttons, added tutorial/usage information
Browse files Browse the repository at this point in the history
Fixed constraints, added copy buttons, added tutorial/usage information
  • Loading branch information
damon-murdoch committed Jan 6, 2024
1 parent 42ae35e commit 5976e73
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 65 deletions.
9 changes: 7 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,13 @@ <h2>Pokemon EV Spread Optimiser</h2>
Result
</div>
<small>
<btn id="paste-export" class="text-secondary btn-link">
Copy Spread
<btn id="copy-evs" class="text-secondary btn-link">
Copy EVs
</btn>
</small>
<small>
<btn id="copy-all" class="text-secondary btn-link">
Copy All
</btn>
</small>
</th>
Expand Down
13 changes: 7 additions & 6 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
### Created by Damon Murdoch ([@SirScrubbington](https://twitter.com/SirScrubbington))

## Introduction
The Spread Optimiser is a html/css/js application which given a Pokemon, Nature, IVs and the
minimum and maximum EV constraints calculates the base stat total of every possible EV spread
combination with the given constraints and provides them to a user in a table. The table is
able to be sorted by each stat, including overall base stat total. This was developed to allow
competitive Pokemon players to develop better optimised EV spreads, and is intended to
compliment spread development.
This tool is an EV Spread Optimiser for competitive Pokemon, where constraints can be applied
in an attempt to generate an ev spread which invests in relevant stats which provide the most
benefit overall. This is done by investing more in lower base-stats, and higher base stats are
generally boosted by nature and / or can be set to use a 'jump stat' (where 4/8 evs provide 2
stat points instead of 1). Speed can be constrained to a relevant benchmark (outspeeding jolly
252 base 100s, etc.) or can be customised to fit your own benchmarks. For more information on
how this tool should be used, please see [usage.md](./usage.md)

## Live Application
The web address for the live application can be accessed
Expand Down
83 changes: 83 additions & 0 deletions usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Spread Optimiser
## A Pokemon EV Spread Optimiser
### Created by Damon Murdoch ([@SirScrubbington](https://twitter.com/SirScrubbington))

## Instructions
This is not a hard-and-fast list of rules for using the application, just a general
process which I usually follow when using it.

1. Select Species

Using the `species` drop-down, select the Pokemon you'd like to use.

2. Select Nature

The nature will be pre-selected to boost whichever of their stats is highest,
and reduce whichever of their offensive stats is lowest. If you'd like to use a
different nature, please select it using the `nature` drop-down.

3. (Optional) Disable Attack / Special Attack Stat

If your Pokemon is strictly a special attacker, you will want to click on the
attack preset in the `presets` row. Click the option `0/0`, which will result in
0 atk evs and ivs. Likewise, you can select the special attack preset for physical
attackers and set it to option `0`, which will result in 0 special attack evs.

4. Select Speed Preset / Constrain Speed Stat

Click on the `preset` drop-down in the `spe` column. This will bring up the standard
set of pre-selectable options, as well as a new set at the bottom after `0/0`. These
are for relevant speed benchmarks such as outspeeding 252+ base 100s, neutral 252
base 90s, uninvested base 60s, etc. For a detailed list of speed tiers, please refer
to my [example speed tier document](https://github.com/damon-murdoch/pokemon-speed-tier-generator/blob/main/EXAMPLE.MD),
or another community speed tier list based on the format you are playing. Generally,
it is recommended to select a preset or benchmark for a specific Pokemon (or group of
Pokemon) you'd like to outspeed (e.g. Mega Charizard Y outspeeding Landorus-Therian by
1 point in 2015). Alternatively, if you have no relevant benchmarks but would like to
invest a few points for the mirror, you may consider choosing the lowest preset under
`0/0`, as this may allow you to outspeed some common Pokemon as well as potentially
providing a speed advantage in the mirror.

5. Select Boost Stat Preset / Constrain Boost Stat

If your boosted stat is `spe`, this is covered by the previous step and you can skip to `Step 6`.

Click on the `preset` drop-down in the column for whichever stat is boosted by the nature - for
example, `atk` with an `adamant` nature. This will bring up the standard set of pre-selectable
options, however there will be a new set at the bottom after `0/0`. These are the `jump stats`
for the Pokemon, where you earn 2 points instead of 1 per each 4/8 evs depending on level.
Generally, it is recommended to select a jump stat as the ev value for your nature boosted
stat, unless you need to deviate to achieve a specific calc.

6. (Optional) Select HP Stat Filter / Constrain HP Stat

If you do not have any specific limitations for your HP stat, or you are otherwise happy with
the spread as-is, you may skip this step.

Click on the `preset/filter` drop-down in the `hp` column, and review the options at the bottom
underneath `0`. These are filters which can be applied to your HP stat, e.g. `x/2` is only divisible
by 2, etc. For a table of HP stat filters and their usage, please see below.

| Filter | Effect | Usage |
| ------ | ------ | ----- |
| x/16 + 1 | Divisible by 16, plus one | Good `Leftovers` / `Grassy Terrain` recovery, brought to just over `50%` by `Super Fang` |
| x/16 | Divisible by 16 | Optimal `Leftovers` / `Grassy Terrain` recovery, brought to exactly `50%` health by `Super Fang` |
| x/10 + 1 | Divisible by 10, plus one | Allows you to attack with `Life Orb` 10 times, surviving with 1 hit point left |
| x/4 + 1 | Divisible by 4, plus one | Allows you to use `Substitute` four times, surviving with 1 hit point left |
| x/4 | Divisible by 4 | Recover exactly `25%` health using `Sitrus Berry`, `Super Fang` activates berry |
| x/2 + 1 | Divisible by 2, plus one | brought to just over `50%` by `Super Fang` |
| x/2 | Divisible by 2 | `Super Fang` activates berry |

7. (Optional) Review Output, apply additional constraints

If you are satisfied with the spread as-is, you may skip this step.

If you want to apply more specific constraints to one or more stats, you may increase or decrease a stat's upper
and lower bounds manually using the input fields `min evs` and `max evs` rows. You will see the spread update in
real-time as you update the constraints. Once satisfied, you may move on to the next step.

8. Export the spread

In the `results` row, you will be able to see the results of the finished spread. You may click `copy evs` to copy
the ev spread to the clipboard (e.g. `4 HP / 244 Atk / 4 Def / 4 SpD / 252 Spe`), or `copy all` to copy the entire
showdown set for the Pokemon - including species, evs, ivs and nature.
184 changes: 127 additions & 57 deletions util/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,24 @@ function updateField(f) {
// Dereference active Pokemon base stats
let bs = active.baseStats;

// Integer contained in the webpage maximum EV input field for the field
max_ev = parseInt(document.getElementById(f + "-max").value);
// Min/Max EV input field for the fields
const max = document.getElementById(f + "-max");
const min = document.getElementById(f + "-min");

// Integer contained in the webpage minimum EV input field for the field
min_ev = parseInt(document.getElementById(f + "-min").value);
// Parse int from max input field
let max_ev = parseInt(max.value);

// Parse int from min input field
let min_ev = parseInt(min.value);

// Max is less than min
if (max_ev < min_ev) {
// Set max to min
max.value = min.value;

// Refresh max_ev
max_ev = min_ev;
}

// Integer contained in the webpage level input field
level = parseInt(document.getElementById("level").value);
Expand Down Expand Up @@ -589,80 +602,137 @@ function changePokemonData() {
}
}

function exportSpread() {

function getEvSpread() {

// EV Spread String
const evSpread = [];

// Loop over the indexes
for (const index in fields) {

// Get the field data
const field = fields[index];

// Get the ev result for the field
const ev = parseInt(document.getElementById('result-' + field).value);
if (ev > 0) { evSpread.push(`${ev} ${pretty_fields[index]}`); }
}

// At least one specified ev
if (evSpread.length > 0){
// Return EV Spread array, joined as string
return `EVs: ${evSpread.join(' / ')}`;
}
else // No specified evs
{
// Return null
return null;
}
}

function getIvSpread() {

// EV Spread String
let evString = [];
let ivString = [];
const ivSpread = [];

// Loop over the indexes
for (const index in fields) {

// Get the field data
const field = fields[index];

// Get the ev result for the field
const iv = parseInt(document.getElementById(field + '-iv').value);
if (iv < 31) { ivSpread.push(`${iv} ${pretty_fields[index]}`); }
}

// At least one specified iv
if (ivSpread.length > 0){
// Return IV Spread array, joined as string
return `IVs: ${ivSpread.join(' / ')}`;
}
else // No specified ivs
{
// Return null
return null;
}
}

function exportSpread() {

// Spread string array
const spread = [];

// Species name
let name = 'none';

// Get the nature for the set
const nature = document.getElementById('nature-select').value;

// Window is defined
if (window.active) {

// Get name from active species
name = window.active.name;
}

// Loop over the indexes
for (const index in fields) {

// Get the field data
const field = fields[index];
// Add species name to spread
spread.push(name);

// Get the ev result for the field
const ev = parseInt(document.getElementById('result-' + field).value);
if (ev > 0) { evString.push(`${ev} ${pretty_fields[index]}`); }
// Add ev spread, if present
const evs = getEvSpread();
if (evs) {spread.push(evs);}

// Get the ev result for the field
const iv = parseInt(document.getElementById(field + '-iv').value);
if (iv < 31) { ivString.push(`${iv} ${pretty_fields[index]}`); }
}
// Add iv spread, if present
const ivs = getIvSpread();
if (ivs) {spread.push(ivs);}

// Join ev / iv string arrays
evString = `EVs: ${evString.join(' / ')}`;
ivString = `IVs: ${ivString.join(' / ')}`;
}
// Get the nature for the set
const nature = document.getElementById('nature-select').value;

// Build final string
const finalString = `${name}\n${evString}\n${ivString}\n${nature} nature`;
// Add nature (Capitalised)
spread.push(`${toCapitalCase(nature)} Nature`);

// Return final string
return finalString;
return spread.join(`\n`);
}

async function copyToClipboard(content){
// If the clipboard module exists in the client's browser
if (navigator.clipboard) {

try {
// Copy the string to the clipboard
await navigator.clipboard.writeText(content);

// Successful copy alert
window.alert(
"Content copied to clipboard successfully."
);
} catch (err) {
// Report the failure to the error console
console.error(
"Failed to copy content `" + content + "`! Reason: `" + err + "`"
);
}
} // Clipboard module is not available
else {
// Report failure to console, continue
console.error("Clipboard interaction not supported by browser.");
}
}

// --- Add Event Listeners --- //

// Export to clipboard event listener
// Copy EVs event listener
document
.getElementById("paste-export")
.getElementById("copy-evs")
.addEventListener("click", async (event) => {
// If the clipboard module exists in the client's browser
if (navigator.clipboard) {

// Export spread ev data
const spread = exportSpread();

try {
// Copy the string to the clipboard
await navigator.clipboard.writeText(spread);

// Successful copy alert
window.alert(
" Spread copied to clipboard successfully."
);
} catch (err) {
// Report the failure to the error console
console.error(
"Failed to copy content `" + spread + "`! Reason: `" + err + "`"
);
}
} // Clipboard module is not available
else {
// Report failure to console, continue
console.error("Clipboard interaction not supported by browser.");
}
// Copy the ev spread to the clipboard
await copyToClipboard(getEvSpread());
});

// Copy All event listener
document
.getElementById("copy-all")
.addEventListener("click", async (event) => {
// Copy the ev spread to the clipboard
await copyToClipboard(exportSpread());
});

0 comments on commit 5976e73

Please sign in to comment.