Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
37 changes: 26 additions & 11 deletions scripts/crawl-pokeapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const DATA_DIR = join(PROJECT_ROOT, 'data');
const CRIES_DIR = join(PROJECT_ROOT, 'cries');
const SPRITES_RAW_DIR = join(PROJECT_ROOT, 'sprites', 'raw');
const SPRITES_TERMINAL_DIR = join(PROJECT_ROOT, 'sprites', 'terminal');
const SPRITES_RAW_SHINY_DIR = join(PROJECT_ROOT, 'sprites', 'raw_shiny');
const SPRITES_TERMINAL_SHINY_DIR = join(PROJECT_ROOT, 'sprites', 'terminal_shiny');

// --- Config ---
const GEN4_START = 387;
Expand Down Expand Up @@ -366,6 +368,11 @@ async function main() {
if (spriteUrl) {
await downloadFile(spriteUrl, join(SPRITES_RAW_DIR, `${id}.png`));
}
// Download shiny sprite (official game palette)
const shinyUrl = pokemonData.sprites?.front_shiny;
if (shinyUrl) {
await downloadFile(shinyUrl, join(SPRITES_RAW_SHINY_DIR, `${id}.png`));
}

process.stdout.write(` ${koName}\n`);
await sleep(DELAY_MS);
Expand All @@ -387,18 +394,26 @@ async function main() {
}
}

// Phase 3: Convert sprites to terminal art
// Phase 3: Convert sprites to terminal art (both regular and shiny)
console.log('\nConverting sprites to terminal art...');
for (let id = GEN4_START; id <= GEN4_END; id++) {
const rawPath = join(SPRITES_RAW_DIR, `${id}.png`);
const termPath = join(SPRITES_TERMINAL_DIR, `${id}.txt`);
if (existsSync(termPath)) continue; // idempotent
if (!existsSync(rawPath)) continue;
try {
const buf = readFileSync(rawPath);
writeFileSync(termPath, convertPngToTerminal(buf) + '\n', 'utf-8');
} catch (err: any) {
console.error(` Sprite conversion failed for #${id}: ${err.message}`);
const conversionVariants: { rawDir: string; outDir: string; label: string }[] = [
{ rawDir: SPRITES_RAW_DIR, outDir: SPRITES_TERMINAL_DIR, label: 'terminal' },
{ rawDir: SPRITES_RAW_SHINY_DIR, outDir: SPRITES_TERMINAL_SHINY_DIR, label: 'terminal_shiny' },
];
for (const { rawDir, outDir, label } of conversionVariants) {
if (!existsSync(rawDir)) continue;
mkdirSync(outDir, { recursive: true });
for (let id = GEN4_START; id <= GEN4_END; id++) {
const rawPath = join(rawDir, `${id}.png`);
const termPath = join(outDir, `${id}.txt`);
if (existsSync(termPath)) continue; // idempotent
if (!existsSync(rawPath)) continue;
try {
const buf = readFileSync(rawPath);
writeFileSync(termPath, convertPngToTerminal(buf) + '\n', 'utf-8');
} catch (err: any) {
console.error(` ${label} conversion failed for #${id}: ${err.message}`);
}
}
}

Expand Down
96 changes: 96 additions & 0 deletions scripts/fetch-shiny-sprites.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env tsx
/**
* Download official shiny PNG sprites from PokeAPI for every species we ship.
*
* PokeAPI exposes `sprites.front_shiny` per /pokemon/{id} — these are the
* exact front-facing sprites used in the games. We mirror our existing
* `sprites/raw/{id}.png` set into `sprites/raw_shiny/{id}.png`.
*
* Idempotent: skips ids whose shiny PNG already exists.
*
* Usage: tsx scripts/fetch-shiny-sprites.ts [--max 1025]
*/

import { existsSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = join(__dirname, '..');
const RAW_DIR = join(PROJECT_ROOT, 'sprites', 'raw');
const RAW_SHINY_DIR = join(PROJECT_ROOT, 'sprites', 'raw_shiny');

const POKEAPI_BASE = 'https://pokeapi.co/api/v2';
const DELAY_MS = 50;

const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));

async function fetchJSON(url: string): Promise<any> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
return res.json();
}

