293 lines
7.7 KiB
Dart
293 lines
7.7 KiB
Dart
import 'package:asciineverdie/src/core/engine/game_mutations.dart';
|
|
import 'package:asciineverdie/src/core/engine/progress_service.dart';
|
|
import 'package:asciineverdie/src/core/engine/reward_service.dart';
|
|
import 'package:asciineverdie/src/core/model/combat_state.dart';
|
|
import 'package:asciineverdie/src/core/model/combat_stats.dart';
|
|
import 'package:asciineverdie/src/core/model/game_state.dart';
|
|
import 'package:asciineverdie/src/core/model/game_statistics.dart';
|
|
import 'package:asciineverdie/src/core/model/hall_of_fame.dart';
|
|
import 'package:asciineverdie/src/core/model/monetization_state.dart';
|
|
import 'package:asciineverdie/src/core/model/monster_combat_stats.dart';
|
|
import 'package:asciineverdie/src/core/model/pq_config.dart';
|
|
import 'package:asciineverdie/src/core/storage/hall_of_fame_storage.dart';
|
|
import 'package:asciineverdie/src/core/storage/save_manager.dart';
|
|
import 'package:asciineverdie/src/core/storage/save_repository.dart';
|
|
import 'package:asciineverdie/src/core/storage/save_service.dart';
|
|
import 'package:asciineverdie/src/core/storage/statistics_storage.dart';
|
|
import 'package:asciineverdie/src/core/util/balance_constants.dart';
|
|
|
|
export 'package:asciineverdie/src/core/storage/save_repository.dart'
|
|
show SaveOutcome;
|
|
export 'package:asciineverdie/src/core/storage/save_service.dart'
|
|
show SaveFileInfo;
|
|
|
|
/// 테스트용 Fake SaveManager
|
|
///
|
|
/// 여러 테스트 파일에서 중복되던 Mock을 통합
|
|
class FakeSaveManager implements SaveManager {
|
|
final List<GameState> savedStates = [];
|
|
|
|
/// 커스텀 로드 동작 설정
|
|
(SaveOutcome, GameState?, bool, MonetizationState?) Function(String?)? onLoad;
|
|
|
|
/// 저장 결과 설정 (기본: 성공)
|
|
SaveOutcome saveOutcome = const SaveOutcome.success();
|
|
|
|
@override
|
|
Future<SaveOutcome> saveState(
|
|
GameState state, {
|
|
String? fileName,
|
|
bool cheatsEnabled = false,
|
|
MonetizationState? monetization,
|
|
}) async {
|
|
savedStates.add(state);
|
|
return saveOutcome;
|
|
}
|
|
|
|
@override
|
|
Future<(SaveOutcome, GameState?, bool, MonetizationState?)> loadState({
|
|
String? fileName,
|
|
}) async {
|
|
if (onLoad != null) {
|
|
return onLoad!(fileName);
|
|
}
|
|
return (const SaveOutcome.success(), null, false, null);
|
|
}
|
|
|
|
@override
|
|
Future<List<SaveFileInfo>> listSaves() async => [];
|
|
|
|
@override
|
|
Future<SaveOutcome> deleteSave({String? fileName}) async {
|
|
return const SaveOutcome.success();
|
|
}
|
|
|
|
@override
|
|
Future<bool> saveExists({String? fileName}) async => false;
|
|
}
|
|
|
|
/// 테스트용 팩토리 클래스
|
|
///
|
|
/// 테스트에서 자주 사용되는 객체 생성을 중앙화
|
|
class MockFactories {
|
|
MockFactories._();
|
|
|
|
/// 기본 PqConfig 생성
|
|
static const PqConfig config = PqConfig();
|
|
|
|
/// GameMutations 생성
|
|
static GameMutations createMutations([PqConfig? cfg]) {
|
|
return GameMutations(cfg ?? config);
|
|
}
|
|
|
|
/// ProgressService 생성
|
|
static ProgressService createProgressService([PqConfig? cfg]) {
|
|
final c = cfg ?? config;
|
|
final mutations = GameMutations(c);
|
|
return ProgressService(
|
|
config: c,
|
|
mutations: mutations,
|
|
rewards: RewardService(mutations, c),
|
|
);
|
|
}
|
|
|
|
/// 기본 GameState 생성
|
|
///
|
|
/// [seed]: 결정론적 랜덤 시드
|
|
/// [level]: 캐릭터 레벨
|
|
static GameState createGameState({int seed = 42, int level = 1}) {
|
|
return GameState.withSeed(seed: seed);
|
|
}
|
|
|
|
/// 테스트용 CombatState 생성
|
|
static CombatState createCombat({
|
|
int playerHpMax = 100,
|
|
int playerHpCurrent = 100,
|
|
int monsterHpMax = 50,
|
|
int monsterHpCurrent = 50,
|
|
int monsterLevel = 1,
|
|
String monsterName = 'Test Monster',
|
|
}) {
|
|
final playerStats = CombatStats.empty().copyWith(
|
|
hpMax: playerHpMax,
|
|
hpCurrent: playerHpCurrent,
|
|
mpMax: 50,
|
|
mpCurrent: 50,
|
|
atk: 20,
|
|
def: 10,
|
|
attackDelayMs: 1000,
|
|
);
|
|
|
|
final monsterStats = MonsterCombatStats(
|
|
name: monsterName,
|
|
level: monsterLevel,
|
|
atk: 10,
|
|
def: 5,
|
|
magDef: 5,
|
|
hpMax: monsterHpMax,
|
|
hpCurrent: monsterHpCurrent,
|
|
criRate: 0.05,
|
|
criDamage: 1.5,
|
|
evasion: 0.0,
|
|
accuracy: 0.8,
|
|
attackDelayMs: 1000,
|
|
expReward: 100,
|
|
);
|
|
|
|
return CombatState(
|
|
playerStats: playerStats,
|
|
monsterStats: monsterStats,
|
|
playerAttackAccumulatorMs: 0,
|
|
monsterAttackAccumulatorMs: 0,
|
|
totalDamageDealt: 0,
|
|
totalDamageTaken: 0,
|
|
turnsElapsed: 0,
|
|
isActive: true,
|
|
);
|
|
}
|
|
|
|
/// 테스트용 MonsterCombatStats 생성
|
|
static MonsterCombatStats createMonsterStats({
|
|
String name = 'Test Monster',
|
|
int level = 1,
|
|
int atk = 10,
|
|
int def = 5,
|
|
int magDef = 5,
|
|
int hpMax = 100,
|
|
int? hpCurrent,
|
|
double criRate = 0.05,
|
|
double criDamage = 1.5,
|
|
double evasion = 0.0,
|
|
double accuracy = 0.8,
|
|
int attackDelayMs = 1000,
|
|
int expReward = 100,
|
|
}) {
|
|
return MonsterCombatStats(
|
|
name: name,
|
|
level: level,
|
|
atk: atk,
|
|
def: def,
|
|
magDef: magDef,
|
|
hpMax: hpMax,
|
|
hpCurrent: hpCurrent ?? hpMax,
|
|
criRate: criRate,
|
|
criDamage: criDamage,
|
|
evasion: evasion,
|
|
accuracy: accuracy,
|
|
attackDelayMs: attackDelayMs,
|
|
expReward: expReward,
|
|
);
|
|
}
|
|
|
|
/// 밸런스 상수 기반 몬스터 스탯 생성
|
|
static MonsterCombatStats createBalancedMonsterStats({
|
|
required int level,
|
|
MonsterType type = MonsterType.normal,
|
|
}) {
|
|
final base = MonsterBaseStats.generate(level, type);
|
|
return MonsterCombatStats(
|
|
name: 'Balanced Monster Lv$level',
|
|
level: level,
|
|
atk: base.atk,
|
|
def: base.def,
|
|
magDef: base.def, // 물리 방어와 동일
|
|
hpMax: base.hp,
|
|
hpCurrent: base.hp,
|
|
criRate: 0.05,
|
|
criDamage: 1.5,
|
|
evasion: 0.0,
|
|
accuracy: 0.8,
|
|
attackDelayMs: 1000,
|
|
expReward: base.exp,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 테스트용 Fake HallOfFameStorage
|
|
///
|
|
/// 파일 시스템 접근 없이 메모리에서 동작
|
|
class FakeHallOfFameStorage extends HallOfFameStorage {
|
|
HallOfFame _hallOfFame = HallOfFame.empty();
|
|
|
|
@override
|
|
Future<HallOfFame> load() async => _hallOfFame;
|
|
|
|
@override
|
|
Future<bool> save(HallOfFame hallOfFame) async {
|
|
_hallOfFame = hallOfFame;
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> addEntry(HallOfFameEntry entry) async {
|
|
_hallOfFame = _hallOfFame.addEntry(entry);
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> deleteEntry(String id) async {
|
|
_hallOfFame = _hallOfFame.removeEntry(id);
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> clear() async {
|
|
_hallOfFame = HallOfFame.empty();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// 테스트용 Fake StatisticsStorage
|
|
///
|
|
/// 파일 시스템 접근 없이 메모리에서 동작
|
|
class FakeStatisticsStorage extends StatisticsStorage {
|
|
CumulativeStatistics _stats = CumulativeStatistics.empty();
|
|
|
|
@override
|
|
Future<CumulativeStatistics> loadCumulative() async => _stats;
|
|
|
|
@override
|
|
Future<bool> saveCumulative(CumulativeStatistics stats) async {
|
|
_stats = stats;
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> mergeSession(SessionStatistics session) async {
|
|
_stats = _stats.mergeSession(session);
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> updateHighestLevel(int level) async {
|
|
if (level <= _stats.highestLevel) return true;
|
|
_stats = _stats.updateHighestLevel(level);
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> updateHighestGold(int gold) async {
|
|
if (gold <= _stats.highestGoldHeld) return true;
|
|
_stats = _stats.updateHighestGold(gold);
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> recordGameStart() async {
|
|
_stats = _stats.recordGameStart();
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> recordGameComplete() async {
|
|
_stats = _stats.recordGameComplete();
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> clear() async {
|
|
_stats = CumulativeStatistics.empty();
|
|
return true;
|
|
}
|
|
}
|