npc0 commited on
Commit
3ebd172
·
verified ·
1 Parent(s): 4073fd3

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -0
app.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import time
3
+ import math
4
+ from collections import deque, defaultdict
5
+
6
+ # --- 設定參數 ---
7
+ MAX_RECENT_EVENTS = 1000 # 暫存區大小 (達到此數量觸發壓縮)
8
+ GRID_PRECISION = 4 # 網格精度 (4小數點約=11公尺)
9
+ MAX_GRID_INTENSITY = 20.0 # 單一網格最大亮度 (避免單點過曝)
10
+
11
+ # --- 資料結構 ---
12
+ # 1. 近期事件 (Delta Queue): 用於快速同步
13
+ # 格式: {'t': time, 'u': user_id, 'l': [lat, lng], 's': speed}
14
+ recent_events = deque()
15
+
16
+ # 2. 歷史網格 (Snapshot Grid): 儲存壓縮後的長期記憶
17
+ # Key: (lat_round, lng_round), Value: intensity
18
+ archived_grid = defaultdict(float)
19
+
20
+ # 3. 狀態版本控制
21
+ snapshot_version = 0
22
+ last_snapshot_time = time.time()
23
+
24
+ def get_intensity_by_speed(speed_kmh):
25
+ """
26
+ 根據速度決定迷霧驅散強度 (亮度)
27
+ < 5 km/h (步行/停留): 強度 1.0
28
+ < 20 km/h (跑步/腳踏車): 強度 0.5
29
+ > 20 km/h (車輛): 強度 0.1
30
+ """
31
+ if speed_kmh is None: return 0.5
32
+ if speed_kmh < 5: return 1.0
33
+ if speed_kmh < 20: return 0.5
34
+ return 0.1
35
+
36
+ def compress_events_to_grid(events_list):
37
+ """將事件列表壓縮進網格"""
38
+ global archived_grid
39
+ for event in events_list:
40
+ lat, lng = event['l']
41
+ speed = event['s']
42
+
43
+ # 網格化座標
44
+ grid_key = (round(lat, GRID_PRECISION), round(lng, GRID_PRECISION))
45
+
46
+ # 累加亮度
47
+ intensity = get_intensity_by_speed(speed)
48
+ archived_grid[grid_key] += intensity
49
+
50
+ # 限制最大亮度 (防止單點無限疊加)
51
+ if archived_grid[grid_key] > MAX_GRID_INTENSITY:
52
+ archived_grid[grid_key] = MAX_GRID_INTENSITY
53
+
54
+ def trigger_snapshot():
55
+ """檢查並執行快照壓縮"""
56
+ global recent_events, snapshot_version, last_snapshot_time
57
+
58
+ # 如果暫存區超過閾值,將最舊的一半資料壓縮進歷史網格
59
+ if len(recent_events) > MAX_RECENT_EVENTS:
60
+ events_to_archive = []
61
+ count_to_remove = int(MAX_RECENT_EVENTS / 2)
62
+
63
+ for _ in range(count_to_remove):
64
+ events_to_archive.append(recent_events.popleft())
65
+
66
+ compress_events_to_grid(events_to_archive)
67
+
68
+ snapshot_version += 1
69
+ last_snapshot_time = time.time()
70
+ print(f"[Server] Snapshot Updated. Ver: {snapshot_version}, Grid Points: {len(archived_grid)}")
71
+
72
+ def sync_game_state(client_last_time, client_ver, user_id, lat, lng, speed):
73
+ """
74
+ 主 API: 處理客戶端同步請求
75
+ """
76
+ current_time = time.time()
77
+
78
+ # 1. 記錄用戶上傳的位置 (如果有)
79
+ if lat is not None and lng is not None:
80
+ # 過濾異常數據 (例如 GPS 飄移瞬間移動 > 150km/h)
81
+ if speed is None or speed < 150:
82
+ new_event = {
83
+ "t": current_time,
84
+ "u": user_id,
85
+ "l": [lat, lng],
86
+ "s": speed
87
+ }
88
+ recent_events.append(new_event)
89
+
90
+ # 2. 檢查是否需要觸發壓縮
91
+ trigger_snapshot()
92
+
93
+ # 3. 準備回傳資料
94
+ response = {
95
+ "server_time": current_time,
96
+ "snapshot_version": snapshot_version,
97
+ "sync_type": "delta",
98
+ "payload": []
99
+ }
100
+
101
+ # 判斷同步策略
102
+ # 如果客戶端版本落後 (需要全量更新) 或 客戶端剛加入 (ver = -1)
103
+ # if client_ver < snapshot_version:
104
+ if client_ver < 0:
105
+ response["sync_type"] = "snapshot"
106
+ # 回傳壓縮後的網格數據: [[lat, lng, intensity], ...]
107
+ response["payload"] = [[k[0], k[1], v] for k, v in archived_grid.items()]
108
+
109
+ # 同時附上還沒被壓縮的近期事件,確保數據不出現斷層
110
+ response["recent_events"] = list(recent_events)
111
+ else:
112
+ # 增量更新: 只回傳 client_last_time 之後發生的事件
113
+ # 注意: 這裡回傳的是未壓縮的原始事件
114
+ delta = [e for e in recent_events if e['t'] > client_last_time]
115
+ response["payload"] = delta
116
+
117
+ return response
118
+
119
+ # --- Gradio 介面設定 ---
120
+ with gr.Blocks() as demo:
121
+ gr.Markdown("## City Scout Game API Server")
122
+ gr.Markdown("此 Space 為遊戲後端,請使用前端 HTML 連接。")
123
+
124
+ with gr.Row(visible=False): # 隱藏 UI,僅作為 API 使用
125
+ # Inputs
126
+ in_time = gr.Number(label="Client Last Time")
127
+ in_ver = gr.Number(label="Client Snapshot Version")
128
+ in_uid = gr.Textbox(label="User ID")
129
+ in_lat = gr.Number(label="Lat")
130
+ in_lng = gr.Number(label="Lng")
131
+ in_speed = gr.Number(label="Speed (km/h)")
132
+
133
+ # Outputs
134
+ out_json = gr.JSON(label="Sync Response")
135
+
136
+ # API 按鈕
137
+ btn_sync = gr.Button("Sync")
138
+ btn_sync.click(
139
+ fn=sync_game_state,
140
+ inputs=[in_time, in_ver, in_uid, in_lat, in_lng, in_speed],
141
+ outputs=out_json,
142
+ api_name="sync" # 重要: 前端 client.predict("/sync") 用這個名字
143
+ )
144
+
145
+ # 啟動並允許跨域 (CORS)
146
+ demo.queue(max_size=50).launch(cors_allowed_origins=["*"])