improvements
@@ -1,8 +1,12 @@
|
|||||||
@namespace Faraon.Client.Components
|
@namespace Faraon.Client.Components
|
||||||
|
@using Faraon.Core.AI
|
||||||
|
|
||||||
<div class="lobby-screen">
|
<div class="lobby-screen">
|
||||||
<div class="lobby-container">
|
<div class="lobby-container">
|
||||||
<div class="lobby-header">
|
<div class="lobby-header">
|
||||||
|
<div class="lobby-logo-wrapper">
|
||||||
|
<img src="assets/logo/logo_2_modern.jpg" class="lobby-logo-img" alt="FARAÓN Logo" />
|
||||||
|
</div>
|
||||||
<h1 class="lobby-title">FARAÓN</h1>
|
<h1 class="lobby-title">FARAÓN</h1>
|
||||||
<div class="lobby-subtitle">Tradičná mariášová kartová hra s moderným nábojom</div>
|
<div class="lobby-subtitle">Tradičná mariášová kartová hra s moderným nábojom</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,13 +47,14 @@
|
|||||||
<option value="@PlayerType.AI">🤖 AI Počítač</option>
|
<option value="@PlayerType.AI">🤖 AI Počítač</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- Obtiažnosť AI (ak je AI) -->
|
<!-- Výber AI osobnosti a obtiažnosti (ak je AI) -->
|
||||||
@if (p.Type == PlayerType.AI)
|
@if (p.Type == PlayerType.AI)
|
||||||
{
|
{
|
||||||
<select class="player-setup-select" @bind="p.Difficulty">
|
<select class="player-setup-select" style="font-weight: 700; color: var(--gold);" value="@(p.PersonalityId ?? "fox")" @onchange="(e) => SelectPersonality(idx, e.Value?.ToString())">
|
||||||
<option value="@AIDifficulty.Easy">🌱 Začiatočník (Ľahký)</option>
|
@foreach (var pers in AIPersonality.All)
|
||||||
<option value="@AIDifficulty.Medium">⚔️ Taktik (Stredný)</option>
|
{
|
||||||
<option value="@AIDifficulty.Hard">🔥 Veľmajster (Ťažký)</option>
|
<option value="@pers.Id">@pers.BaseAvatar @pers.Name (@GetDiffLabel(pers.Difficulty))</option>
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -255,9 +260,9 @@
|
|||||||
private List<Player> PlayerConfigs = new()
|
private List<Player> PlayerConfigs = new()
|
||||||
{
|
{
|
||||||
new Player("Ty (Hráč 1)", PlayerType.Human, "👑"),
|
new Player("Ty (Hráč 1)", PlayerType.Human, "👑"),
|
||||||
new Player("AI Jano", PlayerType.AI, "🦊", AIDifficulty.Medium),
|
new Player(AIPersonality.Fox.Name, PlayerType.AI, AIPersonality.Fox.BaseAvatar, AIPersonality.Fox.Difficulty, AIPersonality.Fox.Id),
|
||||||
new Player("AI Zuzka", PlayerType.AI, "👸", AIDifficulty.Hard),
|
new Player(AIPersonality.Queen.Name, PlayerType.AI, AIPersonality.Queen.BaseAvatar, AIPersonality.Queen.Difficulty, AIPersonality.Queen.Id),
|
||||||
new Player("AI Mišo", PlayerType.AI, "🤖", AIDifficulty.Easy)
|
new Player(AIPersonality.Robot.Name, PlayerType.AI, AIPersonality.Robot.BaseAvatar, AIPersonality.Robot.Difficulty, AIPersonality.Robot.Id)
|
||||||
};
|
};
|
||||||
|
|
||||||
private void SetPlayerCount(int count)
|
private void SetPlayerCount(int count)
|
||||||
@@ -272,10 +277,27 @@
|
|||||||
PlayerConfigs[index].Avatar = Avatars[nextIdx];
|
PlayerConfigs[index].Avatar = Avatars[nextIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SelectPersonality(int index, string? personalityId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(personalityId)) return;
|
||||||
|
var pers = AIPersonality.GetById(personalityId);
|
||||||
|
PlayerConfigs[index].PersonalityId = pers.Id;
|
||||||
|
PlayerConfigs[index].Name = pers.Name;
|
||||||
|
PlayerConfigs[index].Avatar = pers.BaseAvatar;
|
||||||
|
PlayerConfigs[index].Difficulty = pers.Difficulty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDiffLabel(AIDifficulty diff) => diff switch
|
||||||
|
{
|
||||||
|
AIDifficulty.Easy => "Ľahký",
|
||||||
|
AIDifficulty.Hard => "Veľmajster",
|
||||||
|
_ => "Taktik"
|
||||||
|
};
|
||||||
|
|
||||||
private async Task StartGame()
|
private async Task StartGame()
|
||||||
{
|
{
|
||||||
var activePlayers = PlayerConfigs.Take(SelectedPlayerCount).Select(p =>
|
var activePlayers = PlayerConfigs.Take(SelectedPlayerCount).Select(p =>
|
||||||
new Player(p.Name, p.Type, p.Avatar, p.Difficulty)
|
new Player(p.Name, p.Type, p.Avatar, p.Difficulty, p.PersonalityId)
|
||||||
).ToList();
|
).ToList();
|
||||||
|
|
||||||
await OnStartGame.InvokeAsync((activePlayers, Rules));
|
await OnStartGame.InvokeAsync((activePlayers, Rules));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@namespace Faraon.Client.Components
|
@namespace Faraon.Client.Components
|
||||||
|
|
||||||
<div class="player-slot @PositionClass @(IsFrozen ? "frozen-slot" : (IsCurrentTurn ? "active-turn-slot" : ""))">
|
<div class="player-slot @PositionClass @(IsFrozen ? "frozen-slot" : (IsCurrentTurn ? "active-turn-slot" : ""))">
|
||||||
<div class="avatar-badge @(IsFrozen ? "is-frozen" : (IsCurrentTurn ? "active-turn" : "")) @(Player.HasFinished ? "is-finished" : "") @(CanBeRevived && Player.HasFinished && ReviveTurnsRemaining > 0 ? "can-be-revived" : "")">
|
<div class="avatar-badge @(IsFrozen ? "is-frozen" : (IsCurrentTurn ? "active-turn" : "")) @(Player.HasFinished ? "is-finished" : "") @(CanBeRevived && Player.HasFinished && ReviveTurnsRemaining > 0 ? "can-be-revived" : "") emotion-aura-@Player.CurrentEmotion.ToString().ToLowerInvariant()">
|
||||||
@if (IsFrozen && !Player.HasFinished && !Player.IsEliminated)
|
@if (IsFrozen && !Player.HasFinished && !Player.IsEliminated)
|
||||||
{
|
{
|
||||||
<div class="frozen-indicator-badge">
|
<div class="frozen-indicator-badge">
|
||||||
@@ -24,7 +24,13 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="avatar-icon">@Player.Avatar</div>
|
<div class="avatar-icon-wrapper emotion-anim-@Player.CurrentEmotion.ToString().ToLowerInvariant()">
|
||||||
|
<span class="avatar-icon">@Player.GetLiveAvatar()</span>
|
||||||
|
@if (!Player.HasFinished && !Player.IsEliminated && Player.GetEmotionBadge() != null)
|
||||||
|
{
|
||||||
|
<span class="avatar-emotion-dot-badge">@Player.GetEmotionBadge()</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<div class="avatar-name">
|
<div class="avatar-name">
|
||||||
@Player.Name
|
@Player.Name
|
||||||
@if (Player.Type == PlayerType.AI)
|
@if (Player.Type == PlayerType.AI)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ else
|
|||||||
<!-- Horná lišta -->
|
<!-- Horná lišta -->
|
||||||
<header class="game-header">
|
<header class="game-header">
|
||||||
<div style="display: flex; align-items: center; gap: 10px;">
|
<div style="display: flex; align-items: center; gap: 10px;">
|
||||||
|
<img src="assets/logo/logo_2_modern.jpg" style="width: 34px; height: 34px; border-radius: 50%; border: 1.5px solid #00e5ff; object-fit: cover; box-shadow: 0 0 12px rgba(0,229,255,0.6);" alt="Logo" />
|
||||||
<h1 class="game-title">FARAÓN</h1>
|
<h1 class="game-title">FARAÓN</h1>
|
||||||
@if (Engine.State.Rules.OlympicMode)
|
@if (Engine.State.Rules.OlympicMode)
|
||||||
{
|
{
|
||||||
@@ -25,20 +26,21 @@ else
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center; gap: 12px;">
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
<!-- Rýchlosť hry -->
|
<!-- Rýchlosť hry -->
|
||||||
<button class="btn-speed-control" @onclick="CycleSpeed" title="Zmeniť rýchlosť hry">
|
<button class="btn-speed-control" @onclick="CycleSpeed" title="Zmeniť rýchlosť hry">
|
||||||
@GetSpeedButtonText(Engine.State.Rules.SpeedLevel)
|
@GetSpeedButtonText(Engine.State.Rules.SpeedLevel)
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Zvuk -->
|
<!-- Zvuk -->
|
||||||
<button class="btn-header-action" @onclick="ToggleMute" title="@(IsMuted ? "Zapnúť zvuk" : "Stlmiť zvuk")">
|
<button class="btn-header-action @(IsMuted ? "btn-sound-muted" : "")" @onclick="ToggleMute" title="@(IsMuted ? "Zapnúť zvuk" : "Stlmiť zvuk")">
|
||||||
@(IsMuted ? "🔇" : "🔊")
|
<span>@(IsMuted ? "🔇" : "🔊")</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Nová hra -->
|
<!-- Menu -->
|
||||||
<button class="btn-header-action" @onclick="ReturnToLobby" title="Späť do menu">
|
<button class="btn-header-action" @onclick="ReturnToLobby" title="Späť do menu">
|
||||||
⚙️ Menu
|
<span style="font-size: 0.9rem;">⚙️</span>
|
||||||
|
<span>Menu</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -174,7 +176,7 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div style="display: flex; align-items: center; gap: 8px;">
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
<span class="avatar-icon">@ViewingPlayer.Avatar</span>
|
<span class="avatar-icon">@ViewingPlayer.GetLiveAvatar()</span>
|
||||||
<span class="avatar-name">
|
<span class="avatar-name">
|
||||||
@ViewingPlayer.Name
|
@ViewingPlayer.Name
|
||||||
@if (ViewingPlayer.Type == PlayerType.AI)
|
@if (ViewingPlayer.Type == PlayerType.AI)
|
||||||
@@ -475,6 +477,40 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dynamické emócie hráčov na herné udalosti
|
||||||
|
if (e.SourcePlayer != null)
|
||||||
|
{
|
||||||
|
switch (e.Type)
|
||||||
|
{
|
||||||
|
case GameEventType.AceSkipped:
|
||||||
|
SetPlayerEmotion(e.SourcePlayer, PlayerEmotion.Freezing, 3500);
|
||||||
|
break;
|
||||||
|
case GameEventType.AceCountered:
|
||||||
|
case GameEventType.SevenCanceled:
|
||||||
|
SetPlayerEmotion(e.SourcePlayer, PlayerEmotion.Relieved, 3000);
|
||||||
|
break;
|
||||||
|
case GameEventType.CardsBurned:
|
||||||
|
SetPlayerEmotion(e.SourcePlayer, PlayerEmotion.Confident, 3500);
|
||||||
|
break;
|
||||||
|
case GameEventType.PlayerFinished:
|
||||||
|
case GameEventType.MatchWon:
|
||||||
|
SetPlayerEmotion(e.SourcePlayer, PlayerEmotion.Celebrating, 10000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.TargetPlayer != null)
|
||||||
|
{
|
||||||
|
switch (e.Type)
|
||||||
|
{
|
||||||
|
case GameEventType.SevenStacked:
|
||||||
|
SetPlayerEmotion(e.TargetPlayer, e.ExtraData >= 6 ? PlayerEmotion.Shocked : PlayerEmotion.Angry, 3500);
|
||||||
|
break;
|
||||||
|
case GameEventType.PlayerRevived:
|
||||||
|
SetPlayerEmotion(e.TargetPlayer, PlayerEmotion.Confident, 3500);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
@@ -746,6 +782,27 @@ else
|
|||||||
var aiPlayer = Engine.State.CurrentPlayer;
|
var aiPlayer = Engine.State.CurrentPlayer;
|
||||||
if (aiPlayer.Type != PlayerType.AI) return;
|
if (aiPlayer.Type != PlayerType.AI) return;
|
||||||
|
|
||||||
|
if (aiPlayer.Hand.Count <= 2)
|
||||||
|
{
|
||||||
|
SetPlayerEmotion(aiPlayer, PlayerEmotion.Confident, 3500);
|
||||||
|
}
|
||||||
|
else if (aiPlayer.Hand.Count >= 8)
|
||||||
|
{
|
||||||
|
SetPlayerEmotion(aiPlayer, PlayerEmotion.Desperate, 3500);
|
||||||
|
}
|
||||||
|
else if (Engine.State.PendingSkip)
|
||||||
|
{
|
||||||
|
SetPlayerEmotion(aiPlayer, PlayerEmotion.Freezing, 3500);
|
||||||
|
}
|
||||||
|
else if (Engine.State.PendingDrawCount > 0)
|
||||||
|
{
|
||||||
|
SetPlayerEmotion(aiPlayer, PlayerEmotion.Shocked, 3500);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetPlayerEmotion(aiPlayer, PlayerEmotion.Thinking, 3500);
|
||||||
|
}
|
||||||
|
|
||||||
if (Engine.State.Rules.AIHumanMode)
|
if (Engine.State.Rules.AIHumanMode)
|
||||||
{
|
{
|
||||||
string thought = AIDialogueService.GetThinkingDialogue(aiPlayer, Engine.State);
|
string thought = AIDialogueService.GetThinkingDialogue(aiPlayer, Engine.State);
|
||||||
@@ -962,6 +1019,25 @@ else
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetPlayerEmotion(Player player, PlayerEmotion emotion, int durationMs = 3000)
|
||||||
|
{
|
||||||
|
player.CurrentEmotion = emotion;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
if (emotion != PlayerEmotion.Neutral && emotion != PlayerEmotion.Celebrating)
|
||||||
|
{
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(durationMs);
|
||||||
|
if (player.CurrentEmotion == emotion)
|
||||||
|
{
|
||||||
|
player.CurrentEmotion = PlayerEmotion.Neutral;
|
||||||
|
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)>();
|
||||||
|
|||||||
|
After Width: | Height: | Size: 956 KiB |
|
After Width: | Height: | Size: 765 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 998 KiB |
|
After Width: | Height: | Size: 923 KiB |
|
After Width: | Height: | Size: 691 KiB |
@@ -57,32 +57,103 @@ body, html {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* App Loader */
|
/* App Loader (WASM modern esports Pharaoh loader) */
|
||||||
.app-loader {
|
.app-loader {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: radial-gradient(circle at center, #1b4d3e 0%, #050b08 100%);
|
background: radial-gradient(circle at center, #0f2b38 0%, #061118 60%, #02060a 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader-card {
|
.loader-logo-container {
|
||||||
font-size: 4rem;
|
position: relative;
|
||||||
animation: bounce 1.2s infinite alternate ease-in-out;
|
width: 140px;
|
||||||
|
height: 140px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-logo-img {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 3px solid #00e5ff;
|
||||||
|
box-shadow: 0 0 30px rgba(0, 229, 255, 0.6), 0 0 60px rgba(255, 215, 0, 0.3);
|
||||||
|
animation: loader-pulse 2.2s infinite ease-in-out alternate;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-spinner-ring {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 140px;
|
||||||
|
height: 140px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid transparent;
|
||||||
|
border-top-color: #00e5ff;
|
||||||
|
border-right-color: #ffd700;
|
||||||
|
animation: loader-spin 1.4s linear infinite;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader-spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader-pulse {
|
||||||
|
0% { transform: scale(0.96); box-shadow: 0 0 20px rgba(0, 229, 255, 0.5); }
|
||||||
|
100% { transform: scale(1.04); box-shadow: 0 0 45px rgba(0, 229, 255, 0.85), 0 0 70px rgba(255, 215, 0, 0.4); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-title {
|
||||||
|
font-family: 'Cinzel', serif;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
color: var(--gold);
|
||||||
|
text-shadow: 0 0 25px rgba(255, 215, 0, 0.7), 0 0 45px rgba(0, 229, 255, 0.5);
|
||||||
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader-text {
|
.loader-text {
|
||||||
margin-top: 1rem;
|
font-family: 'Outfit', sans-serif;
|
||||||
font-family: 'Cinzel', serif;
|
font-size: 1.05rem;
|
||||||
font-size: 1.5rem;
|
color: #94a3b8;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 1px;
|
||||||
color: var(--gold);
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes bounce {
|
.loader-progress-bar {
|
||||||
0% { transform: translateY(0) rotate(-5deg); }
|
width: 240px;
|
||||||
100% { transform: translateY(-20px) rotate(5deg); }
|
height: 6px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid rgba(0, 229, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-progress-fill {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #00e5ff, #ffd700, #00e5ff);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: loader-progress-anim 1.8s infinite linear;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader-progress-anim {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MAIN GAME CONTAINER */
|
/* MAIN GAME CONTAINER */
|
||||||
@@ -135,45 +206,63 @@ body, html {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-game-icon {
|
/* Top Game Header Controls */
|
||||||
background: rgba(255, 255, 255, 0.1);
|
.btn-header-action,
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
.btn-speed-control {
|
||||||
color: #fff;
|
display: inline-flex !important;
|
||||||
border-radius: 50%;
|
align-items: center !important;
|
||||||
width: 38px;
|
justify-content: center !important;
|
||||||
height: 38px;
|
gap: 6px !important;
|
||||||
display: flex;
|
height: 32px !important;
|
||||||
align-items: center;
|
padding: 0 14px !important;
|
||||||
justify-content: center;
|
border-radius: 18px !important;
|
||||||
cursor: pointer;
|
font-family: 'Outfit', sans-serif !important;
|
||||||
transition: all 0.2s ease;
|
font-size: 0.85rem !important;
|
||||||
font-size: 1.1rem;
|
font-weight: 700 !important;
|
||||||
|
color: #e2e8f0 !important;
|
||||||
|
background: rgba(10, 25, 20, 0.85) !important;
|
||||||
|
border: 1.5px solid rgba(245, 176, 65, 0.45) !important;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5), inset 0 1px 1px rgba(255, 255, 255, 0.1) !important;
|
||||||
|
backdrop-filter: blur(8px) !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important;
|
||||||
|
user-select: none !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
outline: none !important;
|
||||||
|
line-height: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-game-icon:hover {
|
.btn-header-action:hover,
|
||||||
background: rgba(245, 176, 65, 0.3);
|
.btn-speed-control:hover {
|
||||||
border-color: var(--gold);
|
background: rgba(245, 176, 65, 0.25) !important;
|
||||||
transform: scale(1.1);
|
border-color: var(--gold) !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
box-shadow: 0 0 14px rgba(245, 176, 65, 0.55), 0 4px 12px rgba(0, 0, 0, 0.6) !important;
|
||||||
|
transform: translateY(-2px) scale(1.04) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-header-action:active,
|
||||||
|
.btn-speed-control:active {
|
||||||
|
transform: translateY(0) scale(0.97) !important;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-speed-control {
|
.btn-speed-control {
|
||||||
width: auto !important;
|
|
||||||
min-width: 60px;
|
|
||||||
padding: 0 10px;
|
|
||||||
border-radius: 20px !important;
|
|
||||||
font-size: 0.8rem !important;
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: 0.3px;
|
|
||||||
background: rgba(245, 176, 65, 0.15) !important;
|
|
||||||
border-color: rgba(245, 176, 65, 0.45) !important;
|
|
||||||
color: var(--gold) !important;
|
color: var(--gold) !important;
|
||||||
white-space: nowrap;
|
font-weight: 800 !important;
|
||||||
|
letter-spacing: 0.3px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-speed-control:hover {
|
.btn-header-action.btn-sound-muted {
|
||||||
background: rgba(245, 176, 65, 0.35) !important;
|
border-color: rgba(239, 68, 68, 0.5) !important;
|
||||||
border-color: var(--gold) !important;
|
color: #f87171 !important;
|
||||||
box-shadow: 0 0 10px rgba(245, 176, 65, 0.4);
|
}
|
||||||
|
|
||||||
|
.btn-header-action.btn-sound-muted:hover {
|
||||||
|
border-color: #ef4444 !important;
|
||||||
|
background: rgba(239, 68, 68, 0.22) !important;
|
||||||
|
box-shadow: 0 0 14px rgba(239, 68, 68, 0.55) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TABLE FELT & SLOTS */
|
/* TABLE FELT & SLOTS */
|
||||||
@@ -405,9 +494,134 @@ body, html {
|
|||||||
100% { transform: translate(-50%, -50%) scale(1.25); }
|
100% { transform: translate(-50%, -50%) scale(1.25); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar-icon-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
}
|
||||||
|
|
||||||
.avatar-icon {
|
.avatar-icon {
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-emotion-dot-badge {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -4px;
|
||||||
|
right: -10px;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.85));
|
||||||
|
animation: badge-pop 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
z-index: 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes badge-pop {
|
||||||
|
0% { transform: scale(0) rotate(-15deg); opacity: 0; }
|
||||||
|
100% { transform: scale(1) rotate(0deg); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AVATAR EMOTION MICRO-ANIMATIONS */
|
||||||
|
.emotion-anim-thinking {
|
||||||
|
animation: anim-thinking 1.8s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes anim-thinking {
|
||||||
|
0% { transform: translateY(0) rotate(-2deg); }
|
||||||
|
100% { transform: translateY(-4px) rotate(3deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-anim-confident {
|
||||||
|
animation: anim-confident 1.4s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes anim-confident {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
100% { transform: scale(1.14); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-anim-shocked {
|
||||||
|
animation: anim-shocked 0.35s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes anim-shocked {
|
||||||
|
0% { transform: translateX(-4px) scale(1.1); }
|
||||||
|
100% { transform: translateX(4px) scale(1.1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-anim-freezing {
|
||||||
|
animation: anim-freezing 0.12s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes anim-freezing {
|
||||||
|
0% { transform: translate(-2px, -1px); }
|
||||||
|
50% { transform: translate(2px, 1px); }
|
||||||
|
100% { transform: translate(-1px, 2px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-anim-angry {
|
||||||
|
animation: anim-angry 0.22s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes anim-angry {
|
||||||
|
0% { transform: rotate(-5deg) scale(1.06); }
|
||||||
|
100% { transform: rotate(5deg) scale(1.06); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-anim-relieved {
|
||||||
|
animation: anim-relieved 1.4s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes anim-relieved {
|
||||||
|
0% { transform: translateY(0); }
|
||||||
|
100% { transform: translateY(-3px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-anim-desperate {
|
||||||
|
animation: anim-desperate 1.8s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes anim-desperate {
|
||||||
|
0% { transform: translateY(0) scale(0.95); }
|
||||||
|
100% { transform: translateY(4px) scale(0.88); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-anim-celebrating {
|
||||||
|
animation: anim-celebrating 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes anim-celebrating {
|
||||||
|
0% { transform: translateY(0) rotate(-7deg) scale(1.1); }
|
||||||
|
100% { transform: translateY(-8px) rotate(7deg) scale(1.22); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* EMOTION AURAS ON AVATAR BADGE */
|
||||||
|
.avatar-badge.emotion-aura-angry {
|
||||||
|
border-color: #ef4444 !important;
|
||||||
|
box-shadow: 0 0 25px rgba(239, 68, 68, 0.85), inset 0 0 14px rgba(239, 68, 68, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-badge.emotion-aura-shocked {
|
||||||
|
border-color: #f59e0b !important;
|
||||||
|
box-shadow: 0 0 25px rgba(245, 158, 11, 0.85), inset 0 0 14px rgba(245, 158, 11, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-badge.emotion-aura-confident {
|
||||||
|
border-color: #eab308 !important;
|
||||||
|
box-shadow: 0 0 25px rgba(234, 179, 8, 0.85), inset 0 0 14px rgba(234, 179, 8, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-badge.emotion-aura-celebrating {
|
||||||
|
border-color: #ffd700 !important;
|
||||||
|
box-shadow: 0 0 35px rgba(255, 215, 0, 1), inset 0 0 20px rgba(255, 215, 0, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-badge.emotion-aura-relieved {
|
||||||
|
border-color: #22c55e !important;
|
||||||
|
box-shadow: 0 0 25px rgba(34, 197, 94, 0.85), inset 0 0 14px rgba(34, 197, 94, 0.4) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-name {
|
.avatar-name {
|
||||||
@@ -1557,6 +1771,27 @@ body, html {
|
|||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lobby-logo-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-logo-img {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 3px solid #00e5ff;
|
||||||
|
box-shadow: 0 0 25px rgba(0, 229, 255, 0.65), 0 0 45px rgba(255, 215, 0, 0.35), 0 8px 24px rgba(0, 0, 0, 0.85);
|
||||||
|
animation: logo-glow 3s infinite ease-in-out alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes logo-glow {
|
||||||
|
0% { transform: scale(1); box-shadow: 0 0 20px rgba(0, 229, 255, 0.5), 0 0 35px rgba(255, 215, 0, 0.25); }
|
||||||
|
100% { transform: scale(1.05); box-shadow: 0 0 40px rgba(0, 229, 255, 0.9), 0 0 60px rgba(255, 215, 0, 0.5); }
|
||||||
|
}
|
||||||
|
|
||||||
.lobby-title {
|
.lobby-title {
|
||||||
font-family: 'Cinzel', serif;
|
font-family: 'Cinzel', serif;
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 765 KiB |
@@ -11,15 +11,23 @@
|
|||||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@600;800;900&family=Outfit:wght@400;600;700;900&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@600;800;900&family=Outfit:wght@400;600;700;900&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
|
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" href="css/app.css" />
|
<link rel="stylesheet" href="css/app.css" />
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/jpeg" href="assets/logo/logo_2_modern.jpg" />
|
||||||
|
<link rel="apple-touch-icon" href="assets/logo/logo_2_modern.jpg" />
|
||||||
<link href="Faraon.Client.styles.css" rel="stylesheet" />
|
<link href="Faraon.Client.styles.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="app-loader">
|
<div class="app-loader">
|
||||||
<div class="loader-card">🃏</div>
|
<div class="loader-logo-container">
|
||||||
<div class="loader-text">Načítavam Faraóna...</div>
|
<img src="assets/logo/logo_2_modern.jpg" class="loader-logo-img" alt="FARAÓN" />
|
||||||
|
<div class="loader-spinner-ring"></div>
|
||||||
|
</div>
|
||||||
|
<div class="loader-title">FARAÓN</div>
|
||||||
|
<div class="loader-text">Pripravujem stôl a miešam karty...</div>
|
||||||
|
<div class="loader-progress-bar">
|
||||||
|
<div class="loader-progress-fill"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
namespace Faraon.Core.AI;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Faraon.Core.Models;
|
||||||
|
|
||||||
|
public enum PlayerEmotion
|
||||||
|
{
|
||||||
|
Neutral = 0,
|
||||||
|
Thinking = 1,
|
||||||
|
Confident = 2,
|
||||||
|
Shocked = 3,
|
||||||
|
Freezing = 4,
|
||||||
|
Angry = 5,
|
||||||
|
Relieved = 6,
|
||||||
|
Desperate = 7,
|
||||||
|
Celebrating = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AIPersonality
|
||||||
|
{
|
||||||
|
public string Id { get; init; } = "";
|
||||||
|
public string Name { get; init; } = "";
|
||||||
|
public string Title { get; init; } = "";
|
||||||
|
public string BaseAvatar { get; init; } = "👤";
|
||||||
|
public string Description { get; init; } = "";
|
||||||
|
public AIDifficulty Difficulty { get; init; } = AIDifficulty.Medium;
|
||||||
|
public string ColorTheme { get; init; } = "#f5b041";
|
||||||
|
public Dictionary<PlayerEmotion, string> EmotionAvatars { get; init; } = new();
|
||||||
|
|
||||||
|
public string GetAvatarForEmotion(PlayerEmotion emotion)
|
||||||
|
{
|
||||||
|
if (EmotionAvatars.TryGetValue(emotion, out var av))
|
||||||
|
{
|
||||||
|
return av;
|
||||||
|
}
|
||||||
|
return BaseAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly AIPersonality Fox = new()
|
||||||
|
{
|
||||||
|
Id = "fox",
|
||||||
|
Name = "Fero 'Lišiak'",
|
||||||
|
Title = "Prefíkaný blufér",
|
||||||
|
BaseAvatar = "🦊",
|
||||||
|
Description = "Rád vyčkáva a nečakane mení farbu na stole. Keď sa mu darí, uškŕňa sa.",
|
||||||
|
Difficulty = AIDifficulty.Hard,
|
||||||
|
ColorTheme = "#ea580c",
|
||||||
|
EmotionAvatars = new()
|
||||||
|
{
|
||||||
|
[PlayerEmotion.Neutral] = "🦊",
|
||||||
|
[PlayerEmotion.Thinking] = "🧐",
|
||||||
|
[PlayerEmotion.Confident] = "😏",
|
||||||
|
[PlayerEmotion.Shocked] = "🙀",
|
||||||
|
[PlayerEmotion.Freezing] = "🥶",
|
||||||
|
[PlayerEmotion.Angry] = "😾",
|
||||||
|
[PlayerEmotion.Relieved] = "😸",
|
||||||
|
[PlayerEmotion.Desperate] = "😿",
|
||||||
|
[PlayerEmotion.Celebrating] = "🥳"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly AIPersonality Queen = new()
|
||||||
|
{
|
||||||
|
Id = "queen",
|
||||||
|
Name = "Zuzka 'Kráľovná'",
|
||||||
|
Title = "Chladnokrvná taktička",
|
||||||
|
BaseAvatar = "👸",
|
||||||
|
Description = "Vždy si stráži horníkov a premyslene kontroluje priebeh každej partie.",
|
||||||
|
Difficulty = AIDifficulty.Hard,
|
||||||
|
ColorTheme = "#ec4899",
|
||||||
|
EmotionAvatars = new()
|
||||||
|
{
|
||||||
|
[PlayerEmotion.Neutral] = "👸",
|
||||||
|
[PlayerEmotion.Thinking] = "🤔",
|
||||||
|
[PlayerEmotion.Confident] = "💅",
|
||||||
|
[PlayerEmotion.Shocked] = "😱",
|
||||||
|
[PlayerEmotion.Freezing] = "🥶",
|
||||||
|
[PlayerEmotion.Angry] = "😡",
|
||||||
|
[PlayerEmotion.Relieved] = "😌",
|
||||||
|
[PlayerEmotion.Desperate] = "😭",
|
||||||
|
[PlayerEmotion.Celebrating] = "👑"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly AIPersonality Robot = new()
|
||||||
|
{
|
||||||
|
Id = "robot",
|
||||||
|
Name = "Robo 'Algoritmus'",
|
||||||
|
Title = "Kvantový analytik",
|
||||||
|
BaseAvatar = "🤖",
|
||||||
|
Description = "Počíta každú zahratú kartu. Neznáša chyby a emócie vyjadruje v bleskoch a iskričkách.",
|
||||||
|
Difficulty = AIDifficulty.Hard,
|
||||||
|
ColorTheme = "#06b6d4",
|
||||||
|
EmotionAvatars = new()
|
||||||
|
{
|
||||||
|
[PlayerEmotion.Neutral] = "🤖",
|
||||||
|
[PlayerEmotion.Thinking] = "🧠",
|
||||||
|
[PlayerEmotion.Confident] = "⚡",
|
||||||
|
[PlayerEmotion.Shocked] = "💥",
|
||||||
|
[PlayerEmotion.Freezing] = "🧊",
|
||||||
|
[PlayerEmotion.Angry] = "🛑",
|
||||||
|
[PlayerEmotion.Relieved] = "🔋",
|
||||||
|
[PlayerEmotion.Desperate] = "🫠",
|
||||||
|
[PlayerEmotion.Celebrating] = "🚀"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly AIPersonality Grandpa = new()
|
||||||
|
{
|
||||||
|
Id = "grandpa",
|
||||||
|
Name = "Dedo Jozef",
|
||||||
|
Title = "Starý mariášnik",
|
||||||
|
BaseAvatar = "👴",
|
||||||
|
Description = "Hrá poctivú starú školu, žiadne zbytočné výmysly. Pri hre si rád dá virtuálne pivo.",
|
||||||
|
Difficulty = AIDifficulty.Medium,
|
||||||
|
ColorTheme = "#84cc16",
|
||||||
|
EmotionAvatars = new()
|
||||||
|
{
|
||||||
|
[PlayerEmotion.Neutral] = "👴",
|
||||||
|
[PlayerEmotion.Thinking] = "👓",
|
||||||
|
[PlayerEmotion.Confident] = "🍻",
|
||||||
|
[PlayerEmotion.Shocked] = "😵",
|
||||||
|
[PlayerEmotion.Freezing] = "🧣",
|
||||||
|
[PlayerEmotion.Angry] = "🤬",
|
||||||
|
[PlayerEmotion.Relieved] = "👴",
|
||||||
|
[PlayerEmotion.Desperate] = "🤦♂️",
|
||||||
|
[PlayerEmotion.Celebrating] = "🏆"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly AIPersonality CyberKid = new()
|
||||||
|
{
|
||||||
|
Id = "cyberkid",
|
||||||
|
Name = "CyberKid 'Gamer'",
|
||||||
|
Title = "Turbo útočník",
|
||||||
|
BaseAvatar = "👾",
|
||||||
|
Description = "Rýchly a agresívny hráč. Hádže sedmy a esá bez váhania.",
|
||||||
|
Difficulty = AIDifficulty.Medium,
|
||||||
|
ColorTheme = "#a855f7",
|
||||||
|
EmotionAvatars = new()
|
||||||
|
{
|
||||||
|
[PlayerEmotion.Neutral] = "👾",
|
||||||
|
[PlayerEmotion.Thinking] = "📡",
|
||||||
|
[PlayerEmotion.Confident] = "😎",
|
||||||
|
[PlayerEmotion.Shocked] = "🤯",
|
||||||
|
[PlayerEmotion.Freezing] = "❄️",
|
||||||
|
[PlayerEmotion.Angry] = "👿",
|
||||||
|
[PlayerEmotion.Relieved] = "🎮",
|
||||||
|
[PlayerEmotion.Desperate] = "💀",
|
||||||
|
[PlayerEmotion.Celebrating] = "🌟"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly AIPersonality Cat = new()
|
||||||
|
{
|
||||||
|
Id = "cat",
|
||||||
|
Name = "Mačka Minda",
|
||||||
|
Title = "Hravá šibalica",
|
||||||
|
BaseAvatar = "🐱",
|
||||||
|
Description = "Hravá a nevyspytateľná. Občas spôsobí chaos, ale teší sa z každej vyhratej rybičky.",
|
||||||
|
Difficulty = AIDifficulty.Easy,
|
||||||
|
ColorTheme = "#f59e0b",
|
||||||
|
EmotionAvatars = new()
|
||||||
|
{
|
||||||
|
[PlayerEmotion.Neutral] = "🐱",
|
||||||
|
[PlayerEmotion.Thinking] = "🐾",
|
||||||
|
[PlayerEmotion.Confident] = "😸",
|
||||||
|
[PlayerEmotion.Shocked] = "🙀",
|
||||||
|
[PlayerEmotion.Freezing] = "😿",
|
||||||
|
[PlayerEmotion.Angry] = "😾",
|
||||||
|
[PlayerEmotion.Relieved] = "😻",
|
||||||
|
[PlayerEmotion.Desperate] = "😿",
|
||||||
|
[PlayerEmotion.Celebrating] = "🐟"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly AIPersonality Wizard = new()
|
||||||
|
{
|
||||||
|
Id = "wizard",
|
||||||
|
Name = "Čarodej Baltazár",
|
||||||
|
Title = "Tajomný iluzionista",
|
||||||
|
BaseAvatar = "🧙",
|
||||||
|
Description = "Verí v magické spálenie 4 kariet a zázračné obraty v poslednej chvíli.",
|
||||||
|
Difficulty = AIDifficulty.Medium,
|
||||||
|
ColorTheme = "#6366f1",
|
||||||
|
EmotionAvatars = new()
|
||||||
|
{
|
||||||
|
[PlayerEmotion.Neutral] = "🧙",
|
||||||
|
[PlayerEmotion.Thinking] = "🔮",
|
||||||
|
[PlayerEmotion.Confident] = "✨",
|
||||||
|
[PlayerEmotion.Shocked] = "👁️",
|
||||||
|
[PlayerEmotion.Freezing] = "🧊",
|
||||||
|
[PlayerEmotion.Angry] = "🌩️",
|
||||||
|
[PlayerEmotion.Relieved] = "🧙♂️",
|
||||||
|
[PlayerEmotion.Desperate] = "🥀",
|
||||||
|
[PlayerEmotion.Celebrating] = "🪄"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly AIPersonality Bear = new()
|
||||||
|
{
|
||||||
|
Id = "bear",
|
||||||
|
Name = "Medveď Mišo",
|
||||||
|
Title = "Tatranský silák",
|
||||||
|
BaseAvatar = "🐻",
|
||||||
|
Description = "Keď dostane veľa sediem, hrozivo zúri. No keď vyloží eso, trasie sa celý stôl.",
|
||||||
|
Difficulty = AIDifficulty.Easy,
|
||||||
|
ColorTheme = "#b45309",
|
||||||
|
EmotionAvatars = new()
|
||||||
|
{
|
||||||
|
[PlayerEmotion.Neutral] = "🐻",
|
||||||
|
[PlayerEmotion.Thinking] = "🍯",
|
||||||
|
[PlayerEmotion.Confident] = "😁",
|
||||||
|
[PlayerEmotion.Shocked] = "😳",
|
||||||
|
[PlayerEmotion.Freezing] = "🥶",
|
||||||
|
[PlayerEmotion.Angry] = "🐻❄️",
|
||||||
|
[PlayerEmotion.Relieved] = "🐻",
|
||||||
|
[PlayerEmotion.Desperate] = "😭",
|
||||||
|
[PlayerEmotion.Celebrating] = "🍖"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly AIPersonality[] All =
|
||||||
|
[
|
||||||
|
Fox,
|
||||||
|
Queen,
|
||||||
|
Robot,
|
||||||
|
Grandpa,
|
||||||
|
CyberKid,
|
||||||
|
Cat,
|
||||||
|
Wizard,
|
||||||
|
Bear
|
||||||
|
];
|
||||||
|
|
||||||
|
public static AIPersonality GetById(string id)
|
||||||
|
{
|
||||||
|
foreach (var p in All)
|
||||||
|
{
|
||||||
|
if (p.Id.Equals(id, System.StringComparison.OrdinalIgnoreCase)) return p;
|
||||||
|
}
|
||||||
|
return Fox;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
namespace Faraon.Core.Models;
|
namespace Faraon.Core.Models;
|
||||||
|
|
||||||
|
using Faraon.Core.AI;
|
||||||
|
|
||||||
public enum PlayerType
|
public enum PlayerType
|
||||||
{
|
{
|
||||||
Human = 0,
|
Human = 0,
|
||||||
@@ -20,6 +22,8 @@ public class Player
|
|||||||
public PlayerType Type { get; set; } = PlayerType.Human;
|
public PlayerType Type { get; set; } = PlayerType.Human;
|
||||||
public AIDifficulty Difficulty { get; set; } = AIDifficulty.Medium;
|
public AIDifficulty Difficulty { get; set; } = AIDifficulty.Medium;
|
||||||
public string Avatar { get; set; } = "👑";
|
public string Avatar { get; set; } = "👑";
|
||||||
|
public string? PersonalityId { get; set; }
|
||||||
|
public PlayerEmotion CurrentEmotion { get; set; } = PlayerEmotion.Neutral;
|
||||||
public List<Card> Hand { get; } = new();
|
public List<Card> Hand { get; } = new();
|
||||||
|
|
||||||
public int? FinishedPosition { get; set; }
|
public int? FinishedPosition { get; set; }
|
||||||
@@ -39,12 +43,39 @@ public class Player
|
|||||||
|
|
||||||
public Player() { }
|
public Player() { }
|
||||||
|
|
||||||
public Player(string name, PlayerType type, string avatar = "👤", AIDifficulty difficulty = AIDifficulty.Medium)
|
public Player(string name, PlayerType type, string avatar = "👤", AIDifficulty difficulty = AIDifficulty.Medium, string? personalityId = null)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Type = type;
|
Type = type;
|
||||||
Avatar = avatar;
|
Avatar = avatar;
|
||||||
Difficulty = difficulty;
|
Difficulty = difficulty;
|
||||||
|
PersonalityId = personalityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLiveAvatar()
|
||||||
|
{
|
||||||
|
if (Type == PlayerType.AI && !string.IsNullOrEmpty(PersonalityId))
|
||||||
|
{
|
||||||
|
var p = AIPersonality.GetById(PersonalityId);
|
||||||
|
return p.GetAvatarForEmotion(CurrentEmotion);
|
||||||
|
}
|
||||||
|
return Avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? GetEmotionBadge()
|
||||||
|
{
|
||||||
|
return CurrentEmotion switch
|
||||||
|
{
|
||||||
|
PlayerEmotion.Thinking => "💡",
|
||||||
|
PlayerEmotion.Confident => "✨",
|
||||||
|
PlayerEmotion.Shocked => "💧",
|
||||||
|
PlayerEmotion.Freezing => "❄️",
|
||||||
|
PlayerEmotion.Angry => "🔥",
|
||||||
|
PlayerEmotion.Relieved => "🍀",
|
||||||
|
PlayerEmotion.Desperate => "💀",
|
||||||
|
PlayerEmotion.Celebrating => "🎉",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
@@ -52,5 +83,6 @@ public class Player
|
|||||||
Hand.Clear();
|
Hand.Clear();
|
||||||
FinishedPosition = null;
|
FinishedPosition = null;
|
||||||
IsSkipped = false;
|
IsSkipped = false;
|
||||||
|
CurrentEmotion = PlayerEmotion.Neutral;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -476,4 +476,32 @@ public class GameEngineTests
|
|||||||
Assert.NotNull(longGameJoke);
|
Assert.NotNull(longGameJoke);
|
||||||
Assert.False(string.IsNullOrWhiteSpace(longGameJoke.Punchline));
|
Assert.False(string.IsNullOrWhiteSpace(longGameJoke.Punchline));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AIPersonality_ChangesLiveAvatar_BasedOnEmotion()
|
||||||
|
{
|
||||||
|
var fox = AIPersonality.Fox;
|
||||||
|
Assert.Equal("fox", fox.Id);
|
||||||
|
Assert.Equal("🦊", fox.GetAvatarForEmotion(PlayerEmotion.Neutral));
|
||||||
|
Assert.Equal("🧐", fox.GetAvatarForEmotion(PlayerEmotion.Thinking));
|
||||||
|
Assert.Equal("🙀", fox.GetAvatarForEmotion(PlayerEmotion.Shocked));
|
||||||
|
Assert.Equal("🥶", fox.GetAvatarForEmotion(PlayerEmotion.Freezing));
|
||||||
|
Assert.Equal("😾", fox.GetAvatarForEmotion(PlayerEmotion.Angry));
|
||||||
|
Assert.Equal("🥳", fox.GetAvatarForEmotion(PlayerEmotion.Celebrating));
|
||||||
|
|
||||||
|
var player = new Player(fox.Name, PlayerType.AI, fox.BaseAvatar, fox.Difficulty, fox.Id);
|
||||||
|
Assert.Equal("🦊", player.GetLiveAvatar());
|
||||||
|
|
||||||
|
player.CurrentEmotion = PlayerEmotion.Shocked;
|
||||||
|
Assert.Equal("🙀", player.GetLiveAvatar());
|
||||||
|
Assert.Equal("💧", player.GetEmotionBadge());
|
||||||
|
|
||||||
|
player.CurrentEmotion = PlayerEmotion.Freezing;
|
||||||
|
Assert.Equal("🥶", player.GetLiveAvatar());
|
||||||
|
Assert.Equal("❄️", player.GetEmotionBadge());
|
||||||
|
|
||||||
|
player.CurrentEmotion = PlayerEmotion.Angry;
|
||||||
|
Assert.Equal("😾", player.GetLiveAvatar());
|
||||||
|
Assert.Equal("🔥", player.GetEmotionBadge());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||