Dies ist eine Adaption von Core War , einem Programm von KOTH aus dem 20. Jahrhundert. Genauer gesagt wird ein unglaublich vereinfachter Befehlssatz verwendet, der hauptsächlich auf dem ursprünglichen Vorschlag basiert .
Hintergrund
In Core War kämpfen zwei Programme um die Kontrolle über den Computer. Das Ziel jedes Programms ist es, durch Auffinden und Beenden des gegnerischen Programms zu gewinnen.
Der Kampf findet im Hauptspeicher des Computers statt. Dieser Speicher wird als Core bezeichnet und enthält 8192 Adressen. Wenn der Kampf beginnt, wird der Code für jeden Konkurrenten (als Krieger bezeichnet) in einem zufälligen Speicherblock abgelegt. Die Programmausführung wechselt zwischen Kriegern und führt jeweils eine Anweisung aus. Jeder Befehl ist in der Lage, einen Teil des Kerns zu ändern, was zur Möglichkeit führt, Programme selbst zu ändern.
Ziel ist es, das gegnerische Programm zu beenden. Ein Programm wird beendet, wenn versucht wird, einen ungültigen Befehl auszuführen, bei dem es sich um einen beliebigen DAT
Befehl handelt.
Der Befehlssatz
Jedes Programm besteht aus einer Reihe von Anweisungen auf niedriger Ebene, die jeweils zwei Felder enthalten, die als A- und B-Felder bezeichnet werden.
Dieser Befehlssatz lehnt sich stark an die ursprüngliche Spezifikation an. Die wichtigsten Änderungen sind 1) Erläuterungen zum Hinzufügen / Entfernen von Befehlen und 2) eine Änderung des #
Adressierungsmodus, damit er überall verwendet werden kann. Die meisten Vollversionen von Core Wars haben über 20 Opcodes, 8 Adressierungsmodi und eine Reihe von "Anweisungsmodifikatoren".
Opcodes
Jeder Befehl muss einen von sieben verschiedenen Operationscodes haben.
DAT A B
- (Daten) - Hier stehen einfach die ZahlenA
undB
. Wichtig ist, dass ein Prozess abstürzt, wenn er versucht, einen DAT-Befehl auszuführen.MOV A B
- (Verschieben) - Verschiebt den Inhalt des SpeicherortsA
zum SpeicherortB
. Hier ist eine Demonstration von Vorher und Nachher:MOV 2 1 ADD @4 #5 JMP #1 -1
MOV 2 1 JMP #1 -1 JMP #1 -1
ADD A B
- (Hinzufügen) - Fügt den Inhalt des SpeicherortsA
zum Speicherort hinzuB
. Die beiden ersten Felder von beiden werden hinzugefügt, und die zweiten Felder werden hinzugefügt.ADD 2 1 MOV @4 #5 JMP #1 -1
ADD 2 1 MOV @5 #4 JMP #1 -1
SUB A B
- (subtrahieren) - Hiermit wird der Inhalt des Speicherorts vom Speicherort subtrahiertA
(und das Ergebnis dort gespeichert)B
.SUB 2 1 MOV @4 #5 JMP #1 -1
SUB 2 1 MOV @3 #6 JMP #1 -1
JMP A B
- (Sprung) - Sprung zur PositionA
, die im nächsten Zyklus ausgeführt wird.B
muss eine Zahl sein, tut aber nichts (Sie können sie jedoch zum Speichern von Informationen verwenden).JMP 2 1337 ADD 1 2 ADD 2 3
Der Sprung bedeutet,
ADD 2 3
dass der nächste Zyklus ausgeführt wird.JMZ A B
- (Sprung bei Null) - Wenn beide ZeilenfelderB
0 sind, springt das Programm zur PositionA
.JMZ 2 1 SUB 0 @0 DAT 23 45
Da die beiden Felder der Anweisung 1 0 sind, wird der DAT-Befehl in der nächsten Runde ausgeführt, was zum unmittelbar bevorstehenden Tod führt.
CMP A B
- (vergleichen und überspringt , wenn nicht gleich) - Wenn die Felder in AnweisungenA
undB
nicht gleich sind, lassen Sie die nächste Anweisung.CMP #1 2 ADD 2 #3 SUB @2 3
Da die beiden Felder von Befehl 1 und 2 den gleichen Wert haben, wird der Befehl ADD nicht übersprungen und in der nächsten Runde ausgeführt.
Wenn zwei Befehle addiert / subtrahiert werden, werden die beiden Felder (A und B) paarweise addiert / subtrahiert. Der Adressierungsmodus und der Opcode werden nicht geändert.
Adressierungsmodi
Es gibt drei Arten von Adressierungsmodi. Jedes der beiden Felder eines Befehls hat einen dieser drei Adressierungsmodi.
Sofort
#X
-X
ist die Zeile, die direkt bei der Berechnung verwendet werden soll. Zum Beispiel#0
ist die erste Zeile des Programms. Negative Zeilen beziehen sich auf Zeilen im Kern vor dem Start des Programms.... //just a space-filler ... ADD #3 #4 DAT 0 1 DAT 2 4
Dadurch wird die erste der beiden DAT-Zeilen zur zweiten hinzugefügt, da sich diese in den Zeilen 3 bzw. 4 befinden. Sie möchten diesen Code jedoch nicht verwenden, da der DAT Ihren Bot im nächsten Zyklus tötet.
Relativ
X
- Die Zahl gibtX
die Position einer Zielspeicheradresse relativ zur aktuellen Adresse an. Die Nummer an dieser Stelle wird für die Berechnung verwendet. Wenn line#35
ausgeführt wird und enthält-5
, wird line#30
verwendet.... //just a space-filler ... ADD 2 1 DAT 0 1 DAT 2 4
Dadurch wird die zweite DAT-Zeile zur ersten hinzugefügt.
Indirekt
@X
- Die NummerX
repräsentiert eine relative Adresse. Der Inhalt an dieser Stelle wird vorübergehend zur Nummer X hinzugefügt, um eine neue relative Adresse zu bilden, von der die Nummer abgerufen wird. Wenn die Zeile#35
ausgeführt wird@4
und das zweite Feld#39
die Nummer enthält-7
, wird die Zeile#32
verwendet.... //just a space-filler ... ADD @1 @1 DAT 0 1 DAT 2 4
Dadurch wird das erste DAT zum zweiten DAT hinzugefügt, jedoch auf eine komplexere Weise. Das erste Feld ist @ 1, das die Daten von dieser relativen Adresse abruft. Dies ist das erste Feld des ersten DAT, eine 0. Dies wird als zweite relative Adresse von dieser Position interpretiert, sodass 1 + 0 = 1 die Summe ergibt Versatz von der ursprünglichen Anweisung. Für das zweite Feld erhält @ 1 den Wert von dieser relativen Adresse (die 1 im zweiten Feld des ersten DAT) und fügt ihn auf die gleiche Weise zu sich selbst hinzu. Der Gesamtversatz beträgt dann 1 + 1 = 2. Diese Anweisung wird also ähnlich wie folgt ausgeführt
ADD 1 2
.
Jedes Programm kann bis zu 64 Anweisungen enthalten.
Wenn eine Runde beginnt, werden die beiden Programme zufällig in einer Speicherbank mit 8192 Speicherplätzen abgelegt. Der Befehlszeiger für jedes Programm beginnt am Anfang des Programms und wird nach jedem Ausführungszyklus inkrementiert. Das Programm stirbt, sobald der Befehlszeiger versucht, einen DAT
Befehl auszuführen .
Parameter des Kerns
Die Kerngröße beträgt 8192 mit einem Timeout von 8192 * 8 = 65536 Ticks. Der Kern ist zyklisch, sodass das Schreiben an Adresse 8195 dem Schreiben an Adresse 3 entspricht. Alle nicht verwendeten Adressen werden mit initialisiert DAT #0 #0
.
Jeder Teilnehmer darf nicht länger als 64 Linien sein. Ganzzahlen werden als 32-Bit-Ganzzahlen mit Vorzeichen gespeichert.
Parsing
Um den Wettbewerbern das Programmieren zu erleichtern, werde ich dem Parser eine Zeilenbeschriftung hinzufügen. Alle Wörter, die in einer Zeile vor einem Opcode vorkommen, werden als Zeilenbezeichnungen interpretiert. Hat zum Beispiel tree mov 4 6
die Zeilenbezeichnung tree
. Wenn sich irgendwo im Programm ein Feld befindet, das tree
#tree
oder enthält @tree
, wird eine Zahl ersetzt. Auch die Großschreibung wird ignoriert.
Hier ist ein Beispiel, wie Zeilenbeschriftungen ersetzt werden:
labelA add labelB @labelC
labelB add #labelC labelC
labelC sub labelA @labelB
Hier stehen die Bezeichnungen A, B und C in den Zeilen 0, 1 und 2. Die Instanzen von #label
werden durch die Zeilennummer der Bezeichnung ersetzt. Instanzen von label
oder @label
werden durch die relative Position des Etiketts ersetzt. Adressierungsarten bleiben erhalten.
ADD 1 @2
ADD #2 1
SUB -2 @-1
Wertung
Für jedes Teilnehmerpaar wird jede mögliche Schlacht durchgeführt. Da das Ergebnis eines Kampfes von den relativen Offsets der beiden Programme abhängt, wird jeder mögliche Offset (etwa 8000 davon) ausprobiert. Darüber hinaus hat jedes Programm die Möglichkeit, sich bei jedem Versatz zuerst zu bewegen. Das Programm, das die Mehrheit dieser Offsets gewinnt, ist der Gewinner des Paares.
Für jedes Paar, das ein Krieger gewinnt, erhält er 2 Punkte. Für jedes Unentschieden erhält ein Krieger 1 Punkt.
Du darfst mehr als einen Krieger einreichen. Es gelten die typischen Regeln für mehrere Einreichungen, wie kein Tag-Teaming, keine Zusammenarbeit, keine Königswerdung usw. In Core War gibt es dafür sowieso keinen Platz, daher sollte es keine große Sache sein.
Der Controller
Der Code für den Controller sowie zwei einfache Beispiel-Bots befinden sich hier . Da dieser Wettbewerb (bei Verwendung der offiziellen Einstellungen) vollständig deterministisch ist, entspricht die von Ihnen erstellte Rangliste genau der offiziellen Rangliste.
Beispiel Bot
Hier ist ein Beispiel-Bot, der einige Funktionen der Sprache demonstriert.
main mov bomb #-1
add @main main
jmp #main 0
bomb dat 0 -1
Dieser Bot löscht langsam den gesamten Speicher im Kern, indem er ihn durch eine "Bombe" ersetzt. Da die Bombe eine DAT
Anweisung ist, wird jedes Programm, das eine Bombe erreicht, zerstört.
Es gibt zwei Zeilenbezeichnungen, "main" und "bomb", die dazu dienen, Zahlen zu ersetzen. Nach der Vorverarbeitung sieht das Programm folgendermaßen aus:
MOV 3 #-1
ADD @-1 -1
JMP #0 0
DAT 0 -1
Die erste Zeile kopiert die Bombe in die Zeile unmittelbar über dem Programm. In der nächsten Zeile wird der Wert von bomb ( 0 -1
) zum Befehl move hinzugefügt und die Verwendung des @
Adressierungsmodus demonstriert . Dieser Zusatz bewirkt, dass der Befehl move auf ein neues Ziel zeigt. Der nächste Befehl springt bedingungslos zum Programmstart zurück.
Aktuelle Rangliste
24 - Turbo
22 - DwarvenEngineer
20 - HanShotFirst
18 - Dwarf
14 - ScanBomber
10 - Paranoid
10 - FirstTimer
10 - Janitor
10 - Evolved
6 - EasterBunny
6 - CopyPasta
4 - Imp
2 - Slug
Paarweise Ergebnisse:
Dwarf > Imp
CopyPasta > Imp
Evolved > Imp
FirstTimer > Imp
Imp > Janitor
Imp > ScanBomber
Slug > Imp
DwarvenEngineer > Imp
HanShotFirst > Imp
Turbo > Imp
EasterBunny > Imp
Paranoid > Imp
Dwarf > CopyPasta
Dwarf > Evolved
Dwarf > FirstTimer
Dwarf > Janitor
Dwarf > ScanBomber
Dwarf > Slug
DwarvenEngineer > Dwarf
HanShotFirst > Dwarf
Turbo > Dwarf
Dwarf > EasterBunny
Dwarf > Paranoid
Evolved > CopyPasta
FirstTimer > CopyPasta
Janitor > CopyPasta
ScanBomber > CopyPasta
CopyPasta > Slug
DwarvenEngineer > CopyPasta
HanShotFirst > CopyPasta
Turbo > CopyPasta
CopyPasta > EasterBunny
Paranoid > CopyPasta
Evolved > FirstTimer
Evolved > Janitor
ScanBomber > Evolved
Evolved > Slug
DwarvenEngineer > Evolved
HanShotFirst > Evolved
Turbo > Evolved
EasterBunny > Evolved
Paranoid > Evolved
Janitor > FirstTimer
ScanBomber > FirstTimer
FirstTimer > Slug
DwarvenEngineer > FirstTimer
HanShotFirst > FirstTimer
Turbo > FirstTimer
FirstTimer > EasterBunny
FirstTimer > Paranoid
ScanBomber > Janitor
Janitor > Slug
DwarvenEngineer > Janitor
HanShotFirst > Janitor
Turbo > Janitor
Janitor > EasterBunny
Janitor > Paranoid
ScanBomber > Slug
DwarvenEngineer > ScanBomber
HanShotFirst > ScanBomber
Turbo > ScanBomber
ScanBomber > EasterBunny
ScanBomber > Paranoid
DwarvenEngineer > Slug
HanShotFirst > Slug
Turbo > Slug
EasterBunny > Slug
Paranoid > Slug
DwarvenEngineer > HanShotFirst
Turbo > DwarvenEngineer
DwarvenEngineer > EasterBunny
DwarvenEngineer > Paranoid
Turbo > HanShotFirst
HanShotFirst > EasterBunny
HanShotFirst > Paranoid
Turbo > EasterBunny
Turbo > Paranoid
Paranoid > EasterBunny
Das neueste Update (neue Versionen von Turbo und Paranoid) benötigte ungefähr 5 Minuten, um auf einem alten Laptop ausgeführt zu werden. Ich möchte Ilmari Karonen für seine Verbesserungen am Controller danken . Wenn Sie eine lokale Kopie des Controllers haben, sollten Sie Ihre Dateien aktualisieren.