refactor(shared): animation, l10n, theme 모듈을 core에서 shared로 이동

- core/animation → shared/animation
- core/l10n → shared/l10n
- core/constants/ascii_colors → shared/theme/ascii_colors
- import 경로 업데이트
This commit is contained in:
JiWoong Sul
2026-02-23 15:49:14 +09:00
parent 8fcb7bf2b7
commit 8f351df0b6
24 changed files with 1409 additions and 1498 deletions

View File

@@ -0,0 +1,760 @@
import 'package:flutter/material.dart';
import 'package:asciineverdie/src/shared/animation/ascii_animation_type.dart';
/// ASCII 애니메이션 프레임 데이터
class AsciiAnimationData {
const AsciiAnimationData({required this.frames, this.frameIntervalMs = 200});
/// 각 프레임 (문자열, 최소 5줄)
final List<String> frames;
/// 프레임 간격 (밀리초)
final int frameIntervalMs;
}
/// 터미널 색상 테마
enum AsciiColorTheme {
/// 클래식 녹색 터미널
green,
/// 엠버 (호박색) 터미널
amber,
/// 화이트 온 블랙
white,
/// 시스템 테마 (라이트/다크 모드 따름)
system,
}
/// 테마별 색상 데이터
class AsciiThemeColors {
const AsciiThemeColors({
required this.textColor,
required this.backgroundColor,
});
final Color textColor;
final Color backgroundColor;
}
/// 테마별 색상 반환
AsciiThemeColors getThemeColors(AsciiColorTheme theme, Brightness brightness) {
return switch (theme) {
AsciiColorTheme.green => const AsciiThemeColors(
textColor: Color(0xFF00FF00),
backgroundColor: Color(0xFF0D0D0D),
),
AsciiColorTheme.amber => const AsciiThemeColors(
textColor: Color(0xFFFFB000),
backgroundColor: Color(0xFF1A1000),
),
AsciiColorTheme.white => const AsciiThemeColors(
textColor: Color(0xFFE0E0E0),
backgroundColor: Color(0xFF121212),
),
AsciiColorTheme.system =>
brightness == Brightness.dark
? const AsciiThemeColors(
textColor: Color(0xFFE0E0E0),
backgroundColor: Color(0xFF1E1E1E),
)
: const AsciiThemeColors(
textColor: Color(0xFF1E1E1E),
backgroundColor: Color(0xFFF5F5F5),
),
};
}
/// 몬스터 카테고리 (ASCII NEVER DIE 테마)
enum MonsterCategory {
/// 기본 버그 (Syntax Error, Type Mismatch 등)
bug,
/// 멀웨어 (Virus, Worm, Trojan 등)
malware,
/// 네트워크 위협 (Flood, DDoS, Injection 등)
network,
/// 시스템 위협 (Kernel, Memory, Buffer 등)
system,
/// 암호화/보안 (Encryption, Hash, Zero-Day 등)
crypto,
/// AI/ML 위협 (Neural Network, Machine Learning 등)
ai,
/// 보스 몬스터
boss,
}
/// 몬스터 이름으로 카테고리 결정 (ASCII NEVER DIE 테마)
MonsterCategory getMonsterCategory(String? monsterBaseName) {
if (monsterBaseName == null || monsterBaseName.isEmpty) {
return MonsterCategory.bug;
}
final name = monsterBaseName.toLowerCase();
// 보스 몬스터
if (name.startsWith('boss:') ||
name.contains('dragon') ||
name.contains('hydra') ||
name.contains('titan') ||
name.contains('leviathan') ||
name.contains('colossus') ||
name.contains('emperor') ||
name.contains('singularity') ||
name.contains('primordial') ||
name.contains('glitch god')) {
return MonsterCategory.boss;
}
// AI/ML 위협
if (name.contains('neural') ||
name.contains('ai ') ||
name.contains('deep') ||
name.contains('model') ||
name.contains('adversarial') ||
name.contains('training') ||
name.contains('federated') ||
name.contains('prompt') ||
name.contains('hallucination')) {
return MonsterCategory.ai;
}
// 암호화/보안 위협
if (name.contains('quantum') ||
name.contains('zero day') ||
name.contains('zero-day') ||
name.contains('crypto') ||
name.contains('hash') ||
name.contains('blockchain') ||
name.contains('homomorphic') ||
name.contains('zero knowledge') ||
name.contains('smart contract')) {
return MonsterCategory.crypto;
}
// 네트워크 위협
if (name.contains('flood') ||
name.contains('ddos') ||
name.contains('dos') ||
name.contains('injection') ||
name.contains('sql') ||
name.contains('xss') ||
name.contains('csrf') ||
name.contains('amplification') ||
name.contains('tunnel') ||
name.contains('shell') ||
name.contains('backdoor') ||
name.contains('c2') ||
name.contains('beacon')) {
return MonsterCategory.network;
}
// 시스템 위협
if (name.contains('kernel') ||
name.contains('memory') ||
name.contains('buffer') ||
name.contains('stack') ||
name.contains('heap') ||
name.contains('overflow') ||
name.contains('corruption') ||
name.contains('segfault') ||
name.contains('panic') ||
name.contains('rootkit') ||
name.contains('firmware') ||
name.contains('bootkit')) {
return MonsterCategory.system;
}
// 멀웨어
if (name.contains('virus') ||
name.contains('worm') ||
name.contains('trojan') ||
name.contains('ransomware') ||
name.contains('malware') ||
name.contains('botnet') ||
name.contains('cryptominer') ||
name.contains('keylogger') ||
name.contains('spyware') ||
name.contains('dropper') ||
name.contains('loader') ||
name.contains('payload')) {
return MonsterCategory.malware;
}
// 기본: 버그
return MonsterCategory.bug;
}
/// 버그 전투 애니메이션 (기본 버그 모양)
const battleAnimationBug = AsciiAnimationData(
frames: [
// 프레임 1: 대치
'''
o /\\_/\\
/|\\ ( o.o )
/ \\ > ^ <''',
// 프레임 2: 접근
'''
o /\\_/\\
/|\\ ( o.o )
/ \\ > ^ <''',
// 프레임 3: 공격 (근접)
'''
o_/ /\\_/\\
/| ( >.< )
/ \\ > ^ <''',
// 프레임 4: 히트
'''
o **** /\\_/\\
/|\\ *** ( X.X )
/ \\ > ~ <''',
// 프레임 5: 복귀 (승리 포즈)
'''
\\o/ /\\_/\\
/|\\ ( -.-)
/ \\ > ^ <''',
],
frameIntervalMs: 220,
);
/// 마을/상점 애니메이션 (8줄 x 40자 고정)
/// 캐릭터 위치: 머리=4, 몸통=5, 다리=6 (전투 애니메이션 기준)
const townAnimation = AsciiAnimationData(
frames: [
// 프레임 1: 상점 앞 대기
' ___________ \n'
' / SHOP \\ \n'
' | [======] | \n'
' | @@@@ | \n'
' | ITEMS | o \n'
' | | /|\\ \n'
' |___________| / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 2: 이동 중
' ___________ \n'
' / SHOP \\ \n'
' | [======] | \n'
' | @@@@ | \n'
' | ITEMS | o \n'
' | | /|\\ \n'
' |___________| / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 3: 거래 시작
' ___________ \n'
' / SHOP \\ \n'
' | [======] | \$ \n'
' | @@@@ | \$ \n'
' | ITEMS | o \$ \n'
' | | /|\\ \n'
' |___________| / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 4: 거래 중
' ___________ \n'
' / SHOP \\ \n'
' | [<====>] | \$\$\$ \n'
' | @@@@ | \$\$\$ \n'
' | SOLD! | o \n'
' | | /|\\ \n'
' |___________| / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 5: 거래 완료
' ___________ \n'
' / SHOP \\ \n'
' | [======] | + \n'
' | @@@@ | + \n'
' | ITEMS | \\o/ \n'
' | | /|\\ \n'
' |___________| / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
],
frameIntervalMs: 280,
);
/// 걷는 애니메이션 (8줄 x 40자 고정)
const walkingAnimation = AsciiAnimationData(
frames: [
// 프레임 1: 양발 벌림
' \n'
' \n'
' \n'
' \n'
' o \n'
' /|\\ \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 2: 왼발 앞으로
' \n'
' \n'
' \n'
' \n'
' o \n'
' /|\\ \n'
' /| \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 3: 두 발 모음
' \n'
' \n'
' \n'
' \n'
' o \n'
' /|\\ \n'
' || \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 4: 오른발 앞으로
' \n'
' \n'
' \n'
' \n'
' o \n'
' /|\\ \n'
' |\\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 5: 양발 벌림 (복귀)
' \n'
' \n'
' \n'
' \n'
' o \n'
' /|\\ \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
],
frameIntervalMs: 180,
);
/// 멀웨어 전투 애니메이션 (바이러스 모양)
const battleAnimationMalware = AsciiAnimationData(
frames: [
// 프레임 1: 대치
'''
o /\\_/\\
/|\\ ( o o )
/ \\ /|=====|\\''',
// 프레임 2: 접근
'''
o /\\_/\\
/|\\ ( o o )
/ \\ /|=====|\\''',
// 프레임 3: 공격 (근접)
'''
o_/ /\\_/\\
/| ( >.< )
/ \\ /|=====|\\''',
// 프레임 4: 히트
'''
o **** /\\_/\\
/|\\*** ( X X )
/ \\ /|=====|\\''',
// 프레임 5: 복귀 (승리 포즈)
'''
\\o/ /\\_/\\
/|\\ ( - - )
/ \\ /|=====|\\''',
],
frameIntervalMs: 220,
);
/// 네트워크 전투 애니메이션 (패킷 모양)
const battleAnimationNetwork = AsciiAnimationData(
frames: [
// 프레임 1: 대치
'''
o O
/|\\ /|\\
/ \\ / | \\''',
// 프레임 2: 접근
'''
o O
/|\\ /|\\
/ \\ / | \\''',
// 프레임 3: 공격 (근접)
'''
o_/ O
/| X|X
/ \\ / | \\''',
// 프레임 4: 히트
'''
o **** O
/|\\ *** X|X
/ \\ / | \\''',
// 프레임 5: 복귀 (승리 포즈)
'''
\\o/ O
/|\\ /|\\
/ \\ / | \\''',
],
frameIntervalMs: 220,
);
/// 시스템 전투 애니메이션 (커널 모양)
const battleAnimationSystem = AsciiAnimationData(
frames: [
// 프레임 1: 대치
'''
o .-.
/|\\ (o.o)
/ \\ |=|''',
// 프레임 2: 접근
'''
o .-.
/|\\ (o.o)
/ \\ |=|''',
// 프레임 3: 공격 (근접)
'''
o_/ .-.
/| (>.>)
/ \\ |=|''',
// 프레임 4: 히트
'''
o **** .-.
/|\\*** (X.X)
/ \\ |~|''',
// 프레임 5: 복귀 (승리 포즈)
'''
\\o/ .-.
/|\\ (-.-)
/ \\ |=|''',
],
frameIntervalMs: 250,
);
/// 암호화 전투 애니메이션 (자물쇠 모양)
const battleAnimationCrypto = AsciiAnimationData(
frames: [
// 프레임 1: 대치
'''
o __/\\__
/|\\ < (O)(O) >
/ \\ \\ \\/ /''',
// 프레임 2: 접근
'''
o __/\\__
/|\\ < (O)(O) >
/ \\ \\ \\/ /''',
// 프레임 3: 공격 (근접)
'''
o_/ __/\\__
/| < (X)(X) >
/ \\ \\ \\/ /''',
// 프레임 4: 히트
'''
o **** __/\\__
/|\\***< (X)(X) >
/ \\ \\ ~~ /''',
// 프레임 5: 복귀 (승리 포즈)
'''
\\o/ __/\\__
/|\\ < (-)(-)>
/ \\ \\ \\/ /''',
],
frameIntervalMs: 200,
);
/// AI 전투 애니메이션 (뉴럴넷 모양)
const battleAnimationAI = AsciiAnimationData(
frames: [
// 프레임 1: 대치
'''
o .---.
/|\\ ( o o )
/ \\ ~~~~~''',
// 프레임 2: 접근
'''
o .---.
/|\\ ( o o )
/ \\ ~~~~~''',
// 프레임 3: 공격 (근접)
'''
o_/ .---.
/| ( >.< )
/ \\ ~~~~~''',
// 프레임 4: 히트
'''
o **** .---.
/|\\** ( X X )
/ \\ ~~~~~''',
// 프레임 5: 복귀 (승리 포즈)
'''
\\o/ .---.
/|\\ ( - - )
/ \\ ~~~~~''',
],
frameIntervalMs: 280,
);
/// 보스 전투 애니메이션 (드래곤/보스 모양)
const battleAnimationBoss = AsciiAnimationData(
frames: [
// 프레임 1: 대치
'''
o /\\ /\\
/|\\ ( o V o )
/ \\ \\ ~~~ /''',
// 프레임 2: 접근
'''
o /\\ /\\
/|\\ ( o V o )
/ \\ \\ ~~~ /''',
// 프레임 3: 공격 (근접)
'''
o_/ /\\ /\\
/| ( X V X )
/ \\ \\ ~~~ /''',
// 프레임 4: 히트
'''
o ****/\\ /\\
/|\\** ( X V X )
/ \\ \\ ~~~ /''',
// 프레임 5: 복귀 (승리 포즈)
'''
\\o/ /\\ /\\
/|\\ ( - V - )
/ \\ \\ ~~~ /''',
],
frameIntervalMs: 200,
);
/// 몬스터 카테고리별 전투 애니메이션 반환
AsciiAnimationData getBattleAnimation(MonsterCategory category) {
return switch (category) {
MonsterCategory.bug => battleAnimationBug,
MonsterCategory.malware => battleAnimationMalware,
MonsterCategory.network => battleAnimationNetwork,
MonsterCategory.system => battleAnimationSystem,
MonsterCategory.crypto => battleAnimationCrypto,
MonsterCategory.ai => battleAnimationAI,
MonsterCategory.boss => battleAnimationBoss,
};
}
/// 레벨업 축하 애니메이션 (8줄 x 40자 고정)
/// 캐릭터 위치: 머리=4, 몸통=5, 다리=6 (전투 애니메이션 기준)
const levelUpAnimation = AsciiAnimationData(
frames: [
// 프레임 1: 시작
' * * * \n'
' * * * \n'
' * * \n'
' \n'
' * \\O/ * \n'
' /|\\ \n'
' * / \\ * \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 2: 별 확산
' * * * \n'
' * * \n'
' * * \n'
' \n'
' * \\O/ * \n'
' /|\\ \n'
' * / \\ * \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 3: 레벨업 텍스트
' * L E V E L U P ! * \n'
' * * \n'
' * * \n'
' \n'
' * \\O/ * \n'
' /|\\ \n'
' * / \\ * \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 4: 빛나는 캐릭터
' * * * * * \n'
' * * \n'
' * * * * \n'
' \n'
' * \\O/ * \n'
' * /|\\ * \n'
' * / \\ * \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 5: 마무리
' + \n'
' +++ \n'
' +++++ \n'
' \n'
' \\O/ \n'
' /|\\ \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
],
frameIntervalMs: 300,
);
/// 퀘스트 완료 애니메이션 (8줄 x 40자 고정)
/// 캐릭터 위치: 머리=4, 몸통=5, 다리=6 (전투 애니메이션 기준)
const questCompleteAnimation = AsciiAnimationData(
frames: [
// 프레임 1: 퀘스트 깃발
' [=======] \n'
' || || \n'
' || || \n'
' ||_____|| \n'
' \\O/ \n'
' /|\\ \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 2: 승리
' [QUEST!] \n'
' || || \n'
' || || \n'
' ||_____|| \n'
' \\\\O// \n'
' /|\\ \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 3: 보상
' COMPLETE! \n'
' \n'
' \n'
' \$\$\$ \n'
' \\O/ \$\$\$ \n'
' /|\\ \$\$\$ \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 4: 축하
' * * * * * \n'
' \n'
' \n'
' * * * * * \n'
' \\O/ +EXP \n'
' /|\\ +GOLD \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 5: 마무리
' [ VICTORY! ] \n'
' \n'
' \n'
' \n'
' \\O/ \n'
' /|\\ \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
],
frameIntervalMs: 350,
);
/// Act 완료 애니메이션 (8줄 x 40자 고정)
/// 캐릭터 위치: 머리=4, 몸통=5, 다리=6 (전투 애니메이션 기준)
const actCompleteAnimation = AsciiAnimationData(
frames: [
// 프레임 1: 커튼
' ______________________________ \n'
' | A C T | \n'
' | C O M P L E T E | \n'
' |______________________________| \n'
' \n'
' \n'
' \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 2: 캐릭터 등장
' ______________________________ \n'
' | * * * * * | \n'
' | * * * * * | \n'
' |______________________________| \n'
' \\O/ \n'
' /|\\ \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 3: 플롯 진행 표시
' ______________________________ \n'
' | PROLOGUE --> ACT | \n'
' | STORY CONTINUES --> | \n'
' |______________________________| \n'
' \\O/ \n'
' /|\\ \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 4: 축하
' ______________________________ \n'
' | * * * * * | \n'
' | * * * * * | \n'
' |______________________________| \n'
' * \\O/ * \n'
' /|\\ \n'
' * / \\ * \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 5: 마무리
' ______________________________ \n'
' | +---------+ | \n'
' | | NEXT | | \n'
' |____| CHAPTER |_______________| \n'
' \\O/ \n'
' /|\\ \n'
' / \\ \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
],
frameIntervalMs: 400,
);
/// 부활 애니메이션 (8줄 x 40자 고정)
/// 어둠에서 빛으로, 캐릭터가 다시 일어남
const resurrectionAnimation = AsciiAnimationData(
frames: [
// 프레임 1: 어둠
' \n'
' . . . . . . \n'
' . . \n'
' . R . I . P . \n'
' . ___ . \n'
' . |___| . \n'
' . . . . . . \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 2: 빛이 비침
' * \n'
' . .|. . . . \n'
' . | . \n'
' . | . \n'
' . _O_ . \n'
' . /___\\ . \n'
' . . . . . . \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 3: 일어나는 중
' * * * \n'
' . | . . . \n'
' . | . \n'
' . O . \n'
' . /|\\ . \n'
' . | | . \n'
' . . . . . . \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 4: 서있음
' * * * * * \n'
' \n'
' \n'
' O \n'
' /|\\ \n'
' / \\ \n'
' \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
// 프레임 5: 부활 완료
' * RESURRECTED! * \n'
' \n'
' \n'
' \\O/ \n'
' /|\\ \n'
' / \\ \n'
' \n'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
],
frameIntervalMs: 600, // 5프레임 × 600ms = 3초
);
/// 타입별 애니메이션 데이터 반환 (기본 전투는 bug)
AsciiAnimationData getAnimationData(AsciiAnimationType type) {
return switch (type) {
AsciiAnimationType.battle => battleAnimationBug,
AsciiAnimationType.town => townAnimation,
AsciiAnimationType.walking => walkingAnimation,
AsciiAnimationType.levelUp => levelUpAnimation,
AsciiAnimationType.questComplete => questCompleteAnimation,
AsciiAnimationType.actComplete => actCompleteAnimation,
AsciiAnimationType.resurrection => resurrectionAnimation,
};
}