Compare commits
5 Commits
01cf81f930
...
ea2285ed1d
Author | SHA1 | Date | |
---|---|---|---|
ea2285ed1d | |||
6c7d61f916 | |||
2acbf9945c | |||
6aa1cac328 | |||
0b163e8ffd |
3
bot.py
3
bot.py
@ -93,7 +93,8 @@ class BotClient(discord.Client):
|
|||||||
"Run Showdown damage calculations.\n"
|
"Run Showdown damage calculations.\n"
|
||||||
"Example format:\n"
|
"Example format:\n"
|
||||||
"` %calc -2 8 SpA Choice Specs Torkoal Overheat vs. 252 HP / 4+ SpD Assault Vest Abomasnow in Sun through Light Screen`\n"
|
"` %calc -2 8 SpA Choice Specs Torkoal Overheat vs. 252 HP / 4+ SpD Assault Vest Abomasnow in Sun through Light Screen`\n"
|
||||||
"Supported: attacker/defender boosts, EVs, item, species; attacker ability; weather/terrain; screens."
|
"Supported: attacker/defender boosts, EVs, tera, item, ability, species; weather/terrain; screens."
|
||||||
|
"Not supported (popular): non-default multi hits; hazards."
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _calculate(self, args: list[str]) -> str:
|
async def _calculate(self, args: list[str]) -> str:
|
||||||
|
67
calc.js
67
calc.js
@ -12,6 +12,14 @@ import { createToken, Lexer } from "chevrotain";
|
|||||||
|
|
||||||
const gen = Generations.get(9);
|
const gen = Generations.get(9);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
function escapeRegExp(text) {
|
||||||
|
return text.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a lexer.
|
* Creates a lexer.
|
||||||
*
|
*
|
||||||
@ -27,20 +35,36 @@ function buildLexer() {
|
|||||||
|
|
||||||
const Item = createToken({
|
const Item = createToken({
|
||||||
name: "Item",
|
name: "Item",
|
||||||
pattern: new RegExp([...gen.items].map((i) => i.name).join("|")),
|
pattern: new RegExp(
|
||||||
|
[...gen.items].map((i) => escapeRegExp(i.name)).join("|")
|
||||||
|
),
|
||||||
});
|
});
|
||||||
const Ability = createToken({
|
const Ability = createToken({
|
||||||
name: "Ability",
|
name: "Ability",
|
||||||
pattern: new RegExp([...gen.abilities].map((a) => a.name).join("|")),
|
pattern: new RegExp(
|
||||||
|
[...gen.abilities].map((a) => escapeRegExp(a.name)).join("|")
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Pokemon = createToken({
|
const Pokemon = createToken({
|
||||||
name: "Pokemon",
|
name: "Pokemon",
|
||||||
pattern: new RegExp([...gen.species].map((s) => s.name).join("|")),
|
pattern: new RegExp(
|
||||||
|
[...gen.species]
|
||||||
|
.flatMap((s) => [
|
||||||
|
// Important: formes are expanded first because the parser takes the
|
||||||
|
// first match, and the original species is always a subset of a
|
||||||
|
// forme.
|
||||||
|
...(s.otherFormes || []).map(escapeRegExp),
|
||||||
|
escapeRegExp(s.name),
|
||||||
|
])
|
||||||
|
.join("|")
|
||||||
|
),
|
||||||
});
|
});
|
||||||
const Move = createToken({
|
const Move = createToken({
|
||||||
name: "Move",
|
name: "Move",
|
||||||
pattern: new RegExp([...gen.moves].map((m) => m.name).join("|")),
|
pattern: new RegExp(
|
||||||
|
[...gen.moves].map((m) => escapeRegExp(m.name)).join("|")
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const whitespace = createToken({
|
const whitespace = createToken({
|
||||||
@ -58,6 +82,19 @@ function buildLexer() {
|
|||||||
pattern: /vs\.?/,
|
pattern: /vs\.?/,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const teraEnter = createToken({
|
||||||
|
name: "teraEnter",
|
||||||
|
pattern: "Tera",
|
||||||
|
push_mode: "tera_mode",
|
||||||
|
});
|
||||||
|
const Tera = createToken({
|
||||||
|
name: "Tera",
|
||||||
|
pattern: new RegExp(
|
||||||
|
[...gen.types].map((t) => escapeRegExp(t.name)).join("|")
|
||||||
|
),
|
||||||
|
pop_mode: true,
|
||||||
|
});
|
||||||
|
|
||||||
const terrainEnter = createToken({
|
const terrainEnter = createToken({
|
||||||
name: "terrainEnter",
|
name: "terrainEnter",
|
||||||
pattern: "in",
|
pattern: "in",
|
||||||
@ -66,10 +103,12 @@ function buildLexer() {
|
|||||||
const Weather = createToken({
|
const Weather = createToken({
|
||||||
name: "Weather",
|
name: "Weather",
|
||||||
pattern: /Sun|Rain|Sand|Snow/,
|
pattern: /Sun|Rain|Sand|Snow/,
|
||||||
|
pop_mode: true,
|
||||||
});
|
});
|
||||||
const Terrain = createToken({
|
const Terrain = createToken({
|
||||||
name: "Terrain",
|
name: "Terrain",
|
||||||
pattern: /(Electric|Grassy|Misty|Psychic) Terrain/,
|
pattern: /(Electric|Grassy|Misty|Psychic) Terrain/,
|
||||||
|
pop_mode: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const screenEnter = createToken({
|
const screenEnter = createToken({
|
||||||
@ -91,10 +130,12 @@ function buildLexer() {
|
|||||||
Ability,
|
Ability,
|
||||||
Pokemon,
|
Pokemon,
|
||||||
Move,
|
Move,
|
||||||
|
teraEnter,
|
||||||
terrainEnter,
|
terrainEnter,
|
||||||
screenEnter,
|
screenEnter,
|
||||||
],
|
],
|
||||||
terrain_mode: [whitespace, screenEnter, Weather, Terrain],
|
tera_mode: [whitespace, Tera],
|
||||||
|
terrain_mode: [whitespace, Weather, Terrain],
|
||||||
screen_mode: [whitespace, Move],
|
screen_mode: [whitespace, Move],
|
||||||
},
|
},
|
||||||
defaultMode: "default_mode",
|
defaultMode: "default_mode",
|
||||||
@ -211,8 +252,6 @@ function parseAndCalculate(line) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Stat":
|
|
||||||
throw Error("Impossible state: bare Stat");
|
|
||||||
case "Item":
|
case "Item":
|
||||||
opts().item = unwrapToken("Item", item);
|
opts().item = unwrapToken("Item", item);
|
||||||
break;
|
break;
|
||||||
@ -229,6 +268,9 @@ function parseAndCalculate(line) {
|
|||||||
case "Move":
|
case "Move":
|
||||||
move = unwrapToken("Move", item);
|
move = unwrapToken("Move", item);
|
||||||
break;
|
break;
|
||||||
|
case "teraEnter":
|
||||||
|
opts().teraType = unwrapToken("Tera", it.next().value);
|
||||||
|
break;
|
||||||
case "terrainEnter":
|
case "terrainEnter":
|
||||||
{
|
{
|
||||||
let next = it.next().value;
|
let next = it.next().value;
|
||||||
@ -284,7 +326,7 @@ function parseAndCalculate(line) {
|
|||||||
function test() {
|
function test() {
|
||||||
const text =
|
const text =
|
||||||
"-2 8 SpA Choice Specs Torkoal Overheat vs. 252 HP / 4+ SpD Assault Vest Abomasnow in Sun through Light Screen";
|
"-2 8 SpA Choice Specs Torkoal Overheat vs. 252 HP / 4+ SpD Assault Vest Abomasnow in Sun through Light Screen";
|
||||||
const res = parseAndCalculate(text);
|
var res = parseAndCalculate(text);
|
||||||
|
|
||||||
assert(res.attacker.boosts.spa === -2, "should have -2 SpA");
|
assert(res.attacker.boosts.spa === -2, "should have -2 SpA");
|
||||||
assert(res.attacker.evs.spa === 8, "should have 8 SpA EVs");
|
assert(res.attacker.evs.spa === 8, "should have 8 SpA EVs");
|
||||||
@ -299,6 +341,15 @@ function test() {
|
|||||||
res.desc().replace(/:.*/, "") === text,
|
res.desc().replace(/:.*/, "") === text,
|
||||||
"non-damage text should be equivalent to input"
|
"non-damage text should be equivalent to input"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
res = parseAndCalculate("Tera Electric Iron Hands Wild Charge vs Basculin");
|
||||||
|
assert(res.attacker.teraType === "Electric", "should parse tera type");
|
||||||
|
|
||||||
|
res = parseAndCalculate("Gallade-Mega Triple Axel vs Gligar");
|
||||||
|
assert(res.attacker.name === "Gallade-Mega", "should parse Mega forme");
|
||||||
|
|
||||||
|
res = parseAndCalculate("Zoroark-Hisui Night Slash vs Golem");
|
||||||
|
assert(res.attacker.name === "Zoroark-Hisui", "should parse regional forme");
|
||||||
}
|
}
|
||||||
|
|
||||||
export { parseAndCalculate, test };
|
export { parseAndCalculate, test };
|
||||||
|
Loading…
Reference in New Issue
Block a user