| import gradio as gr |
| import requests |
| import json |
| from meta_data import TOP_META_DECKS_2026, SYNERGIES, CARD_TYPES, ALL_CARDS, EVOLUTION_CARDS, HEROES |
|
|
| |
| |
| CR_API_BASE = "https://api.clashroyale.com/v1" |
| ROYALEAPI_BASE = "https://api.royaleapi.com/v1" |
|
|
| def get_player_cards(tag): |
| """Fetch player cards from Clash Royale API.""" |
| |
| tag = tag.strip().upper() |
| if not tag.startswith('#'): |
| tag = '#' + tag |
| |
| |
| |
| try: |
| |
| |
| return {"mock": True, "tag": tag, "message": "API key required. Add your Clash Royale API key to fetch real data."} |
| except Exception as e: |
| return {"error": str(e)} |
|
|
| def analyze_meta(): |
| """Analyze current meta decks and return summary.""" |
| archetypes = {} |
| tiers = {} |
| top_cards = {} |
| |
| for deck in TOP_META_DECKS_2026: |
| |
| arch = deck["archetype"] |
| archetypes[arch] = archetypes.get(arch, 0) + 1 |
| |
| |
| tier = deck["tier"] |
| tiers[tier] = tiers.get(tier, 0) + 1 |
| |
| |
| for card in deck["cards"]: |
| top_cards[card] = top_cards.get(card, 0) + 1 |
| |
| |
| sorted_cards = sorted(top_cards.items(), key=lambda x: x[1], reverse=True) |
| |
| output = "## 2026 META ANALYSIS\n\n" |
| output += f"**Total Meta Decks Analyzed:** {len(TOP_META_DECKS_2026)}\n\n" |
| |
| output += "### Archetype Distribution\n" |
| for arch, count in sorted(archetypes.items(), key=lambda x: x[1], reverse=True): |
| output += f"- {arch}: {count} decks\n" |
| |
| output += "\n### Tier Distribution\n" |
| for tier, count in sorted(tiers.items()): |
| output += f"- {tier}: {count} decks\n" |
| |
| output += "\n### Top 20 Most Used Cards\n" |
| for i, (card, count) in enumerate(sorted_cards[:20], 1): |
| card_type = CARD_TYPES.get(card, "unknown") |
| output += f"{i}. **{card}** ({card_type}) - in {count} decks\n" |
| |
| output += "\n### Best Decks by Win Rate\n" |
| sorted_decks = sorted(TOP_META_DECKS_2026, key=lambda x: x["win_rate"], reverse=True) |
| for i, deck in enumerate(sorted_decks[:10], 1): |
| output += f"{i}. **{deck['name']}** - {deck['win_rate']}% WR ({deck['tier']} tier, {deck['archetype']})\n" |
| output += f" Cards: {', '.join(deck['cards'])}\n\n" |
| |
| return output |
|
|
| def get_card_synergies(card_name): |
| """Get synergies for a specific card.""" |
| card_name = card_name.strip().title() |
| |
| if card_name not in ALL_CARDS: |
| return f"Card '{card_name}' not found. Available cards: {', '.join(sorted(ALL_CARDS))}" |
| |
| synergies = SYNERGIES.get(card_name, []) |
| card_type = CARD_TYPES.get(card_name, "unknown") |
| |
| output = f"## {card_name}\n" |
| output += f"**Type:** {card_type}\n\n" |
| |
| if card_name in EVOLUTION_CARDS: |
| output += "**Has Evolution:** Yes\n\n" |
| if card_name in HEROES: |
| output += "**Type:** Hero\n\n" |
| |
| if synergies: |
| output += "### Best Synergies\n" |
| for syn in synergies: |
| syn_type = CARD_TYPES.get(syn, "unknown") |
| output += f"- {syn} ({syn_type})\n" |
| |
| |
| containing_decks = [d for d in TOP_META_DECKS_2026 if card_name in d["cards"]] |
| if containing_decks: |
| output += f"\n### Meta Decks Using {card_name}\n" |
| for deck in containing_decks: |
| output += f"- **{deck['name']}** ({deck['tier']} tier, {deck['win_rate']}% WR)\n" |
| |
| return output |
|
|
| def recommend_deck(player_cards_input): |
| """Recommend a deck based on player's highest level cards.""" |
| try: |
| |
| player_cards = {} |
| |
| if player_cards_input.strip().startswith('{'): |
| |
| player_cards = json.loads(player_cards_input) |
| else: |
| |
| for item in player_cards_input.split(','): |
| if ':' in item: |
| card, level = item.strip().rsplit(':', 1) |
| player_cards[card.strip()] = int(level.strip()) |
| else: |
| player_cards[item.strip()] = 14 |
| |
| if not player_cards: |
| return "No cards provided. Format: 'Card:Level, Card:Level' or JSON" |
| |
| |
| deck_scores = [] |
| |
| for deck in TOP_META_DECKS_2026: |
| score = 0 |
| missing_cards = [] |
| total_level = 0 |
| |
| for card in deck["cards"]: |
| if card in player_cards: |
| level = player_cards[card] |
| total_level += level |
| |
| score += level * 10 |
| |
| if level >= 14: |
| score += 50 |
| elif level >= 13: |
| score += 30 |
| else: |
| missing_cards.append(card) |
| |
| |
| score -= len(missing_cards) * 100 |
| |
| |
| if len(deck["cards"]) > len(missing_cards): |
| avg_level = total_level / (len(deck["cards"]) - len(missing_cards)) |
| score += avg_level * 5 |
| |
| |
| score += deck["win_rate"] * 2 |
| |
| deck_scores.append({ |
| "deck": deck, |
| "score": score, |
| "missing": missing_cards, |
| "avg_level": total_level / max(1, len(deck["cards"]) - len(missing_cards)) |
| }) |
| |
| |
| deck_scores.sort(key=lambda x: x["score"], reverse=True) |
| |
| |
| output = "## DECK RECOMMENDATIONS\n\n" |
| output += f"Based on {len(player_cards)} cards in your collection\n\n" |
| |
| for i, rec in enumerate(deck_scores[:5], 1): |
| deck = rec["deck"] |
| output += f"### {i}. {deck['name']} (Score: {rec['score']:.0f})\n" |
| output += f"**Archetype:** {deck['archetype']} | **Tier:** {deck['tier']} | **Win Rate:** {deck['win_rate']}%\n\n" |
| |
| output += "**Cards:**\n" |
| for card in deck["cards"]: |
| if card in player_cards: |
| level = player_cards[card] |
| output += f"- {card}: Level {level}" |
| if level >= 14: |
| output += " MAXED" |
| output += "\n" |
| else: |
| output += f"- {card}: **MISSING**\n" |
| |
| if rec["missing"]: |
| output += f"\n**Missing Cards:** {', '.join(rec['missing'])}\n" |
| output += "*Consider requesting these from your clan or trading*\n" |
| |
| output += f"\n**Average Level of Owned Cards:** {rec['avg_level']:.1f}\n\n" |
| output += "---\n\n" |
| |
| return output |
| |
| except Exception as e: |
| return f"Error: {str(e)}\n\nExpected format:\n- JSON: {'{\"Card\": 14, \"Card2\": 13}'}\n- Or: Card:14, Card2:13, Card3:12" |
|
|
| def get_all_cards_list(): |
| """Return list of all cards with types.""" |
| output = "## ALL CLASH ROYALE CARDS\n\n" |
| |
| |
| by_type = {} |
| for card, ctype in CARD_TYPES.items(): |
| by_type.setdefault(ctype, []).append(card) |
| |
| for ctype in sorted(by_type.keys()): |
| output += f"### {ctype.replace('_', ' ').title()}s ({len(by_type[ctype])})\n" |
| for card in sorted(by_type[ctype]): |
| markers = [] |
| if card in EVOLUTION_CARDS: |
| markers.append("Evo") |
| if card in HEROES: |
| markers.append("Hero") |
| marker_str = f" [{' | '.join(markers)}]" if markers else "" |
| output += f"- {card}{marker_str}\n" |
| output += "\n" |
| |
| output += f"\n**Total Cards:** {len(ALL_CARDS)}\n" |
| output += f"**Evolutions Available:** {len(EVOLUTION_CARDS)}\n" |
| output += f"**Heroes:** {len(HEROES)}\n" |
| |
| return output |
|
|
| |
| with gr.Blocks(title="Clash Royale Deck Recommender 2026", theme=gr.themes.Soft()) as demo: |
| gr.Markdown(""" |
| # Clash Royale Meta Analyzer & Deck Recommender 2026 |
| |
| Analyze the current meta, check card synergies, and get personalized deck recommendations based on your card levels. |
| |
| **Data Sources:** RoyaleAPI, BestClashDecks, community tier lists (Updated for 2026 meta) |
| """) |
| |
| with gr.Tab("Meta Analysis"): |
| gr.Markdown("View the current best decks and most popular cards in the 2026 meta.") |
| analyze_btn = gr.Button("Analyze Meta", variant="primary") |
| meta_output = gr.Markdown() |
| analyze_btn.click(analyze_meta, outputs=meta_output) |
| |
| with gr.Tab("Card Synergies"): |
| gr.Markdown("Check which cards work best together and what meta decks use them.") |
| card_input = gr.Textbox(label="Card Name", placeholder="e.g. Goblin Giant, P.E.K.K.A, Hog Rider") |
| synergy_btn = gr.Button("Get Synergies", variant="primary") |
| synergy_output = gr.Markdown() |
| synergy_btn.click(get_card_synergies, inputs=card_input, outputs=synergy_output) |
| |
| with gr.Tab("Deck Recommender"): |
| gr.Markdown(""" |
| Get a deck recommendation based on your highest level cards. |
| |
| **Input Format:** |
| - JSON: `{"Goblin Giant": 14, "Sparky": 13, "Dark Prince": 12}` |
| - Simple: `Goblin Giant:14, Sparky:13, Dark Prince:12` |
| """) |
| cards_input = gr.Textbox( |
| label="Your Cards & Levels", |
| placeholder='{"Goblin Giant": 14, "Sparky": 13, "Zap": 14}', |
| lines=5 |
| ) |
| recommend_btn = gr.Button("Recommend Deck", variant="primary") |
| recommend_output = gr.Markdown() |
| recommend_btn.click(recommend_deck, inputs=cards_input, outputs=recommend_output) |
| |
| with gr.Tab("All Cards"): |
| gr.Markdown("Complete list of all cards including evolutions and heroes.") |
| cards_btn = gr.Button("Show All Cards", variant="primary") |
| cards_output = gr.Markdown() |
| cards_btn.click(get_all_cards_list, outputs=cards_output) |
| |
| with gr.Tab("Player Lookup (API)"): |
| gr.Markdown(""" |
| **Coming Soon:** Look up your actual player data using your Clash Royale tag. |
| |
| To use this feature, you need a Clash Royale API key from [developer.clashroyale.com](https://developer.clashroyale.com/) |
| |
| Your player tag can be found in your Clash Royale profile (starts with #) |
| """) |
| tag_input = gr.Textbox(label="Player Tag", placeholder="#2YUGQP28V") |
| lookup_btn = gr.Button("Look Up Player", variant="primary") |
| lookup_output = gr.JSON() |
| lookup_btn.click(get_player_cards, inputs=tag_input, outputs=lookup_output) |
| |
| gr.Markdown(""" |
| --- |
| **Note:** This tool uses aggregated meta data from RoyaleAPI, BestClashDecks, and community tier lists. |
| For the most accurate personal recommendations, enter your actual card levels in the Deck Recommender tab. |
| """) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|