npc0 commited on
Commit
876e202
·
verified ·
1 Parent(s): 0118ef0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -77
app.py CHANGED
@@ -1,61 +1,39 @@
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)
@@ -63,34 +41,43 @@ def trigger_snapshot():
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,
@@ -98,49 +85,39 @@ def sync_game_state(client_last_time, client_ver, user_id, lat, lng, speed):
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
- # 啟動
146
  demo.queue().launch()
 
1
  import gradio as gr
2
  import time
 
3
  from collections import deque, defaultdict
4
 
5
+ # --- Configuration ---
6
+ MAX_RECENT_EVENTS = 1000 # Trigger snapshot compression at this count
7
+ GRID_PRECISION = 4 # ~11 meters precision
8
+ RADIUS_BASE = 20 # Visual radius for the fog reveal
9
 
10
+ # --- Data Structures ---
11
+ # 1. Recent Events (Delta): For fast, smooth updates
 
12
  recent_events = deque()
13
 
14
+ # 2. Historical Grid (Snapshot): Compressed long-term memory
15
+ # Key: (lat, lng), Value: max_radius (based on speed)
16
+ archived_grid = defaultdict(int)
17
 
18
+ # 3. Version Control
19
  snapshot_version = 0
20
  last_snapshot_time = time.time()
21
 
22
+ def get_radius_by_speed(speed_kmh):
23
  """
24
+ Determine radius of fog revealed based on speed.
25
+ Walking (< 5km/h) = Large radius (High clear)
26
+ Driving (> 20km/h) = Small radius (Low clear)
 
27
  """
28
+ if speed_kmh is None: return 10
29
+ if speed_kmh < 6: return 35 # Walking: Big reveal
30
+ if speed_kmh < 25: return 15 # Biking: Medium
31
+ return 8 # Driving: Tiny trace
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  def trigger_snapshot():
34
+ """Compress recent events into the grid to save bandwidth."""
35
  global recent_events, snapshot_version, last_snapshot_time
36
 
 
37
  if len(recent_events) > MAX_RECENT_EVENTS:
38
  events_to_archive = []
39
  count_to_remove = int(MAX_RECENT_EVENTS / 2)
 
41
  for _ in range(count_to_remove):
42
  events_to_archive.append(recent_events.popleft())
43
 
44
+ # Bake into grid
45
+ for event in events_to_archive:
46
+ lat, lng = event['l']
47
+ radius = event['r'] # Radius stored in event
48
+
49
+ grid_key = (round(lat, GRID_PRECISION), round(lng, GRID_PRECISION))
50
+ # Keep the largest radius ever recorded for this spot
51
+ if radius > archived_grid[grid_key]:
52
+ archived_grid[grid_key] = radius
53
 
54
  snapshot_version += 1
55
  last_snapshot_time = time.time()
56
+ print(f"[Server] Snapshot v{snapshot_version} | Points: {len(archived_grid)}")
57
 
58
  def sync_game_state(client_last_time, client_ver, user_id, lat, lng, speed):
59
  """
60
+ Main API Endpoint
61
  """
62
  current_time = time.time()
63
 
64
+ # 1. Record User Position
65
  if lat is not None and lng is not None:
66
+ # Basic filter for bad GPS data
67
  if speed is None or speed < 150:
68
+ radius = get_radius_by_speed(speed)
69
  new_event = {
70
  "t": current_time,
71
  "u": user_id,
72
  "l": [lat, lng],
73
+ "r": radius
74
  }
75
  recent_events.append(new_event)
76
 
77
+ # 2. Check Snapshot Trigger
78
  trigger_snapshot()
79
 
80
+ # 3. Prepare Response
81
  response = {
82
  "server_time": current_time,
83
  "snapshot_version": snapshot_version,
 
85
  "payload": []
86
  }
87
 
88
+ # Logic: Full Snapshot vs Delta
89
+ if client_ver < snapshot_version:
 
 
90
  response["sync_type"] = "snapshot"
91
+ # Return: [[lat, lng, radius], ...]
92
  response["payload"] = [[k[0], k[1], v] for k, v in archived_grid.items()]
93
+ # Also include recent events to prevent gaps
94
+ response["recent_events"] = [[e['l'][0], e['l'][1], e['r']] for e in recent_events]
 
95
  else:
96
+ # Delta: Return only new events [[lat, lng, radius], ...]
97
+ delta = [[e['l'][0], e['l'][1], e['r']] for e in recent_events if e['t'] > client_last_time]
 
98
  response["payload"] = delta
99
 
100
  return response
101
 
102
+ # --- Gradio App ---
103
  with gr.Blocks() as demo:
104
+ gr.Markdown("## Fog of War Server")
 
105
 
106
+ with gr.Row(visible=False):
107
+ in_time = gr.Number()
108
+ in_ver = gr.Number()
109
+ in_uid = gr.Textbox()
110
+ in_lat = gr.Number()
111
+ in_lng = gr.Number()
112
+ in_speed = gr.Number()
113
+ out_json = gr.JSON()
 
 
 
114
 
 
115
  btn_sync = gr.Button("Sync")
116
  btn_sync.click(
117
  fn=sync_game_state,
118
  inputs=[in_time, in_ver, in_uid, in_lat, in_lng, in_speed],
119
  outputs=out_json,
120
+ api_name="sync"
121
  )
122
 
 
123
  demo.queue().launch()