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 ILocalStorageService Storage
@inject NavigationManager Nav
<div class="battle-arena-wrapper" tabindex="0" @ref="arenaElement" @onkeydown="HandleKeyDown" autofocus>
<!-- Top Header / Bar -->
<div class="battle-arena-wrapper" tabindex="0" @onkeydown="HandleKeyDown" @ref="arenaElement">
<!-- Top HUD / Level Info -->
<div class="battle-header">
<button class="btn-back" @onclick="GoBackToMap">⬅ Mapa</button>
<div class="level-info">
<span class="world-title">@CurrentLevel.Name</span>
@if (CurrentLevel.IsBoss)
{
<span class="boss-badge">👑 BOSS</span>
<span class="boss-badge">👑 BOSS SÚBOJ</span>
}
</div>
<div class="combo-counter @(State.ComboStreak > 1 ? "active-streak" : "")">
🔥 Kombo: <strong>@State.ComboStreak</strong>
🔥 Séria: <strong>@State.ComboStreak</strong>
</div>
</div>
<!-- Feedback / Toast Banner -->
@if (!string.IsNullOrEmpty(State.LastFeedbackMessage))
<!-- Dynamic Feedback Banner -->
@if (!string.IsNullOrEmpty(State.LastFeedbackMessage) && !State.IsBattleOver)
{
<div class="feedback-banner @GetFeedbackClass()">
<div class="feedback-banner @GetFeedbackClass() anim-fade-in">
@State.LastFeedbackMessage
</div>
}
<!-- Main Combat Arena -->
<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;">
<!-- Player Side -->
<div class="combatant player-side @GetPlayerAnimationClass()">
<!-- Visual Combat Stage with Dynamic World Background -->
<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;">
<!-- Hero Side -->
<div class="combatant hero-side @GetPlayerAnimationClass()">
<div class="combatant-info">
<span class="combatant-name">@Hero.Name</span>
<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")">
@(i <= State.PlayerCurrentHp ? "❤️" : "🤍")
</span>
if (i < State.PlayerCurrentHp)
{
<span class="heart full">❤️</span>
}
else
{
<span class="heart empty">🖤</span>
}
}
</div>
</div>
<!-- Player Visual (4 Phases & Combat Poses) -->
<!-- Hero Visual (4 Phases & Combat Poses) -->
<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)"
alt="@Hero.Name"
class="character-image"
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>'"/>
class="character-image hero-img" />
</div>
</div>
<!-- VS Divider / Dynamic Timer -->
<div class="arena-center">
<!-- Center Round Timer Orb -->
<div class="arena-center-timer">
<div class="timer-orb @GetTimerColorClass()">
<div class="timer-seconds">@RemainingTimeSeconds.ToString("F1")s</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>
<img src="@GetCharacterImage(State.Enemy.AssetFolder, State.EnemyHpPhase, State.EnemyPose)"
alt="@State.Enemy.Name"
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>'"/>
class="character-image enemy-img" />
</div>
</div>
</div>
@@ -105,39 +104,116 @@
}
</div>
}
else
</div>
<!-- FULLSCREEN VICTORY / DEFEAT CELEBRATION OVERLAY -->
@if (State.IsBattleOver)
{
<!-- Victory / Defeat Modal -->
<div class="battle-end-card @(State.IsVictory ? "victory-card" : "defeat-card")">
<h2>@(State.IsVictory ? "🏆 VÍŤAZSTVO!" : "💀 PORÁŽKA!")</h2>
<p>@State.LastFeedbackMessage</p>
<div class="end-actions">
<div class="battle-end-overlay anim-fade-in">
<div class="celebration-modal @(State.IsVictory ? "victory-theme" : "defeat-theme") anim-victory-modal-pop">
@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
{
<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>
@code {
[Parameter] public Hero Hero { 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 EventCallback OnReturnToMap { get; set; }
[Parameter] public EventCallback<Level> OnLevelCompleted { get; set; }
[Parameter] public EventCallback<Level> OnNextLevelRequested { get; set; }
private BattleState State => BattleEngine.State;
private string CurrentInput { get; set; } = string.Empty;
private System.Threading.Timer? _timer;
private double RemainingTimeSeconds { get; set; } = 6.0;
private Level? NextLevel => DataLoader.GetNextLevel(PlayerGrade, CurrentLevel.Id);
protected override void OnInitialized()
{
StartBattle();
@@ -229,6 +305,18 @@
await OnLevelCompleted.InvokeAsync(CurrentLevel);
}
private async Task HandleNextLevelClick()
{
if (NextLevel != null)
{
await OnNextLevelRequested.InvokeAsync(NextLevel);
}
else
{
await OnVictoryContinue();
}
}
private async Task GoBackToMap()
{
_timer?.Dispose();
@@ -297,23 +385,28 @@
if (e.Key == "Enter")
{
if (State.IsVictory)
await OnVictoryContinue();
{
if (NextLevel != null)
await HandleNextLevelClick();
else
await OnVictoryContinue();
}
else
{
await RetryBattle();
}
}
return;
}
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)
{
await HandleChoiceSelected(State.CurrentProblem.Choices[choiceIndex - 1]);
return;
}
// Handle A/N shortcuts for ÁNO / NIE
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));
@@ -329,7 +422,6 @@
}
else
{
// Direct numeric input (0-9, Backspace, Enter)
if (e.Key.Length == 1 && char.IsDigit(e.Key[0]))
{
if (CurrentInput.Length < 6)
@@ -356,4 +448,3 @@
}
}
}
@@ -1,106 +1,379 @@
@inject IGameDataLoader DataLoader
@inject ILocalStorageService Storage
<div class="character-select-screen">
<div class="parchment-container select-card-compact">
<div class="select-header">
<h1 class="game-main-title">🏰 ČÍSELNÁ VÝPRAVA</h1>
<p class="subtitle">Vytvor si svojho hrdinu a vyber ročník</p>
<div class="wizard-screen">
@switch (CurrentStep)
{
case WizardStep.SelectGrade:
{
<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 class="select-content-grid">
<!-- LEFT: ROČNÍK & MENO -->
<div class="select-col-left">
<div class="selection-section">
<h2 class="step-title">1. Tvoj ročník</h2>
<div class="grade-toggle-group">
<button class="grade-btn @(SelectedGrade == 1 ? "active" : "")" @onclick="() => SetGrade(1)">
<span class="grade-badge">🎒 1. ROČNÍK</span>
<span class="grade-desc">Do 10 / 20</span>
</button>
<button class="grade-btn @(SelectedGrade == 2 ? "active" : "")" @onclick="() => SetGrade(2)">
<span class="grade-badge">🎒 2. ROČNÍK</span>
<span class="grade-desc">Do 20 / 100</span>
</button>
<button class="grade-btn @(SelectedGrade == 3 ? "active" : "")" @onclick="() => SetGrade(3)">
<span class="grade-badge">🎒 3. ROČNÍK</span>
<span class="grade-desc">Násobilka & delenie</span>
<div class="wizard-header">
<h1 class="wizard-title">🎒 VYBER SI ROČNÍK</h1>
<p class="wizard-subtitle">Zvoľ si úroveň matematických výziev pre svoje dobrodružstvo</p>
</div>
<div class="grades-grid">
<!-- GRADE 1 -->
<div class="grade-card grade-1-card @(SelectedGrade == 1 ? "selected" : "")" @onclick="() => SelectGradeAndProceed(1)">
<div class="grade-banner-stage" style="background: url('assets/images/backgrounds/world1_meadow.jpg') center/cover no-repeat;">
<div class="grade-banner-overlay">
<span class="grade-card-icon">⭐</span>
<div class="grade-card-badge grade-1-badge">1. ROČNÍK</div>
</div>
</div>
<div class="grade-card-content">
<h3 class="grade-card-title">Prváčik</h3>
<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>
</div>
</div>
<div class="selection-section">
<h2 class="step-title">3. Meno hrdinu</h2>
<div class="name-field-row">
<input type="text" class="hero-name-input" @bind="HeroName" placeholder="Tvoje meno..." maxlength="20" />
<button class="btn-start-quest" @onclick="StartQuest">
Vstúpiť do hry 🚀
<!-- GRADE 2 -->
<div class="grade-card grade-2-card @(SelectedGrade == 2 ? "selected" : "")" @onclick="() => SelectGradeAndProceed(2)">
<div class="grade-banner-stage" style="background: url('assets/images/backgrounds/world2_forest.jpg') center/cover no-repeat;">
<div class="grade-banner-overlay">
<span class="grade-card-icon">🌟</span>
<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>
</div>
</div>
</div>
</div>
break;
}
<!-- RIGHT: VÝBER HRDINU -->
<div class="select-col-right">
<div class="selection-section">
<h2 class="step-title">2. Vyber si hrdinu</h2>
<div class="heroes-grid">
case WizardStep.SelectHero:
{
<div class="parchment-container wizard-container">
<div class="wizard-top-bar">
<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)
{
<div class="hero-card @(SelectedHeroId == hero.Id ? "selected-card" : "")"
@onclick="() => SelectHero(hero.Id)">
<div class="hero-avatar">
@(hero.Id == "knight" ? "⚔️" : hero.Id == "mage" ? "🧙‍♂️" : "🏹")
var isSelected = SelectedHeroId == hero.Id;
var heroImg = $"{hero.AssetFolder}_idle_p1.png";
var themeClass = hero.Id switch
{
"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>
<h3 class="hero-name">@hero.Name</h3>
<span class="hero-title">@hero.Title</span>
<div class="hero-skill">
✨ @hero.SpecialAttackName
<div class="hero-portrait-stage">
<img src="@heroImg" alt="@hero.Name" class="hero-portrait-img anim-hero-idle" />
</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;
}
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 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 {
[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 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()
{
if (DataLoader.Heroes.Any())
{
SelectedHeroId = DataLoader.Heroes.First().Id;
UpdateDefaultName();
}
}
private void SetGrade(int grade)
private void SelectGradeAndProceed(int grade)
{
SelectedGrade = grade;
CurrentStep = WizardStep.SelectHero;
}
private void SelectHero(string heroId)
private void SelectHeroAndProceed(string 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()
{
var finalName = string.IsNullOrWhiteSpace(HeroName) ? "Mladý Hrdina" : HeroName.Trim();
var profile = new PlayerProfile
{
Name = string.IsNullOrWhiteSpace(HeroName) ? "Mladý Hrdina" : HeroName,
Name = finalName,
SelectedGrade = SelectedGrade,
SelectedHeroId = SelectedHeroId,
CurrentWorldId = 1,
UnlockedLevels = new HashSet<string> { $"{SelectedGrade}-1-1" }
UnlockedLevels = new HashSet<string> { $"{SelectedGrade}-1-1" },
CompletedLevels = new HashSet<string>()
};
await Storage.SaveProfileAsync(profile);
+229 -38
View File
@@ -1,63 +1,182 @@
@inject IGameDataLoader DataLoader
<div class="world-map-screen">
<!-- Top Player Status Bar -->
<!-- TOP STATUS BAR -->
<div class="map-top-bar">
<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="grade-tag">@(Profile.SelectedGrade). Ročník</span>
<span class="grade-tag">🎒 @(Profile.SelectedGrade). Ročník</span>
</div>
<div class="stats-pill">
<span>⭐ Hviezdy: <strong>@Profile.TotalStars</strong></span>
</div>
<button class="btn-change-hero" @onclick="OnChangeHero">Zmeniť hrdinu 🔄</button>
</div>
<!-- Worlds Container -->
<div class="worlds-carousel">
@foreach (var world in Worlds)
{
<div class="world-card">
<div class="world-header">
<h2>@world.Name</h2>
<p>@world.Description</p>
<div class="stats-pill-group">
<div class="stat-badge stars-badge">
<span>⭐ <strong>@Profile.TotalStars</strong> Hviezd</span>
</div>
<div class="stat-badge gold-badge">
<span>💰 <strong>@Profile.Gold</strong> Zlata</span>
</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" : "")"
disabled="@(!isUnlocked)"
@onclick="() => SelectLevel(level)">
<div class="node-icon">
@if (isCompleted)
<div class="map-top-actions">
<button class="btn-top-action" @onclick="OnChangeHero" title="Zmeniť ročník alebo hrdinu">
🎒 Zmeniť hrdinu
</button>
</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>
}
else if (level.IsBoss)
var w = Worlds[i];
var worldIndex = i + 1;
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)
{
<span>⚔️</span>
}
else
else if (!isWorldUnlocked)
{
<span>🔒</span>
}
</div>
<span class="level-title">@level.Name</span>
@if (level.IsBoss)
else
{
<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>
}
</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>
@@ -69,9 +188,81 @@
[Parameter] public EventCallback OnChangeHero { get; set; }
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)
{
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)
{
<CharacterSelect OnProfileReady="HandleProfileReady" />
<CharacterSelect OnProfileReady="HandleProfileReady"
OnCancel="() => CurrentScreen = ScreenType.MainMenu" />
}
else if (CurrentScreen == ScreenType.WorldMap)
{
@@ -34,11 +35,42 @@
else if (CurrentScreen == ScreenType.Battle && SelectedLevel != null)
{
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"
CurrentLevel="SelectedLevel"
PlayerGrade="Profile.SelectedGrade"
BackgroundImage="@bg"
Config="DataLoader.Config"
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>
@@ -57,6 +89,9 @@
private PlayerProfile Profile { get; set; } = new();
private Level? SelectedLevel { get; set; }
private bool IsTransitioning { get; set; } = false;
private string TransitionTargetText { get; set; } = string.Empty;
protected override async Task OnInitializedAsync()
{
try
@@ -85,19 +120,44 @@
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;
CurrentScreen = ScreenType.Battle;
IsTransitioning = false;
StateHasChanged();
}
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.TotalStars += completedLevel.IsBoss ? 3 : 1;
Profile.Gold += completedLevel.IsBoss ? 50 : 20;
// Unlock next level in current world
var worlds = DataLoader.GetWorldsForGrade(Profile.SelectedGrade);
var currentWorld = worlds.FirstOrDefault(w => w.Levels.Any(l => l.Id == completedLevel.Id));
if (currentWorld != null)
@@ -109,7 +169,6 @@
}
else
{
// Unlock first level of next world
var nextWorldIndex = worlds.IndexOf(currentWorld) + 1;
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<World> GetWorldsForGrade(int grade);
Hero? GetHero(string id);
Level? GetNextLevel(int grade, string currentLevelId);
}
public class GameDataLoader : IGameDataLoader
@@ -60,4 +61,29 @@ public class GameDataLoader : IGameDataLoader
return _heroes.FirstOrDefault(h => h.Id.Equals(id, StringComparison.OrdinalIgnoreCase))
?? _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,
"grade": 1,
"name": "Rozprávková lúka (Počítanie do 10)",
"description": "Jednoduché sčítanie a odčítanie do 10 pre malých prváčikov.",
"name": "Svet 1: Rozprávková lúka (Do 10)",
"description": "Základné sčítanie a odčítanie v obore do 10 pre prváčikov.",
"background": "assets/images/backgrounds/world1_meadow.jpg",
"levels": [
{
"id": "1-1-1",
"levelNumber": 1,
"name": "Lúčny Motýlik",
"enemy": {
"id": "meadow_butterfly",
"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"
},
"enemy": { "id": "meadow_butterfly", "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
},
{
"id": "1-1-2",
"levelNumber": 2,
"name": "Zvedavý Zajačik",
"enemy": {
"id": "curious_bunny",
"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"
},
"enemy": { "id": "curious_bunny", "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
},
{
@@ -57,23 +27,8 @@
"levelNumber": 3,
"name": "Lesný Medveď (Boss)",
"isBoss": true,
"enemy": {
"id": "gentle_bear_boss",
"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"
},
"enemy": { "id": "gentle_bear_boss", "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
}
]
@@ -81,32 +36,86 @@
{
"id": 2,
"grade": 1,
"name": "Slnečná čistinka (Počítanie do 20 bez prechodu)",
"description": "Sčítanie a odčítanie v obore do 20 (napr. 10 + 4, 15 - 3).",
"name": "Svet 2: Slnečná čistinka (Do 20 bez prechodu)",
"description": "Sčítanie a odčítanie v obore do 20 bez prechodu cez desiatku.",
"background": "assets/images/backgrounds/world2_forest.jpg",
"levels": [
{
"id": "1-2-1",
"levelNumber": 1,
"name": "Lesný Škriatok",
"enemy": {
"id": "forest_gnome",
"name": "Lesný Škriatok",
"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"
},
"enemy": { "id": "forest_gnome", "name": "Lesný Škriatok", "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" },
"requiredCorrectAnswers": 4
},
{
"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
}
]
},
{
"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,
"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.",
"background": "assets/images/backgrounds/world1_meadow.jpg",
"levels": [
@@ -19,10 +19,10 @@
},
"mathConfig": {
"operations": ["+"],
"minA": 1,
"maxA": 10,
"minB": 1,
"maxB": 10,
"minA": 2,
"maxA": 9,
"minB": 2,
"maxB": 9,
"maxSum": 20,
"allowCarryOver": true,
"inputType": "DirectInput"
@@ -43,9 +43,9 @@
"mathConfig": {
"operations": ["+", "-"],
"minA": 5,
"maxA": 20,
"minB": 1,
"maxB": 10,
"maxA": 18,
"minB": 2,
"maxB": 9,
"maxSum": 20,
"allowCarryOver": true,
"inputType": "DirectInput"
@@ -55,21 +55,21 @@
{
"id": "2-1-3",
"levelNumber": 3,
"name": "Kamenný Golem (Boss)",
"name": "Golem Čísel (Boss)",
"isBoss": true,
"enemy": {
"id": "stone_golem_boss",
"name": "Golem Čísiel",
"name": "Golem Čísel",
"maxHp": 60,
"attackPower": 1,
"assetFolder": "assets/images/enemies/golem"
},
"mathConfig": {
"operations": ["+", "-"],
"minA": 8,
"minA": 6,
"maxA": 20,
"minB": 4,
"maxB": 10,
"minB": 3,
"maxB": 9,
"maxSum": 20,
"allowCarryOver": true,
"inputType": "DirectInput"
@@ -81,20 +81,63 @@
{
"id": 2,
"grade": 2,
"name": "Les porovnávania a rovností",
"description": "Porovnávanie čísel pomocou znakov <, >, = a dopĺňanie do desiatky.",
"name": "Svet 2: Les porovnávania (Do 100)",
"description": "Porovnávanie čísel pomocou znakov <, >, = v obore do 100.",
"background": "assets/images/backgrounds/world2_forest.jpg",
"levels": [
{
"id": "2-2-1",
"levelNumber": 1,
"name": "Múdra Sova",
"name": "Lesný Goblin",
"enemy": {
"id": "wise_owl",
"name": "Múdra Sova",
"maxHp": 30,
"id": "forest_goblin_comp",
"name": "Lesný Goblin",
"maxHp": 35,
"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": {
"operations": ["<", ">", "="],
@@ -104,7 +147,235 @@
"maxB": 100,
"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
},
{
"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,
"grade": 3,
"name": "Rieka malej násobilky (2, 3, 4, 5)",
"description": "Základy násobenia a delenia číslami 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 a 5.",
"background": "assets/images/backgrounds/world3_river.jpg",
"levels": [
{
@@ -11,11 +11,11 @@
"levelNumber": 1,
"name": "Riečny Škriatok",
"enemy": {
"id": "river_sprite",
"id": "river_sprite_goblin",
"name": "Riečny Škriatok",
"maxHp": 30,
"maxHp": 35,
"attackPower": 1,
"assetFolder": "assets/images/enemies/sprite"
"assetFolder": "assets/images/enemies/goblin"
},
"mathConfig": {
"operations": ["*"],
@@ -29,13 +29,34 @@
{
"id": "3-1-2",
"levelNumber": 2,
"name": "Vodník Násobka",
"name": "Vodný Slizík",
"enemy": {
"id": "water_demon",
"name": "Vodník Násobka",
"maxHp": 40,
"id": "water_slime_div",
"name": "Vodný Slizík",
"maxHp": 45,
"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": {
"operations": ["*", "/"],
@@ -45,22 +66,71 @@
"inputType": "DirectInput"
},
"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,
"name": "Jazerný Drak (Boss)",
"name": "Starý Lesný Golem (Boss)",
"isBoss": true,
"enemy": {
"id": "lake_dragon_boss",
"name": "Jazerný Drak",
"maxHp": 60,
"id": "ancient_forest_golem_boss",
"name": "Starý Lesný Golem",
"maxHp": 70,
"attackPower": 1,
"assetFolder": "assets/images/enemies/dragon"
"assetFolder": "assets/images/enemies/golem"
},
"mathConfig": {
"operations": ["*", "/"],
"factors": [2, 3, 4, 5],
"factors": [6, 7, 8, 9, 10],
"minMultiplier": 1,
"maxMultiplier": 10,
"inputType": "DirectInput"
@@ -68,5 +138,237 @@
"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="preload" id="webassembly" />
<link rel="stylesheet" href="css/app.css" />
<link href="CiselnaVyprava.styles.css" rel="stylesheet" />
<script type="importmap"></script>
</head>
@@ -38,8 +36,9 @@
<a href="." class="reload">Obnoviť hru</a>
<span class="dismiss">🗙</span>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>
</html>
+2 -8
View File
@@ -59,21 +59,15 @@
<mimeMap fileExtension=".wav" mimeType="audio/wav" />
</staticContent>
<!-- URL Rewrite for Single Page Application (SPA routing) -->
<!-- SPA URL Rewrite -->
<rewrite>
<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">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(_framework|assets|css|data)/" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>