Kapitel 1: Das Spiel ____________________ 1.1 Einleitung ______________ Wohl Niemand könnte das Prinzip des vorliegenden Computerspiels besser in Worte fassen als sein Erfinder, A. K. Dewdney: "Zwei Computerprogramme in ihrer natürlichen Umgebung - dem Speicherchip eines Computers - beschleichen einander von Adresse zu Adresse. Manchmal sind sie darauf aus, den Gegner auszukundschaften. Manchmal legen sie einen Sperrgürtel aus numerischen Bomben, und manchmal kopieren sie sich selbst aus der Gafahrenzone oder halten inne, um Schäden zu beheben. Das ist das Spiel, das ich "Krieg der Sterne" nennen möchte. Es ist ganz anders als die meisten anderen Computerspiele, denn Menschen spielen hier überhaupt nicht mehr mit. Zwar sind die rivalisierenden Programme natürlich von irgendwem geschrieben; aber sobald der Kampf einmal tobt, kann der Erfinder eines Programmes nur mehr hilflos abwarten, ob das Produkt stundenlangen überlegens und Programmierens siegt oder untergeht. Den Ausgang der Schlacht entscheidet allein, welches Programm an einer empfindlichen Stelle getroffen wird. " (Zitat aus: Artikel "Computer Kurzweil", Spektrum der Wissenschaft, August 1984, von A. K. Dewdney) 1.2 Einige Begriffe ___________________ Im Verlauf dieser Anleitung werden wir wiederholt einige Fachbegriffe verwenden, um etwas zu erklären. Es wäre zwar an einigen Stellen möglich gewesen, die englischen Wörter durch deutsche Synonyme zu ersetzen, doch Sie werden sehen, dass eine Bezeichnung wie "Assembler" (von englisch to assemble, = montieren) nach kurzer Zeit weniger verwirrend ist als das deutsche Wort "Uebersetzer". Sie finden übrigens im Anhang B der Anleitung eine alphabetisch geordnete Aufstellung der im Text verwendeten Fachbegriffe. 1.2.1 Programm -------------- Ein Programm ist eine Folge von Anweisungen, die einem beliebigen ausführenden Objekt eine bestimmte Handlungsweise vorschreiben. Ein Fernsehprogramm ist daher im Prinzip das gleiche wie ein Programm einer Waschmaschine oder eines Computers. Der grösste Unterschied dieser drei Beispiele besteht in der Art und Weise der Formulierung des Programmes. Das Fernsehprogramm ist durch Zeitangaben und Namen der Sendungen, die zu diesen Zeiten ausgestrahlt werden, formuliert. Das Programm einer älteren Waschmaschine ist durch die Form der Steuerscheibe bestimmt, einer Metallscheibe, die an bestimmten Stellen Einbuchtungen oder Erhebungen besitzt und die sich langsam um die eigene Achse dreht. Ein Fühler berührt den Rand der Scheibe und löst je nach dem augenblicklichen Stand der Scheibe bestimmte Kontake aus, die z.B. Wasser einlassen oder die Trommel in Bewegung setzen. Bei einer moderneren Maschine übernimmt bereits ein Computer die Aufgabe der Steuerscheibe. Somit wären wir beim dritten Beispiel angelangt, beim Computer. Sehen wir uns als erstes ein Textverarbeitungsprogramm an. Es besteht aus nichts anderem als Anweisungen in einer besonderen, dem Computer verständlichen Sprache. Duch diese Sprache weiss der Computer immer, was er als nächstes tun soll: Eingaben von der Tastatur annehmen, einen Text ausdrucken, auf eine bestimmte Aktion des Benutzers einen Buchstaben des Textes löschen. Der grosse Vorteil des Computers gegenüber einer alten Waschmaschine, die durch die Steuerkurve "programmiert" wurde, ist nun, dass anstelle eines Textverarbeitungsprogrammes genausogut ein beliebiges anderes Programm hätte laufen können. Man sagt, der Computer sei "frei programmierbar". 1.2.2 Hardware und Software --------------------------- Dies sind die Bezeichnungen für einen Computer, auf dem ein Programm laufen kann und für das Programm, das auf dem Computer oder eben der Hardware läuft. Software kann aber auch aus Text oder aus Daten für eine Datenbank bestehen. Generell kann man beliebige Informationen als Software bezeichnen. Genauso gilt als Hardware nicht nur ein Computer, sondern auch die Zusatzgeräte rund um einen Computer, wie Diskettenlaufwerke, Monitoren, oder Speicherkarten. 1.2.3 Programmiersprache ------------------------ Eine Programmiersprache ist die Art und Weise, einem Computer eine Folge von Anweisungen beizubringen. Diese Sprachen können entweder nur aus Zahlen oder aus einer Mischung von verständlichen Wörtern und Zahlen bestehen. Man unterscheidet hier vor allem zwischen zwei verschiedenen Sprachgruppen: Maschinensprachen und Hochsprachen. Eine Hochsprache ist auf den Menschen zugeschnitten und daher leichter zu lernen und zu bedienen. Eine Maschinensprache ist hingegen auf einen Computer zugeschnitten, und das bedingt die Anpassung des Menschen an die Maschine. Maschinensprache ist schnell, aber schwieriger zu erlernen. Grosse Softwareprojekte werden in der Regel in Hochsprachen erstellt. 1.2.4 Interpreter ----------------- Ein Interpreter liest jeweils aus einem Programm in der Hochsprache den nächsten einzelnen Befehl, übersetzt ihn in die Maschinensprache und führt diese dann aus. Wenn in einem Programm ein solcher Befehl mehrmals vorkommt, muss er jedesmal neu übersetzt werden, was natürlich viel Zeit kostet. Basic ist das Standardbeispiel für eine Sprache, die während der Ausführung übersetzt wird, und deshalb im Vergleich mit Compilersprachen sehr langsam ist. Weil zudem eine Anweisung in einer Hochsprache in mehrere Maschinensprachebefehle übersetzt werden muss, verstärkt sich die Verlangsmung der Interpretersprachen zusätzlich. 1.2.5 Compiler -------------- Der Compiler dagegen nimmt sich gleich zu Beginn das ganze Programm in der Hochsprache vor und übersetzt es in ein vollständiges Maschinenspracheprogramm. Er montiert sozusagen die einzelnen Befehle des Programmes zu einem grossen, dem Computer verständl ichen Maschinenspracheprogramm, das später in einem Stück läuft. Pascal oder C sind Beispiele für Sprachen, die vor dem Starten des Programmes zuerst vollständig in den Maschinencode übersetzt und erst dann vom Computer ausgeführt werden. Das bringt zwar schnelle Programme, auf der anderen Seite jedoch eine langwierige Fehlersuche. Wenn auch nur eine winzige Kleinigkeit eines solchen Programmes geändert werden soll, muss der Programmtext nach der Korrektur ganz oder teilweise neu übersetzt (= compiliert) werden. 1.2.6 Terminal -------------- Es handelt sich hier um einen Sammelbegriff für Geräte, die die Kommunikation zwischen Computer und Mensch möglich machen. Ein Terminal kann ein Monitor, ein Drucker oder eine Tastatur sein, aber auch ein Computer, der die Ein- und Ausgaben für ein gröss eres, vernetztes (zusammengeschaltetes) Computersystem übernimmt, sein. 1.2.7 Bit und Byte ------------------- Ein Bit ist die kleinste Speichereinheit eines Computers. Ein solches Bit kann nur zwei Zustände annehmen: 0 oder 1. In der Computertechnologie werden 8 dieser Bits zu einem Byte zusammengefasst. Diese 8 Bits können dann als Ganzes insgesamt 256 verschiedene Zustände annehmen. Diese Zustände werden durch die Zahlen von 0 bis 255 beschrieben. Der gesamte Speicher eines Computers besteht ausschliesslich aus sehr vielen einzelnen Bytes. Die Bytes werden von 0 beginnend durchnummeriert, das heisst, jedem einzelnen Byte im Speicher wird eine eigene Nummer oder Adresse zugewiesen. So kann auf ein beliebiges Byte im Speicher durch seine Adresse zugegriffen werden. Wenn man über Computer spricht, werden unweigerlich die Begriffe KByte oder MByte fallen. Ein KByte ist ganz einfach ein 'Kilobyte' (wird auch so ausgesprochen) und bedeutet 1024 Bytes. Ein MByte ist dementsprechend ein 'Megabyte' und umfasst 1024 mal 1024 Bytes, also 1'048'576 Bytes. Die Gross- und Kleinschreibung bei diesen Begriffen muss beachtet werden: Ein kByte und ein mByte sind im Gegensatz zum KByte und zum MByte nur 1000, bzw. 1000000 Bytes gross. 1.2.8 Assembler --------------- Ein Assembler ist ein Programm, das einen Programmtext, der in einer Assemblersprache geschrieben wurde, in den ausführbaren Maschinencode übersetzt. Im Prinzip ist eine Assemblersprache und Maschinensprache genau dasselbe, der Unterschied besteht einzig und allein darin, dass eine Assemblersprache die Befehle der Maschinensprache anstatt in Zahlen in Buchstaben akzeptiert. Für jeden Befehl in Maschinensprache steht ein Wort, das die Bedeutung des Befehls ungefähr umschreibt. Da man sich Wörter viel leichter merken kann als nackte Zahlen, wird Maschinensprache heutzutage vorwiegend mit Hilfe eines Assemblers programmiert. Es ist wichtig, dass man den Unterschied zwischen einem Assembler und einer Assemblersprache kennt: Der Assembler übersetzt die Assemblersprache in den entsprechenden Maschinencode. 1.2.9 Simulation ---------------- Der Computer eignet sich hervorragend dazu, Teile der wirklichen Welt im Zahlenmodell nachzuspielen oder zu simulieren. Alles kann simuliert werden; der Erfolg der Simulation hängt wesentlich davon ab, wie gut der Programmierer das zu simulierende Problem verstanden hat. Da die Ereignisse in der Realität oft von Tausenden von Variablen abhängen, sind viele Dinge nur schwer wirkungsvoll zu simulieren. Es gibt aber etliche andere Probleme, die sehr gut simuliert werden können. Aus zwei Gründen sind Simulationen wichtig: Einmal kann man die Parameter eines Problems variieren und ihren Einfluss auf das Ergebnis beobachten, auch wenn derartige Experimente im wirklichen Leben zu kostspielig oder zu gefährlich sind. Zum zweiten kann man in der Simulation Situationen schaffen, die in der Realität nicht auftreten können. 1.2.10 Emulator --------------- Ein Emulator oder eine Emulation ist ein Sonderfall einer Simulation. Wenn auf einem beliebigen Computer das Verhalten eines anderen Computers simuliert wird, so spricht man von einer Emulation. Für den Amiga gibt es eine IBM-PC Emulation, die rein softwaremässig funktioniert. Erfahrungsgemäss sind Simulationen eines anderen Computers nicht so leistungfähig wie das Vorbild, doch meistens reicht es, um einfache Anwendungen ausführen zu können. Eine hardwaremässige Emulation eines IBM-PC ist mit dem Sidecar möglich, einem Zusatzgerät, das an den Amiga 1000 angeschlossen wird und ihn in einen PC umwandelt. Das später in dieser Anleitung noch genau erklärte Computersystem MARS wird auf dem Amiga nur durch Software emuliert. 1.3 Geschichte ______________ Alles begann mit einem Artikel in der Ausgabe der Zeitschrift "Spektrum der Wissenschaft" vom April 1984. Der amerikanische Autor von Computer-Kurzweil, A. K. Dewdney stellte die Idee eines Spieles vor, in dem sich zwei Programme in einem Computer auf Leben und Tod bekämpfen. Der Sinn des Spieles war es, das gegnerische Programm aus dem Speicher zu löschen. Die Programme sollten in einer speziellen, nur für diesen Zweck entwickelten Sprache geschrieben sein. Diesem Artikel folgten in unregelmässigen Zeitabständen weitere über dasselbe Thema, und in diesen Artikeln erweiterte A. K. Dewdney seine Spielidee mit neuen Befehlen seiner Programmiersprache und neuen Gedanken zum Aufbau solcher Kampfprogramme. Mit der Zeit verbreitete sich das Spiel, und bald wurden erste Turniere ausgetragen. Schliesslich wurde die Internationale Gesellschaft für Core Wars gegründet und legte ein offizielles Regelwerk vor. Auf der Basis dieser "Core War Standards" wurde das hier vorgestellte Programm entwickelt. 1.4 Core Wars _____________ 1.4.1 Der Name "Core Wars" -------------------------- Der Name "Core Wars" bedeutet soviel wie "Krieg der Kerne". Diese Bezeichnung ist von einer veralteten Speicher-Technologie abgeleitet. In den fünfziger und sechziger Jahren wurden Computerspeicher aus Tausenden von kleinen, kreisrunden Ringen aus magnetischem Material aufgebaut, die an einem Netzwerk aus feinen Drähten aufgereiht waren. Jeder Ring (Durchmesser kleiner als 1 mm) konnte in zwei Richtungen magnetisiert werden, was den beiden Werten 0 und 1 eines Bits entsprach. Diese Datenspeicher wurden Kernspeicher genannt, weil die kleinen Ringe gewissermassen die Speicherkerne des Computers darstellten. Die Kernspeicher waren zu ihrer Zeit Wunder der Feinmechanik: Auf einer Fläche von etwa 50 mal 50 Millimetern konnten schon 4096 Bits oder 512 Bytes ge speichert werden. Doch das war vor etwa 20 Jahren: Heute finden auf der gleichen Fläche rund 15'000 mal mehr Informationen Platz! In der Computertechnologie wird die CPU mit dem Schreib-Lesespeicher (RAM) deshalb auch oft als Kern bezeichnet. 1.4.2 Die Sprache der Kampfprogramme ------------------------------------ Die beiden Kampfprogramme für den "Krieg der Kerne" sind in einer speziellen Sprache geschrieben, die zur Klasse der Assembler gehört: Der Redcode. Jede Anweisung eines Redcode-Programmes entspricht einem Befehl des imaginären Computers, in dem der Kampf stattfindet. Mit Assemblersprachen wird vergleichsweise wenig gearbeitet, weil die Programme schwieriger zu verstehen und zu ändern sind, als entspechende Programme in einer Hochsprache wie Pascal oder Basic. Es gibt allerdings einige Aufgaben, für die sich Assemblersprachen gut eignen. Wenn ein Programm so wenig Platz wie möglich belegen oder so schnell wie möglich laufen soll, wird es normalerweise in einer Assemblersprache geschrieben. In Assemblersprachen lassen sich überdies einige Dinge machen, die in einer höheren Programmiersprache so gut wie unmöglich sind. So kann man beispielsweise ein Assembler-Programm dazu bringen, seine eigenen Befehle abzuändern, sich selbst zu kopieren und die Ausführung an einer völlig anderen Stelle im Speicher fortzusetzen. 1.4.3 Das System "Core Wars" ---------------------------- Core Wars besteht aus vier Hauptbestandteilen: Ein Speicherfeld, in dem der Kampf stattfinden kann, die Assemblersprache Redcode, einen imaginären Computer namens MARS (eine Abkürzung von Memory Array Redcode Simulator und einem Satz von wettstreitenden Kampfprogrammen. 1.4.3.1 Das Speicherfeld Der Speicher, in dem sich die zwei Programme bekämpfen, besteht aus 8000 Adressen. Jede Adresse kann eine Redcode-Anweisung mit allen ihren Parametern aufnehmen. Das ganze Speicherfeld ist in sich geschlossen, es besteht aus einer Folge von Adressen von 0 bis 7999. Als nächste Adresse nach 7999 folgt wieder die Adresse 0. MARS reduziert alle Adressen, die grösser als 7999 sind, indem es den Rest nach der Division durch 8000 nimmt. Die Zahl 9378 wird also als Adresse 1378 interpretiert. Stellen Sie sich das Speicherfeld von MARS wie einen grossen Ring aus 8000 Segmenten vor. In jedem Segment findet eine Anweisung Platz, die dem Kampfprogramm sagt, was es als nächstes tun soll. 1.4.3.2 Redcode Die Assemblersprache Redcode, in der die Kampfprogramme geschrieben werden, besteht aus insgesamt 10 Befehlen. Jeder Befehl hat eine bestimmte Wirkung auf die Inhalte von beliebigen Adressen im Speicherfeld von Core Wars. Es gibt Anweisungen, den Inhalt von einer Adresse auf eine andere zu übertragen, die Inhalte arithmetisch zu verändern und innerhalb des Programmes vorwärts und rückwärts zu springen. Die Redcode-Befehle verlangen oft Argumente, die z. B. auf bestimmte Speicherzellen hinweisen oder Zahlenwerte für arithmetische Operationen darstellen. 1.4.3.3 MARS und Assembler MARS ist der Name des Systems, in dem die Kampfprogramme laufen können. MARS existiert nicht in Wirklichkeit, sondern wird durch das Programm "Core Wars" (nicht zu verwechseln mit Redcode) auf dem Amiga simuliert. Der Assembler übersetzt jede Anweisung eines Redcode-Kampfprogrammes in einen Zahlencode, der dann im Speicherfeld von MARS abgelegt wird. Jede Adresse im Speicherfeld kann eine solche Zahl aufnehmen, mit anderen Worten kann jede einzelne der 8000 Zellen des Speicherfeldes eine vollständige Redcode-Anweisung aufnehmen. Der Abschnitt 1.4 enthält die Redcode-Anweisungen mit ihren Bedeutungen. Die Buchstaben A und B stehen für die Argumente (Zahlenwerte), die die betreffende Anweisung benötigt. Der Redcode-Programmierer muss bei jeder Anweisung mindestens ein Argument angeben, meistens sogar deren zwei. 1.4.4 Die Grundregeln des Spiels -------------------------------- Zwei Kampfprogramme werden von MARS in das Speicherfeld auf zufällig ausgewählte Plätze geladen. Kein Programm weiss, wo im Speicher das andere sitzt. Die einzige Regel beim Auswählen der Plätze ist, dass zum Zeitpunkt des Kampfbeginns zwischen den Programmen mindestens 1000 freie Speicherzellen vorhanden sein müssen. Dann werden die Programme von MARS abwechslungsweise ausgeführt: Ein einzelner Befehl vom ersten Programm wird abgearbeitet, dann ein einzelner Befehl des zweiten Programmes, und so weiter. Was ein Kampfprogramm während des ihm zustehenden Ausführungszyklus tut, bleibt ganz dem Programmierer überlassen. Es ist natürlich bestrebt, das andere Programm zu zerstören, indem es dessen Befehle löscht. Möglich ist aber auch eine Defensivstrategie: Ein Programm kann sich darauf verlegen, erlittene Schäden zu reparieren oder auszuweichen, wenn es angegriffen wird. Die Schlacht endet, wenn MARS auf einen nicht ausführbaren Befehl stösst. Das Programm mit der fehlerhaften Anweisung - vermutlich eine vom Gegner gelöschte Speicherzelle - wird zum Verlierer erklärt. Diese letzte Regel ist hier vorerst etwas vereinfacht dargestellt worden, weil sich durch einen bestimmten Redcode-Befehl die Programmausführung auf mehrere, voneinander unabhängige Einzelprogramme verteilen lässt. Doch mehr dazu im nächsten Abschnitt, in dem die verschiedenen Redcode-Befehle erklärt werden. 1.5 Die Redcode-Befehle _______________________ 1.5.1 Die Befehlstabelle ------------------------ Anweisung Argumente Kurzbeschreibung MOV A, B Uebertragen: Uebertrage den Inhalt 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 Programmes um A Zellen weiter vorne oder weiter hinten im Speicher 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 Programmes 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 überspringe die nächste Anweisung, wenn sie ungleich sind; sonst führe die nächste Anweisung aus. SPL A 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 jedoch Zahlenwerte, auf die zugegriffen werden kann. 1.5.2 Die 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. Relokatibel bedeutet: Wenn ein Programm an verschiedenen Stellen im Speicher eines Computers ausführbar sein soll, so darf es keine absoluten, also mit festen Zahlenwerten angegebenen, Sprungadressen besitzen. Die relative Adressierung, die in Redcode-Programmen verwendet wird, berücksichtigt diesen Umstand. 1.5.3 Die Programmzeiger ------------------------ Um die Sprache der Kampfprogramme, Redcode, zu lernen, ist es wichtig, einen Begriff aus der Welt der Programme genau zu kennen: Der Programmzeiger. Eingangs des ersten 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 Zahlenbombe werfen, sich in Sicherheit bringen, 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 als letztes bearbeiteten Programmzeile. Um dies etwas plastischer darzustellen sehen Sie sich bitte das folgende Listing eines Kampfprogrammes an. Was die einzelnen Redcode-Befehle bedeuten, ist im Moment noch unwichtig, Sie sollten sich aber den Aufbau der Programmzeilen einprägen. (2340) MOV #0, -1 (2341) JMP $-1 (2342) SPL $-2 (2343) MOV $10, $113 (2344) SPL $112 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 dazu, die Bedeutung und Funktion von Programmzeigern zu erklären. 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 natürlich seinen eigenen Programmzeiger, der immer auf die als nächstes auszuführende Anweisung zeigt. Jetzt ist wieder unser Kampfprogramm an der Reihe: MARS schaut sich den Inhalt des Programmzeigers an, der zu unseren Kampfprogramm gehört. MARS findet dort 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 unseres Kampfprogrammes weist. Wieder kommt das gegnerische Programm zum Zuge, und so geht der Kampf weiter, bis alle Programme eines Spielers zerstört sind und nur noch Redcode-Programme des Gegners übriggeblieben sind. Das alles tönt auf den ersten Blick vielleicht etwas kompliziert, aber durch die Programmzeiger ist zu jedem Zeitpunkt klar definiert, welche Anweisung von welchem Programm als nächste auszuführen ist. Um die Arbeitsweise der Redcode-Anweisungen zu verstehen, ist es wichtig, dass Ihnen der Sinn und Zweck der Programmzeiger geläufig ist - der Programmzeiger ist in einem gewissen Sinne das Herz eines Kampfprogrammes. 1.5.4 Genaue Beschreibung der Redcode-Anweisungen ------------------------------------------------- Die nächsten Abschnitte enthalten zu jedem der zehn Befehle eine genaue Beschreibung seiner Wirkung auf den Programmzeiger und auf die Speicherzellen. Wir haben versucht, möglichst viele Befehle durch Beispiele zu erklären. Wir empfehlen Ihnen auch, diese Beispiele laufend durch das Core Wars Programm auszuprobieren und sie gegeneinander kämpfen zu lassen. Wenn Sie eine Idee für ein eigenes Testprogramm haben, sollten Sie dieses ebenfalls sofort ausprobieren. Um mit dem Programm Core Wars zurechtzukommen, können Sie in Kapitel 2 unter "Eine erste Anwendung" nachschlagen. Dort wird anhand eines Beispiels gezeigt, wie Sie ein Kampfprogramm assemblieren und eine Schlacht zwischen zwei Redcode-Programmen starten und am Bildschirm betrachten können. Denn auch für MARS und die Sprache Redcode gilt die alte Wahrheit, dass man Programmieren nur durch Programmieren lernt! 1.5.5 DAT - Daten ----------------- Die wichtigste Eigenschaft dieser Anweisung ist, das sie eigentlich gar keine richtige Anweisung darstellt. Wenn nämlich der Programmzeiger eines Kampfprogrammes auf eine Zeile mit einem DAT-Befehl stösst, wird der Programmzeiger sofort gelöscht und das betreffende Programm "stirbt". DAT-Befehle können jedoch auch Bestandteile eines Programmes sein, sie können Informationen enthalten, die das Programm während seiner Ausführung benötigt. Hier 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 Munition dienen: Mit dem MOV-Befehl, der gleich erklärt wird, kann diese Zeile "DAT 0" einer Granate gleich in beliebige Speicherzellen im ganzen Speicherfeld geworfen werden. Wenn durch eine solche DAT-Granate ein gegnerisches Programm getroffen wird, und der gegnerische Programmzeiger arglos die mit dem DAT-Treffer zerstörte Programmzeile ausführen will, ist es um ihn geschehen. Wir werden im Laufe der Erklärungen der anderen Redcode- Anweisungen weitere Anwendungsmöglichkeiten des DAT-Befehls anhand von Beispielen aufzeigen. 1.5.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 move, bewegen) sagt MARS, drei Schritte oder Adressen im Speicherfeld vorwärts zu gehen und was es dort findet, 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 nackte Zahl als Argument vorfindet, wird sie als direkt angesehen, und das Gleiche gilt für eine Zahl mit vorangehendem $-Zeichen. Sie werden es bereits vermutet haben: Es gibt noch weitere Arten, wie ein Argument interpretiert werden kann. Erlaubt sind zwei zusätzliche Adressierungsmodi. Wenn einem Argument ein @-Zeichen voran geht (wir nennen dieses Zeichen seit jeher den "Klammeraffen"), 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. Um dies etwas klarer darzustellen, haben wir hier ein Beispiel für die indirekte Adressierung: (2719) MOV @3, 100 (2720) JMP -5 (2721) DAT 50 (2722) DAT -1 (2723) - - - (2818) - (2819) MOV 1, 10 (2820) - Verfolgen wir nun Schritt für Schritt, was geschieht, wenn die Zeile Nummer 2719 ausgeführt wird. Dort steht der Befehl "MOV", es sollen also Daten zwischen zwei Speicherzellen übertragen werden. Das erste Argument "@3" ist durch den Klammeraffen 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', und dieser Wert wird nun als direkt interpretiert. Das bedeutet, dass wir von Zeile 2722 genau eine Zelle nach hinten gehen müssen, um den wirklichen Wert zu finden, der durch den MOV-Befehl übertragen werden soll. Wir stehen deshalb vor der Zeile $2722-1=2721$, und in dieser Zeile steht die Anweisung "DAT 50". Das zweite Argument unserer MOV-Anweisung ist 100 und wird, da keine spezielle Adressierungsart angegeben wird, als direkt interpretiert. Das Ziel der Verschiebung liegt dadurch 100 Zeilen weiter, 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 "DAT 50" überschrieben. Um anschliessend nicht auf die DAT-Anweisung aufzulaufen und zu sterben, springt der Programmzähler fünf Schritte zu rück, um dort andere Anweisungen zu bearbeiten. Bisher haben Sie die Adressierungsmodi "direkt" mit dem $-Zeichen und "indirekt" mit dem @-Zeichen kennengelernt. Die angekündigte dritte Möglichkeit, ein Argument zu schreiben, ist unmittelbar. Diese Adressierungsart wird mit dem #-Zeichen eingeleitet und ist eigentlich ganz leicht zu verstehen: "MOV #3, 100" überträgt ganz einfach den Zahlenwert 3 auf die Speicherzelle, die 100 Zeilen weiter vorne im Speicher steht. "#" bedeutet, dass die folgende Zahl unverändert übernommen wird. Es ist bei der Anwendung der unmittelbaren Adressierung allerdings 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 absoluter Wert in eine mit ihrer Zeilennummer angegebenen Speicherzelle übertragen werden kann - und dies ist nicht gestattet, weil ein Redcode-Kampfprogramm keine Möglichkeit haben darf, seine eigene Position im Speicher zu erfahren. Das gleiche 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 graue Theorie in ein erstes Beispielprogramm umzusetzen. 1.5.6.1 Knirps MOV 0, 1 Knirps ist das einfachste Beispiel eines Redcode-Programmes, das sich selbst im Speicherfeld fortpflanzen kann. Es kopiert den Inhalt der relativen Adresse 0 in die relative Adresse 1, also die nächstfolgende Adresse. Es stellt sich nun die Frage, was in der relativen Adresse 0 überhaupt enthalten ist - dort steht nichts anderes als die Anweisung "MOV 0, 1" selbst! Knirps kopiert sich deshalb bei jeder Ausführung in die nächstfolgende Speicherzelle. Der Programmzeiger wird nach dem Ausführen des Befehls 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 die Seele eines anderen Programmes stehlen: nämlich dessen Programmzeiger. Wie es das macht, ist leicht einzusehen. 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 rast von hinten über ein vor ihm liegendes Programm und überschreibt den Code des Gegners mit einer unendlichen Folge von "MOV 0, 1" - Befehlen. 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. Es segelt zwar noch unter der alten Flagge, ist aber fortan dazu verdammt, dem feindlichen Knirps bis ans Ende der Schlacht willig hinterherzustapfen. Spätestens An dieser Stelle sollten Sie einen kleinen Abstecher in Kapitel zwei machen und sich dort die Einführung zu Core Wars ansehen. Sie können dann das eben kennengelernte Kampfprogramm Knirps gegen eines der auf der Programmdiskette enthaltenen grösseren Kampfprogramme spielen lassen. 1.5.7 ADD und SUB ----------------- Diese beiden Anweisungen addieren (ADD) oder subtrahieren (SUB) die Inhalte von zwei Speicherzellen, die durch die beiden Argumente bestimmt werden. Das erste Argument kann unmittelbar, direkt oder indirekt angegeben werden, das zweite darf aber 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 einen Schritt weiter vorne liegt und zählt 5 dazu. Das Ergebnis wird dann wieder dort gespeichert, wo das zweite Argument hinzeigt: einen Schritt vor unserer Anweisung. "SUB" arbeitet genau gleich, ausser dass anstelle einer Addition eine Subtraktion erfolgt: Das erste Argument wird vom zweiten abgezogen. 1.5.8 Der allgemeine Sprungbefehl JMP ------------------------------------- Wenn der Programmzeiger eines Kampfprogrammes zu einer JMP-Anweisung gelangt, wird das Argument dieser Anweisung einfach zum Wert des Programmzeigers hinzugezählt. Wieder ein Beispiel: (5632) MOV #0, 100 (5633) JMP -1 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 hier einen Sprung um eine Speicherzelle nach hinten, und die Programmzeile mit der MOV-Anweisung wird erneut ausgeführt. Diese zwei Zeilen stellen eine sogenannte "ewige Schleife" dar. Sie kann nicht mehr verlassen werden und die zwei Befehle werden ausgeführt, bis der Kampf entschieden ist oder vom Benützer abgebrochen wird. Die Adressierung des JMP-Befehls ist wieder etwas eingeschränkt: Es ist nur möglich, die Argumente entweder direkt (wie in unserem Beispiel) oder indirekt anzugeben. Die unmittelbare Adressierung ist hier verboten, weil sonst an eine absolute Adresse im Speicherfeld gesprungen werden könnte. Sie kennen nun genug Befehle, um das erste grössere Kampfprogramm in seiner Funktion und in seinem Aufbau zu verstehen. Wenn sie die folgende Beschreibung durchgelesen haben, können Sie ruhig eine kleine Verschnaufpause einlegen und Gnom nach Ihren Wünschen abändern. Natürlich können und sollen Sie Ihre Kreationen immer wieder mit dem Programm "Core Wars" ausprobieren. 1.5.8.1 Gnom DAT -1 start ADD #5, -1 MOV #0, @-2 JMP -2 Gnom ist ein sehr dummes, aber auch sehr gefährliches Programm. Es bombardiert über den ganzen Speicher hinweg jede fünfte Adresse mit einer Null. Null ist die Zahl, die eine nichtausführbare DAT-Anweisung anzeigt, und so kann eine ins feindliche Programm geschossene Null dieses lahmlegen. Angenommen, Gnom belegt die absoluten Adressen 1 bis 4. Unter Adresse 1 steht anfangs "DAT -1", aber die Ausführung des Programmes beginnt mit dem nächsten Befehl: "ADD #5, -1". Das Wort "start" anfangs der Zeile ist ein sogenanntes Schlüsselwort (Symbol). 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. Dieser erste Befehl "ADD #5, -1" bewirkt, dass eine 5 zum Inhalt der vorhergehenden Adresse, nämlich der Anweisung "DAT -1", addiert wird. Das Ergebnis ist ein "DAT 4" auf der Absolutadresse 1. Als nächstes führt Gnom den Befehl auf der Absolutadresse 3 aus: "MOV #0, @-2". Darin ist die 0 als unmittelbar spezifiziert. Sie wird folglich selbst auf eine Zieladresse übertrag en, die ihrerseits indirekt angegeben ist und sich folgendermassen errechnet: Zuerst zählt MARS von Adresse 3 zwei Adressen zurück und landet so bei Adresse 1. Sodann nimmt es den dort stehenden Datenwert, nämlich 4, und interpretiert ihn als Adresse relativ zur gegenwärtigen Position. Das heisst, es zählt von Adresse 1 aus 4 Adressen weiter und legt folglich eine 0 unter Adresse 5 ab. Die letzte Anweisung von Gnom, "JMP -2", erzeugt eine Endlosschleife. Sie verlagert die Ausführung zurück zur Absolutadresse 2. Erneut wird also der DAT-Befehl um 5 erhöht, so dass er den neuen Wert "DAT 9" annimmt. Im nächsten Ausführungszyklus wird dem zufolge eine 0 bei der Absolutadresse 10 eingespeichert. Die folgenden 0-Bomben fallen dann auf die Adressen 15, 20, 25 und so weiter. Das Programm selbst ist unbeweglich, aber seine Artillerie bedroht das ganze Speicherfeld. Nach etwa 1600 Zyklen schliesslich erreicht Gnom die Adressen 7990, 7995 und 8000. Für MARS ist 8000 gleichbedeutend mit 0, und so hat Gnom haarscharf am Selbstmord vorbeigesteuert und sein nächstes Geschoss landet wieder auf Adresse 5. "Es ist eine bittere Erkenntnis, dass kein stationäres Programm mit mehr als vier aufeinanderfolgnden Anweisungen einen Treffer von Gnom vermeiden kann." (Aus: Artikel "Computer Kurzweil, Spektrum der Wissenschaft, Ausgabe vom August 1984, von A. K. Dewdney) Ein gegnerisches Programm hat nur drei Möglichkeiten: Sich umherzubewegen, um so dem Bombardement 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, bei 40 Prozent der Zweikämpfe bekommt dagegen keines der Programme einen tödlichen Treffer ab. Rufen Sie sich nun das erste Beispiel, Knirps, wieder in Erinnerung. Was geschieht, wenn wir Knirps gegen Gnom antreten lassen? Probieren Sie den Zweikampf ruhig einige Male mit Core Wars aus und beobachten Sie im Grafikmodus, wie sich die beiden Programme im Speicher verhalten. Das Sperrfeuer von Nullen, das Gnom legt, wandert schneller durch das Speicherfeld als Knirps, aber deswegen ist Gnom nicht unbedingt im Vorteil. Selbst wenn er Knirps mit seinem Sperrfeuer einholt, muss er ihn ja erst treffen. Die Quote für einen tödlichen Treffer von Knirps steht etwa 1 zu fünf gegen die DAT-Bomben von Gnom. 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 die Zeile "MOV 0, 1" von Knirps. Folglich wird Gnom "umgedreht" und im ein zweites Knirps-Programm verwandelt, das dem ersten endlos dem Kreis des MARS-Speichers entlang nachjagt. Nach den Regeln von Core Wars wird das Spiel nach einer bestimmten Anzahl von Ausführungszyklen abgebrochen und endet in diesem Falle unentschieden. Es ist noch zu beachten, dass es sich hierbei um das höchstwahrscheinliche Resultat handelt. Vielleicht analysieren Sie selbst die anderen Möglichkeiten und entdecken darunter eine, die ein ganz verrücktes Ergebnis liefert - mehr wird nicht verraten. 1.5.9 Die bedingten Sprünge mit JMZ und JMN ------------------------------------------- Diese beiden Redcode-Befehle verändern wie der JMP-Befehl den Programmzeiger und erlauben es dadurch, innerhalb eines Programmes herumzuspringen. "JMZ" und "JMN" erweitern aber die Möglichkeiten des einfachen JMP-Befehls: Sie überprüfen zuerst die Speicherzelle, die durch das zweite Argument bestimmt wird, und entscheiden dann aufgrund ihres Inhalts, ob gesprungen wird oder nicht. "JMZ 3, -1" führt den Sprung nur aus, wenn der Inhalt der Speicherzelle, die eine Adresse weiter hinten im Speicher liegt, genau Null ist. Wenn der Inhalt grösser oder kleiner Null ist, wird ganz normal mit der nächsten Anweisung fortgefahren. Das erste Argument des Befehles gibt die Richtung und die Distanz des Sprunges gleich wie beim JMP-Befehl an. Der andere Befehl, "JMN", ist das Gegenteil von "JMZ": Der Sprung wird nur dann ausgeführt, wenn der Inhalt der durch das zweite Argument angegebenen Adresse ungleich Null ist. Durch diese beiden Befehle können anstelle von Endlosschleifen auch solche Schleifen programmiert werden, die nur eine von vornherein bestimmte Anzahl von Durchgängen ausgeführt wird. Dazu wird eine Speicherzelle mit einem DAT-Befehl als Zähler verwendet und bei jedem Schleifendurchlauf durch die SUB- oder die ADD-Anweisung erhöht oder vermindert. 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, wenn der Zähler die gestellte Bedingung erfüllt. 1.5.10 Der Vergleichsbefehl CMP ------------------------------- Mit ihm 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 beiden Werte ungleich sind, wird die unmittelbar folgende Anweisung bearbeitet. Hier ein weiteres Programmbeispiel, das zwar kein vollständiges Kampfprogramm ist, dafür aber die Anwendung des Vergleichsbefehls sehr schön aufzeigt: 1.5.10.1 Zwillinge DAT 0 DAT 99 start MOV @-2, @-1 CMP -3, #9 JMP 4 ADD #1, -5 ADD #1, -5 JMP -5 MOV #99, 93 JMP 93 Unsere ersten beiden Beispiele Knirps und Gnom stehen für eine Klasse von Programmen, die sich als klein und agressiv, aber nicht als intelligent klassifizieren lassen. Auf der nächsten Stufe kommen Programme, die grösser und etwas weniger angriffslustig, dafür aber klug genug sind, um mit Programmen der niedrigeren Stufe fertigzuwerden. Die klügeren Programme vermögen einem Angriff dadurch zu entgehen, dass sie sich selbst aus der Gefahrenzone hinauskopieren. Jedes dieser Programme hat ein Codesegment wie das eines Programmes namens Zwillinge. Zwillinge soll kein vollständiges Kampfprogramm sein. Seine einzige Aufgabe ist es, 100 Adressen weiter vorn eine Kopie von sich zu erzeugen und dann die Ausführung an die neue Kopie zu übertragen. Zwillinge hat drei Hauptkomponenten. Zwei DAT-Anweisungen zu Beginn dienen als Zeiger: Sie geben an, welche Anweisung als nächstes an welche Stelle im Speicherfeld kopiert werden soll. Den eigentlichen Kopiervorgang übernimmt eine Scheife in der Mitte des Programmes; sie transportiert jeden Befehl zu einer Adresse 100 Schritte hinter seine aktuellen Position. Bei jedem Passieren der Schleife werden beide Zeiger um 1 erhöht, so dass sie eine neue Quell- und Zieladresse bezeichnen. Ein Vergleichsbefehl innerhalb der Schleife prüft den Wert der ersten DAT-Anweisung. Sobald sie neunmal erhöht wurde, ist das ganze Programm kopiert, und die Schleife wird verlassen. Nur eine letzte Korrektur fehlt noch: Die Zieladresse steht in der zweiten Anweisung des Programmes und hat einen Anfangswert von "DAT 99"; als sie kopiert wurde, war sie jedoch bereits um 1 erhöht, so dass in der neuen Version des Programmes "DAT 100" steht. Dieser Uebertragungsfehler wird durch den Befehl "MOV #99, 93" korrigiert und dann die Ausführung des Programmes durch den letzten JMP-Befehl an die neue Kopie übergeben. 1.5.11 Die Spaltung von Programmen durch SPL -------------------------------------------- Dies ist der letzte noch unbekannte Redcode-Befehl. "SPL" kommt vom englischen Verb 'to split', das auf Deutsch 'spalten' bedeutet. Und genau das bewirkt dieser Befehl: Wenn der Programmzeiger eines Redcode-Programmes auf eine SPL-Anweisung trifft, wird das Programm an zwei Stellen gleichzeitig weitergeführt: Das erste Mal mit der Zeile, die gleich nach der SPL-Anweisung folgt und das zweite Mal an der Adresse, die durch das Argument der SPL-Anweisung bestimmt wird. (0143) - (0144) MOV #0, 100 (0145) SPL 2 (0146) JMP 101 (0147) - Dieses Programmfragment zeigt den Gebrauch von "SPL": der Programmzeiger wird irgendwann auf Zeile 145 treffen. Durch diese Zeile wird ein neuer Programmzeiger geboren, der seine Tätigkeit in diesem Fall zwei Zeilen weiter vorne bei Adresse 147 aufnimmt. Gleichzeitig läuft aber der ursprüngliche Programmzeiger weiter auf Zeile Nummer 146 und springt von dort 101 Programmzeilen weiter nach vorne auf Zeile $146+101=247$. Dieser Vorgang kann fast beliebig oft wiederholt werden. So ist es möglich, dass aus einem Anfangsprogramm maximal 63 neue Programme entstehen, die alle gleichzeitig abgearbeitet werden. Diese Aufspaltung von Programmen wirft aber einige Probleme auf, wie Sie gleich sehen werden. 1.6 Der Split-Befehl SPL und seine Wirkung __________________________________________ 1.6.1 Die Ausführung mehrerer Programme --------------------------------------- Durch den SPL-Befehl erhalten die Redcode-Programmierer die Möglichkeit, mehrere Programme gleichzeitig unter derselben Flagge laufen zu lassen. Entsprechend muss festgelegt werden, wie MARS in diesem Fall die Rechenzeiten verteilt. Das kann auf zweierlei Arten geschehen. Angenommen, dem einen Spieler gehörten die Programme $A_1$, $A_2$ und $A_3$, dem anderen $B_1$ und $B_2$. Eine Möglichkeit wäre, erst alle Programme des einen und dann die des anderen Spielers laufen zu lassen. Die Reihenfolge der Ausführung wäre also $A_1$, $A_2$, $A_3$ und dann $B_1$ und $B_2$. Dieser Zyklus würde sich endlos wiederholen. Bei der anderen Möglichkeit wechseln sich die Programme der Spieler ab. Die Reihenfolge wäre dann $A_1$, $B_1$, $A_2$, $B_2$, $A_3$, $B_1$ und so weiter. Beide Vorgehensweisen wirken sich ganz unterschiedlich aus. Die erste Methode begünstigt den, der möglichst viele Programme ins Spiel bringt, was der Entwicklung intelligenter Strategien wohl kaum förderlich ist. Die Zweite dagegen bedeutet, dass ein Programm um so seltener drankommt, je mehr Progrmme ein Spieler laufen hat. 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 Programme zum Anhalten zu bringen. Der Redcode-Programmierer muss sich beim Schreiben eines Programmes, das sich mehrmals aufteilt, vor Augen halten, dass er zwar mehrere Programmzeiger gleichzeitig laufen lassen kann, aber dass dadurch natürlich ein einzelnes seiner Programme viel langsa mer läuft. Als nächstes wollen wir noch einige Programme behandeln, die die Anwendung des SPL-Befehls anschaulicher darstellen: 1.6.2 Die Knirps-Kanone ----------------------- start SPL 2 JMP -1 MOV 0, 1 Was passiert, wenn die erste Zeile dieses Programmes an die Reihe kommt? Die Anweisung "SPL 2" bedeutet, dass sich das Programm aufteilt und zwei Programmzeilen ausgeführt werden: "JMP -1" und "MOV 0, 1". Die erste Instruktion bewirkt einen Rücksprung zur SPL-Anweisung, während die zweite einen Knirps (erinnern Sie sich? Das erste Programmbeispiel...) 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 so schiebt sich eine Marschkolonne von 63 Knirpsen durch die Kern-Arena - bereit, jedes feindliche Programm einfach niederzuwalzen. Auf den ersten Blick scheint gegen ein solches Heer von Knirpsen keine Verteidigung möglich, aber der Schein trügt: 1.6.3 Das Knirps-Grab --------------------- MOV #0, -1 JMP -1 Dies ist wiederum nur ein Programmfragment, das zu einem beliebigen grösseren Kampfprogramm gehören kann, das seine untere Flanke vor Knirpsen schützen will. Diese zwei Zeilen namens Knirps-Grab (manchmal 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 plaziert Knirps-Grab eine Null direkt vor sich - in der Hoffnung, einen herannahenden Knirps damit zu treffen. Hier ist die Anfangs vereinbarte Ausführungsregel entscheidend. Wenn Kirps-Kanone dem Spieler A gehört und Knirps-Grab dem Spieler B, dann braucht A $n$ Züge, um $n$ Knirpse eine Zelle weiter nach vorn zu bewegen; nur einer dieser Knirpse kann auf dem Speicherplatz vor Knirps-Grab landen. Unter sonst gleichen Bedingungen braucht B sein Knirps-Grab dagegen nur einmal auszuführen, um einen ankommenden Knirps zu löschen. Versuchen Sie, eigene Programme zu schreiben, die das Knirps-Grab verwenden, um sich vor von hinten anrollenden Knirpsen zu schützen. 1.7 Die Regeln auf einen Blick ______________________________ Das MARS-System besteht aus dem MARS Simulationsprogramm und dem Redcode-Assembler. Wenn Sie nicht mit dem Texteditor des Programmes "Core Wars" arbeiten wollen, benötigen Sie darüberhinaus einen beliebigen Editor oder eine Textverarbeitung, um die Kampfprogramme zu schreiben. Wenn eine Textverarbeitung verwendet wird, ist es wichtig, dass sie in der Lage ist, Texte im ASCII-Format abzuspeichern. Der Redcode-Assembler übersetzt die Programme, die im ASCII-Format vorliegen müssen, in den für das MARS Simulationsprogramm ausführbaren Programmcode, der auch Objektcode genannt wird. Man sagt auch: Der Redcode-Assembler übersetzt den Quelltext in den Objektcode. Der MARS-Emulator lädt zwei der Kampfprogramme in das Spielfeld, ein 8000 Zellen umfassender Speicher, der von MARS simuliert und verwaltet wird. Die Positionen, an denen die beiden Kampfprogramme A und B im Speicherfeld abgelegt werden, werden zufällig ausgewählt, sie müssen jedoch mindestens 1000 Zellen voneinander entfernt sein. Nun folgt die Ausführung der beiden Redcode-Programme. Es wird abwechslungsweise eine Anweisung von Partei A und anschliessend eine Anweisung von Partei B ausgeführt. Dieser Zyklus wiederholt sich so lange, bis alle Programme einer Partei zerstört sind oder bis eine bestimmte Anzahl von Zyklen ausgeführt wurden. Ein Kampfprogramm gilt als zerstört, wenn es versucht, eine DAT-Anweisung auszuführen. Dabei ist es egal, ob die DAT-Anweisung von Anfang an an dieser Stelle stand oder vom Gegner dorthin kopiert wurde. Der Gewinner des Spiels ist das Programm, das noch läuft (intakt ist), wenn alle anderen Programme schon zerstört sind. Es gibt zwei Fälle, in denen ein Kampf unentschieden ausgehen kann: Entweder wird eine bestimmte (wählbare) Anzahl von Ausführungszyklen überschritten, oder beide Programme haben zum Zeitpunkt der Zerstörung genau dieselbe Anzahl gültiger Instruktionen ausgeführt Das Speicherfeld, in dem der Kampf der Programme stattfindet, ist ein in sich geschlossener Bereich. Nach der Speicherzelle mit der Adresse 7999 folgt wieder die Adresse 0 und umgekehrt. Die Adressierung der Redcode-Befehle ist immer relativ zu der augenblicklichen Position des Programmes im Speicherfeld oder mit anderen Worten relativ zum Inhalt des Programmzeigers. Ein Redcode-Programm kann zu jeder Zeit den Inhalt einer beliebigen Speicherzelle (relativ adressiert) im Speicherfeld erfahren oder einen Sprung zu dieser Speicherzelle ausführen. Der SPL-Befehl ermöglicht es, dass mehrere Programme für den gleichen Spieler laufen können. Die Anzahl der gleichzeitig laufenden Programme ist auf 64 pro Spieler beschränkt. Das ergibt, sofern beide Spieler dieses Maximum ausnützen, im besten Falle 128 gleichzeitig laufende Programme. 1.8 Erleichterungen ___________________ Der Redcode-Assembler bietet einige Erleichterungen beim Schreiben eines Kampfprogrammes. Es handelt sich um einen "symbolischen Assembler", der in der Lage ist, sich bestimmte Adressen unter einem Namen oder eben dem Symbol zu merken. Dies bringt den Vorteil, dass die Sprungdistanzen bei den Befehlen SPL, JMP, JMN und JMZ nicht mehr von Hand ausgezählt werden müssen. Man gibt einfach anstelle der Sprungdistanz in Zahlen einen Namen ein. Dieser Name wird dann nochmals an den Beginn der Zeile, zu der gesprungen werden soll, gesetzt. Ein Beispiel ist das Kampfprogramm Gnom, das schon in einem früheren Abschnitt erklärt wurde. Hier ist das Programm, so wie es ohne Symbole geschrieben wird: DAT -1 start ADD #5, -1 MOV #0, @-2 JMP -2 Streng genommen ist das wort "start" auch ein Symbol, aber dieser Name ist reserviert und zeigt dem Assembler, an welcher Zeile das Programm später gestartet werden muss. "start" steht immer vor der als erstes auszuführenden Programmzeile und darf deshalb nur einmal am Beginn einer Zeile verwendet werden. Ein Programm ohne das "start"-Symbol wird bei der ersten Zeile im Quelltext gestartet. Und so sieht das Programm aus, wenn mit Symbolen gearbeitet werden kann: adresse DAT -1 start ADD #5, adresse MOV #0, @adresse JMP start Der JMP-Befehl in der letzten Zeile springt immer noch um zwei Adressen zurück, aber anstelle der Zahl -2 steht dort nun das Symbol "start". Der Assembler berechnet beim Assemblieren des Programmes die Distanz zwischen dem JMP-Befehl und der Zeile, an deren Anfang das Symbol "start" geschrieben steht, und fügt dann anstelle von "JMP start" die Zeile "JMP -2" in das Programm ein. Genau gleich funktioniert das Ganze bei den beiden vorhergehenden Programmzeilen "ADD #5, adresse" und "MOV #0, @adresse". Die Befehle beziehen sich immer noch auf die ursprünglichen Zeilen, aber die Distanzen werden aufgrund der Symbole vom Assembler ausgerechnet. Wir empfehlen die Verwendung von Symbolen, denn ein Programm wie Gnom wird durch die verwendeten Namen verständlicher und leichter lesbar. Mit Symbolen kann auch gerechnet werden. Man muss dazu wissen, dass jedes Symbol mit der Zahl in Verbindung gebracht wird, die sich aus der Numerierung der Programmzeilen bei Null beginnend ergibt. In unserem Beispiel würde das Symbol "adresse" die Zahl 0 und das Symbol "start" die Zahl 1 enthalten. Anstelle von "MOV #0, @adresse" könnte auch geschrieben werden: "MOV #start + adresse - 10, @adresse". Dies Formulierung ergäbe zwar keinen Sinn, aber es zeigt, dass mit Symbolen und den Operatoren "+" und "-" bestimmte Werte ausgerechnet werden können, die dann vom Assembler als Zahlen eingesetzt werden. Der Term "start + adresse - 10" ergäbe in diesem Fall 1 + 0 - 10 = -9. Bei der Benützung von Symbolen ist eigentlich nur wichtig, dass jedes Symbol an irgendeiner Stelle im Programm definiert wird. Ein Symbol gilt als definiert, wenn es in einer Zeile vor dem Redcode-Befehl beginnend mit der ersten Zeichen der Zeile auftaucht. Ein Symbol darf beliebig oft als Argument eines Redcode-Befehls verwendet werden, aber es darf nur genau einmal pro Programmtext definiert werden. Es existiert neben "start" noch ein weiteres reserviertes Symbol, das *-Zeichen. Es bezieht sich immer auf die aktuelle Programmzeile, in der es sich befindet. Wieder zeigt ein Beispiel die Funktion des *-Symbols: Diese Version des Programmes Gnom DAT -1 start ADD #5, -1 MOV #0, @-2 JMP -2 bewirkt genau dasselbe wie diese Version: DAT -1 start ADD #5, -1 adresse MOV adresse, @-2 JMP -2 Doch das Ganze kann auch so formuliert werden: DAT -1 start ADD #5, -1 MOV *, @-2 JMP -2 Das *-Zeichen (Vorsicht: es hat bei Redcode nichts mit einer Multiplikation zu tun, wie man von anderen Sprachen her vermuten könnte) wird beim Assemblieren durch den Wert ersetzt, den ein normales Symbol auch hätte, wenn es in dieser Zeile definiert würde - nämlich exakt Null. Deshalb können Sie sich eine ausschliesslich für eine Zeile benützte Definition ersparen, wenn sie den reservierten Symbolnamen '*' benutzen. Weitere Informationen, wie Sie einen Programmtext genau schreiben müssen und welche Beschränkungen Sie dabei berücksichtigen sollten, finden Sie im zweiten Kapitel dieser Anleitung: Der Abschnitt 2.4 befasst sich mit dem Assembler des Programmes "Core Wars". 1.9 Eine neue Adressierungsart ______________________________ Wir haben Ihnen noch eine letzte Adressierungsart vorenthalten. Sie kennen die unmittelbare, die direkte und die indirekte Adressierung von Argumenten für die Redcode-Befehle. Wenn Sie den einen oder andern dieser drei Modi noch nicht hundertprozentig verstanden haben, sollten Sie sich nochmals die Abschnitte 1.3 und 1.4 dieses Kapitels vornehmen. Probieren Sie die dort abgedruckten Beispielprogramme nochmals mit dem "Core Wars"--Programm aus. Eine gute Hilfe zum Verstehen der Adressierunsarten ist auch das genaue Verfolgen der disassemblierten (zurückübersetzten) Programmzeilen eines schwierigen Programmes. Nähere Informationen finden Sie im Abschnitt 2.8 ("Die Statusanzeige") im zweiten Kapitel. Doch nun zurück zu unserer vierten Adressierungsart. Sie nennt sich "indirekt-dekrement", und wie Sie vielleicht vermuten, handelt es sich dabei um eine Abwandlung der indirekten Adressierung. Der Name tönt komplizierter, als sich die Sache in der MARS-Wirklichkeit verhält. Wir wollen den Vorgang der indirekten Adressierung nochmals aufwärmen: Das Argument des aktuellen Redcode-Befehls zeigt direkt auf eine beliebige Adresse. Der Inhalt dieser Adresse wird nun ein zweites Mal direkt interpretiert und zeigt dadurch auf die entgültige Adresse, die dann je nach Befehl verwendet oder verändert wird. Dekrementieren bedeutet, einen Wert um eine bestimmte Zahl zu vermindern. In der Computertechnologie ist diese Zahl typischerweise genau eins, so auch bei der indirekt-dekrement Adressierung. Wenn MARS auf eine Anweisung trifft, deren Argument oder Argumemte auf diese Weise adressiert sind, laufen folgende Schritte in der angegebenen Reihenfolge ab: 1. Das betreffende Argument des Redcode-Befehls wird direkt interpretiert und führt so auf eine Zwischenadresse. Bis jetzt geschieht genau dasselbe wie bei der indirekten Adressierung. 2. Nun wird dekrementiert: Vom Inhalt dieser Zwischenadresse wird eins abgezogen (subtrahiert) und das Ergebnis der Rechnung wird wieder in die Zwischenadresse zurückgespeichert. 3. Ab jetzt läuft alles wieder wie bei der indirekten Adressierung: Der neue Inhalt der Zwischenadresse wird als direkt angesehen und zeigt deshalb auf die Endadresse, mit der oder mit deren Inhalt (je nach Befehl) weitergearbeitet wird. Uff. Wenn Sie die neue Adressierungsart einigermassen verdaut haben, können wir Ihnen Abschnitt 3.1 im dritten Kapitel empfehlen. Dort sind weitere Redcode-Programme aufgelistet und werden etwas auseinandergenommen. Vor allem das Gewinnerprogramm der ersten Core Wars Weltmeisterschaft im Jahre 1986 bedient sich ausgiebig der indirekt-dekrement Adressierung. 1.10 Auf den zweiten Blick __________________________ An dieser Stelle haben Sie nun das Grundwissen eines Redcode-Programmierers und Sie können behaupten, die Regeln der Sprache zu beherrschen. Alles, was ab jetzt noch kommt, sollte keine nennenswerten Schwierigkeiten mehr bieten, weil neue Tatsachen nur noch auf der bis zu diesem Punkt behandelten Theorie basieren. Doch wie in jeder Programmiersprache gibt es auch in Redcode verschiedene Sonderfälle, die viel Zeit und Nerven kosten können, wenn man sie nicht in ihrer Wirkung kennt. Deshalb haben wir eine Liste der Eigenheiten von Redcode mit der Erklärung der jeweiligen Falltüren erstellt. 1.10.1 Unmittelbare Adressierung bei Sprungbefehlen --------------------------------------------------- Das Ziel eines Sprunges kann, obwohl vier verschiedene Adressierungsarten bekannt sind, nur durch die drei Modi "direkt", "indirekt" und "indirekt-inkrement" angegeben werden. Die unmittelbare Adressierung ist jeweils bei der Sprungadresse der Befehle "JMP", "JMZ", "JMN", "DJN" und "SPL" nicht erlaubt! Das ist darin begründet, dass jede Adressierung eine Speicherzelle nie explizit oder anders ausgedrückt nie direkt mit ihrer wirklichen Nummer erreichen kann. Eine andere Speicherzelle wird bei Redcode immer relativ zur augenblicklichen Position im Speicher angesprochen. Dadurch wird verhindert dass a) ein Redcode-Programm seine eigene Position im Speicherfeld erfahren kann und dass b) alle Redcode-Programme lauffähig sind, egal an welche Position des Speichers sie zu Beginn eines Kampfes geladen wurden. 1.10.2 Unmittelbare Adressierung bei MOV, ADD, SUB und DJN ---------------------------------------------------------- Auf den ersten Blick haben diese vier Befehle nicht viel gemeinsam, aber dennoch gehören sie zusammen unter diesen Titel: Alle vier verändern den Inhalt einer beliebigen Speicherzelle. "ADD" und "SUB" führen mit der durch das zweite Argument bestimmten Speicherzelle eine Subtraktion oder Addition durch. "MOV" kopiert den Inhalt einer Adresse in eine andere Adresse, die durch das zweite Argument angegeben wird. Der alte Inhalt dieser Zieladresse geht dabei verloren, sie wird also ebenfalls verändert. Der vierte im Bunde ist der Befehl "DJN", der eine Speicherzelle um eins vermindert und dann den neuen Inhalt dieser Zelle mit Null vergleicht. Bei den genannten Befehlen ist es ebenfalls nicht erlaubt, bei der Angabe der Zieladresse den Modus unmittelbar zu verwenden. Die Gründe dafür sind dieselben wie bei den Sprungbefehlen: Ein Redcode-Programm darf seine Position im Speicher nicht erfahren und soll an jeder Stelle im Speicherfeld lauffähig sein. 1.10.3 Der Vergleichsbefehl CMP ------------------------------- Bei seiner Anwendung muss beachtet werden, dass entweder ganze Speicherzellen oder nur einzelne Werte verglichen werden können. Wir rufen uns in Erinnerung, dass eine MARS-Speicherzelle einen vollständigen Redcode-Befehl, also das Befehlswort mit seinen maximal zwei Argumenten und Adressierungsmodi, aufnehmen kann. Ob nun nur zwei Werte oder gleich eine ganze Speicherzelle verglichen wird, hängt von der verwendeten Adressierungsart ab. Wenn weder das erste noch das zweite Argument des CMP-Befehls unmittelbar adressiert ist, weisen die Argumente auf "ganze" Speicherzellen. Somit werden beide Zellen, die durch die Argumente bestimmt sind, vollständig verglichen. Vollständig bedeutet, dass sowohl der Befehl, als auch die Argumentwerte und die Adressierungsmodi verglichen werden. Der Vergleich ist nur dann wahr, wenn wirklich alle diese Punkte der beiden Befehle übereinstimmen. Der Verlgeich ist aber falsch, sobald auch nur einer der Punkte der einen Adresse von dem entsprechenden Punkt der anderen Adresse abweicht. Einzelne Werte dagegen werden verglichen, wenn eines der beiden Argumente eine unmittelbare Zahl ist, z.B. #8. Normalerweise ist nur eines der Argumente unmittelbar, weil ein Vergleich der beiden Zahlen #3 und #9 von vornherein bekannt ist und deshalb keinen Sinn ergibt. Das andere Argument ist also direkt, indirekt oder indirekt-dekrement adressiert und stellt eine Ziel- oder Vergleichsadresse dar. In dieser Adresse wird der Wert, der das zweite (B-) Argument darstellt, mit dem unmittelbaren Wert des CMP-Befehls verglichen. In diesem Fall werden folglich nur zwei Zahlen verglichen: Das B-Argument der Zieladresse und die unmittelbare Zahl in der CMP-Anweisung. 1.10.4 DAT-Befehle mit dem MOV-Befehl kopieren ---------------------------------------------- Der MOV-Befehl dient in erster Linie dazu, ganze Programmzeilen an eine andere Stelle im Speicherfeld zu kopieren oder bestimmte Zahlen als DAT-Befehle im Speicher abzulegen. Wenn in einer MOV-Anweisung die Quelladresse (das erste Argument) unmittelbar angegeben ist, wird an die Zieladresse (das zweite Argument) ein DAT-Befehl kopiert. Dieser DAT-Befehl besitzt den Wert, der durch die Zieladresse unmittelbar angegeben ist. Die Anweisung "MOV #8, 10" kopiert deshalb einen "DAT 8"-Befehl auf die Zieladresse, die 10 Zeilen weiter hinten im Speicherfeld liegt. Ist jedoch die Quelladresse direkt, indirekt oder indirekt-inkrement adressiert, wird die ganze Programmzeile, die durch die Quelladresse bestimmt wird, in die Speicherzelle kopiert, die durch die Zieladresse des MOV-Befehls angegeben wird. Wichtig bei der MOV-Anweisung ist, dass Sie wissen, in welchen Fällen neue DAT-Befehle geschaffen und wann ganze Programmzeilen kopiert werden. 1.10.5 Die Anwendung von ADD und SUB ------------------------------------ Diese Befehle dienen zum Addieren und Subtrahieren in Speicherzellen. Diese Operationen können sowohl auf Zahlen (unmittelbar) als auch Programmzeilen (direkt, indirekt und indirekt-inkrement) angewandt werden. Beachten Sie aber, dass sich die beiden Befehle je nach Anweisung unterschiedlich verhalten können. Jede Zelle im MARS-Speicherfeld besteht aus einer Anweisung und zwei Argumenten. Es spielt dabei keine Rolle, ob der enthaltene Befehl zwei oder nur ein Argument benötigt: Im Speicher belegt er eine ganze Zeile. Wenn zwei Zeilen addiert oder subtrahiert werden sollen, erfolgt die Operation in zwei Schritten: Zuerst wird Argument A der ersten Zeile mit Argument A der zweiten Zeile verrechnet, anschliessend geschieht dasselbe mit den B-Argumenten. Das heisst, dass die Addition einer DAT-Zeile zu einem Sprungbefehl sinnlos ist, weil die Anweisung DAT das Argument B verwendet, die Anweisung "JMP" (springen) jedoch das Argument A. Wenn der Wert eines Befehls Unmittelbar (#) angegeben wird, bezieht sich diese Zahl auf beide Argumente. Die drei folgenden Programmfragmente liefern sinnvolle Werte: DAT 8 ADD #1, -1 Addieren von 1 zu der DAT-Anweisung hinter dem ADD-Befehl: Nach der Ausführung enthält die Zeile "DAT 9". DAT 15 DAT 6 SUB -1, -2 "SUB -1, -2" subtrahiert von "DAT 15" den Wert 6, der in der anderen DAT-Zeile enthalten ist. "DAT 15" wird danach zu "DAT 9". CMP 5, -100 ADD #1, -1 Hier wird zur gesamten CMP-Befehlszeile oder mit anderen Worten zu beiden Argumenten der Wert eins addiert, weil dieser unmittelbar adressiert ist. Anschliessend würde also die Zeile, die 6 Schritte ($5+1$) nach CMP steht, mit der Zeile verglichen, die sich 99 ($-100+1$) Schritte vor der CMP-Anweisung befindet. Schliesslich noch eine schlechte Anwendung von "ADD": DAT 10 JMP -30 ADD -2, -1 Hier bezieht sich der ADD-Befehl auf die Anweisung "JMP -30" und bleibt ohne Wirkung, weil "DAT 10" keinen sinnvollen Wert zur Addition liefern kann: "JMP" benötigt das Argument A, "DAT" dagegen das Argument B. 1.11 Turniere _____________ Core Wars stand im Herbst der Jahres 1986 im Rampenlicht des Computermuseums in Boston. Dort fand das erste internationale Core Wars -- Turnier statt. Von den 31 teilnehmenden Programmen erwiesen sich drei als äusserst robust. Sieger wurde schliesslich ein Programm namens 'mice', in Englisch die Mehrzahl von Maus, also Mäuse. Der erste Preis bestand übrigens in einer Trophäe mit einer Kernspeicher-Karte aus einem alten CDC-6600 - Computer. Das Turnier war darauf angelegt, möglichst viele Begegnungen stattfinden zu lassen. Aber ein vollständiger Kampf zwischen den 31 Teilnehmern, jeder gegen jeden, hätte 465 Schlachten erfordert, und soviel Zeit stand nicht zur Verfügung. Die Teilnehmer wurden daher willkürlich in zwei nahezu gleich grosse Gruppen aufgeteilt. Innerhalb der Gruppen trat dann jeder gegen jeden an. Die vier bestplatzierten Programme von jeder Gruppe kämpften dann wieder jedes gegen jedes. Von diesen insgesamt acht Programmen kamen drei ins Finale, aus dem dann der endgültige Sieger des Turniers hervorging, eben "Mice". Dieses Siegerprogramm und ein Programm namens "Chang", das den zweiten Platz erringen konnte, werden im dritten Kapitel vorgestellt und erklärt. Neu hinzugekommen ist das Kampfprogramm "Mausefalle", welches "Mice" in den meisten Fällen schlagen kann. Im Dezember des Jahres 1988 findet übrigens wiederum ein Core Wars Turnier statt, von dem aber die Resultate und die Siegerprogramme noch nicht bekannt sind.