diff --git a/Chrono/Shared/Pages/DeckDetail.razor b/Chrono/Shared/Pages/DeckDetail.razor index 572b144..7559325 100644 --- a/Chrono/Shared/Pages/DeckDetail.razor +++ b/Chrono/Shared/Pages/DeckDetail.razor @@ -80,6 +80,74 @@ } + +
+
+

Opening Hand Simulator

+
+ @if (hand.Count == 0) + { + + } + else + { + + @if (!hasMulliganed) + { + + } + } +
+
+ + @if (hand.Count > 0) + { +
+ @foreach (var card in hand) + { + var isDiver = deck.Divers.Contains(card.Name); + var isSelected = mulliganSelected.Contains(card); +
+
+ @if (!string.IsNullOrEmpty(card.ImageFile)) + { + @card.Name + } + else + { +
@card.Name
+ } +
+ @if (isDiver) + { + Diver + } + @if (isSelected) + { +
+ } +
+ } +
+

+ @if (!hasMulliganed) + { + Select non-diver cards to mulligan. You can only mulligan once. + } + else + { + Mulligan used. + } +

+ } +
} @@ -123,6 +191,88 @@ selectedCard = null; } + private List hand = []; + private HashSet mulliganSelected = []; + private bool hasMulliganed = false; + private bool canMulligan => mulliganSelected.Count > 0 && !hasMulliganed; + + private void DrawInitialHand() + { + if (deck == null) return; + hand.Clear(); + mulliganSelected.Clear(); + hasMulliganed = false; + + var random = new Random(); + + // 1. Get Divers + var divers = deck.Divers.Select(LookupCard).Where(c => c != null).Cast().ToList(); + // 2. Get Main Deck (excluding divers if they are in the main list, though usually they are separate in data) + var mainDeck = deck.Cards.Select(LookupCard).Where(c => c != null).Cast().ToList(); + + + + // Add 3 random non-diver cards from main deck + var nonDivers = mainDeck.Where(c => !deck.Divers.Contains(c.Name)).OrderBy(_ => random.Next()).Take(4).ToList(); + hand.AddRange(nonDivers); + + // Final shuffle of hand for display + hand = hand.OrderBy(_ => random.Next()).ToList(); + + // Add 2 random divers + if (divers.Count > 0) + { + var newHand = divers.Take(2).ToList(); + newHand.AddRange(hand); + + hand = newHand; + } + } + + private void ToggleMulliganSelection(CardData card) + { + if (hasMulliganed) return; + if (deck?.Divers.Contains(card.Name) == true) return; // Cannot mulligan divers + + if (mulliganSelected.Contains(card)) + mulliganSelected.Remove(card); + else + mulliganSelected.Add(card); + } + + private void Mulligan() + { + if (deck == null || hasMulliganed || mulliganSelected.Count == 0) return; + + var random = new Random(); + var mainDeck = deck.Cards.Select(LookupCard).Where(c => c != null).Cast().ToList(); + var nonDiversInDeck = mainDeck.Where(c => !deck.Divers.Contains(c.Name)).ToList(); + + // Filter out cards currently in hand + var currentHandNames = hand.Select(c => c.Name).ToList(); + var availablePool = nonDiversInDeck.Where(c => !currentHandNames.Contains(c.Name)).ToList(); + + var cardsToReplace = mulliganSelected.ToList(); + foreach (var oldCard in cardsToReplace) + { + if (availablePool.Count > 0) + { + var newCardIdx = random.Next(availablePool.Count); + var newCard = availablePool[newCardIdx]; + + var handIdx = hand.IndexOf(oldCard); + if (handIdx != -1) + { + hand[handIdx] = newCard; + } + availablePool.RemoveAt(newCardIdx); + } + } + + mulliganSelected.Clear(); + hasMulliganed = true; + } + private List<(int Cost, int Count)> GetManaDistribution() { if (deck == null) return []; diff --git a/Chrono/Shared/Pages/DeckDetail.razor.css b/Chrono/Shared/Pages/DeckDetail.razor.css index d36f681..e905734 100644 --- a/Chrono/Shared/Pages/DeckDetail.razor.css +++ b/Chrono/Shared/Pages/DeckDetail.razor.css @@ -4,6 +4,139 @@ padding: 0 1rem 3rem; } +.simulator { + margin-top: 3rem; + padding: 2rem; + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: var(--radius); +} + +.simulator-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} + +.simulator-header h2 { + margin: 0; +} + +.simulator-controls { + display: flex; + gap: 1rem; + align-items: center; +} + +.hand-display { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; + min-height: 250px; + margin-bottom: 1.5rem; +} + +.hand-card-wrapper { + position: relative; + width: 160px; + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; +} + +.hand-card-wrapper:hover { + transform: translateY(-5px); +} + +.hand-card-wrapper.selected { + transform: scale(0.95); + opacity: 0.7; +} + +.hand-card-img { + width: 100%; + border-radius: 10px; + overflow: hidden; + border: 2px solid transparent; + aspect-ratio: 2/3; +} + +.hand-card-wrapper.diver .hand-card-img { + border-color: #ffd700; + box-shadow: 0 0 15px rgba(255, 215, 0, 0.3); +} + +.hand-card-wrapper.selected .hand-card-img { + border-color: var(--accent); +} + +.hand-card-img img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.card-placeholder { + width: 100%; + height: 100%; + background: var(--bg-elevated); + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: 1rem; + font-size: 0.9rem; + color: var(--text-muted); +} + +.diver-tag { + position: absolute; + top: -8px; + left: 50%; + transform: translateX(-50%); + background: #ffd700; + color: black; + font-size: 0.7rem; + font-weight: bold; + padding: 2px 8px; + border-radius: 4px; + text-transform: uppercase; + z-index: 2; +} + +.mulligan-overlay { + position: absolute; + inset: 0; + background: rgba(255, 77, 77, 0.4); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 2rem; + border-radius: 10px; + z-index: 3; +} + +.simulator-hint { + text-align: center; + font-size: 0.9rem; + color: var(--text-muted); + margin-top: 1rem; +} + +@media (max-width: 600px) { + .simulator-header { + flex-direction: column; + gap: 1rem; + align-items: flex-start; + } + + .hand-card-wrapper { + width: 120px; + } +} + .back-link { display: inline-flex; align-items: center; diff --git a/Chrono/Shared/Pages/Decks.razor b/Chrono/Shared/Pages/Decks.razor index 2082874..9bad984 100644 --- a/Chrono/Shared/Pages/Decks.razor +++ b/Chrono/Shared/Pages/Decks.razor @@ -70,19 +70,30 @@ } +@inject NavigationManager Nav + @code { + [SupplyParameterFromQuery] public string? s1 { get; set; } + [SupplyParameterFromQuery] public string? s2 { get; set; } private List decks = []; private bool showPrecons; - protected override void OnInitialized() + protected override void OnParametersSet() { RefreshDecks(); } private void RefreshDecks() { - decks = DeckDatabase.Decks - .Where(d => d.IsVisible) + var query = DeckDatabase.Decks.Where(d => d.IsVisible); + + if (!string.IsNullOrEmpty(s1)) + query = query.Where(d => d.Syndicates.Contains(s1, StringComparer.OrdinalIgnoreCase)); + + if (!string.IsNullOrEmpty(s2)) + query = query.Where(d => d.Syndicates.Contains(s2, StringComparer.OrdinalIgnoreCase)); + + decks = query .Where(d => showPrecons ? d.DeckType is "precon" or "store" diff --git a/Chrono/Shared/Pages/SyndicatePairings.razor b/Chrono/Shared/Pages/SyndicatePairings.razor index 0fd2d9a..2629dff 100644 --- a/Chrono/Shared/Pages/SyndicatePairings.razor +++ b/Chrono/Shared/Pages/SyndicatePairings.razor @@ -1,105 +1,57 @@ @page "/syndicate-pairings" -@namespace Shared.Pages +@using Model +@inject NavigationManager Nav - - - +Syndicates - Chrono
-
+

Syndicate Pairings

-

Explore all @pairings.Count combinations of syndicates

-
+

Explore the possible combinations of syndicates

+
- @foreach (var doc in pairings) + @foreach (var pairing in GetPairings()) { - var s1 = doc.Syndicates?[0] ?? ""; - var s2 = doc.Syndicates?[1] ?? ""; -
+
-
- - @s1 +
+
+ @pairing.S1
-
- - @s2 +
+
+ @pairing.S2
- Dual-Syndicate Pairing + Browse Decks
}
- - - - @code { - private List pairings = []; - private DocData? selectedDoc; - private CardData? selectedCard; - private DocData? selectedGenericDoc; + private record Pairing(string S1, string S2); - protected override void OnInitialized() + private IEnumerable GetPairings() { - pairings = DocDatabase.Docs - .Where(d => d.Category == "Syndicate Pairings") - .OrderBy(d => d.Title) - .ToList(); - } - - private void OpenPairing(DocData doc) - { - selectedDoc = doc; - } - - private void ClosePairing() - { - selectedDoc = null; - } - - private void CloseCard() - { - selectedCard = null; - } - - private void CloseGenericDoc() - { - selectedGenericDoc = null; - } - - private void HandleNavigate(string name) - { - var card = CardDatabase.Cards.FirstOrDefault(c => string.Equals(c.Name, name, StringComparison.OrdinalIgnoreCase)); - if (card != null) + var syndicates = new[] { "Singularity", "Phasetide", "Silence", "Lifeblood", "Sungrace", "Splintergleam" }; + for (int i = 0; i < syndicates.Length; i++) { - if (card.Category is "Agent" or "Spell") + for (int j = i + 1; j < syndicates.Length; j++) { - selectedCard = card; + yield return new Pairing(syndicates[i], syndicates[j]); } - else - { - selectedGenericDoc = new DocData { Title = card.Name, Content = card.Description ?? "", Category = card.Category }; - } - - StateHasChanged(); - return; - } - - var doc = DocDatabase.Docs.FirstOrDefault(d => string.Equals(d.Title, name, StringComparison.OrdinalIgnoreCase)); - if (doc != null) - { - selectedGenericDoc = doc; - StateHasChanged(); } } + private void NavigateToDecks(Pairing pairing) + { + Nav.NavigateTo($"/decks?s1={pairing.S1}&s2={pairing.S2}"); + } } diff --git a/Chrono/Standalone/Components/Layout/NavMenu.razor b/Chrono/Standalone/Components/Layout/NavMenu.razor index 7ac5ac3..270ed09 100644 --- a/Chrono/Standalone/Components/Layout/NavMenu.razor +++ b/Chrono/Standalone/Components/Layout/NavMenu.razor @@ -36,11 +36,6 @@ Syndicates
-