Skip to content
Merged
31 changes: 21 additions & 10 deletions .github/workflows/gifs.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
name: Verify Gifs Filename
name: Verify Sprites

on:
pull_request:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Verify Gifs Filename
run: |
npm install
npm run compile:test
npm run test:gifs
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Compile tests
run: npm run compile:test

- name: Verify Sprites
run: npm run test:gifs
134 changes: 98 additions & 36 deletions src/test/gifs.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,110 @@
import * as fs from 'fs';
import * as path from 'path';

import { PokemonColor } from '../common/types';
import {
PokemonColor,
PokemonExtraSprite,
PokemonGeneration,
} from '../common/types';
import { getAllPokemon, POKEMON_DATA } from '../common/pokemon-data';

const defaultPokemonConfig = {
colors: [PokemonColor.default],
states: ['idle', 'walk'],
type MissingGif = {
generation: number;
pokemon: string;
states: string[];
};

const allPokemon = getAllPokemon().reduce(
(acc, pokemon) => ({
...acc,
[pokemon]: defaultPokemonConfig,
}),
{} as { [key: string]: { colors: string[]; states: string[] } },
);
function checkGifFilenames(folder: string) {
for (const pokemon in allPokemon) {
const allowedColors = allPokemon[pokemon].colors;
const allowedStates = allPokemon[pokemon].states;
if (!allowedColors) {
console.error(`No colors found for pokemon "${pokemon}"`);
return;
}
const mediaFolder = './media';
const DELAY_MS = 5;

function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async function runGifCheck(folder: string): Promise<MissingGif[]> {
console.log(`Checking GIFs in folder: ${folder}`);

// Get the generation number from POKEMON_DATA
// Group pokemon by generation
const genMap: Record<number, string[]> = {};
getAllPokemon().forEach((pokemon) => {
const generation = POKEMON_DATA[pokemon]?.generation || 1;
const genFolder = `gen${generation}`;

allowedColors.forEach((color) => {
allowedStates.forEach((state) => {
const filename = `${color}_${state}_8fps.gif`;
const filePath = `${folder}/${genFolder}/${pokemon}/${filename}`;
if (!fs.existsSync(filePath)) {
// \x1b[31m is the ANSI escape code for red, and \x1b[0m resets the color back to the terminal's default.
console.error(`\x1b[31mFile "${filePath}" does not exist.\x1b[0m`);
return false;
} else {
console.log(`File "${filePath}" exists.`);
if (!genMap[generation]) genMap[generation] = [];
genMap[generation].push(pokemon);
});

const missingPokemon: MissingGif[] = [];
// Iterate generations starting at 1
for (
let generation = PokemonGeneration.Gen1;
generation <= PokemonGeneration.Gen4;
generation++
) {
console.log(`\nChecking generation ${generation}...`);
const pokes = genMap[generation] || [];
// Order by POKEMON_DATA id when available
pokes.sort(
(a, b) => (POKEMON_DATA[a]?.id || 0) - (POKEMON_DATA[b]?.id || 0),
);

for (const pokemon of pokes) {
const cfg = POKEMON_DATA[pokemon];
console.log(` Checking ${pokemon}...`);
const colors =
cfg?.possibleColors && cfg.possibleColors.length > 0
? cfg.possibleColors
: [PokemonColor.default];
const states = ['idle', 'walk'];
if (
cfg?.extraSprites &&
cfg.extraSprites.includes(PokemonExtraSprite.leftFacing)
) {
states.push('walk_left');
}

const missing: string[] = [];

for (const color of colors) {
for (const state of states) {
const filename = `${color}_${state}_8fps.gif`;
const filePath = path.join(
folder,
`gen${generation}`,
pokemon,
filename,
);
if (!fs.existsSync(filePath)) {
missing.push(`${color}_${state}`);
}
}
});
});
}

if (missing.length > 0) {
console.error(` \x1b[31mmissing ${missing.join(', ')}\x1b[0m`);
missingPokemon.push({ generation, pokemon, states: missing });
}

// Wait a short time between pokemon checks to reduce CI flakiness
await sleep(DELAY_MS);
}
}

return missingPokemon;
}

const mediaFolder = './media';
checkGifFilenames(mediaFolder);
(async () => {
const missing = await runGifCheck(mediaFolder);
if (missing.length > 0) {
setTimeout(() => {
console.error(`\nMissing GIFs:`);
missing.forEach(({ generation, pokemon, states }) => {
console.error(` Gen ${generation} - ${pokemon}: ${states.join(', ')}`);
});

// Non-zero exit to fail CI when there are missing GIFs
process.exit(1);
}, 1000);
} else {
console.log('All GIFs are present!');
process.exit(0);
}
})();