REDCODE-HANDBUCH
CoreDuel implementiert den Standard ICWS'94 mit den pMARS-Erweiterungen (P-Space, FOR/ROF, EQU-Ausdrücke). Wer pMARS kennt, findet sich hier sofort zurecht.
0. Worum es geht
Du schreibst kleine Programme, Bots, und schickst sie in die Arena, wo sie gegen andere Bots kämpfen. Ein Bot ist in Redcode geschrieben, einer Assembler-ähnlichen Sprache mit 19 Befehlen. Alle Bots eines Matches werden in einen gemeinsamen Speicher geladen, den Core: einen Ring aus 8000 Zellen (das Ende geht in den Anfang über) in einem einfachen emulierten Rechner namens MARS. Mehr gibt es in diesem Rechner nicht: keinen Bildschirm, keine Dateien, nur Befehle in Zellen und ein paar Prozesse, die sie ausführen.
Jeder Bot startet mit einem Prozess an einer zufälligen Stelle, mindestens 100 Zellen von den anderen entfernt. Der Emulator gibt den Bots reihum einen Zug; in seinem Zug führt ein Bot einen Befehl eines seiner Prozesse aus. Ein Prozess stirbt, wenn er einen DAT-Befehl ausführt (oder durch null teilt); ein Bot stirbt mit seinem letzten Prozess. Weil sich alle den Speicher teilen, gewinnt man so: den Code des Gegners mit DAT-Bomben überschreiben, ihn durch Scannen des Cores aufspüren oder den Core mit Kopien von sich selbst fluten, und dabei den eigenen Code am Leben halten.
Eine Runde endet, wenn nur ein Bot übrig ist, oder nach 80000 Zyklen. Ein Match besteht aus einer oder mehreren Runden; die Überlebenden jeder Runde bekommen Punkte, die beste Summe gewinnt. Das ist das ganze Spiel: ein besseres Programm schreiben als der andere.
So geht es los:
- Bots: einen Klassiker aus der Bibliothek kopieren (Imp, Dwarf) oder einen neuen schreiben; der Editor prüft den Code beim Tippen.
- Arena: die Bot-Slots füllen (ein einzelner Bot ist ein Testlauf), Runden und Tempo einstellen, START, dem Core zusehen.
- Tutorial: acht kurze Lektionen, die Schritt für Schritt einen funktionierenden Bot bauen. Der Rest dieses Handbuchs ist die Sprachreferenz.
1. Der Core
Der Core ist ein Ring aus 8000 Zellen. Jede Zelle enthält einen Befehl. Adressen laufen im Kreis: Zelle 7999 + 1 = Zelle 0. Absolute Adressen gibt es in Redcode nicht; jede Adresse ist relativ zu dem Befehl, der sie verwendet.
Vor jeder Runde ist jede Zelle DAT.F $0, $0. Die Krieger werden an zufälligen Positionen platziert, mindestens MINDISTANCE (100) Zellen voneinander entfernt. Jeder Krieger startet mit einem Prozess an seinem ORG.
Die Prozesse sind abwechselnd am Zug: Krieger A führt einen Befehl aus, dann Krieger B und so weiter. Hat ein Krieger mehrere Prozesse, laufen sie reihum (Round-Robin), einer pro Zug.
Ein Prozess stirbt, wenn er DAT ausführt oder durch null teilt. Ein Krieger stirbt, wenn sein letzter Prozess stirbt. Die Runde endet, wenn nur noch ein Krieger übrig ist, oder nach MAXCYCLES (80000) Zyklen.
2. Aufbau eines Programms
;redcode-94
;name Dwarf
;author A. K. Dewdney
;strategy Bombs every 4th cell.
;assert CORESIZE == 8000
ORG start
target DAT.F #0, #0
start ADD.AB #4, target
MOV.AB #0, @target
JMP start
END
- Alles nach
;ist ein Kommentar. Die Zeilen;name,;authorund;strategywerden in der Bot-Liste und in der Arena angezeigt. ;assert EXPRmuss wahr sein, sonst wird der Krieger abgelehnt. Damit lässt sich angeben, für welche Core-Größe er optimiert ist.- Text vor
;redcode(Mail-Header, Fließtext) wird ignoriert. - Labels stehen am Zeilenanfang. Ein abschließender Doppelpunkt ist erlaubt (
start: ADD ...). Bei Labels wird Groß-/Kleinschreibung unterschieden. - Bei Opcodes, Modifikatoren und Adressierungsarten spielt Groß-/Kleinschreibung keine Rolle.
- Ein Programm darf höchstens
MAXLENGTH(100) Befehle haben.
3. Befehlssyntax
label OPCODE.MOD A-mode A-value, B-mode B-value ; comment
Jeder Befehl hat einen Opcode, einen Modifikator, einen A-Operanden und einen B-Operanden. Jeder Operand besteht aus einer Adressierungsart und einem Wert. Werte sind Ausdrücke (siehe 8), die relativ zur Adresse des Befehls selbst ausgewertet werden.
Wird nur ein Operand angegeben, ergänzt der Assembler den anderen:
DAT xwird zuDAT #0, x.- Bei allen anderen Opcodes ist der einzelne Operand A, und B wird
$0.
4. Adressierungsarten
Jeder Operand wird zu einem Zeiger (einer Zelladresse) aufgelöst, bevor der Opcode ausgeführt wird. Beide Operanden werden immer ausgewertet, A zuerst, auch wenn der Opcode einen davon ignoriert. Deshalb treten die Nebeneffekte von Prä-/Post-Inkrement sogar bei JMP auf.
#unmittelbar. Der Zeiger ist der aktuelle Befehl selbst; der Wert wird als Zahl verwendet.$direkt (Standard). Zeiger = hier + Wert.@B-indirekt. Zeiger = (hier + Wert) + B-Feld dieser Zelle.<B-indirekt mit Prädekrement. Zuerst das B-Feld der Zelle (hier + Wert) dekrementieren, dann wie@.>B-indirekt mit Postinkrement. Wie@, danach das B-Feld dieser Zelle inkrementieren.*A-indirekt. Wie@, aber über das A-Feld.{A-indirekt mit Prädekrement. Wie<, über das A-Feld.}A-indirekt mit Postinkrement. Wie>, über das A-Feld.
Hinweis: In ICWS'94 hat auch der unmittelbare Operand einen Zeiger, nämlich den Befehl selbst. MOV.I #0, 1 kopiert den ganzen Befehl eine Zelle weiter. Das ist der klassische Imp.
5. Modifikatoren
Der Modifikator legt fest, welche Felder der Opcode liest und schreibt. Für einen Befehl mit A-Zeiger a und B-Zeiger b:
.AA-Feld von a ins A-Feld von b..BB-Feld von a ins B-Feld von b..ABA-Feld von a ins B-Feld von b..BAB-Feld von a ins A-Feld von b..Fbeide Felder, A nach A und B nach B..Xbeide Felder über Kreuz, A nach B und B nach A..Ider ganze Befehl, einschließlich Opcode und Adressierungsarten. Nur beiMOV; bei Arithmetik verhält sich.Iwie.F.
Standard-Modifikator, wenn keiner angegeben ist:
DAT,NOP:.FMOV,CMP,SEQ,SNE:.AB, wenn A unmittelbar ist,.B, wenn B unmittelbar ist, sonst.IADD,SUB,MUL,DIV,MOD:.AB, wenn A unmittelbar ist,.B, wenn B unmittelbar ist, sonst.FSLT,LDP,STP:.AB, wenn A unmittelbar ist, sonst.BJMP,JMZ,JMN,DJN,SPL:.B
6. Opcodes
DATBeendet den Prozess, der ihn ausführt. Außerdem die übliche Datenzelle und Bombe.MOVKopiert von A nach B.ADDB = B + A (gemäß Modifikator).SUBB = B - A.MULB = B * A.DIVB = B / A. Division durch null beendet den Prozess; bei.F/.X/.Iwird das andere Feld trotzdem geschrieben, sofern sein Divisor nicht null ist.MODB = B mod A. Gleiche Null-Regel wie beiDIV.JMPSpringt zum A-Zeiger.JMZSpringt zu A, wenn die geprüften B-Felder null sind. Bei.F/.X/.Imüssen beide Felder null sein.JMNSpringt zu A, wenn die geprüften Felder nicht null sind. Bei.F/.X/.I: wenn eines davon nicht null ist.DJNDekrementiert die geprüften Felder von B und springt dann zu A, wenn das Ergebnis nicht null ist. Der klassische Schleifenzähler.SPLFügt einen neuen Prozess am A-Zeiger hinzu, eingereiht hinter die bestehenden Prozesse. Der aktuelle Prozess macht mit dem nächsten Befehl weiter. Bis zuMAXPROCESSES(8000) pro Krieger; darüber hinaus bewirktSPLnichts.SEQÜberspringt den nächsten Befehl, wenn A gleich B ist (gemäß Modifikator).CMPist ein Synonym.SNEÜberspringt den nächsten Befehl, wenn A sich von B unterscheidet.SLTÜberspringt den nächsten Befehl, wenn A kleiner als B ist (vorzeichenlos, 0..7999).LDPLädt aus dem P-Space: Der Wert in P-Space[A-Wert] wird ins B-Ziel geschrieben.STPSpeichert in den P-Space: Der A-Wert wird nach P-Space[B-Wert] geschrieben.NOPTut nichts (wertet aber trotzdem beide Operanden aus).
Alle Arithmetik erfolgt modulo CORESIZE. Zahlen im Core liegen immer im Bereich 0..7999; -1 wird als 7999 gespeichert.
7. Direktiven
ORG labelWo die Ausführung beginnt. Standard: der erste Befehl.END [label]Ende des Quelltexts; ein optionales Label wirkt wieORG.name EQU exprTextkonstante; jede spätere Verwendung von name wird durch expr ersetzt. Kann einen Operanden samt Adressierungsart (EQU #7) oder sogar einen Opcode enthalten.FOR n ... ROFWiederholt den Block n-mal. Ein Label in derFOR-Zeile oder in der Zeile direkt davor ist der Zähler (ab 1).&counterim Block wird zum zweistelligen Zählerwert expandiert,label&iergibt alsolabel01,label02, ... Blöcke mitFOR 0werden übersprungen, praktisch für lange Kommentare.PIN nP-Space-Kennung. Zwei Krieger mit derselbenPINteilen sich ihren P-Space (außer Zelle 0).
ORG loop
loop FOR 3
MOV.I #0, &loop
ROF
8. Ausdrücke
Operatoren, höchste Priorität zuerst: unäres - + !, dann * / % (ganzzahlig, abschneidend), dann + -, dann Vergleiche == != < > <= >=, dann && und ||. Klammern sind erlaubt. Vergleiche liefern 1 oder 0.
Vordefinierte Konstanten: CORESIZE, MAXCYCLES, MAXPROCESSES, MAXLENGTH, MINDISTANCE, PSPACESIZE, ROUNDS, WARRIORS (2), VERSION (92), CURLINE (Index des aktuellen Befehls).
Ein Label in einem Ausdruck steht für seinen Abstand zum aktuellen Befehl, label+1 ist also die Zelle nach label, und start-target ist schlicht eine Anzahl von Zellen.
9. P-Space
Jeder Krieger hat ein privates Array aus PSPACESIZE (500) Zellen, das zwischen den Runden eines Matches erhalten bleibt. Indizes laufen im Kreis. Zelle 0 ist besonders und immer privat: Zu Beginn jeder Runde enthält sie das vorige Ergebnis, 0, wenn der Krieger gestorben ist, sonst die Zahl der überlebenden Krieger (1 = Sieg, 2 = Unentschieden). In der ersten Runde enthält sie CORESIZE-1.
LDP.AB #0, x lädt dieses Ergebnis ins B-Feld von x. STP.B x, #1 speichert das B-Feld von x in Zelle 1. So kann ein Krieger nach einer Niederlage die Strategie wechseln.
10. Match-Regeln
- Core-Größe 8000, max. Zyklen 80000, max. Prozesse 8000, max. Länge 100, min. Abstand 100, P-Space 500.
- Ein Match besteht aus N Runden. Die Startpositionen ergeben sich aus dem Match-Seed und der Rundennummer; derselbe Seed spielt dasselbe Match erneut ab.
- Wertung: jeder Überlebende einer Runde bekommt (W*W-1)/S Punkte, W = Zahl der Krieger, S = Überlebende; die übrigen 0. Bei zwei Kriegern sind das 3 für einen Sieg, je 1 bei Unentschieden, 0 bei Niederlage.
- Bis zu 36 Krieger teilen sich den Core (die Optionen begrenzen die Slots); sie starten mindestens MINDISTANCE voneinander entfernt, derselbe Bot darf mehrere Slots belegen.
- Welcher Krieger zuerst zieht, wechselt jede Runde.
- Der Testmodus (ein Bot) meldet bei
MAXCYCLESÜBERLEBT oder GESTORBEN.
11. Typische Muster
- Imp:
MOV.I #0, 1. Durch Bomben nicht zu töten, gewinnt aber allein nie. - Imp-Gate:
JMP 0, <gate. Dekrementiert jeden Zyklus die Zelle davor und macht ankommende Imps zuMOV.I 0, 0. - Bomber (Dwarf): Schleife aus
ADD,MOV,JMP, dieDATs mit einer Schrittweite abwirft, die den ganzen Core abdeckt. - Scanner: Zellen mit null vergleichen, Gefundenes bombardieren.
- Replikator (Paper): sich per
MOV-Schleife an eine andere Stelle kopieren, dann perSPLdorthin verzweigen. - SPL-Bombe:
SPL 0flächendeckend; ein Krieger, der darauf landet, füllt seine Prozess-Warteschlange mit nutzlosen Schleifen. - Boot: den Krieger weit weg kopieren und die Kopie ausführen, sodass der Originalcode als Köder dient.