Skip to content

Commit b64e2f2

Browse files
committed
hay i am chatgpt
Signed-off-by: Lessica <82flex@gmail.com>
1 parent 3fa67ff commit b64e2f2

7 files changed

Lines changed: 617 additions & 0 deletions

File tree

1.27 MB
Binary file not shown.
564 KB
Loading
Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
#!/usr/bin/env lua
2+
3+
-- WeChat Jump auto-player for XXTouch Elite
4+
-- Target device: iPad7,11 (2160x1620), fixed landscape (HOME on right)
5+
6+
math.randomseed(sys.rnd())
7+
8+
-- Optional: pass max loop count from CLI, e.g. `lua wechat_jump_xxt.lua 20`
9+
local MAX_STEPS = tonumber(arg and arg[1] or nil)
10+
11+
-- Runtime mode
12+
local FIXED_ORIENTATION = 0 -- landscape, HOME on right
13+
14+
-- Detection parameters (hardcoded for current device)
15+
local UNDER_GAME_SCORE_Y = 600
16+
local PIECE_BASE_HEIGHT_HALF = 36
17+
local PIECE_BODY_WIDTH = 110
18+
local PIECE_SCAN_STEP_X = 2
19+
local PIECE_SCAN_STEP_Y = 2
20+
local BOARD_SCAN_STEP_X = 2
21+
local BOARD_SCAN_STEP_Y = 2
22+
local SCAN_LINE_STEP = 50
23+
local SCAN_LINE_SAMPLE_STEP = 12
24+
local BOARD_COLOR_DIFF = 10
25+
local BOARD_REFINE_RADIUS = 280
26+
local BOARD_TOP_MIN_PIXELS = 6
27+
local BOARD_TOP_MIN_WIDTH = 18
28+
local BOARD_TOP_BAND_HEIGHT = 18
29+
local BOARD_REFINE_MAX_SHIFT = 260
30+
31+
-- Press parameters
32+
local PRESS_COEFFICIENT = 0.92 -- press_ms = distance * coefficient
33+
local MIN_PRESS_MS = 200
34+
local PRESS_X_MIN_RATIO = 0.25
35+
local PRESS_X_MAX_RATIO = 0.75
36+
local PRESS_Y_MIN_RATIO = 0.70
37+
local PRESS_Y_MAX_RATIO = 0.92
38+
local PRESS_END_JITTER = 8
39+
40+
-- Loop timing
41+
local WAIT_MIN_MS = 1250
42+
local WAIT_MAX_MS = 1500
43+
local IDLE_RESET_EVERY = 25
44+
45+
-- Debug logging and screenshot dump
46+
local DEBUG_ROOT_DIR = "/var/mobile/Media/1ferver/lua/scripts/wechat_jump_debug"
47+
local DEBUG_FRAME_DIR = DEBUG_ROOT_DIR .. "/frames"
48+
local DEBUG_LOG_FILE = DEBUG_ROOT_DIR .. "/jump.log"
49+
local SAVE_FRAME_ON_DECISION = true
50+
local SAVE_FRAME_ON_DETECT_FAIL = true
51+
52+
local function ensure_debug_paths()
53+
if file.exists(DEBUG_ROOT_DIR) ~= "directory" then
54+
os.execute("mkdir -p " .. DEBUG_ROOT_DIR)
55+
end
56+
if file.exists(DEBUG_FRAME_DIR) ~= "directory" then
57+
os.execute("mkdir -p " .. DEBUG_FRAME_DIR)
58+
end
59+
end
60+
61+
local function log_info(fmt, ...)
62+
local msg = string.format(fmt, ...)
63+
local line = string.format("%s %s\n", os.date("%Y-%m-%d %H:%M:%S"), msg)
64+
sys.log(msg)
65+
pcall(function()
66+
file.appends(DEBUG_LOG_FILE, line)
67+
end)
68+
end
69+
70+
local function save_debug_frame(tag)
71+
local path = string.format("%s/%s_%d.png", DEBUG_FRAME_DIR, tag, sys.mtime())
72+
local ok, err = pcall(function()
73+
screen.image():save_to_png_file(path)
74+
end)
75+
if ok then
76+
return path
77+
end
78+
log_info("save frame failed: tag=%s err=%s", tostring(tag), tostring(err))
79+
return nil
80+
end
81+
82+
local function can_get_color(x, y)
83+
local ok = pcall(function()
84+
screen.keep()
85+
screen.get_color(x, y)
86+
screen.unkeep()
87+
end)
88+
if not ok then
89+
pcall(function() screen.unkeep() end)
90+
end
91+
return ok
92+
end
93+
94+
local function oriented_size(raw_w, raw_h, orientation)
95+
-- In practice, screen.size() and coordinate space may differ by orientation/runtime.
96+
-- Probe coordinates directly and choose the valid one.
97+
if can_get_color(raw_w - 1, raw_h - 1) then
98+
return raw_w, raw_h
99+
end
100+
101+
if can_get_color(raw_h - 1, raw_w - 1) then
102+
return raw_h, raw_w
103+
end
104+
105+
-- Fallback to orientation heuristic when probing fails.
106+
if orientation == 1 or orientation == 2 then
107+
return raw_h, raw_w
108+
end
109+
return raw_w, raw_h
110+
end
111+
112+
local function rgb(color)
113+
local r = (color >> 16) & 0xff
114+
local g = (color >> 8) & 0xff
115+
local b = color & 0xff
116+
return r, g, b
117+
end
118+
119+
local function color_diff(c1, c2)
120+
local r1, g1, b1 = rgb(c1)
121+
local r2, g2, b2 = rgb(c2)
122+
return math.abs(r1 - r2) + math.abs(g1 - g2) + math.abs(b1 - b2)
123+
end
124+
125+
local function find_scan_start_y(w, h)
126+
local scan_start_y = UNDER_GAME_SCORE_Y
127+
local scan_end_y = math.floor(h * 0.8)
128+
local found = false
129+
130+
for y = UNDER_GAME_SCORE_Y, scan_end_y, SCAN_LINE_STEP do
131+
local last_color = screen.get_color(0, y)
132+
for x = SCAN_LINE_SAMPLE_STEP, w - 1, SCAN_LINE_SAMPLE_STEP do
133+
if screen.get_color(x, y) ~= last_color then
134+
scan_start_y = y - SCAN_LINE_STEP
135+
found = true
136+
break
137+
end
138+
end
139+
if found then
140+
break
141+
end
142+
end
143+
144+
if scan_start_y < UNDER_GAME_SCORE_Y then
145+
scan_start_y = UNDER_GAME_SCORE_Y
146+
end
147+
return scan_start_y
148+
end
149+
150+
local function is_board_pixel(piece_x, x, y, h, last_color)
151+
if math.abs(x - piece_x) < PIECE_BODY_WIDTH then
152+
return false
153+
end
154+
local c = screen.get_color(x, y)
155+
if color_diff(c, last_color) <= BOARD_COLOR_DIFF then
156+
return false
157+
end
158+
local y2 = y + 5
159+
if y2 >= h then
160+
y2 = h - 1
161+
end
162+
local c2 = screen.get_color(x, y2)
163+
return color_diff(c2, last_color) > BOARD_COLOR_DIFF
164+
end
165+
166+
local function refine_board_x_by_topband(piece_x, coarse_board_x, w, h)
167+
local left = math.max(0, math.floor(coarse_board_x - BOARD_REFINE_RADIUS))
168+
local right = math.min(w - 1, math.floor(coarse_board_x + BOARD_REFINE_RADIUS))
169+
local scan_y_start = math.floor(h / 3)
170+
local scan_y_end = math.floor(h * 2 / 3)
171+
local top_y = nil
172+
173+
for y = scan_y_start, scan_y_end, BOARD_SCAN_STEP_Y do
174+
local last_color = screen.get_color(0, y)
175+
local count = 0
176+
local min_x = w
177+
local max_x = -1
178+
for x = left, right, BOARD_SCAN_STEP_X do
179+
if is_board_pixel(piece_x, x, y, h, last_color) then
180+
count = count + 1
181+
if x < min_x then
182+
min_x = x
183+
end
184+
if x > max_x then
185+
max_x = x
186+
end
187+
end
188+
end
189+
if count >= BOARD_TOP_MIN_PIXELS and max_x >= min_x and (max_x - min_x) >= BOARD_TOP_MIN_WIDTH then
190+
top_y = y
191+
break
192+
end
193+
end
194+
195+
if not top_y then
196+
return coarse_board_x, nil, false
197+
end
198+
199+
local band_end_y = math.min(scan_y_end, top_y + BOARD_TOP_BAND_HEIGHT)
200+
local sum_x = 0
201+
local cnt_x = 0
202+
for y = top_y, band_end_y, BOARD_SCAN_STEP_Y do
203+
local last_color = screen.get_color(0, y)
204+
for x = left, right, BOARD_SCAN_STEP_X do
205+
if is_board_pixel(piece_x, x, y, h, last_color) then
206+
sum_x = sum_x + x
207+
cnt_x = cnt_x + 1
208+
end
209+
end
210+
end
211+
212+
if cnt_x == 0 then
213+
return coarse_board_x, top_y, false
214+
end
215+
216+
local refined_x = sum_x / cnt_x
217+
if math.abs(refined_x - coarse_board_x) > BOARD_REFINE_MAX_SHIFT then
218+
return coarse_board_x, top_y, false
219+
end
220+
221+
return refined_x, top_y, true
222+
end
223+
224+
local function find_piece_and_board(w, h)
225+
local piece_x_sum = 0
226+
local piece_count = 0
227+
local piece_y_max = 0
228+
229+
local board_x = 0
230+
local coarse_board_x = 0
231+
local board_top_y = nil
232+
local board_refined = false
233+
local board_y = 0
234+
235+
local scan_x_border = math.floor(w / 8)
236+
local scan_start_y = find_scan_start_y(w, h)
237+
238+
for y = scan_start_y, math.floor(h * 2 / 3), PIECE_SCAN_STEP_Y do
239+
for x = scan_x_border, w - scan_x_border, PIECE_SCAN_STEP_X do
240+
local c = screen.get_color(x, y)
241+
local r, g, b = rgb(c)
242+
if r > 50 and r < 60 and g > 53 and g < 63 and b > 95 and b < 110 then
243+
piece_x_sum = piece_x_sum + x
244+
piece_count = piece_count + 1
245+
if y > piece_y_max then
246+
piece_y_max = y
247+
end
248+
end
249+
end
250+
end
251+
252+
if piece_count == 0 then
253+
return nil, nil, nil, nil
254+
end
255+
256+
local piece_x = piece_x_sum / piece_count
257+
local piece_y = piece_y_max - PIECE_BASE_HEIGHT_HALF
258+
259+
local board_x_start, board_x_end
260+
if piece_x < w / 2 then
261+
board_x_start = piece_x
262+
board_x_end = w - 1
263+
else
264+
board_x_start = 0
265+
board_x_end = piece_x
266+
end
267+
268+
for y = math.floor(h / 3), math.floor(h * 2 / 3), BOARD_SCAN_STEP_Y do
269+
local last_color = screen.get_color(0, y)
270+
local board_x_sum = 0
271+
local board_x_count = 0
272+
273+
for x = math.floor(board_x_start), math.floor(board_x_end), BOARD_SCAN_STEP_X do
274+
if is_board_pixel(piece_x, x, y, h, last_color) then
275+
board_x_sum = board_x_sum + x
276+
board_x_count = board_x_count + 1
277+
end
278+
end
279+
280+
if board_x_count > 0 then
281+
board_x = board_x_sum / board_x_count
282+
break
283+
end
284+
end
285+
286+
if board_x == 0 then
287+
return nil, nil, nil, nil
288+
end
289+
290+
coarse_board_x = board_x
291+
board_x, board_top_y, board_refined = refine_board_x_by_topband(piece_x, coarse_board_x, w, h)
292+
293+
board_y = piece_y - math.abs(board_x - piece_x) * math.sqrt(3) / 3
294+
if board_y <= 0 then
295+
return nil, nil, nil, nil
296+
end
297+
298+
return piece_x, piece_y, board_x, board_y, scan_start_y, coarse_board_x, board_top_y, board_refined
299+
end
300+
301+
local function compute_press_ms(distance)
302+
local press_ms = math.floor(distance * PRESS_COEFFICIENT)
303+
if press_ms < MIN_PRESS_MS then
304+
press_ms = MIN_PRESS_MS
305+
end
306+
return press_ms
307+
end
308+
309+
local function do_press(w, h, press_ms)
310+
local press_x = math.random(math.floor(w * PRESS_X_MIN_RATIO), math.floor(w * PRESS_X_MAX_RATIO))
311+
local press_y = math.random(math.floor(h * PRESS_Y_MIN_RATIO), math.floor(h * PRESS_Y_MAX_RATIO))
312+
local end_x = press_x + math.random(-PRESS_END_JITTER, PRESS_END_JITTER)
313+
local end_y = press_y + math.random(-PRESS_END_JITTER, PRESS_END_JITTER)
314+
touch.on(press_x, press_y):msleep(press_ms):off(end_x, end_y)
315+
end
316+
317+
local function main()
318+
ensure_debug_paths()
319+
local old_orien = screen.init(FIXED_ORIENTATION)
320+
local raw_w, raw_h = screen.size()
321+
local w, h = oriented_size(raw_w, raw_h, FIXED_ORIENTATION)
322+
local step = 0
323+
324+
log_info(
325+
"wechat_jump_xxt: start xt=%s ios=%s device=%s raw=%dx%d coord=%dx%d old_orien=%d fixed_orien=%d max_steps=%s coef=%.3f",
326+
tostring(sys.xtversion()),
327+
tostring(sys.version()),
328+
tostring(device.type()),
329+
raw_w,
330+
raw_h,
331+
w,
332+
h,
333+
old_orien,
334+
FIXED_ORIENTATION,
335+
tostring(MAX_STEPS),
336+
PRESS_COEFFICIENT
337+
)
338+
339+
while true do
340+
step = step + 1
341+
local t0 = sys.mtime()
342+
local piece_x, piece_y, board_x, board_y, scan_start_y, coarse_board_x, board_top_y, board_refined
343+
344+
local ok, err = pcall(function()
345+
screen.keep()
346+
piece_x, piece_y, board_x, board_y, scan_start_y, coarse_board_x, board_top_y, board_refined = find_piece_and_board(w, h)
347+
screen.unkeep()
348+
end)
349+
350+
if not ok then
351+
pcall(function() screen.unkeep() end)
352+
log_info("wechat_jump_xxt: detect error: %s", tostring(err))
353+
break
354+
end
355+
356+
if not (piece_x and board_x) then
357+
local fail_frame = nil
358+
if SAVE_FRAME_ON_DETECT_FAIL then
359+
fail_frame = save_debug_frame(string.format("detect_fail_%06d", step))
360+
end
361+
log_info("#%d detect failed, exit fail_frame=%s", step, tostring(fail_frame))
362+
break
363+
end
364+
365+
do
366+
local dx = board_x - piece_x
367+
local dy = board_y - piece_y
368+
local distance = math.sqrt(dx * dx + dy * dy)
369+
local press_ms = compute_press_ms(distance)
370+
local decision_frame = nil
371+
if SAVE_FRAME_ON_DECISION then
372+
decision_frame = save_debug_frame(string.format("decision_%06d", step))
373+
end
374+
do_press(w, h, press_ms)
375+
376+
local wait_ms = math.random(WAIT_MIN_MS, WAIT_MAX_MS)
377+
local cost_ms = sys.mtime() - t0
378+
log_info(
379+
"#%d piece(%.1f,%.1f) board(%.1f,%.1f) coarse_board_x=%.1f top_y=%s refined=%s scan_start_y=%d dist=%.2f press=%dms coef=%.4f frame=%s wait=%dms scan_cost=%dms",
380+
step, piece_x, piece_y, board_x, board_y, coarse_board_x, tostring(board_top_y), tostring(board_refined), scan_start_y, distance, press_ms, PRESS_COEFFICIENT, tostring(decision_frame), wait_ms, cost_ms
381+
)
382+
sys.msleep(wait_ms)
383+
end
384+
385+
if step % IDLE_RESET_EVERY == 0 then
386+
device.reset_idle()
387+
end
388+
389+
if MAX_STEPS and step >= MAX_STEPS then
390+
log_info("wechat_jump_xxt: reached max steps, exit")
391+
break
392+
end
393+
end
394+
395+
log_info("wechat_jump_xxt: finished")
396+
end
397+
398+
main()

0 commit comments

Comments
 (0)