diff --git a/lib/__tests__/tarot-reference.test.ts b/lib/__tests__/tarot-reference.test.ts new file mode 100644 index 0000000..a35587f --- /dev/null +++ b/lib/__tests__/tarot-reference.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest'; +import { buildReferenceBlock, buildContextMeta } from '../tarot/reference'; +import { findCard } from '../tarot/cards'; + +const picks = [ + { card: findCard('the-fool')!, position: '과거', reversed: false }, + { card: findCard('the-magician')!, position: '현재', reversed: true }, + { card: findCard('the-high-priestess')!, position: '미래', reversed: false }, +]; + +describe('buildReferenceBlock', () => { + it('각 카드의 위치·정역·키워드·의미를 텍스트 블록으로 만든다', () => { + const block = buildReferenceBlock(picks); + expect(block).toContain('과거'); + expect(block).toContain('The Fool'); + expect(block).toContain('정방향'); + expect(block).toContain('역방향'); + expect(block.length).toBeGreaterThan(50); + }); +}); +describe('buildContextMeta', () => { + it('메이저 비율·원소 분포·정역 흐름을 계산한다', () => { + const meta = buildContextMeta(picks); + expect(meta.major_minor_ratio).toBe('3:0'); + expect(meta.orientation_flow).toBe('upright→reversed→upright'); + expect(typeof meta.element_distribution).toBe('object'); + }); +}); diff --git a/lib/__tests__/tarot-shuffle.test.ts b/lib/__tests__/tarot-shuffle.test.ts new file mode 100644 index 0000000..e830ff4 --- /dev/null +++ b/lib/__tests__/tarot-shuffle.test.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from 'vitest'; +import { fisherYates, buildShuffle } from '../tarot/shuffle'; +import { TAROT_DECK } from '../tarot/cards'; + +describe('fisherYates', () => { + it('원본을 변형하지 않고 같은 원소 집합을 반환한다', () => { + const input = [1, 2, 3, 4, 5]; + const out = fisherYates(input); + expect(input).toEqual([1, 2, 3, 4, 5]); + expect([...out].sort()).toEqual([1, 2, 3, 4, 5]); + }); +}); +describe('buildShuffle', () => { + it('요청한 수만큼, 중복 없이, reversed 필드를 갖고 반환한다', () => { + const out = buildShuffle(TAROT_DECK, 20); + expect(out).toHaveLength(20); + expect(new Set(out.map((c) => c.slug)).size).toBe(20); + for (const c of out) expect(typeof c.reversed).toBe('boolean'); + }); +}); diff --git a/lib/tarot/reference.ts b/lib/tarot/reference.ts new file mode 100644 index 0000000..9899b8d --- /dev/null +++ b/lib/tarot/reference.ts @@ -0,0 +1,29 @@ +import type { Pick } from './shuffle'; + +export function buildReferenceBlock(picks: Pick[]): string { + return picks + .map((p, i) => { + const c = p.card; + const dir = p.reversed ? '역방향' : '정방향'; + const kws = (p.reversed ? c.reversedKeywords : c.keywords).join(', '); + const meaning = p.reversed ? c.meaningReversed : c.meaningUpright; + const arcana = c.arcana === 'major' ? `Major (${c.id})` : `Minor (${c.suit})`; + return [ + `## ${i + 1}. 위치: ${p.position} | 카드: ${c.nameEn} (${dir})`, + `- 아르카나: ${arcana}`, + `- 원소: ${c.element}`, + `- ${dir} 키워드: ${kws}`, + `- ${dir} 의미: ${meaning}`, + ].join('\n'); + }) + .join('\n\n'); +} + +export function buildContextMeta(picks: Pick[]) { + const major = picks.filter((p) => p.card.arcana === 'major').length; + const minor = picks.length - major; + const element_distribution: Record = { air: 0, water: 0, fire: 0, earth: 0 }; + for (const p of picks) element_distribution[p.card.element] += 1; + const orientation_flow = picks.map((p) => (p.reversed ? 'reversed' : 'upright')).join('→'); + return { major_minor_ratio: `${major}:${minor}`, element_distribution, orientation_flow }; +} diff --git a/lib/tarot/shuffle.ts b/lib/tarot/shuffle.ts new file mode 100644 index 0000000..adce78f --- /dev/null +++ b/lib/tarot/shuffle.ts @@ -0,0 +1,18 @@ +import type { TarotCard } from './cards'; + +export type Pick = { card: TarotCard; position: string; reversed: boolean }; + +export function fisherYates(input: T[]): T[] { + const a = [...input]; + for (let i = a.length - 1; i > 0; i -= 1) { + const j = Math.floor(Math.random() * (i + 1)); + [a[i], a[j]] = [a[j], a[i]]; + } + return a; +} + +export function buildShuffle(deck: TarotCard[], size: number): (TarotCard & { reversed: boolean })[] { + return fisherYates(deck) + .slice(0, size) + .map((c) => ({ ...c, reversed: Math.random() < 0.5 })); +}