async function downloadFile(url: string, dest: string): Promise<void> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} downloading ${url}`);
const buf = Buffer.from(await res.arrayBuffer());
mkdirSync(dirname(dest), { recursive: true });
writeFileSync(dest, buf);
}

async function main(): Promise<void> {
mkdirSync(RAW_SHINY_DIR, { recursive: true });

const maxArgIdx = process.argv.indexOf('--max');
const cap = maxArgIdx > 0 ? parseInt(process.argv[maxArgIdx + 1], 10) : Number.POSITIVE_INFINITY;

const rawIds = readdirSync(RAW_DIR)
.filter(f => f.endsWith('.png'))
.map(f => parseInt(f.replace('.png', ''), 10))
.filter(n => Number.isFinite(n) && n <= cap)
.sort((a, b) => a - b);

console.log(`Fetching shiny sprites for ${rawIds.length} species...`);
let downloaded = 0;
let skipped = 0;
let missing = 0;
const errors: string[] = [];

for (const id of rawIds) {
const dest = join(RAW_SHINY_DIR, `${id}.png`);
if (existsSync(dest)) {
skipped++;
continue;
}
try {
const data = await fetchJSON(`${POKEAPI_BASE}/pokemon/${id}`);
const url = data?.sprites?.front_shiny;
if (!url) {
missing++;
process.stdout.write(` #${id}: no front_shiny\n`);
continue;
}
await downloadFile(url, dest);
downloaded++;
if (downloaded % 50 === 0) {
process.stdout.write(` ${downloaded} downloaded (current id=${id})\n`);
}
await sleep(DELAY_MS);
} catch (err: any) {
errors.push(`#${id}: ${err.message}`);
process.stdout.write(` #${id}: ${err.message}\n`);
}
}

console.log(`\nDone. downloaded=${downloaded} skipped=${skipped} missing=${missing} errors=${errors.length}`);
if (errors.length > 0) {
console.log('Errors:');
for (const e of errors) console.log(` ${e}`);
}
}

main().catch(err => {
console.error(err);
process.exit(1);
});
47 changes: 29 additions & 18 deletions scripts/generate-braille-sprites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ import { PNG } from 'pngjs';

const __dirname = dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = join(__dirname, '..');
const RAW_DIR = join(PROJECT_ROOT, 'sprites', 'raw');
const BRAILLE_DIR = join(PROJECT_ROOT, 'sprites', 'braille');

// Each (input, output) pair is processed identically. Shiny variants come from
// PokeAPI sprites.front_shiny (downloaded via scripts/fetch-shiny-sprites.ts).
const VARIANTS: { rawDir: string; outDir: string; label: string }[] = [
{ rawDir: join(PROJECT_ROOT, 'sprites', 'raw'), outDir: join(PROJECT_ROOT, 'sprites', 'braille'), label: 'braille' },
{ rawDir: join(PROJECT_ROOT, 'sprites', 'raw_shiny'), outDir: join(PROJECT_ROOT, 'sprites', 'braille_shiny'), label: 'braille_shiny' },
];

// Braille dot positions (Unicode offset = sum of bit values)
// Col 0: dots 1,2,3,7 → bits 0,1,2,6
Expand Down Expand Up @@ -98,25 +103,31 @@ function convertToBraille(pngBuffer: Buffer, targetWidth: number = 20): string {
}

