test: shop_service, arena_service, potion_service 테스트 추가
- shop_service 17개 (가격 계산, 장비 생성, 자동 구매) - arena_service 12개 (매칭, 장비 교환, 점수 계산) - potion_service 29개 (사용, 구매, 드랍, 긴급 선택)
This commit is contained in:
326
test/core/engine/arena_service_test.dart
Normal file
326
test/core/engine/arena_service_test.dart
Normal file
@@ -0,0 +1,326 @@
|
||||
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<EquipmentItem>? 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<EquipmentItem> _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,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user