KoTH: Politischer Simulator
Es ist Wahlzeit und Ihre Aufgabe ist es, Ihren Konkurrenten in einer direkten Rivalität zu schlagen! Sie versuchen beide, eine Stadt mit 256 Einwohnern in einem 16x16-Raster zu gewinnen. Im Moment ist die Stadt noch nicht in Wahlregionen unterteilt, aber hier kommen Ihre Gerrymandering-Fähigkeiten ins Spiel! Sie können auch in beliebigen Bereichen der Stadt Kampagnen durchführen, um Unterstützung zu erhalten.
Allgemeines
Alle Bots (JS-Funktionen) werden einmal pro Spiel gegen alle anderen Bots ausgeführt. In jedem Spiel beginnt die Stadt damit, dass alle Wähler neutral sind. Das Spiel verfolgt für jeden Wähler eine Nummer und bestimmt, wen er unterstützt. Aus der Sicht eines bestimmten Bots bedeutet eine positive Zahl, dass der Wähler für ihn stimmt, während eine negative Zahl eine Stimme für den Gegner ist. Null ist neutral.
Die Stadt ist in 16 Blöcke unterteilt, die alle 4x4 Plätze sind. Abstimmungsbereiche bestehen aus einem oder mehreren Blöcken. Es gibt anfänglich 16 von ihnen, wobei jeder Block seine eigene Region hat.
Beide Bots beginnen mit 100 $ und können sich einmal pro Spielzug bewegen. Diese Bewegungen werden effektiv gleichzeitig ausgeführt, sodass es keinen Vorteil für die erste Runde gibt. Bewegungen fallen in vier Kategorien:
- Kampagnen: Dies führt dazu, dass jede Person in einem bestimmten Bereich ihre Unterstützung für den Kandidaten erhöht, der Kampagnen durchführt. Der Betrag, den es ändert, hängt von ihren Nachbarn ab.
- Umfrage: Dadurch erhält jede Person Unterstützung für den Kandidaten in einem bestimmten Bereich.
- Bestechung: Dies führt dazu, dass eine bestimmte Person ihre Unterstützung für den Kandidaten erhöht.
- Zusammenführen / Auflösen von Regionen : Dadurch werden die Abstimmungsregionen neu gestaltet.
Am Ende jeder Runde, nachdem beide Kandidaten umgezogen sind, erhalten beide 10 $.
Einzelheiten
Die folgenden Bewegungen sind zulässig. Wenn ein ungültiger Zug ausgeführt wird (unzureichendes Geld oder ungültige Koordinaten), wird der Zug des Bots übersprungen.
Alle Koordinaten sollten innerhalb 0 <= n < 16und für das zweite Paar in rechteckigen Begrenzungsbereichen liegen 0 <= n <= 16(da diese exklusiv sind ).
campaign([x, y], [x, y]): Kampagne innerhalb der durch die beiden Koordinatenpaare bestimmten Grenze- Kostet 1 USD pro betroffener Person
- Jede Person in der Region ändert ihre Unterstützung gemäß den folgenden Regeln:
- Fügen Sie für alle orthogonal benachbarten Nachbarn (einschließlich Diagonalen) 0,1 Unterstützung für den Kandidaten hinzu, der die Mehrheit unterstützt (gewichtet), oder 0,2, wenn die Gesamtunterstützung für diesen Kandidaten mindestens 2 beträgt
- Fügen Sie 0,25 für den Kandidaten hinzu, der sich für eine Kampagne einsetzt
poll([x, y], [x, y]): Abfrage innerhalb der durch die beiden Koordinatenpaare bestimmten Grenze- Kosten 0,25 USD pro befragter Person, aufgerundet
- In der nächsten Runde werden die folgenden Informationen über jede Person in dem Gebiet (nach dem Zug des Gegners) gegeben:
- Ihre Unterstützung für den Wahlkandidaten, wobei positive Zahlen eine Abstimmung für sie anzeigen und negative Zahlen eine Abstimmung für den Gegner darstellen
bribe([x, y]): Bestechen Sie die Person an dem durch das Koordinatenpaar bestimmten Ort- Kostet mindestens 5 $
- Fügen Sie für jedes Mal, wenn eine Person innerhalb des Abstimmungsblocks ( nicht der Abstimmungsregion) bestochen wurde, 1 USD hinzu
- Fügen Sie bis zu 3,5 Unterstützung für den Bestechungskandidaten hinzu
- Für jedes Mal, wenn eine Person innerhalb des Abstimmungsblocks bestochen wurde, wird die hinzugefügte Unterstützung um 0,15 verringert
- Dies kann letztendlich dazu führen, dass jemand in einem Abstimmungsblock bestochen wird, um seine Unterstützung für den Kandidaten zu verringern
- Kostet mindestens 5 $
merge([x, y], [x, y]): Füge die durch die beiden Koordinatenpaare bestimmten Abstimmungsbereiche zusammen- Kostet 25 USD für jeden Block in der neu gebildeten Region (ein Block ist kostenlos)
- Erfordert, dass die Regionen, in denen sich die beiden angegebenen Personen befinden, sich berühren
- Beachten Sie, dass die Koordinaten Personen und nicht Blöcken entsprechen. Um auf einen Block zu verweisen, multiplizieren Sie einfach seine Koordinaten mit 4
unmerge([x, y]): Lösen Sie den durch das Koordinatenpaar bestimmten Abstimmungsbereich- Kostet 25 US-Dollar für jeden Block in der Region
- Jeder Block in der Region wird zu einer eigenen Region
Wenn beide Bots in derselben Runde versuchen, sich zusammenzuführen oder zu trennen (auch wenn sie sich nicht einmischen), werden beide Runden übersprungen und keiner zahlt etwas. Züge werden in der folgenden Reihenfolge verarbeitet (die Reihenfolge der übrigen spielt keine Rolle):
- Bestechung
- Unterstützung von Nachbarn bei Kampagnen
- Unterstützung von Kandidaten beim Campigning
Gewinnen
Am Ende jeder Runde, nachdem beide Kandidaten umgezogen sind, werden allen Regionen ihre Stimmen hinzugefügt. Jede Person wählt entweder einen Kandidaten oder ist neutral, unabhängig davon, um wie viel (dh eine Punktzahl von +0,05 oder +30 wäre hier identisch). Wenn die folgenden Bedingungen erfüllt sind, wird eine Wahl abgehalten und der Gewinner ausgewählt:
- Alle Regionen bestehen aus weniger als halb neutralen Wählern
- Die Anzahl der Regionen, die für jeden Kandidaten stimmen, ist nicht gebunden
I / O.
Alle Bots sollten in Form von Javascript-Funktionen eingereicht werden. Die folgenden Informationen werden als Argumente für die Funktion bereitgestellt:
- Ein Array von Abstimmungsbereichen, die als Objekte mit den folgenden Eigenschaften dargestellt werden:
blocks: Ein Array von Abstimmungsblöcken, dargestellt als Koordinaten[x, y]der Person oben links (wie[4, 0]oder[12, 12])number_neutral: Die Anzahl der Menschen in der Region, die neutral sindnumber_you: Die Anzahl der Personen in der Region, die für den Bot stimmennumber_opponent: Die Anzahl der Personen in der Region, die für den Gegner des Bots stimmenabsolute_average: Der durchschnittliche absolute Wert der Unterstützung von Personen für einen Kandidaten- Höhere Zahlen bedeuten, dass Kampagnen oder Bestechungen in der Regel weniger effektiv sind
- Genau 0 würde bedeuten, dass jede Person in der Region neutral ist
- Der Geldbetrag, den der Bot hat
- Ein Objekt, das die Ergebnisse des letzten Zuges enthält (leer, es sei denn, es war eine Umfrage)
- Ein Array
peopleenthält Objekte, die jede abgefragte Person darstellen:position: Die Koordinaten der Person, formatiert als[x, y]region: Die nummerierte ID der Region, in der sich die Person befindet (der Index der Region im ersten Argument)support: Eine Zahl, die angibt, ob die Person neutral ist (0), für den Bot stimmt (positiv) oder für den Gegner des Bots stimmt (negativ)
- Ein Objekt
amountsmit den folgenden Eigenschaften:number_neutral: Die Anzahl der Menschen in der Region, die neutral sindnumber_you: Die Anzahl der Personen in der Region, die für den Bot stimmennumber_opponent: Die Anzahl der Personen in der Region, die für den Gegner des Bots stimmenabsolute_average: Der durchschnittliche absolute Wert der Unterstützung von Personen für einen Kandidaten
- Ein Array
- Ein Objekt, das zur Speicherung zwischen Runden (aber nicht zwischen Runden / Spielen) verwendet werden kann.
Um sich zu bewegen, sollte das Ergebnis einer der oben genannten Funktionen zurückgegeben werden. Beispielsweise:
{
"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]
);
}
}
Regeln
- Das Ausführen von Bots sollte eine angemessene Zeit in Anspruch nehmen
- Bots sollten fair spielen und den Controller oder andere Einsendungen nicht auf eine Weise manipulieren, die hier nicht erlaubt ist
- Jeder Bot wird einmal gegen jeden anderen Bot ausgeführt, wobei der Gewinner der Bot ist, der am häufigsten gewinnt
- Bei einem Unentschieden gewinnt der frühere Bot
- Ungültige Rückgabewerte oder Bots, bei denen ein Fehler auftritt, setzen für die Runde keine Bewegung voraus
- Bots müssen deterministisch sein
Herausforderungsidee und ursprünglicher Sandbox-Vorschlag von HyperNeutrino.
Regler: https://redwolfprograms.github.io/political-simulator
Chatroom: Hier
Fälligkeitsdatum: Donnerstag, 5. November 2020, UTC Mittag (8:00 EST)
Antworten
Abotcus
Es stellt sich heraus, dass Sie nicht so viele ausgefallene Funktionen benötigen, um genauso gut oder besser als Randgrab zu sein! Abotcus greift zu Beginn nach einem Bereich und wendet anschließend eine einfache Formel an, um jeden Block zu gewichten, und wählt den Block mit dem besten Gewicht aus. Wenn es so aussieht, als wären unnötige Dinge drin, dann weil ich erwartet hatte, dass ich viel mehr ausgefallene Funktionen hinzufügen würde ... aber ich musste einfach nicht ¯ \ _ (ツ) _ / ¯
(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);
}
}
```
Landraub
Kurze Beschreibung der Strategie:
- Führen Sie in der ersten Runde eine große Kampagne in der Mitte durch, wobei Sie das gesamte anfängliche Geld verbrauchen.
- Wenn es eine Region mit mehr als 2 neutralen Wählern gibt, wählen Sie in der Region mit den neutralsten Wählern.
- Andernfalls, wenn es eine Region gibt, in der ich und mein Gegner die gleiche Anzahl von Wählern haben, kampieren Sie in dieser Region.
- Ansonsten Kampagne in der Region mit den neutralsten Wählern.
Dies beruht auf der Tatsache, dass es viel einfacher ist, neutrale Wähler zu beanspruchen als Wähler, die bereits für Ihren Gegner stimmen werden. Es weiß nichts über Gerrymandering, daher ist es möglicherweise nicht sehr gut, wenn jemand mit Wahlregionen herumspielt, aber es ist ein Anfang.
(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])
}
}
Linke Politik
v1.1
Greift nach der linken Seite der Karte und dann nach rechts. Wenn dies erreicht ist, wird die rechte Seite langsam aktiviert, um einen Deadlock zu vermeiden.
(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;
}
}
}
Derzeit schlägt nur der Beispielbot. Die Strategie ist einfach zu langsam, um viel zu gewinnen.
Randgrab
Begann als Weiterentwicklung von Landgrab, um die Zufälligkeit zu erhöhen, und fügte dann langsam mehr und mehr Funktionen hinzu, bis es derzeit alle anderen Teilnehmer schlägt (Landgrab, Leftist Policy 1.1 und Greedy-Kampagne 9).
Features sind:
- Am Anfang weniger Land holen, um Geld zu sparen
- Sofort angrenzende Gebiete ergreifen, wenn wir genug Geld haben
- Entpriorisieren Sie Bereiche, in denen wir bereits genug Vorsprung haben, um zu gewinnen, und der Gegner noch keine Kampagne gestartet hat
- Priorisieren Sie Bereiche, in denen die Abstimmung am nächsten ist (die "Swing-Zustände", wenn Sie so wollen).
- Mehr Zufälligkeit, einschließlich der zufälligen Auswahl einer der vier Ecken, die bei einem 3x3 beansprucht werden sollen
- Eine ziemlich chaotische (aber immer noch deterministische!)
rVariable, die jede Zufälligkeit kontrolliert
Zu den Schwächen gehören:
- Unentschlossene Zustände nicht hoch genug zu priorisieren
- Keine Nicht-Kampagnenfunktionen nutzen
- Kann durch Regionsänderungen verworfen werden, obwohl dies teilweise korrigiert wurde
- Kann durch Ansprüche abgeworfen werden, die nicht gut an Grenzen ausgerichtet sind
(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]);
}
}
Gierige Kampagne 9 pro Region
- In jeder Runde haben wir mindestens 10 US-Dollar. Dies ermöglicht es, einen 3x3-Bereich zu kampieren.
- Da brauchen wir in jeder Region nur 9 Gürtel. Wir kampagnen nur oben 3x3, geben aber die rechts oder unten auf.
- In jeder Runde wählen wir die gierige Kampagnenposition: Je mehr Regionen uns in der nächsten Runde unterstützen, desto besser.
(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]);
}
Liberale Agenda
Der geistige Nachfolger der linken Politik. Kampagnen auf der gesamten linken Seite drehen sich um eins, dann werden beide oberen rechten Blöcke zusammengerollt und dann wieder die linke Seite geschützt. Sobald es sehr sicher ist, dass die linke Seite bedeckt ist, bewegt es sich nach rechts.
(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])
}
}
Ironischerweise ist es das einzige, was die linke Politik schlagen kann!