import 'package:asciineverdie/src/core/engine/arena_service.dart'; import 'package:asciineverdie/src/core/model/arena_match.dart'; import 'package:asciineverdie/src/core/model/combat_stats.dart'; import 'package:asciineverdie/src/core/model/equipment_item.dart'; import 'package:asciineverdie/src/core/model/equipment_slot.dart'; import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; import 'package:asciineverdie/src/core/model/item_stats.dart'; import 'package:asciineverdie/src/core/util/deterministic_random.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { late ArenaService service; setUp(() { service = ArenaService(rng: DeterministicRandom(42)); }); // ============================================================================ // 상대 결정 (matchmaking) // ============================================================================ group('findOpponent', () { test('엔트리가 2개 미만이면 null 반환', () { final hof = HallOfFame(entries: [_createEntry(id: 'a', level: 10)]); final result = service.findOpponent(hof, 'a'); expect(result, isNull); }); test('1위 도전자는 2위와 대결', () { // 점수(score) 높은 순: a(Lv50) > b(Lv30) > c(Lv10) final hof = HallOfFame( entries: [ _createEntry(id: 'a', level: 50), _createEntry(id: 'b', level: 30), _createEntry(id: 'c', level: 10), ], ); final opponent = service.findOpponent(hof, 'a'); expect(opponent, isNotNull); expect(opponent!.id, 'b'); }); test('2위 이하는 바로 위 순위와 대결', () { final hof = HallOfFame( entries: [ _createEntry(id: 'a', level: 50), _createEntry(id: 'b', level: 30), _createEntry(id: 'c', level: 10), ], ); // c는 3위 → 2위(b)와 대결 final opponent = service.findOpponent(hof, 'c'); expect(opponent, isNotNull); expect(opponent!.id, 'b'); }); test('존재하지 않는 ID는 null 반환', () { final hof = HallOfFame( entries: [ _createEntry(id: 'a', level: 50), _createEntry(id: 'b', level: 30), ], ); final opponent = service.findOpponent(hof, 'nonexistent'); expect(opponent, isNull); }); }); // ============================================================================ // 장비 교환 (equipment exchange) // ============================================================================ group('createResultFromSimulation', () { test('도전자 승리 시 상대 베팅 슬롯 장비를 획득', () { final challengerEquip = _createEquipmentList(helmName: 'Basic Helm'); final opponentEquip = _createEquipmentList(helmName: 'Epic Helm'); final match = ArenaMatch( challenger: _createEntry( id: 'challenger', level: 10, equipment: challengerEquip, ), opponent: _createEntry( id: 'opponent', level: 10, equipment: opponentEquip, ), // 도전자가 상대의 helm 슬롯을 노림 challengerBettingSlot: EquipmentSlot.helm, // 상대가 도전자의 shield 슬롯을 노림 opponentBettingSlot: EquipmentSlot.shield, ); final result = service.createResultFromSimulation( match: match, challengerHp: 50, opponentHp: 0, turns: 10, ); // 도전자 승리 expect(result.isVictory, isTrue); // 도전자가 상대의 helm(Epic Helm)을 획득 final challengerHelm = result.updatedChallenger.finalEquipment! .firstWhere((e) => e.slot == EquipmentSlot.helm); expect(challengerHelm.name, 'Epic Helm'); // 상대의 helm은 기본 장비로 대체 final opponentHelm = result.updatedOpponent.finalEquipment!.firstWhere( (e) => e.slot == EquipmentSlot.helm, ); expect(opponentHelm.name, isNot('Epic Helm')); }); test('도전자 패배 시 도전자 베팅 슬롯 장비를 상대가 획득', () { final challengerEquip = _createEquipmentList(shieldName: 'Rare Shield'); final opponentEquip = _createEquipmentList(shieldName: 'Old Shield'); final match = ArenaMatch( challenger: _createEntry( id: 'challenger', level: 10, equipment: challengerEquip, ), opponent: _createEntry( id: 'opponent', level: 10, equipment: opponentEquip, ), challengerBettingSlot: EquipmentSlot.helm, // 상대가 도전자의 shield를 노림 opponentBettingSlot: EquipmentSlot.shield, ); final result = service.createResultFromSimulation( match: match, challengerHp: 0, opponentHp: 50, turns: 10, ); // 도전자 패배 expect(result.isVictory, isFalse); // 상대가 도전자의 shield(Rare Shield)를 획득 final opponentShield = result.updatedOpponent.finalEquipment!.firstWhere( (e) => e.slot == EquipmentSlot.shield, ); expect(opponentShield.name, 'Rare Shield'); // 도전자의 shield는 기본 장비로 대체 final challengerShield = result.updatedChallenger.finalEquipment! .firstWhere((e) => e.slot == EquipmentSlot.shield); expect(challengerShield.name, isNot('Rare Shield')); }); test('양쪽 모두 HP > 0이면 패배 처리', () { final match = _createSimpleMatch(); final result = service.createResultFromSimulation( match: match, challengerHp: 50, opponentHp: 50, turns: 100, ); // 도전자 HP > 0이지만 상대도 > 0이므로 패배 expect(result.isVictory, isFalse); }); }); // ============================================================================ // 베팅 슬롯 선택 (betting slot selection) // ============================================================================ group('selectOpponentBettingSlot', () { test('장비가 없으면 기본 슬롯(helm) 반환', () { final entry = _createEntry(id: 'test', level: 5, equipment: []); final slot = service.selectOpponentBettingSlot(entry); expect(slot, EquipmentSlot.helm); }); test('무기(weapon)는 약탈 대상에서 제외', () { // 무기만 높은 점수(score)를 가진 장비 final equipment = [ const EquipmentItem( name: 'God Keyboard', slot: EquipmentSlot.weapon, level: 99, weight: 5, stats: ItemStats(atk: 999), rarity: ItemRarity.legendary, ), const EquipmentItem( name: 'Basic Shield', slot: EquipmentSlot.shield, level: 1, weight: 8, stats: ItemStats(def: 1), rarity: ItemRarity.common, ), ]; final entry = _createEntry(id: 'test', level: 50, equipment: equipment); final slot = service.selectOpponentBettingSlot(entry); // 무기가 아닌 shield가 선택되어야 함 expect(slot, isNot(EquipmentSlot.weapon)); }); }); group('getBettableSlots', () { test('무기(weapon)를 제외한 슬롯 목록 반환', () { final slots = service.getBettableSlots(); expect(slots, isNot(contains(EquipmentSlot.weapon))); expect(slots.length, EquipmentSlot.values.length - 1); }); }); // ============================================================================ // 아레나 점수 계산 (arena score) // ============================================================================ group('HallOfFameArenaX.calculateArenaScore', () { test('레벨이 높을수록 점수가 높다', () { final low = _createEntry(id: 'low', level: 5); final high = _createEntry(id: 'high', level: 50); final lowScore = HallOfFameArenaX.calculateArenaScore(low); final highScore = HallOfFameArenaX.calculateArenaScore(high); expect(highScore, greaterThan(lowScore)); }); test('장비가 좋을수록 점수가 높다', () { final weak = _createEntry(id: 'weak', level: 10); final strong = _createEntry( id: 'strong', level: 10, equipment: [ const EquipmentItem( name: 'Epic Sword', slot: EquipmentSlot.weapon, level: 50, weight: 5, stats: ItemStats(atk: 100), rarity: ItemRarity.epic, ), ], ); final weakScore = HallOfFameArenaX.calculateArenaScore(weak); final strongScore = HallOfFameArenaX.calculateArenaScore(strong); expect(strongScore, greaterThan(weakScore)); }); }); } // ============================================================================ // 테스트 헬퍼 (test helpers) // ============================================================================ /// 테스트용 HallOfFameEntry 생성 HallOfFameEntry _createEntry({ required String id, required int level, List? equipment, CombatStats? stats, }) { return HallOfFameEntry( id: id, characterName: 'Test Character $id', race: 'Human', klass: 'Fighter', level: level, totalPlayTimeMs: 3600000, totalDeaths: 0, monstersKilled: 100, questsCompleted: 10, clearedAt: DateTime(2025, 1, 1), finalStats: stats, finalEquipment: equipment, ); } /// 테스트용 장비 목록 생성 (helm, shield 이름 커스텀 가능) List _createEquipmentList({ String helmName = 'Test Helm', String shieldName = 'Test Shield', }) { return [ EquipmentItem.defaultWeapon(), EquipmentItem( name: shieldName, slot: EquipmentSlot.shield, level: 5, weight: 8, stats: const ItemStats(def: 5), rarity: ItemRarity.common, ), EquipmentItem( name: helmName, slot: EquipmentSlot.helm, level: 5, weight: 4, stats: const ItemStats(def: 3), rarity: ItemRarity.common, ), ]; } /// 간단한 ArenaMatch 생성 ArenaMatch _createSimpleMatch() { final equip = _createEquipmentList(); return ArenaMatch( challenger: _createEntry(id: 'c', level: 10, equipment: equip), opponent: _createEntry(id: 'o', level: 10, equipment: equip), challengerBettingSlot: EquipmentSlot.helm, opponentBettingSlot: EquipmentSlot.shield, ); }