changes
@@ -1,5 +1,6 @@
|
||||
@inject IGameDataLoader DataLoader
|
||||
@inject IBattleEngine BattleEngine
|
||||
@implements IDisposable
|
||||
|
||||
<div class="battle-arena-wrapper" tabindex="0" @onkeydown="HandleKeyDown" @ref="arenaElement">
|
||||
<!-- Top HUD / Level Info -->
|
||||
@@ -88,7 +89,7 @@
|
||||
{
|
||||
<div class="equation-box @(State.IsDefensePhase ? "defense-glow" : "attack-glow")">
|
||||
<span class="equation-title">
|
||||
@(State.IsDefensePhase ? "🛡️ OBRANNÝ TEST: Vyber správnu odpoveď!" : "⚔️ VYPOČÍTAJ A ZAÚTOČ:")
|
||||
@(State.IsDefensePhase ? "🛡️ ZÁCHRANNÁ OBRANA: Vyrieš náročnú výzvu a aktivuj štít!" : "⚔️ VYPOČÍTAJ A ZAÚTOČ:")
|
||||
</span>
|
||||
<h1 class="equation-text">@State.CurrentProblem.QuestionText</h1>
|
||||
</div>
|
||||
@@ -213,11 +214,30 @@
|
||||
private double RemainingTimeSeconds { get; set; } = 6.0;
|
||||
|
||||
private Level? NextLevel => DataLoader.GetNextLevel(PlayerGrade, CurrentLevel.Id);
|
||||
private string? _loadedLevelId;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (CurrentLevel != null && !string.IsNullOrEmpty(CurrentLevel.Id))
|
||||
{
|
||||
_loadedLevelId = CurrentLevel.Id;
|
||||
StartBattle();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (CurrentLevel != null && !string.IsNullOrEmpty(CurrentLevel.Id) && CurrentLevel.Id != _loadedLevelId)
|
||||
{
|
||||
_loadedLevelId = CurrentLevel.Id;
|
||||
StartBattle();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer?.Dispose();
|
||||
}
|
||||
|
||||
private void StartBattle()
|
||||
{
|
||||
@@ -407,6 +427,16 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Key is "<" or "=" or ">")
|
||||
{
|
||||
var match = State.CurrentProblem.Choices.FirstOrDefault(c => c == e.Key);
|
||||
if (match != null)
|
||||
{
|
||||
await HandleChoiceSelected(match);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.Key.Equals("a", StringComparison.OrdinalIgnoreCase) || e.Key.Equals("y", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var match = State.CurrentProblem.Choices.FirstOrDefault(c => c.StartsWith("Á", StringComparison.OrdinalIgnoreCase) || c.StartsWith("A", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
var currentWorld = worlds.FirstOrDefault(w => w.Levels.Any(l => l.Id == SelectedLevel.Id));
|
||||
var bg = currentWorld?.Background ?? "assets/images/backgrounds/world1_meadow.jpg";
|
||||
|
||||
<BattleArena Hero="hero"
|
||||
<BattleArena @key="SelectedLevel.Id"
|
||||
Hero="hero"
|
||||
CurrentLevel="SelectedLevel"
|
||||
PlayerGrade="Profile.SelectedGrade"
|
||||
BackgroundImage="@bg"
|
||||
@@ -154,9 +155,12 @@
|
||||
|
||||
private void UnlockNextLevel(Level completedLevel)
|
||||
{
|
||||
Profile.CompletedLevels.Add(completedLevel.Id);
|
||||
bool isFirstTime = Profile.CompletedLevels.Add(completedLevel.Id);
|
||||
if (isFirstTime)
|
||||
{
|
||||
Profile.TotalStars += completedLevel.IsBoss ? 3 : 1;
|
||||
Profile.Gold += completedLevel.IsBoss ? 50 : 20;
|
||||
}
|
||||
|
||||
var worlds = DataLoader.GetWorldsForGrade(Profile.SelectedGrade);
|
||||
var currentWorld = worlds.FirstOrDefault(w => w.Levels.Any(l => l.Id == completedLevel.Id));
|
||||
|
||||
@@ -51,7 +51,7 @@ public class BattleEngine : IBattleEngine
|
||||
|
||||
public void NextProblem()
|
||||
{
|
||||
State.CurrentProblem = _mathGenerator.GenerateProblem(State.CurrentLevel.MathConfig);
|
||||
State.CurrentProblem = _mathGenerator.GenerateProblem(State.CurrentLevel.MathConfig, State.EnemyHpPhase);
|
||||
State.ProblemStartTime = DateTime.UtcNow;
|
||||
State.IsDefensePhase = false;
|
||||
State.PlayerPose = CombatPose.Idle;
|
||||
@@ -117,9 +117,7 @@ public class BattleEngine : IBattleEngine
|
||||
State.EnemyPose = CombatPose.Attack;
|
||||
State.IsDefensePhase = true;
|
||||
State.LastResult = CombatResultType.PlayerMissed;
|
||||
State.LastFeedbackMessage = $"❌ Vedľa! Pozor na protiútok! Aktivuj štít!";
|
||||
|
||||
State.CurrentProblem = _mathGenerator.GenerateDefenseTest();
|
||||
State.CurrentProblem = _mathGenerator.GenerateDefenseTest(State.CurrentLevel.MathConfig);
|
||||
State.ProblemStartTime = DateTime.UtcNow;
|
||||
|
||||
return CombatResultType.PlayerMissed;
|
||||
|
||||
@@ -4,41 +4,99 @@ namespace CiselnaVyprava.Services;
|
||||
|
||||
public interface IMathProblemGenerator
|
||||
{
|
||||
MathProblem GenerateProblem(MathConfig config);
|
||||
MathProblem GenerateDefenseTest();
|
||||
MathProblem GenerateProblem(MathConfig config, int hpPhase = 1);
|
||||
MathProblem GenerateDefenseTest(MathConfig config);
|
||||
}
|
||||
|
||||
public class MathProblemGenerator : IMathProblemGenerator
|
||||
{
|
||||
private readonly Random _rand = new();
|
||||
|
||||
public MathProblem GenerateProblem(MathConfig config)
|
||||
public MathProblem GenerateProblem(MathConfig config, int hpPhase = 1)
|
||||
{
|
||||
var op = config.Operations[_rand.Next(config.Operations.Count)];
|
||||
|
||||
return op switch
|
||||
{
|
||||
"+" => GenerateAddition(config),
|
||||
"-" => GenerateSubtraction(config),
|
||||
"*" => GenerateMultiplication(config),
|
||||
"/" => GenerateDivision(config),
|
||||
"<" or ">" or "=" => GenerateComparison(config),
|
||||
_ => GenerateAddition(config)
|
||||
"+" => GenerateAddition(config, hpPhase),
|
||||
"-" => GenerateSubtraction(config, hpPhase),
|
||||
"*" => GenerateMultiplication(config, hpPhase),
|
||||
"/" => GenerateDivision(config, hpPhase),
|
||||
"<" or ">" or "=" => GenerateComparison(config, hpPhase),
|
||||
_ => GenerateAddition(config, hpPhase)
|
||||
};
|
||||
}
|
||||
|
||||
private MathProblem GenerateAddition(MathConfig config)
|
||||
private MathProblem GenerateAddition(MathConfig config, int hpPhase)
|
||||
{
|
||||
// Phase 4: Possibility of 3-term chain or climax numbers
|
||||
if (hpPhase >= 4 && _rand.Next(3) == 0 && config.MaxSum >= 10)
|
||||
{
|
||||
int c1 = Math.Max(1, config.MinA);
|
||||
int c2 = Math.Max(1, config.MinB);
|
||||
int maxPart = Math.Max(2, config.MaxSum / 3);
|
||||
int a3 = _rand.Next(c1, maxPart + 1);
|
||||
int b3 = _rand.Next(c2, Math.Min(config.MaxB, config.MaxSum - a3 - 1) + 1);
|
||||
int c3 = _rand.Next(1, Math.Min(config.MaxB, config.MaxSum - a3 - b3) + 1);
|
||||
int sum3 = a3 + b3 + c3;
|
||||
|
||||
var chainProblem = new MathProblem
|
||||
{
|
||||
DisplayA = $"{a3} + {b3} + {c3}",
|
||||
Operation = "+",
|
||||
DisplayB = "",
|
||||
CorrectAnswer = sum3.ToString(),
|
||||
QuestionText = $"{a3} + {b3} + {c3} = ?",
|
||||
InputType = config.InputType
|
||||
};
|
||||
if (config.InputType == "MultipleChoice")
|
||||
{
|
||||
chainProblem.Choices = GenerateNumericChoices(sum3, 1, config.MaxSum);
|
||||
}
|
||||
return chainProblem;
|
||||
}
|
||||
|
||||
int a, b;
|
||||
int minA = config.MinA;
|
||||
int maxA = config.MaxA;
|
||||
int minB = config.MinB;
|
||||
int maxB = config.MaxB;
|
||||
|
||||
// Scale difficulty by phase
|
||||
if (hpPhase == 1)
|
||||
{
|
||||
maxA = Math.Max(minA, minA + (maxA - minA) / 2);
|
||||
maxB = Math.Max(minB, minB + (maxB - minB) / 2);
|
||||
}
|
||||
else if (hpPhase >= 3)
|
||||
{
|
||||
minA = Math.Min(maxA, minA + (maxA - minA) / 3);
|
||||
minB = Math.Min(maxB, minB + (maxB - minB) / 3);
|
||||
}
|
||||
|
||||
if (config.AllowCarryOver)
|
||||
{
|
||||
a = _rand.Next(config.MinA, config.MaxA + 1);
|
||||
b = _rand.Next(config.MinB, Math.Min(config.MaxB, config.MaxSum - a) + 1);
|
||||
a = _rand.Next(minA, maxA + 1);
|
||||
int remainingMax = Math.Min(maxB, config.MaxSum - a);
|
||||
b = _rand.Next(minB, Math.Max(minB, remainingMax) + 1);
|
||||
|
||||
// In Phase 3-4, prefer carry over (sum of units >= 10) if possible
|
||||
if (hpPhase >= 3 && config.MaxSum >= 20)
|
||||
{
|
||||
int tries = 0;
|
||||
while ((a % 10 + b % 10 < 10) && tries < 15)
|
||||
{
|
||||
tries++;
|
||||
a = _rand.Next(minA, maxA + 1);
|
||||
remainingMax = Math.Min(maxB, config.MaxSum - a);
|
||||
b = _rand.Next(minB, Math.Max(minB, remainingMax) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
a = _rand.Next(config.MinA, config.MaxA + 1);
|
||||
b = _rand.Next(config.MinB, config.MaxB + 1);
|
||||
a = _rand.Next(minA, maxA + 1);
|
||||
b = _rand.Next(minB, maxB + 1);
|
||||
}
|
||||
|
||||
int sum = a + b;
|
||||
@@ -60,10 +118,63 @@ public class MathProblemGenerator : IMathProblemGenerator
|
||||
return problem;
|
||||
}
|
||||
|
||||
private MathProblem GenerateSubtraction(MathConfig config)
|
||||
private MathProblem GenerateSubtraction(MathConfig config, int hpPhase)
|
||||
{
|
||||
int b = _rand.Next(config.MinB, config.MaxB + 1);
|
||||
int a = _rand.Next(Math.Max(b, config.MinA), config.MaxA + 1);
|
||||
// Phase 4: Possibility of 3-term mixed chain
|
||||
if (hpPhase >= 4 && _rand.Next(3) == 0 && config.MaxA >= 15)
|
||||
{
|
||||
int a3 = _rand.Next(Math.Max(10, config.MinA), config.MaxA + 1);
|
||||
int b3 = _rand.Next(2, Math.Max(3, a3 / 2));
|
||||
int c3 = _rand.Next(1, 6);
|
||||
int res3 = a3 - b3 + c3;
|
||||
|
||||
var chainProblem = new MathProblem
|
||||
{
|
||||
DisplayA = $"{a3} - {b3} + {c3}",
|
||||
Operation = "-",
|
||||
DisplayB = "",
|
||||
CorrectAnswer = res3.ToString(),
|
||||
QuestionText = $"{a3} - {b3} + {c3} = ?",
|
||||
InputType = config.InputType
|
||||
};
|
||||
if (config.InputType == "MultipleChoice")
|
||||
{
|
||||
chainProblem.Choices = GenerateNumericChoices(res3, 0, config.MaxA + 10);
|
||||
}
|
||||
return chainProblem;
|
||||
}
|
||||
|
||||
int minA = config.MinA;
|
||||
int maxA = config.MaxA;
|
||||
int minB = config.MinB;
|
||||
int maxB = config.MaxB;
|
||||
|
||||
if (hpPhase == 1)
|
||||
{
|
||||
maxA = Math.Max(minA, minA + (maxA - minA) / 2);
|
||||
maxB = Math.Max(minB, minB + (maxB - minB) / 2);
|
||||
}
|
||||
else if (hpPhase >= 3)
|
||||
{
|
||||
minA = Math.Min(maxA, minA + (maxA - minA) / 3);
|
||||
minB = Math.Min(maxB, minB + (maxB - minB) / 3);
|
||||
}
|
||||
|
||||
int b = _rand.Next(minB, maxB + 1);
|
||||
int a = _rand.Next(Math.Max(b, minA), maxA + 1);
|
||||
|
||||
// In Phase 3-4 with carry, prefer crossing ten (e.g., 14 - 8, 32 - 17)
|
||||
if (hpPhase >= 3 && config.AllowCarryOver && config.MaxA >= 15)
|
||||
{
|
||||
int tries = 0;
|
||||
while ((a % 10 >= b % 10) && tries < 15)
|
||||
{
|
||||
tries++;
|
||||
b = _rand.Next(minB, maxB + 1);
|
||||
a = _rand.Next(Math.Max(b, minA), maxA + 1);
|
||||
}
|
||||
}
|
||||
|
||||
int diff = a - b;
|
||||
|
||||
var problem = new MathProblem
|
||||
@@ -84,19 +195,34 @@ public class MathProblemGenerator : IMathProblemGenerator
|
||||
return problem;
|
||||
}
|
||||
|
||||
private MathProblem GenerateMultiplication(MathConfig config)
|
||||
private MathProblem GenerateMultiplication(MathConfig config, int hpPhase)
|
||||
{
|
||||
var factors = config.Factors != null && config.Factors.Count > 0
|
||||
? new List<int>(config.Factors)
|
||||
: new List<int> { 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||
|
||||
int factorA;
|
||||
if (config.Factors != null && config.Factors.Count > 0)
|
||||
int minMult = config.MinMultiplier;
|
||||
int maxMult = config.MaxMultiplier;
|
||||
|
||||
if (hpPhase == 1)
|
||||
{
|
||||
factorA = config.Factors[_rand.Next(config.Factors.Count)];
|
||||
var lowerFactors = factors.Where(f => f <= 5).ToList();
|
||||
factorA = lowerFactors.Any() ? lowerFactors[_rand.Next(lowerFactors.Count)] : factors[_rand.Next(factors.Count)];
|
||||
maxMult = Math.Min(5, config.MaxMultiplier);
|
||||
}
|
||||
else if (hpPhase >= 3)
|
||||
{
|
||||
var upperFactors = factors.Where(f => f >= 6).ToList();
|
||||
factorA = upperFactors.Any() ? upperFactors[_rand.Next(upperFactors.Count)] : factors[_rand.Next(factors.Count)];
|
||||
minMult = Math.Max(5, config.MinMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
factorA = _rand.Next(2, 10);
|
||||
factorA = factors[_rand.Next(factors.Count)];
|
||||
}
|
||||
|
||||
int factorB = _rand.Next(config.MinMultiplier, config.MaxMultiplier + 1);
|
||||
int factorB = _rand.Next(minMult, maxMult + 1);
|
||||
int product = factorA * factorB;
|
||||
|
||||
var problem = new MathProblem
|
||||
@@ -117,19 +243,34 @@ public class MathProblemGenerator : IMathProblemGenerator
|
||||
return problem;
|
||||
}
|
||||
|
||||
private MathProblem GenerateDivision(MathConfig config)
|
||||
private MathProblem GenerateDivision(MathConfig config, int hpPhase)
|
||||
{
|
||||
var factors = config.Factors != null && config.Factors.Count > 0
|
||||
? new List<int>(config.Factors)
|
||||
: new List<int> { 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||
|
||||
int divisor;
|
||||
if (config.Factors != null && config.Factors.Count > 0)
|
||||
int minMult = config.MinMultiplier;
|
||||
int maxMult = config.MaxMultiplier;
|
||||
|
||||
if (hpPhase == 1)
|
||||
{
|
||||
divisor = config.Factors[_rand.Next(config.Factors.Count)];
|
||||
var lower = factors.Where(f => f <= 5).ToList();
|
||||
divisor = lower.Any() ? lower[_rand.Next(lower.Count)] : factors[_rand.Next(factors.Count)];
|
||||
maxMult = Math.Min(5, config.MaxMultiplier);
|
||||
}
|
||||
else if (hpPhase >= 3)
|
||||
{
|
||||
var upper = factors.Where(f => f >= 6).ToList();
|
||||
divisor = upper.Any() ? upper[_rand.Next(upper.Count)] : factors[_rand.Next(factors.Count)];
|
||||
minMult = Math.Max(5, config.MinMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisor = _rand.Next(2, 10);
|
||||
divisor = factors[_rand.Next(factors.Count)];
|
||||
}
|
||||
|
||||
int result = _rand.Next(config.MinMultiplier, config.MaxMultiplier + 1);
|
||||
int result = _rand.Next(minMult, maxMult + 1);
|
||||
int dividend = divisor * result;
|
||||
|
||||
var problem = new MathProblem
|
||||
@@ -150,41 +291,370 @@ public class MathProblemGenerator : IMathProblemGenerator
|
||||
return problem;
|
||||
}
|
||||
|
||||
private MathProblem GenerateComparison(MathConfig config)
|
||||
private MathProblem GenerateComparison(MathConfig config, int hpPhase)
|
||||
{
|
||||
if (hpPhase >= 3)
|
||||
{
|
||||
int a = _rand.Next(config.MinA, config.MaxA + 1);
|
||||
int b = _rand.Next(config.MinB, config.MaxB + 1);
|
||||
int leftVal = a + b;
|
||||
string leftStr = $"{a} + {b}";
|
||||
|
||||
string answer = a < b ? "<" : (a > b ? ">" : "=");
|
||||
int rightVal;
|
||||
string rightStr;
|
||||
if (hpPhase == 3)
|
||||
{
|
||||
rightVal = _rand.Next(Math.Max(1, leftVal - 3), leftVal + 4);
|
||||
rightStr = rightVal.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
int c = _rand.Next(config.MinA, config.MaxA + 1);
|
||||
int d = _rand.Next(config.MinB, config.MaxB + 1);
|
||||
rightVal = c + d;
|
||||
rightStr = $"{c} + {d}";
|
||||
if (_rand.Next(3) == 0)
|
||||
{
|
||||
rightVal = leftVal;
|
||||
rightStr = $"{a + 1} + {b - 1}";
|
||||
}
|
||||
}
|
||||
|
||||
string comp = leftVal < rightVal ? "<" : (leftVal > rightVal ? ">" : "=");
|
||||
|
||||
return new MathProblem
|
||||
{
|
||||
DisplayA = a.ToString(),
|
||||
DisplayA = leftStr,
|
||||
Operation = "?",
|
||||
DisplayB = b.ToString(),
|
||||
CorrectAnswer = answer,
|
||||
QuestionText = $"{a} ? {b}",
|
||||
DisplayB = rightStr,
|
||||
CorrectAnswer = comp,
|
||||
QuestionText = $"{leftStr} ? {rightStr}",
|
||||
InputType = "MultipleChoice",
|
||||
Choices = new List<string> { "<", "=", ">" }
|
||||
};
|
||||
}
|
||||
|
||||
public MathProblem GenerateDefenseTest()
|
||||
int x = _rand.Next(config.MinA, config.MaxA + 1);
|
||||
int y = _rand.Next(config.MinB, config.MaxB + 1);
|
||||
|
||||
if (hpPhase == 2 && _rand.Next(2) == 0)
|
||||
{
|
||||
// Simple True/False defense shield challenge
|
||||
int a = _rand.Next(1, 10);
|
||||
int b = _rand.Next(1, 10);
|
||||
bool isCorrect = _rand.Next(2) == 0;
|
||||
int displayedResult = isCorrect ? (a + b) : (a + b + (_rand.Next(2) == 0 ? 1 : -1));
|
||||
if (displayedResult <= 0) displayedResult = a + b + 2;
|
||||
y = Math.Clamp(x + (_rand.Next(2) == 0 ? 1 : -1), config.MinB, config.MaxB);
|
||||
}
|
||||
|
||||
string answer = x < y ? "<" : (x > y ? ">" : "=");
|
||||
|
||||
return new MathProblem
|
||||
{
|
||||
DisplayA = $"{a} + {b} = {displayedResult}",
|
||||
DisplayA = x.ToString(),
|
||||
Operation = "?",
|
||||
DisplayB = y.ToString(),
|
||||
CorrectAnswer = answer,
|
||||
QuestionText = $"{x} ? {y}",
|
||||
InputType = "MultipleChoice",
|
||||
Choices = new List<string> { "<", "=", ">" }
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// ADVANCED / CHALLENGING RESCUE DEFENSE CHALLENGES
|
||||
// ========================================================
|
||||
public MathProblem GenerateDefenseTest(MathConfig config)
|
||||
{
|
||||
bool hasComparison = config.Operations.Any(o => o is "<" or ">" or "=");
|
||||
bool hasMultDiv = config.Operations.Any(o => o is "*" or "/");
|
||||
|
||||
if (hasComparison)
|
||||
{
|
||||
return GenerateChallengingComparisonDefense(config);
|
||||
}
|
||||
else if (hasMultDiv)
|
||||
{
|
||||
int variant = _rand.Next(3);
|
||||
return variant switch
|
||||
{
|
||||
0 => GenerateMissingFactorDefense(config),
|
||||
1 => GenerateMixedMultAddDefense(config),
|
||||
_ => GenerateTrickyVerificationDefense(config, isMult: true)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
int variant = _rand.Next(3);
|
||||
return variant switch
|
||||
{
|
||||
0 => GenerateMissingNumberDefense(config),
|
||||
1 => GenerateThreeTermChainDefense(config),
|
||||
_ => GenerateTrickyVerificationDefense(config, isMult: false)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private MathProblem GenerateChallengingComparisonDefense(MathConfig config)
|
||||
{
|
||||
// Compare expressions: (a + b) ? (c - d)
|
||||
int valLeft, valRight;
|
||||
string exprLeft, exprRight;
|
||||
|
||||
if (config.MaxA > 100) // Grade 3 large numbers
|
||||
{
|
||||
int a = _rand.Next(10, 50) * 10;
|
||||
int b = _rand.Next(5, 30) * 10;
|
||||
valLeft = a + b;
|
||||
exprLeft = $"{a} + {b}";
|
||||
|
||||
int c = _rand.Next(20, 80) * 10;
|
||||
int d = _rand.Next(5, 25) * 10;
|
||||
valRight = c - d;
|
||||
exprRight = $"{c} - {d}";
|
||||
}
|
||||
else if (config.MaxSum > 20 || config.MaxA > 20) // Grade 2
|
||||
{
|
||||
int a = _rand.Next(10, 45);
|
||||
int b = _rand.Next(5, 30);
|
||||
valLeft = a + b;
|
||||
exprLeft = $"{a} + {b}";
|
||||
|
||||
int c = _rand.Next(30, 80);
|
||||
int d = _rand.Next(5, 25);
|
||||
valRight = c - d;
|
||||
exprRight = $"{c} - {d}";
|
||||
}
|
||||
else // Grade 1
|
||||
{
|
||||
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}";
|
||||
}
|
||||
|
||||
// Sometimes create equal expressions
|
||||
if (_rand.Next(3) == 0)
|
||||
{
|
||||
valRight = valLeft;
|
||||
int offset = _rand.Next(1, 8);
|
||||
exprRight = $"{valLeft + offset} - {offset}";
|
||||
}
|
||||
|
||||
string answer = valLeft < valRight ? "<" : (valLeft > valRight ? ">" : "=");
|
||||
|
||||
return new MathProblem
|
||||
{
|
||||
DisplayA = exprLeft,
|
||||
Operation = "?",
|
||||
DisplayB = exprRight,
|
||||
CorrectAnswer = answer,
|
||||
QuestionText = $"Porovnaj výrazy:\n( {exprLeft} ) ? ( {exprRight} )",
|
||||
InputType = "MultipleChoice",
|
||||
Choices = new List<string> { "<", "=", ">" },
|
||||
IsDefenseTest = true
|
||||
};
|
||||
}
|
||||
|
||||
private MathProblem GenerateMissingNumberDefense(MathConfig config)
|
||||
{
|
||||
bool isAdd = _rand.Next(2) == 0;
|
||||
int missing, known, result;
|
||||
string equation;
|
||||
|
||||
if (config.MaxSum > 100 || config.MaxA > 100) // Grade 3
|
||||
{
|
||||
known = _rand.Next(10, 50) * 10;
|
||||
missing = _rand.Next(10, 40) * 10;
|
||||
if (isAdd)
|
||||
{
|
||||
result = known + missing;
|
||||
equation = _rand.Next(2) == 0 ? $"{known} + ❓ = {result}" : $"❓ + {known} = {result}";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = known;
|
||||
int total = known + missing;
|
||||
equation = $"{total} - ❓ = {result}";
|
||||
}
|
||||
}
|
||||
else if (config.MaxSum > 20 || config.MaxA > 20) // Grade 2
|
||||
{
|
||||
known = _rand.Next(12, 50);
|
||||
missing = _rand.Next(8, 35);
|
||||
if (isAdd)
|
||||
{
|
||||
result = known + missing;
|
||||
equation = _rand.Next(2) == 0 ? $"{known} + ❓ = {result}" : $"❓ + {known} = {result}";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = known;
|
||||
int total = known + missing;
|
||||
equation = $"{total} - ❓ = {result}";
|
||||
}
|
||||
}
|
||||
else // Grade 1
|
||||
{
|
||||
known = _rand.Next(3, 10);
|
||||
missing = _rand.Next(3, 10);
|
||||
if (isAdd)
|
||||
{
|
||||
result = known + missing;
|
||||
equation = _rand.Next(2) == 0 ? $"{known} + ❓ = {result}" : $"❓ + {known} = {result}";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = known;
|
||||
int total = known + missing;
|
||||
equation = $"{total} - ❓ = {result}";
|
||||
}
|
||||
}
|
||||
|
||||
return new MathProblem
|
||||
{
|
||||
DisplayA = equation,
|
||||
Operation = "",
|
||||
DisplayB = "",
|
||||
CorrectAnswer = missing.ToString(),
|
||||
QuestionText = $"Doplň chýbajúce číslo ❓:\n{equation}",
|
||||
InputType = "MultipleChoice",
|
||||
Choices = GenerateNumericChoices(missing, 1, 1000),
|
||||
IsDefenseTest = true
|
||||
};
|
||||
}
|
||||
|
||||
private MathProblem GenerateThreeTermChainDefense(MathConfig config)
|
||||
{
|
||||
int a, b, c, result;
|
||||
string expr;
|
||||
|
||||
if (config.MaxSum > 100 || config.MaxA > 100) // Grade 3
|
||||
{
|
||||
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) // Grade 2
|
||||
{
|
||||
a = _rand.Next(15, 50);
|
||||
b = _rand.Next(10, 30);
|
||||
c = _rand.Next(5, 20);
|
||||
result = a + b - c;
|
||||
expr = $"{a} + {b} - {c}";
|
||||
}
|
||||
else // Grade 1
|
||||
{
|
||||
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
|
||||
{
|
||||
DisplayA = expr,
|
||||
Operation = "",
|
||||
DisplayB = "",
|
||||
CorrectAnswer = result.ToString(),
|
||||
QuestionText = $"Vypočítaj trojčlenný reťazec:\n{expr} = ?",
|
||||
InputType = "MultipleChoice",
|
||||
Choices = GenerateNumericChoices(result, 0, 1000),
|
||||
IsDefenseTest = true
|
||||
};
|
||||
}
|
||||
|
||||
private MathProblem GenerateMissingFactorDefense(MathConfig config)
|
||||
{
|
||||
int factorA;
|
||||
if (config.Factors != null && config.Factors.Count > 0)
|
||||
{
|
||||
factorA = config.Factors[_rand.Next(config.Factors.Count)];
|
||||
}
|
||||
else
|
||||
{
|
||||
factorA = _rand.Next(2, 10);
|
||||
}
|
||||
|
||||
int missing = _rand.Next(2, 10);
|
||||
int product = factorA * missing;
|
||||
bool isDiv = _rand.Next(2) == 0;
|
||||
string equation = isDiv
|
||||
? $"{product} ÷ ❓ = {factorA}"
|
||||
: (_rand.Next(2) == 0 ? $"{factorA} × ❓ = {product}" : $"❓ × {factorA} = {product}");
|
||||
|
||||
return new MathProblem
|
||||
{
|
||||
DisplayA = equation,
|
||||
Operation = "",
|
||||
DisplayB = "",
|
||||
CorrectAnswer = missing.ToString(),
|
||||
QuestionText = $"Nájdi neznáme číslo ❓:\n{equation}",
|
||||
InputType = "MultipleChoice",
|
||||
Choices = GenerateNumericChoices(missing, 1, 10),
|
||||
IsDefenseTest = true
|
||||
};
|
||||
}
|
||||
|
||||
private MathProblem GenerateMixedMultAddDefense(MathConfig config)
|
||||
{
|
||||
int f1 = _rand.Next(2, 8);
|
||||
int f2 = _rand.Next(2, 6);
|
||||
int prod = f1 * f2;
|
||||
int add = _rand.Next(4, 16);
|
||||
|
||||
bool isSub = _rand.Next(2) == 0 && prod > add;
|
||||
int result = isSub ? prod - add : prod + add;
|
||||
string expr = isSub ? $"{f1} × {f2} - {add}" : $"{f1} × {f2} + {add}";
|
||||
|
||||
return new MathProblem
|
||||
{
|
||||
DisplayA = expr,
|
||||
Operation = "",
|
||||
DisplayB = "",
|
||||
CorrectAnswer = result.ToString(),
|
||||
QuestionText = $"Kombinovaný výpočet (najprv násob!):\n{expr} = ?",
|
||||
InputType = "MultipleChoice",
|
||||
Choices = GenerateNumericChoices(result, 1, 100),
|
||||
IsDefenseTest = true
|
||||
};
|
||||
}
|
||||
|
||||
private MathProblem GenerateTrickyVerificationDefense(MathConfig config, bool isMult)
|
||||
{
|
||||
bool isCorrect = _rand.Next(2) == 0;
|
||||
string equation;
|
||||
|
||||
if (isMult)
|
||||
{
|
||||
int a = _rand.Next(3, 10);
|
||||
int b = _rand.Next(3, 10);
|
||||
int prod = a * b;
|
||||
int displayed = isCorrect ? prod : (prod + (_rand.Next(2) == 0 ? _rand.Next(1, 4) : -_rand.Next(1, 4)));
|
||||
if (displayed <= 0) displayed = prod + 2;
|
||||
equation = $"{a} × {b} = {displayed}";
|
||||
}
|
||||
else
|
||||
{
|
||||
int a = _rand.Next(15, 45);
|
||||
int b = _rand.Next(15, 45);
|
||||
int sum = a + b;
|
||||
int offset = _rand.Next(2) == 0 ? (_rand.Next(2) == 0 ? 1 : -1) : (_rand.Next(2) == 0 ? 10 : -10);
|
||||
int displayed = isCorrect ? sum : (sum + offset);
|
||||
if (displayed <= 0) displayed = sum + 2;
|
||||
equation = $"{a} + {b} = {displayed}";
|
||||
}
|
||||
|
||||
return new MathProblem
|
||||
{
|
||||
DisplayA = equation,
|
||||
Operation = "",
|
||||
DisplayB = "",
|
||||
CorrectAnswer = isCorrect ? "ÁNO" : "NIE",
|
||||
QuestionText = $"Je to správne?\n{a} + {b} = {displayedResult}",
|
||||
QuestionText = $"Je táto rovnica správna?\n{equation}",
|
||||
InputType = "MultipleChoice",
|
||||
Choices = new List<string> { "ÁNO", "NIE" },
|
||||
IsDefenseTest = true
|
||||
@@ -194,13 +664,22 @@ public class MathProblemGenerator : IMathProblemGenerator
|
||||
private List<string> GenerateNumericChoices(int correct, int min, int max)
|
||||
{
|
||||
var set = new HashSet<int> { correct };
|
||||
while (set.Count < 3)
|
||||
int tries = 0;
|
||||
while (set.Count < 4 && tries < 30)
|
||||
{
|
||||
int offset = _rand.Next(-5, 6);
|
||||
if (offset == 0) offset = 1;
|
||||
tries++;
|
||||
int offset = _rand.Next(-4, 5);
|
||||
if (offset == 0) offset = _rand.Next(2) == 0 ? 1 : -1;
|
||||
int candidate = Math.Clamp(correct + offset, min, max);
|
||||
set.Add(candidate);
|
||||
}
|
||||
int fallback = 1;
|
||||
while (set.Count < 4)
|
||||
{
|
||||
set.Add(Math.Max(min, correct + fallback));
|
||||
fallback++;
|
||||
}
|
||||
|
||||
return set.OrderBy(_ => _rand.Next()).Select(x => x.ToString()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 884 KiB |
|
After Width: | Height: | Size: 884 KiB |
|
After Width: | Height: | Size: 883 KiB |
|
After Width: | Height: | Size: 883 KiB |
|
After Width: | Height: | Size: 769 KiB |
|
After Width: | Height: | Size: 769 KiB |
|
After Width: | Height: | Size: 769 KiB |
|
After Width: | Height: | Size: 770 KiB |
|
After Width: | Height: | Size: 758 KiB |
|
After Width: | Height: | Size: 758 KiB |
|
After Width: | Height: | Size: 758 KiB |
|
After Width: | Height: | Size: 759 KiB |
|
After Width: | Height: | Size: 744 KiB |
|
After Width: | Height: | Size: 745 KiB |
|
After Width: | Height: | Size: 744 KiB |
|
After Width: | Height: | Size: 745 KiB |
|
After Width: | Height: | Size: 993 KiB |
|
After Width: | Height: | Size: 993 KiB |
|
After Width: | Height: | Size: 993 KiB |
|
After Width: | Height: | Size: 995 KiB |
|
After Width: | Height: | Size: 861 KiB |
|
After Width: | Height: | Size: 862 KiB |
|
After Width: | Height: | Size: 861 KiB |
|
After Width: | Height: | Size: 863 KiB |
|
After Width: | Height: | Size: 841 KiB |
|
After Width: | Height: | Size: 841 KiB |
|
After Width: | Height: | Size: 841 KiB |
|
After Width: | Height: | Size: 842 KiB |
|
After Width: | Height: | Size: 831 KiB |
|
After Width: | Height: | Size: 832 KiB |
|
After Width: | Height: | Size: 831 KiB |
|
After Width: | Height: | Size: 832 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 857 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 857 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 857 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 859 KiB |
|
After Width: | Height: | Size: 756 KiB |
|
After Width: | Height: | Size: 756 KiB |
|
After Width: | Height: | Size: 756 KiB |
|
After Width: | Height: | Size: 758 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 737 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 737 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 736 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 739 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 726 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 726 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 726 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 728 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 896 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 896 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 896 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 896 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 767 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 767 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 767 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 768 KiB |
|
Before Width: | Height: | Size: 966 KiB After Width: | Height: | Size: 780 KiB |
|
Before Width: | Height: | Size: 966 KiB After Width: | Height: | Size: 781 KiB |
|
Before Width: | Height: | Size: 966 KiB After Width: | Height: | Size: 780 KiB |
|
Before Width: | Height: | Size: 966 KiB After Width: | Height: | Size: 782 KiB |
|
Before Width: | Height: | Size: 966 KiB After Width: | Height: | Size: 752 KiB |
|
Before Width: | Height: | Size: 966 KiB After Width: | Height: | Size: 752 KiB |
|
Before Width: | Height: | Size: 966 KiB After Width: | Height: | Size: 752 KiB |
|
Before Width: | Height: | Size: 966 KiB After Width: | Height: | Size: 753 KiB |