Advertisement
Draco18s

Vampire Mk 7.1

Mar 31st, 2018
241
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // == Shared low-level helpers for all solutions ==
  2. var QUEEN = 5;
  3.  
  4. var WHITE = 1;
  5. var COL_MIN = WHITE;
  6. var COL_LIM = 9;
  7.  
  8. var CENTRE = 4;
  9.  
  10. var NOP = {cell: CENTRE};
  11.  
  12. var DIR_FORWARDS = false;
  13. var DIR_REVERSE = true;
  14. var SIDE_RIGHT = true;
  15. var SIDE_LEFT = false;
  16. function sanity_check(movement) {
  17.     var me = view[CENTRE].ant;
  18.     if(!movement || (movement.cell|0) !== movement.cell || movement.cell < 0 || movement.cell > 8) {
  19.         return false;
  20.     }
  21.     if(movement.type) {
  22.         if(movement.color) {
  23.             return false;
  24.         }
  25.         if((movement.type|0) !== movement.type || movement.type < 1 || movement.type > 4) {
  26.             return false;
  27.         }
  28.         if(view[movement.cell].ant || view[movement.cell].food) {
  29.             return false;
  30.         }
  31.         if(me.type !== QUEEN || me.food < 1) {
  32.             return false;
  33.         }
  34.         return true;
  35.     }
  36.     if(movement.color) {
  37.         if((movement.color|0) !== movement.color || movement.color < COL_MIN || movement.color >= COL_LIM) {
  38.             return false;
  39.         }
  40.         if(view[movement.cell].color === movement.color) {
  41.             return false;
  42.         }
  43.         return true;
  44.     }
  45.     if(view[movement.cell].ant && movement.cell != 4) {
  46.         return false;
  47.     }
  48.     if(view[movement.cell].food + me.food > 1 && me.type !== QUEEN) {
  49.         return false;
  50.     }
  51.     return true;
  52. }
  53.  
  54. function as_array(o) {
  55.     if(Array.isArray(o)) {
  56.         return o;
  57.     }
  58.     return [o];
  59. }
  60.  
  61. function best_of(movements) {
  62.     var m;
  63.     for(var i = 0; i < movements.length; ++ i) {
  64.         if(typeof(movements[i]) === 'function') {
  65.             m = movements[i]();
  66.         } else {
  67.             m = movements[i];
  68.         }
  69.         if(sanity_check(m)) {
  70.             return m;
  71.         }
  72.     }
  73.     return null;
  74. }
  75.  
  76. function play_safe(movement) {
  77.     // Avoid disqualification: no-op if moves are invalid
  78.     return best_of(as_array(movement)) || NOP;
  79. }
  80.  
  81. var RAND_SEED = (() => {
  82.     var s = 0;
  83.     for(var i = 0; i < 9; ++ i) {
  84.         s += view[i].color * (i + 1);
  85.         s += view[i].ant ? i * i : 0;
  86.         s += view[i].food ? i * i * i : 0;
  87.     }
  88.     return s % 29;
  89. })();
  90.  
  91. var ROTATIONS = [
  92.     [0, 1, 2, 3, 4, 5, 6, 7, 8],
  93.     [6, 3, 0, 7, 4, 1, 8, 5, 2],
  94.     [8, 7, 6, 5, 4, 3, 2, 1, 0],
  95.     [2, 5, 8, 1, 4, 7, 0, 3, 6],
  96. ];
  97.  
  98. function areAdjacent(A, B) {
  99.     if(A == 4 || B == 4 || A == B) return true;
  100.     if(A % 2 == 0 && B % 2 == 0) return false;
  101.     if(A % 2 == 1 && B % 2 == 0) return areAdjacent(B,A);
  102.     if(A % 2 == 1 && B % 2 == 1) return !(8-A == B || 8-B == A);
  103.     if(A == 0 && (B == 1 || B == 3)) return true;
  104.     if(A == 2 && (B == 1 || B == 5)) return true;
  105.     if(A == 6 && (B == 3 || B == 7)) return true;
  106.     if(A == 8 && (B == 5 || B == 7)) return true;
  107.     return false;
  108. }
  109.  
  110. function try_all(fns, limit, wrapperFn, checkFn) {
  111.     var m;
  112.     fns = as_array(fns);
  113.     for(var i = 0; i < fns.length; ++ i) {
  114.         if(typeof(fns[i]) !== 'function') {
  115.             if(checkFn(m = fns[i])) {
  116.                 return m;
  117.             }
  118.             continue;
  119.         }
  120.         for(var j = 0; j < limit; ++ j) {
  121.             if(checkFn(m = wrapperFn(fns[i], j))) {
  122.                 return m;
  123.             }
  124.         }
  125.     }
  126.     return null;
  127. }
  128.  
  129. function identify_rotation(testFns) {
  130.     // testFns MUST be functions, not constants
  131.     return try_all(
  132.         testFns,
  133.         4,
  134.         (fn, r) => fn(ROTATIONS[r]) ? ROTATIONS[r] : null,
  135.         (r) => r
  136.     );
  137. }
  138.  
  139. function near(a, b) {
  140.     return (
  141.         Math.abs(a % 3 - b % 3) < 2 &&
  142.         Math.abs(Math.floor(a / 3) - Math.floor(b / 3)) < 2
  143.     );
  144. }
  145.  
  146. function try_all_angles(solverFns) {
  147.     return try_all(
  148.         solverFns,
  149.         4,
  150.         (fn, r) => fn(ROTATIONS[r]),
  151.         sanity_check
  152.     );
  153. }
  154.  
  155. function try_all_cells(solverFns, skipCentre) {
  156.     return try_all(
  157.         solverFns,
  158.         9,
  159.         (fn, i) => ((i === CENTRE && skipCentre) ? null : fn(i)),
  160.         sanity_check
  161.     );
  162. }
  163.  
  164. function try_all_cells_near(p, solverFns) {
  165.     return try_all(
  166.         solverFns,
  167.         9,
  168.         (fn, i) => ((i !== p && near(p, i)) ? fn(i) : null),
  169.         sanity_check
  170.     );
  171. }
  172.  
  173. function ant_type_at(i, friend) {
  174.     return (view[i].ant && view[i].ant.friend === friend) ? view[i].ant.type : 0;
  175. }
  176.  
  177. function friend_at(i) {
  178.     return ant_type_at(i, true);
  179. }
  180.  
  181. function foe_at(i) {
  182.     return ant_type_at(i, false);
  183. }
  184.  
  185. function foe_near() {
  186.     for(var i = 0; i < 9; ++ i) {
  187.         if(i !== 4 && view[i].ant && !view[i].ant.friend) {
  188.             return true;
  189.         }
  190.     }
  191.     return false;
  192. }
  193.  
  194. function ant_type_near(p, friend) {
  195.     for(var i = 0; i < 9; ++ i) {
  196.         if(i !== 4 && ant_type_at(i, friend) && near(i, p)) {
  197.             return true;
  198.         }
  199.     }
  200.     return false;
  201. }
  202.  
  203. function move_agent(agents) {
  204.     var me = view[CENTRE].ant;
  205.     var buddies = [0, 0, 0, 0, 0, 0];
  206.     for(var i = 0; i < 9; ++ i) {
  207.         ++ buddies[friend_at(i)];
  208.     }
  209.  
  210.     for(var i = 0; i < agents.length; i += 2) {
  211.         if(agents[i] === me.type) {
  212.             return agents[i+1](me, buddies);
  213.         }
  214.     }
  215.     return null;
  216. }
  217.  
  218. function grab_nearby_food() {
  219.     return try_all_cells((i) => (view[i].food ? {cell: i} : null), true);
  220. }
  221.  
  222. function go_anywhere() {
  223.     return try_all_cells((i) => ({cell: i}), true);
  224. }
  225.  
  226. function colours_excluding(cols) {
  227.     var r = [];
  228.     for(var i = COL_MIN; i < COL_LIM; ++ i) {
  229.         if(cols.indexOf(i) === -1) {
  230.             r.push(i);
  231.         }
  232.     }
  233.     return r;
  234. }
  235.  
  236. function generate_band(start, width) {
  237.     var r = [];
  238.     for(var i = 0; i < width; ++ i) {
  239.         r.push(start + i);
  240.     }
  241.     return r;
  242. }
  243.  
  244. function colour_band(colours) {
  245.     return {
  246.         contains: function(c) {
  247.             return colours.indexOf(c) !== -1;
  248.         },
  249.         next: function(c) {
  250.             return colours[(colours.indexOf(c) + 1) % colours.length];
  251.         },
  252.         prev: function(c) {
  253.             return colours[(colours.indexOf(c) + colours.length - 1) % colours.length];
  254.         }
  255.     };
  256. }
  257.  
  258. function random_colour_band(colours) {
  259.     return {
  260.         contains: function(c) {
  261.             return colours.indexOf(c) !== -1;
  262.         },
  263.         next: function() {
  264.             return colours[RAND_SEED % colours.length];
  265.         }
  266.     };
  267. }
  268.  
  269. function fast_diagonal(colourBand) {
  270.     var m = try_all_angles([
  271.         // Avoid nearby checked areas
  272.         (rot) => {
  273.             if(
  274.                 !colourBand.contains(view[rot[0]].color) &&
  275.                 colourBand.contains(view[rot[5]].color) &&
  276.                 colourBand.contains(view[rot[7]].color)
  277.             ) {
  278.                 return {cell: rot[0]};
  279.             }
  280.         },
  281.  
  282.         // Go in a straight diagonal line if possible
  283.         (rot) => {
  284.             if(
  285.                 !colourBand.contains(view[rot[0]].color) &&
  286.                 colourBand.contains(view[rot[8]].color)
  287.             ) {
  288.                 return {cell: rot[0]};
  289.             }
  290.         },
  291.  
  292.         // When in doubt, pick randomly but avoid doubling-back
  293.         (rot) => (colourBand.contains(view[rot[0]].color) ? null : {cell: rot[0]}),
  294.  
  295.         // Double-back when absolutely necessary
  296.         (rot) => ({cell: rot[0]})
  297.     ]);
  298.  
  299.     // Lay a colour track so that we can avoid doubling-back
  300.     // (and mess up our foes as much as possible)
  301.     if(!colourBand.contains(view[CENTRE].color)) {
  302.         var prevCol = m ? view[8-m.cell].color : WHITE;
  303.  
  304.         var colours = [0, 0, 0, 0, 0, 0, 0, 0, 0];
  305.         for(var i = 0; i < 9; ++ i) {
  306.             ++ colours[view[i].color];
  307.         }
  308.  
  309.         return {cell: CENTRE, color: colourBand.next(prevCol)};
  310.     }
  311.  
  312.     return m;
  313. }
  314.  
  315. function checkAllNearEnvirons(colours, buddies) {
  316.         var nearMoves = [victims.length];
  317.         for(var e = 0; e < victims.length; e++) {
  318.                 var env = victims[e];
  319.                 nearMoves[e] = null;
  320.                 if(env.near_nest(colours)) {
  321.                         nearMoves[e] = env.near_nest_move(colours, buddies);
  322.                 }
  323.         }
  324.         return best_of(nearMoves);
  325. }
  326.  
  327. function follow_edge(obstacleFn, side) {
  328.     // Since we don't know which direction we came from, this can cause us to get
  329.     // stuck on islands, but the random orientation helps to ensure we don't get
  330.     // stuck forever.
  331.  
  332.     var order = ((side === SIDE_LEFT)
  333.         ? [0, 3, 6, 7, 8, 5, 2, 1, 0]
  334.         : [0, 1, 2, 5, 8, 7, 6, 3, 0]
  335.     );
  336.     return try_all(
  337.         [obstacleFn],
  338.         order.length - 1,
  339.         (fn, i) => (fn(order[i+1]) && !fn(order[i])) ? {cell: order[i]} : null,
  340.         sanity_check
  341.     );
  342. }
  343.  
  344. function start_dotted_path(colourBand, side, protectedCols) {
  345.     var right = (side === SIDE_RIGHT);
  346.     return try_all_angles([
  347.         (rot) => ((
  348.             !protectedCols.contains(view[rot[right ? 5 : 3]].color) &&
  349.             !colourBand.contains(view[rot[right ? 5 : 3]].color) &&
  350.             !colourBand.contains(view[rot[right ? 2 : 0]].color) &&
  351.             !colourBand.contains(view[rot[1]].color)
  352.         )
  353.             ? {cell: rot[right ? 5 : 3], color: colourBand.next(WHITE)}
  354.             : null)
  355.     ]);
  356. }
  357.  
  358. function lay_dotted_path(colourBand, side, protectedCols) {
  359.     var right = (side === SIDE_RIGHT);
  360.     return try_all_angles([
  361.         (rot) => {
  362.             var ahead = rot[right ? 2 : 0];
  363.             var behind = rot[right ? 8 : 6];
  364.             if(
  365.                 colourBand.contains(view[behind].color) &&
  366.                 !protectedCols.contains(view[ahead].color) &&
  367.                 !colourBand.contains(view[ahead].color) &&
  368.                 !colourBand.contains(view[rot[right ? 6 : 8]].color)
  369.             ) {
  370.                 return {cell: ahead, color: colourBand.next(view[behind].color)};
  371.             }
  372.         }
  373.     ]);
  374. }
  375.  
  376. function follow_dotted_path(colourBand, side, direction) {
  377.     var forwards = (direction === DIR_REVERSE) ? 7 : 1;
  378.     var right = (side === SIDE_RIGHT);
  379.  
  380.     return try_all_angles([
  381.         // Cell on our side? advance
  382.         (rot) => {
  383.             if(
  384.                 colourBand.contains(view[rot[right ? 5 : 3]].color) &&
  385.                 // Prevent sticking / trickery
  386.                 !colourBand.contains(view[rot[right ? 3 : 5]].color) &&
  387.                 !colourBand.contains(view[rot[0]].color) &&
  388.                 !colourBand.contains(view[rot[2]].color)
  389.             ) {
  390.                 return {cell: rot[forwards]};
  391.             }
  392.         },
  393.  
  394.         // Cell ahead and behind? advance
  395.         (rot) => {
  396.             var passedCol = view[rot[right ? 8 : 6]].color;
  397.             var nextCol = view[rot[right ? 2 : 0]].color;
  398.             if(
  399.                 colourBand.contains(passedCol) &&
  400.                 nextCol === colourBand.next(passedCol) &&
  401.  
  402.                 // Prevent sticking / trickery
  403.                 !colourBand.contains(view[rot[right ? 3 : 5]].color) &&
  404.                 !colourBand.contains(view[rot[right ? 0 : 2]].color)
  405.             ) {
  406.                 return {cell: rot[forwards]};
  407.             }
  408.         }
  409.     ]);
  410. }
  411.  
  412. function escape_dotted_path(colourBand, side, newColourBand) {
  413.     var right = (side === SIDE_RIGHT);
  414.     if(!newColourBand) {
  415.         newColourBand = colourBand;
  416.     }
  417.  
  418.     return try_all_angles([
  419.         // Escape from beside the line
  420.         (rot) => {
  421.             var approachingCol = view[rot[right ? 2 : 0]].color;
  422.             if(
  423.                 !colourBand.contains(view[rot[right ? 8 : 6]].color) ||
  424.                 !colourBand.contains(approachingCol) ||
  425.                 colourBand.contains(view[rot[7]].color) ||
  426.                 colourBand.contains(view[rot[right ? 6 : 8]].color)
  427.             ) {
  428.                 // not oriented, or in a corner
  429.                 return null;
  430.             }
  431.             return best_of([
  432.                 {cell: rot[right ? 0 : 2], color: newColourBand.next(approachingCol)},
  433.                 {cell: rot[right ? 3 : 5]},
  434.                 {cell: rot[right ? 0 : 2]},
  435.                 {cell: rot[right ? 6 : 8]},
  436.                 {cell: rot[right ? 2 : 0]},
  437.                 {cell: rot[right ? 8 : 6]},
  438.                 {cell: rot[right ? 5 : 3]}
  439.             ]);
  440.         },
  441.  
  442.         // Escape from inside the line
  443.         (rot) => {
  444.             if(
  445.                 !colourBand.contains(view[rot[7]].color) ||
  446.                 !colourBand.contains(view[rot[1]].color) ||
  447.                 colourBand.contains(view[CENTRE].color)
  448.             ) {
  449.                 return null;
  450.             }
  451.             return best_of([
  452.                 {cell: rot[3]},
  453.                 {cell: rot[5]},
  454.                 {cell: rot[0]},
  455.                 {cell: rot[2]},
  456.                 {cell: rot[6]},
  457.                 {cell: rot[8]}
  458.             ]);
  459.         }
  460.     ]);
  461. }
  462.  
  463. function latch_to_dotted_path(colourBand, side) {
  464.     var right = (side === SIDE_RIGHT);
  465.  
  466.     return try_all_angles([
  467.         (rot) => {
  468.             var approachingCol = view[rot[right ? 2 : 0]].color;
  469.             if(
  470.                 colourBand.contains(approachingCol) &&
  471.                 view[rot[right ? 8 : 6]].color === colourBand.next(approachingCol) &&
  472.                 !colourBand.contains(view[rot[right ? 5 : 3]].color)
  473.             ) {
  474.                 // We're on the wrong side; go inside the line
  475.                 return {cell: rot[right ? 5 : 3]};
  476.             }
  477.         },
  478.  
  479.         // Inside the line? pick a side
  480.         (rot) => {
  481.             var passedCol = view[rot[7]].color;
  482.             var approachingCol = view[rot[1]].color;
  483.             if(
  484.                 !colourBand.contains(passedCol) ||
  485.                 !colourBand.contains(approachingCol) ||
  486.                 colourBand.contains(view[CENTRE].color)
  487.             ) {
  488.                 return null;
  489.             }
  490.             if((approachingCol === colourBand.next(passedCol)) === right) {
  491.                 return best_of([{cell: rot[3]}, {cell: rot[6]}, {cell: rot[0]}]);
  492.             } else {
  493.                 return best_of([{cell: rot[5]}, {cell: rot[2]}, {cell: rot[8]}]);
  494.             }
  495.         }
  496.     ]);
  497. }
  498.  
  499.  
  500. // == High-level logic begins here ==
  501.  
  502.  
  503. var TARGET_COLOURS_ZIG = colour_band([4, 5, 7, 8]);
  504. var TARGET_COLOURS_FIREFLY = colour_band([2, 5, 8]);
  505. var GROUND_COLOURS_BH = colour_band([2, 7, 8]);
  506. var SAFE_COLOURS = random_colour_band([8]);
  507.  
  508. var THIEF = 1;
  509. var BOUNCER = 2;
  510. var LANCE = 3;
  511. var LANCE_TIP = 4;
  512.  
  513. var INITIAL_GATHER = 12;
  514.  
  515. function colour_band_prev(band, base) {
  516.     if(!band.contains(base)) {
  517.         return band.next(WHITE);
  518.     }
  519.     var cur = band.next(base);
  520.     var c;
  521.     while((c = band.next(cur)) !== base) {
  522.         cur = c;
  523.     }
  524.     return cur;
  525. }
  526.  
  527. function white_near(p) {
  528.     for(var i = 0; i < 9; ++ i) {
  529.         if(near(i, p) && view[i].color === WHITE) {
  530.             return true;
  531.         }
  532.     }
  533.     return false;
  534. }
  535.  
  536. function white_near(p, min) {
  537.     var c = 0;
  538.     for(var i = 0; i < 9; ++ i) {
  539.         if(near(i, p) && view[i].color === WHITE) {
  540.             if(++c >= min) return true;
  541.         }
  542.     }
  543.     return false;
  544. }
  545.  
  546. var TARGET_ARRANGEMENT_RAIL = [
  547.     [8,4,5,8,5,2,4,2,6],
  548.     [8,5,2,4,2,6,6,4,5],
  549.     [4,2,6,6,4,5,8,4,5],
  550.     [6,4,5,8,4,5,8,5,2]
  551. ];
  552. var TARGET_NEAR_RAIL = [
  553.     [5,8,0,4,8,0,6,4,0],
  554.     [2,4,0,5,8,0,4,8,0],
  555.     [4,6,0,2,4,0,5,8,0],
  556.     [4,8,0,4,6,0,2,4,0],
  557.     [4,5,0,5,2,0,2,6,0],
  558.     [4,5,0,4,5,0,5,2,0],
  559.     [2,6,0,4,5,0,4,5,0],
  560.     [5,2,0,2,6,0,4,5,0]
  561. ];
  562. var TARGET_COLOURS_RAIL1 = colour_band([4,2,5,4]);
  563. var rail_miners = {
  564.     name:function() { return "rail_miners"; },
  565.     near_nest: function(colours) {
  566.         var bestScore = 0;
  567.         var enemyQueen = false;
  568.         // check every rotation for each 3x3 rail possibility
  569.         TARGET_NEAR_RAIL.forEach(function (arrangement) {
  570.             ROTATIONS.forEach(function (rot){
  571.                 var score = 0;
  572.                 for(var i = 0; i < 9; i++) {
  573.                     score += arrangement[i] == view[rot[i]].color?1:0;
  574.                     enemyQueen |= view[i].ant && view[i].ant.type == QUEEN && !view[i].ant.friend;
  575.                 }
  576.                 if(score > bestScore) {
  577.                     bestScore = score;
  578.                 }
  579.             });
  580.         });
  581.         if(bestScore >= (5 - (enemyQueen && view[4].ant.type == 1?1:0))) {
  582.             if(highway.likely_nest(colours)) return false;
  583.             return true;
  584.         }
  585.     },
  586.     worth_leeching: function(myFood, buddies) {
  587.         if(buddies[LANCE_TIP]) return true;
  588.         if(buddies[THIEF]) return myFood > 6 && ((myFood < 500) || (myFood % 3 == 0));
  589.         return myFood > 6 && ((myFood < 120) || (myFood % 3 == 0));
  590.     },
  591.     near_nest_move: function(colours, buddies) {
  592.         if(!this.worth_leeching(view[4].ant.food, buddies)) return null;
  593.         //if there's an ant we're dodging
  594.         var victim_pos = -1;
  595.         var enemyCount = 0;
  596.         for(var a=0; a<9; a++) {
  597.             if(view[a].ant != null && !view[a].ant.friend) {
  598.                 if(view[a].ant.type == 5) {
  599.                     victim_pos = a;
  600.                 }
  601.                 else {
  602.                     enemyCount++;
  603.                 }
  604.             }
  605.         }
  606.         if(victim_pos >= 0 && enemyCount >= buddies[THIEF]) {
  607.             //if next to the queen and we're outnumbered, move back to the center of the rail.
  608.             var target = TARGET_COLOURS_RAIL1.prev(view[victim_pos].color);
  609.             var best = best_of([
  610.                 try_all_cells((i) => ((view[i].color == target && i != 4 && areAdjacent(i,victim_pos)) ? {cell: i} : null)),
  611.             ]);
  612.             if(best != null) return best;
  613.         }
  614.         for(var a=0; a<9; a++) {
  615.             if(a != 4 && view[a].ant == null) {
  616.                 var target = TARGET_COLOURS_RAIL1.prev(view[a].color);
  617.                 var ret = best_of([
  618.                     try_all_cells((i) => ((i != a && view[i].color == target && i % 2 == 0 && areAdjacent(i,a)) ? {cell: i} : null)),
  619.                     NOP
  620.                 ]);
  621.                 if(ret.cell != 4) {
  622.                     return ret;
  623.                 }
  624.             }
  625.         }
  626.         if(!buddies[THIEF]) {
  627.             //if there is not and we are alone
  628.             for(var a=0; a<9; a++) {
  629.                 if(a != 4) {
  630.                     var target = TARGET_COLOURS_RAIL1.prev(view[a].color);
  631.                     var best = best_of([
  632.                         try_all_cells((i) => ((view[i].color == target && i != 4 && areAdjacent(i,a)) ? {cell: i} : null)),
  633.                     ]);
  634.                     if(best != null) return best;
  635.                 }
  636.             }
  637.         }
  638.         return null;
  639.     },
  640.     likely_nest: function(colours) {
  641.         var bestScore = 0;
  642.         // check every rotation for each 3x3 rail possibility
  643.         var q = 0;
  644.         TARGET_ARRANGEMENT_RAIL.forEach(function (arrangement) {
  645.             var j = 0;
  646.             ROTATIONS.forEach(function (rot){
  647.                 var score = 0;
  648.                 for(var i = 0; i < 9; i++) {
  649.                     score += arrangement[i] == view[rot[i]].color?1:0;
  650.                     //score -= view[rot[i]].color == 7?2:0;//fake rail detection
  651.                 }
  652.                 if(score > bestScore) {
  653.                     bestScore = score;
  654.                 }
  655.                 j++;
  656.             });
  657.             q++;
  658.         });
  659.         if(view[4].ant.type == THIEF && view[4].color == 4 && bestScore >= 3 && view[1].color != view[7].color && view[3].color != view[5].color) return true;
  660.         if(bestScore >= 8) {
  661.             if(highway.likely_nest(colours)) return false;
  662.             return true;
  663.         }
  664.  
  665.         return false;
  666.     },
  667.  
  668.     likely_victim: function(victim_pos) {
  669.         return true;
  670.     },
  671.  
  672.     follow_victim: function(me, buddies, colours, victim_pos) {
  673.         if(me.type === THIEF && !buddies[QUEEN]) {
  674.             // Queen may be on other side of victim; try orbiting to find her
  675.             return try_all_angles([
  676.                 (rot) => ((victim_pos === rot[1]) ? {cell: rot[0]} : null),
  677.                 (rot) => ((victim_pos === rot[0]) ? {cell: rot[3]} : null)
  678.             ]);
  679.         }
  680.  
  681.         if(me.type === QUEEN && !buddies[THIEF] && me.food > 0) {
  682.             // Make a buddy to help us steal
  683.             return best_of([
  684.                 (victim_pos % 2 == 0 && view[deRotate(victim_pos,2)].ant == null) ? {cell: deRotate(victim_pos,2), type: THIEF} : null,
  685.                 try_all_cells((i) => (near(i, victim_pos) ? {cell: i, type: THIEF} : null), true)
  686.             ]);
  687.         }
  688.         var current = view[CENTRE].color;
  689.         var target = TARGET_COLOURS_RAIL1.next(current);
  690.         var antitarget = TARGET_COLOURS_RAIL1.prev(current);
  691.         var forwardCell = -1
  692.         for(var i = 0; i < 9; i++) {
  693.             if(i % 2 == 1 && view[i].color == target && view[8-i].color == antitarget){
  694.                 forwardCell = i;
  695.             }
  696.         }
  697.         if(forwardCell < 0 && current == 4) {
  698.             current = 4
  699.             target = 4
  700.             antitarget = 5
  701.             for(var i = 0; i < 9; i++) {
  702.                 if(i % 2 == 1 && view[i].color == target && view[8-i].color == antitarget){
  703.                     forwardCell = i;
  704.                 }
  705.             }
  706.         }
  707.         return best_of([
  708.             // Crazy multi-stage logic to inject thieves and blockers where we need them
  709.             (view[victim_pos].ant.food > 30 && view[deRotate(victim_pos,1)].ant != null && !view[deRotate(victim_pos,1)].ant.friend && view[deRotate(victim_pos,1)].ant.type == 4) &&
  710.                 (victim_pos % 2 == 0 && view[deRotate(victim_pos,2)].ant == null) ? {cell: deRotate(victim_pos,2), type: LANCE_TIP} : null,
  711.             (view[victim_pos].ant.food > 30 && view[deRotate(victim_pos,-1)].ant != null && !view[deRotate(victim_pos,-1)].ant.friend && view[deRotate(victim_pos,-1)].ant.type == 4) &&
  712.                 (victim_pos % 2 == 0 && view[deRotate(victim_pos,-2)].ant == null) ? {cell: deRotate(victim_pos,-2), type: LANCE_TIP} : null,
  713.             (view[victim_pos].ant.food > 30 && !buddies[LANCE_TIP]) &&
  714.                 (victim_pos % 2 == 0 && view[deRotate(victim_pos,4)].ant == null) ? {cell: deRotate(victim_pos,4), type: THIEF} : null,
  715.             (view[victim_pos].ant.food > 30) &&
  716.                 (forwardCell == victim_pos && victim_pos % 2 == 1 && view[deRotate(victim_pos,4)].ant != null && view[deRotate(victim_pos,4)].ant.friend) ? {cell: deRotate(victim_pos,3)} : null,
  717.  
  718.             (view[victim_pos].ant.food > 30) &&
  719.                 try_all_cells((i) => (near(i, victim_pos) ? {cell: i, type: THIEF} : null), true),
  720.  
  721.             // Continue to steal and get fat
  722.             NOP
  723.         ]);
  724.     },
  725.     find_victim: function(me, buddies, colours) {
  726.         var current = view[CENTRE].color;
  727.         if(me.type === QUEEN && current == WHITE && buddies[LANCE_TIP]) {
  728.             //TODO: better bounce-out
  729.             //if(buddies[LANCE_TIP] == 2 && me.food > 150) {
  730.             //    return best_of([
  731.             //        try_all_cells((i) => (view[i].ant == null ? {cell: i, type: BOUNCER} : null), true),
  732.             //        NOP
  733.             //    ]);
  734.             //}
  735.             return NOP;
  736.         }
  737.         // Easier to navigate alone, and thief reaching queen first would be a
  738.         // disaster, so the thief just gives up.
  739.         if(me.type === THIEF) {
  740.             for(var i = 0; i < 9; ++ i) {
  741.                 if(foe_at(i) === QUEEN) {
  742.                     return NOP;
  743.                 }
  744.                 if(foe_at(i) === 4 && view[deRotate(i,1)].ant != null && view[deRotate(i,1)].ant.type == QUEEN) {
  745.                     //TODO: better bounce-out
  746.                     //if(view[8-deRotate(i,1)].ant != null && view[8-deRotate(i,1)].ant.type != QUEEN && i == 0) {
  747.                     //    return {cell:deRotate(i,1),color:WHITE}
  748.                     //}
  749.                     return NOP;
  750.                 }
  751.             }
  752.             return go_anywhere;
  753.         }
  754.         var target = TARGET_COLOURS_RAIL1.next(current);
  755.         var antitarget = TARGET_COLOURS_RAIL1.prev(current);
  756.         var ret = best_of([
  757.             try_all_cells((i) => ((i % 2 == 1 && view[i].color == target && view[8-i].color == antitarget) ? {cell: i} : null)),
  758.             //try_all_cells((i) => ((view[i].color == current) ? {cell: i} : null)),
  759.  
  760.             // We actually don't want to move if we're on the rails and there's no open next spot
  761.             NOP
  762.         ]);
  763.         if((ret == null || ret.cell == 4) && current == 4) {
  764.             current = 4
  765.             target = 4
  766.             antitarget = 5
  767.             ret = best_of([
  768.                 try_all_cells((i) => ((i % 2 == 1 && view[i].color == target && view[8-i].color == antitarget) ? {cell: i} : null)),
  769.                 //try_all_cells((i) => ((view[i].color == current) ? {cell: i} : null)),
  770.  
  771.                 // We actually don't want to move if we're on the rails and there's no open next spot
  772.                 NOP
  773.             ]);
  774.         }
  775.         // if there wasn't an obvious "forward" spot...
  776.         if(ret == null || ret.cell == 4) {
  777.             if(buddies[LANCE_TIP]) {
  778.                 return best_of([
  779.                     try_all_cells((i) => (view[deRotate(i,2)].ant != null && view[deRotate(i,2)].ant.friend && view[deRotate(i,2)].ant.type == LANCE_TIP && view[deRotate(i,1)].ant != null && !view[deRotate(i,1)].ant.friend && view[deRotate(i,1)].ant.type == 4) ? {cell: i, type:THIEF} : null),
  780.                     try_all_cells((i) => (view[deRotate(i,-2)].ant != null && view[deRotate(i,-2)].ant.friend && view[deRotate(i,-2)].ant.type == LANCE_TIP && view[deRotate(i,-1)].ant != null && !view[deRotate(i,-1)].ant.friend && view[deRotate(i,-1)].ant.type == 4) ? {cell: i, type:THIEF} : null),
  781.  
  782.                     // We actually don't want to move if we're on the rails and there's no open next spot
  783.                     NOP
  784.                 ]);
  785.             }
  786.             var numAnts = 0;
  787.             var enemyBehind = false;
  788.             for(var na = 0; na < 9; na++) {
  789.                 if(view[na].ant != null && !view[na].ant.friend) numAnts++;
  790.                 if(view[na].ant != null && view[na].color == antitarget) enemyBehind = true;
  791.             }
  792.             if(numAnts == 0 || (enemyBehind && numAnts == 1)) {
  793.                 // if we're the only ant we can see (that is: unable to follow the rail)...
  794.                 if(rail_repair.likely_nest(colours)) {
  795.                     //try repairing the rail
  796.                     ret = rail_repair.find_victim(me, buddies, colours);
  797.                 }
  798.                 else {
  799.                     // otherwise go anywhere
  800.                     ret = go_anywhere;
  801.                 }
  802.             }
  803.             else {
  804.                 var likelyType4 = -1;
  805.                 for(var t = 0; t < 9; t++) {
  806.                     if(view[t].ant && !view[t].ant.friend && view[t].ant.type == 4) {
  807.                         likelyType4 = t;
  808.                     }
  809.                 }
  810.                 if(likelyType4 >= 0 && view[likelyType4].ant.food == 0 && me.food > 0) {
  811.                     ret = best_of([
  812.                         try_all_cells((i) => (buddies[THIEF] < 2 && i % 2 == 0 && view[i].ant == null && near(i,likelyType4)) ? {cell: i,type:THIEF} : null),
  813.                         NOP
  814.                         ]);
  815.                 }
  816.                 else {
  817.                     ret = go_anywhere;
  818.                 }
  819.             }
  820.         }
  821.         else {
  822.             ret = best_of([
  823.                 try_all_cells((i) => (i % 2 == 1 && view[i].food > 0) ? {cell: i} : null),
  824.                 ret
  825.             ]);
  826.         }
  827.         return ret;
  828.     }
  829. };
  830.  
  831. var TARGET_RAIL_REPAIRATION = [
  832.     [0,0,0,8,0,0,4,2,6],
  833.     [0,0,0,4,0,0,6,4,5],
  834.     [0,0,0,6,0,0,8,4,5],
  835.     [0,0,0,8,0,0,8,5,2]
  836. ];
  837. var knownRotation = -1;
  838. var knownNest = -1;
  839. var rail_repair = {
  840.     name:function() { return "rail_repair"; },
  841.     near_nest: function(colours) { return false; },
  842.     near_nest_move: function(colours, buddies) { return null; },
  843.     likely_nest: function(colours) {
  844.         var bestScore = 0;
  845.         // check every rotation for each 3x3 rail possibility
  846.         TARGET_RAIL_REPAIRATION.forEach(function (arrangement) {
  847.             ROTATIONS.forEach(function (rot){
  848.                 var score = 0;
  849.                 for(var i = 0; i < 9; i++) {
  850.                     score += arrangement[i] == view[rot[i]].color?1:0;
  851.                     //score -= view[rot[i]].color == 7?1:0;//fake rail
  852.                     score -= i != 4 && view[rot[i]].ant != null && view[rot[i]].ant.friend ? 4 : 0;
  853.                 }
  854.                 if(score > bestScore) {
  855.                     bestScore = score;
  856.                     knownRotation = ROTATIONS.indexOf(rot);
  857.                     knownNest = TARGET_RAIL_REPAIRATION.indexOf(arrangement);
  858.                 }
  859.             });
  860.         });
  861.         if(bestScore >= 4) {
  862.             if(highway.likely_nest(colours)) return false;
  863.             return true;
  864.         }
  865.  
  866.         return false;
  867.     },
  868.     worth_leeching: function(myFood, buddies) {
  869.         return myFood > 4 && ((myFood < 80) || (myFood % 3 == 0));
  870.     },
  871.     likely_victim: function(victim_pos) {
  872.         if(view[victim_pos].color === WHITE) {
  873.             return false;
  874.         }
  875.         for(var i = 0; i < 9; ++ i) {
  876.             if(i !== victim_pos && near(i, victim_pos)) {
  877.                 return true;
  878.             }
  879.         }
  880.         return false;
  881.     },
  882.  
  883.     follow_victim: function(me, buddies, colours, victim_pos) {
  884.         if(me.type === THIEF && !buddies[QUEEN]) {
  885.             // Queen may be on other side of victim; try orbiting to find her
  886.             return try_all_angles([
  887.                 (rot) => ((victim_pos === rot[1]) ? {cell: rot[0]} : null),
  888.                 (rot) => ((victim_pos === rot[0]) ? {cell: rot[3]} : null)
  889.             ]);
  890.         }
  891.  
  892.         if(me.type === QUEEN && !buddies[THIEF] && me.food > 0) {
  893.             // Make a buddy to help us steal
  894.             return best_of([
  895.                 try_all_cells((i) => (near(i, victim_pos) ? {cell: i, type: THIEF} : null), true),
  896.                 try_all_cells((i) => ({cell: i, type: THIEF}), true)
  897.             ]);
  898.         }
  899.  
  900.         return best_of([
  901.             // Ziggurat will try to drown us in workers; make sure we have plenty
  902.             // of our own (but only if it's worth it)
  903.             // Rail Miners doesn't currently spam workers, but this code doesn't hurt
  904.             (view[victim_pos].ant.food > 4 && buddies[THIEF] < 3) &&
  905.                 try_all_cells((i) => (near(i, victim_pos) ? {cell: i, type: THIEF} : null), true),
  906.  
  907.             // Continue to steal and get fat
  908.             NOP
  909.         ]);
  910.     },
  911.  
  912.     find_victim: function(me, buddies, colours) {
  913.         var current = view[CENTRE].color;
  914.  
  915.         // Easier to navigate alone, and thief reaching queen first would be a
  916.         // disaster, so the thief just gives up.
  917.         if(me.type === THIEF) {
  918.             return go_anywhere;
  919.         }
  920.  
  921.         //find first bad cell to repaint
  922.         var PAINT_ORDER = [0, 2, 3, 4, 5, 6, 7, 8, 1];
  923.         for(var o = 0; o < 9; o++) {
  924.             var i = PAINT_ORDER[o];
  925.             if(TARGET_ARRANGEMENT_RAIL[knownNest][i] != view[ROTATIONS[knownRotation][i]].color) {
  926.                 return {cell:ROTATIONS[knownRotation][i],color:TARGET_ARRANGEMENT_RAIL[knownNest][i]}
  927.             }
  928.         }
  929.         return {cell:ROTATIONS[knownRotation][1]};
  930.     }
  931. };
  932.  
  933. var TARGET_ARRANGEMENT_WIND = [
  934.         [5,4,0,7,6,0,6,4,0],
  935.         [7,6,0,6,4,0,5,4,0],
  936.         [6,4,0,5,4,0,7,6,0]
  937. ];
  938. var TARGET_ARRANGEMENT_WINDCENTER = [
  939.         [2,7,6,2,6,4,6,5,4],
  940.         [2,6,4,6,5,4,2,7,6],
  941.         [6,5,4,2,7,6,2,6,4]
  942. ];
  943. var WIND_BAND = colour_band([5,6,7]);
  944. var windmill = {
  945.     name:function() { return "windmill"; },
  946.     near_nest: function(colours) { return false; },
  947.     near_nest_move: function(colours, buddies) { return null; },
  948.     likely_nest: function(colours) { // Main nest detection
  949.         var bestScore = 0;
  950.         // check every rotation for each 3x3 rail possibility
  951.         TARGET_ARRANGEMENT_WIND.forEach(function (arrangement) {
  952.             ROTATIONS.forEach(function (rot){
  953.                 var score = 0;
  954.                 for(var i = 0; i < 9; i++) {
  955.                     score += arrangement[i] == view[rot[i]].color?1:0;
  956.                 }
  957.                 if(score > bestScore) {
  958.                     bestScore = score;
  959.                 }
  960.             });
  961.         });
  962.         if(bestScore >= 5) {
  963.             return true;
  964.         }
  965.  
  966.         var bestScore = 0;
  967.         // check every rotation for each 3x3 rail possibility
  968.         TARGET_ARRANGEMENT_WINDCENTER.forEach(function (arrangement) {
  969.             ROTATIONS.forEach(function (rot){
  970.                 var score = 0;
  971.                 for(var i = 0; i < 9; i++) {
  972.                     score += arrangement[i] == view[rot[i]].color?1:0;
  973.                 }
  974.                 if(score > bestScore) {
  975.                     bestScore = score;
  976.                 }
  977.             });
  978.         });
  979.         if(bestScore >= 8) {
  980.             return true;
  981.         }
  982.         var buddies = [0, 0, 0, 0, 0, 0];
  983.         for(var i = 0; i < 9; ++ i) {
  984.             ++ buddies[friend_at(i)];
  985.         }
  986.         return buddies[LANCE] || buddies[LANCE_TIP];
  987.     },
  988.     worth_leeching: function(myFood, buddies) {
  989.         return myFood > 5 || (myFood > 1 && buddies[LANCE]);
  990.     },
  991.     likely_victim: function(victim_pos) {
  992.         return false;
  993.     },
  994.  
  995.     follow_victim: function(me, buddies, colours, victim_pos) {
  996.         // nest is chaotic and varies by direction of approach
  997.         // We'll let the Find Victim logic handle this
  998.         return NOP;
  999.     },
  1000.  
  1001.     find_victim: function(me, buddies, colours) {
  1002.         if(buddies[LANCE_TIP]) {
  1003.             var lancePos = -1;
  1004.             for(var i=0;i<9;i++) {
  1005.                 if(view[i].ant && view[i].ant.friend && view[i].ant.type == LANCE_TIP) {
  1006.                     lancePos = i;
  1007.                 }
  1008.             }
  1009.             if(buddies[LANCE]) {
  1010.                 if(view[8-lancePos].ant != null && view[8-lancePos].ant.friend && view[8-lancePos].ant.type == 3) {
  1011.                     return NOP;
  1012.                 }
  1013.                 if(view[deRotate(lancePos,3)].ant != null && view[deRotate(lancePos,3)].ant.friend && view[deRotate(lancePos,3)].ant.type == 3) {
  1014.                     if(view[lancePos].color == 2 && view[deRotate(lancePos,1)].color == 5) {
  1015.                         return {cell:deRotate(lancePos,1)};
  1016.                     }
  1017.                     return NOP;
  1018.                 }
  1019.                 if(view[4].color == 6 && view[lancePos].color == 6 && view[deRotate(lancePos,1)].ant != null && view[deRotate(lancePos,1)].ant.type == LANCE && view[deRotate(lancePos,1)].ant.friend) {
  1020.                     if(view[deRotate(lancePos,2)].ant != null && !view[deRotate(lancePos,2)].ant.friend) {
  1021.                         return {cell:8-deRotate(lancePos,2)};
  1022.                     }
  1023.                     return NOP;
  1024.                 }
  1025.                 if(view[lancePos].color == 2 && view[deRotate(lancePos,-3)].color == 5 && view[deRotate(lancePos,-3)].ant != null && view[deRotate(lancePos,-3)].ant.friend && view[deRotate(lancePos,-3)].ant.type == LANCE) {
  1026.                     return NOP;
  1027.                 }
  1028.                 return {cell:deRotate(lancePos,-1)};
  1029.             }
  1030.             if(view[deRotate(lancePos,-1)].ant != null && view[deRotate(lancePos,-1)].ant.type == 5) {
  1031.                 return {cell:deRotate(lancePos,2)};
  1032.             }
  1033.             if(view[lancePos].color == 5 && view[deRotate(lancePos,1)].color == 7) {
  1034.                 return {cell:deRotate(lancePos,1)};
  1035.             }
  1036.             return {cell:deRotate(lancePos,-1)};
  1037.         }
  1038.         else if(buddies[LANCE]) {
  1039.             var lancePos = -1;
  1040.             for(var i=0;i<9;i++) {
  1041.                 if(view[i].ant && view[i].ant.friend && view[i].ant.type == LANCE) {
  1042.                     lancePos = i;
  1043.                 }
  1044.             }
  1045.             if(view[4].color == 3 && lancePos % 2 == 1) return NOP;
  1046.             //if(view[lancePos].ant.food > 0) return NOP;
  1047.             var moveNext = lancePos % 2 == 1 ? {cell:deRotate(lancePos,2)} : {cell:deRotate(lancePos,1)};
  1048.             if(view[moveNext.cell].ant != null && !view[moveNext.cell].ant.friend) {
  1049.                 moveNext = {cell:deRotate(lancePos,1),type:LANCE_TIP};
  1050.             }
  1051.             if(view[lancePos].ant.food > 0) {
  1052.                 return {cell:deRotate(lancePos,4),type:LANCE_TIP};
  1053.             }
  1054.  
  1055.             if(view[deRotate(lancePos,1)].ant != null && !view[deRotate(lancePos,1)].ant.friend && view[deRotate(lancePos,2)].ant != null && !view[deRotate(lancePos,2)].ant.friend) {
  1056.                 return {cell:deRotate(lancePos,-2), type: BOUNCER};
  1057.             }
  1058.  
  1059.             if(view[lancePos].color == 6 && view[moveNext.cell].color == 8 && view[deRotate(lancePos,2)].color == 5) {
  1060.                 return {cell:moveNext.cell,type:LANCE_TIP};
  1061.             }
  1062.  
  1063.             return moveNext;
  1064.         }
  1065.         else {
  1066.             var current = view[CENTRE].color;
  1067.             var standOn = WIND_BAND.next(WIND_BAND.next(WIND_BAND.next(current)));
  1068.             var target = WIND_BAND.next(current);
  1069.             var antitarget = WIND_BAND.next(target);
  1070.             if(current != standOn) return wilderness.find_victim(me, buddies, colours);
  1071.  
  1072.             var ret = best_of([
  1073.                 try_all_cells((i) => ((i % 2 == 1 && view[i].color == target && view[8-i].color == antitarget && !(view[deRotate(i,1)].color == 2 && view[deRotate(i,-1)].color == 2)) ? {cell: i, type: LANCE} : null), true),
  1074.                 NOP
  1075.             ]);
  1076.             if(ret.cell == 4) {
  1077.                 return wilderness.find_victim(me, buddies, colours);
  1078.             }
  1079.             return ret;
  1080.         }
  1081.         return NOP;
  1082.     }
  1083. };
  1084.  
  1085. var TARGET_ARRANGEMENT_HIGHWAY = [
  1086.     [2,3,7,6,8,2,3,7,6],
  1087.     [2,3,7,7,6,4,4,2,3],
  1088.     [2,4,6,7,3,2,4,6,7],
  1089.     [3,2,4,4,6,7,7,3,2],
  1090.     [3,4,7,7,2,6,6,3,4],
  1091.     [3,4,7,2,6,3,4,7,2],
  1092.     [3,6,2,2,7,4,4,3,6],
  1093.     [4,7,2,2,5,6,3,4,7],
  1094.     [4,6,7,2,6,3,3,4,7],
  1095.     [4,6,7,7,3,2,2,4,6],
  1096.     [6,4,2,3,7,6,4,2,3],
  1097.     [7,3,2,2,4,6,6,7,3],
  1098.     [7,4,3,6,2,7,4,3,5]
  1099. ];
  1100. var HIGHWAY_BAND = colour_band([2,7,4,3,6]);
  1101. var HIGHWAY_BAND2 = colour_band([2,3,7,6,4]);
  1102.  
  1103. var highway = {
  1104.     name:function() { return "highway"; },                                       // For debugging
  1105.     near_nest: function(colours) { return false; },                   // For dodging enemy workers without getting lost
  1106.     near_nest_move: function(colours, buddies) { return null; }, // How to move when near_nest is true
  1107.     likely_nest: function(colours) { // Main nest detection
  1108.         var bestScore = 0;
  1109.         // check every rotation for each 3x3 rail possibility
  1110.         TARGET_ARRANGEMENT_HIGHWAY.forEach(function (arrangement) {
  1111.             ROTATIONS.forEach(function (rot){
  1112.                 var score = 0;
  1113.                 for(var i = 0; i < 9; i++) {
  1114.                     score += arrangement[i] == view[rot[i]].color?1:0;
  1115.                 }
  1116.                 if(score > bestScore) {
  1117.                     bestScore = score;
  1118.                 }
  1119.             });
  1120.         });
  1121.         if(bestScore >= 7) {
  1122.             return true;
  1123.         }
  1124.         if(this.isCenter(colours)) return true;
  1125.  
  1126.         return false;
  1127.     },           // Main nest detection
  1128.     isCenter: function(colours) {
  1129.         var bestScore = 0;
  1130.         ROTATIONS.forEach(function (rot){
  1131.             var score = 0;
  1132.             for(var i = 0; i < 9; i++) {
  1133.                 if(i >= 3 && i <= 5 && [2,7,4,3,6].indexOf(view[rot[i]].color) >= 0 && (i == 4 || view[rot[i]].color != view[rot[8-i]].color)) {
  1134.                     if(i != 4) {
  1135.                         score++;
  1136.                     }
  1137.                     else {
  1138.                         if(view[rot[3]].color != view[rot[5]].color && view[rot[1]].color == view[rot[7]].color && (view[rot[4]].color != view[rot[1]].color && view[rot[4]].color != view[rot[3]].color && view[rot[4]].color != view[rot[5]].color && view[rot[4]].color != view[rot[7]].color)) {
  1139.                             score++;
  1140.                         }
  1141.                     }
  1142.                 }
  1143.                 else if(i >= 6) {
  1144.                     if(view[rot[i]].color == view[rot[i-6]].color && [2,7,4,3,6].indexOf(view[rot[i]].color) >= 0 && (i == 7 || view[rot[i]].color != view[rot[8-i]].color) && view[rot[i]].color != view[4].color) {
  1145.                         score += 2;
  1146.                     }
  1147.                 }
  1148.             }
  1149.             if(score > bestScore) {
  1150.                 bestScore = score;
  1151.             }
  1152.         });
  1153.         if(bestScore >= 7) {
  1154.             return true;
  1155.         }
  1156.         return false;
  1157.     },
  1158.     worth_leeching:function(myFood, buddies){ return myFood > 80 && myFood < 500; }, // Is this nest worth leeching?
  1159.     likely_victim: function(victim_pos) {
  1160.         return true;
  1161.     },   // Identifying the target queen
  1162.     follow_victim: function(me, buddies, colours, victim_pos) {
  1163.         if(me.type == QUEEN && buddies[THIEF] < 3) {
  1164.             return best_of([
  1165.                 try_all_cells((i) => (near(i, victim_pos) ? {cell: i, type: THIEF} : null), true),
  1166.                 try_all_cells((i) => ({cell: i, type: THIEF}), true)
  1167.             ]);
  1168.         }
  1169.         if(me.type == THIEF && buddies[QUEEN])
  1170.             return NOP;
  1171.         return go_anywhere;
  1172.     },   // How to handle what happens when the enemy queen is found
  1173.     find_victim: function(me, buddies, colours) {
  1174.         if(me.type == THIEF && !buddies[QUEEN]) {
  1175.             for(var i=0;i<9;i++) {
  1176.                 if(foe_at(i)) return NOP;
  1177.             }
  1178.             var target = HIGHWAY_BAND.prev(view[4].color);
  1179.             var followRail = best_of([
  1180.                 try_all_cells((i) => (i % 2 == 1 && view[i].color == target) ? {cell:i} : null),
  1181.                 NOP
  1182.             ]);
  1183.         }
  1184.         else {
  1185.             var target = HIGHWAY_BAND.next(view[4].color);
  1186.             var followRail = best_of([
  1187.                 try_all_cells((i) => (i % 2 == 1 && view[i].color == target) ? {cell:i} : null),
  1188.                 NOP
  1189.             ]);
  1190.         }
  1191.         return followRail;
  1192.     }                 // How to follow the nest
  1193. }
  1194.  
  1195. var wilderness = {
  1196.     name:function() { return "wilderness"; },
  1197.     near_nest: function(colours) { return false; },
  1198.     near_nest_move: function(colours, buddies) { return null; },
  1199.     likely_nest: function(colours) {
  1200.         return true;
  1201.     },
  1202.     worth_leeching: function(myFood, buddies) {
  1203.         return true;
  1204.     },
  1205.     likely_victim: function(victim_pos) {
  1206.         return true;
  1207.     },
  1208.  
  1209.     follow_victim: function(me, buddies, colours, victim_pos) {
  1210.         // We stumbled across a random queen; make the most of it
  1211.         // TODO
  1212.  
  1213.         // avoids blocking off the rail miner queen from her workers
  1214.         // (we'd like to leech her again)
  1215.         if(me.type === QUEEN && !buddies[THIEF] && me.food > 0) {
  1216.             // Make a buddy to help us steal
  1217.             return best_of([
  1218.                 try_all_cells((i) => (near(i, victim_pos) ? {cell: i, type: THIEF} : null), true),
  1219.                 try_all_cells((i) => ({cell: i, type: THIEF}), true)
  1220.             ]);
  1221.         }
  1222.         else if(me.type === QUEEN){
  1223.             var enemyCount = 0;
  1224.             var allyPos = -1;
  1225.             for(var a=0; a<9; a++) {
  1226.                 if(a != 4 && view[a].ant != null) {
  1227.                     if(view[a].ant.friend) {
  1228.                         if(near(a,victim_pos)){
  1229.                             allyPos = a;
  1230.                         }
  1231.                     }
  1232.                     else if(view[a].ant.type != 5) {
  1233.                         enemyCount++;
  1234.                     }
  1235.                 }
  1236.             }
  1237.             if(enemyCount >= buddies[THIEF] && allyPos >= 0) {
  1238.                 //if next to the queen and we're outnumbered, move back to the center of the rail.
  1239.                 var target = TARGET_COLOURS_RAIL1.prev(view[allyPos].color);
  1240.                 var best = best_of([
  1241.                     try_all_cells((i) => (near(i, victim_pos) && i % 2 == 0 ? {cell: i, type: THIEF} : null), true),
  1242.                     try_all_cells((i) => (near(i, victim_pos) ? {cell: i, type: THIEF} : null), true)
  1243.                 ]);
  1244.                 if(best != null) return best;
  1245.  
  1246.                 best_of([
  1247.                     try_all_cells((i) => ((view[i].color == target && i != 4 && areAdjacent(i,a)) ? {cell: i} : null))
  1248.                 ]);
  1249.                 if(best != null) return best;
  1250.  
  1251.                 return best_of([
  1252.                     {cell:deRotate(allyPos,1)},
  1253.                     {cell:deRotate(allyPos,-1)}
  1254.                 ]);
  1255.             }
  1256.         }
  1257.  
  1258.         return NOP;
  1259.     },
  1260.     find_victim: function(me, buddies, colours) {
  1261.         if(me.type === QUEEN) {
  1262.             var in_void = true;
  1263.             for(var i = 0; i < 9; ++ i) {
  1264.                 if(view[i].color !== WHITE && !SAFE_COLOURS.contains(view[i].color)) {
  1265.                     in_void = false;
  1266.                     break;
  1267.                 }
  1268.             }
  1269.             if(!in_void) {
  1270.                 // because of avoiding returning Miner on a Rail workers
  1271.                 // we dodge sideways and this takes us back onto track
  1272.                 var nearMove = checkAllNearEnvirons(colours, buddies)
  1273.                 if(nearMove) return nearMove;
  1274.                 if(rail_miners.likely_nest(colours)) {
  1275.                     var r = try_all_angles.bind(null, [
  1276.                         (rot) => (foe_at(rot[1]) === QUEEN ? {cell: rot[8], type: BOUNCER} : null)
  1277.                     ]);
  1278.                     var m = r();
  1279.                     if(sanity_check(m)) {
  1280.                         return m;
  1281.                     }
  1282.                     var m = try_all_cells((i) => ({cell: i, type: BOUNCER}), true);
  1283.                     if(m) {
  1284.                         return m;
  1285.                     }
  1286.                 }
  1287.             }
  1288.             return best_of([
  1289.                 // Make a buddy once we have a reasonable stash of food so we can
  1290.                 // search the board faster
  1291.                 // (but avoid making buddies when there's a potential nest nearby;
  1292.                 // better to wait until we find their queen)
  1293.                 (!buddies[THIEF] && me.food >= INITIAL_GATHER && in_void) &&
  1294.                     try_all_cells((i) => ({cell: i, type: THIEF}), true),
  1295.  
  1296.                 // Follow buddy in search of victims
  1297.                 buddies[THIEF] && try_all_angles.bind(null, [
  1298.                     (rot) => (friend_at(rot[1]) === THIEF ? {cell: rot[2]} : null),
  1299.                     (rot) => (friend_at(rot[0]) === THIEF ? {cell: rot[1]} : null)
  1300.                 ]),
  1301.                 buddies[THIEF] && NOP, // Don't lose our buddy!
  1302.  
  1303.                 // Random walk until we can make a buddy or find the victim
  1304.                 grab_nearby_food,
  1305.                 foe_near() ? go_anywhere : fast_diagonal.bind(null, SAFE_COLOURS),
  1306.                 go_anywhere
  1307.             ]);
  1308.         } else if(me.type === THIEF) {
  1309.             return best_of([
  1310.                 // Lost the queen! Random walk because we have nothing better to do.
  1311.                 // (don't leave lines; they could disrupt the pattern)
  1312.                 !buddies[QUEEN] && go_anywhere,
  1313.                 buddies[BOUNCER] && go_anywhere,
  1314.  
  1315.                 // Follow queen in search of victims
  1316.                 try_all_angles.bind(null, [
  1317.                     (rot) => (friend_at(rot[1]) === QUEEN ? {cell: rot[0]} : null),
  1318.                     (rot) => (friend_at(rot[0]) === QUEEN ? {cell: rot[3]} : null)
  1319.                 ]),
  1320.                 NOP // Don't lose our buddy!
  1321.             ]);
  1322.         }
  1323.     }
  1324. };
  1325.  
  1326. var victims = [highway, rail_miners, rail_repair, windmill];
  1327.  
  1328. function guess_environment(colours, buddies) {
  1329.     var food = view[4].ant.food;
  1330.     if(view[4].ant.type !== QUEEN) {
  1331.         for(var i = 0; i < 9; i++) {
  1332.             if(i != 4 && view[i].ant && view[i].ant.friend && view[i].ant.type === QUEEN) {
  1333.                 food = view[i].ant.food;
  1334.             }
  1335.         }
  1336.     }
  1337.     for(var i = 0; i < victims.length; ++ i) {
  1338.         if(victims[i].likely_nest(colours) && victims[i].worth_leeching(food, buddies)) {
  1339.             return victims[i];
  1340.         }
  1341.     }
  1342.  
  1343.     return wilderness;
  1344. }
  1345.  
  1346. function is_safe(i) {
  1347.     var nearThief = false;
  1348.     var nearOfficer = false;
  1349.     for(var j = 0; j < 9; ++ j) {
  1350.         if(friend_at(j) === THIEF) {
  1351.             nearThief = true;
  1352.         }
  1353.         if(foe_at(j) && foe_at(j) !== QUEEN) {
  1354.             nearOfficer = true;
  1355.         }
  1356.     }
  1357.     return nearThief && !nearOfficer;
  1358. }
  1359.  
  1360. function move(me, buddies) {
  1361.     var colours = [0, 0, 0, 0, 0, 0, 0, 0, 0];
  1362.     for(var i = 0; i < 9; ++ i) {
  1363.         ++ colours[view[i].color];
  1364.     }
  1365.     var env = guess_environment(colours,buddies);
  1366.     var victim_pos = -1;
  1367.     for(var i = 0; i < 9; ++ i) {
  1368.         if(foe_at(i) === QUEEN && env.likely_victim(i) && view[i].ant.food > 0) {
  1369.             victim_pos = i;
  1370.             if(view[i].ant.food > 0) {
  1371.                 break;
  1372.             }
  1373.         }
  1374.     }
  1375.  
  1376.     var in_void = true;
  1377.     for(var i = 0; i < 9; ++ i) {
  1378.         if(view[i].color !== WHITE || (i != 4 && me.type === BOUNCER && friend_at(i) === BOUNCER)) {
  1379.             in_void = false;
  1380.             break;
  1381.         }
  1382.     }
  1383.     if(me.type === BOUNCER) {
  1384.         if(env === wilderness && in_void) {
  1385.             // Our work is done; leave queen and wander at random
  1386.             if(buddies[QUEEN]) {
  1387.                 return best_of([
  1388.                     try_all_cells((i) => (ant_type_near(i, true) ? null : {cell: i}), true),
  1389.                     go_anywhere
  1390.                 ]);
  1391.             }
  1392.             return NOP;
  1393.         }
  1394.         else if(env === rail_miners) {
  1395.             // Our work is done; leave queen and wander at random
  1396.             if(buddies[QUEEN]) {
  1397.                 var allAngles = try_all_angles.bind(null, [
  1398.                     (rot) => (friend_at(rot[1]) === QUEEN ? {cell: rot[0]} : null),
  1399.                     (rot) => (friend_at(rot[0]) === QUEEN ? {cell: rot[3]} : null),
  1400.                     NOP
  1401.                 ]);
  1402.                 return best_of([
  1403.                     //if next to an enemy queen, move out of the way
  1404.                     try_all_cells((i) => (foe_at(i) == QUEEN ? {cell:9-i} : null), true),
  1405.                     try_all_cells((i) => (foe_at(i) == QUEEN ? {cell:7-i} : null), true),
  1406.                     allAngles
  1407.                 ]);
  1408.             }
  1409.             return NOP;
  1410.         } else if(buddies[QUEEN]) {
  1411.             // Escort queen out of nest
  1412.             var allAngles = try_all_angles.bind(null, [
  1413.                 (rot) => (friend_at(rot[1]) === QUEEN ? {cell: rot[0]} : null),
  1414.                 (rot) => (friend_at(rot[0]) === QUEEN ? {cell: rot[3]} : null),
  1415.                 go_anywhere
  1416.             ]);
  1417.  
  1418.             return best_of([
  1419.                 //if next to an enemy queen, move out of the way
  1420.                 try_all_cells((i) => (foe_at(i) == QUEEN ? {cell:9-i} : null), true),
  1421.                 try_all_cells((i) => (foe_at(i) == QUEEN ? {cell:7-i} : null), true),
  1422.                 allAngles
  1423.             ]);
  1424.         }
  1425.         else {
  1426.             return go_anywhere;
  1427.         }
  1428.     } else if(buddies[BOUNCER]) {
  1429.         if(me.type === QUEEN) {
  1430.             // Be escorted out of nest
  1431.             return try_all_angles.bind(null, [
  1432.                 (rot) => (friend_at(rot[1]) === BOUNCER ? {cell: rot[2]} : null),
  1433.                 (rot) => (friend_at(rot[0]) === BOUNCER ? {cell: rot[1]} : null),
  1434.                 go_anywhere,
  1435.                 NOP
  1436.             ]);
  1437.         } else {
  1438.             // Get out of the way
  1439.             return try_all_angles.bind(null, [
  1440.                 (rot) => (friend_at(rot[1]) === QUEEN ? {cell: rot[7]} : null),
  1441.                 (rot) => (friend_at(rot[0]) === QUEEN ? {cell: rot[8]} : null),
  1442.                 (rot) => (friend_at(rot[1]) === BOUNCER ? {cell: rot[7]} : null),
  1443.                 (rot) => (friend_at(rot[0]) === BOUNCER ? {cell: rot[8]} : null),
  1444.                 go_anywhere
  1445.             ]);
  1446.         }
  1447.     }
  1448.     if(victim_pos !== -1) {
  1449.         // abandon the queen if she's dry.
  1450.         // abandon rail miner's queen so she has at least 10 food (otherwise she produces workers 3:4 food she aquires)
  1451.         // value is higher than 10 because there's two to three rounds of theft (at 4 ants each) before the queen gets out of range
  1452.         // this can still leave the rail miner's queen lower than 10, but unlikely
  1453.         // other queens are abandoned if they have less than 5 food, due to the "max 4 ants stealing" and at 0 food, she's not a target.
  1454.         if(view[victim_pos].ant.food < 5 || ((env == rail_miners || env == rail_repair) && view[victim_pos].ant.food < 28)) {
  1455.             if(me.type == THIEF) {
  1456.                 if(rail_miners.near_nest(colours)) {
  1457.                     // we'd rather reuse the workers
  1458.                     return NOP;
  1459.                 }
  1460.             }
  1461.             // Victim is out of food; bounce out of nest
  1462.             if(env == rail_miners || env == rail_repair) {
  1463.                 // due to wanting to avoid scattering workers and spawning multiple bouncers
  1464.                 // cell 6 (Rail queen at cell 1) is the only cell that will take us directly away from her
  1465.                 var r = try_all_angles.bind(null, [
  1466.                     (rot) => (foe_at(rot[1]) === QUEEN ? {cell: rot[8], type: BOUNCER} : null)
  1467.                 ]);
  1468.                 var m = r();
  1469.                 if(sanity_check(m)) {
  1470.                     return m;
  1471.                 }
  1472.                 // However if we can't spawn there, use the regular rules
  1473.             }
  1474.             var m = try_all_cells((i) => ({cell: i, type: BOUNCER}), true);
  1475.             if(m) {
  1476.                 return m;
  1477.             }
  1478.         }
  1479.         if(me.type === QUEEN && buddies[THIEF] && !is_safe(CENTRE)) {
  1480.             // Try to avoid getting food stolen back from us
  1481.             var m = try_all_cells((i) => (is_safe(i) ? {cell: i} : null), true);
  1482.             if(m) {
  1483.                 return m;
  1484.             }
  1485.         }
  1486.         return env.follow_victim(me, buddies, colours, victim_pos);
  1487.     } else {
  1488.         if(me.type == THIEF && buddies[QUEEN] == 0) {
  1489.             if(rail_miners.near_nest(colours)) {
  1490.                 // we'd rather reuse the workers
  1491.                 // with SlM they won't be next to the queen, but they may have food
  1492.                 // and if our queen comes by, she'll pick it up.
  1493.                 return NOP;
  1494.             }
  1495.         }
  1496.         return env.find_victim(me, buddies, colours);
  1497.     }
  1498. }
  1499.  
  1500. // LANCE is only used by windmill targetting, easier to break this out as its own method
  1501. function moveLance(me, buddies) {
  1502.     var queenPos = -1;
  1503.     var tipPos = -1;
  1504.     if(buddies[BOUNCER]) {
  1505.         for(var i=0;i<9;i++) {
  1506.             if(view[i].ant && view[i].ant.friend && view[i].ant.type == BOUNCER) {
  1507.                 return {cell:8-i};
  1508.             }
  1509.         }
  1510.     }
  1511.     if(!buddies[QUEEN]) {
  1512.         for(var i=0;i<9;i++) {
  1513.             if(i % 2 == 0 && view[i].ant && !view[i].ant.friend && view[i].ant.type == QUEEN) {
  1514.                 if(view[deRotate(i,3)].ant != null && view[deRotate(i,3)].ant.friend && view[deRotate(i,3)].ant.type == LANCE_TIP) return NOP;
  1515.                 return {cell:deRotate(i,1)};
  1516.             }
  1517.         }
  1518.         return NOP;
  1519.     }
  1520.     for(var i=0;i<9;i++) {
  1521.         if(view[i].ant && view[i].ant.friend && view[i].ant.type == QUEEN) {
  1522.             queenPos = i;
  1523.         }
  1524.         if(view[i].ant && view[i].ant.friend && view[i].ant.type == LANCE_TIP) {
  1525.             tipPos = i;
  1526.         }
  1527.     }
  1528.     if(buddies[LANCE_TIP]) {
  1529.         if(deRotate(queenPos,-1) == tipPos && view[tipPos].color == 8) return {cell:8-tipPos};
  1530.         if(deRotate(queenPos,-1) == tipPos) return try_all_cells((i) => (areAdjacent(i,tipPos) && view[i].color == 5 ? {cell:i} : null));
  1531.         if(view[8-tipPos].ant != null && !view[8-tipPos].ant.friend && view[8-tipPos].ant.type == 5) return {cell:8-tipPos,color:6};
  1532.         return try_all_cells((i) => (!areAdjacent(i,queenPos) && !areAdjacent(i,tipPos) ? {cell:i} : null));
  1533.     }
  1534.     if(view[4].color != 4 && view[4].color != 6) {
  1535.         if(view[8-queenPos].ant != null && !view[8-queenPos].ant.friend && view[8-queenPos].ant.type == 5) return NOP;
  1536.         return best_of([
  1537.             try_all_cells((i) => (i % 2 == 1 && (view[i].color == 4 || view[i].color == 6) && view[deRotate(i,1)].color != 2 && view[deRotate(i,-1)].color != 2 && areAdjacent(i,queenPos) ? {cell: i} : null), true),
  1538.             NOP
  1539.         ]);
  1540.     }
  1541.     else {
  1542.         var queenOn = view[8-queenPos].color;
  1543.         var target = WIND_BAND.next(queenOn);
  1544.         var prior = WIND_BAND.next(target);
  1545.         var followRail = best_of([
  1546.             try_all_cells((i) => (view[deRotate(i,-3)].color == prior && view[deRotate(i,-1)].color == target && areAdjacent(i,queenPos) && (view[i].color == 4 || view[i].color == 6) ? {cell: i} : null), true),
  1547.             queenPos % 2 == 1 ? (view[queenPos].color == 4 || view[4].color == 4 ? NOP : {cell:deRotate(queenPos,-2)}) : (view[queenPos].color == 4 || view[queenPos].color == 6 ? {cell:deRotate(queenPos,-1)} : NOP)
  1548.         ]);
  1549.  
  1550.         if(view[deRotate(queenPos,-1)].ant != null) {
  1551.             if(!view[deRotate(queenPos,-1)].ant.friend && view[deRotate(queenPos,-2)].ant != null && !view[deRotate(queenPos,-2)].ant.friend) {
  1552.                 return {cell:deRotate(queenPos,1)};
  1553.             }
  1554.             return NOP;
  1555.         }
  1556.         if(me.food > 0 && queenPos % 2 == 0) {
  1557.             return {cell:deRotate(queenPos,-1)};
  1558.         }
  1559.         if(view[deRotate(queenPos,-3)].ant != null && !view[deRotate(queenPos,-3)].ant.friend && (view[queenPos].color == 1 || view[deRotate(queenPos,1)].color == 1 || view[deRotate(queenPos,-1)].color == 1)) {
  1560.             return {cell:queenPos,color:3};
  1561.         }
  1562.         if(view[deRotate(queenPos,-4)].ant != null && !view[deRotate(queenPos,-4)].ant.friend && (view[queenPos].color == 1 || view[deRotate(queenPos,1)].color == 1 || view[deRotate(queenPos,-1)].color == 1)) {
  1563.             return {cell:queenPos,color:3};
  1564.         }
  1565.         return followRail;
  1566.     }
  1567.     return NOP;
  1568. }
  1569.  
  1570. // LANCE_TIP never needs to move
  1571. // Unfortunately, reusing an existing worker type for this purpose is not easily possible.
  1572. // Used against Sliding Miners as a stationary blocker to prevent the queen slipping past.
  1573. function moveTip(me, buddies) {
  1574.     var queenPos = -1;
  1575.     var in_void = true;
  1576.     for(var i=0;i<9;i++) {
  1577.         if(view[i].ant && view[i].ant.friend && view[i].ant.type == QUEEN) {
  1578.             queenPos = i;
  1579.         }
  1580.         if(view[i].color != WHITE) {
  1581.             in_void = false;
  1582.         }
  1583.     }
  1584.     if(buddies[QUEEN] && !buddies[LANCE]) {
  1585.         if(view[8-queenPos].ant != null && !view[8-queenPos].ant.friend && view[8-queenPos].ant.type == 4) {
  1586.             return {cell:deRotate(queenPos,2)};
  1587.         }
  1588.         if(in_void) return {cell:deRotate(queenPos,4)};
  1589.     }
  1590.     if(buddies[QUEEN] && buddies[LANCE]) {
  1591.         return {cell:deRotate(queenPos,2)};
  1592.     }
  1593.     if(buddies[QUEEN] && buddies[BOUNCER]) {
  1594.         // rail miners bounce out
  1595.         return go_anywhere;
  1596.     }
  1597.     return NOP;
  1598. }
  1599.  
  1600. function deRotate(m, amt) {
  1601.     var rotationsCW = [1,2,5,8,7,6,3,0];
  1602.     var rotationsCCW = [3,6,7,8,5,2,1,0];
  1603.     if(m == 4 || m < 0 || m > 8 || amt == 0) return m;
  1604.     if(amt > 0)
  1605.         return rotationsCW[(rotationsCW.indexOf(m)+amt)%8];
  1606.     amt = -amt;
  1607.     return rotationsCCW[(rotationsCCW.indexOf(m)+amt)%8];
  1608. }
  1609.  
  1610. return play_safe(move_agent([
  1611.     THIEF, move,
  1612.     QUEEN, move,
  1613.     BOUNCER, move,
  1614.     LANCE, moveLance,
  1615.     LANCE_TIP, moveTip
  1616. ]));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement