APP_DESCRIPTION_HTML = """

đŸŽĨ Watch the Demo

🧩 What This App Does

This dashboard lets you watch (or join!) teams of Large Language Models (LLMs) play Codenames against each other. Two teams — Red and Blue — face off in a 4v4 format. Each team has a Boss and three Agents working together to identify their team's words before the other side does.

🤖 How It Works

🧠 Why It's Interesting

đŸ•šī¸ Main Features

""" # Game rules HTML EXAMPLE_GAME_RULES_HTML = """

đŸ‘Ĩ Team Roles

Each team has four members with distinct responsibilities:

📋 What Each Role Sees

🎮 How a Turn Works

1ī¸âƒŖ Boss Gives a Clue

The Red Boss (seeing the board) might say:

"Atmosphere: 2"

This clue suggests 2 red words are related to "atmosphere". Looking at the board, the Boss is thinking of:

âš ī¸ Important: The clue must be ONE word and ONE number. The number indicates how many words relate to that clue.

2ī¸âƒŖ Team Discussion

The Captain and Players discuss without seeing the colors:

3ī¸âƒŖ Captain Makes Final Selection

The Captain decides which words to touch, in order:

  1. AIR ✅ (Red - Correct!)
  2. SPACE ✅ (Red - Correct!)

The team can stop after any correct guess or continue up to the number given (+1 bonus from previous turns if applicable).

âš ī¸ Mistakes to Avoid

🏆 Winning the Game

The game ends when:

💡 Strategy Tips

For the Boss:

For Captain & Players:

""" # Game board description HTML EXAMPLE_GAME_BOARD_HTML = """

đŸŽ¯ Example Game Board

Below is a sample Codenames board showing the Boss's view with color-coded words:

Remember: Only the Boss sees these colors (image above). The Captain and Players only see the words (image below)!

