KoTH: Simulateur politique
C'est le moment des élections et votre travail consiste à battre votre concurrent dans une rivalité frontale! Vous essayez tous les deux de gagner une ville de 256 personnes dans une grille 16x16. À l'heure actuelle, la ville n'a pas encore été divisée en régions de vote, mais c'est là que vos compétences de gerrymandering entrent en jeu! Vous pouvez également faire campagne dans des zones arbitraires de la ville pour obtenir du soutien.
Général
Tous les bots (fonctions JS) seront exécutés contre tous les autres bots une fois par partie. Dans chaque match, la ville commencera avec tous les électeurs étant neutres. Le jeu gardera une trace d'un numéro pour chaque électeur, déterminant qui ils soutiennent. Du point de vue d'un bot particulier, un nombre positif signifie que l'électeur votera pour lui, tandis qu'un nombre négatif est un vote pour l'adversaire. Zéro est neutre.
La ville est divisée en 16 blocs, qui sont tous des carrés de 4x4. Les régions de vote sont constituées d'un ou plusieurs blocs. Il y en a initialement 16, chaque bloc ayant sa propre région.
Les deux robots commencent avec 100 $ et peuvent se déplacer une fois par tour. Ces mouvements sont exécutés efficacement simultanément, il n'y a donc aucun avantage au premier tour. Les mouvements se divisent en quatre catégories:
- Campagne: Cela amènera chaque personne dans une zone spécifique à augmenter son soutien au candidat qui fait campagne. Le montant qu'il change dépend de leurs voisins.
- Sondage: Cela obtiendra le soutien de chaque personne pour le candidat dans un domaine spécifique.
- Pot-de-vin: cela amènera une personne en particulier à augmenter son soutien au candidat.
- Fusion / annulation de la région: cela remodèlera les régions de vote.
À la fin de chaque tour, après que les deux candidats se soient déplacés, les deux recevront 10 $.
Détails
Les mouvements suivants sont autorisés. Si un coup invalide est donné (argent insuffisant ou coordonnées invalides), le tour du bot sera ignoré.
Toutes les coordonnées doivent être à l'intérieur 0 <= n < 16et pour la deuxième paire dans des zones de délimitation rectangulaires 0 <= n <= 16(car elles sont exclusives ).
campaign([x, y], [x, y]): Campagne dans la limite déterminée par les deux paires de coordonnées- Coûts 1 $ par personne touchée
- Chaque personne de la zone changera son accompagnement selon les règles suivantes:
- Pour tous les voisins orthogonalement (y compris les diagonales) adjacents, ajoutez 0,1 soutien pour le candidat le plus favorable (pondéré), ou 0,2 si leur soutien total est d'au moins 2 pour ce candidat
- Ajouter 0,25 pour le candidat qui fait campagne
poll([x, y], [x, y]): Sondage dans la limite déterminée par les deux paires de coordonnées- Coûts 0,25 USD par personne interrogée, arrondis
- Au tour suivant, les informations suivantes sont données sur chaque personne dans la zone (après le mouvement de l'adversaire):
- Leur soutien au candidat au scrutin, où les nombres positifs indiquent un vote pour lui, et les nombres négatifs étant un vote pour l'opposant
bribe([x, y]): Corrompre la personne à l'endroit déterminé par la paire de coordonnées- Coûte au moins 5 $
- Pour chaque fois qu'une personne dans le bloc de vote ( pas la région de vote) a été soudoyée, ajoutez 1 $
- Ajoutez jusqu'à 3,5 support pour le candidat corrompu
- Pour chaque fois qu'une personne dans le bloc de vote a été soudoyée, le soutien ajouté est diminué de 0,15
- Finalement, cela peut amener à corrompre quelqu'un dans un bloc de vote pour réduire son soutien au candidat.
- Coûte au moins 5 $
merge([x, y], [x, y]): Fusionner les régions de vote déterminées par les deux paires de coordonnées- Coûte 25 $ pour chaque bloc dans la région nouvellement formée (un bloc est gratuit)
- Nécessite que les régions contenant les deux personnes spécifiées soient en contact
- Notez que les coordonnées correspondent à des personnes et non à des blocs. Pour référencer un bloc, il suffit de multiplier ses coordonnées par 4
unmerge([x, y]): Annule la fusion de la région de vote déterminée par la paire de coordonnées- Coûte 25 $ pour chaque bloc de la région
- Chaque bloc de la région devient sa propre région
Si les deux robots tentent une fusion ou une annulation de la fusion sur le même tour (même s'ils n'interféreront pas), les deux tours seront sautés et aucun ne paiera rien. Les déplacements seront traités dans l'ordre suivant (l'ordre du reste n'a pas d'importance):
- Pots-de-vin
- Soutien des voisins dans la campagne
- Accompagnement des candidats dans le campigning
Gagnant
À la fin de chaque tour, après que les deux candidats ont déménagé, toutes les régions verront leurs votes ajoutés. Chaque personne votera pour un candidat ou sera neutre, quel que soit le montant (c'est-à-dire qu'un score de +0,05 ou +30 serait identique ici). Si les conditions suivantes sont remplies, une élection aura lieu et le gagnant sera choisi:
- Toutes les régions sont composées de moins de la moitié des électeurs neutres
- Le nombre de régions votant pour chaque candidat n'est pas égal
E / S
Tous les robots doivent être soumis sous la forme de fonctions Javascript. Les informations suivantes seront fournies en tant qu'arguments de la fonction:
- Un tableau de régions de vote, représentées sous forme d'objets avec les propriétés suivantes:
blocks: Un tableau de blocs de vote, représentés par les coordonnées[x, y]de la personne en haut à gauche (comme[4, 0]ou[12, 12])number_neutral: Le nombre de personnes dans la région qui sont neutresnumber_you: Le nombre de personnes dans la région votant pour le botnumber_opponent: Le nombre de personnes dans la région votant pour l'adversaire du botabsolute_average: La valeur absolue moyenne du soutien des gens à un candidat- Des nombres plus élevés indiquent qu'une campagne ou un pot-de-vin sera généralement moins efficace
- Exactement 0 signifierait que chaque personne de la région est neutre
- Le montant d'argent dont dispose le bot
- Un objet contenant les résultats du dernier déplacement (vide sauf s'il s'agissait d'un sondage)
- Un tableau
peoplecontiendra des objets représentant chaque personne interrogée:position: Les coordonnées de la personne, au format[x, y]region: ID numéroté de la région dans laquelle se trouve la personne (l'index de la région dans le premier argument)support: Un nombre indiquant si la personne est neutre (0), votant pour le bot (positif) ou votant pour l'adversaire du bot (négatif)
- Un objet
amountscontenant les propriétés suivantes:number_neutral: Le nombre de personnes dans la région qui sont neutresnumber_you: Le nombre de personnes dans la région votant pour le botnumber_opponent: Le nombre de personnes dans la région votant pour l'adversaire du botabsolute_average: La valeur absolue moyenne du soutien des gens à un candidat
- Un tableau
- Un objet qui peut être utilisé pour le stockage entre les tours (mais pas entre les tours / parties)
Pour se déplacer, le résultat de l'une des fonctions ci-dessus doit être renvoyé. Par exemple:
{
"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]
);
}
}
Règles
- Les bots devraient prendre un temps raisonnable pour s'exécuter
- Les robots doivent jouer équitablement, sans manipuler le contrôleur ou d'autres soumissions d'une manière non autorisée ici
- Chaque bot sera exécuté contre tous les autres bot une fois, le gagnant étant le bot qui gagne le plus souvent
- En cas d'égalité, le robot précédent l'emporte
- Valeurs de retour invalides ou bots dont l'erreur ne supposera aucun mouvement pour le tour
- Les robots doivent être déterministes
Idée de défi et proposition de bac à sable originale par HyperNeutrino.
Manette: https://redwolfprograms.github.io/political-simulator
Chatroom: ici
Date d'échéance: jeudi 5 novembre 2020, midi UTC (8h00 HNE)
Réponses
Abotcus
Il s'avère que vous n'avez pas besoin de tant de fonctionnalités sophistiquées pour être aussi bon ou meilleur que Randgrab! Abotcus saisit une zone au début, puis applique une formule simple pour peser chaque bloc et choisit le bloc avec le meilleur poids. S'il semble qu'il y ait des choses inutiles là-dedans, c'est parce que je m'attendais à ajouter beaucoup plus de fonctionnalités sophistiquées ... mais je n'avais tout simplement pas besoin de ¯ \ _ (ツ) _ / ¯
(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
Brève description de la stratégie:
- Au premier tour, faites une grande campagne dans le centre en utilisant tout l'argent initial.
- Sinon, s'il y a une région avec plus de 2 électeurs neutres, faites campagne dans la région avec les électeurs les plus neutres.
- Sinon, s'il y a une région dans laquelle mon adversaire et moi avons le même nombre d'électeurs, faites campagne dans cette région.
- Sinon, faites campagne dans la région avec les électeurs les plus neutres.
Cela repose sur le fait qu'il est beaucoup plus facile de revendiquer des électeurs neutres que des électeurs qui vont déjà voter pour votre adversaire. Il ne connaît pas le gerrymandering, donc il pourrait ne pas très bien faire si quelqu'un joue avec les régions de vote, mais c'est un début.
(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])
}
}
Politique de gauche
v1.1
Attrape le côté gauche de la carte, puis gerrymanders le côté droit. Après avoir accompli cela, il fera lentement campagne du côté droit pour éviter l'impasse.
(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;
}
}
}
Ne bat actuellement que le bot exemple. La stratégie est trop lente pour gagner beaucoup.
Randgrab
A commencé comme une évolution de Landgrab pour augmenter le caractère aléatoire, puis a lentement ajouté de plus en plus de fonctionnalités jusqu'à ce qu'il bat actuellement tous les autres concurrents (Landgrab, Leftist Policy 1.1 et Greedy Campaign 9).
Les fonctionnalités incluent:
- Saisir moins de terres au début pour économiser de l'argent
- Saisir les zones adjacentes à la fois si nous avons assez d'argent
- Dé-priortiser les zones où nous avons déjà suffisamment d'avance pour gagner et où l'adversaire n'a pas encore fait campagne
- Donner la priorité aux zones où le vote est le plus proche (les "états de transition", si vous voulez)
- Plus d'aléatoire, y compris le choix au hasard de l'un des quatre coins à réclamer lors d'un 3x3
- Une
rvariable assez chaotique (mais toujours déterministe!) Qui contrôle tous les aléas
Les faiblesses comprennent:
- Ne pas donner la priorité aux états indécis suffisamment
- Ne pas profiter des fonctions non liées à la campagne
- Peut être perturbé par les changements de région, bien que cela ait été partiellement corrigé pour
- Peut être rejeté par des revendications qui ne correspondent pas bien aux frontières
(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]);
}
}
Campagne gourmande 9 par région
- À chaque tour, nous avons au moins 10 $. Cela permet de faire campagne sur une zone 3x3.
- Puisque nous n'avons besoin que de 9 ceintures dans chaque région. Nous faisons simplement campagne en haut 3x3, mais abandonnons ceux de droite ou du bas.
- A chaque tour, nous choisissons une position de campagne gourmande: plus la région nous soutiendra au prochain tour, mieux c'est.
(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]);
}
Agenda libéral
Le successeur spirituel de la politique de gauche. Campagnes tout le côté gauche, tournez un, puis gerrymanders les deux blocs en haut à droite ensemble, puis revient à la protection du côté gauche. Une fois qu'il est extrêmement confiant qu'il a couvert la gauche, il se déplace vers la droite.
(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])
}
}
Ironiquement, c'est la seule chose que la politique de gauche puisse battre!