#!/usr/bin/env python3 """make_queue.py — 夜に回す「条件 × 種」を queue.json に並べる(依頼 v12 の 4 章) 条件は Unity と同じ所から引く(手で写さない): Assets/ServiceSim/Experiment/serving-robot-params.json の unity_conditions 最初の夜:6条件(③ fam_p1・⑦ fam_p5 × ロボット 0・1・2 台)× 種 0〜9 = 60 回 + ③2台・すれ違い「入」× 種 0〜9 = 10 回 … 合わせて 70 回 スタッフ4人・42組/時・上げ膳(mode=serve)・1往復3卓・レジ1つは、 その JSON の条件がそのまま持っている値なので、ここでは選び直さない。 すれ違い(依頼 v13 の 依頼1)は条件ごとに持たせる(pass_by: on / off)。 標準は off(基準データ cs:7 の18回と同じ動き)で、 --pass-by-extra で「その条件だけ on の回」を足す。 実行ファイルには -ssPassBy on|off で渡す(依頼 v12 の 2 章に足す引数)。 使い方(プロジェクトの中で) # 最初の夜(70回) python3 tools/make_queue.py --out queue.json \ --pass-by-extra fam_p1_s4_r2_a42 # 60回だけ python3 tools/make_queue.py --out queue.json python3 tools/make_queue.py --conditions fam_p1_s4_r1_a42 --seeds 1 --out /tmp/q.json 標準ライブラリだけで動く。Unity も台帳も読み書きしない。 """ import argparse import json import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) PROJECT = os.path.dirname(HERE) PARAMS = os.path.join(PROJECT, 'Assets', 'ServiceSim', 'Experiment', 'serving-robot-params.json') # 条件の名前の付け方は Unity の SimParams.Condition.Id と同じにする。 # {店}_s{スタッフ}_r{ロボット}_a{来客/時} 例: fam_p1_s4_r1_a42 # 実行ファイルに -ssCondition で渡す字(依頼 v12 の 2 章) def condition_id(c): return '%s_s%d_r%d_a%d' % (c['store'], int(c['staff']), int(c['robots']), int(c['arrivals_per_h'])) def parse_seeds(text): """'0-9' / '0,1,2' / '3' を種の一覧にする""" out = [] for part in str(text).split(','): part = part.strip() if not part: continue if '-' in part[1:]: # 先頭の - は負の数のため避ける a, b = part.split('-', 1) lo, hi = int(a), int(b) if hi < lo: lo, hi = hi, lo out.extend(range(lo, hi + 1)) else: out.append(int(part)) seen, uniq = set(), [] for s in out: if s not in seen: seen.add(s) uniq.append(s) return uniq def load_conditions(path): with open(path, encoding='utf-8') as f: d = json.load(f) meta = d.get('meta') or {} conds = [] for item in d.get('unity_conditions') or []: c = item.get('condition') or {} missing = [k for k in ('store', 'staff', 'robots', 'arrivals_per_h', 'mode') if k not in c] if missing: sys.exit('unity_conditions に %s がありません: %r' % (missing, c)) conds.append(c) return meta, conds def main(): ap = argparse.ArgumentParser() ap.add_argument('--params', default=PARAMS, help='serving-robot-params.json') ap.add_argument('--seeds', default='0-9', help='種(例 0-9 / 0,1,2)') ap.add_argument('--conditions', default='', help='条件の名前をカンマ区切りで絞る(既定は6条件ぜんぶ)') ap.add_argument('--label', default='night', help='出力フォルダに入る名札') ap.add_argument('--pass-by-extra', default='', help='すれ違い「入」でも回す条件(カンマ区切り)。' 'その条件だけ、同じ種で on の回を足す(依頼 v13)') ap.add_argument('--pass-by', default='off', choices=('off', 'on'), help='ふだんの回のすれ違い(既定 off=基準データと同じ動き)') ap.add_argument('--out', default=os.path.join(PROJECT, 'queue.json')) a = ap.parse_args() if not os.path.exists(a.params): sys.exit('試算の値が見つかりません: %s' % a.params) meta, conds = load_conditions(a.params) seeds = parse_seeds(a.seeds) if not seeds: sys.exit('--seeds が空です') want = [s.strip() for s in a.conditions.split(',') if s.strip()] if want: known = {condition_id(c) for c in conds} bad = [w for w in want if w not in known] if bad: sys.exit('知らない条件です: %s\n 使える条件: %s' % (', '.join(bad), ', '.join(sorted(known)))) conds = [c for c in conds if condition_id(c) in want] extra = [s.strip() for s in a.pass_by_extra.split(',') if s.strip()] known = {condition_id(c) for c in conds} bad = [w for w in extra if w not in known] if bad: sys.exit('--pass-by-extra に知らない条件があります: %s\n 使える条件: %s' % (', '.join(bad), ', '.join(sorted(known)))) def make(c, seed, pass_by): return { 'condition': condition_id(c), 'seed': seed, 'pass_by': pass_by, # 'on' / 'off'(実行ファイルへ -ssPassBy で渡す) 'store': c['store'], 'staff': int(c['staff']), 'robots': int(c['robots']), 'arrivals_per_h': int(c['arrivals_per_h']), 'mode': c.get('mode', 'serve'), 'trays': int(c.get('trays', 1)), 'fragile_by': c.get('fragile_by', 'any'), 'registers': int(c.get('registers', 1)), } runs = [] for c in conds: for s in seeds: runs.append(make(c, s, a.pass_by)) # すれ違い「入」でも回す条件を、同じ種で足す(依頼 v13) for c in conds: if condition_id(c) not in extra: continue if int(c['robots']) < 2: print(' ※ %s はロボットが %d 台なので、すれ違いは起きません(足しますが参考)' % (condition_id(c), int(c['robots']))) for s in seeds: runs.append(make(c, s, 'on')) q = { 'schema': 'servicesim-night-queue/0.1', 'label': a.label, 'params_model': meta.get('model', ''), 'layout_generation': meta.get('layout_generation', ''), 'params_source': os.path.relpath(a.params, PROJECT), 'seeds': seeds, 'conditions': [condition_id(c) for c in conds], 'pass_by': a.pass_by, 'pass_by_extra': extra, 'count': len(runs), 'runs': runs, } out_dir = os.path.dirname(os.path.abspath(a.out)) if out_dir and not os.path.isdir(out_dir): os.makedirs(out_dir) with open(a.out, 'w', encoding='utf-8') as f: json.dump(q, f, ensure_ascii=False, indent=1) f.write('\n') print('並べました: %s' % a.out) print(' 試算の版 %s / 店の形の版 %s' % (q['params_model'], q['layout_generation'])) n_on = sum(1 for r in runs if r['pass_by'] == 'on') print(' 条件 %d / 種 %s / 合わせて %d 回(すれ違い 切 %d 回・入 %d 回)' % (len(conds), ','.join(str(s) for s in seeds), len(runs), len(runs) - n_on, n_on)) for cid in q['conditions']: mark = '(+すれ違い入も %d 回)' % len(seeds) if cid in extra else '' print(' %s%s' % (cid, mark)) if __name__ == '__main__': main()