This commit is contained in:
Michal Vrba
2026-09-21 18:24:23 +02:00
parent ce2b8bb91c
commit d1ce9fb0b8
43 changed files with 2908 additions and 447 deletions
+132 -41
View File
@@ -1,61 +1,61 @@
@inject IGameDataLoader DataLoader
@inject IBattleEngine BattleEngine @inject IBattleEngine BattleEngine
@inject ILocalStorageService Storage
@inject NavigationManager Nav
<div class="battle-arena-wrapper" tabindex="0" @ref="arenaElement" @onkeydown="HandleKeyDown" autofocus> <div class="battle-arena-wrapper" tabindex="0" @onkeydown="HandleKeyDown" @ref="arenaElement">
<!-- Top Header / Bar --> <!-- Top HUD / Level Info -->
<div class="battle-header"> <div class="battle-header">
<button class="btn-back" @onclick="GoBackToMap">⬅ Mapa</button> <button class="btn-back" @onclick="GoBackToMap">⬅ Mapa</button>
<div class="level-info"> <div class="level-info">
<span class="world-title">@CurrentLevel.Name</span> <span class="world-title">@CurrentLevel.Name</span>
@if (CurrentLevel.IsBoss) @if (CurrentLevel.IsBoss)
{ {
<span class="boss-badge">👑 BOSS</span> <span class="boss-badge">👑 BOSS SÚBOJ</span>
} }
</div> </div>
<div class="combo-counter @(State.ComboStreak > 1 ? "active-streak" : "")"> <div class="combo-counter @(State.ComboStreak > 1 ? "active-streak" : "")">
🔥 Kombo: <strong>@State.ComboStreak</strong> 🔥 Séria: <strong>@State.ComboStreak</strong>
</div> </div>
</div> </div>
<!-- Feedback / Toast Banner --> <!-- Dynamic Feedback Banner -->
@if (!string.IsNullOrEmpty(State.LastFeedbackMessage)) @if (!string.IsNullOrEmpty(State.LastFeedbackMessage) && !State.IsBattleOver)
{ {
<div class="feedback-banner @GetFeedbackClass()"> <div class="feedback-banner @GetFeedbackClass() anim-fade-in">
@State.LastFeedbackMessage @State.LastFeedbackMessage
</div> </div>
} }
<!-- Main Combat Arena --> <!-- Visual Combat Stage with Dynamic World Background -->
<div class="arena-stage" style="background: linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.35)), url('assets/images/backgrounds/world1_meadow.jpg') center/cover no-repeat;"> <div class="arena-stage" style="background: linear-gradient(rgba(0,0,0,0.35), rgba(0,0,0,0.45)), url('@BackgroundImage') center/cover no-repeat;">
<!-- Player Side --> <!-- Hero Side -->
<div class="combatant hero-side @GetPlayerAnimationClass()">
<div class="combatant player-side @GetPlayerAnimationClass()">
<div class="combatant-info"> <div class="combatant-info">
<span class="combatant-name">@Hero.Name</span> <span class="combatant-name">@Hero.Name</span>
<div class="hp-hearts"> <div class="hp-hearts">
@for (int i = 1; i <= State.PlayerMaxHp; i++) @for (int i = 0; i < State.PlayerMaxHp; i++)
{ {
<span class="heart @(i <= State.PlayerCurrentHp ? "full" : "empty")"> if (i < State.PlayerCurrentHp)
@(i <= State.PlayerCurrentHp ? "❤️" : "🤍") {
</span> <span class="heart full">❤️</span>
}
else
{
<span class="heart empty">🖤</span>
}
} }
</div> </div>
</div> </div>
<!-- Player Visual (4 Phases & Combat Poses) --> <!-- Hero Visual (4 Phases & Combat Poses) -->
<div class="character-sprite-box"> <div class="character-sprite-box">
<div class="sprite-phase-indicator">Fáza @State.PlayerHpPhase / 4</div>
<img src="@GetCharacterImage(Hero.AssetFolder, State.PlayerHpPhase, State.PlayerPose)" <img src="@GetCharacterImage(Hero.AssetFolder, State.PlayerHpPhase, State.PlayerPose)"
alt="@Hero.Name" alt="@Hero.Name"
class="character-image" class="character-image hero-img" />
onerror="this.src='data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'180\' height=\'240\'><rect width=\'100%\' height=\'100%\' fill=\'%234a90e2\'/><text x=\'50%\' y=\'50%\' fill=\'white\' font-size=\'20\' text-anchor=\'middle\'>🧙‍♂️ @Hero.Name (Fáza @State.PlayerHpPhase)</text></svg>'"/>
</div> </div>
</div> </div>
<!-- VS Divider / Dynamic Timer --> <!-- Center Round Timer Orb -->
<div class="arena-center"> <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.ToString("F1")s</div>
<div class="timer-label">@(State.IsDefensePhase ? "OBRANA!" : "ÚTOK")</div> <div class="timer-label">@(State.IsDefensePhase ? "OBRANA!" : "ÚTOK")</div>
@@ -77,8 +77,7 @@
<div class="sprite-phase-indicator">Fáza @State.EnemyHpPhase / 4</div> <div class="sprite-phase-indicator">Fáza @State.EnemyHpPhase / 4</div>
<img src="@GetCharacterImage(State.Enemy.AssetFolder, State.EnemyHpPhase, State.EnemyPose)" <img src="@GetCharacterImage(State.Enemy.AssetFolder, State.EnemyHpPhase, State.EnemyPose)"
alt="@State.Enemy.Name" alt="@State.Enemy.Name"
class="character-image enemy-img" class="character-image enemy-img" />
onerror="this.src='data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'180\' height=\'240\'><rect width=\'100%\' height=\'100%\' fill=\'%23e74c3c\'/><text x=\'50%\' y=\'50%\' fill=\'white\' font-size=\'20\' text-anchor=\'middle\'>👾 @State.Enemy.Name (Fáza @State.EnemyHpPhase)</text></svg>'"/>
</div> </div>
</div> </div>
</div> </div>
@@ -105,39 +104,116 @@
} }
</div> </div>
} }
else </div>
<!-- FULLSCREEN VICTORY / DEFEAT CELEBRATION OVERLAY -->
@if (State.IsBattleOver)
{ {
<!-- Victory / Defeat Modal --> <div class="battle-end-overlay anim-fade-in">
<div class="battle-end-card @(State.IsVictory ? "victory-card" : "defeat-card")"> <div class="celebration-modal @(State.IsVictory ? "victory-theme" : "defeat-theme") anim-victory-modal-pop">
<h2>@(State.IsVictory ? "🏆 VÍŤAZSTVO!" : "💀 PORÁŽKA!")</h2>
<p>@State.LastFeedbackMessage</p>
<div class="end-actions">
@if (State.IsVictory) @if (State.IsVictory)
{ {
<button class="action-btn next-level-btn" @onclick="OnVictoryContinue">Pokračovať v ceste ➡️</button> <div class="celebration-particles">
<span class="particle star-p1">✨</span>
<span class="particle star-p2">⭐</span>
<span class="particle star-p3">🌟</span>
<span class="particle star-p4">✨</span>
</div>
<div class="celebration-header">
<span class="celebration-crown">👑</span>
<h1 class="celebration-title">LEVEL PREKONANÝ!</h1>
<p class="celebration-subtitle">Úspešne si vyriešil všetky úlohy a zvíťazil!</p>
</div>
<div class="celebration-body">
<div class="celebration-hero-stage">
<img src="@GetCharacterImage(Hero.AssetFolder, 1, CombatPose.Attack)"
alt="@Hero.Name"
class="celebration-hero-img anim-hero-victory" />
<span class="victory-hero-name">@Hero.Name</span>
</div>
<div class="celebration-rewards-card">
<div class="reward-row">
<span class="reward-icon">⭐</span>
<div class="reward-info">
<strong>@(CurrentLevel.IsBoss ? "+3 HVIEZDY" : "+1 HVIEZDA")</strong>
<span>Odmena za víťazstvo</span>
</div>
</div>
<div class="reward-row">
<span class="reward-icon">💰</span>
<div class="reward-info">
<strong>@(CurrentLevel.IsBoss ? "+50 ZLATA" : "+20 ZLATA")</strong>
<span>Kráľovská odmena</span>
</div>
</div>
</div>
</div>
<div class="celebration-actions">
@if (NextLevel != null)
{
<button class="btn-grand-celebration btn-next-quest" @onclick="HandleNextLevelClick">
🚀 POSTÚPIŤ DO LEVELU: @NextLevel.Name ➜
</button>
}
<div class="secondary-actions-row">
<button class="btn-secondary-celebration" @onclick="OnVictoryContinue">
🗺️ Mapa sveta
</button>
<button class="btn-secondary-celebration" @onclick="RetryBattle">
🔁 Hrať znova
</button>
</div>
</div>
} }
else else
{ {
<button class="action-btn retry-btn" @onclick="RetryBattle">Skúsiť znova 🔄</button> <div class="defeat-header">
<span class="defeat-icon">💀</span>
<h1 class="defeat-title">NEVZDÁVAJ SA!</h1>
<p class="defeat-subtitle">@State.LastFeedbackMessage</p>
</div>
<div class="defeat-body">
<p class="defeat-encouragement">
„Každý udatný počtár sa učí na vlastných chybách. Skús to znova a ukáž svoju matematickú silu!“
</p>
</div>
<div class="celebration-actions">
<button class="btn-grand-celebration btn-retry-quest" @onclick="RetryBattle">
🔄 SKÚSIŤ ZNOVA ⚔️
</button>
<button class="btn-secondary-celebration" @onclick="GoBackToMap">
🗺️ Návrat na mapu
</button>
</div>
} }
</div> </div>
</div> </div>
} }
</div> </div>
</div>
@code { @code {
[Parameter] public Hero Hero { get; set; } = new(); [Parameter] public Hero Hero { get; set; } = new();
[Parameter] public Level CurrentLevel { get; set; } = new(); [Parameter] public Level CurrentLevel { get; set; } = new();
[Parameter] public int PlayerGrade { get; set; } = 1;
[Parameter] public string BackgroundImage { get; set; } = "assets/images/backgrounds/world1_meadow.jpg";
[Parameter] public GameConfig Config { get; set; } = new(); [Parameter] public GameConfig Config { get; set; } = new();
[Parameter] public EventCallback OnReturnToMap { get; set; } [Parameter] public EventCallback OnReturnToMap { get; set; }
[Parameter] public EventCallback<Level> OnLevelCompleted { get; set; } [Parameter] public EventCallback<Level> OnLevelCompleted { get; set; }
[Parameter] public EventCallback<Level> OnNextLevelRequested { get; set; }
private BattleState State => BattleEngine.State; private BattleState State => BattleEngine.State;
private string CurrentInput { get; set; } = string.Empty; private string CurrentInput { get; set; } = string.Empty;
private System.Threading.Timer? _timer; private System.Threading.Timer? _timer;
private double RemainingTimeSeconds { get; set; } = 6.0; private double RemainingTimeSeconds { get; set; } = 6.0;
private Level? NextLevel => DataLoader.GetNextLevel(PlayerGrade, CurrentLevel.Id);
protected override void OnInitialized() protected override void OnInitialized()
{ {
StartBattle(); StartBattle();
@@ -229,6 +305,18 @@
await OnLevelCompleted.InvokeAsync(CurrentLevel); await OnLevelCompleted.InvokeAsync(CurrentLevel);
} }
private async Task HandleNextLevelClick()
{
if (NextLevel != null)
{
await OnNextLevelRequested.InvokeAsync(NextLevel);
}
else
{
await OnVictoryContinue();
}
}
private async Task GoBackToMap() private async Task GoBackToMap()
{ {
_timer?.Dispose(); _timer?.Dispose();
@@ -297,23 +385,28 @@
if (e.Key == "Enter") if (e.Key == "Enter")
{ {
if (State.IsVictory) if (State.IsVictory)
await OnVictoryContinue(); {
if (NextLevel != null)
await HandleNextLevelClick();
else else
await OnVictoryContinue();
}
else
{
await RetryBattle(); await RetryBattle();
} }
}
return; return;
} }
if (State.CurrentProblem.InputType == "MultipleChoice" || State.IsDefensePhase) if (State.CurrentProblem.InputType == "MultipleChoice" || State.IsDefensePhase)
{ {
// Handle number keys 1, 2, 3 to pick choices
if (int.TryParse(e.Key, out int choiceIndex) && choiceIndex >= 1 && choiceIndex <= State.CurrentProblem.Choices.Count) if (int.TryParse(e.Key, out int choiceIndex) && choiceIndex >= 1 && choiceIndex <= State.CurrentProblem.Choices.Count)
{ {
await HandleChoiceSelected(State.CurrentProblem.Choices[choiceIndex - 1]); await HandleChoiceSelected(State.CurrentProblem.Choices[choiceIndex - 1]);
return; return;
} }
// Handle A/N shortcuts for ÁNO / NIE
if (e.Key.Equals("a", StringComparison.OrdinalIgnoreCase) || e.Key.Equals("y", StringComparison.OrdinalIgnoreCase)) 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)); var match = State.CurrentProblem.Choices.FirstOrDefault(c => c.StartsWith("Á", StringComparison.OrdinalIgnoreCase) || c.StartsWith("A", StringComparison.OrdinalIgnoreCase));
@@ -329,7 +422,6 @@
} }
else else
{ {
// Direct numeric input (0-9, Backspace, Enter)
if (e.Key.Length == 1 && char.IsDigit(e.Key[0])) if (e.Key.Length == 1 && char.IsDigit(e.Key[0]))
{ {
if (CurrentInput.Length < 6) if (CurrentInput.Length < 6)
@@ -356,4 +448,3 @@
} }
} }
} }
@@ -1,106 +1,379 @@
@inject IGameDataLoader DataLoader @inject IGameDataLoader DataLoader
@inject ILocalStorageService Storage @inject ILocalStorageService Storage
<div class="character-select-screen"> <div class="wizard-screen">
<div class="parchment-container select-card-compact"> @switch (CurrentStep)
<div class="select-header"> {
<h1 class="game-main-title">🏰 ČÍSELNÁ VÝPRAVA</h1> case WizardStep.SelectGrade:
<p class="subtitle">Vytvor si svojho hrdinu a vyber ročník</p> {
<div class="parchment-container wizard-container">
<div class="wizard-top-bar">
<button class="btn-wizard-back" @onclick="HandleBackToMenu">
⬅ Späť do menu
</button>
<div class="wizard-stepper">
<span class="step-badge active">1. Ročník 🎒</span>
<span class="step-divider">➔</span>
<span class="step-badge">2. Hrdina 🛡️</span>
<span class="step-divider">➔</span>
<span class="step-badge">3. Meno 👑</span>
</div>
</div> </div>
<div class="select-content-grid"> <div class="wizard-header">
<!-- LEFT: ROČNÍK & MENO --> <h1 class="wizard-title">🎒 VYBER SI ROČNÍK</h1>
<div class="select-col-left"> <p class="wizard-subtitle">Zvoľ si úroveň matematických výziev pre svoje dobrodružstvo</p>
<div class="selection-section"> </div>
<h2 class="step-title">1. Tvoj ročník</h2>
<div class="grade-toggle-group"> <div class="grades-grid">
<button class="grade-btn @(SelectedGrade == 1 ? "active" : "")" @onclick="() => SetGrade(1)"> <!-- GRADE 1 -->
<span class="grade-badge">🎒 1. ROČNÍK</span> <div class="grade-card grade-1-card @(SelectedGrade == 1 ? "selected" : "")" @onclick="() => SelectGradeAndProceed(1)">
<span class="grade-desc">Do 10 / 20</span> <div class="grade-banner-stage" style="background: url('assets/images/backgrounds/world1_meadow.jpg') center/cover no-repeat;">
</button> <div class="grade-banner-overlay">
<button class="grade-btn @(SelectedGrade == 2 ? "active" : "")" @onclick="() => SetGrade(2)"> <span class="grade-card-icon">⭐</span>
<span class="grade-badge">🎒 2. ROČNÍK</span> <div class="grade-card-badge grade-1-badge">1. ROČNÍK</div>
<span class="grade-desc">Do 20 / 100</span> </div>
</button> </div>
<button class="grade-btn @(SelectedGrade == 3 ? "active" : "")" @onclick="() => SetGrade(3)"> <div class="grade-card-content">
<span class="grade-badge">🎒 3. ROČNÍK</span> <h3 class="grade-card-title">Prváčik</h3>
<span class="grade-desc">Násobilka & delenie</span> <p class="grade-card-desc">Základné sčítanie a odčítanie v rozprávkovej lúke.</p>
<ul class="grade-topics-list">
<li>➕ Sčítanie a odčítanie do 10 a 20</li>
<li>⚖️ Porovnávanie čísel (&lt;, &gt;, =)</li>
<li>🔟 Počítanie po desiatkach do 100</li>
</ul>
<button class="btn-grade-select btn-grade-1" @onclick:stopPropagation="true" @onclick="() => SelectGradeAndProceed(1)">
Zvoliť 1. ročník ➜
</button> </button>
</div> </div>
</div> </div>
<div class="selection-section"> <!-- GRADE 2 -->
<h2 class="step-title">3. Meno hrdinu</h2> <div class="grade-card grade-2-card @(SelectedGrade == 2 ? "selected" : "")" @onclick="() => SelectGradeAndProceed(2)">
<div class="name-field-row"> <div class="grade-banner-stage" style="background: url('assets/images/backgrounds/world2_forest.jpg') center/cover no-repeat;">
<input type="text" class="hero-name-input" @bind="HeroName" placeholder="Tvoje meno..." maxlength="20" /> <div class="grade-banner-overlay">
<button class="btn-start-quest" @onclick="StartQuest"> <span class="grade-card-icon">🌟</span>
Vstúpiť do hry 🚀 <div class="grade-card-badge grade-2-badge">2. ROČNÍK</div>
</div>
</div>
<div class="grade-card-content">
<h3 class="grade-card-title">Druhák</h3>
<p class="grade-card-desc">Počítanie do 100 s prechodom a úvod do násobilky.</p>
<ul class="grade-topics-list">
<li>➕ Sčítanie a odčítanie do 100 s prechodom</li>
<li>⚖️ Porovnávanie hodnôt do 100</li>
<li>✖️ Malá násobilka (2, 3, 5, 10)</li>
</ul>
<button class="btn-grade-select btn-grade-2" @onclick:stopPropagation="true" @onclick="() => SelectGradeAndProceed(2)">
Zvoliť 2. ročník ➜
</button>
</div>
</div>
<!-- GRADE 3 -->
<div class="grade-card grade-3-card @(SelectedGrade == 3 ? "selected" : "")" @onclick="() => SelectGradeAndProceed(3)">
<div class="grade-banner-stage" style="background: url('assets/images/backgrounds/world3_river.jpg') center/cover no-repeat;">
<div class="grade-banner-overlay">
<span class="grade-card-icon">👑</span>
<div class="grade-card-badge grade-3-badge">3. ROČNÍK</div>
</div>
</div>
<div class="grade-card-content">
<h3 class="grade-card-title">Tretiak</h3>
<p class="grade-card-desc">Kompletná násobilka, delenie a počítanie do tisícky!</p>
<ul class="grade-topics-list">
<li>✖️ Celá násobilka a delenie (2 – 10)</li>
<li>🧮 Počítanie do 1000 (stovky a desiatky)</li>
<li>🏆 Finálne bitky o kráľovskú citadelu</li>
</ul>
<button class="btn-grade-select btn-grade-3" @onclick:stopPropagation="true" @onclick="() => SelectGradeAndProceed(3)">
Zvoliť 3. ročník ➜
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div>
break;
}
<!-- RIGHT: VÝBER HRDINU --> case WizardStep.SelectHero:
<div class="select-col-right"> {
<div class="selection-section"> <div class="parchment-container wizard-container">
<h2 class="step-title">2. Vyber si hrdinu</h2> <div class="wizard-top-bar">
<div class="heroes-grid"> <button class="btn-wizard-back" @onclick="() => CurrentStep = WizardStep.SelectGrade">
⬅ Späť na ročník
</button>
<div class="wizard-stepper">
<span class="step-badge completed" @onclick="() => CurrentStep = WizardStep.SelectGrade">✓ @SelectedGrade. Ročník</span>
<span class="step-divider">➔</span>
<span class="step-badge active">2. Hrdina 🛡️</span>
<span class="step-divider">➔</span>
<span class="step-badge">3. Meno 👑</span>
</div>
</div>
<div class="wizard-header">
<h1 class="wizard-title">🛡️ ZVOĽ SI SVOJHO HRDINU</h1>
<p class="wizard-subtitle">Vyber si postavu, s ktorou zachrániš Číselné Kráľovstvo</p>
</div>
<div class="hero-showcase-grid">
@foreach (var hero in DataLoader.Heroes) @foreach (var hero in DataLoader.Heroes)
{ {
<div class="hero-card @(SelectedHeroId == hero.Id ? "selected-card" : "")" var isSelected = SelectedHeroId == hero.Id;
@onclick="() => SelectHero(hero.Id)"> var heroImg = $"{hero.AssetFolder}_idle_p1.png";
<div class="hero-avatar"> var themeClass = hero.Id switch
@(hero.Id == "knight" ? "⚔️" : hero.Id == "mage" ? "🧙‍♂️" : "🏹") {
"knight" => "hero-theme-knight",
"mage" => "hero-theme-mage",
"archer" => "hero-theme-archer",
_ => "hero-theme-knight"
};
var icon = hero.Id switch
{
"knight" => "⚔️",
"mage" => "🔮",
"archer" => "🏹",
_ => "🛡️"
};
<div class="hero-showcase-card @themeClass @(isSelected ? "selected" : "")"
@onclick="() => SelectHeroAndProceed(hero.Id)">
<div class="hero-card-banner">
<span class="hero-crest-icon">@icon</span>
<span class="hero-class-label">@hero.Title</span>
</div> </div>
<h3 class="hero-name">@hero.Name</h3>
<span class="hero-title">@hero.Title</span> <div class="hero-portrait-stage">
<div class="hero-skill"> <img src="@heroImg" alt="@hero.Name" class="hero-portrait-img anim-hero-idle" />
✨ @hero.SpecialAttackName
</div> </div>
<div class="hero-info-box">
<h3 class="hero-title-name">@hero.Name</h3>
<p class="hero-lore-desc">@hero.Description</p>
<div class="hero-skill-pill">
✨ <strong>@hero.SpecialAttackName</strong>
</div>
<div class="hero-stats-row">
<span class="stat-tag">❤️ @hero.BaseHp HP</span>
<span class="stat-tag">⚡ @hero.AttackPower ÚTOK</span>
<span class="stat-tag">🛡️ ŠTÍT</span>
</div>
</div>
<button class="btn-hero-select" @onclick:stopPropagation="true" @onclick="() => SelectHeroAndProceed(hero.Id)">
Zvoliť hrdinu ➜
</button>
</div> </div>
} }
</div> </div>
</div> </div>
</div> break;
</div> }
case WizardStep.NameAndStart:
{
<div class="parchment-container wizard-container wizard-name-container">
<div class="wizard-top-bar">
<button class="btn-wizard-back" @onclick="() => CurrentStep = WizardStep.SelectHero">
⬅ Späť na výber hrdinu
</button>
<div class="wizard-stepper">
<span class="step-badge completed" @onclick="() => CurrentStep = WizardStep.SelectGrade">✓ @SelectedGrade. Ročník</span>
<span class="step-divider">➔</span>
<span class="step-badge completed" @onclick="() => CurrentStep = WizardStep.SelectHero">✓ @CurrentSelectedHero.Name</span>
<span class="step-divider">➔</span>
<span class="step-badge active">3. Meno & Štart 👑</span>
</div> </div>
</div> </div>
<div class="wizard-header">
<h1 class="wizard-title">👑 POJMENUJ SVOJHO HRDINU</h1>
<p class="wizard-subtitle">Posledný krok pred otvorením brány kráľovstva!</p>
</div>
<div class="naming-content-stage">
<!-- LEFT: HERO AVATAR & BADGES -->
<div class="naming-hero-preview">
<div class="hero-stage-pedestal">
<img src="@CurrentSelectedHeroImg" alt="@CurrentSelectedHero.Name" class="naming-hero-img anim-hero-idle" />
</div>
<div class="naming-badges-row">
<span class="grade-pill-badge">🎒 @SelectedGrade. Ročník</span>
<span class="hero-pill-badge">🛡️ @CurrentSelectedHero.Title</span>
</div>
<div class="naming-stat-summary">
<span>❤️ 5 Životov</span>
<span>⚡ 10 Útok</span>
<span>⏱️ &lt;3s Kritický zásah (150%)</span>
</div>
</div>
<!-- RIGHT: NAME INPUT & ACTION BUTTON -->
<div class="naming-form-box">
<div class="form-group">
<label class="form-label" for="heroNameInput">Ako sa volá tvoj hrdina?</label>
<div class="name-input-with-dice">
<input id="heroNameInput"
type="text"
class="hero-name-big-input"
@bind="HeroName"
placeholder="Zadaj meno hrdinu..."
maxlength="20" />
<button type="button"
class="btn-dice-randomizer @(IsRollingDice ? "rolling" : "")"
@onclick="RollRandomName"
title="Vyžrebovať náhodné meno!">
<span class="dice-icon">🎲</span>
<span class="dice-label">Náhodné meno</span>
</button>
</div>
</div>
<div class="quick-names-section">
<span class="quick-names-title">Alebo si vyber obľúbené meno:</span>
<div class="quick-names-grid">
@foreach (var suggestion in QuickNameSuggestions)
{
<button type="button"
class="name-chip-btn @(HeroName == suggestion ? "active-chip" : "")"
@onclick="() => SetHeroName(suggestion)">
@suggestion
</button>
}
</div>
</div>
<div class="naming-action-footer">
<button class="btn-grand-start" @onclick="StartQuest">
⚔️ VSTÚPIŤ DO HRY A ZAČAŤ VÝPRAVU 🚀
</button>
<p class="autosave-notice">💾 Tvoj postup a hviezdičky sa automaticky ukladajú na tomto zariadení.</p>
</div>
</div>
</div>
</div>
break;
}
}
</div>
@code { @code {
[Parameter] public EventCallback<PlayerProfile> OnProfileReady { get; set; } [Parameter] public EventCallback<PlayerProfile> OnProfileReady { get; set; }
[Parameter] public EventCallback OnCancel { get; set; }
private enum WizardStep
{
SelectGrade,
SelectHero,
NameAndStart
}
private WizardStep CurrentStep { get; set; } = WizardStep.SelectGrade;
private int SelectedGrade { get; set; } = 2; private int SelectedGrade { get; set; } = 2;
private string SelectedHeroId { get; set; } = "knight"; private string SelectedHeroId { get; set; } = "knight";
private string HeroName { get; set; } = "Mladý Hrdina"; private string HeroName { get; set; } = "Udatný Rytier";
private bool IsRollingDice { get; set; } = false;
private Hero CurrentSelectedHero => DataLoader.GetHero(SelectedHeroId) ?? DataLoader.Heroes.FirstOrDefault() ?? new Hero();
private string CurrentSelectedHeroImg => $"{CurrentSelectedHero.AssetFolder}_idle_p1.png";
private readonly Random _random = new();
private readonly List<string> QuickNameSuggestions = new()
{
"Arthur", "Eliška", "Maxim", "Sofia", "Oliver",
"Viktória", "Lucas", "Nela", "Jakub", "Mia"
};
private readonly List<string> AllHeroNamesPool = new()
{
// Chlapčenské mená
"Arthur", "Maxim", "Oliver", "Lucas", "Leo", "Alex", "Matias", "Michal",
"Adam", "Jakub", "Filip", "Damián", "Teodor", "Bruno", "Hugo", "Šimon",
"Tobiáš", "Viktor", "Benjamín", "Richard", "Tomáš", "Patrik", "Matej",
// Dievčenské mená
"Eliška", "Sofia", "Viktória", "Nela", "Mia", "Nina", "Ema", "Tamara",
"Laura", "Dorotka", "Linda", "Sára", "Stella", "Lea", "Hana", "Kristína",
"Amélia", "Zoja", "Diana", "Barborka", "Zuzka", "Veronika", "Natália",
// Hrdinské rozprávkové tituly
"Udatný Arthur", "Bystrá Sofia", "Rýchly Alex", "Odvážna Eliška", "Múdry Oliver",
"Čarovná Mia", "Hrdina Maxim", "Jasná Stella", "Šampión Leo", "Zlatá Viktória",
"Rytier Pravítko", "Mág Číselník", "Lučostrelec Stovka", "Kráľovský Strážca"
};
protected override void OnInitialized() protected override void OnInitialized()
{ {
if (DataLoader.Heroes.Any()) if (DataLoader.Heroes.Any())
{ {
SelectedHeroId = DataLoader.Heroes.First().Id; SelectedHeroId = DataLoader.Heroes.First().Id;
UpdateDefaultName();
} }
} }
private void SetGrade(int grade) private void SelectGradeAndProceed(int grade)
{ {
SelectedGrade = grade; SelectedGrade = grade;
CurrentStep = WizardStep.SelectHero;
} }
private void SelectHero(string heroId) private void SelectHeroAndProceed(string heroId)
{ {
SelectedHeroId = heroId; SelectedHeroId = heroId;
UpdateDefaultName();
CurrentStep = WizardStep.NameAndStart;
}
private void UpdateDefaultName()
{
HeroName = SelectedHeroId switch
{
"knight" => "Udatný Rytier",
"mage" => "Mág Číselník",
"archer" => "Bystrý Lučostrelec",
_ => "Mladý Hrdina"
};
}
private async Task RollRandomName()
{
IsRollingDice = true;
StateHasChanged();
var available = AllHeroNamesPool.Where(n => n != HeroName).ToList();
if (!available.Any()) available = AllHeroNamesPool;
HeroName = available[_random.Next(available.Count)];
await Task.Delay(300);
IsRollingDice = false;
StateHasChanged();
}
private void SetHeroName(string name)
{
HeroName = name;
}
private async Task HandleBackToMenu()
{
await OnCancel.InvokeAsync();
} }
private async Task StartQuest() private async Task StartQuest()
{ {
var finalName = string.IsNullOrWhiteSpace(HeroName) ? "Mladý Hrdina" : HeroName.Trim();
var profile = new PlayerProfile var profile = new PlayerProfile
{ {
Name = string.IsNullOrWhiteSpace(HeroName) ? "Mladý Hrdina" : HeroName, Name = finalName,
SelectedGrade = SelectedGrade, SelectedGrade = SelectedGrade,
SelectedHeroId = SelectedHeroId, SelectedHeroId = SelectedHeroId,
CurrentWorldId = 1, CurrentWorldId = 1,
UnlockedLevels = new HashSet<string> { $"{SelectedGrade}-1-1" } UnlockedLevels = new HashSet<string> { $"{SelectedGrade}-1-1" },
CompletedLevels = new HashSet<string>()
}; };
await Storage.SaveProfileAsync(profile); await Storage.SaveProfileAsync(profile);
+229 -38
View File
@@ -1,63 +1,182 @@
@inject IGameDataLoader DataLoader @inject IGameDataLoader DataLoader
<div class="world-map-screen"> <div class="world-map-screen">
<!-- Top Player Status Bar --> <!-- TOP STATUS BAR -->
<div class="map-top-bar"> <div class="map-top-bar">
<div class="player-pill"> <div class="player-pill">
<span class="avatar-icon">@(Profile.SelectedHeroId == "knight" ? "⚔️" : Profile.SelectedHeroId == "mage" ? "🧙‍♂️" : "🏹")</span> <img src="@HeroAvatarUrl" alt="Hrdina" class="mini-hero-avatar" />
<div class="player-info-text">
<span class="player-name">@Profile.Name</span> <span class="player-name">@Profile.Name</span>
<span class="grade-tag">@(Profile.SelectedGrade). Ročník</span> <span class="grade-tag">🎒 @(Profile.SelectedGrade). Ročník</span>
</div> </div>
<div class="stats-pill">
<span>⭐ Hviezdy: <strong>@Profile.TotalStars</strong></span>
</div>
<button class="btn-change-hero" @onclick="OnChangeHero">Zmeniť hrdinu 🔄</button>
</div> </div>
<!-- Worlds Container --> <div class="stats-pill-group">
<div class="worlds-carousel"> <div class="stat-badge stars-badge">
@foreach (var world in Worlds) <span>⭐ <strong>@Profile.TotalStars</strong> Hviezd</span>
{ </div>
<div class="world-card"> <div class="stat-badge gold-badge">
<div class="world-header"> <span>💰 <strong>@Profile.Gold</strong> Zlata</span>
<h2>@world.Name</h2> </div>
<p>@world.Description</p>
</div> </div>
<div class="levels-path">
@foreach (var level in world.Levels)
{
bool isUnlocked = Profile.UnlockedLevels.Contains(level.Id);
bool isCompleted = Profile.CompletedLevels.Contains(level.Id);
<button class="level-node @(level.IsBoss ? "boss-node" : "") @(isUnlocked ? "unlocked" : "locked") @(isCompleted ? "completed" : "")" <div class="map-top-actions">
disabled="@(!isUnlocked)" <button class="btn-top-action" @onclick="OnChangeHero" title="Zmeniť ročník alebo hrdinu">
@onclick="() => SelectLevel(level)"> 🎒 Zmeniť hrdinu
<div class="node-icon"> </button>
@if (isCompleted) </div>
</div>
<!-- MAIN WORLD MAP CONTENT AREA -->
<div class="world-map-content">
<!-- WORLD SELECTION TABS -->
<div class="world-tabs-nav">
@for (int i = 0; i < Worlds.Count; i++)
{ {
<span>⭐</span> var w = Worlds[i];
} var worldIndex = i + 1;
else if (level.IsBoss) var isSelected = SelectedWorldIndex == worldIndex;
var isWorldUnlocked = w.Levels.Any(l => Profile.UnlockedLevels.Contains(l.Id));
var completedInWorld = w.Levels.Count(l => Profile.CompletedLevels.Contains(l.Id));
var totalInWorld = w.Levels.Count;
var isWorldCompleted = totalInWorld > 0 && completedInWorld == totalInWorld;
<button class="world-tab-btn @(isSelected ? "active" : "") @(isWorldUnlocked ? "unlocked" : "locked") @(isWorldCompleted ? "world-completed" : "")"
disabled="@(!isWorldUnlocked)"
@onclick="() => SelectWorld(worldIndex)">
<span class="world-tab-icon">
@if (isWorldCompleted)
{ {
<span>👑</span> <span>🌟</span>
} }
else if (isUnlocked) else if (!isWorldUnlocked)
{
<span>⚔️</span>
}
else
{ {
<span>🔒</span> <span>🔒</span>
} }
</div> else
<span class="level-title">@level.Name</span>
@if (level.IsBoss)
{ {
<span class="boss-tag">BOSS</span> <span>@(worldIndex == 1 ? "🌸" : worldIndex == 2 ? "🌲" : worldIndex == 3 ? "🌊" : worldIndex == 4 ? "⛰️" : "🏰")</span>
} }
</span>
<span class="world-tab-label">Svet @worldIndex</span>
<span class="world-tab-progress">@completedInWorld/@totalInWorld</span>
</button> </button>
} }
</div> </div>
<!-- CURRENT SELECTED WORLD SHOWCASE -->
@if (ActiveWorld != null)
{
var completedCount = ActiveWorld.Levels.Count(l => Profile.CompletedLevels.Contains(l.Id));
var totalCount = ActiveWorld.Levels.Count;
var progressPercent = totalCount > 0 ? (int)((double)completedCount / totalCount * 100) : 0;
<div class="active-world-card parchment-container">
<!-- WORLD BANNER HEADER -->
<div class="world-header-row">
<div class="world-title-col">
<h2 class="active-world-title">@ActiveWorld.Name</h2>
<p class="active-world-desc">@ActiveWorld.Description</p>
</div>
<div class="world-progress-col">
<div class="progress-label-row">
<span>Postup vo svete:</span>
<strong>@completedCount z @totalCount splnených</strong>
</div>
<div class="world-progress-bar">
<div class="world-progress-fill" style="width: @(progressPercent)%"></div>
</div>
</div>
</div>
<!-- LEVELS RICH CARDS GRID -->
<div class="levels-rich-grid">
@foreach (var level in ActiveWorld.Levels)
{
var isUnlocked = Profile.UnlockedLevels.Contains(level.Id);
var isCompleted = Profile.CompletedLevels.Contains(level.Id);
var enemyImg = $"{level.Enemy.AssetFolder}_idle_p1.png";
var mathDesc = GetMathDescription(level.MathConfig);
<div class="level-rich-card @(level.IsBoss ? "boss-card" : "") @(isCompleted ? "status-completed" : isUnlocked ? "status-current" : "status-locked")">
<!-- CARD STATUS BADGE -->
<div class="card-status-banner">
@if (isCompleted)
{
<span class="badge-completed">🏆 PREKONANÝ</span>
<span class="stars-awarded">@(level.IsBoss ? "⭐⭐⭐" : "⭐")</span>
}
else if (level.IsBoss)
{
<span class="badge-boss">👑 VEĽKÝ BOSS</span>
}
else if (isUnlocked)
{
<span class="badge-active">⚔️ NOVÁ VÝZVA</span>
}
else
{
<span class="badge-locked">🔒 ZAMKNUTÉ</span>
}
</div>
<!-- ENEMY AVATAR STAGE -->
<div class="level-enemy-stage">
<div class="enemy-avatar-wrapper @(isCompleted ? "defeated-glow" : isUnlocked ? "active-glow" : "locked-silhouette")">
<img src="@enemyImg" alt="@level.Enemy.Name" class="level-enemy-img @(isUnlocked && !isCompleted ? "anim-hero-idle" : "")" />
@if (!isUnlocked)
{
<div class="lock-overlay">🔒</div>
}
@if (isCompleted)
{
<div class="check-overlay">✓</div>
}
</div>
</div>
<!-- LEVEL DETAILS -->
<div class="level-info-body">
<div class="level-number-tag">Level @level.LevelNumber</div>
<h3 class="level-name-title">@level.Name</h3>
<div class="level-enemy-name">Protivník: <strong>@level.Enemy.Name</strong></div>
<div class="level-math-pill">
@mathDesc
</div>
<div class="level-stats-row">
<span class="stat-bubble">❤️ @level.Enemy.MaxHp HP</span>
<span class="stat-bubble">🎯 @level.RequiredCorrectAnswers správne</span>
</div>
</div>
<!-- ACTION BUTTON -->
<div class="level-card-footer">
@if (isCompleted)
{
<button class="btn-level-action btn-replay" @onclick="() => SelectLevel(level)">
🔁 Hrať znova (Tréning)
</button>
}
else if (isUnlocked)
{
<button class="btn-level-action btn-battle @(level.IsBoss ? "btn-boss-battle" : "")" @onclick="() => SelectLevel(level)">
⚔️ VYZVAŤ NA SÚBOJ! ➜
</button>
}
else
{
<button class="btn-level-action btn-locked" disabled="true">
🔒 Splň predchádzajúci level
</button>
}
</div>
</div>
}
</div>
</div> </div>
} }
</div> </div>
@@ -69,9 +188,81 @@
[Parameter] public EventCallback OnChangeHero { get; set; } [Parameter] public EventCallback OnChangeHero { get; set; }
private List<World> Worlds => DataLoader.GetWorldsForGrade(Profile.SelectedGrade); private List<World> Worlds => DataLoader.GetWorldsForGrade(Profile.SelectedGrade);
private int SelectedWorldIndex { get; set; } = 1;
private World? ActiveWorld => Worlds.FirstOrDefault(w => w.Id == SelectedWorldIndex)
?? Worlds.FirstOrDefault();
private string HeroAvatarUrl
{
get
{
var hero = DataLoader.GetHero(Profile.SelectedHeroId) ?? DataLoader.Heroes.FirstOrDefault();
return hero != null ? $"{hero.AssetFolder}_idle_p1.png" : "assets/images/heroes/knight_idle_p1.png";
}
}
protected override void OnInitialized()
{
// Automatically select the world that has the highest unlocked level
if (Worlds.Any())
{
var highestWorldWithUnlocked = Worlds
.Where(w => w.Levels.Any(l => Profile.UnlockedLevels.Contains(l.Id)))
.OrderByDescending(w => w.Id)
.FirstOrDefault();
if (highestWorldWithUnlocked != null)
{
SelectedWorldIndex = highestWorldWithUnlocked.Id;
}
else
{
SelectedWorldIndex = Worlds.First().Id;
}
}
}
private void SelectWorld(int worldIndex)
{
SelectedWorldIndex = worldIndex;
}
private async Task SelectLevel(Level level) private async Task SelectLevel(Level level)
{ {
await OnLevelSelected.InvokeAsync(level); await OnLevelSelected.InvokeAsync(level);
} }
private string GetMathDescription(MathConfig config)
{
if (config.Operations.Contains("<") || config.Operations.Contains(">") || config.Operations.Contains("="))
{
return "⚖️ Porovnávanie čísel (< > =)";
}
if (config.Operations.Contains("*") && config.Operations.Contains("/"))
{
return "✖️ ➗ Násobilka a delenie";
}
if (config.Operations.Contains("*"))
{
return "✖️ Malá násobilka";
}
if (config.Operations.Contains("/"))
{
return "➗ Delenie čísel";
}
if (config.Operations.Contains("+") && config.Operations.Contains("-"))
{
return $"➕ ➖ Sčítanie a odčítanie do {config.MaxSum}";
}
if (config.Operations.Contains("+"))
{
return $"➕ Sčítanie do {config.MaxSum}";
}
if (config.Operations.Contains("-"))
{
return $"➖ Odčítanie do {config.MaxA}";
}
return "➕ Matematická výzva";
}
} }
+64 -8
View File
@@ -23,7 +23,8 @@
} }
else if (CurrentScreen == ScreenType.CharacterSelect) else if (CurrentScreen == ScreenType.CharacterSelect)
{ {
<CharacterSelect OnProfileReady="HandleProfileReady" /> <CharacterSelect OnProfileReady="HandleProfileReady"
OnCancel="() => CurrentScreen = ScreenType.MainMenu" />
} }
else if (CurrentScreen == ScreenType.WorldMap) else if (CurrentScreen == ScreenType.WorldMap)
{ {
@@ -34,11 +35,42 @@
else if (CurrentScreen == ScreenType.Battle && SelectedLevel != null) else if (CurrentScreen == ScreenType.Battle && SelectedLevel != null)
{ {
var hero = DataLoader.GetHero(Profile.SelectedHeroId) ?? DataLoader.Heroes.First(); var hero = DataLoader.GetHero(Profile.SelectedHeroId) ?? DataLoader.Heroes.First();
var worlds = DataLoader.GetWorldsForGrade(Profile.SelectedGrade);
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 Hero="hero"
CurrentLevel="SelectedLevel" CurrentLevel="SelectedLevel"
PlayerGrade="Profile.SelectedGrade"
BackgroundImage="@bg"
Config="DataLoader.Config" Config="DataLoader.Config"
OnReturnToMap="() => CurrentScreen = ScreenType.WorldMap" OnReturnToMap="() => CurrentScreen = ScreenType.WorldMap"
OnLevelCompleted="HandleLevelCompleted" /> OnLevelCompleted="HandleLevelCompleted"
OnNextLevelRequested="HandleNextLevelRequested" />
}
<!-- FAIRYTALE LEVEL TRANSITION SCREEN -->
@if (IsTransitioning)
{
var hero = DataLoader.GetHero(Profile.SelectedHeroId) ?? DataLoader.Heroes.FirstOrDefault();
var heroAvatar = hero != null ? $"{hero.AssetFolder}_idle_p1.png" : "assets/images/heroes/knight_idle_p1.png";
<div class="transition-screen-overlay anim-fade-in">
<div class="transition-parchment-box anim-victory-modal-pop">
<div class="transition-hero-marching">
<img src="@heroAvatar"
class="marching-hero-img anim-hero-march"
alt="Hrdina na ceste" />
</div>
<h2 class="transition-title">🗺️ Vstupuješ do novej oblasti...</h2>
<p class="transition-target">📍 @TransitionTargetText</p>
<div class="transition-sparkles">
<span class="sparkle">✨</span>
<span class="sparkle">⭐</span>
<span class="sparkle">✨</span>
</div>
</div>
</div>
} }
</div> </div>
@@ -57,6 +89,9 @@
private PlayerProfile Profile { get; set; } = new(); private PlayerProfile Profile { get; set; } = new();
private Level? SelectedLevel { get; set; } private Level? SelectedLevel { get; set; }
private bool IsTransitioning { get; set; } = false;
private string TransitionTargetText { get; set; } = string.Empty;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
@@ -85,19 +120,44 @@
await Storage.SaveProfileAsync(Profile); await Storage.SaveProfileAsync(Profile);
} }
private void HandleLevelSelected(Level level) private async Task HandleLevelSelected(Level level)
{ {
TransitionTargetText = $"{level.Name} (Protivník: {level.Enemy.Name})";
IsTransitioning = true;
StateHasChanged();
await Task.Delay(850);
SelectedLevel = level; SelectedLevel = level;
CurrentScreen = ScreenType.Battle; CurrentScreen = ScreenType.Battle;
IsTransitioning = false;
StateHasChanged();
} }
private async Task HandleLevelCompleted(Level completedLevel) private async Task HandleLevelCompleted(Level completedLevel)
{
UnlockNextLevel(completedLevel);
await Storage.SaveProfileAsync(Profile);
CurrentScreen = ScreenType.WorldMap;
}
private async Task HandleNextLevelRequested(Level nextLevel)
{
if (SelectedLevel != null)
{
UnlockNextLevel(SelectedLevel);
await Storage.SaveProfileAsync(Profile);
}
await HandleLevelSelected(nextLevel);
}
private void UnlockNextLevel(Level completedLevel)
{ {
Profile.CompletedLevels.Add(completedLevel.Id); Profile.CompletedLevels.Add(completedLevel.Id);
Profile.TotalStars += completedLevel.IsBoss ? 3 : 1; Profile.TotalStars += completedLevel.IsBoss ? 3 : 1;
Profile.Gold += completedLevel.IsBoss ? 50 : 20; Profile.Gold += completedLevel.IsBoss ? 50 : 20;
// Unlock next level in current world
var worlds = DataLoader.GetWorldsForGrade(Profile.SelectedGrade); var worlds = DataLoader.GetWorldsForGrade(Profile.SelectedGrade);
var currentWorld = worlds.FirstOrDefault(w => w.Levels.Any(l => l.Id == completedLevel.Id)); var currentWorld = worlds.FirstOrDefault(w => w.Levels.Any(l => l.Id == completedLevel.Id));
if (currentWorld != null) if (currentWorld != null)
@@ -109,7 +169,6 @@
} }
else else
{ {
// Unlock first level of next world
var nextWorldIndex = worlds.IndexOf(currentWorld) + 1; var nextWorldIndex = worlds.IndexOf(currentWorld) + 1;
if (nextWorldIndex < worlds.Count && worlds[nextWorldIndex].Levels.Any()) if (nextWorldIndex < worlds.Count && worlds[nextWorldIndex].Levels.Any())
{ {
@@ -117,8 +176,5 @@
} }
} }
} }
await Storage.SaveProfileAsync(Profile);
CurrentScreen = ScreenType.WorldMap;
} }
} }
@@ -10,6 +10,7 @@ public interface IGameDataLoader
List<Hero> Heroes { get; } List<Hero> Heroes { get; }
List<World> GetWorldsForGrade(int grade); List<World> GetWorldsForGrade(int grade);
Hero? GetHero(string id); Hero? GetHero(string id);
Level? GetNextLevel(int grade, string currentLevelId);
} }
public class GameDataLoader : IGameDataLoader public class GameDataLoader : IGameDataLoader
@@ -60,4 +61,29 @@ public class GameDataLoader : IGameDataLoader
return _heroes.FirstOrDefault(h => h.Id.Equals(id, StringComparison.OrdinalIgnoreCase)) return _heroes.FirstOrDefault(h => h.Id.Equals(id, StringComparison.OrdinalIgnoreCase))
?? _heroes.FirstOrDefault(); ?? _heroes.FirstOrDefault();
} }
public Level? GetNextLevel(int grade, string currentLevelId)
{
var worlds = GetWorldsForGrade(grade);
for (int w = 0; w < worlds.Count; w++)
{
var world = worlds[w];
for (int l = 0; l < world.Levels.Count; l++)
{
if (world.Levels[l].Id == currentLevelId)
{
if (l + 1 < world.Levels.Count)
{
return world.Levels[l + 1];
}
else if (w + 1 < worlds.Count && worlds[w + 1].Levels.Any())
{
return worlds[w + 1].Levels.First();
}
return null;
}
}
}
return null;
}
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

