Wstęp

Reguły

Wojny Rdzeniowe

Reguły gry według standardu CWS'88
(wersja ASCII: cws88.txt)


Spis treści.
1. Wprowadzenie.
2. Reguły walki.
3. Rdzeń.
4. Tryby adesowania.
5. Rozkazy Redcode.
6. Wykonywanie rozkazów.
7. Pseudoinstrukcje.
8. Składnia kodu źródłowego.


1. Wprowadzenie.
Redcode jest językiem asemlerowym, w którym pisane są programy walczące w Wojnach Rdzeniowych. Redcode jest asemblerem wirtualnego procesora o nazwie MARS. Ponieważ MARS jako urządzenie nie istnieje, jego działanie musi być symulowane programowo (stąd też jego nazwa: Memory Array Redcode Simulator), tak więc mianem tym określa się również programy, które potrafią pobierać z wirtualnej pamięci rozkazy Redcode, dekodować je i wykonywać, tak jak robiłby to prawdziwy MARS.


2. Reguły walki.
Wojny Rdzeniowe polegają na walce programów napisanych w Redcode. Walka toczy się w pamięci MARS-a, zwanej rdzeniem. Przed rozpoczęciem bitwy MARS umieszcza walczących wojowników na rdzeniu dbając o to, aby ich kody nie pokrywały się. W trakcie rozgrywek MARS w każdym cyklu wykonuje po jednej instrukcji każdego wojownika, zawsze w tej samej kolejności. Walka kończy się, gdy jeden z walczących programów zostanie pokonany, lub gdy zostanie wykonana określona liczba cykli. W drugim przypadku walka pozostaje nierozstrzygnięta i MARS ogłasza remis.

MARS jest systemem wieloprocesowym. Każdy wojownik w momencie uruchomienia dysponuje jednym procesem, ale w trakcie walki może się podzielić na wiele procesów. Procesy jednego wojownika tworzą kolejkę i wykonywane są na przemian po jednej instrukcji. Jeśli któryś z procesów zostanie zniszczony, zostanie również usunięty z kolejki. Wojownik przegrywa walkę, gdy z jego kolejki procesów zostanie usunięty ostatni proces.


3. Rdzeń.
Rdzeń jest pamięcią, w której są umieszczane i wykonywane walczące programy. Pamięć ta jest zorganizowana w zamknięty pierścień komórek. Pojedyncza komórka składa się z pola operacji, pola argumentu A oraz pola argumentu B. Każda instrukcja Redcode mieści się dokładnie w jednej komórce pamięci.

Ponieważ rdzeń nie ma ani początku, ani końca, niemożliwe jest przypisanie poszczególnym komórkom jednoznacznych adresów. Wszystkie adresy są więc liczone względem bieżącej pozycji. Na przykład adres 0 wskazuje bieżącą instrukcję, adres -1 instrukcję poprzednią, a adres 1 instrukcję następną.

Cykliczna struktura rdzenia powoduje również, że każda komórka może mieć wiele różnych adresów. Jeśli przyjmiemy rozmiar rdzenia za M, to bieżącą instrukcję znajdziemy pod adresami {..., -2M, -M, 0, M, 2M, ...}, poprzednią pod {..., -1-2M, -1-M, -1, M-1, 2M-1, ...}, a następną pod {..., 1-2M, 1-M, 1, M+1, 2M+1, ...}.

Przed walką rdzeń jest inicjowany przez wpisanie instrukcji DAT 0, 0 do każdej jego komórki.

Pola argumentów mogą zawierać liczby całkowite z zakresu [0..M-1].


4. Tryby adresowania.
Redcode dopuszcza stosowanie czterech trybów adresowania. Tryb adresowania poszczególnych argumentów określa się poprzez odpowiednie oznaczenie ich typów. Dopuszczalnymi trybami adresowania są:

Tryb natychmiastowy, oznaczany symbolem #. Jego wartość znajduje się w bieżącej instrukcji. Jeżeli argument A jest natychmiastowy, jego wartości należy szukać w polu argumentu A, zaś jeśli argument B jest natychmiastowy, wartość znajduje się w polu argumentu B.

