--VB0 --OZ1 11 topaz.font --OZ2 18 Bocklin.font --OZ3 27 bocklin.font --DF titelb --FF31 --ZS2 --SA0 --PA --SA1 --ZS0 --FF21 DEFEND --DF titelc --FF31 --ZS1 --SA0 --PA --SA1 --ZS0 --FF21 DEFEND --DF titelchen --FF31 --ZS0 --PA --ZS0 --FF21 DEFEND --DF climode --FF00 --PA --FF21 DEFEND --FF31 --ZS3 --SA0 2.4 Redcode-Befehle --SA1 --ZS0 --FF21 --titelb 2.4.1 Befehlstabelle Befehl Arg. Beschreibung ----------------------------------------------------------------------------- MOV A, B Uebertragen: Uebertrage (kopiere) den gesamten Inhalt von von Adresse A auf Adresse B. ADD A, B Addieren: Addiere den Inhalt von Adresse A zu Adresse B und speichere das Resultat in Adresse B. SUB A, B Subtrahieren: Ziehe den Inhalt von Adresse A von Adresse B ab und speichere das Resultat wieder in Adresse B. JMP A Springen: Setze die Ausführung des Programmes um A Zellen weiter vorne oder weiter hinten im Speicher fort. JMZ A, B Springen, wenn Null: Setze die Ausführung des Programmes um A Zellen weiter vorne oder weiter hinten im Speicher fort, wenn der Inhalt von Adresse B gleich Null ist. JMN A, B Springen, wenn nicht Null: Setze die Ausführung des Prog- rammes um A Zellen weiter vorne oder weiter hinten im Spei- cher fort, wenn der Inhalt von Adresse B ungleich Null ist. DJN A, B Vermindern und Springen, wenn nicht Null: Ziehe vom Inhalt von Adresse B eins ab und setze die Ausführung des Program- mes um A Zellen weiter vorne oder weiter hinten im Speicher fort, wenn das Resultat ungleich Null ist. CMP A, B Vergleiche: Vergleiche den Inhalt von Adresse A mit dem von Adresse B und Ueberspringe die nächste Anweisung, wenn sie gleich sind; sonst führe die nächste Anweisung aus. SPL B Spalte auf: Spalte die Ausführung des Programmes auf in die nächstfolgende Anweisung und in die Anweisung, die A Zellen weiter vorne oder weiter hinten im Speicher liegt. DAT B Daten: Nicht ausführbare Anweisung. Enthält Zahlenwerte, auf die zugegriffen werden kann. ----------------------------------------------------------------------------- --titelb 2.4.2 Argumente Wie schon erwähnt, verlangt jeder Redcode-Befehl mindestens ein Argument. Diese Argumente sind Zahlenwerte, die die Befehle näher beschreiben. Ein Beispiel: In der Anweisung `JMP -7' ist nur ein Argument angegeben, weil dieser Befehl zur Ausführung auch nur einen Wert benötigt. Die Anweisung bedeutet für MARS, innerhalb des Programmes um sieben Adressen zurückzuspringen, da das Argument negativ ist. Stände die Anweisung `JMP -7' zufällig an der Adresse 3715 im Speicherfeld, so würde die Ausführung des Programmes mit dem Befehl in der Speicherzelle 3715-7=3708 fortgesetzt. Diese Methode, eine Position im Speicherfeld zu berechnen, wird ` relative Adressierung' genannt. Als Adressierung wird der Weg bezeichnet, durch den ein System den Wert einer Speicherzelle herausfindet, die es bearbeiten soll. In der Sprache Redcode wird nur die erwähnte relative Art der Adressierung verwendet, deshalb hat ein Kampfprogramm keine Möglichkeit, seine eigene Position im Speicher zu erfahren. Ein Kampfprogramm kann zwar wild in der Gegend herumspringen, aber es weiss nie, an welcher Stelle im Speicher es sich im Augenblick befindet. Da der Speicher wie ein Ring ohne Anfang und Ende aufgebaut ist, wäre die Angabe einer Position gar nicht sinnvoll, weil die Adressnummern nur eine Reihenfolge, aber keinen Beginn und kein Ende festlegen. Ein weiterer Grund für die ausschliessliche Verwendung der relativen Adressierung ist die Ausführbarkeit der Kampfprogramme an beliebigen Positionen im Speicherfeld von MARS, die Programme müssen Relokatibel sein: Wenn ein Programm an verschiedenen Stellen im Speicher eines Computers ausführbar sein soll, so darf es keine absoluten (mit festen Zahlenwerten) angegebenen Sprungadressen besitzen. Die relative Adressierung, die in Redcode-Programmen verwendet wird, berücksichtigt diesen Umstand. --titelb 2.3.4 Programmzeiger Um die Sprache der Kampfprogramme, Redcode, zu lernen, ist es wichtig, einen Begriff aus der Welt der Programme genau zu kennen: Programmzeiger. Eingangs des zweiten Kapitels wurde beschrieben, was ein Programm für einen Computer überhaupt ist. Wir sagten, dass ein Programm aus einer Folge von Anweisungen für den Computer besteht. Dies ist auch in der Sprache Redcode der Fall: Ein Kampfprogramm besteht aus einer beliebigen Anzahl von Redcode Befehlen oder Programmzeilen, die dem System (hier also MARS) sagen, was gerade zu tun ist: Eine `DAT'-Anweisung kopieren, eine Speicherzelle inkrementieren, und so weiter. Damit nun MARS genau weiss, welche Anweisung innerhalb des Programmes als Nächstes ausgeführt werden muss, merkt sich MARS für jedes Kampfprogramm die Adresse der zuletzt bearbeiteten Programmzeile. Anhand des folgenden Fragmentes eines Redcode Programmes kann die Funktion des Programmzeigers erläutert werden: --FF00 (2340) MOV #0, $-1 (2341) JMP $-1 (2342) SPL $-2 (2343) MOV $10, $113 (2344) SPL $112 --FF21 Die Zahlen vor den Redcode Anweisungen stellen die Adressen der Speicherzellen dar, in denen die Zeilen gespeichert sind. Diese Adressen sind für ein Programm völlig ohne Bedeutung und sind normalerweise auch nicht bekannt. Ein Redcode-Programm wird rein zufällig in das Speicherfeld gesetzt, dieses Programm könnte irgendwo anders im Speicher stehen. Hier dienen die Adressen ausschliesslich zur Veranschaulichung. Zu Beginn der Ausführung des Programmes besitzt der Programmzeiger den Wert 2340, der Programmzeiger `zeigt' also gewissermassen auf die erste Anweisung des Kampfprogrammes. Diese Anweisung wird ausgeführt und der Programmzeiger um eins aufgezählt, so dass er nun den Wert 2341 besitzt. Weil immer mindestens zwei Kampfprogramme gegeneinander spielen, wird nun auf die gleiche Weise eine Zeile des gegnerischen Programmes bearbeitet. Jedes Programm besitzt seinen eigenen Programmzeiger, der immer auf seine als nächstes auszuführende Anweisung zeigt. Jetzt ist wieder das erste Kampfprogramm an der Reihe: MARS schaut sich den Inhalt des Programmzeigers an, der zum ersten Kampfprogramm gehört. Dort befindet sich die Zahl 2341, die auf die zweite Zeile des Programmes zeigt. Wie schon gehabt, wird nun die Anweisung dieser Zeile ausgeführt und der Programmzeiger wieder um eins erhöht, so dass er auf die dritte Zeile des ersten Kampfprogrammes weist. Wieder kommt das gegnerische Programm zum Zug, und so geht der Kampf weiter, bis alle Programme eines Spielers zerstört und nur noch Programme des Gegners übriggeblieben sind. Durch die Programmzeiger ist zu jedem Zeitpunkt klar definiert, welche Anweisung von welchem Programm als Nächste auszuführen ist. --titelb 2.4.4 Beschreibung der Redcode-Anweisungen Die nächsten Abschnitte enthalten zu jedem der zehn Befehle eine Beschreibung seiner Wirkung auf den Programmzeiger und auf die Speicherzellen. Wo es angebracht ist, wird ein Befehl auch durch Beispiele erklärt, die sich teilweise auch auf der CoreWars-Diskette im Verzeichnis Redcode' befinden. --titelb 2.4.5 DAT: Daten Die wichtigste Eigenschaft dieser Anweisung ist, das sie eigentlich gar keine richtige Anweisung darstellt: Sie ist nicht ausführbar. Wenn der Programmzeiger eines Kampfprogrammes auf eine Zeile mit einem DAT-Befehl stösst, wird der Programmzeiger gelöscht, und das betreffende Programm wird nicht weiter ausgeführt. `DAT'-Befehle können Bestandteile eines Programmes sein, wenn sie Informationen enthalten, die das Programm während seiner Ausführung benötigt. Ein Beispiel eines DAT-Befehls: `DAT 0'. Die Speicherzelle, in der dieser Befehl gespeichert ist, darf unter keinen Umständen ausgeführt werden. Wenn dies trotzdem der Fall ist, und ein Programmzeiger eines Kampfprogrammes in diese Anweisung rennt, wird der Programmzeiger gelöscht. Die oben angeführte DAT-Zeile kann aber auch als Informationsquelle oder als Modellzeile, mit der andere Programme überschrieben werden können, dienen: Mit dem `MOV'-Befehl, der gleich erklärt wird, kann die Zeile `DAT 0' in beliebige Speicherzellen im ganzen Speicherfeld kopiert werden, also auch in gegnerische Programme, die so zerstört werden können. --titelb 2.4.6 MOV: Uebertragen Diese Anweisung dient dazu, eine bestimmte Zahl oder den Inhalt einer der Speicherzellen in eine andere Speicherzelle zu übertragen. Die Anweisung `MOV 3, 100' (von Englisch `to move', bewegen) sagt MARS, drei Schritte oder Adressen im Speicherfeld vorwärts zu gehen und den Inhalt dieser Speicherzelle, in die Zelle 100 Adressen nach dem MOV-Befehl zu kopieren. Der alte Inhalt dieser Zelle wird durch den neuen Wert überschrieben. Die Argumente der Anweisung `MOV 3, 100' sind ``direkt'', das heisst, sie werden als Adressen interpretiert, in denen direkt etwas verändert wird. Um ein Argument als direkt anzugeben, gibt es zwei Möglichkeiten: zum Einen kann einfach eine Zahl geschrieben werden, zum Andern kann der Zahl ein `$'-Zeichen vorangestellt werden. Wenn MARS also eine Zahl als Argument vorfindet, wird sie als direkt angesehen, und das Gleiche gilt für eine Zahl mit vorangehendem `$'-Zeichen. Darüber hinaus sind zwei weitere Adressierungsmodi erlaubt. Wenn einem Argument ein `@'-Zeichen vorangestellt wird (`Klammeraffe'), wird es `indirekt' interpretiert. Bei der Anweisung `MOV @3, 100' geschieht folgendes: Der Inhalt der Adresse, die drei Schritte oder Zellen weiter vorne liegt, wird benutzt, um schliesslich zu einer weiteren Adresse zu kommen, deren Inhalt dann hundert Schritte weiter nach vorne kopiert wird. Die indirekte Adressierung soll an folgendem Beispiel veranschaulicht werden: --FF00 (2719) MOV @3, 100 (2720) JMP -5 (2721) DAT 50 (2722) DAT -1 (2723) DAT 0 (....) DAT 0 (....) DAT 0 (....) DAT 0 (2818) DAT 0 (2819) DAT 50 ; Nach Ausführung von Zelle 2719 (2820) DAT 0 --FF21 Der Programmzeiger enthalte die Nummer der Speicherzelle 2719. Dort steht der Befehl `MOV', es sollen also Daten zwischen zwei Speicherzellen übertragen werden. Das erste Argument `@3' ist durch den Klammeraffen indirekt adressiert und gibt den Ort an, von dem die Daten zu holen sind: Als erstes drei Zellen nach vorne gehen, was zu Zeile Nummer 2722 führt. In dieser Speicherzelle steht eine `DAT'-Anweisung mit dem Argument `-1', dieser Wert wird nun als direkt interpretiert. Das bedeutet, dass von Zeile 2722 genau eine Zelle nach unten gegangen werden muss, um den wirklichen Wert zu finden, der durch den `MOV'-Befehl übertragen werden soll. Wir stehen deshalb vor der Zeile 2722-1=2721, und dort steht die Anweisung `DAT 50'. Das zweite Argument der `MOV'-Anweisung ist 100 und wird, da keine spezielle Adressierungsart angegeben wird, als direkt interpretiert. Das Ziel der Verschiebung liegt dadurch 100 Zeilen weiter oben, also 2719+100=2819. Der MOV-Befehl verschiebt schlussendlich den Inhalt von Zeile 2721 (`DAT 50') nach Zeile 2819. Der ursprüngliche Inhalt dieser Speicherzelle wird durch die kopierte `DAT 50'-Zeile überschrieben. Um anschliessend nicht auf die DAT-Anweisung aufzulaufen, ist die nächste Zeile nach `MOV @3, 100' ein `JMP'-Befehl: Der Programmzeiger wird um fünf vermindert, die Ausführung des Programmes wird also an einer anderen Stelle fortgesetzt. Die angekündigte dritte Adressierungsart eines Argumentes ist `unmittelbar' und wird mit dem `#'-Zeichen eingeleitet. Beispielsweise überträgt `MOV #3, 100' Zahlenwert 3 (umittelbar) auf die Speicherzelle, die 100 Zeilen weiter vorne (direkt) im Speicher steht. `#' bedeutet, dass die folgende Zahl unverändert übernommen wird. Es ist bei der Anwendung der unmittelbaren Adressierung Vorsicht geboten, denn nicht alle Befehle können unmittelbare Werte enthalten. Der `MOV'-Befehl darf z. B. als zweites Argument nie eine unmittelbare Zahl enthalten, denn das würde bedeuten, dass ein Wert in eine mit ihrer Zeilennummer angegebenen Speicherzelle übertragen werden kann - und dies ist nicht gestattet, weil ein Redcode Programm keine Möglichkeit haben darf, seine eigene Position im Speicher zu erfahren. Dasselbe gilt für die Sprungbefehle, doch dazu später noch mehr. Nach den verschiedenen Adressierungsarten und den ersten beiden Befehlen `DAT' und `MOV' ist es an der Zeit, die Theorie in ein erstes Kampfprogramm umzusetzen. --titelc 2.4.6.1 Knirps --FF00 start MOV 0, 1 --FF21 `Knirps' ist das einfachste Beispiel für ein Redcode Programme, das sich selbst im Speicherfeld fortpflanzen kann. Es kopiert den Inhalt der direkten Adresse 0 in die direkte Adresse 1, also die nächstfolgende Adresse. Der Argumentwert wird direkt interpretiert, zeigt also auf dieselbe Stelle wie der Programmzeiger. `Knirps' kopiert sich deshalb bei jeder Ausführung in die nächstfolgende Speicherzelle. Der Programmzeiger wird nach dem Ausführen des Befehls automatisch um eins erhöht, zeigt also ebenfalls auf die nächste Speicherzelle, und der Kopiervorgang wiederholt sich aufs Neue. `Knirps' rollt folglich einer Dampfwalze gleich mit einer Geschwindigkeit von einer Adresse pro Ausführunszyklus durch das Feld, eine Spur von `MOV 0, 1' Anweisungen hinter sich lassend. Ein `Knirps' kann sogar den Programmzeiger eines anderen Programmes stehlen: Da ein `Knirps' mit jedem Ausführungszyklus eine Speicherzelle nach vorne wandert, ist er im Vergleich zu anderen, grösseren Programmen ziemlich schnell. Ein `Knirps' kann von hinten über ein vor ihm liegendes Programm rasen und so den Code des Gegners mit einer Folge von `MOV 0, 1' Anweisungen überschreiben. Früher oder später wird das derart unterwanderte Programm wahrscheinlich in den von `Knirps' überschriebenen Teil zurückspringen. In diesem Moment wird aus ihm ein neuer `Knirps', der zwar immer noch zum gleichen Programmzeiger gehört, jedoch bis zum Ende des Kampfes hinter dem gegnerischen `Knirps' herläuft. --titelb 2.4.7 ADD und SUB: Speicherarithmetik Diese beiden Anweisungen addieren (`ADD') oder subtrahieren (`SUB') die Inhalte von zwei Speicherzellen, die durch die beiden Argumente bestimmt werden. Das erste Argument der Befehle kann unmittelbar, direkt oder indirekt angegeben werden, das zweite darf nur indirekt oder direkt sein. Der Wert des ersten Argumentes wird zum Wert des zweiten Argumentes addiert und das Ergebnis wird an Stelle des zweiten Argumentes gespeichert: `ADD #5, 1' holt sich den Inhalt der Speicherzelle, die eine Zelle weiter vorne liegt und zählt fünf dazu. Das Ergebnis wird wieder dort gespeichert, wo das zweite Argument hinzeigt: einen Schritt vor der Anweisung `ADD #5, 1'. Der `SUB'-Befehl arbeitet genau gleich, ausser dass anstelle einer Addition eine Subtraktion erfolgt: Das erste Argument wird vom Zweiten abgezogen. --titelb 2.4.8 JMP: Unbedingter Sprung Wenn der Programmzeiger eines Kampfprogrammes zu einer `JMP'-Anweisung gelangt, wird der Wert des Argumentes dieser Anweisung einfach zum Wert des Programmzeigers hinzugezählt, als Beispiel: --FF00 (5632) MOV #0, 100 (5633) JMP -1 --FF21 Der Programmzeiger zeigt auf die Anweisung in der Speicherzelle Nr. 5632 und der `MOV'-Befehl wird ausgeführt. Jetzt wird der Programmzeiger um eins erhöht und zeigt dadurch auf die nächste Anweisung: `JMP -1'. Durch den JMP-Befehl wird das Argument dieses Befehls zum Programmzeiger addiert, also 5633+(-1)=5632. `JMP -1' bewirkt einen Sprung um eine Speicherzelle nach unten, und die Programmzeile mit der `MOV'-Anweisung wird erneut ausgeführt. Die zwei Befehle werden bis zum Ende des Kampfes wiederholt. Die Adressierung des `JMP'-Befehls ist eingeschränkt: Es ist nur möglich, die Argumente entweder direkt (Beispiel) oder indirekt anzugeben. Die unmittelbare Adressierung ist verboten, weil sonst an eine absolute Adresse im Speicherfeld gesprungen werden könnte. --titelc 2.4.8.1 Gnom --FF00 DAT -1 start ADD #5, -1 MOV #0, @-2 JMP -2 --FF21 `Gnom' ist ein sehr dummes, aber trotzdem gefährliches Programm. Es beschreibt über den ganzen Speicher hinweg jede fünfte Adresse mit einer `DAT 0'-Anweisung. Eine `DAT'-Anweisung ist nicht ausführbar, und so wird eine ins feindliche Programm geschriebene `DAT 0'-Zeile dieses zerstören. `Gnom' belege die absoluten Adressen 1 bis 4 (nächstes Listing). Die Ausführung des Programmes beginnt mit dem Befehl in Adresse 2: `ADD #5, -1'. Das Wort sy{start' im obigen Listing ist ein Schlüsselwort (Symbol) des Assemblers mit einer besonderen Bedeutung. Es darf in einem Programm nur einmal benützt werden und bestimmt den Startpunkt des Programmes, also die Zeile, die als erstes ausgeführt wird. Der erste Befehl `ADD #5, -1' bewirkt, dass der Wert 5 zum Inhalt der vorhergehenden Adresse, nämlich der Anweisung `DAT -1', addiert wird. Das Ergebnis ist ein `DAT 4' auf der Adresse 1. Als Nächstes führt `Gnom' den Befehl in der Adresse 3 aus: `MOV #0, @-2'. Der ummittelbare Wert 0 wird auf eine Zieladresse übertragen, die ihrerseits indirekt angegeben ist und so bestimmt wird: Zuerst zählt MARS von Adresse 3 zwei Adressen zurück (`@-2') und landet so bei Adresse 1. Der dort stehende Datenwert 4 wird als Adresse relativ zur gegenwärtigen Position interpretiert, so dass sich die Zieladresse 1+4=5 ergibt, in die eine `DAT 0'-Anweisung geschrieben wird. --FF00 (7995) * DAT 0 ; 1599. Durchlauf (7996) DAT 0 (7997) DAT 0 (7998) DAT 0 (7999) DAT 0 (0000) * DAT 0 ; 1600. Durchlauf (0001) DAT -1 (0002) ADD #5, -1 (0003) MOV #0, @-2 (0004) JMP -2 (0005) * DAT 0 ; erster Durchlauf (0006) DAT 0 (0007) DAT 0 (0008) DAT 0 (0009) DAT 0 (0010) * DAT 0 ; zweiter Durchlauf --FF21 Die letzte Anweisung von `Gnom', `JMP -2', erzeugt eine Endlosschleife. Sie verlagert die Ausführung zurück zur Adresse 2, an der das Programm begonnen wurde. Erneut wird der `DAT'-Befehl um 5 erhöht, so dass er den neuen Wert `DAT 9' annimmt. Im nächsten Ausführungszyklus wird demzufolge eine `DAT 0'-Anweisung in die Adresse 10 geschrieben. Die folgenden `DAT 0'-Anweisungen fallen dann auf die Adressen 15, 20, 25 und so weiter. Das Programm selbst ist unbeweglich, aber es beschreibt das ganze Speicherfeld. Nach 1598 Schleifendurchläufen schliesslich erreicht `Gnom' die Adressen 7990, 7995 und 8000. Für MARS ist 8000 gleichbedeutend mit 0, und so hat sich `Gnom' einmal durch den ganzen Speicher gearbeitet und beginnt wieder von vorne. Der Erfinder des Spiels drückt es so aus: `Es ist eine bittere Erkenntnis, dass kein stationäres Programm mit mehr als vier aufeinanderfolgnden Anweisungen einen Treffer von `Gnom' vermeiden kann'[1]. Ein gegnerisches Programm hat nur drei Möglichkeiten: Sich umherzubewegen, um so den `DAT 0'-Anweisungen auszuweichen, die Treffer hinzunehmen und den Schaden zu reparieren oder `Gnom' als erstes zu erwischen. Um mit der letzten Strategie durchzukommen, muss ein Programm schon Glück haben: Es hat ja keine Ahnung, wo im Speicherfeld `Gnom' sitzt, und dieser kann im Mittel 1600 Ausführungszyklen durchlaufen, ehe er einen Treffer hinnehmen muss. Ist das zweite Programm auch ein `Gnom', gewinnt jedes in etwa 30 Prozent der Fälle, in 40 Prozent der Fälle geht der Kampf unentschieden aus. Wenn `Gnom' gegen `Knirps' antritt, wandern die `DAT 0'-Anweisungen von `Gnom' schneller durch das Speicherfeld als sich `Knirps' vorwärtskopieren kann. Deswegen ist `Gnom' nicht unbedingt im Vorteil. Selbst wenn er `Knirps' mit seinen `DAT 0'-Anweisungen einholt, steht die Wahrscheinlichkeit, dass `Knirps' genau bei der Position seines Programmzeigers überschrieben wird, bei 1 zu fünf. Wenn `Knirps' umgekehrt `Gnom' zuerst erreicht, marschiert er höchstwahrscheinlich schnurstracks durch dessen Code hindurch. Verlagert `Gnoms' Anweisung `JMP -2' die Ausführung dann zwei Schritte zurück, so steht dort eine der `MOV 0, 1'-Anweisungen von `Knirps'. Folglich wird `Gnom' `umgedreht' und in ein zweites `Knirps'-Programm verwandelt, das dem ersten endlos im Speicherfeld hinterher läuft. Nach den Regeln von CoreWars wird das Spiel nach einer bestimmten Anzahl von Ausführungszyklen abgebrochen und endet in diesem Fall unentschieden. Es ist noch zu beachten, dass es sich hierbei um das höchstwahrscheinliche Resultat handelt. Es gibt ausser den beiden besprochenen eine weitere Möglichkeit des Verlaufes eines Kampfes zwischen `Gnom' und `Knirps', die allerdings relativ selten auftritt: `Knirps' kann sich auch in einen `Gnom' verwandeln. `Knirps' will den stillstehenden `Gnom' von unten her überrollen und kopiert sich auf die erste Zeile `DAT' `adr' von `Gnom' (`adr' ist eine beliebige Adresse). Im Speicherfeld besteht nun die folgende Situation, wenn die erste Anweisung von `Gnom' zu Beginn des Kampfes auf Adresse 1 steht: --FF00 (0000) MOV 0, 1 (0001) MOV 0, 1 ; Inhalt vor Ueberschreiben: DAT `adr' (0002) ADD #5, -1 (0003) MOV #0, @-2 (0004) JMP -2 (0005) DAT 0 --FF21 Wenn `Gnom' genau zu diesem Zeitpunkt die Anweisung auf Adresse 2 ausführt, wird der Wert 5 zum Befehl `MOV 0, 1' addiert, was zur Anweisung `MOV 5, 6' in Adresse 1 führt. Als Nächstes will `Knirps' diese Anweisung ausführen und kopiert so die Anweisung `DAT 0' aus Adresse 1+5=6 um eine Speicherzellen nach oben auf die Adresse 1+6=7 und somit drei Zellen ausserhalb von `Gnom', der deshalb nicht mehr von `Knirps' überschrieben werden kann. Nachdem `Gnom' seine nächste Zeile `MOV #0, @-2' ausgeführt und damit eine `DAT 0'-Anweisung in die Adresse 1+5=6 kopiert hat, geht `Knirps' einen Schritt weiter und befindet sich auf der `ADD #5, -1'-Anweisung von `Gnom'. Die Programmzeiger des ursprünglichen `Gnom' und des zu einem `Gnom' verwandelten `Knirps' verfolgen sich nun bis zum Ende des Kampfes im Code des alten `Gnom', allerdings kommen die kopierten `DAT 0'-Anweisungen nun um zwei Zellen verschoben zu liegen: Nachdem im gesamten Speicher durch die beiden Programmzeiger im Code von `Gnom' noch einmal jede 5. Speicherzelle mit `DAT 0'-Anweisungen beschrieben wurde, überschreibt `Knirps' zuerst mit einer `DAT 0'-Anweisung den alten Code von `Gnom', so dass der Programmzeiger von `Gnom' schliesslich als erster eine `DAT 0'-Anweisung auszuführen versucht und so zum Verlierer wird. MARS ist ein diskretes, deterministisches und in sich geschlossenes System. Deshalb wird der Ausgang eines Kampfes zwischen zwei Programmen schon zu Beginn einerseits durch die Programme selbst und andererseits durch die anfängliche Position der Programme im Speicherfeld entschieden. Um den obigen Ausgang des Kampfes zwischen `Gnom' und `Knirps' zu erhalten, muss der Abstand der Programmzeiger beim Einlesen der beiden Programme nach Abzug einer Konstante ohne Rest durch drei teilbar sein. Diese Forderung ergibt sich, weil der Programmzeiger von `Knirps' genau dann die Adresse 1 überschreiben muss, wenn sich der Programmzeiger von `Gnom' auf Adresse 4 befindet und die Anweisung `JMP -2' ausführt. Die erwähnte Konstante ist davon abhängig, mit welchem der beiden Programme der Kampf begonnen wird: `Gnom' beginnt: Der Abstand der beiden Programmzeiger muss nach der Ausführung der ersten Anweisung von `Gnom' durch drei teilbar sein. Für den ursprünglichen Abstand d0 ergibt sich also d0 = 3n+1, wobei 0 <= n <= 2666. `Knirps' beginnt: Der Abstand der Programmzeiger muss nach der Aus- führung der ersten Anweisung von `Knirps' durch drei teilbar sein. Für den ursprünglichen Abstand d1 ergibt sich also d1 = 3n-1, wobei 0 <= n <= 2666. --titelb 2.4.9 JMZ und JMN: Bedingter Sprung Die Befehle `JMZ' und `JMN' verändern wie der `JMP'-Befehl den Programmzeiger und erlauben es zusätzlich, die Veränderung von gewissen Bedingungen abhängig zu machen. `JMZ' und `JMN' überprüfen zuerst die Speicherzelle, die durch das zweite Argument bestimmt wird, und entscheiden dann aufgrund deren Inhalte, ob gesprungen wird oder nicht. JMZ a, b: Die Ausführung des Programmes wird an der durch Argument a bestimmten Adresse fortgesetzt, wenn der Inhalt der durch Argument b bestimmten Adresse `gleich Null' ist.' JMN a, b: Die Ausführung des Programmes wird an der durch Argument a bestimmten Adresse fortgesetzt, wenn der Inhalt der durch Argument b bestimmten Adresse `ungleich Null' ist. Wenn die Bedingung zum Sprung nicht erfüllt wird, wird der Programmzeiger normal um eins erhöht, so dass beim nächsten Zyklus die der `JMZ'- oder `JMN'-Anweisung folgende Zeile des Programmes ausgeführt wird.' Mit Hilfe dieser Befehle können Schleifen programmiert werden, die nur eine bestimmte Anzahl von Durchgängen aufweisen. Dazu wird eine Speicherzelle mit einem `DAT'-Befehl als Zähler verwendet und bei jedem Schleifendurchlauf durch die SUB- oder die ADD-Anweisung vermindert bzw. erhöht. Der anschliessende `JMZ'- oder `JMN'-Befehl überprüft zuerst die Speicherzelle mit dem Zähler und springt nur an den Anfang der Schleife zurück oder aus der Schleife hinaus, wenn der Zähler die gestellte Bedingung erfüllt. Zur Veranschaulichung sollen Beispiele für zwei verschiedene Schleifen- konstruktionen dienen: --titelchen a) Konstruktion mit JMZ: --FF00 zähler DAT 10 start ... ; Beliebige Anzahl Befehle SUB #1, zähler JMZ break, zähler ... ; Beliebige Anzahl Befehle JMP start break ... ; Beliebige Anzahl Befehle --FF21 --titelchen b) Konstruktion mit JMN: --FF00 zähler DAT 10 start ... ; Beliebige Anzahl Befehle SUB #1, zähler JMN start, zähler --FF21 --titelb 2.4.10 DJN: Bedingter Sprung mit Dekrementierung `DJN' entspricht dem Befehl `JMN', ausser dass ersterer den Inhalt der durch das Argument B bestimmten Adresse vor dem Vergleichen um eins dekrementiert. `DJN' wird verwendet, um Schleifenkonstruktionen wie unter `a)' und `b)' mit weniger Befehlen auskommen zu lassen: --titelchen c) Konstruktion mit DJN: --FF00 zähler DAT 10 start ... ; Beliebige Anzahl Befehle DJN start, zähler --FF21 --titelb 2.4.11 CMP: Vergleichen Mit `CMP' lassen sich zwei beliebige Speicherzellen miteinander vergleichen und aufgrund des Ergebnisses bestimmte Massnahmen ergreifen. Der Inhalt des ersten Argumentes wird mit den Inhalt des zweiten Argumentes verglichen. Wenn beide Werte gleich sind, wird die Anweisung, die sofort nach dem CMP-Befehl folgt, übersprungen und die Ausführung des Programmes erst bei der übernächsten Zeile fortgesetzt. Wenn die Werte ungleich sind, wird die unmittelbar folgende Anweisung bearbeitet. Die Relation `gleich' oder `ungleich' hängt von der Adressierungsart des `CMP'-Befehls ab. Sobald eines der Argumente von `CMP' unmittelbar adressiert ist und das andere Argument auf eine vollständige Speicherzelle weist, wird nur das Argument B dieser Zelle mit dem durch die absolute Zahl gegebenen Wert verglichen. Wenn beide Argumente von `CMP' auf eine Speicherzelle weisen, wird der vollständige Inhalt beider Zellen verglichen: Befehle, Argumente und Adressierungsarten müssen jeweils gleich sein, damit der Vergleich wahr ist und die nächste Programmzeile übersprungen wird. Das folgende Programm benützt den `CMP'-Befehl, um sich 100 Zeilen nach vorne zu kopieren und die Ausführung dann in die Kopie von sich selbst zu verlagern. --titelc 2.4.11.1 Zwillinge --FF00 quelle DAT 0 ziel DAT 99 start MOV @quelle, @ziel ADD #1, quelle ADD #1, ziel CMP quelle, #9 JMP start MOV #99, ziel + 100 JMP start + 100 --FF21 Zwei `DAT'-Anweisungen zu Beginn des Programmes dienen als Zeiger: Sie geben an, welche Anweisung (`quelle') als Nächstes an welche Stelle im Speicherfeld (`ziel') kopiert werden soll. Den eigentlichen Kopiervorgang übernimmt eine Scheife in der Mitte des Programmes; sie kopiert jeden Befehl zu einer entsprechenden Adresse 100 Zeilen vor der aktuellen Position des kopierten Befehls. Bei jedem Durchlauf der Schleife werden beide Zeiger um eins erhöht, sie bezeichnen dann eine neue Quell- und Zieladresse. Ein Vergleichsbefehl innerhalb der Schleife prüft den Wert der `DAT'-Anweisung `quelle'. Sobald sie neun Mal (Anzahl zu kopierende Programmzeilen) erhöht wurde, ist das ganze Programm kopiert, und der Befehl `JMP start' wird übersprungen. Weil die `DAT'-Anweisung `ziel' als zweite kopiert wurde und deshalb beim Kopieren bereits den Wert 100 hatte, muss dieser durch den Befehl `MOV #99, ziel+100' wieder auf 99 zurückgesetzt werden. Schliesslich wird mit der `JMP'-Anweisung der Programmzeiger auf die erste ausführbare Zeile des neuen Programmes gesetzt. Im dritten Kapitel befindet sich ein weiteres Beispiel für ein Programm, das sich selbst kopiert. Es ist kürzer und kann viel schneller ausgeführt werden als Zwillinge, weil es zur Kontrolle der Schleife anstelle der `CMP'- die `DJN'-Anweisung verwendet. --titelb 2.4.12 SPL: Aufspalten von Programmen `SPL' (`to split', spalten) ermöglicht es, einen neuen Programmzeiger zu erzeugen. Wenn ein Programmzeiger auf eine `SPL'-Anweisung trifft, wird das Programm an zwei Stellen gleichzeitig weitergeführt: Das erste Mal mit der Zeile, die nach der `SPL'-Anweisung folgt und das zweite Mal an der Adresse, die durch das Argument der `SPL'-Anweisung bestimmt wird. --FF00 (0143) ... ; x (0144) ... ; x (0145) SPL 2 ; x, y=x+2 (0146) JMP 101 ; x (0147) ... ; y --FF21 Der Programmzeiger `x' weise auf Zeile 145. Bei der Ausführung der `SPL'-Anweisung wird ein neuer Programmzeiger `y' mit dem Wert y=x+2 erzeugt, der das Programm ab Zeile 147 ausführen wird. Der ursprüngliche Programmzeiger `x' bleibt bestehen und führt die dem `SPL'-Befehl folgende Zeile aus. Der Vorgang des Aufspaltens eines Programmzeigers kann für eine Partei bis zu 63 mal wiederholt werden, weil die höchste Anzahl erlaubter Programmzeiger für eine Partei nach [6] 64 beträgt. --titelc 2.4.12.1 Ausführung mehrerer Programme Durch den `SPL'-Befehl erhalten die Redcode-Programmierer die Möglichkeit, mehrere Programme gleichzeitig für dieselbe Partei laufen zu lassen. Weil MARS die Programme nur Schritt für Schritt und nicht wirklich gleichzeitig abarbeiten kann, muss ein Mechanismus existieren, nach dem die Rechenzeit unter den abgespalteten Programmen verteilt wird. Zur Partei `A' sollen die Programme `A_1', `A_2' und `A_3' gehören, zur Partei `B' `B_1' und `B_2'. Eine Möglichkeit wäre, erst alle Programme des einen und dann die des anderen Spielers laufen zu lassen. Es würde also jeweils eine Zeile von `A_1', `A_2', `A_3' und dann von B_1 und B_2 ausgeführt. Dieser Zyklus würde sich bis zum Ende des Kampfes wiederholen. Bei einer weiteren Möglichkeit wechseln sich die Programme der Spieler nach jeder ausgeführten Zeile eines der Programme der jeweiligen Partei ab: Die Reihenfolge wäre dann `A_1', `B_1', `A_2', `B_2', `A_3', `B_1' und so weiter. Beide Mechanismen wirken sich unterschiedlich aus. Die erste Methode begünstigt den, der möglichst viele Programme ins Spiel bringt, was der Entwicklung intelligenter Strategien kaum förderlich ist. Die Zweite dagegen bedeutet, dass ein einzelnes Programm der Partei A (drei Zeiger) im Vergleich zu einem Programm der Partei B (zwei Zeiger) `langsamer' ausgeführt wird. Weil sich der Vorteil einer ungehemmten Programmvermehrung auf diese Weise schnell aufhebt, gab der Erfinder von MARS diesem Verfahren den Vorzug. Ziel des Spieles ist es jetzt, alle feindlichen Programmzeiger zum `Absturz' durch nicht ausführbare Anweisungen zu bringen. Es folgen zwei Beispiele zur Anwendung des `SPL'-Befehls: --titelc 2.4.12.2 Knirps-Kanone --FF00 start SPL 2 JMP -1 MOV 0, 1 --FF21 Die Anweisung `SPL 2' bewirkt, dass nach dieser Anweisung die Zeilen `JMP -1' und `MOV 0, 1' (quasi-)gleichzeitig ausgeführt werden. Die erste Instruktion bewirkt einen Rücksprung zur `SPL'-Anweisung, während die zweite einen Knirps in Marsch setzt. Dieser wälzt sich stur durch den Speicher, denn die Zieladresse der MOV-Anweisung ist stets die jeweils nächste Adresse. Mit jedem zweiten Programmzyklus wird also ein neuer Knirps erzeugt, und schliesslich bewegen sich 63 Knirpse durch den Speicher. Einer solchen Menge von Knirpsen scheint kein Programm gewachsen zu sein, doch der Schein trügt: --titelc 2.4.12.3 Knirpsgrab --FF00 falle MOV #0, -1 JMP -1 ... start SPL falle ... --FF21 Dies ist wiederum ein Programmfragment, das zu einem beliebigen grösseren Programm gehören kann, das seine untere Flanke vor `Knirpsen' schützen will. Diese zwei Zeilen namens `Knirpsgrab' (auch `Knirpsfalle' genannt) werden durch eine SPL-Anweisung innerhalb des grossen Programmes einmal gestartet und arbeiten danach ohne weitere Unterstützung des grossen Programmes weiter. Bei jeder Ausführung schreibt `Knirpsgrab' eine `DAT 0'-Anweisung direkt unter sich, um einen von unten herannahenden `Knirps' damit zu treffen. Die Wahrscheinlichkeit, dass ein ankommender `Knirps' überschrieben wird, ist um so grösser, je langsamer die `Knirpse' ankommen. Wenn z. B. `KnirpsKanone' `n' `Knirpse' erzeugt hat und diese zur Partei A gehören und `Knirpsgrab' zur Partei B, dann braucht A `n' Ausführungszyklen, um `n' `Knirpse' eine Zeile nach vorn zu bewegen. `Knirpsgrab' kann demnach bis zu `n' mal ausgeführt werden, bis ein neuer `Knirps' überschrieben wird. Das Blatt beginnt sich zu wenden, wenn nur ein `Knirps' für A im Spiel ist und wenn B zusätzlich in einem Hauptprogramm einen Programmzeiger verwendet: Pro Zyklus des `Knirpsgrabes' kann sich der `Knirps' zwei Speicherzellen nach oben kopieren, die Wahrscheinlichkeit eines Treffers für `Knirpsgrab' ist demnach 1:2.