File diff suppressed because it is too large Load Diff
@@ -2,54 +2,24 @@
{ {
"id": 1, "id": 1,
"grade": 1, "grade": 1,
"name": "Rozprávková lúka (Počítanie do 10)", "name": "Svet 1: Rozprávková lúka (Do 10)",
"description": "Jednoduché sčítanie a odčítanie do 10 pre malých prváčikov.", "description": "Základné sčítanie a odčítanie v obore do 10 pre prváčikov.",
"background": "assets/images/backgrounds/world1_meadow.jpg", "background": "assets/images/backgrounds/world1_meadow.jpg",
"levels": [ "levels": [
{ {
"id": "1-1-1", "id": "1-1-1",
"levelNumber": 1, "levelNumber": 1,
"name": "Lúčny Motýlik", "name": "Lúčny Motýlik",
"enemy": { "enemy": { "id": "meadow_butterfly", "name": "Lúčny Motýlik", "maxHp": 20, "attackPower": 1, "assetFolder": "assets/images/enemies/goblin" },
"id": "meadow_butterfly", "mathConfig": { "operations": ["+"], "minA": 1, "maxA": 5, "minB": 1, "maxB": 5, "maxSum": 10, "allowCarryOver": false, "inputType": "DirectInput" },
"name": "Lúčny Motýlik",
"maxHp": 20,
"attackPower": 1,
"assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["+"],
"minA": 1,
"maxA": 5,
"minB": 1,
"maxB": 5,
"maxSum": 10,
"allowCarryOver": false,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 3 "requiredCorrectAnswers": 3
}, },
{ {
"id": "1-1-2", "id": "1-1-2",
"levelNumber": 2, "levelNumber": 2,
"name": "Zvedavý Zajačik", "name": "Zvedavý Zajačik",
"enemy": { "enemy": { "id": "curious_bunny", "name": "Zvedavý Zajačik", "maxHp": 25, "attackPower": 1, "assetFolder": "assets/images/enemies/slime" },
"id": "curious_bunny", "mathConfig": { "operations": ["+", "-"], "minA": 2, "maxA": 10, "minB": 1, "maxB": 5, "maxSum": 10, "allowCarryOver": false, "inputType": "DirectInput" },
"name": "Zvedavý Zajačik",
"maxHp": 25,
"attackPower": 1,
"assetFolder": "assets/images/enemies/slime"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 2,
"maxA": 10,
"minB": 1,
"maxB": 5,
"maxSum": 10,
"allowCarryOver": false,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 3 "requiredCorrectAnswers": 3
}, },
{ {
@@ -57,23 +27,8 @@
"levelNumber": 3, "levelNumber": 3,
"name": "Lesný Medveď (Boss)", "name": "Lesný Medveď (Boss)",
"isBoss": true, "isBoss": true,
"enemy": { "enemy": { "id": "gentle_bear_boss", "name": "Strážca Lúky", "maxHp": 40, "attackPower": 1, "assetFolder": "assets/images/enemies/golem" },
"id": "gentle_bear_boss", "mathConfig": { "operations": ["+", "-"], "minA": 3, "maxA": 10, "minB": 1, "maxB": 5, "maxSum": 10, "allowCarryOver": false, "inputType": "DirectInput" },
"name": "Strážca Lúky",
"maxHp": 40,
"attackPower": 1,
"assetFolder": "assets/images/enemies/golem"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 3,
"maxA": 10,
"minB": 1,
"maxB": 5,
"maxSum": 10,
"allowCarryOver": false,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 4 "requiredCorrectAnswers": 4
} }
] ]
@@ -81,33 +36,87 @@
{ {
"id": 2, "id": 2,
"grade": 1, "grade": 1,
"name": "Slnečná čistinka (Počítanie do 20 bez prechodu)", "name": "Svet 2: Slnečná čistinka (Do 20 bez prechodu)",
"description": "Sčítanie a odčítanie v obore do 20 (napr. 10 + 4, 15 - 3).", "description": "Sčítanie a odčítanie v obore do 20 bez prechodu cez desiatku.",
"background": "assets/images/backgrounds/world2_forest.jpg", "background": "assets/images/backgrounds/world2_forest.jpg",
"levels": [ "levels": [
{ {
"id": "1-2-1", "id": "1-2-1",
"levelNumber": 1, "levelNumber": 1,
"name": "Lesný Škriatok", "name": "Lesný Škriatok",
"enemy": { "enemy": { "id": "forest_gnome", "name": "Lesný Škriatok", "maxHp": 30, "attackPower": 1, "assetFolder": "assets/images/enemies/goblin" },
"id": "forest_gnome", "mathConfig": { "operations": ["+", "-"], "minA": 10, "maxA": 19, "minB": 1, "maxB": 5, "maxSum": 20, "allowCarryOver": false, "inputType": "DirectInput" },
"name": "Lesný Škriatok", "requiredCorrectAnswers": 4
"maxHp": 30,
"attackPower": 1,
"assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 10,
"maxA": 19,
"minB": 1,
"maxB": 5,
"maxSum": 20,
"allowCarryOver": false,
"inputType": "DirectInput"
}, },
{
"id": "1-2-2",
"levelNumber": 2,
"name": "Kráľovský Ježko",
"enemy": { "id": "royal_hedgehog", "name": "Kráľovský Ježko", "maxHp": 35, "attackPower": 1, "assetFolder": "assets/images/enemies/slime" },
"mathConfig": { "operations": ["+", "-"], "minA": 11, "maxA": 19, "minB": 1, "maxB": 8, "maxSum": 20, "allowCarryOver": false, "inputType": "DirectInput" },
"requiredCorrectAnswers": 4 "requiredCorrectAnswers": 4
} }
] ]
},
{
"id": 3,
"grade": 1,
"name": "Svet 3: Zrkadlové jazierko (Porovnávanie < > =)",
"description": "Zisťovanie, ktoré číslo je väčšie, menšie alebo rovnaké.",
"background": "assets/images/backgrounds/world3_river.jpg",
"levels": [
{
"id": "1-3-1",
"levelNumber": 1,
"name": "Vodná Víla",
"enemy": { "id": "water_fairy", "name": "Vodná Víla", "maxHp": 30, "attackPower": 1, "assetFolder": "assets/images/enemies/goblin" },
"mathConfig": { "operations": ["<", ">", "="], "minA": 1, "maxA": 20, "minB": 1, "maxB": 20, "inputType": "MultipleChoice" },
"requiredCorrectAnswers": 4
},
{
"id": "1-3-2",
"levelNumber": 2,
"name": "Riečny Had (Boss)",
"isBoss": true,
"enemy": { "id": "river_serpent_boss", "name": "Riečny Had", "maxHp": 50, "attackPower": 1, "assetFolder": "assets/images/enemies/golem" },
"mathConfig": { "operations": ["<", ">", "="], "minA": 5, "maxA": 20, "minB": 5, "maxB": 20, "inputType": "MultipleChoice" },
"requiredCorrectAnswers": 5
}
]
},
{
"id": 4,
"grade": 1,
"name": "Svet 4: Horská pevnosť (Desiatky)",
"description": "Počítanie po desiatkach do 100 (10, 20, 30...).",
"background": "assets/images/backgrounds/world1_meadow.jpg",
"levels": [
{
"id": "1-4-1",
"levelNumber": 1,
"name": "Kamenný Strážca",
"enemy": { "id": "stone_watcher", "name": "Kamenný Strážca", "maxHp": 35, "attackPower": 1, "assetFolder": "assets/images/enemies/golem" },
"mathConfig": { "operations": ["+", "-"], "minA": 10, "maxA": 50, "minB": 10, "maxB": 40, "maxSum": 100, "allowCarryOver": false, "inputType": "DirectInput" },
"requiredCorrectAnswers": 4
}
]
},
{
"id": 5,
"grade": 1,
"name": "Svet 5: Kráľovský Hrad (Finále 1. ročníka)",
"description": "Veľký záverečný test všetkých zručností prvého ročníka!",
"background": "assets/images/backgrounds/title_screen_bg.jpg",
"levels": [
{
"id": "1-5-1",
"levelNumber": 1,
"name": "Kráľovský Drak (Veľký Boss)",
"isBoss": true,
"enemy": { "id": "king_dragon_boss", "name": "Kráľovský Drak", "maxHp": 60, "attackPower": 1, "assetFolder": "assets/images/enemies/golem" },
"mathConfig": { "operations": ["+", "-"], "minA": 5, "maxA": 20, "minB": 1, "maxB": 10, "maxSum": 20, "allowCarryOver": false, "inputType": "DirectInput" },
"requiredCorrectAnswers": 6
}
]
} }
] ]
@@ -2,7 +2,7 @@
{ {
"id": 1, "id": 1,
"grade": 2, "grade": 2,
"name": "Lúka sčítania a odčítania", "name": "Svet 1: Lúka sčítania a odčítania (Do 20 s prechodom)",
"description": "Základné sčítanie a odčítanie do 20 s prechodom cez desiatku.", "description": "Základné sčítanie a odčítanie do 20 s prechodom cez desiatku.",
"background": "assets/images/backgrounds/world1_meadow.jpg", "background": "assets/images/backgrounds/world1_meadow.jpg",
"levels": [ "levels": [
@@ -19,10 +19,10 @@
}, },
"mathConfig": { "mathConfig": {
"operations": ["+"], "operations": ["+"],
"minA": 1, "minA": 2,
"maxA": 10, "maxA": 9,
"minB": 1, "minB": 2,
"maxB": 10, "maxB": 9,
"maxSum": 20, "maxSum": 20,
"allowCarryOver": true, "allowCarryOver": true,
"inputType": "DirectInput" "inputType": "DirectInput"
@@ -43,9 +43,9 @@
"mathConfig": { "mathConfig": {
"operations": ["+", "-"], "operations": ["+", "-"],
"minA": 5, "minA": 5,
"maxA": 20, "maxA": 18,
"minB": 1, "minB": 2,
"maxB": 10, "maxB": 9,
"maxSum": 20, "maxSum": 20,
"allowCarryOver": true, "allowCarryOver": true,
"inputType": "DirectInput" "inputType": "DirectInput"
@@ -55,21 +55,21 @@
{ {
"id": "2-1-3", "id": "2-1-3",
"levelNumber": 3, "levelNumber": 3,
"name": "Kamenný Golem (Boss)", "name": "Golem Čísel (Boss)",
"isBoss": true, "isBoss": true,
"enemy": { "enemy": {
"id": "stone_golem_boss", "id": "stone_golem_boss",
"name": "Golem Čísiel", "name": "Golem Čísel",
"maxHp": 60, "maxHp": 60,
"attackPower": 1, "attackPower": 1,
"assetFolder": "assets/images/enemies/golem" "assetFolder": "assets/images/enemies/golem"
}, },
"mathConfig": { "mathConfig": {
"operations": ["+", "-"], "operations": ["+", "-"],
"minA": 8, "minA": 6,
"maxA": 20, "maxA": 20,
"minB": 4, "minB": 3,
"maxB": 10, "maxB": 9,
"maxSum": 20, "maxSum": 20,
"allowCarryOver": true, "allowCarryOver": true,
"inputType": "DirectInput" "inputType": "DirectInput"
@@ -81,20 +81,63 @@
{ {
"id": 2, "id": 2,
"grade": 2, "grade": 2,
"name": "Les porovnávania a rovností", "name": "Svet 2: Les porovnávania (Do 100)",
"description": "Porovnávanie čísel pomocou znakov <, >, = a dopĺňanie do desiatky.", "description": "Porovnávanie čísel pomocou znakov <, >, = v obore do 100.",
"background": "assets/images/backgrounds/world2_forest.jpg", "background": "assets/images/backgrounds/world2_forest.jpg",
"levels": [ "levels": [
{ {
"id": "2-2-1", "id": "2-2-1",
"levelNumber": 1, "levelNumber": 1,
"name": "Múdra Sova", "name": "Lesný Goblin",
"enemy": { "enemy": {
"id": "wise_owl", "id": "forest_goblin_comp",
"name": "Múdra Sova", "name": "Lesný Goblin",
"maxHp": 30, "maxHp": 35,
"attackPower": 1, "attackPower": 1,
"assetFolder": "assets/images/enemies/owl" "assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["<", ">", "="],
"minA": 1,
"maxA": 50,
"minB": 1,
"maxB": 50,
"inputType": "MultipleChoice"
},
"requiredCorrectAnswers": 4
},
{
"id": "2-2-2",
"levelNumber": 2,
"name": "Slizký Strážca",
"enemy": {
"id": "slime_comp_watcher",
"name": "Slizký Strážca",
"maxHp": 40,
"attackPower": 1,
"assetFolder": "assets/images/enemies/slime"
},
"mathConfig": {
"operations": ["<", ">", "="],
"minA": 20,
"maxA": 100,
"minB": 20,
"maxB": 100,
"inputType": "MultipleChoice"
},
"requiredCorrectAnswers": 4
},
{
"id": "2-2-3",
"levelNumber": 3,
"name": "Strážca Hvozdu (Boss)",
"isBoss": true,
"enemy": {
"id": "forest_golem_comp_boss",
"name": "Strážca Hvozdu",
"maxHp": 55,
"attackPower": 1,
"assetFolder": "assets/images/enemies/golem"
}, },
"mathConfig": { "mathConfig": {
"operations": ["<", ">", "="], "operations": ["<", ">", "="],
@@ -104,7 +147,235 @@
"maxB": 100, "maxB": 100,
"inputType": "MultipleChoice" "inputType": "MultipleChoice"
}, },
"requiredCorrectAnswers": 5
}
]
},
{
"id": 3,
"grade": 2,
"name": "Svet 3: Riečna zátoka (Do 100 bez prechodu)",
"description": "Sčítanie a odčítanie desiatok a dvojciferných čísel bez prechodu.",
"background": "assets/images/backgrounds/world3_river.jpg",
"levels": [
{
"id": "2-3-1",
"levelNumber": 1,
"name": "Riečny Goblin",
"enemy": {
"id": "river_goblin_easy",
"name": "Riečny Goblin",
"maxHp": 35,
"attackPower": 1,
"assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 10,
"maxA": 80,
"minB": 1,
"maxB": 9,
"maxSum": 100,
"allowCarryOver": false,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 4 "requiredCorrectAnswers": 4
},
{
"id": "2-3-2",
"levelNumber": 2,
"name": "Kvapkový Sliz",
"enemy": {
"id": "droplet_slime",
"name": "Kvapkový Sliz",
"maxHp": 45,
"attackPower": 1,
"assetFolder": "assets/images/enemies/slime"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 20,
"maxA": 70,
"minB": 10,
"maxB": 25,
"maxSum": 100,
"allowCarryOver": false,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 4
},
{
"id": "2-3-3",
"levelNumber": 3,
"name": "Balvanový Netvor (Boss)",
"isBoss": true,
"enemy": {
"id": "river_golem_boss",
"name": "Balvanový Netvor",
"maxHp": 65,
"attackPower": 1,
"assetFolder": "assets/images/enemies/golem"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 20,
"maxA": 90,
"minB": 5,
"maxB": 30,
"maxSum": 100,
"allowCarryOver": false,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 5
}
]
},
{
"id": 4,
"grade": 2,
"name": "Svet 4: Horský priesmyk (Do 100 s prechodom)",
"description": "Sčítanie a odčítanie s prechodom cez desiatku v obore do 100.",
"background": "assets/images/backgrounds/world1_meadow.jpg",
"levels": [
{
"id": "2-4-1",
"levelNumber": 1,
"name": "Horský Goblin",
"enemy": {
"id": "mountain_goblin",
"name": "Horský Goblin",
"maxHp": 40,
"attackPower": 1,
"assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 15,
"maxA": 75,
"minB": 4,
"maxB": 9,
"maxSum": 100,
"allowCarryOver": true,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 4
},
{
"id": "2-4-2",
"levelNumber": 2,
"name": "Magický Horský Sliz",
"enemy": {
"id": "mountain_magic_slime",
"name": "Magický Horský Sliz",
"maxHp": 50,
"attackPower": 1,
"assetFolder": "assets/images/enemies/slime"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 20,
"maxA": 85,
"minB": 6,
"maxB": 15,
"maxSum": 100,
"allowCarryOver": true,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 5
},
{
"id": "2-4-3",
"levelNumber": 3,
"name": "Horský Obor (Boss)",
"isBoss": true,
"enemy": {
"id": "mountain_giant_boss",
"name": "Horský Obor",
"maxHp": 70,
"attackPower": 1,
"assetFolder": "assets/images/enemies/golem"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 25,
"maxA": 90,
"minB": 8,
"maxB": 25,
"maxSum": 100,
"allowCarryOver": true,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 5
}
]
},
{
"id": 5,
"grade": 2,
"name": "Svet 5: Hrad malej násobilky (Úvod do násobenia)",
"description": "Základy násobenia číslami 2, 3, 5 a 10.",
"background": "assets/images/backgrounds/title_screen_bg.jpg",
"levels": [
{
"id": "2-5-1",
"levelNumber": 1,
"name": "Hradný Strážca",
"enemy": {
"id": "castle_guard_goblin",
"name": "Hradný Strážca",
"maxHp": 40,
"attackPower": 1,
"assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["*"],
"factors": [2, 5],
"minMultiplier": 1,
"maxMultiplier": 10,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 4
},
{
"id": "2-5-2",
"levelNumber": 2,
"name": "Kráľovský Sliz",
"enemy": {
"id": "royal_castle_slime",
"name": "Kráľovský Sliz",
"maxHp": 50,
"attackPower": 1,
"assetFolder": "assets/images/enemies/slime"
},
"mathConfig": {
"operations": ["*"],
"factors": [2, 3, 5, 10],
"minMultiplier": 1,
"maxMultiplier": 10,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 5
},
{
"id": "2-5-3",
"levelNumber": 3,
"name": "Veľký Hradný Pán (Finále 2. ročníka)",
"isBoss": true,
"enemy": {
"id": "castle_lord_boss",
"name": "Veľký Hradný Pán",
"maxHp": 75,
"attackPower": 1,
"assetFolder": "assets/images/enemies/golem"
},
"mathConfig": {
"operations": ["*"],
"factors": [2, 3, 5, 10],
"minMultiplier": 1,
"maxMultiplier": 10,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 6
} }
] ]
} }
@@ -2,8 +2,8 @@
{ {
"id": 1, "id": 1,
"grade": 3, "grade": 3,
"name": "Rieka malej násobilky (2, 3, 4, 5)", "name": "Svet 1: Rieka malej násobilky (2, 3, 4, 5)",
"description": "Základy násobenia a delenia číslami 2, 3, 4, 5.", "description": "Základy násobenia a delenia číslami 2, 3, 4 a 5.",
"background": "assets/images/backgrounds/world3_river.jpg", "background": "assets/images/backgrounds/world3_river.jpg",
"levels": [ "levels": [
{ {
@@ -11,11 +11,11 @@
"levelNumber": 1, "levelNumber": 1,
"name": "Riečny Škriatok", "name": "Riečny Škriatok",
"enemy": { "enemy": {
"id": "river_sprite", "id": "river_sprite_goblin",
"name": "Riečny Škriatok", "name": "Riečny Škriatok",
"maxHp": 30, "maxHp": 35,
"attackPower": 1, "attackPower": 1,
"assetFolder": "assets/images/enemies/sprite" "assetFolder": "assets/images/enemies/goblin"
}, },
"mathConfig": { "mathConfig": {
"operations": ["*"], "operations": ["*"],
@@ -29,13 +29,34 @@
{ {
"id": "3-1-2", "id": "3-1-2",
"levelNumber": 2, "levelNumber": 2,
"name": "Vodník Násobka", "name": "Vodný Slizík",
"enemy": { "enemy": {
"id": "water_demon", "id": "water_slime_div",
"name": "Vodník Násobka", "name": "Vodný Slizík",
"maxHp": 40, "maxHp": 45,
"attackPower": 1, "attackPower": 1,
"assetFolder": "assets/images/enemies/water_demon" "assetFolder": "assets/images/enemies/slime"
},
"mathConfig": {
"operations": ["/"],
"factors": [2, 3, 4, 5],
"minMultiplier": 1,
"maxMultiplier": 10,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 4
},
{
"id": "3-1-3",
"levelNumber": 3,
"name": "Jazerný Strážca (Boss)",
"isBoss": true,
"enemy": {
"id": "lake_golem_boss",
"name": "Jazerný Strážca",
"maxHp": 65,
"attackPower": 1,
"assetFolder": "assets/images/enemies/golem"
}, },
"mathConfig": { "mathConfig": {
"operations": ["*", "/"], "operations": ["*", "/"],
@@ -45,22 +66,71 @@
"inputType": "DirectInput" "inputType": "DirectInput"
}, },
"requiredCorrectAnswers": 5 "requiredCorrectAnswers": 5
}
]
}, },
{ {
"id": "3-1-3", "id": 2,
"grade": 3,
"name": "Svet 2: Les veľkej násobilky (6, 7, 8, 9, 10)",
"description": "Násobenie a delenie vyššími číslami 6 až 10.",
"background": "assets/images/backgrounds/world2_forest.jpg",
"levels": [
{
"id": "3-2-1",
"levelNumber": 1,
"name": "Lesný Piadimužík",
"enemy": {
"id": "forest_dwarf_goblin",
"name": "Lesný Piadimužík",
"maxHp": 40,
"attackPower": 1,
"assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["*"],
"factors": [6, 7, 8, 9, 10],
"minMultiplier": 1,
"maxMultiplier": 10,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 4
},
{
"id": "3-2-2",
"levelNumber": 2,
"name": "Živicový Sliz",
"enemy": {
"id": "resin_slime_div",
"name": "Živicový Sliz",
"maxHp": 50,
"attackPower": 1,
"assetFolder": "assets/images/enemies/slime"
},
"mathConfig": {
"operations": ["/"],
"factors": [6, 7, 8, 9, 10],
"minMultiplier": 1,
"maxMultiplier": 10,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 5
},
{
"id": "3-2-3",
"levelNumber": 3, "levelNumber": 3,
"name": "Jazerný Drak (Boss)", "name": "Starý Lesný Golem (Boss)",
"isBoss": true, "isBoss": true,
"enemy": { "enemy": {
"id": "lake_dragon_boss", "id": "ancient_forest_golem_boss",
"name": "Jazerný Drak", "name": "Starý Lesný Golem",
"maxHp": 60, "maxHp": 70,
"attackPower": 1, "attackPower": 1,
"assetFolder": "assets/images/enemies/dragon" "assetFolder": "assets/images/enemies/golem"
}, },
"mathConfig": { "mathConfig": {
"operations": ["*", "/"], "operations": ["*", "/"],
"factors": [2, 3, 4, 5], "factors": [6, 7, 8, 9, 10],
"minMultiplier": 1, "minMultiplier": 1,
"maxMultiplier": 10, "maxMultiplier": 10,
"inputType": "DirectInput" "inputType": "DirectInput"
@@ -68,5 +138,237 @@
"requiredCorrectAnswers": 6 "requiredCorrectAnswers": 6
} }
] ]
},
{
"id": 3,
"grade": 3,
"name": "Svet 3: Krištáľové údolie (Počítanie do 1000)",
"description": "Sčítanie a odčítanie v obore do 1000 po stovkách a desiatkach.",
"background": "assets/images/backgrounds/world1_meadow.jpg",
"levels": [
{
"id": "3-3-1",
"levelNumber": 1,
"name": "Jaskynný Goblin",
"enemy": {
"id": "cave_goblin_1000",
"name": "Jaskynný Goblin",
"maxHp": 45,
"attackPower": 1,
"assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 100,
"maxA": 800,
"minB": 50,
"maxB": 200,
"maxSum": 1000,
"allowCarryOver": false,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 4
},
{
"id": "3-3-2",
"levelNumber": 2,
"name": "Trblietavý Sliz",
"enemy": {
"id": "glitter_slime_1000",
"name": "Trblietavý Sliz",
"maxHp": 55,
"attackPower": 1,
"assetFolder": "assets/images/enemies/slime"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 200,
"maxA": 900,
"minB": 100,
"maxB": 300,
"maxSum": 1000,
"allowCarryOver": true,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 5
},
{
"id": "3-3-3",
"levelNumber": 3,
"name": "Kryštálový Golem (Boss)",
"isBoss": true,
"enemy": {
"id": "crystal_golem_boss",
"name": "Kryštálový Golem",
"maxHp": 75,
"attackPower": 1,
"assetFolder": "assets/images/enemies/golem"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 150,
"maxA": 850,
"minB": 50,
"maxB": 350,
"maxSum": 1000,
"allowCarryOver": true,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 6
}
]
},
{
"id": 4,
"grade": 3,
"name": "Svet 4: Veterné bralo (Porovnávanie do 1000)",
"description": "Porovnávanie veľkých trojciferných čísel pomocou znakov <, >, =.",
"background": "assets/images/backgrounds/world3_river.jpg",
"levels": [
{
"id": "3-4-1",
"levelNumber": 1,
"name": "Veterný Škriatok",
"enemy": {
"id": "wind_sprite_goblin",
"name": "Veterný Škriatok",
"maxHp": 40,
"attackPower": 1,
"assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["<", ">", "="],
"minA": 100,
"maxA": 999,
"minB": 100,
"maxB": 999,
"inputType": "MultipleChoice"
},
"requiredCorrectAnswers": 4
},
{
"id": "3-4-2",
"levelNumber": 2,
"name": "Búrkový Sliz",
"enemy": {
"id": "storm_slime_comp",
"name": "Búrkový Sliz",
"maxHp": 50,
"attackPower": 1,
"assetFolder": "assets/images/enemies/slime"
},
"mathConfig": {
"operations": ["<", ">", "="],
"minA": 300,
"maxA": 1000,
"minB": 300,
"maxB": 1000,
"inputType": "MultipleChoice"
},
"requiredCorrectAnswers": 5
},
{
"id": "3-4-3",
"levelNumber": 3,
"name": "Hromový Golem (Boss)",
"isBoss": true,
"enemy": {
"id": "thunder_golem_boss",
"name": "Hromový Golem",
"maxHp": 75,
"attackPower": 1,
"assetFolder": "assets/images/enemies/golem"
},
"mathConfig": {
"operations": ["<", ">", "="],
"minA": 100,
"maxA": 1000,
"minB": 100,
"maxB": 1000,
"inputType": "MultipleChoice"
},
"requiredCorrectAnswers": 6
}
]
},
{
"id": 5,
"grade": 3,
"name": "Svet 5: Kráľovská citadela (Veľké finále)",
"description": "Záverečná výzva tretieho ročníka – mix násobilky, delenia a veľkých čísel!",
"background": "assets/images/backgrounds/title_screen_bg.jpg",
"levels": [
{
"id": "3-5-1",
"levelNumber": 1,
"name": "Elitný Goblin",
"enemy": {
"id": "elite_guard_goblin",
"name": "Elitný Goblin",
"maxHp": 50,
"attackPower": 1,
"assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["*", "/"],
"factors": [2, 3, 4, 5, 6, 7, 8, 9, 10],
"minMultiplier": 1,
"maxMultiplier": 10,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 5
},
{
"id": "3-5-2",
"levelNumber": 2,
"name": "Magický Plazmový Sliz",
"enemy": {
"id": "plasma_slime_boss",
"name": "Magický Plazmový Sliz",
"maxHp": 65,
"attackPower": 1,
"assetFolder": "assets/images/enemies/slime"
},
"mathConfig": {
"operations": ["+", "-", "*", "/"],
"minA": 10,
"maxA": 100,
"minB": 5,
"maxB": 50,
"maxSum": 200,
"factors": [2, 3, 4, 5, 6, 7, 8, 9, 10],
"minMultiplier": 1,
"maxMultiplier": 10,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 6
},
{
"id": "3-5-3",
"levelNumber": 3,
"name": "Arcimág Čísel (Záverečný Boss)",
"isBoss": true,
"enemy": {
"id": "archmage_golem_final_boss",
"name": "Arcimág Čísel",
"maxHp": 85,
"attackPower": 1,
"assetFolder": "assets/images/enemies/golem"
},
"mathConfig": {
"operations": ["+", "-", "*", "/"],
"minA": 10,
"maxA": 500,
"minB": 10,
"maxB": 200,
"maxSum": 1000,
"factors": [2, 3, 4, 5, 6, 7, 8, 9, 10],
"minMultiplier": 1,
"maxMultiplier": 10,
"inputType": "DirectInput"
},
"requiredCorrectAnswers": 8
}
]
} }
] ]
+2 -3
View File
@@ -13,8 +13,6 @@
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/png" href="favicon.png" />
<link rel="preload" id="webassembly" /> <link rel="preload" id="webassembly" />
<link rel="stylesheet" href="css/app.css" /> <link rel="stylesheet" href="css/app.css" />
<link href="CiselnaVyprava.styles.css" rel="stylesheet" />
<script type="importmap"></script> <script type="importmap"></script>
</head> </head>
@@ -38,8 +36,9 @@
<a href="." class="reload">Obnoviť hru</a> <a href="." class="reload">Obnoviť hru</a>
<span class="dismiss">🗙</span> <span class="dismiss">🗙</span>
</div> </div>
<script src="_framework/blazor.webassembly.js"></script> <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body> </body>
</html> </html>
+2 -8
View File
@@ -59,21 +59,15 @@
<mimeMap fileExtension=".wav" mimeType="audio/wav" /> <mimeMap fileExtension=".wav" mimeType="audio/wav" />
</staticContent> </staticContent>
<!-- URL Rewrite for Single Page Application (SPA routing) --> <!-- SPA URL Rewrite -->
<rewrite> <rewrite>
<rules> <rules>
<rule name="Direct-File-Access" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" />
</conditions>
<action type="None" />
</rule>
<rule name="SPA-Routing-Fallback" stopProcessing="true"> <rule name="SPA-Routing-Fallback" stopProcessing="true">
<match url=".*" /> <match url=".*" />
<conditions logicalGrouping="MatchAll"> <conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(_framework|assets|css|data)/" negate="true" />
</conditions> </conditions>
<action type="Rewrite" url="/" /> <action type="Rewrite" url="/" />
</rule> </rule>