Tryb bezpośredni, przyjmowany domyślnie, gdy nie ma żadnego symbolu określającego typ argumentu. W trybie bezpośrednim pole argumentu jest adresem, który wskazuje komórkę będącą wartością argumentu. Adres jest liczony względem aktualnie wykonywanej instrukcji. Niektóre MARS-y zezwalają na oznaczanie argumentów bezpośrednich symbolem $.

Tryb pośredni, oznaczany symbolem @. Tutaj, podobnie jak w trybie bezpośrednim, pole argumentu jest adresem wskazującym jakąś komórkę, jednak jest ona nie wartością argumentu, lecz wskaźnikiem do niej. Adres komórki będącej wartością argumentu znajduje się w polu argumentu B wskaźnika i jest on liczony względem pozycji wskaźnika. Na przykład:

x1	DAT	#0,	#0	;komórka docelowa
x2	DAT	#0,	#-1	;wskaźnik (adres pośredni)
x3	MOV	0,	@-1	;kopiuje siebie pod adres x1
Tryb pośredni zmniejszany, oznaczany symbolem <. Wyznaczany jest niemal dokładnie w tak samo, jak tryb pośredni, jedynie wskaźnik (ściślej: wartość jego pola argumentu B) przed użyciem jest zmniejszany o jeden. Na przykład:

x1	DAT	#0,	#0	;komórka docelowa
x2	DAT	#0,	#0	;wskaźnik (adres pośredni)
x3	MOV	0,	<-1	;kopiuje siebie pod adres x1

5. Rozkazy Redcode.
DAT A, B
Rozkaz DAT (data) ma dwa zastosowania. Po pierwsze, umożliwia przechowywanie w swoich polach argumentów różnego rodzaju danych: liczników, adresów, wskaźników itp. Po drugie, proces, który wykona tę instrukcję, zostaje usunięty z kolejki procesów. Gdy wszystkie procesy wojownika zostaną usunięte z kolejki, wojownik przegrywa walkę.

W rozkazie DAT dopuszczalne są tylko dwa typy argumentów: natychmiastowy i pośredni zmniejszany. Efekt zmniejszenia wskaźnika podczas wyznaczania argumentu pośrednego zmniejszanego występuje również przy rozkazie DAT. Proces jest bowien usuwany dopiero po wyznaczeniu obu argumentów.

Argument A może być pominięty, w jego miejsce zostanie wstawiony #0.

MOV A, B
Rozkaz MOV (move) kopiuje A do B. Jeśli argument A jest natychmiastowy, jego wartość jest wstawiana do pola B komórki wskazywanej przez argument B. Jeśli argument A jest innego typu, cała komórka wskazywana przez argument A jest kopiowana do komórki wskazywanej przez argument B.

Argument B nie może być natychmiastowy.

ADD A, B
Rozkaz ADD dodaje A do B i umieszcza wynik w B. Jeśli argument A jest natychmiastowy, jego wartość jest dodawana do pola B komórki wskazywanej przez argument B. Jeśli argument A jest innego typu, pole A i pole B komórki wskazywanej przez argument A są dodawane odpowiednio do pola A i pola B komórki wskazywanej przez argument B.

Argument B nie może być natychmiastowy.

SUB A, B
Rozkaz SUB (subtract) odejmuje A od B i umieszcza wynik w B. Działanie tego rozkazu jest identyczne, jak rozkazu ADD, jedynie zamiast operacji dodawania jest odejmowanie.

Argument B nie może być natychmiastowy.

JMP A, B
Rozkaz JMP (jump) skacze do A. Skok jest wykonywany do instrukcji wskazywanej przez argument A. Argument B może być pominięty, w jego miejsce zostanie wstawiony #0.

Argument A nie może być natychmiastowy.

JMZ A, B
Rozkaz JMZ (jump if zero) wykonuje skok do A, gdy B jest zerem. Jeśli argument B jest natychmiastowy, badana jest jego wartość, natomiast jeśli jest innego typu, badana jest wartość pola B komórki wskazywanej przez argument B. Ewentualny skok wykonywany jest do instrukcji wskazywanej przez argument A.