""" GAME_RULES_HTML = """
""" JS = r""" function initCarousel() { function forceDarkMode() { // Set dark mode on the root elements const gradioContainer = document.querySelector('.gradio-container'); if (gradioContainer) { gradioContainer.classList.add('dark'); gradioContainer.setAttribute('data-theme', 'dark'); } // Set dark mode on document root document.documentElement.setAttribute('data-theme', 'dark'); document.documentElement.style.colorScheme = 'dark'; // Set dark mode on body document.body.setAttribute('data-theme', 'dark'); document.body.classList.add('dark'); // Force dark color scheme const style = document.createElement('style'); style.textContent = ` :root { color-scheme: dark !important; } .gradio-container { color-scheme: dark !important; } body { background-color: #0b0f19 !important; color: #ffffff !important; } `; document.head.appendChild(style); } // Apply dark mode immediately forceDarkMode(); // Also apply after DOM is fully loaded if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', forceDarkMode); } // Re-apply dark mode when Gradio updates the DOM const observer = new MutationObserver(forceDarkMode); observer.observe(document.body, { childList: true, subtree: true }); // --- Carousel setup --- function waitForElements(selectors, callback) { const interval = setInterval(() => { const allExist = selectors.every(sel => document.querySelector(sel)); if (allExist) { clearInterval(interval); callback(); } }, 100); } waitForElements( ['.rules-carousel-track', '.carousel-btn-prev', '.carousel-btn-next'], function() { const track = document.querySelector('.rules-carousel-track'); const prevBtn = document.querySelector('.carousel-btn-prev'); const nextBtn = document.querySelector('.carousel-btn-next'); const indicators = document.querySelectorAll('.carousel-indicator'); const cards = document.querySelectorAll('.rule-card'); let currentIndex = 0; const totalCards = cards.length; function updateCarousel() { const offset = -currentIndex * 100; track.style.transform = `translateX(${offset}%)`; indicators.forEach((indicator, index) => { indicator.classList.toggle('active', index === currentIndex); }); prevBtn.disabled = currentIndex === 0; nextBtn.disabled = currentIndex === totalCards - 1; } prevBtn.addEventListener('click', () => { if (currentIndex > 0) { currentIndex--; updateCarousel(); } }); nextBtn.addEventListener('click', () => { if (currentIndex < totalCards - 1) { currentIndex++; updateCarousel(); } }); indicators.forEach((indicator, index) => { indicator.addEventListener('click', () => { currentIndex = index; updateCarousel(); }); }); updateCarousel(); } ); // --- Boss name helpers (attach to global window) --- window.showBossNameInput = function(team) { console.log(team); if (team === 'red') { const btn = document.getElementById('red_boss_btn'); const cancel_blue_btn = document.getElementById('cancel_blue_boss_btn'); if (btn) btn.click(); if (cancel_blue_btn) cancel_blue_btn.click(); setTimeout(() => { const red_boss_input = document.getElementById('red_boss_input'); if (red_boss_input) { red_boss_input.scrollIntoView({ behavior: 'smooth', block: 'center' }); red_boss_input.focus(); } }, 200); } else if (team === 'blue') { const btn = document.getElementById('blue_boss_btn'); const cancel_red_btn = document.getElementById('cancel_red_boss_btn'); if (btn) btn.click(); if (cancel_red_btn) cancel_red_btn.click(); setTimeout(() => { const blue_boss_input = document.getElementById('blue_boss_input'); if (blue_boss_input) { blue_boss_input.scrollIntoView({ behavior: 'smooth', block: 'center' }); blue_boss_input.focus(); } }, 200); } }; window.refreshStats = function() { console.log("aooo"); const btn = document.getElementById('refresh_btn'); if (btn) btn.click(); }; // --- Tab navigation setup --- function setupTabNavigation() { // Wait for tab buttons to be rendered setTimeout(() => { const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach((link) => { link.addEventListener('click', (e) => { e.preventDefault(); const tabId = link.getAttribute('data-tab-id'); // Find the tab button using data-tab-id attribute const tabButton = document.querySelector(`button[data-tab-id="${tabId}_tab"]`); if (tabButton) { // Click the actual tab button tabButton.click(); // Update active state on nav links navLinks.forEach((l) => l.classList.remove('active')); link.classList.add('active'); } else { console.log('Tab button not found for id:', tabId); } }); }); }, 500); } setupTabNavigation(); return "Carousel + BossName + TabNavigation JS initialized"; } """ JS_BTN = """ () => { // Hide all "Play as Boss" buttons when first message is sent const bossBtns = document.querySelectorAll('.play-as-boss-btn'); bossBtns.forEach(btn => { btn.style.display = 'none'; }); } """ CODENAMES_WORDS = [ "AGENT", "AFRICA", "AIR", "ALIEN", "ALPS", "AMAZON", "AMBULANCE", "AMERICA", "ANGEL", "ANTARCTICA", "APPLE", "ARM", "ATLANTIS", "AUSTRALIA", "AZTEC", "BACK", "BALL", "BAND", "BANK", "BAR", "BARK", "BAT", "BATTERY", "BEACH", "BEAR", "BEAT", "BED", "BEIJING", "BELL", "BELT", "BERLIN", "BERMUDA", "BERRY", "BILL", "BLOCK", "BOARD", "BOLT", "BOMB", "BOND", "BOOM", "BOOT", "BOTTLE", "BOW", "BOX", "BRIDGE", "BRUSH", "BUCK", "BUFFALO", "BUG", "BUGLE", "BUTTON", "CALF", "CANADA", "CAP", "CAPITAL", "CAR", "CARD", "CARROT", "CASINO", "CAST", "CAT", "CELL", "CENTAUR", "CENTER", "CHAIR", "CHANGE", "CHARGE", "CHECK", "CHEST", "CHICK", "CHINA", "CHOCOLATE", "CHURCH", "CIRCLE", "CLIFF", "CLOAK", "CLUB", "CODE", "COLD", "COMIC", "COMPOUND", "CONCERT", "CONDUCTOR", "CONTRACT", "COOK", "COPPER", "COTTON", "COURT", "COVER", "CRANE", "CRASH", "CRICKET", "CROSS", "CROWN", "CYCLE", "CZECH", "DANCE", "DATE", "DAY", "DEATH", "DECK", "DEGREE", "DIAMOND", "DICE", "DINOSAUR", "DISEASE", "DOCTOR", "DOG", "DRAFT", "DRAGON", "DRESS", "DRILL", "DROP", "DUCK", "DWARF", "EAGLE", "EGYPT", "EMBASSY", "ENGINE", "ENGLAND", "EUROPE", "EYE", "FACE", "FAIR", "FALL", "FAN", "FENCE", "FIELD", "FIGHTER", "FIGURE", "FILE", "FILM", "FIRE", "FISH", "FLUTE", "FLY", "FOOT", "FORCE", "FOREST", "FORK", "FRANCE", "GAME", "GAS", "GENIUS", "GERMANY", "GHOST", "GIANT", "GLASS", "GLOVE", "GOLD", "GRACE", "GRASS", "GREECE", "GREEN", "GROUND", "HAM", "HAND", "HAWK", "HEAD", "HEART", "HELICOPTER", "HIMALAYAS", "HOLE", "HOLLYWOOD", "HONEY", "HOOD", "HOOK", "HORN", "HORSE", "HORSESHOE", "HOSPITAL", "HOTEL", "ICE", "INDIA", "IRON", "IVORY", "JACK", "JAM", "JET", "JUPITER", "KANGAROO", "KETCHUP", "KEY", "KID", "KING", "KIWI", "KNIFE", "KNIGHT", "LAB", "LAP", "LASER", "LAWYER", "LEAD", "LEMON", "LEPRECHAUN", "LIFE", "LIGHT", "LIMOUSINE", "LINE", "LINK", "LION", "LITTER", "LOCH NESS", "LOCK", "LOG", "LONDON", "LUCK", "MAIL", "MAMMOTH", "MAPLE", "MARBLE", "MARCH", "MASS", "MATCH", "MERCURY", "MEXICO", "MICROSCOPE", "MILLIONAIRE", "MINE", "MINT", "MISSILE", "MODEL", "MOLE", "MOON", "MOSCOW", "MOUNT", "MOUSE", "MOUTH", "MUG", "NAIL", "NEEDLE", "NET", "NEW YORK", "NIGHT", "NINJA", "NOTE", "NOVEL", "NURSE", "NUT", "OCTOPUS", "OIL", "OLIVE", "OLYMPUS", "OPERA", "ORANGE", "ORGAN", "PALM", "PAN", "PANTS", "PAPER", "PARACHUTE", "PARK", "PART", "PASS", "PASTE", "PENGUIN", "PHOENIX", "PIANO", "PIE", "PILOT", "PIN", "PIPE", "PIRATE", "PISTOL", "PIT", "PITCH", "PLANE", "PLASTIC", "PLATE", "PLATYPUS", "PLAY", "PLOT", "POINT", "POISON", "POLE", "POLICE", "POOL", "PORT", "POST", "POUND", "PRESS", "PRINCESS", "PUMPKIN", "PUPIL", "PYRAMID", "QUEEN", "RABBIT", "RACKET", "RAY", "REVOLUTION", "RING", "ROBIN", "ROBOT", "ROCK", "ROME", "ROOT", "ROSE", "ROULETTE", "ROUND", "ROW", "RULER", "SATELLITE", "SATURN", "SCALE", "SCHOOL", "SCIENTIST", "SCORPION", "SCREEN", "SCUBA DIVER", "SEAL", "SERVER", "SHADOW", "SHAKESPEARE", "SHARK", "SHIP", "SHOE", "SHOP", "SHOT", "SINK", "SKYSCRAPER", "SLIP", "SLUG", "SMUGGLER", "SNOW", "SNOWMAN", "SOCK", "SOLDIER", "SOUL", "SOUND", "SPACE", "SPELL", "SPIDER", "SPIKE", "SPINE", "SPOT", "SPRING", "SPY", "SQUARE", "STADIUM", "STAFF", "STAR", "STATE", "STICK", "STOCK", "STRAW", "STREAM", "STRIKE", "STRING", "SUB", "SUIT", "SUPERHERO", "SWING", "SWITCH", "TABLE", "TABLET", "TAG", "TAIL", "TAP", "TEACHER", "TELESCOPE", "TEMPLE", "THEATER", "THIEF", "THUMB", "TICK", "TIE", "TIME", "TOKYO", "TOOTH", "TORCH", "TOWER", "TRACK", "TRAIN", "TRIANGLE", "TRIP", "TRUNK", "TUBE", "TURKEY", "UNDERTAKER", "UNICORN", "VACUUM", "VAN", "VET", "WAKE", "WALL", "WAR", "WASHER", "WASHINGTON", "WATCH", "WATER", "WAVE", "WEB", "WELL", "WHALE", "WHIP", "WIND", "WITCH", "WORM", "YARD" ] NAMES = [ "Astra", "Nova", "Echo", "Luna", "Orion", "Vega", "Zephyr", "Sol" ] TEAM_MODEL_PRESETS = { "openai": { "boss": "gpt-5", "captain": "gpt-5-mini", "players": ["gpt-5-nano", "gpt-4.1-nano"] }, "google": { "boss": "gemini-2.5-pro", "captain": "gemini-2.5-flash", "players": ["gemini-2.0-flash-001", "gemini-2.0-flash-lite-001"] }, "anthropic": { "boss": "claude-sonnet-4-5-20250929", "captain": "claude-3-7-sonnet-20250219", "players": ["claude-3-5-haiku-20241022", "claude-3-haiku-20240307"] }, "opensource": { "boss": "moonshotai/Kimi-K2-Thinking", # "deepseek-ai/DeepSeek-V3.1", "captain": "deepseek-ai/DeepSeek-R1", # "Qwen/Qwen3-235B-A22B-Instruct-2507", "players": ["openai/gpt-oss-120b", "openai/gpt-oss-20b"] }, } # 🧠 Available model pool for random mode ALL_MODELS = sorted({ model for preset in TEAM_MODEL_PRESETS.values() for model in ([preset["boss"], preset["captain"]] + preset["players"]) }) # Custom header HTML custom_header = """
"""