import gradio as gr from pathlib import Path from playwright.async_api import async_playwright import asyncio, os MOVE_ORDER = ["ArrowUp", "ArrowLeft", "ArrowRight", "ArrowDown"] async def get_score(page): el = await page.query_selector(".score-container") if not el: return 0 text = (await el.inner_text()).strip() try: return int(text.split()[0].replace(",", "")) except Exception: return 0 async def is_game_over(page): msg = await page.query_selector(".game-message.game-over") return msg is not None async def board_signature(page): tiles = await page.query_selector_all(".tile") parts = [] for t in tiles: classes = (await t.get_attribute("class")) or "" parts.append(classes) return "|".join(sorted(parts)) async def play_offline_2048(moves: int = 200): logs = [] img_path = Path("2048_offline_final.png") html_path = Path(__file__).parent / "static" / "2048.html" url = f"file://{html_path.resolve()}" async with async_playwright() as p: browser = await p.chromium.launch( headless=True, args=["--no-sandbox", "--disable-setuid-sandbox", "--allow-file-access-from-files"] ) page = await browser.new_page(viewport={"width": 900, "height": 1100}) await page.goto(url, wait_until="load", timeout=60000) logs.append("Offline 2048 loaded. Starting auto-play...") game = await page.query_selector(".game-container") if game: await game.click() last_sig = await board_signature(page) invalid_count = 0 played = 0 for i in range(moves): move = MOVE_ORDER[i % len(MOVE_ORDER)] await page.keyboard.press(move) await page.wait_for_timeout(60) sig = await board_signature(page) if sig == last_sig: for alt in MOVE_ORDER[1:]: await page.keyboard.press(alt) await page.wait_for_timeout(50) new_sig = await board_signature(page) if new_sig != sig: sig = new_sig break else: invalid_count += 1 else: invalid_count = 0 last_sig = sig played += 1 score = await get_score(page) logs.append(f"Move {played:03d} → score {score}") if await is_game_over(page): logs.append("Game Over detected. Stopping.") break if invalid_count >= 3: first = MOVE_ORDER.pop(0) MOVE_ORDER.append(first) invalid_count = 0 logs.append(f"Rotated move order to escape dead pattern: {MOVE_ORDER}") await page.screenshot(path=str(img_path), full_page=True) await browser.close() return str(img_path), "\n".join(logs) async def run(moves): return await play_offline_2048(moves=int(moves)) ui = gr.Interface( fn=run, inputs=gr.Slider(50, 600, value=250, step=10, label="Max moves"), outputs=[gr.Image(label="Final Screenshot"), gr.Textbox(label="Logs", lines=15)], title="AI Auto-Player: 2048 (Offline, Self-Contained)", description="Playwright launches a local offline copy of 2048 bundled in the Space. Works with internet disabled." ) if __name__ == "__main__": ui.launch()