diff --git a/src/CiselnaVyprava/Components/BattleArena.razor b/src/CiselnaVyprava/Components/BattleArena.razor index 9cc4bb8..4b2fd8b 100644 --- a/src/CiselnaVyprava/Components/BattleArena.razor +++ b/src/CiselnaVyprava/Components/BattleArena.razor @@ -62,8 +62,8 @@
-
@RemainingTimeSeconds.ToString("F1")s
-
@(State.IsDefensePhase ? "OBRANA!" : "ÚTOK")
+
@(RemainingTimeSeconds > 0 ? RemainingTimeSeconds.ToString("F1") + "s" : (State.IsDefensePhase ? "0.0s" : "1 DMG"))
+
@GetTimerLabel()
@@ -325,10 +325,10 @@ if (RemainingTimeSeconds <= 0) { - _timer?.Dispose(); - await InvokeAsync(async () => + if (State.IsDefensePhase) { - if (State.IsDefensePhase) + _timer?.Dispose(); + await InvokeAsync(async () => { var prob = State.CurrentProblem; var result = await BattleEngine.SubmitDefenseAnswerAsync(""); @@ -339,21 +339,9 @@ await PlayCombatSfx(result); _ = AudioService.PlaySfxAsync("owl_idea"); StateHasChanged(); - } - else - { - var prob = State.CurrentProblem; - var result = await BattleEngine.SubmitAnswerAsync(""); - ExplanationProblem = prob; - ExplanationUserAnswer = "Čas vypršal ⏱️"; - IsExplanationFromDefense = false; - ShowExplanationModal = true; - await PlayCombatSfx(result); - _ = AudioService.PlaySfxAsync("owl_idea"); - StateHasChanged(); - } - }); - return; + }); + return; + } } await InvokeAsync(StateHasChanged); @@ -605,11 +593,30 @@ private string GetTimerColorClass() { - double limit = State.IsDefensePhase ? Config.Battle.DefenseTestDurationSeconds : Config.Battle.StandardTimeSeconds; - double ratio = limit > 0 ? (RemainingTimeSeconds / limit) : 0; - if (ratio > 0.45) return "timer-green"; - if (ratio > 0.20) return "timer-yellow"; - return "timer-red"; + if (State.IsDefensePhase) + { + double limit = Config.Battle.DefenseTestDurationSeconds; + double ratio = limit > 0 ? (RemainingTimeSeconds / limit) : 0; + if (ratio > 0.45) return "timer-green"; + if (ratio > 0.20) return "timer-yellow"; + return "timer-red"; + } + else + { + var elapsed = (DateTime.UtcNow - State.ProblemStartTime).TotalSeconds; + if (elapsed <= Config.Battle.CriticalTimeSeconds) return "timer-green"; + if (elapsed <= Config.Battle.StandardTimeSeconds) return "timer-yellow"; + return "timer-red"; + } + } + + private string GetTimerLabel() + { + if (State.IsDefensePhase) return "OBRANA!"; + var elapsed = (DateTime.UtcNow - State.ProblemStartTime).TotalSeconds; + if (elapsed <= Config.Battle.CriticalTimeSeconds) return "KRITIK"; + if (elapsed <= Config.Battle.StandardTimeSeconds) return "ÚTOK"; + return "1 DMG"; } private string GetFeedbackClass() => State.LastResult switch diff --git a/src/CiselnaVyprava/Services/BattleEngine.cs b/src/CiselnaVyprava/Services/BattleEngine.cs index 49331d1..6f7cbb6 100644 --- a/src/CiselnaVyprava/Services/BattleEngine.cs +++ b/src/CiselnaVyprava/Services/BattleEngine.cs @@ -65,34 +65,35 @@ public class BattleEngine : IBattleEngine if (isCorrect) { - State.ComboStreak++; State.SolvedInLevel++; CombatResultType result; - double multiplier; + int totalDamage; if (elapsed <= _config.Battle.CriticalTimeSeconds) { + State.ComboStreak++; result = CombatResultType.CriticalHit; - multiplier = _config.Battle.CriticalDamageMultiplier; - State.LastFeedbackMessage = $"⚡ BLESKOVO! ({elapsed:F1}s) - KRITICKÝ ÚDER!"; + int baseDmg = State.Hero.AttackPower; + int comboBonus = (State.ComboStreak - 1) * _config.Battle.ComboStreakBonusDmg; + totalDamage = (int)(baseDmg * _config.Battle.CriticalDamageMultiplier) + comboBonus; + State.LastFeedbackMessage = $"⚡ BLESKOVO! ({elapsed:F1}s) - KRITICKÝ ÚDER! (-{totalDamage} HP)"; } else if (elapsed <= _config.Battle.StandardTimeSeconds) { + State.ComboStreak++; result = CombatResultType.StandardHit; - multiplier = 1.0; - State.LastFeedbackMessage = $"⚔️ SKVELÝ ZÁSAH! ({elapsed:F1}s)"; + int baseDmg = State.Hero.AttackPower; + int comboBonus = (State.ComboStreak - 1) * _config.Battle.ComboStreakBonusDmg; + totalDamage = baseDmg + comboBonus; + State.LastFeedbackMessage = $"⚔️ SKVELÝ ZÁSAH! ({elapsed:F1}s) (-{totalDamage} HP)"; } else { result = CombatResultType.WeakHit; - multiplier = _config.Battle.WeakDamageMultiplier; - State.LastFeedbackMessage = $"✨ ZÁSAH! ({elapsed:F1}s)"; + totalDamage = 1; + State.LastFeedbackMessage = $"✨ SPRÁVNE! ({elapsed:F1}s) - Zásah za 1 DMG!"; } - int baseDmg = State.Hero.AttackPower; - int comboBonus = (State.ComboStreak - 1) * _config.Battle.ComboStreakBonusDmg; - int totalDamage = (int)(baseDmg * multiplier) + comboBonus; - State.EnemyCurrentHp = Math.Max(0, State.EnemyCurrentHp - totalDamage); State.PlayerPose = CombatPose.Attack; State.EnemyPose = CombatPose.Hurt; diff --git a/src/CiselnaVyprava/Services/MathProblemGenerator.cs b/src/CiselnaVyprava/Services/MathProblemGenerator.cs index b01dece..f386872 100644 --- a/src/CiselnaVyprava/Services/MathProblemGenerator.cs +++ b/src/CiselnaVyprava/Services/MathProblemGenerator.cs @@ -470,8 +470,48 @@ public class MathProblemGenerator : IMathProblemGenerator int valLeft, valRight; string exprLeft, exprRight; - if (config.MaxA > 100) + if (config.MaxSum <= 10 || (config.MaxA <= 10 && config.MaxSum <= 12)) { + // Grade 1 - Within 10 + int a = _rand.Next(1, 6); + int b = _rand.Next(1, 10 - a + 1); + valLeft = a + b; + exprLeft = $"{a} + {b}"; + + int c = _rand.Next(4, 10); + int d = _rand.Next(1, c); + valRight = c - d; + exprRight = $"{c} - {d}"; + } + else if (config.MaxSum <= 20 || (config.MaxA <= 20 && config.MaxSum <= 25)) + { + // Grade 1/2 - Within 20 + int a = _rand.Next(2, 10); + int b = _rand.Next(2, 20 - a + 1); + valLeft = a + b; + exprLeft = $"{a} + {b}"; + + int c = _rand.Next(8, 20); + int d = _rand.Next(1, Math.Min(9, c)); + valRight = c - d; + exprRight = $"{c} - {d}"; + } + else if (config.MaxSum == 100 && !config.AllowCarryOver && config.MinA >= 10) + { + // Grade 1 World 4 - Multiples of 10 + int a = _rand.Next(1, 6) * 10; + int b = _rand.Next(1, 10 - (a / 10) + 1) * 10; + valLeft = a + b; + exprLeft = $"{a} + {b}"; + + int c = _rand.Next(4, 10) * 10; + int d = _rand.Next(1, c / 10) * 10; + valRight = c - d; + exprRight = $"{c} - {d}"; + } + else if (config.MaxSum > 100 || config.MaxA > 100) + { + // Grade 3 - Hundreds up to 1000 int a = _rand.Next(10, 50) * 10; int b = _rand.Next(5, 30) * 10; valLeft = a + b; @@ -482,8 +522,9 @@ public class MathProblemGenerator : IMathProblemGenerator valRight = c - d; exprRight = $"{c} - {d}"; } - else if (config.MaxSum > 20 || config.MaxA > 20) + else { + // Grade 2 - Within 100 int a = _rand.Next(10, 45); int b = _rand.Next(5, 30); valLeft = a + b; @@ -494,23 +535,12 @@ public class MathProblemGenerator : IMathProblemGenerator valRight = c - d; exprRight = $"{c} - {d}"; } - else - { - int a = _rand.Next(2, 9); - int b = _rand.Next(2, 9); - valLeft = a + b; - exprLeft = $"{a} + {b}"; - - int c = _rand.Next(6, 18); - int d = _rand.Next(1, 6); - valRight = c - d; - exprRight = $"{c} - {d}"; - } if (_rand.Next(3) == 0) { valRight = valLeft; - int offset = _rand.Next(1, 8); + int maxOffset = config.MaxSum <= 10 ? 3 : (config.MaxSum <= 20 ? 5 : 10); + int offset = _rand.Next(1, maxOffset); exprRight = $"{valLeft + offset} - {offset}"; } @@ -537,9 +567,75 @@ public class MathProblemGenerator : IMathProblemGenerator int missing, known, result; string equation; string explanation; + int choiceMax; - if (config.MaxSum > 100 || config.MaxA > 100) + if (config.MaxSum <= 10 || (config.MaxA <= 10 && config.MaxSum <= 12)) { + // Grade 1 - Up to 10 + choiceMax = 10; + if (isAdd) + { + known = _rand.Next(1, 6); + missing = _rand.Next(1, 10 - known + 1); + result = known + missing; + equation = _rand.Next(2) == 0 ? $"{known} + ❓ = {result}" : $"❓ + {known} = {result}"; + explanation = $"Neznáme číslo nájdeme odčítaním: {result} - {known} = {missing}."; + } + else + { + missing = _rand.Next(1, 6); + result = _rand.Next(1, 10 - missing + 1); + int total = result + missing; + equation = $"{total} - ❓ = {result}"; + explanation = $"Neznáme číslo zistíme odčítaním: {total} - {result} = {missing}."; + } + } + else if (config.MaxSum <= 20 || (config.MaxA <= 20 && config.MaxSum <= 25)) + { + // Grade 1/2 - Up to 20 + choiceMax = 20; + if (isAdd) + { + known = _rand.Next(3, 11); + missing = _rand.Next(2, 20 - known + 1); + result = known + missing; + equation = _rand.Next(2) == 0 ? $"{known} + ❓ = {result}" : $"❓ + {known} = {result}"; + explanation = $"Neznáme číslo nájdeme odčítaním: {result} - {known} = {missing}."; + } + else + { + missing = _rand.Next(2, 10); + result = _rand.Next(3, 20 - missing + 1); + int total = result + missing; + equation = $"{total} - ❓ = {result}"; + explanation = $"Neznáme číslo zistíme odčítaním: {total} - {result} = {missing}."; + } + } + else if (config.MaxSum == 100 && !config.AllowCarryOver && config.MinA >= 10) + { + // Grade 1 World 4 - Multiples of 10 + choiceMax = 100; + if (isAdd) + { + known = _rand.Next(1, 6) * 10; + missing = _rand.Next(1, 10 - (known / 10) + 1) * 10; + result = known + missing; + equation = _rand.Next(2) == 0 ? $"{known} + ❓ = {result}" : $"❓ + {known} = {result}"; + explanation = $"Neznáme číslo nájdeme odčítaním: {result} - {known} = {missing}."; + } + else + { + missing = _rand.Next(1, 6) * 10; + result = _rand.Next(1, 10 - (missing / 10) + 1) * 10; + int total = result + missing; + equation = $"{total} - ❓ = {result}"; + explanation = $"Neznáme číslo zistíme odčítaním: {total} - {result} = {missing}."; + } + } + else if (config.MaxSum > 100 || config.MaxA > 100) + { + // Grade 3 - Hundreds up to 1000 + choiceMax = 1000; known = _rand.Next(10, 50) * 10; missing = _rand.Next(10, 40) * 10; if (isAdd) @@ -556,8 +652,10 @@ public class MathProblemGenerator : IMathProblemGenerator explanation = $"Neznáme číslo zistíme odčítaním výsledku od celku: {total} - {result} = {missing}."; } } - else if (config.MaxSum > 20 || config.MaxA > 20) + else { + // Grade 2 - Up to 100 + choiceMax = 100; known = _rand.Next(12, 50); missing = _rand.Next(8, 35); if (isAdd) @@ -574,24 +672,6 @@ public class MathProblemGenerator : IMathProblemGenerator explanation = $"Neznáme číslo zistíme odčítaním výsledku od celku: {total} - {result} = {missing}."; } } - else - { - known = _rand.Next(3, 10); - missing = _rand.Next(3, 10); - if (isAdd) - { - result = known + missing; - equation = _rand.Next(2) == 0 ? $"{known} + ❓ = {result}" : $"❓ + {known} = {result}"; - explanation = $"Neznáme číslo nájdeme odčítaním: {result} - {known} = {missing}."; - } - else - { - result = known; - int total = known + missing; - equation = $"{total} - ❓ = {result}"; - explanation = $"Neznáme číslo zistíme odčítaním: {total} - {result} = {missing}."; - } - } return new MathProblem { @@ -602,40 +682,64 @@ public class MathProblemGenerator : IMathProblemGenerator QuestionText = $"Doplň chýbajúce číslo ❓:\n{equation}", Explanation = explanation, InputType = "MultipleChoice", - Choices = GenerateNumericChoices(missing, 1, 1000), + Choices = GenerateNumericChoices(missing, 1, choiceMax), IsDefenseTest = true }; } private MathProblem GenerateThreeTermChainDefense(MathConfig config) { - int a, b, c, result; + int a, b, c, result, choiceMax; string expr; - if (config.MaxSum > 100 || config.MaxA > 100) + if (config.MaxSum <= 10 || (config.MaxA <= 10 && config.MaxSum <= 12)) { + // Grade 1 - Up to 10 + choiceMax = 10; + a = _rand.Next(2, 6); + b = _rand.Next(1, 10 - a + 1); + c = _rand.Next(1, a + b); + result = a + b - c; + expr = $"{a} + {b} - {c}"; + } + else if (config.MaxSum <= 20 || (config.MaxA <= 20 && config.MaxSum <= 25)) + { + // Grade 1/2 - Up to 20 + choiceMax = 20; + a = _rand.Next(4, 10); + b = _rand.Next(3, 20 - a + 1); + c = _rand.Next(2, a + b - 1); + result = a + b - c; + expr = $"{a} + {b} - {c}"; + } + else if (config.MaxSum == 100 && !config.AllowCarryOver && config.MinA >= 10) + { + // Grade 1 World 4 - Multiples of 10 + choiceMax = 100; + a = _rand.Next(2, 6) * 10; + b = _rand.Next(1, 10 - (a / 10) + 1) * 10; + c = _rand.Next(1, (a + b) / 10) * 10; + result = a + b - c; + expr = $"{a} + {b} - {c}"; + } + else if (config.MaxSum > 100 || config.MaxA > 100) + { + choiceMax = 1000; a = _rand.Next(15, 45) * 10; b = _rand.Next(10, 30) * 10; c = _rand.Next(5, 20) * 10; result = a + b - c; expr = $"{a} + {b} - {c}"; } - else if (config.MaxSum > 20 || config.MaxA > 20) + else { + choiceMax = 100; a = _rand.Next(15, 50); b = _rand.Next(10, 30); c = _rand.Next(5, 20); result = a + b - c; expr = $"{a} + {b} - {c}"; } - else - { - a = _rand.Next(4, 9); - b = _rand.Next(3, 8); - c = _rand.Next(2, 6); - result = a + b - c; - expr = $"{a} + {b} - {c}"; - } return new MathProblem { @@ -646,7 +750,7 @@ public class MathProblemGenerator : IMathProblemGenerator QuestionText = $"Vypočítaj trojčlenný reťazec:\n{expr} = ?", Explanation = $"Počítaj po krokoch zľava doprava: najprv {a} + {b} = {a + b}, a potom {a + b} - {c} = {result}.", InputType = "MultipleChoice", - Choices = GenerateNumericChoices(result, 0, 1000), + Choices = GenerateNumericChoices(result, 0, choiceMax), IsDefenseTest = true }; } @@ -730,8 +834,65 @@ public class MathProblemGenerator : IMathProblemGenerator ? $"Rovnica {equation} je správna, pretože {a} × {b} sa skutočne rovná {prod}." : $"Rovnica {equation} je nesprávna! Správny výsledok {a} × {b} je {prod} (nie {displayed})."; } + else if (config.MaxSum <= 10 || (config.MaxA <= 10 && config.MaxSum <= 12)) + { + // Grade 1 - Up to 10 + int a = _rand.Next(1, 6); + int b = _rand.Next(1, 10 - a + 1); + int sum = a + b; + int offset = _rand.Next(2) == 0 ? 1 : -1; + int displayed = isCorrect ? sum : Math.Max(1, Math.Min(10, sum + offset)); + if (!isCorrect && displayed == sum) displayed = sum > 5 ? sum - 1 : sum + 1; + equation = $"{a} + {b} = {displayed}"; + explanation = isCorrect + ? $"Rovnica {equation} je správna, pretože {a} + {b} sa skutočne rovná {sum}." + : $"Rovnica {equation} je nesprávna! Správny výsledok {a} + {b} je {sum} (nie {displayed})."; + } + else if (config.MaxSum <= 20 || (config.MaxA <= 20 && config.MaxSum <= 25)) + { + // Grade 1/2 - Up to 20 + int a = _rand.Next(3, 11); + int b = _rand.Next(2, 20 - a + 1); + int sum = a + b; + int offset = _rand.Next(2) == 0 ? 1 : -1; + int displayed = isCorrect ? sum : Math.Max(1, Math.Min(20, sum + offset)); + if (!isCorrect && displayed == sum) displayed = sum > 10 ? sum - 2 : sum + 2; + equation = $"{a} + {b} = {displayed}"; + explanation = isCorrect + ? $"Rovnica {equation} je správna, pretože {a} + {b} sa skutočne rovná {sum}." + : $"Rovnica {equation} je nesprávna! Správny výsledok {a} + {b} je {sum} (nie {displayed})."; + } + else if (config.MaxSum == 100 && !config.AllowCarryOver && config.MinA >= 10) + { + // Grade 1 World 4 - Multiples of 10 + int a = _rand.Next(1, 6) * 10; + int b = _rand.Next(1, 10 - (a / 10) + 1) * 10; + int sum = a + b; + int offset = _rand.Next(2) == 0 ? 10 : -10; + int displayed = isCorrect ? sum : Math.Max(10, Math.Min(100, sum + offset)); + if (!isCorrect && displayed == sum) displayed = sum > 50 ? sum - 10 : sum + 10; + equation = $"{a} + {b} = {displayed}"; + explanation = isCorrect + ? $"Rovnica {equation} je správna, pretože {a} + {b} sa skutočne rovná {sum}." + : $"Rovnica {equation} je nesprávna! Správny výsledok {a} + {b} je {sum} (nie {displayed})."; + } + else if (config.MaxSum > 100 || config.MaxA > 100) + { + // Grade 3 - Hundreds up to 1000 + int a = _rand.Next(10, 45) * 10; + int b = _rand.Next(10, 45) * 10; + int sum = a + b; + int offset = _rand.Next(2) == 0 ? 10 : -10; + int displayed = isCorrect ? sum : (sum + offset); + if (displayed <= 0) displayed = sum + 20; + equation = $"{a} + {b} = {displayed}"; + explanation = isCorrect + ? $"Rovnica {equation} je správna, pretože {a} + {b} sa skutočne rovná {sum}." + : $"Rovnica {equation} je nesprávna! Správny výsledok {a} + {b} je {sum} (nie {displayed})."; + } else { + // Grade 2 - Up to 100 int a = _rand.Next(15, 45); int b = _rand.Next(15, 45); int sum = a + b; diff --git a/src/CiselnaVyprava/wwwroot/data/game-config.json b/src/CiselnaVyprava/wwwroot/data/game-config.json index 37a8440..1e82a54 100644 --- a/src/CiselnaVyprava/wwwroot/data/game-config.json +++ b/src/CiselnaVyprava/wwwroot/data/game-config.json @@ -1,10 +1,10 @@ { "battle": { - "criticalTimeSeconds": 4.0, - "standardTimeSeconds": 10.0, + "criticalTimeSeconds": 6.0, + "standardTimeSeconds": 16.0, "criticalDamageMultiplier": 1.5, - "weakDamageMultiplier": 0.5, - "defenseTestDurationSeconds": 16.0, + "weakDamageMultiplier": 0.1, + "defenseTestDurationSeconds": 25.0, "defaultPlayerHp": 5, "comboStreakBonusDmg": 1 },