Argument A nie może być natychmiastowy.

JMN A, B
Rozkaz JMN (jump if non-zero) wykonuje skok do A, gdy B jest różne od zera. Działanie tego rozkazu jest identyczne (za wyjątkiem warunku skoku), jak rozkazu JMZ.

Argument A nie może być natychmiastowy.

DJN A, B
Rozkaz DJN (decrement and jump if non-zero) zmniejsza B i skacze do A, gdy B po zmniejszeniu jest różne od zera. Jeśli argument B jest natychmiastowy, zmniejszana (i później badana) jest wartość argumentu B. Jeśli argument B jest innego typu, zmniejszana (i później badana) jest wartość pola B komórki wskazywanej przez argument B. Ewentualny skok wykonywany jest do instrukcji wskazywanej przez argument A.

Argument A nie może być natychmiastowy.

CMP A, B
Rozkaz CMP (compare, skip if equal) porównuje A z B, po czym przeskakuje następną instrukcję, jeśli A i B są równe. Jeśli argument A jest natychmiastowy, porównywana jest wartość argumentu A z wartością pola B komórki wskazywanej przez argument B. Jeśli argument A jest innego typu, porównywane są całe komórki (tzn. zarówno ich pola operacji, jak i pola argumentów) wskazywane przez argument A i argument B.

Argument B nie może być natychmiastowy.

SPL A, B
Rozkaz SPL (split) uruchamia pod A nowy proces. Nowopowstały proces wykonywany jest od instrukcji wskazywanej przez argument A. Bezpośrednio po wykonaniu rozkazu SPL wykonywana jest jednak ta sama instrukcja, jaka byłaby wykonana, gdyby ów nowy proces nie powstał (nowy proces dodawany jest bowiem na końcu kolejki procesów). Jeśli kolejka procesów jest pełna, SPL nie tworzy nowego procesu.

x1	SPL	0
x2	JMP	0
W powyższym przykładzie kolejność wykonywania poszczególnych procesów jest następująca:

[proces 1]	x1	SPL	0	;proces 1 tworzy proces 2 pod x1
[przeciwnik]				;wykonywany jest kod przeciwnika
[proces 1]	x2	JMP	0	;proces 1 zapętla się
[przeciwnik]
[proces 2]	x1	SPL	0	;proces 2 tworzy proces 3 pod x1
[przeciwnik]
[proces 1]	x2	JMP	0
[przeciwnik]
[proces 2]	x2	JMP	0	;proces 2 zapętla się
[przeciwnik]
[proces 3]	x1	SPL	0	;proces 3 tworzy proces 4 pod x1
[przeciwnik]
[proces 1]	x2	JMP	0
[przeciwnik]
[proces 2]	x2	JMP	0
[przeciwnik]
[proces 3]	x2	JMP	0	;proces 3 zapętla się
[przeciwnik]
[proces 4]	x1	SPL	0	;proces 4 tworzy proces 5 pod x1
(...)					;itd...
Argument B może być pominięty, w jego miejsce zostanie wstawiony #0, argument A nie może być natychmiastowy.

SLT A, B
Rozkaz SLT (skip if less than) porównuje A z B, po czym przeskakuje następną instrukcję, jeśli A jest mniejsze niż B. Działanie rozkazu jest analogiczne do CMP.

Argument B nie może być natychmiastowy.

Redcode, podobnie jak inne języki asemblerowe, wprowadza pewne ograniczenia w używaniu argumentów natychmiastowych. Nie wolno ich używać tam, gdzie rozkaz spodziewa się adresu, np. przy skokach. Niedopuszczalne są zatem poniższe konstrukcje (? oznacza dowolny typ argumentu):

	MOV	?A,	#B
	ADD	?A,	#B
	SUB	?A,	#B
	CMP	?A,	#B
	SLT	?A,	#B
	JMP	#A,	?B
	JMZ	#A,	?B
	JMN	#A,	?B
	DJN	#A,	?B
	SPL	#A,	?B
