Files
asciinevrdie/lib/src/shared/animation/ascii_animation_data.dart
JiWoong Sul 8f351df0b6 refactor(shared): animation, l10n, theme 모듈을 core에서 shared로 이동
- core/animation → shared/animation
- core/l10n → shared/l10n
- core/constants/ascii_colors → shared/theme/ascii_colors
- import 경로 업데이트
2026-02-23 15:49:14 +09:00

761 lines
26 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
};
}