File size: 5,796 Bytes
1169888
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env python3
"""Live teaching server — the main agent personally drives the env step-by-step.

IPC (file-based, /content/teach/):
  cmd.txt  : 'START <game_id>' | 'RESET' | 'PING' | '<ACTIONn> [x y]'
  obs.json : latest observation snapshot (grid hex rows + change list)
  err.json : last error (if any)
Trace: collect_selfplay-compatible JSONL (grids_before/after, levels, hash,
state_text) so data/build_dataset.py consumes teaching data unchanged.

NOTE: grids[0] is the CURRENT frame (n_frames history after it) — matches
build_candidates/collect_selfplay usage.
"""
import json
import os
import sys
import time
import traceback

sys.path.insert(0, '/content/qwenjev')
sys.path.insert(0, '/content/qwenjev/train')
os.chdir('/content/qwenjev')

from data.collect_selfplay import make_env, meta_from, grids_from
from data.mock_env import step_env
from qwenjev.state_codec import canonical_state, grid_to_text, frame_hash

TEACH = '/content/teach'
CMD = TEACH + '/cmd.txt'
OBS = TEACH + '/obs.json'
ERR = TEACH + '/err.json'
os.makedirs(TEACH, exist_ok=True)
for f in (CMD, OBS, ERR):
    if os.path.exists(f):
        os.remove(f)


def render(grids):
    g = grids[0]
    return [''.join('%x' % (int(v) & 15) for v in row) for row in g]


def changes(pre_cur, cur, limit=48):
    out = []
    if pre_cur is None:
        return out
    R = max(len(pre_cur), len(cur))
    for r in range(min(R, 64)):
        pr = pre_cur[r] if r < len(pre_cur) else []
        cr = cur[r] if r < len(cur) else []
        C = max(len(pr), len(cr))
        for c in range(min(C, 64)):
            a = pr[c] if c < len(pr) else None
            b = cr[c] if c < len(cr) else None
            if a != b:
                sa = ('%x' % (int(a) & 15)) if a is not None else '.'
                sb = ('%x' % (int(b) & 15)) if b is not None else '.'
                out.append('%d,%d:%s>%s' % (r, c, sa, sb))
                if len(out) >= limit:
                    return out
    return out


def write_obs(env, gid, step_no, prev_grids, terminal=None, extra=None):
    grids = grids_from(env)
    meta = meta_from(env)
    s = {
        'ok': True, 'game': gid, 'step': step_no, 'state': meta['state'],
        'levels': meta['levels_completed'], 'win_levels': meta['win_levels'],
        'available': meta['available_actions'],
        'grid': render(grids),
        'shape': [len(grids[0]), len(grids[0][0])],
        'n_frames': len(grids),
        'changes': changes(prev_grids[0] if prev_grids else None, grids[0]),
    }
    if terminal:
        s['terminal'] = terminal
    if extra:
        s.update(extra)
    tmp = OBS + '.tmp'
    open(tmp, 'w').write(json.dumps(s, separators=(',', ':')))
    os.replace(tmp, OBS)


env = None
game_id = None
step_no = 0
trace_path = None
prev_grids = None

while True:
    try:
        if os.path.exists(ERR):
            os.remove(ERR)
        if os.path.exists(CMD):
            line = open(CMD).read().strip()
            os.remove(CMD)
            if line:
                parts = line.split()
                if parts[0] == 'PING':
                    if env is not None:
                        write_obs(env, game_id, step_no, prev_grids)
                    else:
                        open(OBS, 'w').write(json.dumps({'ok': True, 'idle': True}))
                elif parts[0] == 'START':
                    game_id = parts[1]
                    trace_path = TEACH + '/trace_teach_%s.jsonl' % game_id
                    env = make_env(game_id, None)
                    env.reset()
                    step_no = 0
                    prev_grids = None
                    write_obs(env, game_id, step_no, prev_grids,
                              extra={'trace': trace_path, 'episode_started': True})
                elif parts[0] == 'RESET':
                    env.reset()
                    prev_grids = None
                    write_obs(env, game_id, step_no, prev_grids, extra={'reset': True})
                else:
                    action = parts[0]
                    x = int(parts[1]) if len(parts) > 1 and parts[1] != '-' else None
                    y = int(parts[2]) if len(parts) > 2 and parts[2] != '-' else None
                    grids = grids_from(env)
                    meta = meta_from(env)
                    step_env(env, action, x, y)
                    step_no += 1
                    grids_after = grids_from(env)
                    meta_after = meta_from(env)
                    record = {
                        'game_id': game_id, 'step': step_no - 1,
                        'action': action, 'x': x, 'y': y,
                        'grids_before': grids, 'grids_after': grids_after,
                        'levels_completed_before': meta['levels_completed'],
                        'levels_completed_after': meta_after['levels_completed'],
                        'available_actions': list(meta['available_actions']),
                        'frame_hash': frame_hash(grids[0]),
                        'state_text': canonical_state(grid_to_text(grids[0])),
                        'ts': time.time(),
                    }
                    open(trace_path, 'a').write(json.dumps(record) + '\n')
                    term = meta_after['state'] if meta_after['state'] in ('WIN', 'GAME_OVER') else None
                    write_obs(env, game_id, step_no, grids, terminal=term)
                    if term == 'GAME_OVER':
                        env.reset()
                        prev_grids = None
                    else:
                        prev_grids = grids_after
        time.sleep(0.2)
    except Exception as e:
        open(ERR, 'w').write(json.dumps(
            {'error': '%s: %s' % (type(e).__name__, e),
             'tb': traceback.format_exc()[-1500:]}))
        time.sleep(0.5)