AI human mode
This commit is contained in:
@@ -56,11 +56,30 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Nastavenia pravidiel -->
|
<!-- Nastavenia pravidiel (Collapsible - by default schované) -->
|
||||||
<div style="font-weight: 700; margin-bottom: 8px; color: var(--gold); font-size: 0.95rem;">
|
<div style="margin-bottom: 18px;">
|
||||||
PRAVIDLÁ A NASTAVENIA:
|
<div class="lobby-accordion-header @(ShowRules ? "open" : "")" @onclick="() => ShowRules = !ShowRules">
|
||||||
|
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
|
||||||
|
<span style="font-size: 1.15rem;">⚙️</span>
|
||||||
|
<span style="font-weight: 800; color: var(--gold); font-size: 0.95rem; letter-spacing: 0.5px;">
|
||||||
|
PRAVIDLÁ A NASTAVENIA HRY
|
||||||
|
</span>
|
||||||
|
@if (!ShowRules)
|
||||||
|
{
|
||||||
|
<span class="badge-rules-summary">
|
||||||
|
@(Rules.InitialCardCount) kariet • Sedmy (+3) • @(Rules.SpeedLevel)x Rýchlosť
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="rules-toggle-grid">
|
<div class="accordion-toggle-btn">
|
||||||
|
<span>@(ShowRules ? "Zbaliť" : "Rozbaliť")</span>
|
||||||
|
<span class="accordion-arrow">@(ShowRules ? "▲" : "▼")</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (ShowRules)
|
||||||
|
{
|
||||||
|
<div class="rules-toggle-grid" style="margin-top: 10px; animation: fadeIn 0.25s ease;">
|
||||||
<label class="rule-check-item">
|
<label class="rule-check-item">
|
||||||
<input type="checkbox" @bind="Rules.StackSevens" />
|
<input type="checkbox" @bind="Rules.StackSevens" />
|
||||||
<span>Kumulácia sediem (+3, +6, +9...)</span>
|
<span>Kumulácia sediem (+3, +6, +9...)</span>
|
||||||
@@ -106,6 +125,11 @@
|
|||||||
<span style="font-weight: 800; color: var(--gold);">🏅 Olympijský Faraón (Turnajový režim)</span>
|
<span style="font-weight: 800; color: var(--gold);">🏅 Olympijský Faraón (Turnajový režim)</span>
|
||||||
<span style="font-size: 0.8rem; color: #94a3b8; margin-left: 6px;">— porazený má v ďalšom kole o 1 kartu menej</span>
|
<span style="font-size: 0.8rem; color: #94a3b8; margin-left: 6px;">— porazený má v ďalšom kole o 1 kartu menej</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="rule-check-item" style="grid-column: 1 / -1; background: rgba(56, 189, 248, 0.12); padding: 8px 12px; border-radius: 10px; border: 1px solid rgba(56, 189, 248, 0.35);">
|
||||||
|
<input type="checkbox" @bind="Rules.AIHumanMode" />
|
||||||
|
<span style="font-weight: 800; color: #38bdf8;">🤖💭 AI Human Mode (Premýšľacie bubliny & hlášky)</span>
|
||||||
|
<span style="font-size: 0.8rem; color: #94a3b8; margin-left: 6px;">— boti hovoria svoje myšlienky, blufujú a reagujú na hru</span>
|
||||||
|
</label>
|
||||||
<div style="display: flex; align-items: center; gap: 8px; font-size: 0.9rem;">
|
<div style="display: flex; align-items: center; gap: 8px; font-size: 0.9rem;">
|
||||||
<span>Počiatočný počet kariet:</span>
|
<span>Počiatočný počet kariet:</span>
|
||||||
<select class="player-setup-select" style="width: auto; padding: 2px 8px;" @bind="Rules.InitialCardCount">
|
<select class="player-setup-select" style="width: auto; padding: 2px 8px;" @bind="Rules.InitialCardCount">
|
||||||
@@ -117,117 +141,75 @@
|
|||||||
<div style="display: flex; align-items: center; gap: 8px; font-size: 0.9rem;">
|
<div style="display: flex; align-items: center; gap: 8px; font-size: 0.9rem;">
|
||||||
<span>Rýchlosť hry:</span>
|
<span>Rýchlosť hry:</span>
|
||||||
<select class="player-setup-select" style="width: auto; padding: 2px 8px;" @bind="Rules.SpeedLevel">
|
<select class="player-setup-select" style="width: auto; padding: 2px 8px;" @bind="Rules.SpeedLevel">
|
||||||
<option value="1">🐢 0.5x (Pomalá)</option>
|
<option value="-5">🐢 -5 (0.2x Zen / Extra pomalá)</option>
|
||||||
<option value="2">🚶 0.75x (Pokojná)</option>
|
<option value="-4">🐢 -4 (0.35x Veľmi pomalá)</option>
|
||||||
<option value="3">⚡ 1x (Normálna - štandard)</option>
|
<option value="-3">🚶 -3 (0.5x Pomalá)</option>
|
||||||
<option value="4">🔥 1.5x (Rýchla)</option>
|
<option value="-2">🚶 -2 (0.65x Pokojná)</option>
|
||||||
<option value="5">🚀 2.5x (Turbo / Blesková)</option>
|
<option value="-1">🍃 -1 (0.8x Zvoľnená)</option>
|
||||||
|
<option value="0">⚡ 0 (1x Normálna / Vyvážená)</option>
|
||||||
|
<option value="1">🔥 +1 (1.25x Svižná)</option>
|
||||||
|
<option value="2">🔥 +2 (1.5x Rýchla)</option>
|
||||||
|
<option value="3">🚀 +3 (2x Veľmi rýchla)</option>
|
||||||
|
<option value="4">🚀 +4 (2.75x Turbo)</option>
|
||||||
|
<option value="5">⚡ +5 (4x Blesková)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Výber balíčka kariet (Deck Theme) -->
|
<!-- Výber balíčka kariet (Collapsible - by default iba zvolený deck) -->
|
||||||
<div style="margin-bottom: 24px;">
|
<div style="margin-bottom: 24px;">
|
||||||
<h3 style="font-size: 1.05rem; color: var(--gold); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 1px;">
|
<div class="lobby-accordion-header @(ShowDeckSelector ? "open" : "")" @onclick="() => ShowDeckSelector = !ShowDeckSelector">
|
||||||
🎨 Vizuálny štýl balíčka kariet
|
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
|
||||||
</h3>
|
<span style="font-size: 1.15rem;">🎨</span>
|
||||||
<div class="deck-selector-grid">
|
<span style="font-weight: 800; color: var(--gold); font-size: 0.95rem; letter-spacing: 0.5px;">
|
||||||
<div class="deck-option-card @(Rules.SelectedDeck == "original" ? "selected-deck" : "")"
|
VIZUÁLNY ŠTÝL BALÍČKA
|
||||||
@onclick='() => Rules.SelectedDeck = "original"'>
|
</span>
|
||||||
<div class="deck-preview-img-box">
|
|
||||||
<img src="assets/cards/original/Hearts_Seven.webp" class="deck-preview-thumb" alt="Original Mariáš" />
|
|
||||||
<img src="assets/cards/original/Card_Back.webp" class="deck-preview-thumb-back" alt="Rub" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="deck-info">
|
<div class="accordion-toggle-btn">
|
||||||
<div class="deck-name">Tradičný Mariáš 🎴</div>
|
<span>@(ShowDeckSelector ? "Zavrieť galériu" : "Zmeniť balíček")</span>
|
||||||
<div class="deck-desc">Autentický dvojhlavý Tell vzor (Piatnik)</div>
|
<span class="accordion-arrow">@(ShowDeckSelector ? "▲" : "▼")</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="deck-option-card @(Rules.SelectedDeck == "janosik" ? "selected-deck" : "")"
|
<!-- Aktuálne zvolený balíček (Default náhľad po kliknutí rozbalí výber) -->
|
||||||
@onclick='() => Rules.SelectedDeck = "janosik"'>
|
<div class="selected-deck-summary-card" @onclick="() => ShowDeckSelector = !ShowDeckSelector" title="Klikni pre výber a zmenu balíčka kariet">
|
||||||
<div class="deck-preview-img-box">
|
<div class="deck-preview-img-box">
|
||||||
<img src="assets/cards/janosik/Hearts_King.webp" class="deck-preview-thumb" alt="Zbojníci" />
|
<img src="@CurrentDeck.Thumb" class="deck-preview-thumb" alt="@CurrentDeck.Name" />
|
||||||
<img src="assets/cards/janosik/Card_Back.webp" class="deck-preview-thumb-back" alt="Rub" />
|
<img src="@CurrentDeck.Back" class="deck-preview-thumb-back" alt="Rub" />
|
||||||
</div>
|
</div>
|
||||||
<div class="deck-info">
|
<div class="deck-info" style="flex: 1;">
|
||||||
<div class="deck-name">Zbojníci & Legendy 🪓</div>
|
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
||||||
<div class="deck-desc">Jánošík, Uhorčík, výšivky a tatranský folklór</div>
|
<span class="deck-name" style="color: var(--gold); font-size: 0.98rem;">@CurrentDeck.Name</span>
|
||||||
|
<span class="badge-active-deck">Vybraný</span>
|
||||||
|
</div>
|
||||||
|
<div class="deck-desc">@CurrentDeck.Desc</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn-deck-change-action">
|
||||||
|
@(ShowDeckSelector ? "▲ Skryť" : "🎨 Zmeniť štýl ▾")
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="deck-option-card @(Rules.SelectedDeck == "spongebob" ? "selected-deck" : "")"
|
@if (ShowDeckSelector)
|
||||||
@onclick='() => Rules.SelectedDeck = "spongebob"'>
|
{
|
||||||
|
<div class="deck-selector-grid" style="margin-top: 12px; animation: fadeIn 0.25s ease;">
|
||||||
|
@foreach (var d in AvailableDecks)
|
||||||
|
{
|
||||||
|
<div class="deck-option-card @(Rules.SelectedDeck == d.Id ? "selected-deck" : "")"
|
||||||
|
@onclick="() => SelectDeck(d.Id)">
|
||||||
<div class="deck-preview-img-box">
|
<div class="deck-preview-img-box">
|
||||||
<img src="assets/cards/spongebob/Hearts_OverKnave.webp" class="deck-preview-thumb" alt="SpongeBob Cartoon" />
|
<img src="@d.Thumb" class="deck-preview-thumb" alt="@d.Name" />
|
||||||
<img src="assets/cards/spongebob/Card_Back.webp" class="deck-preview-thumb-back" alt="Rub" />
|
<img src="@d.Back" class="deck-preview-thumb-back" alt="Rub" />
|
||||||
</div>
|
</div>
|
||||||
<div class="deck-info">
|
<div class="deck-info">
|
||||||
<div class="deck-name">SpongeBob Cartoon 🧽</div>
|
<div class="deck-name">@d.Name</div>
|
||||||
<div class="deck-desc">Bikini Bottom edícia (postavičky a veľké symboly)</div>
|
<div class="deck-desc">@d.Desc</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="deck-option-card @(Rules.SelectedDeck == "cyberpunk" ? "selected-deck" : "")"
|
|
||||||
@onclick='() => Rules.SelectedDeck = "cyberpunk"'>
|
|
||||||
<div class="deck-preview-img-box">
|
|
||||||
<img src="assets/cards/cyberpunk/Leaves_King.webp" class="deck-preview-thumb" alt="Cyberpunk 2077" />
|
|
||||||
<img src="assets/cards/cyberpunk/Card_Back.webp" class="deck-preview-thumb-back" alt="Rub" />
|
|
||||||
</div>
|
|
||||||
<div class="deck-info">
|
|
||||||
<div class="deck-name">Cyberpunk 2077 ⚡</div>
|
|
||||||
<div class="deck-desc">Neónový holografický štýl a kybernetickí hrdinovia</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="deck-option-card @(Rules.SelectedDeck == "fantasy" ? "selected-deck" : "")"
|
|
||||||
@onclick='() => Rules.SelectedDeck = "fantasy"'>
|
|
||||||
<div class="deck-preview-img-box">
|
|
||||||
<img src="assets/cards/fantasy/Acorns_OverKnave.webp" class="deck-preview-thumb" alt="Fantasy RPG" />
|
|
||||||
<img src="assets/cards/fantasy/Card_Back.webp" class="deck-preview-thumb-back" alt="Rub" />
|
|
||||||
</div>
|
|
||||||
<div class="deck-info">
|
|
||||||
<div class="deck-name">Fantasy RPG 🐉</div>
|
|
||||||
<div class="deck-desc">Dračí lordi, mágovia, runy a rytieri</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="deck-option-card @(Rules.SelectedDeck == "egypt" ? "selected-deck" : "")"
|
|
||||||
@onclick='() => Rules.SelectedDeck = "egypt"'>
|
|
||||||
<div class="deck-preview-img-box">
|
|
||||||
<img src="assets/cards/egypt/Bells_King.webp" class="deck-preview-thumb" alt="Staroveký Egypt" />
|
|
||||||
<img src="assets/cards/egypt/Card_Back.webp" class="deck-preview-thumb-back" alt="Rub" />
|
|
||||||
</div>
|
|
||||||
<div class="deck-info">
|
|
||||||
<div class="deck-name">Staroveký Egypt ⚔️</div>
|
|
||||||
<div class="deck-desc">Zlatý papyrus, Tutanchamón, Anubis a pyramídy</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="deck-option-card @(Rules.SelectedDeck == "pets" ? "selected-deck" : "")"
|
|
||||||
@onclick='() => Rules.SelectedDeck = "pets"'>
|
|
||||||
<div class="deck-preview-img-box">
|
|
||||||
<img src="assets/cards/pets/Hearts_OverKnave.webp" class="deck-preview-thumb" alt="Kráľovské zvieratká" />
|
|
||||||
<img src="assets/cards/pets/Card_Back.webp" class="deck-preview-thumb-back" alt="Rub" />
|
|
||||||
</div>
|
|
||||||
<div class="deck-info">
|
|
||||||
<div class="deck-name">Kráľovské Zvieratká 🐱</div>
|
|
||||||
<div class="deck-desc">Mačičky a psíkovia v kráľovských korunkách</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="deck-option-card @(Rules.SelectedDeck == "pixel" ? "selected-deck" : "")"
|
|
||||||
@onclick='() => Rules.SelectedDeck = "pixel"'>
|
|
||||||
<div class="deck-preview-img-box">
|
|
||||||
<img src="assets/cards/pixel/Leaves_Seven.webp" class="deck-preview-thumb" alt="Retro Pixel" />
|
|
||||||
<img src="assets/cards/pixel/Card_Back.webp" class="deck-preview-thumb-back" alt="Rub" />
|
|
||||||
</div>
|
|
||||||
<div class="deck-info">
|
|
||||||
<div class="deck-name">Retro 8-Bit Pixel 🕹️</div>
|
|
||||||
<div class="deck-desc">Nostalgický arkádový štýl klasických hier</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Štart tlačidlo -->
|
<!-- Štart tlačidlo -->
|
||||||
@@ -244,6 +226,29 @@
|
|||||||
|
|
||||||
private int SelectedPlayerCount = 4;
|
private int SelectedPlayerCount = 4;
|
||||||
private RuleOptions Rules = new();
|
private RuleOptions Rules = new();
|
||||||
|
private bool ShowRules = false;
|
||||||
|
private bool ShowDeckSelector = false;
|
||||||
|
|
||||||
|
private record DeckInfo(string Id, string Name, string Desc, string Thumb, string Back);
|
||||||
|
|
||||||
|
private readonly DeckInfo[] AvailableDecks = new[]
|
||||||
|
{
|
||||||
|
new DeckInfo("original", "Tradičný Mariáš 🎴", "Autentický dvojhlavý Tell vzor (Piatnik)", "assets/cards/original/Hearts_Seven.webp", "assets/cards/original/Card_Back.webp"),
|
||||||
|
new DeckInfo("janosik", "Zbojníci & Legendy 🪓", "Jánošík, Uhorčík, výšivky a tatranský folklór", "assets/cards/janosik/Hearts_King.webp", "assets/cards/janosik/Card_Back.webp"),
|
||||||
|
new DeckInfo("spongebob", "SpongeBob Cartoon 🧽", "Bikini Bottom edícia (postavičky a veľké symboly)", "assets/cards/spongebob/Hearts_OverKnave.webp", "assets/cards/spongebob/Card_Back.webp"),
|
||||||
|
new DeckInfo("cyberpunk", "Cyberpunk 2077 ⚡", "Neónový holografický štýl a kybernetickí hrdinovia", "assets/cards/cyberpunk/Leaves_King.webp", "assets/cards/cyberpunk/Card_Back.webp"),
|
||||||
|
new DeckInfo("fantasy", "Fantasy RPG 🐉", "Dračí lordi, mágovia, runy a rytieri", "assets/cards/fantasy/Acorns_OverKnave.webp", "assets/cards/fantasy/Card_Back.webp"),
|
||||||
|
new DeckInfo("egypt", "Staroveký Egypt ⚔️", "Zlatý papyrus, Tutanchamón, Anubis a pyramídy", "assets/cards/egypt/Bells_King.webp", "assets/cards/egypt/Card_Back.webp"),
|
||||||
|
new DeckInfo("pets", "Kráľovské Zvieratká 🐱", "Mačičky a psíkovia v kráľovských korunkách", "assets/cards/pets/Hearts_OverKnave.webp", "assets/cards/pets/Card_Back.webp"),
|
||||||
|
new DeckInfo("pixel", "Retro 8-Bit Pixel 🕹️", "Nostalgický arkádový štýl klasických hier", "assets/cards/pixel/Leaves_Seven.webp", "assets/cards/pixel/Card_Back.webp")
|
||||||
|
};
|
||||||
|
|
||||||
|
private DeckInfo CurrentDeck => AvailableDecks.FirstOrDefault(d => d.Id == Rules.SelectedDeck) ?? AvailableDecks[0];
|
||||||
|
|
||||||
|
private void SelectDeck(string deckId)
|
||||||
|
{
|
||||||
|
Rules.SelectedDeck = deckId;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly string[] Avatars = ["👑", "🧙♂️", "🦊", "🦁", "🐯", "🤖", "🐉", "🥷", "👸", "🤠", "🦅", "🐺"];
|
private readonly string[] Avatars = ["👑", "🧙♂️", "🦊", "🦁", "🐯", "🤖", "🐉", "🥷", "👸", "🤠", "🦅", "🐺"];
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,14 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(ThoughtBubble))
|
||||||
|
{
|
||||||
|
<div class="ai-thought-bubble @PositionClass @(IsThoughtFadingOut ? "fading-out" : "")">
|
||||||
|
<span class="thought-bubble-prefix">💭</span>
|
||||||
|
<span class="thought-bubble-text">@ThoughtBubble</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mini karty pre vizuálny odpočet kariet v ruke -->
|
<!-- Mini karty pre vizuálny odpočet kariet v ruke -->
|
||||||
@@ -96,6 +104,8 @@
|
|||||||
[Parameter] public bool CanBeRevived { get; set; } = false;
|
[Parameter] public bool CanBeRevived { get; set; } = false;
|
||||||
[Parameter] public int ReviveRoundsRemaining { get; set; } = 0;
|
[Parameter] public int ReviveRoundsRemaining { get; set; } = 0;
|
||||||
[Parameter] public int ReviveTurnsRemaining { get; set; } = 0;
|
[Parameter] public int ReviveTurnsRemaining { get; set; } = 0;
|
||||||
|
[Parameter] public string? ThoughtBubble { get; set; }
|
||||||
|
[Parameter] public bool IsThoughtFadingOut { get; set; } = false;
|
||||||
[Parameter] public bool IsOlympicMode { get; set; } = false;
|
[Parameter] public bool IsOlympicMode { get; set; } = false;
|
||||||
[Parameter] public string PositionClass { get; set; } = "slot-top";
|
[Parameter] public string PositionClass { get; set; } = "slot-top";
|
||||||
}
|
}
|
||||||
|
|||||||
+310
-37
@@ -1,4 +1,5 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
|
@using Faraon.Core.AI
|
||||||
@inject FxService Fx
|
@inject FxService Fx
|
||||||
@inject IFaraonAI AI
|
@inject IFaraonAI AI
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@@ -24,30 +25,46 @@ else
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="turn-header-info">
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
Ťah #@Engine.State.TurnCount • Na rade:
|
<!-- Rýchlosť hry -->
|
||||||
<span class="active-turn-header-player">
|
<button class="btn-speed-control" @onclick="CycleSpeed" title="Zmeniť rýchlosť hry">
|
||||||
@Engine.State.CurrentPlayer.Avatar @Engine.State.CurrentPlayer.Name
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="game-controls">
|
|
||||||
<!-- Rýchly prepínač rýchlosti hry (5 úrovní) -->
|
|
||||||
<button class="btn-game-icon btn-speed-control" title="Rýchlosť hry: @GetSpeedDescription(Engine.State.Rules.SpeedLevel) (Klikni pre zmenu)" @onclick="CycleSpeed">
|
|
||||||
@GetSpeedButtonText(Engine.State.Rules.SpeedLevel)
|
@GetSpeedButtonText(Engine.State.Rules.SpeedLevel)
|
||||||
</button>
|
</button>
|
||||||
<!-- Rýchly prepínač balíčka kariet -->
|
|
||||||
<button class="btn-game-icon" title="Zmeniť balíček kariet (@Engine.State.Rules.SelectedDeck)" @onclick="ToggleDeck">
|
<!-- Zvuk -->
|
||||||
@GetDeckIcon(Engine.State.Rules.SelectedDeck)
|
<button class="btn-header-action" @onclick="ToggleMute" title="@(IsMuted ? "Zapnúť zvuk" : "Stlmiť zvuk")">
|
||||||
</button>
|
|
||||||
<button class="btn-game-icon" title="Stlmiť / Zapnúť zvuk" @onclick="ToggleMute">
|
|
||||||
@(IsMuted ? "🔇" : "🔊")
|
@(IsMuted ? "🔇" : "🔊")
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-game-icon" title="Návrat do Lobby" @onclick="ReturnToLobby">
|
|
||||||
🏠
|
<!-- Nová hra -->
|
||||||
|
<button class="btn-header-action" @onclick="ReturnToLobby" title="Späť do menu">
|
||||||
|
⚙️ Menu
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- Špeciálny banner kartového vtipu (AI Human Mode) -->
|
||||||
|
@if (CurrentJoke != null)
|
||||||
|
{
|
||||||
|
<div class="ai-joke-banner @(CurrentJoke.IsFadingOut ? "fading-out" : "")">
|
||||||
|
<div class="joke-header">
|
||||||
|
<div class="joke-header-badge">
|
||||||
|
<span>@CurrentJoke.Joke.Icon</span>
|
||||||
|
<span>🎭 @CurrentJoke.Joke.Category</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 10px;">
|
||||||
|
<span class="joke-storyteller">
|
||||||
|
<span>@CurrentJoke.Storyteller.Avatar</span>
|
||||||
|
<span>@CurrentJoke.Storyteller.Name</span>
|
||||||
|
</span>
|
||||||
|
<button class="joke-close-btn" @onclick="DismissJoke" title="Zavrieť vtip">✕</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="joke-setup">@CurrentJoke.Joke.Setup</div>
|
||||||
|
<div class="joke-punchline">@CurrentJoke.Joke.Punchline</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<!-- Herný stôl -->
|
<!-- Herný stôl -->
|
||||||
<div class="poker-table-surface">
|
<div class="poker-table-surface">
|
||||||
<!-- Súperi na stole -->
|
<!-- Súperi na stole -->
|
||||||
@@ -55,12 +72,15 @@ else
|
|||||||
{
|
{
|
||||||
var isOppFrozen = Engine.State.PendingSkip && Engine.State.CurrentPlayer == opp.Player;
|
var isOppFrozen = Engine.State.PendingSkip && Engine.State.CurrentPlayer == opp.Player;
|
||||||
var canOppBeRevived = Engine.State.Rules.RedSevenRevives && Engine.State.LastFinishedPlayer == opp.Player && Engine.State.ReviveTurnsRemaining > 0;
|
var canOppBeRevived = Engine.State.Rules.RedSevenRevives && Engine.State.LastFinishedPlayer == opp.Player && Engine.State.ReviveTurnsRemaining > 0;
|
||||||
|
var thought = GetThoughtState(opp.Player);
|
||||||
<PlayerSlot Player="@opp.Player"
|
<PlayerSlot Player="@opp.Player"
|
||||||
IsCurrentTurn="@(Engine.State.CurrentPlayer == opp.Player)"
|
IsCurrentTurn="@(Engine.State.CurrentPlayer == opp.Player)"
|
||||||
IsFrozen="@isOppFrozen"
|
IsFrozen="@isOppFrozen"
|
||||||
CanBeRevived="@canOppBeRevived"
|
CanBeRevived="@canOppBeRevived"
|
||||||
ReviveRoundsRemaining="@Engine.State.ReviveRoundsRemaining"
|
ReviveRoundsRemaining="@Engine.State.ReviveRoundsRemaining"
|
||||||
ReviveTurnsRemaining="@Engine.State.ReviveTurnsRemaining"
|
ReviveTurnsRemaining="@Engine.State.ReviveTurnsRemaining"
|
||||||
|
ThoughtBubble="@thought?.Text"
|
||||||
|
IsThoughtFadingOut="@(thought?.IsFadingOut ?? false)"
|
||||||
IsOlympicMode="@Engine.State.Rules.OlympicMode"
|
IsOlympicMode="@Engine.State.Rules.OlympicMode"
|
||||||
PositionClass="@opp.PositionClass" />
|
PositionClass="@opp.PositionClass" />
|
||||||
}
|
}
|
||||||
@@ -193,6 +213,17 @@ else
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@{
|
||||||
|
var bottomThought = GetThoughtState(ViewingPlayer);
|
||||||
|
}
|
||||||
|
@if (bottomThought != null && !string.IsNullOrEmpty(bottomThought.Text))
|
||||||
|
{
|
||||||
|
<div class="ai-thought-bubble slot-bottom @(bottomThought.IsFadingOut ? "fading-out" : "")">
|
||||||
|
<span class="thought-bubble-prefix">💭</span>
|
||||||
|
<span class="thought-bubble-text">@bottomThought.Text</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Interaktívna ruka hráča -->
|
<!-- Interaktívna ruka hráča -->
|
||||||
@@ -416,6 +447,34 @@ else
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Engine != null && Engine.State.Rules.AIHumanMode)
|
||||||
|
{
|
||||||
|
// Kartový vtip pri významných udalostiach (veľa sediem, spálenie, oživenie)
|
||||||
|
var joke = AIDialogueService.GetContextualJoke(e.Type, e.ExtraData, Engine.State.TurnCount);
|
||||||
|
if (joke != null)
|
||||||
|
{
|
||||||
|
var storyteller = Engine.State.Players.FirstOrDefault(p => p.Type == PlayerType.AI) ?? e.SourcePlayer ?? Engine.State.CurrentPlayer;
|
||||||
|
TriggerCardJoke(joke, storyteller);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.SourcePlayer != null && e.SourcePlayer.Type == PlayerType.AI)
|
||||||
|
{
|
||||||
|
var reaction = AIDialogueService.GetEventReaction(e, e.SourcePlayer);
|
||||||
|
if (!string.IsNullOrEmpty(reaction))
|
||||||
|
{
|
||||||
|
SetThoughtBubble(e.SourcePlayer, reaction, 2800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.TargetPlayer != null && e.TargetPlayer.Type == PlayerType.AI && e.TargetPlayer != e.SourcePlayer)
|
||||||
|
{
|
||||||
|
var reaction = AIDialogueService.GetEventReaction(e, e.TargetPlayer);
|
||||||
|
if (!string.IsNullOrEmpty(reaction))
|
||||||
|
{
|
||||||
|
SetThoughtBubble(e.TargetPlayer, reaction, 2800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Engine != null && Engine.State.CurrentPlayer.Hand.Count == 1 && !Engine.State.CurrentPlayer.HasFinished)
|
if (Engine != null && Engine.State.CurrentPlayer.Hand.Count == 1 && !Engine.State.CurrentPlayer.HasFinished)
|
||||||
{
|
{
|
||||||
await Fx.PlayHeartbeatAsync();
|
await Fx.PlayHeartbeatAsync();
|
||||||
@@ -457,17 +516,25 @@ else
|
|||||||
|
|
||||||
private int MultiCardDelayMs => Engine?.State.Rules.SpeedLevel switch
|
private int MultiCardDelayMs => Engine?.State.Rules.SpeedLevel switch
|
||||||
{
|
{
|
||||||
1 => 130,
|
-5 => 350,
|
||||||
2 => 105,
|
-4 => 280,
|
||||||
4 => 50,
|
-3 => 220,
|
||||||
5 => 25,
|
-2 => 180,
|
||||||
_ => 80
|
-1 => 140,
|
||||||
|
0 => 110,
|
||||||
|
1 => 85,
|
||||||
|
2 => 65,
|
||||||
|
3 => 45,
|
||||||
|
4 => 30,
|
||||||
|
5 => 15,
|
||||||
|
_ => 110
|
||||||
};
|
};
|
||||||
|
|
||||||
private async Task CycleSpeed()
|
private async Task CycleSpeed()
|
||||||
{
|
{
|
||||||
if (Engine == null) return;
|
if (Engine == null) return;
|
||||||
int next = (Engine.State.Rules.SpeedLevel % 5) + 1;
|
int current = Engine.State.Rules.SpeedLevel;
|
||||||
|
int next = current >= 5 ? -5 : current + 1;
|
||||||
Engine.State.Rules.SpeedLevel = next;
|
Engine.State.Rules.SpeedLevel = next;
|
||||||
await Fx.SetSpeedLevelAsync(next);
|
await Fx.SetSpeedLevelAsync(next);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -475,22 +542,34 @@ else
|
|||||||
|
|
||||||
private string GetSpeedButtonText(int level) => level switch
|
private string GetSpeedButtonText(int level) => level switch
|
||||||
{
|
{
|
||||||
1 => "🐢 0.5x",
|
-5 => "🐢 -5 (0.2x)",
|
||||||
2 => "🚶 0.75x",
|
-4 => "🐢 -4 (0.35x)",
|
||||||
3 => "⚡ 1x",
|
-3 => "🚶 -3 (0.5x)",
|
||||||
4 => "🔥 1.5x",
|
-2 => "🚶 -2 (0.65x)",
|
||||||
5 => "🚀 2.5x",
|
-1 => "🍃 -1 (0.8x)",
|
||||||
_ => "⚡ 1x"
|
0 => "⚡ 0 (1x)",
|
||||||
|
1 => "🔥 +1 (1.25x)",
|
||||||
|
2 => "🔥 +2 (1.5x)",
|
||||||
|
3 => "🚀 +3 (2x)",
|
||||||
|
4 => "🚀 +4 (2.75x)",
|
||||||
|
5 => "⚡ +5 (4x)",
|
||||||
|
_ => "⚡ 0 (1x)"
|
||||||
};
|
};
|
||||||
|
|
||||||
private string GetSpeedDescription(int level) => level switch
|
private string GetSpeedDescription(int level) => level switch
|
||||||
{
|
{
|
||||||
1 => "0.5x (Pomalá)",
|
-5 => "-5 (0.2x Zen / Extra pomalá)",
|
||||||
2 => "0.75x (Pokojná)",
|
-4 => "-4 (0.35x Veľmi pomalá)",
|
||||||
3 => "1x (Normálna - stredná)",
|
-3 => "-3 (0.5x Pomalá)",
|
||||||
4 => "1.5x (Rýchla)",
|
-2 => "-2 (0.65x Pokojná)",
|
||||||
5 => "2.5x (Turbo / Blesková)",
|
-1 => "-1 (0.8x Zvoľnená)",
|
||||||
_ => "1x (Normálna)"
|
0 => "0 (1x Normálna / Vyvážená)",
|
||||||
|
1 => "+1 (1.25x Svižná)",
|
||||||
|
2 => "+2 (1.5x Rýchla)",
|
||||||
|
3 => "+3 (2x Veľmi rýchla)",
|
||||||
|
4 => "+4 (2.75x Turbo)",
|
||||||
|
5 => "+5 (4x Blesková)",
|
||||||
|
_ => "0 (1x Normálna)"
|
||||||
};
|
};
|
||||||
|
|
||||||
private void ToggleDeck()
|
private void ToggleDeck()
|
||||||
@@ -647,6 +726,15 @@ else
|
|||||||
|
|
||||||
if (Engine.State.TurnState == TurnState.Playing && Engine.State.CurrentPlayer.Type == PlayerType.AI)
|
if (Engine.State.TurnState == TurnState.Playing && Engine.State.CurrentPlayer.Type == PlayerType.AI)
|
||||||
{
|
{
|
||||||
|
if (Engine.State.Rules.AIHumanMode && Engine.State.TurnCount >= 28 && Engine.State.TurnCount % 16 == 0 && CurrentJoke == null)
|
||||||
|
{
|
||||||
|
var longJoke = AIDialogueService.GetContextualJoke(null, 0, Engine.State.TurnCount);
|
||||||
|
if (longJoke != null)
|
||||||
|
{
|
||||||
|
TriggerCardJoke(longJoke, Engine.State.CurrentPlayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_aiCts?.Cancel();
|
_aiCts?.Cancel();
|
||||||
_aiCts = new System.Threading.CancellationTokenSource();
|
_aiCts = new System.Threading.CancellationTokenSource();
|
||||||
var token = _aiCts.Token;
|
var token = _aiCts.Token;
|
||||||
@@ -655,12 +743,63 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(Engine.State.Rules.AIDelayMs, token);
|
|
||||||
if (token.IsCancellationRequested || Engine == null) return;
|
|
||||||
|
|
||||||
var aiPlayer = Engine.State.CurrentPlayer;
|
var aiPlayer = Engine.State.CurrentPlayer;
|
||||||
if (aiPlayer.Type != PlayerType.AI) return;
|
if (aiPlayer.Type != PlayerType.AI) return;
|
||||||
|
|
||||||
|
if (Engine.State.Rules.AIHumanMode)
|
||||||
|
{
|
||||||
|
string thought = AIDialogueService.GetThinkingDialogue(aiPlayer, Engine.State);
|
||||||
|
int charDelay = Engine.State.Rules.SpeedLevel switch
|
||||||
|
{
|
||||||
|
-5 => 55,
|
||||||
|
-4 => 45,
|
||||||
|
-3 => 38,
|
||||||
|
-2 => 30,
|
||||||
|
-1 => 24,
|
||||||
|
0 => 18,
|
||||||
|
1 => 14,
|
||||||
|
2 => 10,
|
||||||
|
3 => 7,
|
||||||
|
4 => 4,
|
||||||
|
5 => 2,
|
||||||
|
_ => 18
|
||||||
|
};
|
||||||
|
|
||||||
|
// Postupné písanie myšlienky po písmenkách (Typewriter effect)
|
||||||
|
for (int i = 1; i <= thought.Length; i++)
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested || Engine == null) return;
|
||||||
|
string partial = thought.Substring(0, i) + (i < thought.Length ? " ▌" : "");
|
||||||
|
await InvokeAsync(() => SetThoughtBubble(aiPlayer, partial, 5000));
|
||||||
|
await Task.Delay(charDelay, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dokončený text a krátka prirodzená pauza na prečítanie
|
||||||
|
await InvokeAsync(() => SetThoughtBubble(aiPlayer, thought, 4000));
|
||||||
|
int readPause = Engine.State.Rules.SpeedLevel switch
|
||||||
|
{
|
||||||
|
-5 => 1200,
|
||||||
|
-4 => 1000,
|
||||||
|
-3 => 800,
|
||||||
|
-2 => 650,
|
||||||
|
-1 => 500,
|
||||||
|
0 => 400,
|
||||||
|
1 => 300,
|
||||||
|
2 => 220,
|
||||||
|
3 => 150,
|
||||||
|
4 => 90,
|
||||||
|
5 => 40,
|
||||||
|
_ => 400
|
||||||
|
};
|
||||||
|
await Task.Delay(readPause, token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(Engine.State.Rules.AIDelayMs, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.IsCancellationRequested || Engine == null) return;
|
||||||
|
|
||||||
var decision = AI.MakeDecision(Engine.State, aiPlayer);
|
var decision = AI.MakeDecision(Engine.State, aiPlayer);
|
||||||
|
|
||||||
await InvokeAsync(async () =>
|
await InvokeAsync(async () =>
|
||||||
@@ -719,6 +858,12 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (Engine.State.Rules.AIHumanMode && Engine.State.PendingDrawCount == 0 && !Engine.State.PendingSkip)
|
||||||
|
{
|
||||||
|
var drawThought = AIDialogueService.GetMustDrawDialogue(aiPlayer);
|
||||||
|
SetThoughtBubble(aiPlayer, drawThought, 1800);
|
||||||
|
}
|
||||||
|
|
||||||
if (Engine.State.PendingDrawCount > 0)
|
if (Engine.State.PendingDrawCount > 0)
|
||||||
{
|
{
|
||||||
int drawCount = Engine.State.PendingDrawCount;
|
int drawCount = Engine.State.PendingDrawCount;
|
||||||
@@ -752,6 +897,71 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class PlayerThoughtInfo
|
||||||
|
{
|
||||||
|
public string Text { get; set; } = "";
|
||||||
|
public DateTime FadeOutAt { get; set; }
|
||||||
|
public DateTime Expiry { get; set; }
|
||||||
|
public bool IsFadingOut => DateTime.UtcNow >= FadeOutAt && DateTime.UtcNow <= Expiry;
|
||||||
|
public bool IsExpired => DateTime.UtcNow > Expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<Player, PlayerThoughtInfo> PlayerThoughts = new();
|
||||||
|
|
||||||
|
private PlayerThoughtInfo? GetThoughtState(Player? player)
|
||||||
|
{
|
||||||
|
if (player == null || Engine == null || !Engine.State.Rules.AIHumanMode) return null;
|
||||||
|
if (PlayerThoughts.TryGetValue(player, out var info))
|
||||||
|
{
|
||||||
|
if (!info.IsExpired)
|
||||||
|
{
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetThoughtBubble(Player player, string text, int durationMs = 3200, int fadeDurationMs = 600)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text)) return;
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var fadeAt = now.AddMilliseconds(Math.Max(200, durationMs - fadeDurationMs));
|
||||||
|
var expireAt = now.AddMilliseconds(durationMs);
|
||||||
|
|
||||||
|
var info = new PlayerThoughtInfo
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
FadeOutAt = fadeAt,
|
||||||
|
Expiry = expireAt
|
||||||
|
};
|
||||||
|
PlayerThoughts[player] = info;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
int delayToFade = (int)(fadeAt - DateTime.UtcNow).TotalMilliseconds;
|
||||||
|
if (delayToFade > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(delayToFade);
|
||||||
|
if (PlayerThoughts.TryGetValue(player, out var cur) && cur == info)
|
||||||
|
{
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int delayToExpire = (int)(expireAt - DateTime.UtcNow).TotalMilliseconds;
|
||||||
|
if (delayToExpire > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(delayToExpire);
|
||||||
|
if (PlayerThoughts.TryGetValue(player, out var cur) && cur == info)
|
||||||
|
{
|
||||||
|
PlayerThoughts.Remove(player);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<(Player Player, string PositionClass)> GetOpponents()
|
private IEnumerable<(Player Player, string PositionClass)> GetOpponents()
|
||||||
{
|
{
|
||||||
if (Engine == null) return Enumerable.Empty<(Player, string)>();
|
if (Engine == null) return Enumerable.Empty<(Player, string)>();
|
||||||
@@ -778,6 +988,65 @@ else
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ActiveJokeInfo
|
||||||
|
{
|
||||||
|
public AIDialogueService.CardJoke Joke { get; set; } = default!;
|
||||||
|
public Player Storyteller { get; set; } = default!;
|
||||||
|
public DateTime FadeOutAt { get; set; }
|
||||||
|
public DateTime Expiry { get; set; }
|
||||||
|
public bool IsFadingOut => DateTime.UtcNow >= FadeOutAt && DateTime.UtcNow <= Expiry;
|
||||||
|
public bool IsExpired => DateTime.UtcNow > Expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActiveJokeInfo? CurrentJoke;
|
||||||
|
|
||||||
|
private void TriggerCardJoke(AIDialogueService.CardJoke joke, Player storyteller, int durationMs = 7000, int fadeDurationMs = 700)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var fadeAt = now.AddMilliseconds(Math.Max(200, durationMs - fadeDurationMs));
|
||||||
|
var expireAt = now.AddMilliseconds(durationMs);
|
||||||
|
|
||||||
|
var info = new ActiveJokeInfo
|
||||||
|
{
|
||||||
|
Joke = joke,
|
||||||
|
Storyteller = storyteller,
|
||||||
|
FadeOutAt = fadeAt,
|
||||||
|
Expiry = expireAt
|
||||||
|
};
|
||||||
|
CurrentJoke = info;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
int delayToFade = (int)(fadeAt - DateTime.UtcNow).TotalMilliseconds;
|
||||||
|
if (delayToFade > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(delayToFade);
|
||||||
|
if (CurrentJoke == info)
|
||||||
|
{
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int delayToExpire = (int)(expireAt - DateTime.UtcNow).TotalMilliseconds;
|
||||||
|
if (delayToExpire > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(delayToExpire);
|
||||||
|
if (CurrentJoke == info)
|
||||||
|
{
|
||||||
|
CurrentJoke = null;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DismissJoke()
|
||||||
|
{
|
||||||
|
CurrentJoke = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ToggleMute()
|
private async Task ToggleMute()
|
||||||
{
|
{
|
||||||
IsMuted = await Fx.ToggleMuteAsync();
|
IsMuted = await Fx.ToggleMuteAsync();
|
||||||
@@ -785,12 +1054,16 @@ else
|
|||||||
|
|
||||||
private void HandlePlayAgain()
|
private void HandlePlayAgain()
|
||||||
{
|
{
|
||||||
|
PlayerThoughts.Clear();
|
||||||
|
CurrentJoke = null;
|
||||||
StartNewGame();
|
StartNewGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReturnToLobby()
|
private void ReturnToLobby()
|
||||||
{
|
{
|
||||||
_aiCts?.Cancel();
|
_aiCts?.Cancel();
|
||||||
|
PlayerThoughts.Clear();
|
||||||
|
CurrentJoke = null;
|
||||||
IsInLobby = true;
|
IsInLobby = true;
|
||||||
Engine = null;
|
Engine = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -310,43 +310,45 @@ body, html {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: rgba(10, 25, 18, 0.85);
|
background: rgba(10, 25, 18, 0.88);
|
||||||
border: 2px solid rgba(255, 255, 255, 0.15);
|
border: 2.5px solid rgba(255, 255, 255, 0.2);
|
||||||
border-radius: 16px;
|
border-radius: 20px;
|
||||||
padding: 6px 12px;
|
padding: 10px 18px;
|
||||||
backdrop-filter: blur(8px);
|
min-width: 110px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-badge.active-turn {
|
.avatar-badge.active-turn {
|
||||||
border-color: var(--gold);
|
border-color: var(--gold);
|
||||||
box-shadow: 0 0 25px rgba(245, 176, 65, 0.9), inset 0 0 12px rgba(245, 176, 65, 0.35);
|
box-shadow: 0 0 30px rgba(245, 176, 65, 0.95), inset 0 0 16px rgba(245, 176, 65, 0.4);
|
||||||
transform: scale(1.09);
|
transform: scale(1.12);
|
||||||
animation: turn-pulse 1.8s infinite;
|
animation: turn-pulse 1.8s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes turn-pulse {
|
@keyframes turn-pulse {
|
||||||
0%, 100% { box-shadow: 0 0 16px rgba(245, 176, 65, 0.7); }
|
0%, 100% { box-shadow: 0 0 20px rgba(245, 176, 65, 0.75); }
|
||||||
50% { box-shadow: 0 0 30px rgba(245, 176, 65, 1), 0 0 45px rgba(245, 176, 65, 0.55); }
|
50% { box-shadow: 0 0 38px rgba(245, 176, 65, 1), 0 0 55px rgba(245, 176, 65, 0.6); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TURN INDICATOR BADGE (NA ŤAHU) */
|
/* TURN INDICATOR BADGE (NA ŤAHU) */
|
||||||
.turn-indicator-badge {
|
.turn-indicator-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -14px;
|
top: -16px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 6px;
|
||||||
background: linear-gradient(135deg, #ffd700, #f5b041);
|
background: linear-gradient(135deg, #ffd700, #f5b041);
|
||||||
color: #11281d;
|
color: #11281d;
|
||||||
font-size: 0.66rem;
|
font-size: 0.74rem;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
letter-spacing: 0.8px;
|
letter-spacing: 0.9px;
|
||||||
padding: 2px 9px;
|
padding: 3px 12px;
|
||||||
border-radius: 12px;
|
border-radius: 14px;
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.6), 0 0 14px rgba(255, 215, 0, 0.8);
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.6), 0 0 16px rgba(255, 215, 0, 0.85);
|
||||||
border: 1.5px solid #ffffff;
|
border: 1.5px solid #ffffff;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
@@ -404,25 +406,28 @@ body, html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.avatar-icon {
|
.avatar-icon {
|
||||||
font-size: 1.8rem;
|
font-size: 2.5rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-name {
|
.avatar-name {
|
||||||
font-size: 0.85rem;
|
font-size: 1.05rem;
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
margin-top: 2px;
|
margin-top: 4px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
color: #ffffff;
|
||||||
|
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-card-count {
|
.avatar-card-count {
|
||||||
font-size: 0.75rem;
|
font-size: 0.86rem;
|
||||||
background: rgba(245, 176, 65, 0.2);
|
background: rgba(245, 176, 65, 0.22);
|
||||||
color: var(--gold);
|
color: var(--gold);
|
||||||
padding: 2px 8px;
|
padding: 3px 12px;
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
margin-top: 2px;
|
margin-top: 4px;
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
|
border: 1px solid rgba(245, 176, 65, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FROZEN / ICE STATUS STYLES */
|
/* FROZEN / ICE STATUS STYLES */
|
||||||
@@ -671,18 +676,18 @@ body, html {
|
|||||||
/* OPPONENT MINI CARDS */
|
/* OPPONENT MINI CARDS */
|
||||||
.opponent-mini-cards {
|
.opponent-mini-cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 6px;
|
margin-top: 8px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mini-card-back {
|
.mini-card-back {
|
||||||
width: 22px;
|
width: 26px;
|
||||||
height: 32px;
|
height: 38px;
|
||||||
background: linear-gradient(135deg, #8b1e1e, #4a0d0d);
|
background: linear-gradient(135deg, #8b1e1e, #4a0d0d);
|
||||||
border: 1px solid #ffd700;
|
border: 1px solid #ffd700;
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
margin-left: -12px;
|
margin-left: -14px;
|
||||||
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
|
box-shadow: 1px 2px 6px rgba(0, 0, 0, 0.6);
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1779,6 +1784,239 @@ body, html {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LOBBY COLLAPSIBLE SECTIONS */
|
||||||
|
.lobby-accordion-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-accordion-header:hover {
|
||||||
|
background: rgba(245, 176, 65, 0.12);
|
||||||
|
border-color: rgba(245, 176, 65, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-accordion-header.open {
|
||||||
|
border-color: rgba(245, 176, 65, 0.55);
|
||||||
|
background: rgba(245, 176, 65, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-rules-summary {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: #cbd5e1;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-toggle-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--gold);
|
||||||
|
background: rgba(245, 176, 65, 0.12);
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(245, 176, 65, 0.3);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-accordion-header:hover .accordion-toggle-btn {
|
||||||
|
background: rgba(245, 176, 65, 0.25);
|
||||||
|
border-color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-arrow {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SELECTED DECK PREVIEW CARD */
|
||||||
|
.selected-deck-summary-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
background: linear-gradient(135deg, rgba(245, 176, 65, 0.14) 0%, rgba(0, 0, 0, 0.6) 100%);
|
||||||
|
border: 2px solid rgba(245, 176, 65, 0.5);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.22s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
margin-top: 8px;
|
||||||
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-deck-summary-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: var(--gold);
|
||||||
|
box-shadow: 0 6px 20px rgba(245, 176, 65, 0.3);
|
||||||
|
background: linear-gradient(135deg, rgba(245, 176, 65, 0.22) 0%, rgba(0, 0, 0, 0.7) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-active-deck {
|
||||||
|
font-size: 0.66rem;
|
||||||
|
font-weight: 900;
|
||||||
|
background: var(--gold);
|
||||||
|
color: #000;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-deck-change-action {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-deck-summary-card:hover .btn-deck-change-action {
|
||||||
|
background: rgba(245, 176, 65, 0.25);
|
||||||
|
border-color: var(--gold);
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AI THOUGHT BUBBLE */
|
||||||
|
.ai-thought-bubble {
|
||||||
|
position: absolute;
|
||||||
|
background: linear-gradient(135deg, rgba(15, 23, 42, 0.98) 0%, rgba(30, 41, 59, 0.98) 100%);
|
||||||
|
border: 2px solid #38bdf8;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 0.98rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.85), 0 0 22px rgba(56, 189, 248, 0.5);
|
||||||
|
z-index: 100;
|
||||||
|
max-width: 320px;
|
||||||
|
min-width: 170px;
|
||||||
|
text-align: center;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: thought-pop 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
transition: opacity 0.55s ease, transform 0.55s cubic-bezier(0.4, 0, 0.2, 1), filter 0.55s ease;
|
||||||
|
opacity: 1;
|
||||||
|
filter: blur(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes thought-pop {
|
||||||
|
0% { transform: scale(0.7) translateY(8px); opacity: 0; }
|
||||||
|
100% { transform: scale(1) translateY(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.fading-out {
|
||||||
|
opacity: 0 !important;
|
||||||
|
filter: blur(3px) !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thought-bubble-prefix {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
vertical-align: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thought-bubble-text {
|
||||||
|
vertical-align: middle;
|
||||||
|
color: #ffffff;
|
||||||
|
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thought-typing-cursor {
|
||||||
|
display: inline-block;
|
||||||
|
color: #38bdf8;
|
||||||
|
font-weight: 900;
|
||||||
|
margin-left: 2px;
|
||||||
|
animation: cursor-blink 0.6s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cursor-blink {
|
||||||
|
0% { opacity: 0.15; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-top {
|
||||||
|
top: 105%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-top.fading-out {
|
||||||
|
transform: translateX(-50%) scale(0.9) translateY(-6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-left {
|
||||||
|
top: 50%;
|
||||||
|
left: 110%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-left.fading-out {
|
||||||
|
transform: translateY(-50%) scale(0.9) translateX(-6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-right {
|
||||||
|
top: 50%;
|
||||||
|
right: 110%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-right.fading-out {
|
||||||
|
transform: translateY(-50%) scale(0.9) translateX(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-top-left {
|
||||||
|
top: 105%;
|
||||||
|
left: 0%;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-top-left.fading-out {
|
||||||
|
transform: scale(0.9) translateY(-6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-top-right {
|
||||||
|
top: 105%;
|
||||||
|
right: 0%;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-top-right.fading-out {
|
||||||
|
transform: scale(0.9) translateY(-6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-bottom {
|
||||||
|
bottom: 110%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought-bubble.slot-bottom.fading-out {
|
||||||
|
transform: translateX(-50%) scale(0.9) translateY(6px);
|
||||||
|
}
|
||||||
|
|
||||||
/* Canvas Particle Layer */
|
/* Canvas Particle Layer */
|
||||||
#particle-canvas {
|
#particle-canvas {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -1906,6 +2144,106 @@ body, html {
|
|||||||
100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AI SPECIAL CARD JOKE BANNER */
|
||||||
|
.ai-joke-banner {
|
||||||
|
position: absolute;
|
||||||
|
top: 52px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: linear-gradient(135deg, rgba(35, 12, 50, 0.98) 0%, rgba(15, 23, 42, 0.98) 55%, rgba(45, 15, 25, 0.98) 100%);
|
||||||
|
border: 2px solid #ffd700;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 16px 45px rgba(0, 0, 0, 0.9), 0 0 35px rgba(255, 215, 0, 0.45), inset 0 0 20px rgba(255, 215, 0, 0.18);
|
||||||
|
padding: 14px 22px;
|
||||||
|
max-width: 580px;
|
||||||
|
width: 90%;
|
||||||
|
z-index: 300;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
animation: joke-entrance 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
transition: opacity 0.65s ease, transform 0.65s cubic-bezier(0.4, 0, 0.2, 1), filter 0.65s ease;
|
||||||
|
color: #ffffff;
|
||||||
|
opacity: 1;
|
||||||
|
filter: blur(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes joke-entrance {
|
||||||
|
0% { transform: translateX(-50%) translateY(-30px) scale(0.85); opacity: 0; }
|
||||||
|
100% { transform: translateX(-50%) translateY(0) scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-joke-banner.fading-out {
|
||||||
|
opacity: 0 !important;
|
||||||
|
transform: translateX(-50%) translateY(-25px) scale(0.9) !important;
|
||||||
|
filter: blur(4px) !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.joke-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-bottom: 1px solid rgba(255, 215, 0, 0.35);
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.joke-header-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 900;
|
||||||
|
color: var(--gold);
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.joke-storyteller {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
background: rgba(245, 176, 65, 0.22);
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(245, 176, 65, 0.45);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.joke-close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.joke-close-btn:hover {
|
||||||
|
color: #ffd700;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.joke-setup {
|
||||||
|
font-size: 1.02rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: #f1f5f9;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.joke-punchline {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: #ffd700;
|
||||||
|
text-shadow: 0 0 14px rgba(255, 215, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
/* HOLOGRAPHIC FOIL SHINE ON CARDS */
|
/* HOLOGRAPHIC FOIL SHINE ON CARDS */
|
||||||
.game-card.playable::before {
|
.game-card.playable::before {
|
||||||
content: '';
|
content: '';
|
||||||
|
|||||||
@@ -11,11 +11,17 @@ window.FaraonFx = (function () {
|
|||||||
|
|
||||||
function setSpeedLevel(level) {
|
function setSpeedLevel(level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case 1: speedMultiplier = 0.7; break; // 0.5x
|
case -5: speedMultiplier = 0.35; break; // 0.2x Zen
|
||||||
case 2: speedMultiplier = 0.85; break; // 0.75x
|
case -4: speedMultiplier = 0.45; break; // 0.35x Veľmi pomalá
|
||||||
case 4: speedMultiplier = 1.5; break; // 1.5x
|
case -3: speedMultiplier = 0.60; break; // 0.5x Pomalá
|
||||||
case 5: speedMultiplier = 2.4; break; // 2.5x Turbo
|
case -2: speedMultiplier = 0.75; break; // 0.65x Pokojná
|
||||||
case 3:
|
case -1: speedMultiplier = 0.88; break; // 0.8x Zvoľnená
|
||||||
|
case 0: speedMultiplier = 1.0; break; // 1x Normálna
|
||||||
|
case 1: speedMultiplier = 1.25; break; // 1.25x Svižná
|
||||||
|
case 2: speedMultiplier = 1.55; break; // 1.5x Rýchla
|
||||||
|
case 3: speedMultiplier = 2.0; break; // 2x Veľmi rýchla
|
||||||
|
case 4: speedMultiplier = 2.75; break; // 2.75x Turbo
|
||||||
|
case 5: speedMultiplier = 3.8; break; // 4x Blesková
|
||||||
default: speedMultiplier = 1.0; break;
|
default: speedMultiplier = 1.0; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
namespace Faraon.Core.AI;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Faraon.Core.Models;
|
||||||
|
|
||||||
|
public static class AIDialogueService
|
||||||
|
{
|
||||||
|
private static readonly Random _rng = new();
|
||||||
|
|
||||||
|
// --- EASY (Začiatočník - prezrádza taktiku a neistotu) ---
|
||||||
|
private static readonly string[] EasyThinking =
|
||||||
|
[
|
||||||
|
"Mám ešte jedno eso, len neviem či ho dať teraz...",
|
||||||
|
"Keby mi radšej prišla zeleň, to by bolo super.",
|
||||||
|
"Dúfam, že po mne nikto nehodí sedmu!",
|
||||||
|
"Mám len samé vysoké karty, čo s tým...",
|
||||||
|
"Asi ti zmením farbu, táto sa mi veľmi nepáči.",
|
||||||
|
"Ktorú kartu dať skôr? Táto vyzerá celkom pekne.",
|
||||||
|
"Nemám veľa možností, toto snáď vyjde.",
|
||||||
|
"Mám tu jedného horníka, ale bojím sa ho použiť.",
|
||||||
|
"Ešteže mám túto záchrannú kartu...",
|
||||||
|
"Snáď nikto nezbadá, že mi ostala už len jedna karta!",
|
||||||
|
"Hm... pravidlá hovoria, že toto môžem zahrať, nie?",
|
||||||
|
"Tento ťah snáď nebude úplná katastrofa.",
|
||||||
|
"Čo ak zahrám toto a súper ma zničí?",
|
||||||
|
"Uff, dúfam, že nemáš sedmu na ruke."
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- MEDIUM (Taktik - realistické premýšľanie) ---
|
||||||
|
private static readonly string[] MediumThinking =
|
||||||
|
[
|
||||||
|
"Skúsim ti podržať farbu, nech nemôžeš vyložiť.",
|
||||||
|
"Túto sedmu si radšej pošetrím na neskôr.",
|
||||||
|
"Pozrime sa, čo s týmto urobíš.",
|
||||||
|
"Ak teraz zahrám túto kartu, ďalší ťah mám voľný.",
|
||||||
|
"Idem po istote, netreba zbytočne riskovať.",
|
||||||
|
"Vidím, že zbieraš túto farbu, tak ti ju zmením.",
|
||||||
|
"Rýchle combo by ťa teraz mohlo prekvapiť.",
|
||||||
|
"Mením taktiku na rýchle vyprázdnenie ruky.",
|
||||||
|
"Kľúčové bude neprepustiť ťah bez tlaku.",
|
||||||
|
"Opatrný ťah, ešte nie je čas na otvorené finále.",
|
||||||
|
"Počítam karty, toto by malo bezpečne prejsť.",
|
||||||
|
"Skúsim ťa prinútiť potiahnuť z balíčka."
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- HARD (Veľmajster - psychologické blufovanie a prevaha) ---
|
||||||
|
private static readonly string[] HardThinking =
|
||||||
|
[
|
||||||
|
"Presne do tejto pasce som ťa potreboval dostať.",
|
||||||
|
"Mám v ruke presne to, čo ťa bude bolieť.",
|
||||||
|
"Matematicky ti zostávajú maximálne dva ťahy.",
|
||||||
|
"Risk je zisk. Priprav sa na prekvapenie.",
|
||||||
|
"Túto hru mám už prečítanú od prvého kola.",
|
||||||
|
"Si si naozaj istý, že toto bol tvoj najlepší ťah?",
|
||||||
|
"Môj ďalší krok rozhodne o celom zápase.",
|
||||||
|
"Viem presne, aké karty ti ostali v ruke.",
|
||||||
|
"Pripravujem šach-mat, sleduj pozorne.",
|
||||||
|
"Všetko ide presne podľa môjho majstrovského plánu.",
|
||||||
|
"Tvoj osud je spečatený, žiadna záchrana nepríde.",
|
||||||
|
"Geniálny ťah si žiada trochu trpezlivosti."
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- POSLEDNÁ KARTA (1 karta) ---
|
||||||
|
private static readonly string[] LastCardThoughts =
|
||||||
|
[
|
||||||
|
"Už mi chýba len jedna jediná karta!",
|
||||||
|
"Cítim vôňu víťazstva... už len krok!",
|
||||||
|
"Dúfam, že ma nikto nezablokuje v poslednej chvíli...",
|
||||||
|
"Kto ma teraz nezastaví, ten prehral zápas!",
|
||||||
|
"Už len jeden ťah a titul je môj!"
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- ČELENIE SEDME (Facing Seven) ---
|
||||||
|
private static readonly string[] FacingSevenThoughts =
|
||||||
|
[
|
||||||
|
"Sedma?! Pozrime sa, či mám odpoveď...",
|
||||||
|
"Dúfam, že mám na ruke ďalšiu sedmu!",
|
||||||
|
"Toto bude bolieť, ak nemám obranu...",
|
||||||
|
"Ak mám Zeleného dolníka, tak sa vám vysmejem!"
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- ČELENIE ESU (Facing Ace) ---
|
||||||
|
private static readonly string[] FacingAceThoughts =
|
||||||
|
[
|
||||||
|
"Eso? Skúsim to prebiť vlastným esom!",
|
||||||
|
"Zamrznúť teraz by bola veľká škoda...",
|
||||||
|
"Hľadám v ruke eso na prebitie..."
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- BEŽNÉ LÍZANIE (Musí ťahať z balíčka) ---
|
||||||
|
private static readonly string[] MustDrawThoughts =
|
||||||
|
[
|
||||||
|
"Nič mi nesedí, musím líznuť z balíčka...",
|
||||||
|
"Snáď mi príde niečo poriadne...",
|
||||||
|
"Balíček, nesklam ma v tejto chvíli!",
|
||||||
|
"Škoda, tento ťah musím potiahnuť kartu."
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- REAKCIE NA UDALOSTI ---
|
||||||
|
private static readonly string[] FreezeReactions =
|
||||||
|
[
|
||||||
|
"Brrr, to je ale poriadna zima!",
|
||||||
|
"Zamrzol som na jedno celé kolo...",
|
||||||
|
"Kto tu pustil túto sibírsku fujavicu?!",
|
||||||
|
"Počkaj, hneď ako rozmrznem, ti to vrátim!",
|
||||||
|
"Stojím, ale môj čas ešte len príde.",
|
||||||
|
"Mráz mi nevadí, aspoň lepšie premyslím stratégiu."
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly string[] AceCounterReactions =
|
||||||
|
[
|
||||||
|
"Žiadne státie! Moje eso okamžite rozpúšťa ľad!",
|
||||||
|
"Na mňa si s mrazom neprídeš!",
|
||||||
|
"Vraciam ti to aj s úrokmi, stojíš ty!"
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly string[] SmallPenaltyDrawReactions =
|
||||||
|
[
|
||||||
|
"Ejha, +3 karty? To ešte hravo zvládnem.",
|
||||||
|
"No pekne, sedma ma neobišla...",
|
||||||
|
"Dobre, beriem 3, ale vrátim vám ich dvojnásobne!"
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly string[] BigPenaltyDrawReactions =
|
||||||
|
[
|
||||||
|
"Ejha, toľko kariet mi do ruky ani nevojde!",
|
||||||
|
"To je nejaká organizovaná pomsta?!",
|
||||||
|
"Kde mám teraz zohnať miesto na ruke pre toľko kariet?!",
|
||||||
|
"No výborne, zachvíľu budem mať celý balíček...",
|
||||||
|
"Tento masívny trest vám v ďalšom kole nezabudnem!"
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly string[] SevenCanceledReactions =
|
||||||
|
[
|
||||||
|
"Haha, Zelený dolník vás zachránil!",
|
||||||
|
"Štít aktivovaný, dnes žiadne ťahanie sediem!",
|
||||||
|
"Itel Reding hovorí jasné: Dnes nie!",
|
||||||
|
"Sedmy vymazané na nulu, ideme ďalej!"
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly string[] RevivedTargetReactions =
|
||||||
|
[
|
||||||
|
"Znova žijem! Pozor na mňa!",
|
||||||
|
"Červená sedma robí zázraky! Som späť v hre!",
|
||||||
|
"Ešte sme neskončili, návrat šampióna!"
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly string[] RevivedSourceReactions =
|
||||||
|
[
|
||||||
|
"Nikam neodchádzaš, ešte si s tebou zahrám!",
|
||||||
|
"Vraciam ťa späť do hry, zábava pokračuje!",
|
||||||
|
"Predčasná radosť, poď pekne naspäť k stolu!"
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly string[] SuitChangeReactions =
|
||||||
|
[
|
||||||
|
"Prepínam hru na moju obľúbenú farbu!",
|
||||||
|
"Uvidíme, či máte na túto farbu odpoveď...",
|
||||||
|
"Trochu to tu zamiešame a zmeníme smer!"
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly string[] BurnReactions =
|
||||||
|
[
|
||||||
|
"BUM! Všetko zhorelo na prach!",
|
||||||
|
"Spálené! A ťahám znova!",
|
||||||
|
"Dokonalé 4 karty, stôl je čistý!"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static string GetThinkingDialogue(Player player, GameState state)
|
||||||
|
{
|
||||||
|
if (player.Hand.Count == 1)
|
||||||
|
{
|
||||||
|
return PickRandom(LastCardThoughts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.PendingDrawCount > 0)
|
||||||
|
{
|
||||||
|
return PickRandom(FacingSevenThoughts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.PendingSkip)
|
||||||
|
{
|
||||||
|
return PickRandom(FacingAceThoughts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return player.Difficulty switch
|
||||||
|
{
|
||||||
|
AIDifficulty.Easy => PickRandom(EasyThinking),
|
||||||
|
AIDifficulty.Hard => PickRandom(HardThinking),
|
||||||
|
_ => PickRandom(MediumThinking)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetMustDrawDialogue(Player player)
|
||||||
|
{
|
||||||
|
return PickRandom(MustDrawThoughts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetEventReaction(GameEvent e, Player player)
|
||||||
|
{
|
||||||
|
return e.Type switch
|
||||||
|
{
|
||||||
|
GameEventType.AceSkipped when e.SourcePlayer == player => PickRandom(FreezeReactions),
|
||||||
|
GameEventType.AceCountered when e.SourcePlayer == player => PickRandom(AceCounterReactions),
|
||||||
|
GameEventType.SevenStacked when e.TargetPlayer == player || (e.ExtraData > 0 && e.SourcePlayer != player) =>
|
||||||
|
e.ExtraData >= 6 ? PickRandom(BigPenaltyDrawReactions) : PickRandom(SmallPenaltyDrawReactions),
|
||||||
|
GameEventType.SevenCanceled when e.SourcePlayer == player => PickRandom(SevenCanceledReactions),
|
||||||
|
GameEventType.PlayerRevived when e.TargetPlayer == player => PickRandom(RevivedTargetReactions),
|
||||||
|
GameEventType.PlayerRevived when e.SourcePlayer == player => PickRandom(RevivedSourceReactions),
|
||||||
|
GameEventType.SuitChanged when e.SourcePlayer == player => PickRandom(SuitChangeReactions),
|
||||||
|
GameEventType.CardsBurned when e.SourcePlayer == player => PickRandom(BurnReactions),
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CardJoke(string Icon, string Category, string Setup, string Punchline);
|
||||||
|
|
||||||
|
// --- VTIPY: MASÍVNY TREST (9 - 12+ kariet) ---
|
||||||
|
private static readonly CardJoke[] MultiDrawJokes =
|
||||||
|
[
|
||||||
|
new("🎒", "KAUZÁLNY BATOH", "Pýta sa Faraón hráča pri stole: 'Prečo si prišiel na zápas s turistickým ruksakom?'", "– 'Aby sa mi tam zmestili všetky tie sedmičky z balíčka!'"),
|
||||||
|
new("🏢", "VRCHOL OPTIMIZMU", "Viete, čo je vrchol hráčskeho optimizmu?", "– Keď po 12 potiahnutých kartách zahlásiš: 'Počkajte, ešte dve kolá a končím!'"),
|
||||||
|
new("🏗️", "STAVEBNÝ ZÁMER", "Rozprávajú sa dvaja architekti pri stole:", "– 'Z toľkých kariet v ruke sa už dá postaviť pekný trojizbový byt aj s terasou!'"),
|
||||||
|
new("🍕", "RODINNÁ PIZZA", "Aký je rozdiel medzi rodinnou pizzou a hráčom s 12 kartami na ruke?", "– Rodinná pizza aspoň niekoho pri stole naozaj poteší!"),
|
||||||
|
new("📜", "DAŇ Z NEHNUTEĽNOSTI", "Dôležitý oznam od správcu turnaja:", "– Hráč s viac ako 10 kartami začína platiť daň za zastavanú plochu stola!")
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- VTIPY: SPÁLENIE (4 rovnaké karty) ---
|
||||||
|
private static readonly CardJoke[] BurnJokes =
|
||||||
|
[
|
||||||
|
new("🎩", "ILUZIONISTA", "Kúzelník na javisku: 'A teraz sledujte pozorne, nechám zmiznúť 4 karty!'", "– Hráč z rohu kričí: 'Nič nové, to robím v každom kole spálením!'"),
|
||||||
|
new("🚒", "POŽIARNY POPLACH", "Hasiči práve vyhlásili výjazd k nášmu hernému stolu!", "– Niekto tu práve odpálil 4 rovnaké karty a stôl ľahol popolom!"),
|
||||||
|
new("🧙", "LEGENDA V OHNI", "Stretnú sa 4 rovnaké karty v balíčku a jedna hovorí:", "– 'Dnes z nás bude buď slávna legenda, alebo čistý popol!'"),
|
||||||
|
new("🔥", "ČISTÁ POÉZIA", "Spáliť 4 karty naraz jedným ťahom nie je len obyčajné šťastie...", "– ...je to čisté vizuálne umenie a nočná mora pre súperov!")
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- VTIPY: DLHÁ HRA (TurnCount >= 28) ---
|
||||||
|
private static readonly CardJoke[] LongGameJokes =
|
||||||
|
[
|
||||||
|
new("⏳", "RODINNÝ DÔCHODOK", "Manželka volá kartárovi: 'Kedy už prídeš domov z toho zápasu?'", "– 'Drahá, ešte jedno kolo a môžeme ísť rovno spolu do dôchodku!'"),
|
||||||
|
new("🏛️", "PAMIATKOVÝ ÚRAD", "Táto partia trvá už tak neskutočne dlho...", "– ...že si na ďalší ťah budeme musieť pýtať povolenie od pamiatkárov!"),
|
||||||
|
new("🧔", "ČAS NA HOLENIE", "Začiatok zápasu: hladko oholení mladíci plní síl.", "– Aktuálny stav: fúzatí veteráni čakajúci na záchrannú zeleň!"),
|
||||||
|
new("🕰️", "ŠPORTOVÝ VÝKON", "Lekár sa pýta pacienta: 'Koľko hodín denne venujete športu?'", "– 'Už šiestu hodinu bez prestávky sedím pri napínavom Faraónovi!'")
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- VTIPY: OŽIVENIE (Červená sedma) ---
|
||||||
|
private static readonly CardJoke[] ReviveJokes =
|
||||||
|
[
|
||||||
|
new("🧟", "ZÁKAZ ODCHODU", "Lekár konštatuje: 'Hráč úspešne vyhral a odchádza domov.'", "– Červená sedma na to: 'Ešte nie, kamarát! Poď pekne naspäť k stolu!'"),
|
||||||
|
new("⚡", "VZKRIESENIE ŠAMPIÓNA", "Viete, čo je rýchlejšie ako ranný pondelkový budík?", "– Hráč vrátený do hry Červenou sedmou s chuťou po veľkej pomste!")
|
||||||
|
];
|
||||||
|
|
||||||
|
public static CardJoke? GetContextualJoke(GameEventType? type, int extraData, int turnCount)
|
||||||
|
{
|
||||||
|
if (type == GameEventType.SevenStacked && extraData >= 9)
|
||||||
|
{
|
||||||
|
return PickRandom(MultiDrawJokes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == GameEventType.CardsBurned)
|
||||||
|
{
|
||||||
|
return PickRandom(BurnJokes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == GameEventType.PlayerRevived)
|
||||||
|
{
|
||||||
|
return PickRandom(ReviveJokes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turnCount >= 28 && turnCount % 16 == 0)
|
||||||
|
{
|
||||||
|
return PickRandom(LongGameJokes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T PickRandom<T>(T[] options)
|
||||||
|
{
|
||||||
|
if (options == null || options.Length == 0) return default!;
|
||||||
|
return options[_rng.Next(options.Length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -70,22 +70,29 @@ public class RuleOptions
|
|||||||
public bool PassAndPlayPrivacyScreen { get; set; } = false;
|
public bool PassAndPlayPrivacyScreen { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Úroveň rýchlosti hry (1 = 0.5x, 2 = 0.75x, 3 = 1x Normálna, 4 = 1.5x Rýchla, 5 = 2.5x Turbo).
|
/// Úroveň rýchlosti hry od -5 (Extra pomalá 0.2x) cez 0 (1x Normálna) až po +5 (Blesková 4x).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SpeedLevel { get; set; } = 3;
|
public int SpeedLevel { get; set; } = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rýchlosť AI ťahov v milisekundách podľa zvolenej úrovne rýchlosti.
|
/// Rýchlosť AI ťahov v milisekundách podľa zvolenej úrovne rýchlosti (-5 až +5).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int AIDelayMs
|
public int AIDelayMs
|
||||||
{
|
{
|
||||||
get => SpeedLevel switch
|
get => SpeedLevel switch
|
||||||
{
|
{
|
||||||
1 => 1600,
|
-5 => 3200,
|
||||||
2 => 1200,
|
-4 => 2600,
|
||||||
4 => 500,
|
-3 => 2100,
|
||||||
5 => 200,
|
-2 => 1700,
|
||||||
_ => 900
|
-1 => 1400,
|
||||||
|
0 => 1100,
|
||||||
|
1 => 850,
|
||||||
|
2 => 650,
|
||||||
|
3 => 450,
|
||||||
|
4 => 300,
|
||||||
|
5 => 150,
|
||||||
|
_ => 1100
|
||||||
};
|
};
|
||||||
set { }
|
set { }
|
||||||
}
|
}
|
||||||
@@ -100,4 +107,9 @@ public class RuleOptions
|
|||||||
/// Hrá sa až kým nezostane iba jeden celkový víťaz.
|
/// Hrá sa až kým nezostane iba jeden celkový víťaz.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool OlympicMode { get; set; } = false;
|
public bool OlympicMode { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AI Human Mode: AI hráči zobrazujú premýšľacie bubliny s taktickými a zábavnými hláškami a reagujú na herné udalosti.
|
||||||
|
/// </summary>
|
||||||
|
public bool AIHumanMode { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -431,4 +431,49 @@ public class GameEngineTests
|
|||||||
// When facing penalty and disabled, Green Under Knave cannot be played
|
// When facing penalty and disabled, Green Under Knave cannot be played
|
||||||
Assert.False(ActionValidator.CanPlayCard(greenUnderKnave, topSeven, null, 3, false, rules));
|
Assert.False(ActionValidator.CanPlayCard(greenUnderKnave, topSeven, null, 3, false, rules));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AIDialogueService_ReturnsAppropriateDialogues()
|
||||||
|
{
|
||||||
|
var easyAI = new Player("AI Easy", PlayerType.AI, "🦊", AIDifficulty.Easy);
|
||||||
|
var hardAI = new Player("AI Hard", PlayerType.AI, "🤖", AIDifficulty.Hard);
|
||||||
|
var state = new GameState();
|
||||||
|
|
||||||
|
easyAI.Hand.Add(new Card(Suit.Hearts, Rank.Eight));
|
||||||
|
easyAI.Hand.Add(new Card(Suit.Hearts, Rank.Nine));
|
||||||
|
|
||||||
|
// Thinking dialogue for easy AI
|
||||||
|
var easyThought = AIDialogueService.GetThinkingDialogue(easyAI, state);
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(easyThought));
|
||||||
|
|
||||||
|
// Thinking dialogue for hard AI
|
||||||
|
var hardThought = AIDialogueService.GetThinkingDialogue(hardAI, state);
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(hardThought));
|
||||||
|
|
||||||
|
// Event reactions
|
||||||
|
var freezeEvent = new GameEvent(GameEventType.AceSkipped, easyAI);
|
||||||
|
var freezeReaction = AIDialogueService.GetEventReaction(freezeEvent, easyAI);
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(freezeReaction));
|
||||||
|
|
||||||
|
var penaltyEvent = new GameEvent(GameEventType.SevenStacked, null, TargetPlayer: easyAI, ExtraData: 6);
|
||||||
|
var penaltyReaction = AIDialogueService.GetEventReaction(penaltyEvent, easyAI);
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(penaltyReaction));
|
||||||
|
|
||||||
|
var cancelEvent = new GameEvent(GameEventType.SevenCanceled, easyAI);
|
||||||
|
var cancelReaction = AIDialogueService.GetEventReaction(cancelEvent, easyAI);
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(cancelReaction));
|
||||||
|
|
||||||
|
// Card jokes
|
||||||
|
var multiDrawJoke = AIDialogueService.GetContextualJoke(GameEventType.SevenStacked, 12, 10);
|
||||||
|
Assert.NotNull(multiDrawJoke);
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(multiDrawJoke.Punchline));
|
||||||
|
|
||||||
|
var burnJoke = AIDialogueService.GetContextualJoke(GameEventType.CardsBurned, 0, 10);
|
||||||
|
Assert.NotNull(burnJoke);
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(burnJoke.Setup));
|
||||||
|
|
||||||
|
var longGameJoke = AIDialogueService.GetContextualJoke(null, 0, 32);
|
||||||
|
Assert.NotNull(longGameJoke);
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(longGameJoke.Punchline));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user