This commit is contained in:
Michal Vrba
2026-09-24 18:14:03 +02:00
parent 135230f13a
commit bdd2f8dc48
4 changed files with 258 additions and 89 deletions
+32 -25
View File
@@ -62,8 +62,8 @@
<!-- Center Round Timer Orb --> <!-- Center Round Timer Orb -->
<div class="arena-center-timer"> <div class="arena-center-timer">
<div class="timer-orb @GetTimerColorClass()"> <div class="timer-orb @GetTimerColorClass()">
<div class="timer-seconds">@RemainingTimeSeconds.ToString("F1")s</div> <div class="timer-seconds">@(RemainingTimeSeconds > 0 ? RemainingTimeSeconds.ToString("F1") + "s" : (State.IsDefensePhase ? "0.0s" : "1 DMG"))</div>
<div class="timer-label">@(State.IsDefensePhase ? "OBRANA!" : "ÚTOK")</div> <div class="timer-label">@GetTimerLabel()</div>
</div> </div>
</div> </div>
@@ -325,10 +325,10 @@
if (RemainingTimeSeconds <= 0) if (RemainingTimeSeconds <= 0)
{ {
_timer?.Dispose(); if (State.IsDefensePhase)
await InvokeAsync(async () =>
{ {
if (State.IsDefensePhase) _timer?.Dispose();
await InvokeAsync(async () =>
{ {
var prob = State.CurrentProblem; var prob = State.CurrentProblem;
var result = await BattleEngine.SubmitDefenseAnswerAsync(""); var result = await BattleEngine.SubmitDefenseAnswerAsync("");
@@ -339,21 +339,9 @@
await PlayCombatSfx(result); await PlayCombatSfx(result);
_ = AudioService.PlaySfxAsync("owl_idea"); _ = AudioService.PlaySfxAsync("owl_idea");
StateHasChanged(); StateHasChanged();
} });
else return;
{ }
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;
} }
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
@@ -605,11 +593,30 @@
private string GetTimerColorClass() private string GetTimerColorClass()
{ {
double limit = State.IsDefensePhase ? Config.Battle.DefenseTestDurationSeconds : Config.Battle.StandardTimeSeconds; if (State.IsDefensePhase)
double ratio = limit > 0 ? (RemainingTimeSeconds / limit) : 0; {
if (ratio > 0.45) return "timer-green"; double limit = Config.Battle.DefenseTestDurationSeconds;
if (ratio > 0.20) return "timer-yellow"; double ratio = limit > 0 ? (RemainingTimeSeconds / limit) : 0;
return "timer-red"; 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 private string GetFeedbackClass() => State.LastResult switch
+13 -12
View File
@@ -65,34 +65,35 @@ public class BattleEngine : IBattleEngine
if (isCorrect) if (isCorrect)
{ {
State.ComboStreak++;
State.SolvedInLevel++; State.SolvedInLevel++;
CombatResultType result; CombatResultType result;
double multiplier; int totalDamage;
if (elapsed <= _config.Battle.CriticalTimeSeconds) if (elapsed <= _config.Battle.CriticalTimeSeconds)
{ {
State.ComboStreak++;
result = CombatResultType.CriticalHit; result = CombatResultType.CriticalHit;
multiplier = _config.Battle.CriticalDamageMultiplier; int baseDmg = State.Hero.AttackPower;
State.LastFeedbackMessage = $"⚡ BLESKOVO! ({elapsed:F1}s) - KRITICKÝ ÚDER!"; 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) else if (elapsed <= _config.Battle.StandardTimeSeconds)
{ {
State.ComboStreak++;
result = CombatResultType.StandardHit; result = CombatResultType.StandardHit;
multiplier = 1.0; int baseDmg = State.Hero.AttackPower;
State.LastFeedbackMessage = $"⚔️ SKVELÝ ZÁSAH! ({elapsed:F1}s)"; int comboBonus = (State.ComboStreak - 1) * _config.Battle.ComboStreakBonusDmg;
totalDamage = baseDmg + comboBonus;
State.LastFeedbackMessage = $"⚔️ SKVELÝ ZÁSAH! ({elapsed:F1}s) (-{totalDamage} HP)";
} }
else else
{ {
result = CombatResultType.WeakHit; result = CombatResultType.WeakHit;
multiplier = _config.Battle.WeakDamageMultiplier; totalDamage = 1;
State.LastFeedbackMessage = $"✨ ZÁSAH! ({elapsed:F1}s)"; 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.EnemyCurrentHp = Math.Max(0, State.EnemyCurrentHp - totalDamage);
State.PlayerPose = CombatPose.Attack; State.PlayerPose = CombatPose.Attack;
State.EnemyPose = CombatPose.Hurt; State.EnemyPose = CombatPose.Hurt;
@@ -470,8 +470,48 @@ public class MathProblemGenerator : IMathProblemGenerator
int valLeft, valRight; int valLeft, valRight;
string exprLeft, exprRight; 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 a = _rand.Next(10, 50) * 10;
int b = _rand.Next(5, 30) * 10; int b = _rand.Next(5, 30) * 10;
valLeft = a + b; valLeft = a + b;
@@ -482,8 +522,9 @@ public class MathProblemGenerator : IMathProblemGenerator
valRight = c - d; valRight = c - d;
exprRight = $"{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 a = _rand.Next(10, 45);
int b = _rand.Next(5, 30); int b = _rand.Next(5, 30);
valLeft = a + b; valLeft = a + b;
@@ -494,23 +535,12 @@ public class MathProblemGenerator : IMathProblemGenerator
valRight = c - d; valRight = c - d;
exprRight = $"{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) if (_rand.Next(3) == 0)
{ {
valRight = valLeft; 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}"; exprRight = $"{valLeft + offset} - {offset}";
} }
@@ -537,9 +567,75 @@ public class MathProblemGenerator : IMathProblemGenerator
int missing, known, result; int missing, known, result;
string equation; string equation;
string explanation; 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; known = _rand.Next(10, 50) * 10;
missing = _rand.Next(10, 40) * 10; missing = _rand.Next(10, 40) * 10;
if (isAdd) 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}."; 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); known = _rand.Next(12, 50);
missing = _rand.Next(8, 35); missing = _rand.Next(8, 35);
if (isAdd) 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}."; 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 return new MathProblem
{ {
@@ -602,40 +682,64 @@ public class MathProblemGenerator : IMathProblemGenerator
QuestionText = $"Doplň chýbajúce číslo ❓:\n{equation}", QuestionText = $"Doplň chýbajúce číslo ❓:\n{equation}",
Explanation = explanation, Explanation = explanation,
InputType = "MultipleChoice", InputType = "MultipleChoice",
Choices = GenerateNumericChoices(missing, 1, 1000), Choices = GenerateNumericChoices(missing, 1, choiceMax),
IsDefenseTest = true IsDefenseTest = true
}; };
} }
private MathProblem GenerateThreeTermChainDefense(MathConfig config) private MathProblem GenerateThreeTermChainDefense(MathConfig config)
{ {
int a, b, c, result; int a, b, c, result, choiceMax;
string expr; 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; a = _rand.Next(15, 45) * 10;
b = _rand.Next(10, 30) * 10; b = _rand.Next(10, 30) * 10;
c = _rand.Next(5, 20) * 10; c = _rand.Next(5, 20) * 10;
result = a + b - c; result = a + b - c;
expr = $"{a} + {b} - {c}"; expr = $"{a} + {b} - {c}";
} }
else if (config.MaxSum > 20 || config.MaxA > 20) else
{ {
choiceMax = 100;
a = _rand.Next(15, 50); a = _rand.Next(15, 50);
b = _rand.Next(10, 30); b = _rand.Next(10, 30);
c = _rand.Next(5, 20); c = _rand.Next(5, 20);
result = a + b - c; result = a + b - c;
expr = $"{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 return new MathProblem
{ {
@@ -646,7 +750,7 @@ public class MathProblemGenerator : IMathProblemGenerator
QuestionText = $"Vypočítaj trojčlenný reťazec:\n{expr} = ?", 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}.", Explanation = $"Počítaj po krokoch zľava doprava: najprv {a} + {b} = {a + b}, a potom {a + b} - {c} = {result}.",
InputType = "MultipleChoice", InputType = "MultipleChoice",
Choices = GenerateNumericChoices(result, 0, 1000), Choices = GenerateNumericChoices(result, 0, choiceMax),
IsDefenseTest = true 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 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})."; : $"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 else
{ {
// Grade 2 - Up to 100
int a = _rand.Next(15, 45); int a = _rand.Next(15, 45);
int b = _rand.Next(15, 45); int b = _rand.Next(15, 45);
int sum = a + b; int sum = a + b;
@@ -1,10 +1,10 @@
{ {
"battle": { "battle": {
"criticalTimeSeconds": 4.0, "criticalTimeSeconds": 6.0,
"standardTimeSeconds": 10.0, "standardTimeSeconds": 16.0,
"criticalDamageMultiplier": 1.5, "criticalDamageMultiplier": 1.5,
"weakDamageMultiplier": 0.5, "weakDamageMultiplier": 0.1,
"defenseTestDurationSeconds": 16.0, "defenseTestDurationSeconds": 25.0,
"defaultPlayerHp": 5, "defaultPlayerHp": 5,
"comboStreakBonusDmg": 1 "comboStreakBonusDmg": 1
}, },