Wyjątkiem jest tu rozkaz DAT, który w obu argumentach dopuszcza wyłącznie adresowanie natychmiastowe (#) i pośrednie zmniejszane (<).

Niektóre MARS-y umożliwiają obejście tego ograniczenia, przyjmując adres argumentów natychmiastowych równy 0.


6. Wykonywanie rozkazów.
Ten punkt zawiera opis techniczny przeznaczony dla projektantów MARS-ów oraz zaawansowanych graczy. Jego zadaniem jest jak najdokładniejsze zdefiniowanie reguł, jakimi rządzą się Wojny Rdzeniowe oraz rozwianie wszelkich możliwych niejasności.

MARS, aby poprawnie zinterpretować instrukcję Redcode, korzysta z następujących wewnętrznych rejestrów:

	RR 	- rejestr rozkazu
	RA 	- rejestr argumentu A
	RB 	- rejestr argumentu B
	PC 	- licznik programu (adres aktualnie wykonywanej instrukcji)
	AdrA	- adres argumentu A
	WskA	- wskaźnik argumentu A
	AdrB	- adres argumentu B
	WskB	- wskaźnik argumentu B
	A	- wartość argumentu A
	B	- wartość argumentu B
Rejestry RR, RA i RB służą do przechowywania całych komórek pamięci, można więc w nich wyodrębnić trzy pola: Op, A i B (odpowiednio: pole operacji, pole argumentu A i pole argumentu B). Pozostałe rejestry mogą przechowywać liczby całkowite z zakresu [0..M-1], gdzie M jest wielkością rdzenia. Wszystkie operacje arytmetyczne MARS wykonuje modulo M.

Instrukcje wykonywane przez MARS-a posługują się wyłącznie adresami względnymi, jednak MARS musi mieć możliwość bezwzględnego adresowania rdzenia. Rejestr PC przechowuje zatem bezwzględny adres wykonywanej instrukcji. Rejestry AdrA, WskA, AdrB i WskB przechowują bezwzględne adresy argumentów lub wskaźników liczone względem adresu umieszczonego w PC.

Pierwszym krokiem MARS-a jest odczytanie instrukcji do wykonania i zapamiętanie jej w rejestrze RR. Instrukcja ta znajduje się na rdzeniu pod bezwzględnym adresem przechowywanym w rejestrze PC:

RR = Rdzeń[PC]
Kolejnym krokiem jest opracowanie argumentów. Tak wyznaczany jest natychmiastowy argumentu A:

AdrA = PC
RA = Rdzeń[AdrA]
A = RA.A
Bezpośredni argument A:

AdrA = (PC + RR.A) mod M
RA = Rdzeń[AdrA]
A = RA.B
Pośredni argument A:

WskA = (PC + RR.A) mod M
RA = Rdzeń[WskA]
AdrA = (WskA + RA.B) mod M
RA = Rdzeń[AdrA]
A = RA.B
Pośredni zmniejszany argument A:

WskA = (PC + RR.A) mod M
RA = Rdzeń[WskA]
Rdzeń[WskA].B = (Rdzeń[WskA].B + M - 1) mod M
AdrA = (WskA + RA.B) mod M
AdrA = (WskA + M - 1) mod M
RA = Rdzeń[AdrA]
A = RA.B
Argument B wyznaczany jest podobnie, jedynie zamiast AdrA, WskA i RR.A używane są odpowiednio AdrB, WskB i RR.B, a zamiast wartości A wyliczana jest wartość B:

B = RB.B
Po wyznaczeniu obu argumentów MARS może przystąpić do wykonywania instrukcji umieszczonej w RR.Op. Działanie wszystkich rozkazów zostało opisane w punkcie 5, należy jedynie pamiętać o następujących zasadach:
- MOV albo wpisuje A do Rdzeń[AdrB].B, albo RA do Rdzeń[AdrB],
- ADD i SUB z natychmiastowym argumentem A operują na A i B, wynik umieszczają w Rdzeń[AdrB].B,
- ADD i SUB z adresowym argumentem A operują na RA.A i RB.A oraz na RA.B i RB.B, wyniki umieszczają odpowiednio w Rdzeń[AdrB].A i Rdzeń[AdrB].B,
- skoki i SPL operują na rejestrze AdrA,
- skoki warunkowe testują rejestr B,
- DJN zmniejsza Rdzeń[AdrB].B,
- CMP operuje albo na rejestrach A i B, albo na RA i RB,
- SLT zawsze operuje na rejestrach A i B,
- jeśli rozkaz nie wykonał skoku, to PC = (PC + 1) mod M


7. Pseudoinstrukcje.
Obok jedenastu rozkazów Redcode dopuszcza stosowanie dwóch pseudoinstrukcji: END i EQU. Pseudoinstrukcje nie są rozkazami Redcode, a jedynie dyrektywami kompilatora.

	END <etykieta>
Pseudoinstrukcja END służy do oznaczania punktu startowego oraz końca programu. Punktem startowym programu staje się instrukcja wskazana etykietą podaną jako argument. Argument jest opcjonalny i można go opuścić, punktem startowym będzie wówczas pierwsza instrukcja w programie. Wszystko, co w kodzie źródłowym znajduje się po pseudoinstrukcji END, jest przez kompilator ignorowane.

	<etykieta> EQU <A>
Pseudoinstrukcja EQU (equate) powoduje podmienianie każdego wystąpienia etykiety <etykieta> napisem <A>. Na przykład:

x1	DAT 	#0,	#x1
	DAT	#0,	#x1
	DAT	#0,	#x1
znaczy to samo, co

x1	DAT 	#0,	#0
	DAT	#0,	#-1
	DAT	#0,	#-2
ale

x1	EQU	0
	DAT 	#0,	#x1
	DAT	#0,	#x1
	DAT	#0,	#x1
znaczy tyle, co

	DAT 	#0,	#0
	DAT	#0,	#0
	DAT	#0,	#0

8. Składnia kodu źródłowego.
Program napisany w Redcode składa się z wierszy o następującym formacie:

<etykieta>	<instrukcja>	<komentarz>
Elementy te są opcjonalne, zatem pojedynczy wiersz nie musi zawierać ich wszystkich. Dopuszczalne też są puste wiersze. Znakami rozdzielającymi są spacja i znak tabulacji. Etykieta jest ciągiem liter i cyfr zaczynającym się literą. Małe i duże litery są nierozróżnialne, do liter zalicza się również kreska podkreślenia. Etykieta może mieć dowolną długość, ale tylko pierwsze osiem znaków jest znaczących. Komentarz jest ciągiem dowolnych znaków zaczynającym się średnikiem i kończącym się wraz z końcem wiersza.

Instrukcja składa się z rozkazu i dwóch argumentów, przy czym każdy z argumentów może być poprzedzony symbolem określającym jego typ:

<rozkaz>	<typ A><argument A>,	<tyb B><argument B>
Dopuszczalnymi rozkazami są DAT, MOV, ADD, SUB, JMP, JMZ, JMN, DJN, CMP, SPL, SLT, END oraz EQU. Dopuszczalnymi typami argumentów są: # (adresowanie natychmiastowe), @ (adresowanie pośrednie) i < (adresowanie pośrednie zmniejszane). Oznaczenie typu argumentu można pominąć, spowoduje to przyjęcie typu bezpośredniego. Argumenty są wyrażeniami arytmetycznymi składającymi się z liczb całkowitych, etykiet oraz operatorów dodawania (+), odejmowania (-), mnożenia (*) oraz dzielenia całkowitego (/). Dopuszczalne jest stosowanie nawiasów w wyrażeniach.

CWS'88 w oryginale nie zezwala na oddzielanie argumentów przecinkami, podobnie jak nie zezwala na oddzielanie spacjami składników wyrażeń arytmetycznych. Obecnie jednak większość MARS-ów omija te ograniczenia.

---------------------------------------------------------------------------------------------------------------
Opracował Adam Ryba na podstawie:
[1] International Core War Society, "Core Wars Standard of 1988", 1988.
[2] Mark A. Durham, "Introduction to Redcode", 1991.
[3] Mark A. Durham, "EMI'88, Execute MARS Instruction a'la ICWS'88", 1991.

Wrocław, 1995.03.05

pozioma linia
Do chatki Na górę Ostatnia modyfikacja:
Copyright © 1998 by Adam Ryba