KoTH: политический симулятор
Пришло время выборов, и ваша задача - победить своего конкурента в лобовом соперничестве! Вы оба пытаетесь расположить к себе город с населением 256 человек в сетке 16x16. Прямо сейчас город еще не разделен на регионы для голосования, но именно здесь вам пригодятся ваши навыки джерримендеринга! Вы также можете проводить кампании в произвольных районах города, чтобы получить поддержку.
Общее
Все боты (функции JS) будут запускаться против всех остальных ботов один раз за игру. В каждой игре город будет начинаться с нейтральной позиции всех избирателей. Игра будет отслеживать номер каждого избирателя, определяя, за кого он поддерживает. С точки зрения конкретного бота положительное число означает, что избиратель проголосует за него, а отрицательное число - голос за оппонента. Ноль нейтрален.
Город разделен на 16 кварталов, все из которых представляют собой квадраты 4х4. Регионы для голосования состоят из одного или нескольких блоков. Изначально их 16, каждый блок имеет свой регион.
Оба бота начинаются со 100 долларов и могут двигаться один раз за ход. Эти ходы выполняются эффективно одновременно, поэтому нет преимущества на первом ходу. Ходы делятся на четыре категории:
- Проведение кампании: это заставит каждого человека в определенной области усилить свою поддержку кандидата, который проводит кампанию. Величина его изменения зависит от соседей.
- Голосование: это позволит каждому получить поддержку кандидата в определенной области.
- Подкуп: это заставит конкретное лицо усилить поддержку кандидата.
- Объединение / отключение регионов: это изменит форму регионов для голосования.
В конце каждого хода, после того как оба кандидата переместятся, оба получат по 10 долларов.
подробности
Допускаются следующие ходы. Если дан неверный ход (недостаточно денег или неверные координаты), ход бота будет пропущен.
Все координаты должны быть внутри 0 <= n < 16, а для второй пары в прямоугольных ограничивающих областях 0 <= n <= 16(поскольку они являются исключительными ).
campaign([x, y], [x, y]): Кампания в границах, определенных двумя парами координат.- Стоимость 1 доллар США на человека
- Каждый человек в этом районе будет менять свою опору в соответствии со следующими правилами:
- Для всех соседей, расположенных перпендикулярно (включая диагонали) смежным, добавьте 0,1 поддержки для любого кандидата, которого поддерживает большинство (взвешенное значение), или 0,2, если их общая поддержка не менее 2 для этого кандидата.
- Добавьте 0,25 для кандидата, проводящего кампанию
poll([x, y], [x, y]): Опрос в пределах, определяемых двумя парами координат- Стоимость 0,25 доллара США на одного опрошенного человека, с округлением в большую сторону.
- На следующем ходу о каждом человеке в зоне дается следующая информация (после хода противника):
- Их поддержка кандидата на опрос, где положительные числа указывают на голосование за них, а отрицательные числа - на голосование оппонента.
bribe([x, y]): Подкупить человека в месте, определенном парой координат- Стоит минимум 5 долларов
- За каждый подкуп человека в блоке для голосования ( не в регионе голосования) добавляйте 1 доллар
- Добавить до 3,5 поддержки кандидата на подкуп
- За каждый раз, когда человеку в избирательном блоке давали взятку, добавленная поддержка уменьшается на 0,15.
- В конце концов, это может привести к подкупу кого-либо в избирательном блоке, чтобы уменьшить их поддержку кандидата.
- Стоит минимум 5 долларов
merge([x, y], [x, y]): Объединить регионы голосования, определенные двумя парами координат- Стоит 25 долларов за каждый блок во вновь сформированном регионе (один блок бесплатно)
- Требуются регионы, в которых находятся два человека, которые должны касаться друг друга.
- Обратите внимание, что координаты соответствуют людям, а не блокам. Чтобы ссылаться на блок, просто умножьте его координаты на 4
unmerge([x, y]): Отменить объединение области голосования, определенной парой координат- Стоит 25 долларов за каждый блок в регионе.
- Каждый блок в регионе становится отдельным регионом
Если оба бота попытаются объединиться или разъединиться в один ход (даже если они не будут мешать), оба хода будут пропущены, и ни один из них не будет платить. Ходы будут обрабатываться в следующем порядке (порядок остальных не имеет значения):
- Взятки
- Поддержка соседей в кампании
- Поддержка со стороны кандидатов в походах
Победа
В конце каждого хода, после того как оба кандидата переместятся, голоса всех регионов будут добавлены. Каждый человек будет либо голосовать за одного кандидата, либо быть нейтральным, независимо от того, сколько баллов (например, оценка +0,05 или +30 здесь будет одинаковой). При соблюдении следующих условий будут проведены выборы и будет выбран победитель:
- Во всех регионах менее половины нейтральных избирателей
- Количество регионов, голосующих за каждого кандидата, не привязано
Ввод / вывод
Все боты должны быть представлены в виде функций Javascript. Следующая информация будет предоставлена в качестве аргументов функции:
- Массив регионов для голосования, представленный в виде объектов со следующими свойствами:
blocks: Массив блоков для голосования, представленный в виде координат[x, y]верхнего левого человека (например,[4, 0]или[12, 12])number_neutral: Количество нейтральныхnumber_you: Количество людей в регионе, голосующих за бота.number_opponent: Количество людей в регионе, голосующих за оппонента бота.absolute_average: Среднее абсолютное значение поддержки кандидата.- Более высокие числа указывают на то, что агитация или подкуп обычно менее эффективны.
- Ровно 0 означает, что каждый человек в регионе нейтрален.
- Количество денег у бота
- Объект, содержащий результаты последнего хода (пустой, если это не был опрос)
- Массив
peopleбудет содержать объекты, представляющие каждого опрошенного человека:position: Координаты человека в формате[x, y]region: Пронумерованный идентификатор региона, в котором находится человек (индекс региона в первом аргументе)support: Число, указывающее, является ли человек нейтральным (0), голосует за бота (положительно) или голосует за оппонента бота (отрицательно).
- Объект,
amountsсодержащий следующие свойства:number_neutral: Количество нейтральныхnumber_you: Количество людей в регионе, голосующих за бота.number_opponent: Количество людей в регионе, голосующих за оппонента бота.absolute_average: Среднее абсолютное значение поддержки кандидата.
- Массив
- Объект, который можно использовать для хранения между ходами (но не между раундами / играми)
Для перемещения необходимо вернуть результат одной из функций, указанных выше. Например:
{
"Example": (regions, money, result, storage) => {
storage.block = ((storage.block || 0) + 1) % 16;
return campaign(
[(storage.block / 4 | 0) * 4, (storage.block % 4) * 4],
[(storage.block / 4 | 0) * 4 + 4, (storage.block % 4) * 4 + 4]
);
}
}
Правила
- Боты должны запускаться за разумное время.
- Боты должны играть честно, не манипулировать контроллером или другими материалами способами, запрещенными здесь.
- Каждый бот будет запускаться против каждого другого бота один раз, причем победителем будет бот, который побеждает чаще всего.
- В случае ничьей побеждает более ранний бот.
- Неверные возвращаемые значения или боты, которые при ошибке не предполагают движения за ход
- Боты должны быть детерминированными
Идея вызова и оригинальное предложение песочницы от HyperNeutrino.
Контроллер: https://redwolfprograms.github.io/political-simulator
Чат: Здесь
Дата сдачи: четверг, 5 ноября 2020 г., полдень по всемирному координированному времени (8:00 AM EST)
Ответы
Abotcus
Оказывается, вам не нужно так много необычных функций, чтобы быть таким же или лучше, чем Randgrab! Abotcus захватывает область в начале, затем применяет простую формулу для взвешивания каждого блока и выбирает блок с наилучшим весом. Если кажется, что там есть какие-то ненужные вещи, это потому, что я ожидал добавить гораздо больше необычных функций ... но мне просто не нужно было ¯ \ _ (ツ) _ / ¯
(regions, money, result, storage) => {
if(money == 100) {return campaign([9,1],[15,15])}
var map = [[,,,],[,,,],[,,,],[,,,]];
var weights = [[,,,],[,,,],[,,,],[,,,]];
var blocks = [];
for(var r of regions) {
for(var b of r.blocks) {
map[b[1]/4][b[0]/4] = b;
weights[b[1]/4][b[0]/4] = weight(r.number_you, r.number_opponent, r.number_neutral, r.absolute_average)/r.blocks.length;
blocks.push([b, weights[b[1]/4][b[0]/4]]);
}
}
blocks.sort((a,b) => {
return b[1]-a[1];
});
var start_block = blocks[0][0];
console.log(blocks);
if(money >= 16) {
return campaign(start_block, [start_block[0]+4, start_block[1]+4]);
} else {
return campaign(start_block, [start_block[0]+3, start_block[1]+3]);
}
function weight(own, opp, neut, avg) {
var tot = own+opp+neut;
var cat = 0;
if(opp > tot/2) {
cat = 1;
} else if(own > tot/2) {
cat = 5;
}
return 1/(1+avg+cat);
}
}
```
Landgrab
Краткое описание стратегии:
- На первом ходу проведите большую кампанию в центре, используя все начальные деньги.
- В противном случае, если есть регион с более чем двумя нейтральными избирателями, проводите кампанию в регионе с наиболее нейтральными избирателями.
- В противном случае, если есть регион, в котором у меня и моего оппонента одинаковое количество избирателей, проводите кампанию в этом регионе.
- В противном случае агитация в регионе с наиболее нейтральными избирателями.
Это основано на том факте, что гораздо проще претендовать на нейтральных избирателей, чем на избирателей, которые уже собираются голосовать за вашего оппонента. Он не знает о джерримандеринге, поэтому может не сработать, если кто-то испортит регионы для голосования, но это только начало.
(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])
}
}
Левая политика
v1.1
Хватает левую часть карты, затем джерримандер правую. После этого он будет медленно проводить кампанию на правой стороне, чтобы избежать тупика.
(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;
}
}
}
На данный момент превосходит только примерный бот. Эта стратегия слишком медленная, чтобы выиграть много.
Randgrab
Начиналась как эволюция Landgrab для увеличения случайности, затем постепенно добавляла все больше и больше функций, пока в настоящее время не превосходит всех остальных участников (Landgrab, Leftist Policy 1.1 и Greedy campaign 9).
Возможности включают:
- Захватывайте меньше земли с самого начала, чтобы сэкономить деньги
- Захватываем прилегающие территории сразу, если у нас достаточно денег
- Отмените приоритет областей, где у нас уже достаточно отрыва для победы, а противник еще не провел кампанию
- Расставьте приоритеты в областях, где голосование наиболее близко ("колеблющиеся состояния", если хотите)
- Больше случайности, в том числе случайный выбор любого из четырех углов при выполнении 3x3
- Довольно хаотичная (но все же детерминированная!)
rПеременная, которая контролирует всю случайность
К недостаткам можно отнести:
- Недостаточный приоритет нерешенных состояний
- Не использовать какие-либо функции, не связанные с кампанией
- Может сбрасываться при изменении региона, хотя это было частично исправлено для
- Могут быть отклонены претензиями, которые не соответствуют границам.
(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]);
}
}
Жадная кампания по 9 на каждый регион
- Каждый ход у нас минимум 10 долларов. Это позволяет проводить кампанию на площади 3x3.
- Так как нам нужно всего 9 поясов в каждом регионе. Мы просто проводим кампанию с верхними 3x3, но отказываемся от тех, что справа или снизу.
- Каждый ход мы жадно выбираем позицию кампании: чем больше регион поддержит нас в следующий ход, тем лучше.
(regions, money, result, storage) => {
const turn = storage.turn = storage.turn + 1 || 1;
const gh = money < 21 ? 1 : 2;
const h = 4 * gh - 1;
const gw = 77 <= money ? 3 : 49 <= money ? 2 : 1;
const w = 4 * gw - 1;
const candidate = [];
for (let i = 0; i <= 4 - gw; i++) {
for (let j = 0; j <= 4 - gh; j++) {
let s = gw * gh;
for (let k = 0; k < gw; k++) {
for (let l = 0; l < gh; l++) {
let bx = i + k, by = j + l;
let region = regions.find(r => r.blocks.some(block => block == [bx * 4, by * 4] + ''));
let { number_neutral: n, number_opponent: o, number_you: y } = region;
if (y <= o) {
if (n + y > o) s += 1;
if (n + y == o) s += 0.5;
if (n + y < o) s += 0.5 ** (o - y - n / 2);
} else {
if (n + o > y) s += 0.5;
if (n + o < y) s += -(0.5 ** (y - o - n / 2));
if (n + o == y) s += 0.25;
}
}
}
candidate.push({ s, x: i, y: j });
}
}
const ts = Math.max(...candidate.map(c => c.s));
const best = candidate.filter(c => c.s === ts);
const { x: tx, y: ty } = best[turn % best.length];
return campaign([tx * 4, ty * 4], [tx * 4 + w, ty * 4 + h]);
}
Либеральная повестка дня
Духовный преемник левой политики. Кампании вся левая сторона поворачивается на один, затем объединяет оба верхних правых блока вместе, затем возвращается к защите левой стороны. Как только он будет полностью уверен, что левый прикрыт, он перейдет вправо.
(regions, money, result, storage) => {
storage.merge = (storage.merge||0)
if(money >= 100){
return campaign([1,1],[7,15])
}
else {
var map = [];
regions.forEach(reg => {
reg.blocks.forEach(b => {
map.push({
"pos": b,
"abs_avg":reg.absolute_average,
"num_you":reg.number_you / reg.blocks.length,
"num_opp":reg.number_opponent / reg.blocks.length,
"num_neu":reg.number_neutral / reg.blocks.length,
"won": (reg.number_neutral>=reg.number_you+reg.number_opponent) ? 0 : reg.number_you/ reg.blocks.length - reg.number_opponent / reg.blocks.length
});
});
});
map = map.sort((a,b)=>a.abs_avg-b.abs_avg).sort((a,b)=>a.won - b.won);
var leftmap = map.filter(b=>b.pos[0]<8)
if(money >= 25){
if(storage.merge == 0){
storage.merge++
return merge([8,0],[12,0])
}
return campaign([leftmap[0].pos[0],leftmap[0].pos[1]],[leftmap[0].pos[0]+4,leftmap[0].pos[1] + 4])
}
if(leftmap.every(b=> b.abs_avg > 3)) return campTarget3by3(map[0].pos).filter(b=>b.won<1)
return campTarget3by3(leftmap[0].pos)
}
function campTarget3by3(pos){
var a1,a2,b1,b2
if(pos[0]==0){
a1 = 1
b1 = 4
}
else {
a1 = pos[0]
b1 = pos[0] + 3
}
if(pos[1]==0){
a2 = 1
b2 = 4
}
else {
a2 = pos[1]
b2 = pos[1] + 3
}
return campaign([a1,a2],[b1,b2])
}
}
По иронии судьбы, это единственное, что может победить левая политика!