Initial commit

This commit is contained in:
Octoturge
2026-06-09 22:06:56 +02:00
commit fb42c6e8cc
121 changed files with 14976 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
/**
* Polish-aware slug helper. Lowercases, strips diacritics, replaces
* whitespace and punctuation with hyphens, collapses repeats.
*/
export function slugify(input: string): string {
return input
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/ł/g, 'l')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.replace(/-{2,}/g, '-');
}
/** Strip Polish diacritics for case-insensitive map lookups. */
export function normalizeKey(input: string): string {
return input
.toLowerCase()
.trim()
.replace(/\s+/g, ' ')
.normalize('NFC');
}
/** Parse "5:2" -> { threshold: 5, successes: 2 }. */
export function parseDifficulty(s: string): { threshold: number; successes: number } | null {
const m = s.trim().match(/^(\d+)\s*:\s*([\d\w]+)$/);
if (!m) return null;
const successes = isNaN(Number(m[2])) ? 1 : Number(m[2]);
return { threshold: Number(m[1]), successes };
}
/** Parse "1 punkt", "3 punkty", "5 punktów" → 1/3/5. Returns 0 on failure. */
export function parsePoints(s: string): number {
const m = s.trim().match(/^(\d+)/);
return m ? Number(m[1]) : 0;
}
/** Parse a comma- or whitespace-separated symbol list. */
export function parseList(s: string): string[] {
return s
.split(/[,;]+/)
.map((x) => x.trim())
.filter((x) => x.length > 0);
}
/**
* Parse a Polish range string into structure.
* "Na siebie" → { kind: 'self' }
* "Dotyk" → { kind: 'touch' }
* "15 m" → { kind: 'distance', value: 15, unit: 'm' }
* anything else → { kind: 'special', text }
*/
export function parseRange(s: string): { kind: string; value?: number; unit?: string; text?: string } {
const t = s.trim();
if (/^na siebie$/i.test(t)) return { kind: 'self' };
if (/^dotyk$/i.test(t)) return { kind: 'touch' };
const m = t.match(/^(\d+)\s*(m|km|cm)\b/i);
if (m) return { kind: 'distance', value: Number(m[1]), unit: m[2].toLowerCase() };
return { kind: 'special', text: t };
}
/**
* Parse area-of-effect text into structured shape.
* "3×8 m" → rectangle 3×8
* "promień 5 m" → sphere radius 5
* "stożek 10 m" → cone length 10
* "linia 20 m" → line length 20
*/
export function parseAreaOfEffect(s: string): { shape: string; x?: number; y?: number; unit?: string; text?: string } | null {
const t = s.trim();
let m = t.match(/(\d+)\s*[x×]\s*(\d+)\s*(m|km|cm)?/i);
if (m) return { shape: 'rectangle', x: Number(m[1]), y: Number(m[2]), unit: (m[3] ?? 'm').toLowerCase() };
m = t.match(/promień\s+(\d+)\s*(m|km|cm)?/i);
if (m) return { shape: 'sphere', x: Number(m[1]), unit: (m[2] ?? 'm').toLowerCase() };
m = t.match(/stożek\s+(\d+)\s*(m|km|cm)?/i);
if (m) return { shape: 'cone', x: Number(m[1]), unit: (m[2] ?? 'm').toLowerCase() };
m = t.match(/linia\s+(\d+)\s*(m|km|cm)?/i);
if (m) return { shape: 'line', x: Number(m[1]), unit: (m[2] ?? 'm').toLowerCase() };
return null;
}