luqmanabban commited on
Commit
0fc7635
·
verified ·
1 Parent(s): 2d92cc8

Upload 4 files

Browse files
Files changed (4) hide show
  1. app.py +128 -0
  2. packages.txt +23 -0
  3. postBuild +3 -0
  4. requirements.txt +2 -0
app.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import asyncio, time
3
+ from pathlib import Path
4
+ from typing import AsyncGenerator, Tuple
5
+ from playwright.async_api import async_playwright
6
+
7
+ MOVE_ORDER_DEFAULT = ["ArrowUp", "ArrowLeft", "ArrowRight", "ArrowDown"]
8
+
9
+ async def get_score(page):
10
+ el = await page.query_selector(".score-container")
11
+ if not el:
12
+ return 0
13
+ text = (await el.inner_text()).strip()
14
+ try:
15
+ return int(text.split()[0].replace(",", ""))
16
+ except Exception:
17
+ return 0
18
+
19
+ async def is_game_over(page):
20
+ msg = await page.query_selector(".game-message.game-over, .game-over")
21
+ return msg is not None
22
+
23
+ async def board_signature(page):
24
+ tiles = await page.query_selector_all(".tile")
25
+ parts = []
26
+ for t in tiles:
27
+ classes = (await t.get_attribute("class")) or ""
28
+ parts.append(classes)
29
+ return "|".join(sorted(parts))
30
+
31
+ async def autoplay_stream(url:str, moves:int, fps:int, strategy:str) -> AsyncGenerator[Tuple[str,str], None]:
32
+ """Yields (frame_path, logs_text) repeatedly so the first output streams frames."""
33
+ logs = []
34
+ img_path = Path("live_frame.png")
35
+ final_path = Path("final_frame.png")
36
+ move_order = MOVE_ORDER_DEFAULT.copy()
37
+ if strategy == "Left/Down Bias":
38
+ move_order = ["ArrowLeft", "ArrowDown", "ArrowRight", "ArrowUp"]
39
+ elif strategy == "Clockwise":
40
+ move_order = ["ArrowUp", "ArrowRight", "ArrowDown", "ArrowLeft"]
41
+
42
+ frame_delay_ms = max(50, int(1000 / max(1, fps)))
43
+
44
+ async with async_playwright() as p:
45
+ browser = await p.chromium.launch(
46
+ headless=True,
47
+ args=["--no-sandbox", "--disable-setuid-sandbox"]
48
+ )
49
+ page = await browser.new_page(viewport={"width": 900, "height": 1100})
50
+ await page.goto(url, wait_until="load", timeout=60000)
51
+ logs.append(f"Loaded: {url}")
52
+ yield str(img_path), "\n".join(logs)
53
+
54
+ # Make sure the game surface has focus
55
+ await page.mouse.click(50, 50)
56
+ last_sig = await board_signature(page)
57
+ invalid_count = 0
58
+ played = 0
59
+ last_frame_time = 0
60
+
61
+ for i in range(moves):
62
+ await page.keyboard.press(move_order[i % 4])
63
+ await page.wait_for_timeout(60)
64
+
65
+ sig = await board_signature(page)
66
+ if sig == last_sig:
67
+ for alt in move_order[1:]:
68
+ await page.keyboard.press(alt)
69
+ await page.wait_for_timeout(50)
70
+ new_sig = await board_signature(page)
71
+ if new_sig != sig:
72
+ sig = new_sig
73
+ break
74
+ else:
75
+ invalid_count += 1
76
+ else:
77
+ invalid_count = 0
78
+
79
+ last_sig = sig
80
+ played += 1
81
+ sc = await get_score(page)
82
+ logs.append(f"Move {played:03d} | Score {sc}")
83
+ # throttle logs to last 60 lines
84
+ if len(logs) > 60:
85
+ logs = logs[-60:]
86
+
87
+ # Stream a frame at desired fps
88
+ now = time.time()*1000
89
+ if now - last_frame_time >= frame_delay_ms:
90
+ await page.screenshot(path=str(img_path), full_page=True)
91
+ last_frame_time = now
92
+ yield str(img_path), "\n".join(logs)
93
+
94
+ if await is_game_over(page):
95
+ logs.append("Game Over detected. Stopping.")
96
+ break
97
+
98
+ if invalid_count >= 3:
99
+ first = move_order.pop(0)
100
+ move_order.append(first)
101
+ invalid_count = 0
102
+ logs.append(f"Rotated move order: {move_order}")
103
+
104
+ await page.screenshot(path=str(final_path), full_page=True)
105
+ yield str(final_path), "\n".join(logs) # final frame
106
+ await browser.close()
107
+
108
+ with gr.Blocks(title="Online Game Auto-Player (Live Stream)") as demo:
109
+ gr.Markdown("# Online Game Auto-Player (Live Stream)")
110
+ gr.Markdown("Plays an **ONLINE** game in a headless browser and streams frames while it plays. "
111
+ "Default URL is play2048.co. You can replace with any accessible game URL that reacts to arrow keys or clicks.")
112
+ with gr.Row():
113
+ url_in = gr.Textbox(label="Game URL", value="https://play2048.co")
114
+ with gr.Row():
115
+ moves = gr.Slider(50, 1200, value=300, step=10, label="Max moves")
116
+ fps = gr.Slider(1, 15, value=5, step=1, label="Stream FPS")
117
+ strat = gr.Dropdown(choices=["Up/Left Bias","Left/Down Bias","Clockwise"], value="Up/Left Bias", label="Move Strategy")
118
+ start = gr.Button("Start")
119
+ with gr.Row():
120
+ stream_out = gr.Image(label="Live View (frames)", streaming=True)
121
+ logs_out = gr.Textbox(label="Logs (tail)", lines=18)
122
+ gr.Markdown("**Note:** Internet access must be enabled in Space settings for online URLs. "
123
+ "On free (CPU Basic) Spaces this option is generally unavailable.")
124
+
125
+ start.click(fn=autoplay_stream, inputs=[url_in, moves, fps, strat], outputs=[stream_out, logs_out])
126
+
127
+ if __name__ == "__main__":
128
+ demo.launch()
packages.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ libnss3
2
+ libatk1.0-0
3
+ libatk-bridge2.0-0
4
+ libasound2
5
+ libx11-6
6
+ libx11-xcb1
7
+ libxcb1
8
+ libxcomposite1
9
+ libxdamage1
10
+ libxext6
11
+ libxfixes3
12
+ libxrandr2
13
+ libxshmfence1
14
+ libgbm1
15
+ libgtk-3-0
16
+ libpangocairo-1.0-0
17
+ libpango-1.0-0
18
+ libcairo2
19
+ libcups2
20
+ libdrm2
21
+ libdbus-1-3
22
+ ca-certificates
23
+ fonts-liberation
postBuild ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ #!/bin/bash
2
+ set -e
3
+ python -m playwright install chromium
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio==4.44.0
2
+ playwright==1.46.0