File size: 2,904 Bytes
d8434c0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
// /src/state/runStore.js

export const runStore = {
  params: {
    N: 100,
    speed: 1.0,
    seed: 1234
  },
  metrics: {
    totalDistance: 0,
    steps: 0,
    timeElapsed: 0,
    energy: 0
  },
  pointsCollected: 0,
  points: [],  // array of {x, y, collected: bool}
  order: [],   // order in which visitar (índices)
  botPath: [], // punto por punto que sigue (incluye inicio)
  
  startRun(params) {
    this.params = { ...this.params, ...params };
    this.resetMetrics();
    this._initPoints();
    this._computeOrder();
    this.botPath = [this._startPosition()];
  },
  
  resetRun() {
    this.metrics = {
      totalDistance: 0,
      steps: 0,
      timeElapsed: 0,
      energy: 0
    };
    this.pointsCollected = 0;
    this.points.forEach(p => p.collected = false);
    this.order = [];
    this.botPath = [];
  },

  exportJSON() {
    return JSON.stringify({
      params: this.params,
      metrics: this.metrics,
      pointsCollected: this.pointsCollected,
      order: this.order,
      points: this.points
    }, null, 2);
  },

  _initPoints() {
    const { N, seed } = this.params;
    this.points = [];
    const rng = mulberry32(seed);
    for (let i = 0; i < N; i++) {
      this.points.push({
        x: rng() * 1,  // normalizar al espacio unitario [0,1]
        y: rng() * 1,
        collected: false
      });
    }
  },

  _startPosition() {
    return { x: 0, y: 0 };
  },

  _computeOrder() {
    // greedy nearest neighbor en espacio unitario
    const pts = this.points;
    const visited = new Set();
    let cur = this._startPosition();
    this.order = [];
    for (let i = 0; i < pts.length; i++) {
      let bestIdx = -1;
      let bestD = Infinity;
      pts.forEach((p, idx) => {
        if (visited.has(idx)) return;
        const dx = p.x - cur.x, dy = p.y - cur.y;
        const d = dx*dx + dy*dy;
        if (d < bestD) {
          bestD = d;
          bestIdx = idx;
        }
      });
      if (bestIdx < 0) break;
      visited.add(bestIdx);
      this.order.push(bestIdx);
      cur = { x: pts[bestIdx].x, y: pts[bestIdx].y };
    }
  },

  recordStep(from, to) {
    const dx = to.x - from.x, dy = to.y - from.y;
    const dist = Math.hypot(dx, dy);
    this.metrics.totalDistance += dist;
    this.metrics.steps += 1;
    // tiempo se puede acumular externo
    this.botPath.push({ x: to.x, y: to.y });
  },

  markCollected(idx) {
    this.points[idx].collected = true;
    this.pointsCollected += 1;
  },

  finalize(timeElapsedSec, energyFactor = 1.0) {
    this.metrics.timeElapsed = timeElapsedSec;
    this.metrics.energy = this.metrics.totalDistance * energyFactor;
  }
};

// PRNG auxiliar (por reproducibilidad)
function mulberry32(a) {
  return function() {
    var t = a += 0x6D2B79F5;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  }
}