refactor(engine): 시장 로직을 MarketService로 분리

- ProgressService에서 판매/구매 로직 추출
- SellResult 결과 클래스 정의
- 골드 관리 및 아이템 거래 처리
This commit is contained in:
JiWoong Sul
2026-01-15 01:53:24 +09:00
parent 77dfa48ddf
commit 90c133d577

View File

@@ -0,0 +1,166 @@
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
import 'package:asciineverdie/src/core/engine/potion_service.dart';
import 'package:asciineverdie/src/core/engine/shop_service.dart';
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/item_stats.dart';
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic;
/// 판매 처리 결과
class SellResult {
const SellResult({
required this.state,
required this.continuesSelling,
});
final GameState state;
final bool continuesSelling;
}
/// 시장/판매/구매 서비스
///
/// ProgressService에서 분리된 시장 로직 담당:
/// - 장비 구매
/// - 아이템 판매
/// - 골드 관리
class MarketService {
const MarketService({required this.rng});
final DeterministicRandom rng;
/// 인벤토리에서 Gold 수량 반환
int getGold(GameState state) {
return state.inventory.gold;
}
/// 장비 구매 완료 처리 (개선된 로직)
///
/// 1순위: 빈 슬롯에 Common 장비 최대한 채우기
/// 2순위: 골드 남으면 물약 구매
GameState completeBuying(GameState state) {
var nextState = state;
final level = state.traits.level;
final shopService = ShopService(rng: rng);
// 1. 빈 슬롯 목록 수집
final emptySlots = <int>[];
for (var i = 0; i < Equipment.slotCount; i++) {
if (nextState.equipment.getItemByIndex(i).isEmpty) {
emptySlots.add(i);
}
}
// 2. 골드가 허용하는 한 빈 슬롯에 Common 장비 구매
for (final slotIndex in emptySlots) {
final slot = EquipmentSlot.values[slotIndex];
final item = shopService.generateShopItem(
playerLevel: level,
slot: slot,
targetRarity: ItemRarity.common,
);
final price = shopService.calculateBuyPrice(item);
if (nextState.inventory.gold >= price) {
nextState = nextState.copyWith(
inventory: nextState.inventory.copyWith(
gold: nextState.inventory.gold - price,
),
equipment: nextState.equipment
.setItemByIndex(slotIndex, item)
.copyWith(bestIndex: slotIndex),
);
} else {
break; // 골드 부족 시 중단
}
}
// 3. 물약 자동 구매 (남은 골드의 20% 사용)
final potionService = const PotionService();
final purchaseResult = potionService.autoPurchasePotions(
playerLevel: level,
inventory: nextState.potionInventory,
gold: nextState.inventory.gold,
spendRatio: 0.20,
);
if (purchaseResult.success && purchaseResult.newInventory != null) {
nextState = nextState.copyWith(
inventory: nextState.inventory.copyWith(gold: purchaseResult.newGold),
potionInventory: purchaseResult.newInventory,
);
}
return nextState;
}
/// 판매 처리
///
/// [state] 현재 게임 상태
/// Returns: 업데이트된 상태와 판매 계속 여부
SellResult processSell(GameState state) {
final taskType = state.progress.currentTask.type;
var items = [...state.inventory.items];
var goldAmount = state.inventory.gold;
// sell 태스크 완료 시 아이템 판매 (원본 Main.pas:636-643)
if (taskType == TaskType.sell) {
// 첫 번째 아이템 찾기 (items에는 Gold가 없음)
if (items.isNotEmpty) {
final item = items.first;
final level = state.traits.level;
// 가격 계산: 수량 * 레벨
var price = item.count * level;
// " of " 포함 시 보너스 (원본 639-640)
if (item.name.contains(' of ')) {
price =
price *
(1 + pq_logic.randomLow(rng, 10)) *
(1 + pq_logic.randomLow(rng, level));
}
// 아이템 삭제
items.removeAt(0);
// Gold 추가 (inventory.gold 필드 사용)
goldAmount += price;
}
}
// 판매할 아이템이 남아있는지 확인
final hasItemsToSell = items.isNotEmpty;
if (hasItemsToSell) {
// 다음 아이템 판매 태스크 시작
final nextItem = items.first;
final translatedName = l10n.translateItemNameL10n(nextItem.name);
final itemDesc = l10n.indefiniteL10n(translatedName, nextItem.count);
final taskResult = pq_logic.startTask(
state.progress,
l10n.taskSelling(itemDesc),
1 * 1000,
);
final progress = taskResult.progress.copyWith(
currentTask: TaskInfo(caption: taskResult.caption, type: TaskType.sell),
currentCombat: null, // 비전투 태스크이므로 전투 상태 초기화
);
return SellResult(
state: state.copyWith(
inventory: state.inventory.copyWith(gold: goldAmount, items: items),
progress: progress,
),
continuesSelling: true,
);
}
// 판매 완료 - 인벤토리 업데이트만 하고 다음 태스크로
return SellResult(
state: state.copyWith(
inventory: state.inventory.copyWith(gold: goldAmount, items: items),
),
continuesSelling: false,
);
}
}