KoTH: Political Simulator

Landgrab

Brief description of strategy:

  1. On the first turn, do a large campaign in the center using up all the initial money.
  2. Otherwise, if there is a region with more than 2 neutral voters, campaign in the region with the most neutral voters.
  3. Otherwise, if there is a region in which me and my opponent have the same number of voters, campaign in that region.
  4. Otherwise, campaign in the region with the most neutral voters.

This relies on the fact that it's much easier to claim neutral voters than voters who are already going to vote for your opponent. It doesn't know about gerrymandering, so might not do very well if someone messes with voting regions, but it's a start.

(regions, money, result, storage) => {
   if(money == 100) { return campaign([2, 2], [12, 12]); }
   var best = regions[0];
   var tied;
   for (var i = 0; i < regions.length; i++) {
      if(regions[i].number_neutral > best.number_neutral) {
         best = regions[i];
      }
      if(regions[i].number_neutral == 0 && regions[i].number_you == regions[i].number_opponent) {
        tied = regions[i];
      }
   }
   var b;
   if (tied && best.number_neutral > 2) {
     b = tied.blocks[money % tied.blocks.length];
   } else {
     b = best.blocks[money % best.blocks.length];
   } 
   if (money >= 16) {
        return campaign(b, [b[0] + 4, b[1] + 4])
   } else if (money % 2 == 0) {
        return campaign(b, [b[0] + 3, b[1] + 3])
   } else {
        return campaign([b[0] + 1, b[1] + 1], [b[0] + 4, b[1] + 4])
   }
}

Leftist Policy

v1.1

Grabs the left side of the map, then gerrymanders the right side. Having accomplished that, it will slowly campaign the right side to avoid deadlock.

(regions, money, result, storage) => {
        storage.phase = storage.phase || 0;
        storage.merge = storage.merge || 0;

        if (storage.phase == 0){
            storage.phase+=2;
            return campaign([1,2],[5,14]);
        }

        if (storage.phase <= 6) {
            var result = campaign([storage.phase,1],[storage.phase+1,15]);
            if(money >= 14) storage.phase++;
            return result;
            
        }


        if(storage.phase == 7){

            if(storage.merge < 4){
                var result; 
                if(money >= 25){ 
                    result = merge([9,(storage.merge*4)+1],[15,(storage.merge)*4+1])
                    storage.merge++;
                    storage.phase = 1
                }
                return result;
            }
            else if(regions.filter(r => r.blocks[0][0] <= 5)
                           .filter(r => r.number_you > r.number_opponent + r.number_neutral).length >= regions.length / 2){ 
                area = (storage.merge % 4)*4;
                if(money >= 18){
                    storage.merge++;
                    storage.phase = 1;
                    return campaign([9,area],[15,area+3]);
                }
            }
            else{
                
                storage.phase = 1;
                var result = campaign([storage.phase,1],[storage.phase+1,15]);
                if(money >= 14) storage.phase++;
                return result;
            }
        }
    }

Currently only beats the example bot. The strategy is just too slow to win much.


Randgrab

Started as an evolution of Landgrab to increase randomness, then slowly added more and more features until it currently beats all other contestants (Landgrab, Leftist Policy 1.1, and Greedy campaign 9).

Features include:

  • Grabbing less land at the start to save money
  • Grabbing adjacent areas at once if we have enough money
  • De-priortize areas where we already have enough lead to win and the opponent hasn't campaigned yet
  • Prioritize areas where the vote is closest (the "swing states," if you will)
  • More randomness, including choosing at random any one of the four corners to claim when doing a 3x3
  • A pretty chaotic (but still deterministic!) r variable which controls all randomness

Weaknesses include:

  • Not prioritizing undecided states highly enough
  • Not taking advantage of any non-campaign functions
  • Can be thrown off by region changes, although this has been partially corrected for
  • Can be thrown off by claims which don't align to borders well
(regions, money, result, storage) => {
    if(money == 100) {return campaign([4, 4], [12, 12]);}
    var r = money + money * regions.length;
    regions.forEach(reg => r += reg.blocks[0][0] * reg.number_neutral + reg.blocks[0][1] * reg.number_you + money * reg.number_opponent + reg.blocks.length * reg.absolute_average);
    r = Math.floor(r);
    var tied = [];
    var best = [regions[r % regions.length]];
    var closest = [regions[(2*r) % regions.length]];
    for (var i = 0; i < regions.length; i++) {
        if(regions[i].number_neutral > best[0].number_neutral && !(regions[i].number_you > 8 && regions[i].number_opponent == 0)) {
            best = [regions[i]];
        } else if(regions[i].number_neutral == best[0].number_neutral && !(regions[i].number_you > 5 && regions[i].number_opponent == 0)) {
            best.push(regions[i]);
        }
        if(regions[i].number_neutral == 0 && regions[i].number_you == regions[i].number_opponent) {
            tied.push(regions[i]);
        }
        if(regions[i].number_opponent > regions[i].number_you && regions[i].absolute_average < closest[0].absolute_average) {
            closest = [regions[i]];
        } else if(regions[i].number_opponent > regions[i].number_you && regions[i].absolute_average == closest[0].absolute_average) {
            closest.push(regions[i]);
        }
    }
    var b;
    var choice;
    if (tied.length > 0 && best[0].number_neutral > 4) {
        choice = tied;
    } else {
        choice = (best[0].number_neutral > 2 ? best : closest);
    }
    console.log(choice);
    bt = choice[r % choice.length];
    b = bt.blocks[r % bt.blocks.length];
    var x = Math.floor(r/2) % 2;
    var y = Math.floor(r/4) % 2;
    if (money >= 18 && choice) {
        for(var i = 0; i < choice.length; i++) {
            for(var j = 0; j < choice[i].blocks.length; j++) {
                var c = choice[i].blocks[j];
                if(c[0] == b[0]-4 && c[1] == b[1]) {
                    return campaign([c[0]+1, c[1]], [b[0]+3, b[1]+3]);
                } else if(c[0] == b[0]+4 && c[1] == b[1]) {
                    return campaign([b[0]+1, b[1]], [c[0]+3, c[1]+3]);
                } else if(c[0] == b[0] && c[1] == b[1]-4) {
                    return campaign([c[0], c[1]+1], [b[0]+3, b[1]+3]);
                } else if(c[0] == b[0] && c[1] == b[1]+4) {
                    return campaign([b[0], b[1]+1], [c[0]+3, c[1]+3]);
                }
            }
        }
    }
    if (money >= 16) {
        return campaign(b, [b[0] + 4, b[1] + 4]);
    } else {
        return campaign([b[0] + x, b[1] + y], [b[0] + 3 + x, b[1] + 3 + y]);
    }
}