Spaces:
Sleeping
Sleeping
| 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() | |