// Main
mkdirSync(BRAILLE_DIR, { recursive: true });
for (const { rawDir, outDir, label } of VARIANTS) {
if (!existsSync(rawDir)) {
console.log(`Skipping ${label}: ${rawDir} does not exist`);
continue;
}
mkdirSync(outDir, { recursive: true });

const files = readdirSync(RAW_DIR).filter(f => f.endsWith('.png')).sort();
let count = 0;
const files = readdirSync(rawDir).filter(f => f.endsWith('.png')).sort();
let count = 0;

for (const file of files) {
const id = file.replace('.png', '');
const outPath = join(BRAILLE_DIR, `${id}.txt`);
for (const file of files) {
const id = file.replace('.png', '');
const outPath = join(outDir, `${id}.txt`);

if (existsSync(outPath)) continue; // idempotent
if (existsSync(outPath)) continue; // idempotent

try {
const buf = readFileSync(join(RAW_DIR, file));
const braille = convertToBraille(buf, 20);
writeFileSync(outPath, braille + '\n', 'utf-8');
count++;
} catch (err: any) {
console.error(`Failed ${id}: ${err.message}`);
try {
const buf = readFileSync(join(rawDir, file));
const braille = convertToBraille(buf, 20);
writeFileSync(outPath, braille + '\n', 'utf-8');
count++;
} catch (err: any) {
console.error(`Failed ${label}/${id}: ${err.message}`);
}
}
}

console.log(`Generated ${count} braille sprites (${files.length} total)`);
console.log(`Generated ${count} ${label} sprites (${files.length} total)`);
}
74 changes: 42 additions & 32 deletions scripts/generate-png-sprites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@ import { generateKitty, generateIterm2, generateSixel } from '../src/sprites/png

const __dirname = dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = join(__dirname, '..');
const RAW_DIR = join(PROJECT_ROOT, 'sprites', 'raw');

const KITTY_DIR = join(PROJECT_ROOT, 'sprites', 'kitty');
const SIXEL_DIR = join(PROJECT_ROOT, 'sprites', 'sixel');
const ITERM2_DIR = join(PROJECT_ROOT, 'sprites', 'iterm2');

type RendererTarget = 'kitty' | 'sixel' | 'iterm2';

const DIR_MAP: Record<RendererTarget, string> = { kitty: KITTY_DIR, sixel: SIXEL_DIR, iterm2: ITERM2_DIR };
const VARIANTS: { rawDir: string; suffix: string }[] = [
{ rawDir: join(PROJECT_ROOT, 'sprites', 'raw'), suffix: '' },
{ rawDir: join(PROJECT_ROOT, 'sprites', 'raw_shiny'), suffix: '_shiny' },
];

const EXT_MAP: Record<RendererTarget, string> = { kitty: '.bin', sixel: '.sixel', iterm2: '.b64' };

function outDirFor(target: RendererTarget, suffix: string): string {
return join(PROJECT_ROOT, 'sprites', `${target}${suffix}`);
}

function main(): void {
const arg = process.argv[2];
let targets: RendererTarget[];
Expand All @@ -42,39 +45,46 @@ function main(): void {
targets = ['kitty', 'sixel', 'iterm2'];
}

for (const target of targets) {
mkdirSync(DIR_MAP[target], { recursive: true });
}
for (const { rawDir, suffix } of VARIANTS) {
if (!existsSync(rawDir)) {
console.log(`Skipping suffix='${suffix}': ${rawDir} does not exist`);
continue;
}

for (const target of targets) {
mkdirSync(outDirFor(target, suffix), { recursive: true });
}

const files = readdirSync(RAW_DIR).filter(f => f.endsWith('.png')).sort();
const counts: Record<string, number> = {};
const files = readdirSync(rawDir).filter(f => f.endsWith('.png')).sort();
const counts: Record<string, number> = {};

for (const file of files) {
const id = file.replace('.png', '');
const pngBuf = readFileSync(join(RAW_DIR, file));
for (const file of files) {
const id = file.replace('.png', '');
const pngBuf = readFileSync(join(rawDir, file));

for (const target of targets) {
const outPath = join(DIR_MAP[target], `${id}${EXT_MAP[target]}`);
if (existsSync(outPath)) continue; // idempotent

try {
let data: string;
switch (target) {
case 'kitty': data = generateKitty(pngBuf); break;
case 'iterm2': data = generateIterm2(pngBuf); break;
case 'sixel': data = generateSixel(pngBuf); break;
for (const target of targets) {
const outPath = join(outDirFor(target, suffix), `${id}${EXT_MAP[target]}`);
if (existsSync(outPath)) continue; // idempotent

try {
let data: string;
switch (target) {
case 'kitty': data = generateKitty(pngBuf); break;
case 'iterm2': data = generateIterm2(pngBuf); break;
case 'sixel': data = generateSixel(pngBuf); break;
}
writeFileSync(outPath, data, 'utf-8');
counts[target] = (counts[target] ?? 0) + 1;
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.error(`Failed ${target}${suffix}/${id}: ${msg}`);
}
writeFileSync(outPath, data, 'utf-8');
counts[target] = (counts[target] ?? 0) + 1;
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.error(`Failed ${target}/${id}: ${msg}`);
}
}
}

for (const target of targets) {
console.log(`Generated ${counts[target] ?? 0} ${target} sprites (${files.length} total)`);
for (const target of targets) {
console.log(`Generated ${counts[target] ?? 0} ${target}${suffix} sprites (${files.length} total)`);
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions sprites/braille_shiny/1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@



⢀⣀⣀⣤⣤⣴⡆
⣸⣿⣿⣿⣿⣿⣿⡄
⠹⣿⣿⣿⣿⣿⣯
⠘⠛⠹⠟⠙⠋



10 changes: 10 additions & 0 deletions sprites/braille_shiny/10.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@



⣠⣀⣀⡄
⢸⣿⣿⣿⡆
⢘⣿⣿⣿⡷⢰⡆
⠻⣿⣿⣷⡞
⠉⠁


10 changes: 10 additions & 0 deletions sprites/braille_shiny/100.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@



⢀⣠⣤⣀
⣰⣿⣿⣿⣿⣷⡀
⢻⣿⣿⣿⣿⣿⠃
⠙⠻⠿⠛⠁



10 changes: 10 additions & 0 deletions sprites/braille_shiny/1000.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
⣀⡀
⢰⡿⣱⣶⠆
⢀⣴⣿⣿⣿⡟⠻
⠉⠸⣿⡿
⣴⣿⡗⢦⣤⣴⣷⡆
⢠⣤⣾⣿⣳⣿⣿⣿⣦⠙⠗
⠉⠁⢹⣿⣿⡿⢿⣿⣆⣠⡀
⣀⢀⣠⣤⣾⣿⣾⣿⣿⣿⣿⠿⠛
⢿⣿⣿⣿⣿⡿⠿⠟⠋⠉⠁
⠉⠉⠁
10 changes: 10 additions & 0 deletions sprites/braille_shiny/1001.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
⣀ ⣀
⢀⠘⡆ ⠰⡁⡀
⠈⢹⣧⡀⣤⡴⢆⣴⡗⠳⠆ ⣰⣦
⠸⢏⣷⣿⣿⣿⣟⠁ ⣴⣿⣿⣏
⣾⣿⣿⣿⣿⣿⡀ ⣴⣿⣿⣿⣯⡟
⢈⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⣿⣿⣿⣿
⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀
⢼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⡀
⣀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠄
⠘⠙⠙⠛⠛⠛⠛⠛⠛⠛⠋⠉⠛⠛⠉⠁
10 changes: 10 additions & 0 deletions sprites/braille_shiny/1002.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@


⢀⡀⢸⣷⣿⣷⣿⠇
⣀⣀ ⠈⠉⢿⣿⣿⣿⣿⡛⠂
⢰⣿⠿⠿⠗⣠⣶⣿⣿⣿⣾⣵⣿⣿⡿
⠠⢿⣿⣀⣀⣿⣿⣿⣿⡟⢻⣿⣿⣿⣿⠃
⠈⠿⠿⠿⢿⡿⠻⣯⣀⣿⣿⠏⢽⣿⣇
⠻⠟⠂⠉⠛⣹⣿⣄⠈⠛⠻⠟
⠈⠉⠁

Loading
Loading