anchor.diff:          // stderr.writeln(this);
       }
   
  -    String toString() { return "Pod: $id timeout $timeout shield $shield at ${super.toString()} to [$vx, $vy]"; }
  +    String toString() { return "Pod: $id checked $checked timeout $timeout shield $shield at ${super.toString()} to [${vx.truncate()}, ${vy.truncate()}]"; }
   
       Pod clone(){
           Pod clone = new Pod(this.id, this.x, this.y,
               this.vx, this.vy, this.angle, this.nextCP);
           clone.timeout = this.timeout;
           clone.shield = this.shield;
  -        clone.partner = this.partner;
  +        clone.shieldTimeout = this.shieldTimeout;
  +        clone.checked = this.checked;
  +        clone.boostUsed = this.boostUsed;
   
           return clone;
       }
  
positive.code:     void move(num t) {
          this.x += this.vx * t;
          this.y += this.vy * t;
      }
  
      void end() {
  
negative.code:     num thrust = 100 * (1 - ( 1 / (1 + pow(E, exp))));
      return thrust.round();
  }
  
  void play(List<Pod> pods, List<Checkpoint> checkpoints) {
      // This tracks the time during the turn. The goal is to reach 1.0
      num t = 0.0;
      List<Collision> processed = [];
  
      while (t < 1.0) {
          //stderr.writeln("Playing pods in while loop $processed");
          Collision firstCollision = null;
  
          // We look for all the collisions that are going to occur during the turn
          for (int i = 0; i < pods.length; ++i) {
              // Collision with another pod?
              for (int j = i + 1; j < pods.length; ++j) {
                  Collision col = pods[i].collision(pods[j]);
  
                  // If the collision occurs earlier than the one we currently have we keep it
                  if (col != null && col.t + t < 1.0 && !processed.contains(col) &&
                  (firstCollision == null || col.t < firstCollision.t ) ) {
                      //if(lastCollision == null || !lastCollision.ignorable(col) ){
                          firstCollision = col;
                      //}
                  }
              }
  
              // Collision with another checkpoint?
              // It is unnecessary to check all checkpoints here. 
              // We only test the pod's next checkpoint.
              // We could look for the collisions of the pod with 
              // all the checkpoints, but if such a collision happens 
              // it wouldn't impact the game in any way
              Collision col = pods[i].collision(checkpoints[pods[i].nextCP]);
  
              // If the collision happens earlier than the current one we keep it
              if (col != null && col.t + t < 1.0 &&
              (firstCollision == null || col.t < firstCollision.t) ) {
                  firstCollision = col;
              }
          }
  
          if (firstCollision == null) {
              // No collision, we can move the pods until the end of the turn
              for (int i = 0; i < pods.length; ++i) {
                  pods[i].move(1.0 - t);
              }
  
              // End of the turn
              t = 1.0;
          } else {
              // Move the pods to reach the time `t` of the collision
              for (int i = 0; i < pods.length; ++i) {
                  pods[i].move(firstCollision.t - t);
              }
  
              // Play out the collision
              firstCollision.a.bounce(firstCollision.b);
              processed.add(firstCollision);
  
              t += firstCollision.t;
          }
      }
  
      for (int i = 0; i < pods.length; ++i) {
          pods[i].end();
      }
  }
  
  void test(List<Pod> pods, List<Checkpoint> checkpoints) {
      for (int i = 0; i < pods.length; ++i) {
          pods[i].rotate(new Point(8000, 4500));
          pods[i].boost(100);
      }
  
      play(pods, checkpoints);
  }
  
  Solution naiveSolution(List<Pod> startPods, int length){
      List<Pod> pods = clonePods(startPods);
      List<List<Move>> moves = new List.generate(4, (index) => []);
      
      for(int moveIndex = 0; moveIndex < length; moveIndex++){
          // stderr.writeln("Naive Pods move $moveIndex of $length");
          // printPods(pods);
          [0, 1, 2, 3].forEach((index) {
              Point nextCP = checkpoints[pods[index].nextCP];
              int prevCPid = pods[index].nextCP - 1;
              if(prevCPid < 0 ){ prevCPid = cpCount - 1; }
              int nextNextCPid = pods[index].nextCP + 1;
              if(nextNextCPid >= cpCount  ){ nextNextCPid = 0; }
              
              num dx = checkpoints[prevCPid].x - checkpoints[nextNextCPid].x;
              num dy = checkpoints[prevCPid].y - checkpoints[nextNextCPid].y; 
              num length = sqrt(dx * dx + dy * dy);
              num scaleddx = (450 / length) * dx;
              num scaleddy = (450 / length) * dy;
              
              Point invert1 = new Point(
                  nextCP.x - scaleddy.round() - pods[index].vx * 3,
                  nextCP.y + scaleddx.round() - pods[index].vy * 3);
  
              Point invert2 = new Point(
                  nextCP.x + scaleddy.round() - pods[index].vx * 3,
                  nextCP.y - scaleddx.round() - pods[index].vy * 3);
  
              Point target = pods[index].closer(invert1, invert2);
              target = new Point(nextCP.x, nextCP.y);
              
              // stderr.writeln("Pod $index Angle: ${pods[index].angle} Angle to target ${pods[index].getAngle(target)} ");
              // stderr.writeln("Pod $index Diff Angle: ${pods[index].diffAngle(target)} ");
              num diffAngle = pods[index].diffAngle(target);
              if(diffAngle.abs() > 20){
                  moves[index].add(new Move(pods[index].diffAngle(target), 50));
              } else {
                  moves[index].add(new Move(pods[index].diffAngle(target), 100));
              }
              pods[index].apply(moves[index].last);
          });
          
          play(pods, checkpoints);
      }
      
      // printPods(pods);
      
      return new Solution(
          moves[0],
          moves[1],
          moves[2],
          moves[3]
          );
  
  }
  
  Solution monteCarlo(List<Pod> startPods, int length, int timeout){
      Stopwatch sw = new Stopwatch();
      sw.start();
      Solution naive = naiveSolution(startPods, length);
      stderr.writeln("Naive Solution: ${naive.score(startPods)}");
      Solution bestSolution = naive;
      num bestScore = naive.score(startPods);
      while(sw.elapsedMilliseconds < timeout){
          Solution randomSolution = new Solution(
              new List.generate(length, (index) => randomMove()),
              new List.generate(length, (index) => randomMove()),
              naive.moves2,
              naive.moves3);
          num randomEval = randomSolution.score(startPods);
          if( randomEval > bestScore){
              bestSolution = randomSolution;
              bestScore = randomEval;
          }
      }
      
      return bestSolution;
  }
  
  Solution genetic(List<Pod> startPods, int length, int timeout, Solution lastSolution){
      int poolSize = 10;
      
      Stopwatch sw = new Stopwatch();
      sw.start();
      Solution naive = naiveSolution(startPods, length);
      
      Map<num, Solution> pool = new SplayTreeMap();
      num naiveEval = naive.score(startPods);
      pool[naiveEval] = naive;
      stderr.writeln("Created naive $naiveEval ${sw.elapsedMilliseconds}");
      
      
      for(int i = 0; i < poolSize; i++){
          Solution randomSolution = new Solution(
              new List.generate(length, (index) => randomMove()),
              new List.generate(length, (index) => randomMove()),
              naive.moves2,
              naive.moves3);
          num randomEval = randomSolution.score(startPods);
          //stderr.writeln("${pool.length} $poolSize Random eval $randomEval");
          pool[randomEval] = randomSolution;
      }
      
      stderr.writeln("Filled pool ${pool.length} ${sw.elapsedMilliseconds}");
      stderr.writeln("Pool first ${pool.keys.first} last ${pool.keys.last}");
      
      while(sw.elapsedMilliseconds < timeout){
          // stderr.writeln("Creating GAs ${sw.elapsedMilliseconds}");
          //TODO generate new solution with mutation and crossover
              
          Solution mutatedSolution = pool[pool.keys.toList()[rand.nextInt(pool.length)]].clone();
          mutatedSolution.mutate();
          
          num geneticEval = mutatedSolution.score(startPods);
          if( geneticEval > pool.keys.first && !pool.containsKey(geneticEval)){
              // stderr.writeln("Keeping GA solution $geneticEval");
              pool[geneticEval] = mutatedSolution;
              pool.remove(pool.keys.first);
          }
      }
      stderr.writeln("Returning best of ${pool.length} ${sw.elapsedMilliseconds}");
      stderr.writeln("Pool first ${pool.keys.first} last ${pool.keys.last}");
      stderr.writeln("last key ${pool.keys.last} eval ${pool[pool.keys.last].score(startPods)}");
     
      return pool[pool.keys.last];
      
  }
  
  Move randomMove(){
      return new Move(-20 + rand.nextDouble() * 40, rand.nextInt(111) - 10);
  }
  
  printPods(List<Pod> pods){
      if(pods.length > 0){
          stderr.writeln(" ID|   X   |   Y   |   A| C|  T| S");
      }
      String id;
      String x;
      String y;
      String a;
      String c;
      String t;
      String s;
      for(Pod pod in pods){
          id = pod.id.toString().padLeft(3);
          x = pod.x.round().toString().padLeft(7);
          y = pod.y.round().toString().padLeft(7);
          a = pod.angle.round().toString().padLeft(4);
          c = pod.checked.toString().padLeft(2);
          t = pod.timeout.toString().padLeft(3);
          s = pod.shield.toString().padLeft(5);
          stderr.writeln("$id|$x|$y|$a|$c|$t|$s");
      }
  }
  
  List<Pod> clonePods(List<Pod> startPods){
      List<Pod> pods = startPods.map((pod) => pod.clone()).toList();
      
      pods[0].partner = pods[1];
      pods[1].partner = pods[0];
  
      pods[2].partner = pods[3];
      pods[3].partner = pods[2];
      
      return pods;
  }