using System.Collections.Generic;
using System.Globalization;
using System.Text;
using UnityEngine;
namespace ServiceSim
{
///
/// ロボット1台ずつの位置と状態を、**店の中の時間で5秒おきに** 残す
/// (2026-10-27・エイイチの指示)。出力は robot_track.csv。
///
/// なぜ要るか
/// 10/27 の夜の 1回(③2台・すれ違い入・種0)が 720 分終わらなかった。
/// ロボットが皿を持ったまま 600 秒の打ち切りを 56 回くり返したのだが、
/// **そのときロボットがどこで何をしていたかが記録に無かった**。
/// stops.csv は「止まった」ときだけ、trips.csv は往復ごとなので、
/// 「ずっと同じ所に居た」ことが後から言えなかった。
///
/// なぜ 5 秒おき・店の中の時間なのか
/// ・1コマ(0.05 秒)ごとに書くと、書く重さで Mac の忙しさが変わり、
/// 数字そのものが動いてしまう(busycheck で、忙しさが
/// stuck_count・robot_share を動かすと分かっている)。
/// ・刻みを **ops.NowS(店の中の時間)** で決めるので、
/// 実時間が速くても遅くても同じ行が出る(取り違えない)。
/// ・5 秒おきなら 1回(店の中 200 分)で 2 台 × 2400 = 4800 行ほど。
/// 上限の 720 分でも 17,280 行で、軽い。
///
/// det_frames.csv(1コマごと・位置の合計)との違い
/// det_frames は切り分けの試し用で、ロボット位置を **合計値** でしか
/// 持たないので1台ずつ追えない。こちらは常に書き、1台ずつ追える。
///
public class RobotTrackLogger
{
public struct Row
{
public float tS;
public int robotId;
public float x, z;
public string phase; // いまの仕事(卓へ / 受け渡し口へ / 立ち往生 など)
public bool passBy; // すれ違いの走り方で動いている
public bool backingOff; // すれ違いをあきらめて後ずさりしている
public bool narrow; // すれ違う幅が足りないと見た
public int tableId; // 向かっている卓(無ければ -1)
}
readonly List _rows = new List();
public int RowCount { get { return _rows.Count; } }
public IReadOnlyList Rows { get { return _rows; } }
public void Clear() { _rows.Clear(); }
/// いまの仕事を日本語で(trips.csv などと言葉をそろえる)
public static string PhaseName(ServingRobotController.Phase p)
{
switch (p)
{
case ServingRobotController.Phase.Idle: return "待機";
case ServingRobotController.Phase.ToTable: return "卓へ";
case ServingRobotController.Phase.Dwell: return "卓で止まる";
case ServingRobotController.Phase.ToPass: return "受け渡し口へ";
case ServingRobotController.Phase.StuckWait: return "立ち往生";
case ServingRobotController.Phase.Done: return "往復おわり";
}
return "分からない";
}
///
/// その時刻のロボットを1台ずつ1行に。
/// null の枠は飛ばす(ロボット0台の条件では1行も出ない)
///
public void Sample(float tS, IList robots)
{
if (robots == null) return;
for (int i = 0; i < robots.Count; i++)
{
ServingRobotController r = robots[i];
if (r == null) continue;
Vector3 p = r.transform.position;
Row row = new Row();
row.tS = tS;
row.robotId = r.robotId;
row.x = p.x;
row.z = p.z;
row.phase = PhaseName(r.CurrentPhase);
row.passBy = r.PassByMoving;
row.backingOff = r.PassByBackingOff;
row.narrow = r.PassByNarrow;
row.tableId = r.CurrentPhase == ServingRobotController.Phase.Idle ? -1 : r.TableId;
_rows.Add(row);
}
}
public const string Header =
"condition_id,seed,t_s,robot_id,x,z,phase,pass_by,backing_off,narrow,table_id";
public void Write(string path, string conditionId, int seed)
{
StringBuilder sb = new StringBuilder();
sb.Append(Header).Append('\n');
for (int i = 0; i < _rows.Count; i++)
{
Row r = _rows[i];
sb.Append(conditionId).Append(',').Append(seed.ToString(CultureInfo.InvariantCulture)).Append(',')
.Append(F(r.tS)).Append(',')
.Append(r.robotId.ToString(CultureInfo.InvariantCulture)).Append(',')
.Append(F(r.x)).Append(',').Append(F(r.z)).Append(',')
.Append(r.phase).Append(',')
.Append(r.passBy ? 1 : 0).Append(',')
.Append(r.backingOff ? 1 : 0).Append(',')
.Append(r.narrow ? 1 : 0).Append(',')
.Append(r.tableId.ToString(CultureInfo.InvariantCulture)).Append('\n');
}
System.IO.File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false));
}
static string F(float v)
{
return v.ToString("0.####", CultureInfo.InvariantCulture);
}
}
}