[Refactor] Fix GameController

This commit is contained in:
2026-03-04 06:48:21 +07:00
parent 244f635062
commit 3031d2e4c2
24 changed files with 708 additions and 303 deletions
+5
View File
@@ -14,6 +14,7 @@ using YachtDice.Player;
using YachtDice.Scoring;
using YachtDice.Shop;
using YachtDice.UI;
using YachtDice.UI.Presentation;
namespace YachtDice.DI
{
@@ -61,6 +62,10 @@ namespace YachtDice.DI
// Shop
builder.Register<ShopModel>(Lifetime.Singleton);
// Presentation services
builder.Register<IGameSaveService, GameSaveService>(Lifetime.Singleton);
builder.Register<IScoreSummaryService, ScoreSummaryService>(Lifetime.Singleton);
// Scene MonoBehaviour components
builder.RegisterComponent(scoringSystem);
builder.RegisterComponent(currencyBank);
+1
View File
@@ -16,6 +16,7 @@ namespace YachtDice.Game
public int CurrentRoll { get; private set; }
public int CurrentTurn { get; private set; }
public int MaxRollsPerTurn => maxRollsPerTurn;
public bool CanRoll => CurrentRoll < maxRollsPerTurn && !_diceManager.IsAnyRolling;
public bool CanScore => CurrentRoll > 0 && !_diceManager.IsAnyRolling;
+2 -2
View File
@@ -87,10 +87,10 @@ namespace YachtDice.UI
}
}
public void ResetForNewGame()
public void ResetForNewGame(int maxRolls = 3)
{
ResetForNewTurn();
SetRollButtonState(true, 0, 3);
SetRollButtonState(true, 0, maxRolls);
}
private void OnDestroy()
+46 -300
View File
@@ -1,45 +1,39 @@
using System.Collections.Generic;
using System;
using UnityEngine;
using VContainer;
using YachtDice.Categories;
using YachtDice.Dice;
using YachtDice.Game;
using YachtDice.Scoring;
using YachtDice.Economy;
using YachtDice.Shop;
using YachtDice.Game;
using YachtDice.Inventory;
using YachtDice.Persistence;
using YachtDice.Player;
using YachtDice.Modifiers.Definition;
using YachtDice.Modifiers.Runtime;
using YachtDice.Scoring;
using YachtDice.Shop;
using YachtDice.UI.Presentation;
namespace YachtDice.UI
{
public class GameController : MonoBehaviour
{
[Header("Views")]
[Header("MVP Views")]
[SerializeField] private ScoreCardView scoreCardView;
[SerializeField] private DicePanelView dicePanelView;
[SerializeField] private GameInfoView gameInfoView;
[Header("Settings")]
[SerializeField] private int maxRollsPerTurn = 3;
private const int UpperBonusThreshold = 63;
private const int UpperBonusValue = 35;
private GameManager _gameManager;
private ScoringSystem _scoringSystem;
private DiceManager _diceManager;
private CurrencyBank _currencyBank;
private ShopController _shopController;
private InventoryController _inventoryController;
private ModifierRegistry _modifierRegistry;
private CategoryCatalog _categoryCatalog;
private ModifierCatalog _modifierCatalog;
private DiceCatalog _diceCatalog;
private ShopModel _shopModel;
private PlayerModel _playerModel;
private IGameSaveService _saveService;
private IScoreSummaryService _scoreSummaryService;
private DicePanelPresenter _dicePanelPresenter;
private ScoreCardPresenter _scoreCardPresenter;
private GameInfoPresenter _gameInfoPresenter;
private GameFlowPresenter _gameFlowPresenter;
[Inject]
public void Construct(
@@ -49,12 +43,10 @@ namespace YachtDice.UI
CurrencyBank currencyBank,
ShopController shopController,
InventoryController inventoryController,
ModifierRegistry modifierRegistry,
CategoryCatalog categoryCatalog,
ModifierCatalog modifierCatalog,
DiceCatalog diceCatalog,
ShopModel shopModel,
PlayerModel playerModel)
PlayerModel playerModel,
IGameSaveService saveService,
IScoreSummaryService scoreSummaryService)
{
this._gameManager = gameManager;
this._scoringSystem = scoringSystem;
@@ -62,297 +54,51 @@ namespace YachtDice.UI
this._currencyBank = currencyBank;
this._shopController = shopController;
this._inventoryController = inventoryController;
this._modifierRegistry = modifierRegistry;
this._categoryCatalog = categoryCatalog;
this._modifierCatalog = modifierCatalog;
this._diceCatalog = diceCatalog;
this._shopModel = shopModel;
this._playerModel = playerModel;
this._saveService = saveService;
this._scoreSummaryService = scoreSummaryService;
}
// ── Lifecycle ──────────────────────────────────────────────
private void Start()
{
// Model → Controller
_gameManager.OnTurnStarted += HandleTurnStarted;
_gameManager.OnRollComplete += HandleRollComplete;
_gameManager.OnScored += HandleScored;
_gameManager.OnGameOver += HandleGameOver;
_diceManager.OnDiceSettled += HandleDiceSettled;
_dicePanelPresenter = new DicePanelPresenter(dicePanelView, _gameManager, _diceManager);
_scoreCardPresenter = new ScoreCardPresenter(scoreCardView, _categoryCatalog, _scoringSystem, _diceManager);
_gameInfoPresenter = new GameInfoPresenter(gameInfoView);
// View → Controller
scoreCardView.OnCategorySelected += HandleCategorySelected;
dicePanelView.OnRollClicked += HandleRollClicked;
dicePanelView.OnDiceToggled += HandleDiceToggled;
gameInfoView.OnNewGameClicked += HandleNewGameClicked;
gameInfoView.OnShopClicked += HandleShopClicked;
gameInfoView.OnInventoryClicked += HandleInventoryClicked;
_gameFlowPresenter = new GameFlowPresenter(
_gameManager,
_scoringSystem,
_diceManager,
_currencyBank,
_shopController,
_inventoryController,
_categoryCatalog,
_playerModel,
_saveService,
_scoreSummaryService,
_dicePanelPresenter,
_scoreCardPresenter,
_gameInfoPresenter);
// Currency & Player state
_currencyBank.OnBalanceChanged += HandleCurrencyChanged;
_playerModel.OnChanged += HandlePlayerChangedForSave;
// Initialize
scoreCardView.Initialize(_categoryCatalog);
LoadSaveData();
gameInfoView.SetCurrencyText(_currencyBank.Balance);
// Start the game after all subscriptions are in place
_gameManager.StartNewGame();
_dicePanelPresenter.Initialize();
_scoreCardPresenter.Initialize();
_gameInfoPresenter.Initialize();
_gameFlowPresenter.Initialize();
}
private void OnDestroy()
{
_gameManager.OnTurnStarted -= HandleTurnStarted;
_gameManager.OnRollComplete -= HandleRollComplete;
_gameManager.OnScored -= HandleScored;
_gameManager.OnGameOver -= HandleGameOver;
_diceManager.OnDiceSettled -= HandleDiceSettled;
scoreCardView.OnCategorySelected -= HandleCategorySelected;
dicePanelView.OnRollClicked -= HandleRollClicked;
dicePanelView.OnDiceToggled -= HandleDiceToggled;
gameInfoView.OnNewGameClicked -= HandleNewGameClicked;
gameInfoView.OnShopClicked -= HandleShopClicked;
gameInfoView.OnInventoryClicked -= HandleInventoryClicked;
_currencyBank.OnBalanceChanged -= HandleCurrencyChanged;
if (_playerModel != null)
_playerModel.OnChanged -= HandlePlayerChangedForSave;
DisposeIfNeeded(_gameFlowPresenter);
DisposeIfNeeded(_gameInfoPresenter);
DisposeIfNeeded(_scoreCardPresenter);
DisposeIfNeeded(_dicePanelPresenter);
}
// ── Save / Load ─────────────────────────────────────────
private void LoadSaveData()
private static void DisposeIfNeeded(IDisposable disposable)
{
SaveData save = SaveSystem.Load();
if (save.currency > 0)
_currencyBank.SetBalance(save.currency);
if (_modifierCatalog != null && save.ownedModifiers.Count > 0)
{
var entries = new List<ModifierSaveEntry>();
var permanentIds = new HashSet<string>();
foreach (var oldEntry in save.ownedModifiers)
{
var def = _modifierCatalog.FindById(oldEntry.modifierId);
if (def == null)
{
Debug.LogWarning($"Modifier '{oldEntry.modifierId}' not found in catalog, skipping.");
continue;
}
entries.Add(new ModifierSaveEntry
{
modifierId = oldEntry.modifierId,
isActive = oldEntry.isActive,
remainingUses = oldEntry.remainingUses,
stacks = oldEntry.stacks,
customState = oldEntry.customState,
});
if (!def.HasLimitedUses)
permanentIds.Add(def.Id);
}
_modifierRegistry.LoadSaveData(entries, _modifierCatalog);
_shopModel.LoadPurchasedPermanentIds(permanentIds);
}
if (_diceCatalog != null && save.ownedDiceIds != null && save.ownedDiceIds.Count > 0)
{
_playerModel.Dice.LoadSaveData(save.ownedDiceIds, _diceCatalog);
var dicePermIds = new HashSet<string>(save.ownedDiceIds);
var existingIds = _shopModel.GetPurchasedPermanentIds();
foreach (var id in dicePermIds)
existingIds.Add(id);
_shopModel.LoadPurchasedPermanentIds(existingIds);
}
}
private void PerformSave()
{
var save = new SaveData
{
currency = _currencyBank.Balance,
ownedDiceIds = _playerModel.Dice.GetSaveData(),
};
var entries = _modifierRegistry.GetSaveData();
for (int i = 0; i < entries.Count; i++)
{
save.ownedModifiers.Add(entries[i]);
}
SaveSystem.Save(save);
}
// ── Model Event Handlers ──────────────────────────────────
private void HandleTurnStarted(int turn)
{
int totalCategoryCount = _categoryCatalog.Count;
gameInfoView.SetTurnText(turn, totalCategoryCount);
dicePanelView.ResetForNewTurn();
dicePanelView.SetRollButtonState(true, 0, maxRollsPerTurn);
scoreCardView.ClearAllPreviews();
}
private void HandleRollComplete(int rollNumber)
{
bool canRollAgain = _gameManager.CanRoll;
dicePanelView.SetRollButtonState(canRollAgain, rollNumber, maxRollsPerTurn);
dicePanelView.SetDiceInteractable(true);
int[] values = _diceManager.GetCurrentValues();
dicePanelView.SetAllDiceValues(values);
UpdatePreviewScores();
}
private void HandleDiceSettled(int index, int value)
{
dicePanelView.SetDiceValue(index, value);
}
private void HandleScored(CategoryDefinition category, int finalScore)
{
scoreCardView.SetCategoryScored(category, finalScore);
UpdateTotalDisplay();
PerformSave();
}
private void HandleGameOver(int totalScore)
{
dicePanelView.SetRollButtonState(false, maxRollsPerTurn, maxRollsPerTurn);
dicePanelView.SetDiceInteractable(false);
scoreCardView.SetAllInteractable(false);
int displayTotal = CalculateDisplayTotal();
gameInfoView.ShowGameOver(displayTotal);
PerformSave();
}
// ── View Event Handlers ───────────────────────────────────
private void HandleRollClicked()
{
if (!_gameManager.CanRoll) return;
dicePanelView.SetRollButtonState(false, _gameManager.CurrentRoll, maxRollsPerTurn);
dicePanelView.SetDiceInteractable(false);
scoreCardView.SetAllInteractable(false);
_gameManager.Roll();
}
private void HandleDiceToggled(int index)
{
if (_gameManager.CurrentRoll == 0) return;
if (_diceManager.IsAnyRolling) return;
_gameManager.ToggleDiceLock(index);
bool isLocked = _diceManager.IsLocked(index);
dicePanelView.SetDiceLocked(index, isLocked);
}
private void HandleCategorySelected(CategoryDefinition category)
{
if (!_gameManager.CanScore) return;
if (_scoringSystem.IsCategoryUsed(category)) return;
_gameManager.ScoreInCategory(category);
}
private void HandleNewGameClicked()
{
gameInfoView.HideGameOver();
scoreCardView.ResetAll();
dicePanelView.ResetForNewGame();
_gameManager.StartNewGame();
}
private void HandleShopClicked()
{
_shopController.ToggleVisibility();
}
private void HandleInventoryClicked()
{
_inventoryController.ToggleVisibility();
}
private void HandleCurrencyChanged(int newBalance)
{
gameInfoView.SetCurrencyText(newBalance);
}
private void HandlePlayerChangedForSave()
{
PerformSave();
}
// ── Helpers ────────────────────────────────────────────────
private void UpdatePreviewScores()
{
var dice = _diceManager.GetDice();
var previews = new Dictionary<CategoryDefinition, int>();
var allCategories = _categoryCatalog.All;
for (int i = 0; i < allCategories.Count; i++)
{
var cat = allCategories[i];
if (_scoringSystem.IsCategoryUsed(cat)) continue;
ScoreResult result = _scoringSystem.PreviewScore(dice, cat);
previews[cat] = result.FinalScore;
}
scoreCardView.UpdatePreviews(previews);
}
private void UpdateTotalDisplay()
{
int upperSum = CalculateUpperSum();
bool hasBonus = upperSum >= UpperBonusThreshold;
int displayTotal = CalculateDisplayTotal();
scoreCardView.UpdateTotalDisplay(displayTotal, upperSum, hasBonus);
}
private int CalculateUpperSum()
{
int upperSum = 0;
var allCategories = _categoryCatalog.All;
for (int i = 0; i < allCategories.Count; i++)
{
if (!allCategories[i].IsUpperSection) continue;
int catScore = _scoringSystem.GetCategoryScore(allCategories[i]);
if (catScore >= 0) upperSum += catScore;
}
return upperSum;
}
private int CalculateDisplayTotal()
{
int total = _scoringSystem.TotalScore;
int upperSum = CalculateUpperSum();
if (upperSum >= UpperBonusThreshold)
total += UpperBonusValue;
return total;
if (disposable != null)
disposable.Dispose();
}
}
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7b0077025ca5d884e9709f0d8a3c77e0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,87 @@
using System;
using YachtDice.Game;
namespace YachtDice.UI.Presentation
{
public sealed class DicePanelPresenter : IDisposable
{
private readonly DicePanelView _view;
private readonly GameManager _gameManager;
private readonly DiceManager _diceManager;
public event Action RollClicked;
public event Action<int> DiceToggled;
public DicePanelPresenter(DicePanelView view, GameManager gameManager, DiceManager diceManager)
{
_view = view;
_gameManager = gameManager;
_diceManager = diceManager;
}
public void Initialize()
{
_view.OnRollClicked += HandleRollClicked;
_view.OnDiceToggled += HandleDiceToggled;
_diceManager.OnDiceSettled += HandleDiceSettled;
}
public void Dispose()
{
_view.OnRollClicked -= HandleRollClicked;
_view.OnDiceToggled -= HandleDiceToggled;
_diceManager.OnDiceSettled -= HandleDiceSettled;
}
public void ResetForNewTurn()
{
_view.ResetForNewTurn();
_view.SetRollButtonState(true, 0, _gameManager.MaxRollsPerTurn);
}
public void PrepareForRoll()
{
_view.SetRollButtonState(false, _gameManager.CurrentRoll, _gameManager.MaxRollsPerTurn);
_view.SetDiceInteractable(false);
}
public void HandleRollComplete(int rollNumber)
{
var canRollAgain = _gameManager.CanRoll;
_view.SetRollButtonState(canRollAgain, rollNumber, _gameManager.MaxRollsPerTurn);
_view.SetDiceInteractable(true);
_view.SetAllDiceValues(_diceManager.GetCurrentValues());
}
public void HandleGameOver()
{
_view.SetRollButtonState(false, _gameManager.MaxRollsPerTurn, _gameManager.MaxRollsPerTurn);
_view.SetDiceInteractable(false);
}
public void ResetForNewGame()
{
_view.ResetForNewGame(_gameManager.MaxRollsPerTurn);
}
public void SetDiceLocked(int index, bool isLocked)
{
_view.SetDiceLocked(index, isLocked);
}
private void HandleRollClicked()
{
RollClicked?.Invoke();
}
private void HandleDiceToggled(int index)
{
DiceToggled?.Invoke(index);
}
private void HandleDiceSettled(int index, int value)
{
_view.SetDiceValue(index, value);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f201ff4903d62894bb6245d78d5fa3f9
@@ -0,0 +1,189 @@
using System;
using YachtDice.Categories;
using YachtDice.Economy;
using YachtDice.Game;
using YachtDice.Inventory;
using YachtDice.Player;
using YachtDice.Scoring;
using YachtDice.Shop;
namespace YachtDice.UI.Presentation
{
public sealed class GameFlowPresenter : IDisposable
{
private readonly GameManager _gameManager;
private readonly ScoringSystem _scoringSystem;
private readonly DiceManager _diceManager;
private readonly CurrencyBank _currencyBank;
private readonly ShopController _shopController;
private readonly InventoryController _inventoryController;
private readonly CategoryCatalog _categoryCatalog;
private readonly PlayerModel _playerModel;
private readonly IGameSaveService _saveService;
private readonly IScoreSummaryService _scoreSummaryService;
private readonly DicePanelPresenter _dicePanelPresenter;
private readonly ScoreCardPresenter _scoreCardPresenter;
private readonly GameInfoPresenter _gameInfoPresenter;
public GameFlowPresenter(
GameManager gameManager,
ScoringSystem scoringSystem,
DiceManager diceManager,
CurrencyBank currencyBank,
ShopController shopController,
InventoryController inventoryController,
CategoryCatalog categoryCatalog,
PlayerModel playerModel,
IGameSaveService saveService,
IScoreSummaryService scoreSummaryService,
DicePanelPresenter dicePanelPresenter,
ScoreCardPresenter scoreCardPresenter,
GameInfoPresenter gameInfoPresenter)
{
_gameManager = gameManager;
_scoringSystem = scoringSystem;
_diceManager = diceManager;
_currencyBank = currencyBank;
_shopController = shopController;
_inventoryController = inventoryController;
_categoryCatalog = categoryCatalog;
_playerModel = playerModel;
_saveService = saveService;
_scoreSummaryService = scoreSummaryService;
_dicePanelPresenter = dicePanelPresenter;
_scoreCardPresenter = scoreCardPresenter;
_gameInfoPresenter = gameInfoPresenter;
}
public void Initialize()
{
_gameManager.OnTurnStarted += HandleTurnStarted;
_gameManager.OnRollComplete += HandleRollComplete;
_gameManager.OnScored += HandleScored;
_gameManager.OnGameOver += HandleGameOver;
_dicePanelPresenter.RollClicked += HandleRollClicked;
_dicePanelPresenter.DiceToggled += HandleDiceToggled;
_scoreCardPresenter.CategorySelected += HandleCategorySelected;
_gameInfoPresenter.NewGameClicked += HandleNewGameClicked;
_gameInfoPresenter.ShopClicked += HandleShopClicked;
_gameInfoPresenter.InventoryClicked += HandleInventoryClicked;
_currencyBank.OnBalanceChanged += HandleCurrencyChanged;
_playerModel.OnChanged += HandlePlayerChangedForSave;
_saveService.Load();
_gameInfoPresenter.SetCurrencyText(_currencyBank.Balance);
_gameManager.StartNewGame();
}
public void Dispose()
{
_gameManager.OnTurnStarted -= HandleTurnStarted;
_gameManager.OnRollComplete -= HandleRollComplete;
_gameManager.OnScored -= HandleScored;
_gameManager.OnGameOver -= HandleGameOver;
_dicePanelPresenter.RollClicked -= HandleRollClicked;
_dicePanelPresenter.DiceToggled -= HandleDiceToggled;
_scoreCardPresenter.CategorySelected -= HandleCategorySelected;
_gameInfoPresenter.NewGameClicked -= HandleNewGameClicked;
_gameInfoPresenter.ShopClicked -= HandleShopClicked;
_gameInfoPresenter.InventoryClicked -= HandleInventoryClicked;
_currencyBank.OnBalanceChanged -= HandleCurrencyChanged;
if (_playerModel != null)
_playerModel.OnChanged -= HandlePlayerChangedForSave;
}
private void HandleTurnStarted(int turn)
{
_gameInfoPresenter.SetTurnText(turn, _categoryCatalog.Count);
_dicePanelPresenter.ResetForNewTurn();
_scoreCardPresenter.ClearAllPreviews();
}
private void HandleRollComplete(int rollNumber)
{
_dicePanelPresenter.HandleRollComplete(rollNumber);
_scoreCardPresenter.UpdatePreviewScores();
}
private void HandleScored(CategoryDefinition category, int finalScore)
{
_scoreCardPresenter.SetCategoryScored(category, finalScore);
_scoreCardPresenter.UpdateTotalDisplay(_scoreSummaryService.Calculate());
_saveService.Save();
}
private void HandleGameOver(int totalScore)
{
_dicePanelPresenter.HandleGameOver();
_scoreCardPresenter.SetAllInteractable(false);
_gameInfoPresenter.ShowGameOver(_scoreSummaryService.Calculate().DisplayTotal);
_saveService.Save();
}
private void HandleRollClicked()
{
if (!_gameManager.CanRoll)
return;
_dicePanelPresenter.PrepareForRoll();
_scoreCardPresenter.SetAllInteractable(false);
_gameManager.Roll();
}
private void HandleDiceToggled(int index)
{
if (_gameManager.CurrentRoll == 0)
return;
if (_diceManager.IsAnyRolling)
return;
_gameManager.ToggleDiceLock(index);
_dicePanelPresenter.SetDiceLocked(index, _diceManager.IsLocked(index));
}
private void HandleCategorySelected(CategoryDefinition category)
{
if (!_gameManager.CanScore)
return;
if (_scoringSystem.IsCategoryUsed(category))
return;
_gameManager.ScoreInCategory(category);
}
private void HandleNewGameClicked()
{
_gameInfoPresenter.HideGameOver();
_scoreCardPresenter.ResetAll();
_dicePanelPresenter.ResetForNewGame();
_gameManager.StartNewGame();
}
private void HandleShopClicked()
{
_shopController.ToggleVisibility();
}
private void HandleInventoryClicked()
{
_inventoryController.ToggleVisibility();
}
private void HandleCurrencyChanged(int newBalance)
{
_gameInfoPresenter.SetCurrencyText(newBalance);
}
private void HandlePlayerChangedForSave()
{
_saveService.Save();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 88b64c71e3be57d4c84a553cc2bd3bf3
@@ -0,0 +1,67 @@
using System;
namespace YachtDice.UI.Presentation
{
public sealed class GameInfoPresenter : IDisposable
{
private readonly GameInfoView _view;
public event Action NewGameClicked;
public event Action ShopClicked;
public event Action InventoryClicked;
public GameInfoPresenter(GameInfoView view)
{
_view = view;
}
public void Initialize()
{
_view.OnNewGameClicked += HandleNewGameClicked;
_view.OnShopClicked += HandleShopClicked;
_view.OnInventoryClicked += HandleInventoryClicked;
}
public void Dispose()
{
_view.OnNewGameClicked -= HandleNewGameClicked;
_view.OnShopClicked -= HandleShopClicked;
_view.OnInventoryClicked -= HandleInventoryClicked;
}
public void SetTurnText(int turn, int maxTurns)
{
_view.SetTurnText(turn, maxTurns);
}
public void SetCurrencyText(int amount)
{
_view.SetCurrencyText(amount);
}
public void ShowGameOver(int finalScore)
{
_view.ShowGameOver(finalScore);
}
public void HideGameOver()
{
_view.HideGameOver();
}
private void HandleNewGameClicked()
{
NewGameClicked?.Invoke();
}
private void HandleShopClicked()
{
ShopClicked?.Invoke();
}
private void HandleInventoryClicked()
{
InventoryClicked?.Invoke();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6bc663d9adf9e9e4a921541dd9cf307d
@@ -0,0 +1,112 @@
using System.Collections.Generic;
using UnityEngine;
using YachtDice.Dice;
using YachtDice.Economy;
using YachtDice.Modifiers.Definition;
using YachtDice.Modifiers.Runtime;
using YachtDice.Persistence;
using YachtDice.Player;
using YachtDice.Shop;
namespace YachtDice.UI.Presentation
{
public sealed class GameSaveService : IGameSaveService
{
private readonly CurrencyBank _currencyBank;
private readonly ModifierRegistry _modifierRegistry;
private readonly ModifierCatalog _modifierCatalog;
private readonly DiceCatalog _diceCatalog;
private readonly ShopModel _shopModel;
private readonly PlayerModel _playerModel;
public GameSaveService(
CurrencyBank currencyBank,
ModifierRegistry modifierRegistry,
ModifierCatalog modifierCatalog,
DiceCatalog diceCatalog,
ShopModel shopModel,
PlayerModel playerModel)
{
_currencyBank = currencyBank;
_modifierRegistry = modifierRegistry;
_modifierCatalog = modifierCatalog;
_diceCatalog = diceCatalog;
_shopModel = shopModel;
_playerModel = playerModel;
}
public void Load()
{
var save = SaveSystem.Load();
if (save.currency > 0)
_currencyBank.SetBalance(save.currency);
if (_modifierCatalog != null && save.ownedModifiers != null && save.ownedModifiers.Count > 0)
LoadModifiers(save.ownedModifiers);
if (_diceCatalog != null && save.ownedDiceIds != null && save.ownedDiceIds.Count > 0)
LoadDice(save.ownedDiceIds);
}
public void Save()
{
var save = new SaveData
{
currency = _currencyBank.Balance,
ownedDiceIds = _playerModel.Dice.GetSaveData(),
};
var entries = _modifierRegistry.GetSaveData();
for (var i = 0; i < entries.Count; i++)
save.ownedModifiers.Add(entries[i]);
SaveSystem.Save(save);
}
private void LoadModifiers(List<ModifierSaveEntry> savedModifiers)
{
var validEntries = new List<ModifierSaveEntry>();
var permanentIds = new HashSet<string>();
for (var i = 0; i < savedModifiers.Count; i++)
{
var entry = savedModifiers[i];
var definition = _modifierCatalog.FindById(entry.modifierId);
if (definition == null)
{
Debug.LogWarning($"Modifier '{entry.modifierId}' not found in catalog, skipping.");
continue;
}
validEntries.Add(new ModifierSaveEntry
{
modifierId = entry.modifierId,
isActive = entry.isActive,
remainingUses = entry.remainingUses,
stacks = entry.stacks,
customState = entry.customState,
});
if (!definition.HasLimitedUses)
permanentIds.Add(definition.Id);
}
_modifierRegistry.LoadSaveData(validEntries, _modifierCatalog);
_shopModel.LoadPurchasedPermanentIds(permanentIds);
}
private void LoadDice(List<string> ownedDiceIds)
{
_playerModel.Dice.LoadSaveData(ownedDiceIds, _diceCatalog);
var dicePermanentIds = new HashSet<string>(ownedDiceIds);
var existingPermanentIds = _shopModel.GetPurchasedPermanentIds();
foreach (var id in dicePermanentIds)
existingPermanentIds.Add(id);
_shopModel.LoadPurchasedPermanentIds(existingPermanentIds);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2bed20289c2dd584ca7c6d6803c5f5ae
@@ -0,0 +1,8 @@
namespace YachtDice.UI.Presentation
{
public interface IGameSaveService
{
void Load();
void Save();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 80cd4768a27c8c842b94b00255c9ea18
@@ -0,0 +1,7 @@
namespace YachtDice.UI.Presentation
{
public interface IScoreSummaryService
{
ScoreSummary Calculate();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cfa9b05828486bc419ec4ea51ef6ad28
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using YachtDice.Categories;
using YachtDice.Game;
using YachtDice.Scoring;
namespace YachtDice.UI.Presentation
{
public sealed class ScoreCardPresenter : IDisposable
{
private readonly ScoreCardView _view;
private readonly CategoryCatalog _categoryCatalog;
private readonly ScoringSystem _scoringSystem;
private readonly DiceManager _diceManager;
public event Action<CategoryDefinition> CategorySelected;
public ScoreCardPresenter(
ScoreCardView view,
CategoryCatalog categoryCatalog,
ScoringSystem scoringSystem,
DiceManager diceManager)
{
_view = view;
_categoryCatalog = categoryCatalog;
_scoringSystem = scoringSystem;
_diceManager = diceManager;
}
public void Initialize()
{
_view.Initialize(_categoryCatalog);
_view.OnCategorySelected += HandleCategorySelected;
}
public void Dispose()
{
_view.OnCategorySelected -= HandleCategorySelected;
}
public void ClearAllPreviews()
{
_view.ClearAllPreviews();
}
public void UpdatePreviewScores()
{
var dice = _diceManager.GetDice();
var previews = new Dictionary<CategoryDefinition, int>();
var allCategories = _categoryCatalog.All;
for (var i = 0; i < allCategories.Count; i++)
{
var category = allCategories[i];
if (_scoringSystem.IsCategoryUsed(category))
continue;
var result = _scoringSystem.PreviewScore(dice, category);
previews[category] = result.FinalScore;
}
_view.UpdatePreviews(previews);
}
public void SetCategoryScored(CategoryDefinition category, int finalScore)
{
_view.SetCategoryScored(category, finalScore);
}
public void SetAllInteractable(bool interactable)
{
_view.SetAllInteractable(interactable);
}
public void UpdateTotalDisplay(ScoreSummary summary)
{
_view.UpdateTotalDisplay(summary.DisplayTotal, summary.UpperSum, summary.HasUpperBonus);
}
public void ResetAll()
{
_view.ResetAll();
}
private void HandleCategorySelected(CategoryDefinition category)
{
CategorySelected?.Invoke(category);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5c6cef29e168ca744b536c5f71a5c110
@@ -0,0 +1,16 @@
namespace YachtDice.UI.Presentation
{
public readonly struct ScoreSummary
{
public int DisplayTotal { get; }
public int UpperSum { get; }
public bool HasUpperBonus { get; }
public ScoreSummary(int displayTotal, int upperSum, bool hasUpperBonus)
{
DisplayTotal = displayTotal;
UpperSum = upperSum;
HasUpperBonus = hasUpperBonus;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: da4516c0d195c1e4cb0405efa3e731fd
@@ -0,0 +1,51 @@
using YachtDice.Categories;
using YachtDice.Scoring;
namespace YachtDice.UI.Presentation
{
public sealed class ScoreSummaryService : IScoreSummaryService
{
private const int UpperBonusThreshold = 63;
private const int UpperBonusValue = 35;
private readonly ScoringSystem _scoringSystem;
private readonly CategoryCatalog _categoryCatalog;
public ScoreSummaryService(ScoringSystem scoringSystem, CategoryCatalog categoryCatalog)
{
_scoringSystem = scoringSystem;
_categoryCatalog = categoryCatalog;
}
public ScoreSummary Calculate()
{
var upperSum = CalculateUpperSum();
var hasUpperBonus = upperSum >= UpperBonusThreshold;
var total = _scoringSystem.TotalScore;
if (hasUpperBonus)
total += UpperBonusValue;
return new ScoreSummary(total, upperSum, hasUpperBonus);
}
private int CalculateUpperSum()
{
var upperSum = 0;
var allCategories = _categoryCatalog.All;
for (var i = 0; i < allCategories.Count; i++)
{
var category = allCategories[i];
if (!category.IsUpperSection)
continue;
var categoryScore = _scoringSystem.GetCategoryScore(category);
if (categoryScore >= 0)
upperSum += categoryScore;
}
return upperSum;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fadb6232a88156d4a8f4373758fd160f
+1 -1
View File
@@ -23,7 +23,7 @@ namespace YachtDice.UI
/// <summary>
/// Инициализирует скоркарту из каталога категорий.
/// Вызывается из GameController после DI.
/// Вызывается из презентационного слоя после DI.
/// </summary>
public void Initialize(CategoryCatalog categoryCatalog)
{