[Add] Game Loop Playable

This commit is contained in:
2026-03-28 12:53:34 +07:00
parent 78ad76120f
commit f2173d2c73
20 changed files with 1433 additions and 122 deletions
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2ba2d461ab345d24bb0ddf5375b9ca51
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+23
View File
@@ -0,0 +1,23 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5917f01e056f9c24988a1e0e726becc0, type: 3}
m_Name: RunBalanceConfig
m_EditorClassIdentifier: YachtDice.Runtime::YachtDice.Run.RunBalanceConfigSO
startingBaseQuota: 18
quotaGrowthPerCycle: 6
stageTargetMultipliers:
- 1
- 1.33
- 1.75
stageClearReward: 25
cycleStoredRollBonusMultiplier: 10
baseRollsPerStage: 3
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7c5bc498b7678f74aa0e3e06434936e8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1710,7 +1710,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_text
value: 1234
value: Total Score Text
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_fontSize
+2 -2
View File
@@ -105,8 +105,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 0.5}
m_AnchorMax: {x: 1, y: 0.5}
m_AnchoredPosition: {x: -597.0664, y: 0}
m_SizeDelta: {x: 120, y: 120}
m_AnchoredPosition: {x: -653, y: 0}
m_SizeDelta: {x: 220, y: 120}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2533106893686760601
CanvasRenderer:
File diff suppressed because it is too large Load Diff
+193 -1
View File
@@ -156,7 +156,7 @@ MonoBehaviour:
categoryCatalog: {fileID: 11400000, guid: 4a72801517dbe4d48a74ea0f2ea9fc23, type: 2}
diceCatalog: {fileID: 11400000, guid: 6460bb21b6829cf449eeb36e34749e4d, type: 2}
shopCatalog: {fileID: 11400000, guid: 7efc21139c0b2234194047239dcf8a71, type: 2}
runBalanceConfig: {fileID: 0}
runBalanceConfig: {fileID: 11400000, guid: 7c5bc498b7678f74aa0e3e06434936e8, type: 2}
scoringSystem: {fileID: 1016044386}
currencyBank: {fileID: 389543052}
gameLoopController: {fileID: 2136136801}
@@ -2032,6 +2032,102 @@ PrefabInstance:
serializedVersion: 3
m_TransformParent: {fileID: 356280112}
m_Modifications:
- target: {fileID: 751564641204286872, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 751564641204286872, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 751564641204286872, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 751564641204286872, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 751564641204286872, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 751564641204286872, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1584173944617684199, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1584173944617684199, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1584173944617684199, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1584173944617684199, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1584173944617684199, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1584173944617684199, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2474911662515804829, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2474911662515804829, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2474911662515804829, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2474911662515804829, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2474911662515804829, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2474911662515804829, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2598258165833597008, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2598258165833597008, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2598258165833597008, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2598258165833597008, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2598258165833597008, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2598258165833597008, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4736777721839866001, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_Pivot.x
value: 0.5
@@ -2116,6 +2212,102 @@ PrefabInstance:
propertyPath: m_Name
value: Game Info View
objectReference: {fileID: 0}
- target: {fileID: 5290978124849395756, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5290978124849395756, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5290978124849395756, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5290978124849395756, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5290978124849395756, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5290978124849395756, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5336381240030531919, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5336381240030531919, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5336381240030531919, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5336381240030531919, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5336381240030531919, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5336381240030531919, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 6914999108258352753, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 6914999108258352753, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 6914999108258352753, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 6914999108258352753, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 6914999108258352753, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 6914999108258352753, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8890823772492865494, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8890823772492865494, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8890823772492865494, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8890823772492865494, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8890823772492865494, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8890823772492865494, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
+22 -10
View File
@@ -23,10 +23,10 @@ namespace YachtDice.Game
public int MaxRollsPerTurn => _runLoopService != null ? _runLoopService.State.CurrentStageRollBudget : 0;
public RunPhase CurrentPhase => _runLoopService != null ? _runLoopService.State.Phase : RunPhase.None;
public bool CanRoll => _runLoopService != null && _runLoopService.CanRoll() && !_diceManager.IsAnyRolling;
public bool CanRoll => _runLoopService != null && _runLoopService.CanBeginRoll() && !_diceManager.IsAnyRolling;
public bool CanScore => _runLoopService != null && CurrentPhase == RunPhase.CategorySelection && !_diceManager.IsAnyRolling;
public bool IsGameOver => _runLoopService != null && _runLoopService.State.IsFailed;
public bool IsShopOpen => CurrentPhase == RunPhase.Shop;
public bool IsShopAvailable => _runLoopService != null && _runLoopService.State.IsShopAvailable;
public event Action<int> OnTurnStarted;
public event Action<int> OnRollComplete;
@@ -41,6 +41,7 @@ namespace YachtDice.Game
public event Action<int> OnQuotaChanged;
public event Action<int, int> OnCycleCompleted;
public event Action<RunPhase> OnPhaseChanged;
public event Action<bool> OnShopAvailabilityChanged;
[Inject]
public void Construct(DiceManager diceManager, RunLoopService runLoopService, ScoringSystem scoringSystem)
@@ -50,6 +51,7 @@ namespace YachtDice.Game
_scoringSystem = scoringSystem;
_runLoopService.OnStageStarted += HandleStageStarted;
_runLoopService.OnCategoryRecorded += HandleCategoryRecorded;
_runLoopService.OnStageCleared += HandleStageCleared;
_runLoopService.OnRunFailed += HandleRunFailed;
_runLoopService.OnBetStarted += HandleBetStarted;
@@ -59,6 +61,7 @@ namespace YachtDice.Game
_runLoopService.OnQuotaChanged += HandleQuotaChanged;
_runLoopService.OnCycleCompleted += HandleCycleCompleted;
_runLoopService.OnPhaseChanged += HandlePhaseChanged;
_runLoopService.OnShopAvailabilityChanged += HandleShopAvailabilityChanged;
}
private void OnDestroy()
@@ -67,6 +70,7 @@ namespace YachtDice.Game
return;
_runLoopService.OnStageStarted -= HandleStageStarted;
_runLoopService.OnCategoryRecorded -= HandleCategoryRecorded;
_runLoopService.OnStageCleared -= HandleStageCleared;
_runLoopService.OnRunFailed -= HandleRunFailed;
_runLoopService.OnBetStarted -= HandleBetStarted;
@@ -76,6 +80,7 @@ namespace YachtDice.Game
_runLoopService.OnQuotaChanged -= HandleQuotaChanged;
_runLoopService.OnCycleCompleted -= HandleCycleCompleted;
_runLoopService.OnPhaseChanged -= HandlePhaseChanged;
_runLoopService.OnShopAvailabilityChanged -= HandleShopAvailabilityChanged;
}
public void StartNewGame()
@@ -86,23 +91,21 @@ namespace YachtDice.Game
public void CompleteShop()
{
if (!IsShopOpen)
return;
_runLoopService.CompleteShop();
OnShopClosed?.Invoke();
}
public void Roll()
public bool Roll()
{
if (!CanRoll)
return;
return false;
if (!_runLoopService.TryBeginRoll())
return;
return false;
_diceManager.OnAllDiceSettled += HandleAllDiceSettled;
_diceManager.RollUnlocked();
return true;
}
private void HandleAllDiceSettled()
@@ -139,7 +142,7 @@ namespace YachtDice.Game
public bool CanOpenShopManually()
{
return IsShopOpen;
return IsShopAvailable && !IsGameOver;
}
private void HandleStageStarted(RunStageState stage)
@@ -148,10 +151,14 @@ namespace YachtDice.Game
OnTurnStarted?.Invoke(stage.Index + 1);
}
private void HandleCategoryRecorded(CategoryDefinition category, ScoreResult result)
{
OnScored?.Invoke(category, result.FinalScore);
}
private void HandleStageCleared(RunStageState stage, CategoryDefinition category, ScoreResult result)
{
_diceManager.UnlockAll();
OnScored?.Invoke(category, result.FinalScore);
}
private void HandleRunFailed(RunState state)
@@ -193,5 +200,10 @@ namespace YachtDice.Game
{
OnPhaseChanged?.Invoke(phase);
}
private void HandleShopAvailabilityChanged(bool isAvailable)
{
OnShopAvailabilityChanged?.Invoke(isAvailable);
}
}
}
@@ -271,7 +271,7 @@ namespace YachtDice.Modifiers.Editor
return;
}
_inventoryController = FindObjectOfType<InventoryController>();
_inventoryController = FindAnyObjectByType<InventoryController>();
}
private void RefreshDefinitions()
@@ -122,7 +122,7 @@ namespace YachtDice.Modifiers.Pipeline
{
string traceStr = trace.ToString();
context.DebugLog.Add(traceStr);
Debug.Log(traceStr);
// Debug.Log(traceStr);
}
_isExecuting = false;
+87 -20
View File
@@ -41,6 +41,7 @@ namespace YachtDice.Run
public event Action<RunState> OnBetStarted;
public event Action<RunState> OnShopOpened;
public event Action<RunStageState> OnStageStarted;
public event Action<CategoryDefinition, ScoreResult> OnCategoryRecorded;
public event Action<RunStageState, CategoryDefinition, ScoreResult> OnStageCleared;
public event Action<RunStageState> OnStageFailed;
public event Action<int> OnStoredRollsChanged;
@@ -49,22 +50,29 @@ namespace YachtDice.Run
public event Action<int> OnQuotaChanged;
public event Action<RunState> OnRunFailed;
public event Action<RunPhase> OnPhaseChanged;
public event Action<bool> OnShopAvailabilityChanged;
public event Action OnCategoriesRefreshed;
public void StartNewRun()
{
_scoringSystem.ResetScorecard();
_storedRollBank.Reset();
if (_currencyBank != null)
_currencyBank.SetBalance(0);
_state.BaseQuota = _config.StartingBaseQuota;
_state.BetIndex = 0;
_state.StageIndex = 0;
_state.CurrentStageGoal = _config.GetStageTarget(_state.BaseQuota, 0);
_state.CurrentRoll = 0;
_state.CurrentStageRollBudget = _config.BaseRollsPerStage;
_state.CurrentStageTarget = _config.GetStageTarget(_state.BaseQuota, 0);
_state.CurrentStageTarget = _state.CurrentStageGoal;
_state.StoredRolls = _storedRollBank.Value;
_state.IsActive = true;
_state.IsFailed = false;
_state.IsShopUnlocked = false;
_state.IsShopAvailable = false;
_state.HasRolledThisStage = false;
SetPhase(RunPhase.None);
OnRunStarted?.Invoke(_state);
@@ -73,10 +81,8 @@ namespace YachtDice.Run
public void CompleteShop()
{
if (_state.Phase != RunPhase.Shop || !_state.IsActive)
if (!_state.IsActive)
return;
StartStage(0);
}
public bool CanRoll()
@@ -89,9 +95,14 @@ namespace YachtDice.Run
|| _state.Phase == RunPhase.Rolling;
}
public bool CanBeginRoll()
{
return CanRoll() && HasRemainingRollCapacity();
}
public bool TryBeginRoll()
{
if (!CanRoll())
if (!CanBeginRoll())
return false;
if (_state.CurrentRoll >= _state.CurrentStageRollBudget)
@@ -103,6 +114,13 @@ namespace YachtDice.Run
_state.StoredRolls = _storedRollBank.Value;
}
if (!_state.HasRolledThisStage)
{
_state.HasRolledThisStage = true;
if (_state.IsShopAvailable)
SetShopAvailable(false);
}
_state.CurrentRoll += 1;
SetPhase(RunPhase.Rolling);
return true;
@@ -130,8 +148,7 @@ namespace YachtDice.Run
if (_state.CurrentRoll <= 0)
return false;
var preview = PreviewScore(dice, category);
return preview.FinalScore >= _state.CurrentStageTarget;
return true;
}
public ScoreResult PreviewScore(IReadOnlyList<IDice> dice, CategoryDefinition category)
@@ -182,19 +199,36 @@ namespace YachtDice.Run
if (remainingRolls > 0)
_storedRollBank.Add(remainingRolls);
if (_config.StageClearReward > 0 && _currencyBank != null)
_currencyBank.Add(_config.StageClearReward);
_state.CurrentStageTarget = Math.Max(0, _state.CurrentStageTarget - result.FinalScore);
OnCategoryRecorded?.Invoke(category, result);
SetPhase(RunPhase.StageResolved);
OnStageCleared?.Invoke(clearedStage, category, result);
if (_state.StageIndex >= _config.StageCount - 1)
if (_state.CurrentStageTarget <= 0)
{
ResolveBet();
if (_config.StageClearReward > 0 && _currencyBank != null)
_currencyBank.Add(_config.StageClearReward);
if (!_state.IsShopUnlocked)
_state.IsShopUnlocked = true;
SetPhase(RunPhase.StageResolved);
OnStageCleared?.Invoke(clearedStage, category, result);
if (_state.StageIndex >= _config.StageCount - 1)
{
ResolveBet();
}
else
{
StartStage(_state.StageIndex + 1);
}
}
else if (HasAnyAvailableCategory())
{
StartStageAttempt();
}
else
{
StartStage(_state.StageIndex + 1);
FailCurrentStage();
}
return true;
@@ -220,22 +254,30 @@ namespace YachtDice.Run
_state.StageIndex = 0;
_state.CurrentRoll = 0;
_state.CurrentStageRollBudget = _config.BaseRollsPerStage;
_state.CurrentStageTarget = _config.GetStageTarget(_state.BaseQuota, 0);
_state.CurrentStageGoal = _config.GetStageTarget(_state.BaseQuota, 0);
_state.CurrentStageTarget = _state.CurrentStageGoal;
SetPhase(RunPhase.StartBet);
OnCategoriesRefreshed?.Invoke();
OnBetStarted?.Invoke(_state);
SetPhase(RunPhase.Shop);
OnShopOpened?.Invoke(_state);
StartStage(0);
}
private void StartStage(int stageIndex)
{
_state.StageIndex = stageIndex;
_state.CurrentStageGoal = _config.GetStageTarget(_state.BaseQuota, stageIndex);
_state.CurrentStageTarget = _state.CurrentStageGoal;
StartStageAttempt(_state.IsShopUnlocked);
}
private void StartStageAttempt(bool allowShopAtStart = false)
{
_state.CurrentRoll = 0;
_state.CurrentStageRollBudget = _config.BaseRollsPerStage;
_state.CurrentStageTarget = _config.GetStageTarget(_state.BaseQuota, stageIndex);
_state.HasRolledThisStage = false;
SetShopAvailable(allowShopAtStart);
SetPhase(RunPhase.StageStart);
OnStageStarted?.Invoke(GetCurrentStageState());
@@ -279,6 +321,15 @@ namespace YachtDice.Run
OnPhaseChanged?.Invoke(phase);
}
private void SetShopAvailable(bool isAvailable)
{
_state.IsShopAvailable = isAvailable;
OnShopAvailabilityChanged?.Invoke(isAvailable);
if (isAvailable)
OnShopOpened?.Invoke(_state);
}
private void HandleStoredRollsChanged(int value)
{
_state.StoredRolls = value;
@@ -289,5 +340,21 @@ namespace YachtDice.Run
{
OnCurrencyChanged?.Invoke(value);
}
private bool HasAnyAvailableCategory()
{
var catalog = _scoringSystem.Catalog;
if (catalog == null)
return false;
var categories = catalog.All;
for (var i = 0; i < categories.Count; i++)
{
if (!_scoringSystem.IsCategoryUsed(categories[i]))
return true;
}
return false;
}
}
}
+4
View File
@@ -8,12 +8,16 @@ namespace YachtDice.Run
public int BaseQuota;
public int BetIndex;
public int StageIndex;
public int CurrentStageGoal;
public int CurrentStageTarget;
public int CurrentRoll;
public int CurrentStageRollBudget;
public int StoredRolls;
public bool IsActive;
public bool IsFailed;
public bool IsShopUnlocked;
public bool IsShopAvailable;
public bool HasRolledThisStage;
public RunPhase Phase;
public int StageNumber => StageIndex + 1;
+10
View File
@@ -13,6 +13,7 @@ namespace YachtDice.Shop
private ShopModel _model;
public ShopCatalog Catalog => _catalog;
public event System.Action OnCloseRequested;
[Inject]
public void Construct(ShopCatalog catalog, CurrencyBank currencyBank, ShopModel model)
@@ -25,6 +26,7 @@ namespace YachtDice.Shop
private void Start()
{
shopView.OnBuyClicked += HandleBuyClicked;
shopView.OnCloseRequested += HandleCloseRequested;
_currencyBank.OnBalanceChanged += HandleCurrencyChanged;
_model.OnItemPurchased += HandleItemPurchased;
@@ -35,7 +37,10 @@ namespace YachtDice.Shop
private void OnDestroy()
{
if (shopView != null)
{
shopView.OnBuyClicked -= HandleBuyClicked;
shopView.OnCloseRequested -= HandleCloseRequested;
}
if (_currencyBank != null)
_currencyBank.OnBalanceChanged -= HandleCurrencyChanged;
@@ -83,5 +88,10 @@ namespace YachtDice.Shop
{
shopView.RefreshStates(_catalog.All, _model);
}
private void HandleCloseRequested()
{
OnCloseRequested?.Invoke();
}
}
}
+8 -2
View File
@@ -17,17 +17,18 @@ namespace YachtDice.Shop
private readonly List<ShopItemView> _spawnedItems = new();
public event Action<IShopItem> OnBuyClicked;
public event Action OnCloseRequested;
private void Awake()
{
if (closeButton != null)
closeButton.onClick.AddListener(Hide);
closeButton.onClick.AddListener(HandleCloseClicked);
}
private void OnDestroy()
{
if (closeButton != null)
closeButton.onClick.RemoveListener(Hide);
closeButton.onClick.RemoveListener(HandleCloseClicked);
}
public void Show() => gameObject.SetActive(true);
@@ -92,5 +93,10 @@ namespace YachtDice.Shop
if (tooltipView != null)
tooltipView.Hide();
}
private void HandleCloseClicked()
{
OnCloseRequested?.Invoke();
}
}
}
@@ -53,7 +53,7 @@ namespace YachtDice.Tests
}
[Test]
public void StartNewRun_EntersShopWithConfigQuota()
public void StartNewRun_StartsFirstStageWithoutShop()
{
var categories = CreateFixedCatalog(100, 100, 100);
var service = CreateService(categories);
@@ -62,8 +62,10 @@ namespace YachtDice.Tests
Assert.AreEqual(30, service.State.BaseQuota);
Assert.AreEqual(1, service.State.BetIndex);
Assert.AreEqual(RunPhase.Shop, service.State.Phase);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
Assert.AreEqual(0, service.State.StoredRolls);
Assert.AreEqual(0, _currencyBank.Balance);
Assert.IsFalse(service.State.IsShopAvailable);
}
[Test]
@@ -73,7 +75,6 @@ namespace YachtDice.Tests
var service = CreateService(categories);
service.StartNewRun();
service.CompleteShop();
service.TryBeginRoll();
service.NotifyRollResolved(_emptyDice);
@@ -84,6 +85,41 @@ namespace YachtDice.Tests
Assert.AreEqual(2, service.State.StoredRolls);
Assert.AreEqual(2, service.State.StageNumber);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
Assert.IsTrue(service.State.IsShopAvailable);
}
[Test]
public void CategoryCanBeScored_WithZeroResultAfterFirstRoll()
{
var categories = CreateFixedCatalog(0, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
service.TryBeginRoll();
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.CanScoreCategory(_emptyDice, categories.All[0]));
Assert.IsTrue(service.TryScoreCategoryAsync(_emptyDice, categories.All[0]).GetAwaiter().GetResult());
Assert.AreEqual(30, service.State.CurrentStageTarget);
Assert.AreEqual(1, service.State.StageNumber);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
}
[Test]
public void TargetDecreasesByRecordedScore_AndStageContinues()
{
var categories = CreateFixedCatalog(5, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
service.TryBeginRoll();
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.TryScoreCategoryAsync(_emptyDice, categories.All[0]).GetAwaiter().GetResult());
Assert.AreEqual(25, service.State.CurrentStageTarget);
Assert.AreEqual(1, service.State.StageNumber);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
}
[Test]
@@ -93,7 +129,6 @@ namespace YachtDice.Tests
var service = CreateService(categories);
service.StartNewRun();
service.CompleteShop();
service.TryBeginRoll();
service.NotifyRollResolved(_emptyDice);
service.TryScoreCategoryAsync(_emptyDice, categories.All[0]).GetAwaiter().GetResult();
@@ -117,8 +152,6 @@ namespace YachtDice.Tests
var service = CreateService(categories);
service.StartNewRun();
service.CompleteShop();
ClearStageInOneRoll(service, categories.All[0]);
ClearStageInOneRoll(service, categories.All[1]);
ClearStageInOneRoll(service, categories.All[2]);
@@ -126,9 +159,10 @@ namespace YachtDice.Tests
Assert.AreEqual(135, _currencyBank.Balance);
Assert.AreEqual(60, service.State.BaseQuota);
Assert.AreEqual(2, service.State.BetIndex);
Assert.AreEqual(RunPhase.Shop, service.State.Phase);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
Assert.AreEqual(6, service.State.StoredRolls);
Assert.AreEqual(0, _scoringSystem.CategoriesFilledCount);
Assert.IsTrue(service.State.IsShopAvailable);
}
[Test]
@@ -138,37 +172,56 @@ namespace YachtDice.Tests
var service = CreateService(categories);
service.StartNewRun();
service.CompleteShop();
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
for (var i = 0; i < 3; i++)
{
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
}
Assert.IsTrue(service.State.IsFailed);
Assert.AreEqual(RunPhase.RunFailed, service.State.Phase);
Assert.IsFalse(service.State.IsFailed);
Assert.AreEqual(RunPhase.CategorySelection, service.State.Phase);
}
[Test]
public void Shop_ReopensOnlyAtStartOfNextBet()
public void ShopUnlocksAfterFirstClear_AndClosesAfterFirstRoll()
{
var categories = CreateFixedCatalog(100, 100, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
Assert.AreEqual(RunPhase.Shop, service.State.Phase);
Assert.IsFalse(service.State.IsShopAvailable);
service.CompleteShop();
ClearStageInOneRoll(service, categories.All[0]);
Assert.IsTrue(service.State.IsShopAvailable);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
ClearStageInOneRoll(service, categories.All[1]);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
Assert.IsTrue(service.TryBeginRoll());
Assert.IsFalse(service.State.IsShopAvailable);
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.TryScoreCategoryAsync(_emptyDice, categories.All[1]).GetAwaiter().GetResult());
Assert.IsTrue(service.State.IsShopAvailable);
ClearStageInOneRoll(service, categories.All[2]);
Assert.AreEqual(RunPhase.Shop, service.State.Phase);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
Assert.AreEqual(2, service.State.BetIndex);
Assert.IsTrue(service.State.IsShopAvailable);
}
[Test]
public void CanBeginRoll_BecomesFalse_WhenNoRollsOrStoredRollsRemain()
{
var categories = CreateFixedCatalog(100, 100, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
Assert.IsFalse(service.CanBeginRoll());
Assert.AreEqual(RunPhase.CategorySelection, service.State.Phase);
}
private RunLoopService CreateService(CategoryCatalog catalog)
+6
View File
@@ -82,6 +82,12 @@ namespace YachtDice.UI
rollButtonText.text = $"Бросок {currentRoll + 1}/{maxRolls}";
}
public void SetRollButtonPending()
{
rollButton.interactable = false;
rollButtonText.text = "Бросок...";
}
public void SetRollButtonInteractable(bool interactable)
{
rollButton.interactable = interactable;
+85 -2
View File
@@ -7,8 +7,14 @@ namespace YachtDice.UI
{
public class GameInfoView : MonoBehaviour
{
[Header("Turn Info")]
[Header("Run HUD")]
[SerializeField] private TMP_Text turnText;
[SerializeField] private TMP_Text phaseText;
[SerializeField] private TMP_Text betText;
[SerializeField] private TMP_Text stageText;
[SerializeField] private TMP_Text targetText;
[SerializeField] private TMP_Text quotaText;
[SerializeField] private TMP_Text storedRollsText;
[Header("Currency")]
[SerializeField] private TMP_Text currencyText;
@@ -28,6 +34,9 @@ namespace YachtDice.UI
private void Awake()
{
EnsureHudBindings();
EnsureGameOverBindings();
newGameButton.onClick.AddListener(() => OnNewGameClicked?.Invoke());
gameOverPanel.SetActive(false);
@@ -43,9 +52,27 @@ namespace YachtDice.UI
}
public void SetRunInfoText(string text)
{
if (phaseText != null)
phaseText.text = text;
}
public void SetRunHud(string phase, int bet, int stage, int stageCount, int target, int quota, int storedRolls)
{
if (turnText != null)
turnText.text = text;
turnText.text = $"Turn {stage}/{stageCount}";
if (phaseText != null)
phaseText.text = $"Phase: {phase}";
if (betText != null)
betText.text = $"Bet: {bet}";
if (stageText != null)
stageText.text = $"Stage: {stage}/{stageCount}";
if (targetText != null)
targetText.text = $"Target: {target}";
if (quotaText != null)
quotaText.text = $"Quota: {quota}";
if (storedRollsText != null)
storedRollsText.text = $"Stored Rolls: {storedRolls}";
}
public void SetCurrencyText(int amount)
@@ -80,5 +107,61 @@ namespace YachtDice.UI
if (inventoryButton != null)
inventoryButton.onClick.RemoveAllListeners();
}
private void EnsureHudBindings()
{
if (turnText == null)
return;
phaseText ??= turnText;
betText ??= CreateHudLabel("Bet Text", -34);
stageText ??= CreateHudLabel("Stage Text", -68);
targetText ??= CreateHudLabel("Target Text", -102);
quotaText ??= CreateHudLabel("Quota Text", -136);
storedRollsText ??= CreateHudLabel("Stored Rolls Text", -170);
}
private void EnsureGameOverBindings()
{
if (gameOverPanel == null)
return;
if (finalScoreText == null || finalScoreText == currencyText)
{
TMP_Text source = turnText != null ? turnText : currencyText;
if (source == null)
return;
finalScoreText = Instantiate(source, gameOverPanel.transform);
finalScoreText.name = "Final Score Text";
if (finalScoreText.transform is RectTransform rect)
{
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.pivot = new Vector2(0.5f, 0.5f);
rect.anchoredPosition = new Vector2(0f, 40f);
rect.sizeDelta = new Vector2(360f, 60f);
}
}
}
private TMP_Text CreateHudLabel(string objectName, float yOffset)
{
var clone = Instantiate(turnText, turnText.transform.parent);
clone.name = objectName;
clone.text = string.Empty;
if (clone.transform is RectTransform rect && turnText.transform is RectTransform sourceRect)
{
rect.anchorMin = sourceRect.anchorMin;
rect.anchorMax = sourceRect.anchorMax;
rect.pivot = sourceRect.pivot;
rect.sizeDelta = sourceRect.sizeDelta;
rect.anchoredPosition = sourceRect.anchoredPosition + new Vector2(0f, yOffset);
}
return clone;
}
}
}
@@ -41,7 +41,7 @@ namespace YachtDice.UI.Presentation
public void PrepareForRoll()
{
_view.SetRollButtonState(false, _gameLoopController.CurrentRoll, _gameLoopController.MaxRollsPerTurn);
_view.SetRollButtonPending();
_view.SetDiceInteractable(false);
}
@@ -49,7 +49,7 @@ namespace YachtDice.UI.Presentation
{
var canRollAgain = _gameLoopController.CanRoll;
_view.SetRollButtonState(canRollAgain, rollNumber, _gameLoopController.MaxRollsPerTurn);
_view.SetDiceInteractable(true);
_view.SetDiceInteractable(canRollAgain);
_view.SetAllDiceValues(_diceManager.GetCurrentValues());
}
@@ -65,6 +65,7 @@ namespace YachtDice.UI.Presentation
_gameLoopController.OnQuotaChanged += HandleQuotaChanged;
_gameLoopController.OnCycleCompleted += HandleCycleCompleted;
_gameLoopController.OnPhaseChanged += HandlePhaseChanged;
_gameLoopController.OnShopAvailabilityChanged += HandleShopAvailabilityChanged;
_dicePanelPresenter.RollClicked += HandleRollClicked;
_dicePanelPresenter.DiceToggled += HandleDiceToggled;
@@ -72,13 +73,14 @@ namespace YachtDice.UI.Presentation
_gameInfoPresenter.NewGameClicked += HandleNewGameClicked;
_gameInfoPresenter.ShopClicked += HandleShopClicked;
_gameInfoPresenter.InventoryClicked += HandleInventoryClicked;
_shopController.OnCloseRequested += HandleShopCloseRequested;
_currencyBank.OnBalanceChanged += HandleCurrencyChanged;
_playerModel.OnChanged += HandlePlayerChangedForSave;
_saveService.Load();
_gameInfoPresenter.SetCurrencyText(_currencyBank.Balance);
_gameInfoPresenter.SetShopButtonInteractable(false);
_gameInfoPresenter.SetShopButtonInteractable(_gameLoopController.CanOpenShopManually());
_gameLoopController.StartNewGame();
}
@@ -95,6 +97,7 @@ namespace YachtDice.UI.Presentation
_gameLoopController.OnQuotaChanged -= HandleQuotaChanged;
_gameLoopController.OnCycleCompleted -= HandleCycleCompleted;
_gameLoopController.OnPhaseChanged -= HandlePhaseChanged;
_gameLoopController.OnShopAvailabilityChanged -= HandleShopAvailabilityChanged;
_dicePanelPresenter.RollClicked -= HandleRollClicked;
_dicePanelPresenter.DiceToggled -= HandleDiceToggled;
@@ -102,6 +105,7 @@ namespace YachtDice.UI.Presentation
_gameInfoPresenter.NewGameClicked -= HandleNewGameClicked;
_gameInfoPresenter.ShopClicked -= HandleShopClicked;
_gameInfoPresenter.InventoryClicked -= HandleInventoryClicked;
_shopController.OnCloseRequested -= HandleShopCloseRequested;
_currencyBank.OnBalanceChanged -= HandleCurrencyChanged;
@@ -115,12 +119,14 @@ namespace YachtDice.UI.Presentation
_dicePanelPresenter.ResetForNewTurn();
_dicePanelPresenter.SetRollingEnabled(true);
_scoreCardPresenter.ClearAllPreviews();
_gameInfoPresenter.SetShopButtonInteractable(_gameLoopController.CanOpenShopManually());
}
private void HandleRollComplete(int rollNumber)
{
_dicePanelPresenter.HandleRollComplete(rollNumber);
_scoreCardPresenter.UpdatePreviewScores();
_gameInfoPresenter.SetShopButtonInteractable(_gameLoopController.CanOpenShopManually());
}
private void HandleScored(CategoryDefinition category, int finalScore)
@@ -143,9 +149,11 @@ namespace YachtDice.UI.Presentation
private void HandleRollClicked()
{
if (!_gameLoopController.Roll())
return;
_dicePanelPresenter.PrepareForRoll();
_scoreCardPresenter.SetAllInteractable(false);
_gameLoopController.Roll();
}
private void HandleDiceToggled(int index)
@@ -175,14 +183,9 @@ namespace YachtDice.UI.Presentation
return;
if (_shopController.IsOpen)
{
_shopController.Close();
_gameLoopController.CompleteShop();
}
else
{
_shopController.Open();
}
}
private void HandleInventoryClicked()
@@ -208,16 +211,14 @@ namespace YachtDice.UI.Presentation
private void HandleShopOpened()
{
_shopController.Open();
_gameInfoPresenter.SetShopButtonInteractable(true);
_dicePanelPresenter.SetRollingEnabled(false);
_gameInfoPresenter.SetShopButtonInteractable(_gameLoopController.CanOpenShopManually());
UpdateRunInfoText();
}
private void HandleShopClosed()
{
_shopController.Close();
_gameInfoPresenter.SetShopButtonInteractable(false);
_gameInfoPresenter.SetShopButtonInteractable(_gameLoopController.CanOpenShopManually());
UpdateRunInfoText();
}
@@ -240,16 +241,36 @@ namespace YachtDice.UI.Presentation
private void HandlePhaseChanged(RunPhase phase)
{
if (phase != RunPhase.Shop)
_gameInfoPresenter.SetShopButtonInteractable(false);
_gameInfoPresenter.SetShopButtonInteractable(_gameLoopController.CanOpenShopManually());
UpdateRunInfoText();
}
private void UpdateRunInfoText()
{
var info = $"Bet {_gameLoopController.CurrentBet} | Stage {_gameLoopController.CurrentStage}/3 | Target {_gameLoopController.CurrentStageTarget} | Quota {_gameLoopController.CurrentBaseQuota} | Bank {_gameLoopController.StoredRolls}";
_gameInfoPresenter.SetRunInfoText(info);
var phase = _gameLoopController.CurrentPhase.ToString();
_gameInfoPresenter.SetRunHud(
phase,
_gameLoopController.CurrentBet,
_gameLoopController.CurrentStage,
3,
_gameLoopController.CurrentStageTarget,
_gameLoopController.CurrentBaseQuota,
_gameLoopController.StoredRolls);
}
private void HandleShopCloseRequested()
{
_shopController.Close();
_gameLoopController.CompleteShop();
}
private void HandleShopAvailabilityChanged(bool isAvailable)
{
if (!isAvailable && _shopController.IsOpen)
_shopController.Close();
_gameInfoPresenter.SetShopButtonInteractable(isAvailable);
UpdateRunInfoText();
}
}
}
@@ -39,6 +39,11 @@ namespace YachtDice.UI.Presentation
_view.SetRunInfoText(text);
}
public void SetRunHud(string phase, int bet, int stage, int stageCount, int target, int quota, int storedRolls)
{
_view.SetRunHud(phase, bet, stage, stageCount, target, quota, storedRolls);
}
public void SetCurrencyText(int amount)
{
_view.SetCurrencyText(amount);