1.10-es magyar verzió, írta Ilmari Karonen, fordította Bíró Csaba
Napjainkban nem túl sok kezdő érdeklődik a Core War nevű játék iránt. Ez tulajdonképpen természetes - nem olyan sok ember találja assembly kódok optimalizálását szórakoztató feladatnak - viszont egy másik ok, hogy a kezdet fokozottan nehéz, ugyanis igen nehezen található információ a játék alapjairól. Igaz, van sok jó anyag, de a legtöbbjük vagy túl technikai, vagy elavult, vagy nehezen elérhető, vagy egyszerűen nem teljes.
Hát ezek az okok vezettek ennek az anyagnak a megírásához. A célom, hogy elvezessem a kezdőket a Core War-ral és Redcode-dal való első találkozásuktól addig a pontig, amikor már önállóan is tudnak működő (ha nem sikeres) harcost írni, és megérthetnek technikaibb jellegű cikkeket is.
Őszintén bevallom, hogy én magam is kezdő vagyok a játékhoz. A nyelvet ugyan elég jól ismerem, de még nem írtam igazán sikeres harcost. Mégis úgy döntöttem, nem várok addig, amíg tapaszaltabb leszek, hanem azonnal megírom ezt az ismertetőt, amíg frissen élnek bennem az emlékek, hogy milyen kezdőnek lenni, és próbálkozni a játék megértésével.
Ez az ismertető abszolút kezdőknek lett szánva. Semmi előismeret nem szükséges az assembly nyelről (meg úgy általában a progamozásról), bár az alapdolgok ismerete elősegítheti a megértést. A Redcode, főleg a modern változatok, lehet, hogy assemly kód jellegűnek tűnik, de annál sokkal absztraktabb, és részleteiben teljesen más, mint bármelyik assembly nyelv.
A Redcode azon fajtája, amit ebben az anyagban (leginkább) használunk, a jelenlegi de facto, és ez az ICWS '94 Standard Draft a pMARS 0.8 kiterjesztéseivel. (Valahogy úgy, ahogy a HTML a Netscape kiterjesztéseivel... hmm... szerencsére még nincs Microsoft Corewar Szimulátor. Lehet, hogy túl kicsinek találják a piacot.) A korábbi '88-as standard-et csak említés szintjén tárgyaljuk, a fő célunk a '94-es standard bemutatása. Azok, akik mégis azt akarják tanulni, könnyen találhatnak egy csomó leírást róla a weben.
Fontos:: A Redcode - vagy bármely más programozási nyelv - tanulására nincs jobb módszer, mint a szigorú, lépésről-lépésre való haladás. Ráadásul én megpróbáltam jól tanulható anyagot összeállítani; de ha valamit át akarsz ugrani, hát tedd. Erre való a Tartalomjegyzék című fejezet.
Meg kell még jegyeznem, hogy gyakran arra fogok kényszerülni, hogy valamit hamarabb mutassak meg, mint hogy elmagyaráznám a jelentését. Ezért ha valami nem tűnik azonnal érthetőnek, olvass egy kicsit tovább, és ha még mindig nem érted, próbálj meg nézelődni a szövegben, hogy hol van elmagyarázva.
Persze mindenki másképp tanul, szóval a saját módszered lehet, hogy jobban beválik, mint amit én kitaláltam. De ha valamit unalmasnak találván teljesen átugrasz, megvan az esélyed arra, hogy kihagysz valami fontosat. Megpróbáltam a fontos részeket kiemelve írni, hogy tudd, hol kell megállni és gondolkozni. Ennek ellenére próbálj meg mindent figyelmesen olvasni. Mindent nem részletezhettem ki, mert akkor ez a szöveg olvashatatlanul hosszúra nyúlt volna.
A Core War (vagy Core Wars) programozási játék, ahol assembly programok próbálják megsemmisíteni egymást egy képzeletbeli számítógép memóriájában. A programok (vagy harcosok) egy speciális nyelven íródnak, aminek neve Redcode, őket pedig egy MARS nevű program futtatja (MARS=Memory Array Redcode Simulator).
Mind a Redcode, mind a MARS környezet lényegesen le van egyszerűsítve a valódi számítógéprendszerekhez képest. Ez jó dolog, tekintve a CW programozás célját. Ha a játék egy valódi assembly nyelvet használna, lenne két vagy három ember a Földön, aki képes volna effektív és életképes programot írni, és még lehet, hogy ők sem értenék programjaik működését tökéletesen. Természetesen ez is nagy kihívás lenne, tele lehetőségekkel, de valószínűleg az is évekig tartana, hogy az ember egy közepes erősségű játékossá váljon.
A rendszer, amiben a programok futnak, fölöttébb egyszerű. A core (a virtuális gép memóriája), egy utasításokat tartalmazó lista, ami indításkor, a benne levő harcra kész programoktól eltekintve üres. A core körbefordul, tehát az utolsó utasítást ismét az első követi.
Persze a programok nem tudhatják, hogy a core hol végződik, hiszen nincs abszolút memóriacím. Szóval a 0-ás címen nem az első utasítást van, hanem az amelyik a címzést tartalmazza. A következő utasítás az 1-es és az előző pedig nyilván a -1-es című.
Amint az látható, a memória legkisebb egysége a Core War-ban az utasítás, ellentétben az általános gyakorlatban megszokott byte-tal. Minden Redcode utasítás 3 részből áll maga az utasítás, a forrás cím (ennek neve A-mező), és a cél cím (ez a B-mező). És bár lehet például adatot mozgatni az A- és a B-mező között, de az utasításokat általában, mint összefüggő blokk kell felfogni.
A programok végrehajtása ugyanilyen egyszerű. A MARS egyszerre egy utasítást hajt
végre, azután ugrik a következőre a memóriában, hacsak a vérehajtott utasítás nem
küldi egy másik címre. Ha (mint általában) egyszerre több program is fut, akkor a
programok egyszerre hajtódnak végre: egy utasítás az egyikből, aztán egy a másikból,
és így tovább. Így minden utasítás végrehajtása ugyanannyi időt vesz igénybe:
egy ütemet, legyen az utasítás MOV, DIV vagy akár
DAT (ami mellesleg leállitja a processt)...
A Redcode utasítások száma minden új standard bevezetésekor növekedett, így lett az eredeti 5 utasításból jelenleg 18 vagy 19. És ebben még nincsenek benne az új módosítók és címzési módok, amikből kombinációk százai állíthatók elő. Szerencsére nem kell minden kombinációt megtanulnunk. Elég ha emlékszünk az utasításokra, és hogy a módosítók mit változtatnak rajtuk.
Íme egy lista a Redcode-ban használt utasításokról:
DAT - adat (leállitja a process-t)MOV - mozgatás (adatot másol egyik címről a másikra)ADD - összeadás (hazzáad egy számot egy másikhoz)SUB - kivonás (kivon egy számot egy másikból)MUL - szorzás (megszoroz egy számot egy másikkal)DIV - osztás (eloszt egy számot egy másikkal)MOD - modulus (eloszt egy számot egy másikkal és a maradékot adja)JMP - ugrás (a végrehajtást egy másik címen
folytatja)JMZ - ugrás, ha nulla (megvizsgál egy számot, és ugrik, ha az 0)JMN - ugrás, ha nem nulla (megvizsgál egy számot, és ugrik, ha az nem 0)DJN - csökkentés, és ugrás, ha nem nulla (eggyel csökkent egy számot, és ugrik,
ha az eredmény nem 0)SPL - szétválás (új process-t indít másik címen)CMP - összehasonlítás (ugyanaz, mint a SEQ)SEQ - kihagyás, ha egyenlő (összehasonlít két utasítást, és kihagyja a következőt,
ha egyelőek)SNE - kihagyás, ha nem egyenlő (összehasoonlít két
utasítást, és kihagyja a következőt, ha nem egyelőek)SLT - kihagyás, ha kisebb, mint (összehasoonlít két értéket, és kihagyja a következő
utasítást, ha az első kisebb, mint a második)LDP - betöltés a p-space-ből (betölt egy számot a privát memóriából)STP - mentés p-space-be (kiment egy számot a privát memóriába)NOP - nincs művelet (nem csinál semmit)Ne aggasszon, ha némelyik enyhén szólva furcsának tűnik. Amint mondtam, a Redcode, absztrakt természetéből adódóan, egy kicsit különbözik az általánosan használt assembly nyelvektől.
Az az igazság, hogy a Redcode legegyszerűbb elemei a legfontosabbak. A legtöbb harcos-tipust még az új utasítások bevezetése előtt kitalálták. Talán a legelső Core War program az Imp volt, ezt A. K. Dewdney publikálta 1984-ben az Scientific American-ben. Ez a cikk volt az első Core War-ról szóló tanulmány a szaksajtóban.
MOV 0, 1
Ennyi. Egyetlen vacak MOV. De mit is csinál?
Hát MOV, természetesen utasítást másol. Ha emlékszel még, a
Core War-ban minden cím relatív az aktuális utasításhoz, így az Imp
tulajdonképpen önmagát másolja egy utasítással maga elé.
MOV 0, 1 ; ezt hajtotta végre most
MOV 0, 1 ; ezt az utasítást hajtja végre mindjárt
Tehát az Imp azt az utasítást fogja vérehajtani, amit ő másolt oda az előbb!
És mivel az pontosan ugyanaz mint az első, újra lemásolja magát, megint
továbbmegy a másolásra, és így folytatva a végrehajtást telerakja a core-t
MOV-val. A core-nak pedig nincs tulajdonképpeni vége, így az Imp
miután teljesen teletöltötte, eléri a kiindulási pontját, és boldogan folytatja
a futást a körbe-körbe az idők végezetéig.
Tehát az Imp elkészíti a saját kódját, aztán végrehajtja azt! A Core War-ban az önmódosítás inkább szabály, mint kivétel. Hatékonynak, sikeresnek kell lenned, és ez majdnem mindig azzal jár, hogy a programodnak módosítania kell magát futás közben. Szerencsére az absztrakt környezet ezt sokkal inkább lehetővé teszi, mint a szokásos assembly-ben.
Ehhez kapcsolódva elmondanánk, hogy a Core War-ban természetesen nincsen cache, ám ez így mégsem igaz, mert a végrahajtott utasítás cache-elődik. Így nem változtathatunk meg egy utasítást önmaga végrehajtása közben. De erről majd inkább később...
Az Impnek, mint harcosnak egyetlen hátulütője van. Nem fog túl sok
meccset megnyerni, mert amikor valakit felülír, az is elkezdi a
MOV 0, 1-eket végrehajtani, ő is Imppé válik, és az eredmény
döntetlen. Más programok megöléséhez DAT utasítást
kell annak kódjába másolni.
Ez az, amit a Dewdney által írt másik klasszikus harcos, a Dwarf csinál.
"Bombázza" a core-t egyenlő távolságonként DAT-okkal, és
mellesleg soha nem lövi szét önmagát.
ADD #4, 3 ; a végrahajtás itt kezdődik
MOV 2, @2
JMP -2
DAT #0, #0
Tulajdonképpen ez nem pontosan ugyanaz, mint amit Dewdney írt, de pontosan
ugyanazt teszi. A végrehajtás ismét az első utasításon kezdődik, ami most
egy ADD. Az ADD összeadja a forrást és a célt, az
eredményt pedig a célra teszi. Ha valaki otthonosan mozog más assembly nyelvekben,
már biztos felismerte a # jelet, ami egy módja a közvetlen
címzés jelzésének. Vagyis az ADD hozzáadja a 4-es számot
(és nem a 4-es címen található számot) a 3-as címen található utasításhoz.
Mivel pedig az ADD-tól számított 3. címen található utasítás
a DAT, ezért az eredmény a következő lesz:
ADD #4, 3
MOV 2, @2 ; következő utasítás
JMP -2
DAT #0, #4
Ha két utasítást összeadssz, akkor az A- és a B-mezők egymástól
függetlenül összeadódnak. Ha egy egyszerű számot adsz hozzá egy utasításhoz,
akkor az alapértelmezésben annak B-mezőjéhez adódik. Az is megengedett, hogy a
B-mezőben #-t használjunk. Ekkor az A-mező ugyanannak az ADD-nak
a B-mezőjéhez adódik.
A közvetlen címzés egyszerűnek és ismerősnek tűnhet, ám az új módosítók az ICWS '94 standard-ben új trükköket tesznek vele lehetővé. De nézzük előbb a Dwarf-ot.
A MOV megint egy új címzésmódot mutat nekünk: a
@-t, vagyis az indirekt címzést. Ez azt jelenti, hogy
a DAT nem önmagára másolódik, ahogy az elsőre tűnhet
(milyen jó is lenne), hanem arra az utasításra, amire a B-mezője
mutat. Íme:
ADD #4, 3
MOV 2, @2 ; --.
JMP -2 ; | +2
DAT #0, #4 ; <--' --. A MOV B-mezője ide mutat.
... |
... | +4
... |
DAT #0, #4 ; <------' A DAT B-mezője ide mutat.
Amint látható, a DAT négy utasítással maga elé másolódott.
A következő utasítás, a JMP, egyszerűen visszaugrik két
utasításnyit, vissza az ADD-ra. Mivel a JMP
nem veszi figyelembe a B-mezőjét, üresen hagytam neki. A MARS 0
értékkel fogja inicializálni.
Az is látható, hogy a MARS nem kezdi el követni a hosszabb indirekt láncokat. Ha például az indirekt operandus olyan utasításra mutat, aminek mondjuk 4 a B-mezője, akkor az aktuális cél 4 utasítással utána lesz, mindegy mi a címzésmód.
Tehát az ADD és a MOV fog újra
végrehajtódni. Mire a végrehajtás újra a JMP-hez ér, a
core így fog kinézni:
ADD #4, 3
MOV 2, @2
JMP -2 ; következő utasítás
DAT #0, #8
...
...
...
DAT #0, #4
...
...
...
DAT #0, #8
A Dwarf folytatja a DAT-ok dobálását minden negyedik
utasításra, amíg körbe nem ér, és el nem éri önmagát:
...
DAT #0, #-8
...
...
...
DAT #0, #-4
ADD #4, 3 ; következő utasítás
MOV 2, @2
JMP -2
DAT #0, #-4
...
...
...
DAT #0, #4
...
Most az ADD visszaállítja a DAT-ot
#0, #0-ra, a MOV feleslegesen erőlteti magát
azzal, hogy a DAT-ot odamásolja, ahol egyébként is van,
aztán a program mindent kezd elölről.
Természetesen ez csak akkor működik, ha a core mérete 4-gyel
osztható, mert különben a Dwarf, lelőné a DAT utáni
valamelyik utasítást, megölve így önmagát. Szerencsére a leginkább
elterjedt core-méret jelenleg 8000, esetleg 8192, 55400, illetve 800 -
mind osztható 4-gyel, tehát Dwarfunk biztonságban van.
Itt megjegyzem, hogy a DAT #0, #0 a harcosban nem is lett
volna feltétlenül szükséges, ugyanis a core eredetileg DAT 0,
0-val van feltöltve. Én eddig három pontot tettem az üres
helyekre (...), és ezt a jelölést fogom ezután is
használni, mert rövidebb és jobban átlátható.
A Core War első verziójában csak a közvetlen (#, a
relatív ($ vagy semmi) és a B-mező inirekt
(@) címzésmódok léteztek. Később, az előcsökkentő,
vagyis a < módot is bevezették. Ez ugyanaz, mint az
indirekt mód, csak ennél mielőtt a cél cím kiszámolásra kerülne, a
célra mutató szám eggyel csökken.
DAT #0, #5
MOV 0, <-1 ; következő utasítás
Mikor ez a kódrészlet végrehajtódik, az eredmény a következő lesz:
DAT #0, #4 ; ---.
MOV 0, <-1 ; |
... ; | +4
... ; |
MOV 0, <-1 ; <---'
Az ICWS '94 standard-be 4 újabb címzési mód került, ezek leginkább az A-mező indirekciókat kezelik le, így használhatjuk mind a 8 lehetséges módot:
# - közvetlen$ - relatív (a $ elhagyható)* - A-mező indirekt@ - B-mező indirekt{ - A-mező indirekt előcsökkentéssel< - B-mező indirekt előcsökkentéssel} - A-mező indirekt utónöveléssel> - B-mező indirekt utónövelésselA utónövelő mód hasonló az előcsökkentőhöz, azzal a különbséggel, hogy a célra mutató szám eggyel növekszik, miután az utasítás végrehajtódik (értelemszerűen).
DAT #5, #-10
MOV -1, }-1 ; következő utasítás
végrehajtás után a következőképpen néz ki:
DAT #6, #-10 ; --.
MOV -1, }-1 ; |
... ; |
... ; | +5
... ; |
DAT #5, #-10 ; <--'
Egy fontos dolgot meg kell jegyezni az előcsökkentéssel és az
utónöveléssel kapcsolatban: a mutató szám akkor is
csökkentve/növelve lesz, ha a címet nem használjuk semmire. Így
például a JMP -1, <100 is csökkenti a 100-as
utasítást, pedig azt, hogy az azon a címen lévő érték mire mutat,
nem használjuk. A DAT <50, <60 is csökkenti a
címeket, miközben megöli a process-t.
Ha figyelmesen nézted az utasítástáblázatot pár fejezettel
feljebb, akkor lehet, hogy meglepődtél az SPL nevű
utasításon. Ez természetesen nem található meg a szokásos assembly
nyelvekben...
Még a Core War korai történetében vetették fel, hogy a multitaskinget a játék részévé kellene tenni, mert úgy sokkal érdekesebb lenne. Mivel az egyszerű időosztásos technika nem illene az absztrakt Core War környezethez (mivel például egy operációs rendszer kellene, hogy kontrollálja), kitaláltak egy rendszert, miszerint minden ciklus alatt egy process hajtódik végre egy adott programból.
Az utasítás, amit új process-ek indítására használhatunk, az
SPL. Akárcsak a JMP-nél, itt is egy címet kell
megadnunk az A-mezőben. A különbség a JMP és az
SPL között, hogy az SPL amellett, hogy az új
címre ugrik, folytatja a végrehajtást a következő utasításon is
párhuzamosan.
A két - vagy több - így készült process egyenlően osztja a gépidőt
egymás közt. Egy szimpla process counter helyett, ami az aktuális
utasításra mutatna, a MARS egy process várakozó sort használ,
ahol a végrehajtandó processek listája áll, olyan sorrendben, ahogy
indítva lettek. Egy SPL-lel létrehozott process pontosan az
aktuális után illesztődik be (de abban a körben már nem hajtódik végre -
megj. a fordítótól), amik pedig DAT-ot hajtanak végre,
kikerülnek a sorból. Ha minden process leáll, a harcos veszít.
Fontos megjegyezni, hogy minden programnak saját process várakozó sora van. Ha több program van a core-ban, azok felváltva hajtódnak végre, egy ciklus alatt mindegyikből egy utasítás, tekintet nélkül a várakozó sor hosszára, és az idő mindig egyenlően osztva adott program processei közt. Ha az 1-es programnak 3 processe van, a 2-esnek pedig csak 1, akkor a végrehajtás sorrendje a következő lesz:
Végül egy rövid példa az SPL használatára. (Többet erről
majd a későbbiekben.)
SPL 0 ; itt kezdődik a végrehajtás
MOV 0, 1
Minthogy az SPL önmagára mutat, egy ciklus után a
process-ek így néznek ki:
SPL 0 ; itt a második process
MOV 0, 1 ; itt az első process
Miután mindkét process végrehajtódik, a core így fest:
SPL 0 ; itt a harmadik process
MOV 0, 1 ; itt a második process
MOV 0, 1 ; itt az első process
Tehát ez a kód láthatóan impek sorozatát indítja, egyiket a másik után,
és ezt egészen addig csinálja, amig az impek körbe nem érnek a core-on és
felül nem írják az SPL-t.
A process várakozó sor mérete minden programnál korlátozva van. Ha a
program eléri a maximális process-számot, akkor az SPL
csak a következő utasításon folytatja a végrehajtást,
tulajdonképpen leutánozva a NOP-ot. A legtöbb esetben a
process korlát igen nagy, gyakran a core méretével egyezik meg, de lehet
kevesebb is (akár 1; ekkor a multitasking nem engedélyezett).
És lám az igazság gyakran távolabb áll tőlünk, mint a fikció.
Nemrégiben egy web oldalon jártam, aminek címe ez volt: "Kitalált
utasítások". A tényleg abszurd ötletek közt találtam egy utasítást:
"BBW - Branch Both Ways - Ágazz kétfelé". Mivel a többi
utasítás tényleg kitaláció volt, megállapítottam, hogy a szerző nem túl
járatos a Redcode-ban...
A legfontosabb újdonságok, amit az ICWS '94 standard hozott, nem az
új utasítások vagy a címzésmódok voltak, hanem a módosítók. A
régi '88-as standard-ben a címzésmód elve eldöntötte, hogy az
utasítás melyik részére vonatkozik. Pl. a MOV 1, 2 mindig
egész utasítást mozgat, míg a MOV #1, 2 csak egy számot.
(És mindig a B-mezőre!)
Természetesen ez néha problémát okoz. Mit tegyünk, ha egy
utasításnak csak az A- és B-mezőjét akarjuk átmásolni, és nem az egész
címet. (Pl. ADD-ot akarsz használni.) És ha valamit
a B-mezőről az A-mezőre akarsz másolni? (Lehetséges, de csak igen
trükkösen.) A helyzet tisztázása végett vezették be az utasítás
mósosítókat.
A mósosítók nem mások mint toldalékok, amiket az utasítások
után írunk. Ezek határozzák meg, hogy a forrásnak és a célnak
melyik részére hasson az utasítás. Például a MOV.AB 4, 5
a 4-es utasítás A-mezőjét az 5-ös utasítás B-mezőjére
mozgatja. Összesen 7 különböző módosító van:
MOV.A - a forrás A-mezőjét a cél A-mezőjére mozgatja.MOV.B - a forrás B-mezőjét a cél B-mezőjére mozgatja.MOV.AB - a forrás A-mezőjét a cél B-mezőjére mozgatja.MOV.BA - a forrás B-mezőjét a cél A-mezőjére mozgatja.MOV.F - a forrás mindkét mezőjét a cél megfelelő mezőire mozgatja.MOV.X - a forrás mindkét mezőjét a cél ellentétes mezőire mozgatja.MOV.I - az egész forrás utasítást a célra mozgatja.Természetesen ugyanezek a módosítók használhatók minden utasításhoz,
nemcsak a MOV-hoz, bár néhány utasítás, mint a JMP
vagy az SPL nem veszi figyelembe őket. (Mért is tennék?
Nem mozgatnak adatot, csak ugranak!)
Láttuk, hogy nem minden módosító hat minden utasításra, de megpróbálnak a lehető
leghasonlóbban viselkedni ahhoz, amint a módosító jelez. A leggyakrabban
előforduló példa a .I módosító. A nyelv absztraktsága miatt
a numerikus műveletek nem lettek definiálva az utasításokra, így
a matematikai műveletek nincsenek hatással rájuk. Ez azt jelenti, hogy a
MOV, a SEQ, és az SNE (és persze a
CMP, ami ugyanaz, mint a SEQ) kivételével
a .I módosító ugyanazt jelenti, mint a .F.
Van egy másik dolog is, amit fontos megjegyezni a .I-ről
és a .F-ről, miszerint a címzésmódok is az utasítás
részei, így nem másolódnak át a MOV.F utasítás
hatására.
Át is írhatjuk a régi programjainkat úgy, hogy használjuk a
módosítókat. Az Imp természetesen MOV.I 0, 1 lesz. A
Dwarf pedig:
ADD.AB #4, 3
MOV.I 2, @2
JMP -2
DAT #0, #0
Megjegyezzük, hogy elhagytuk a módosítókat a JMP és a
DAT mögül, mert ott nincsenek használva. A MARS
egyébként beállítja őket JMP.B-re és
DAT.F-re, de kit érdekel?
Jajj, még valami! Honnan tudjuk, hogy milyen utasítás után milyen módosítót kell tenni, pontosabban mit tesz oda a MARS, ha elhagyjuk a módosítót? Általában egy kis érzékkel rá lehet jönni, de azért idetesszük a '94-es standard által definiált alapértelmezéseket.
DAT, NOP.F, de nincs figyelembe véve.MOV, SEQ, SNE, CMP.AB,.B,.I.ADD, SUB, MUL, DIV, MOD.AB,.B,.F.SLT, LDP, STP.AB,.B.JMP, JMZ, JMN,
DJN, SPL.B. (de a JMP és az
SPL nem veszi figyelembe)A # viselkedésének definíciója a '94-es standard-ben
kissé furcsa. Minthogy a standard-nek 100%-ig kompatibilisnek kellett
lennie a régi szintaxissal, a közvetlen címzésmódot nagyon okosan és
sajátságosan definiálták, ezáltal lehetővé vált az
utasítás-módosítók logikus használata, és a címzésmód igen
erős eszközzé vált.
Ismerve a módosítókat, lehet, hogy elgondolkozol azon, hogy a
MOV.F #7, 10 mit csinál? A .F-nek minkét
mezőt mozgatnia kellene, de a forrásban csak egy szám van!? Talán a 7
másolódik a cél minkét mezőjére?
Nem, definíció alapján nem. Az történik, hogy a 7-et átviszi a cél A-mezőjére, a 10-et pedig a B-mezőjére! Miért?
Ennek az az oka, hogy a '94-es standard-ben a forrás (és a cél is)
mindig egész utasítás. Közvetlen címzésmód esetén ez
egyszerűen az aktuális utasítás (tehát 0), akármi az értéke.
Így a MOV.F #7, 10 átmásolja a forrás (0) mindkét
mezőjét a célra (10). Meglepő, ugye?
Ugyanígy működik a dolog a MOV.I esetén is. A
közvetlen címzés eme definíciójának köszönhetően használhatunk
olyan utasításokat is (akár módosító nélkülieket), amiknek a
'88-as standardben nem sok értelmük lett volna, például JMP
#1234. Természetesen nem ugorhatunk egy számra, de ugorhatunk
ennek a számnak a címére, vagyis 0-ra. Ez aztán sok lehetőséget
kínál nekünk, nemcsak hogy "ingyen" tárolhatunk adatot az A-mezőn,
de még a kód meg sem sérül nagyon, ha valaki csökkenti eggyel az
utasítást. Ezek után írjuk át a korábbi imp-készítő kódot kicsit
robosztusabbra:
SPL #0, }1
MOV.I #1234, 1
Ez is ugyanúgy működik, de most az A-mezők szabadok. Csak viccből
csináltam, hogy az SPL növelje az imp A-mezőjét, így
mindegyik imp egy kicsit más. Minthogy az SPL egyébként nem
használja a B-mezőt, ez a növelés szintén "szabad". Hidd el
működik - vagy próbáld ki!
Már tudnod kell, hogy a core-ban a címek körbefordulnak, vagyis az az utasítás, ami egy core-méretnyivel az aktuális előtt vagy után vannak, az az aktuális utasítás önmaga. Azonban ez a tény sokkal tovább mutat: a Core War-ban minden szám mindig átkonvertálódik a 0 - core-méret-1 intervallumba.
Azoknak, akik már tudnak programozni, és ismerik a gépi egészek matematikáját azt is mondhattam volna, hogy a Core War minden számot előjeltelen egésznek tekint és a legnagyobb tárolható szám a core-méret-1. Ha ez nem teljesen világos, olvass tovább...
Gyakorlatilag a Core War-ban minden szám elosztódik a core hosszával, és csak a maradékot tekintjük. Ez úgy képzelhető el legkönnyebben, hogy van egy számológép, ami 8 számjegyet képes kiírni, és ami ezen felül van azt (a szám elejéről) elhagyja. Így a 100*12345678 (ami 1234567800 lenne) 34567800-ként lesz kijelezve (és eltárolva). Hasonlóan, 8000 utasításos core-ral, 7900+222 (8122) csak 122 lesz.
És mi történik a negatív számokkal? Azok szintén normalizálódnak, mégpedig úgy, hogy a core-méret annyiszorosát adjuk hozzájuk, hogy pozitívak legyenek. Ez azt jelenti, hogy amikor én -1-et írtam, azt tulajdonképpen core-méret-1-ként tárolja a MARS, vagyis a szokásos 8000 utasításos core esetén 7999-ként.
Természetesen ez nem okoz problémát a címeknél, amik egyébként is
körmefordulnak. Az egyszerű matematikai műveletek, mint az
ADD vagy a SUB is rendben vannak, mivel
például core-méret=8000 esetén 6+7998 ugyanúgy 4-et
(8004-et) ad eredményül, mint a 6-2.
Akkor mi a probléma? Nos, van néhány utasítás, ahol a dolog
változtat a működésen. Ezek a DIV a MOV
és az SLT. Ezek is mindig előjeltelenné alakítják
a számokat. Ez azt jelenti, hogy -2/2 nem -1, hanem (core-méret-2)/2 =
(core-méret/2)-1. (Vagyis core-méret=8000-re 7998/2=3999,
nem 7999.) Hasonló okok miatt észleli az SLT a -2-t (7998)
nagyobbnak a 0-nál! Ténylegesen a Core War-ban a 0 a lehetséges
legkisebb szám, így az összes többi szám nála nagyobb.
Rendben, a türelmed elnyerte jutalmát. Ezidáig csak információ-részleteket kaptál tőlem. Itt az idő mindent megtudni egyszerre, tehát következnek az utasítások
Persze az itt következőket a legelejére kellett volna vennem, amikor az utasításkészletről volt szó, és ezzel talán megkíméltelek volna egy csomó találgatástól. De nekem (legalábbis szerintem) nagyon jó okom volt várni. Nemcsak azért, hogy az unalmas elméleti fejtegetés előtt mutathassak néhány praktikus kódot, hanem azért is, mert azt akartam, hogy előbb értsd meg a címzésmódok és a módosítók lényegét, és csak aztán az utasításokat részleteikben. Ha az utasításokról a módosítók előtt írtam volna, akkor először a régi '88-as szabályokat kellett volna megtanulnod, aztán az egészet újra a módosítókkal. Nem azt mondom, hogy ez rossz módszer a Redcode megtanulására, de ez szükségtelenül bonyolulttá tette volna ezt az ismertetőt.
DATDAT-ot
adattárolásra találták ki, mint ahogy a legtöbb programozási
nyelvben van. De minthogy a Core War-ban annál jobb, minél kevesebb
utasítással oldunk meg valamit, ezért a pointereket stb. gyakran
tároljuk utasítások használatlan mezőjén. Ez pedig azt jelenti, hogy
a DAT legfontosabb feladata, hogy végrehajtásával
megállíthatunk egy process-t. Természtesen mivel a '94-es standard-ben
nincs illegális utasítás, ezért a DAT is teljesen
legális, pontosan definiált, a definíciója pedig: törli az
éppen végrehajtott process-t a várakozó sorból. Lehet, hogy
most kicsit lefárasztottalak, de ugye az utasítások precíz
definíciójára a félreértések elkerülése végett igenis szükség
van.DAT-ra,
ezért néhány MARS el is hagyta őket. Viszont ne felejtsük el, hogy
az utónövelés és az előcsökkentés mindig végrehajtódik, akkor is,
ha a mezőt semmire nem használjuk. Egy használhatatlan információ a
DAT-ról, ami az előző standard-ekkel való kompatibilitás
miatt maradt fenn: ha csak egy argumentuma van, akkor az a
B-mezőre kerül.MOVMOV adatot másol egyik címről a másikra. Ha még
nem tudsz mindent róla, akkor újra kellene olvasnod a korábbi
fejezeteket. A MOV az egyike azon kevés utasításnak,
amelyek támogatják a .I módosítót, és ráadásul ez az
alapértelmezés, ha nem adunk meg módosítót (és egyik mező sem
használ közvetlen címzést).ADDADD hozzáadja a forrás értékét/értékeit a cél
értékéhez/értékeihez. A módosítók ugyanúgy működnek mint a
MOV esetén, kivéve persze, hogy .I nincs
támogatva, hanem .F-ként viselkedik. (Mert mi lenne
például MOV.AB+DJN.F?) Ezen kívül ne
feledjük, hogy a Core War-ban minden matematikai művelet
modulo core-méret lesz végrehajva.SUBADD, kivéve a
nyilvánvaló különbséget. Természetesen az "aritmetikai-logikai"
utasítások nagyrészt ugyanúgy működnek...MULMUL-ra is igaz. Ha nem tudod kitalálni mit
csinál, akkor valószínűleg kihagytál valami fontosat.DIVDIV szintén
úgy működik, mint a MUL és a többiek, de egy pár dolgot
észben kell tartanunk. Először is, hogy előjel
nélküli osztást hajt végre, ami néha meglepő eredményt szolgáltathat.
A nullával való osztás megállítja a process-t, mintha egy
DAT hajtódott volna végre, és a cél címen nem változtat. Ha
DIV.F-et vagy DIV.X-et használsz két számpár
egyidejű elosztására, és az egyik osztó 0, a másik osztás normálisan el
lesz végezve.MODDIV-ről mondtam itt is érvényes, beleértve
a nullával való osztást is. Ne feledjük, hogy a MOD.AB
#10,#-1 jellegű utasítások eredménye függ a core méretétől. A
szokások 8000 utasításos core esetén az eredmény 9 (7999 mod 10)JMPJMP átadja a vezérlést az A-mezőjében megadott címre. A
nyilvánvaló, de fontos eltérés a "matematikai" utasításoktól, hogy a
JMP csak a címmel törődik, nem az adattal, amire a cím mutat.
Egy másik fontos különbség, hogy a JMP nem használja a
B-mezőjét (és így a módosítóját sem). Két címre ugrani (vagy egy
process-t ugratni, lásd SPL) egyszerre túl erős fegyver
lenne, azonkívül a következő három utasítás definiálását is megnehezítené.
Ne felejtsük el, hogy rakhatunk növelést vagy csökkentést a nem használ
B-mezőbe, ami szerencsés esetben sérülést okozhat az ellenfél
kódjában.JMZJMP-hez hasonlóan működik, de ahelyett,
hogy figyelmen kívül hagyná a B-mezőjét, megnézi az értéket amire az
mutat, és csak akkor ugrik, ha az nulla. Egyébként a végrehajtás a
következő utasításon folytatódik. Mithogy csak egy vizsgálandó utaítás
van, a választható módosítók száma jelentősen korlátozott. A
.AB ugyanazt jelenti mint a .B, a
.BA ugyanaz, mint a .A, valamint a
.X és a .I ugyanaz, mint a
.F. Ha JMZ.F-fel egy utasítás minkét mezőjét
vizsgálod, az ugrás csak akkor történik meg, ha mindkét mező
nulla.JMNJMN ugyanúgy működik, mint a JMZ, de
akkor ugrik, ha a vizsgál érték nem nulla (micsoda
meglepetés...). A JMN.F akkor ugrik, ha valamelyik mező
nem nulla.DJNDJN olyan mint a JMN, de vizsgált
értéket csökkenti eggyel, mielőtt leteszteli azt. Ez az utasítás
jól használható ciklusszervezésre, de szokták az ellenfél kódjának
elrontására is használni.SPLSPL utasítás bevezetése
volt talán a legnagyobb változás a nyelvben, legalább akkora, mint az
ICWS '94-es standard bevezetése. Az SPL úgy működik, mint a
JMP, de a végrehajtás a következő utasításon is
folytatódik, így a process "kettészakad" két új process-re. A következő
utasításon lévő process a másik címen lévő előtt hajtódik végre.
Ez egy apró, de nagyon fontos részlet (a legtöbb mai harcos nem mőködne
enélkül). Ha a program eléri a maximális process-számot, akkor az
SPL síma NOP-ként fog működni. Akár csak a
JMP, az SPL is figyelmen kívül hagyja a
B-mezőjét, és az arra vonatkozó módosítókat.SEQSEQ összehasonlít két utasítást, és kihagyja a
következő utasítást, ha azok egyenlőek. (Mindig csak két utasításnyit
ugrik előre mert mér nincs hely az ugrási címnek.) Minthogy az utasítás
csak egyenlőséget vizsgál, a .I módosító használható.
Teljesen természetes, hogy a
.F, .X és a .I módosítókkal a
következő utasítás csak akkor lesz átugorva, ha minden mező
egyenlő.SNEJMZ-nél vagy a
JMN-nél...)CMPCPM a SEQ másik neve. A SEQ
és a SNE bevezetése előtt ez volt az utasítás egyetlen neve.
Manapség nem sokat számít melyik nevet használod, mivel a legtöbb népszerű
MARS program a SEQ-t is felismeri '88-as módban is.SLTSLT is átugorja a
következő utasítást, ezúttal akkor, ha az első érték kisebb mint a
második. Mivel ez egy aritmetikai, és nem egy logikai összehasonlítás,
ezért nem reagál az a .I-re. Úgy tűnhet, hogy kellene egy
SGT nevű utasítás is (skip if greater than), de
a legtöbb esetben a dolog véghez vihető az SLT-vel,
operandusok megcserélésével. Ne feledjük, hogy
minden érték előjeltelennek lesz tekintve,
így a 0 a legkisebb, a -1 pedig a legnagyobb lehetséges
szám.NOPBiztos feltűnt, hogy két utasítás, név szerint az LDP és
az STP még hiányzik. Ezek igen új elemei a nyelvnek, és
beszélünk is róluk... öööö, nos azonnal. :-)
A P-space a legutolsó módosítás a Redcode-ban, amit a pMARS 0.8-ban mutattak be. A "P" jelenthet privátot, permanent-et (állandó), personal-t (személyes), patetikusat, és így tovább, amit szeretnél. Alapvetően a P-space egy hely a memóriában, amit csak a te programod érhet el, és aminek tartalma megmarad a fordulók közt többfordulós meccsek esetén.
A P-space több dologban különbözik a normál core-tól. Először is a
P-space minden eleme csak egy számot tárolhat, nem egy egész utasítást.
Másrészt a címzés a P-space-ben abszolút, azaz az 1-es P-space cím mindig
ugyanaz, függetlenül attól, hogy az őt tartalmazó utasítás hol van a
core-ban. Végül, de nem utolsósorban, a P-space csak két
spciális utasítással érhető el, az
LDP-vel és az STP-vel.
A fenti utasítások szintaxisa egy kicsit szokatlan. Az
STP például tekinti forrását a szokásos módon, és ezt rakja
be a P-space-be, a cél által mutatott helyre. Így a P-space címet nem a
célcím határozza meg, hanem a cél értéke, vagyis az az
érték, amit az STP felülírna, ha MOV lenne.
Tehát például az STP.AB #4, #5 a 4-es értéket
tenné az 5-ös P-space címre. Hasonlóan,
STP.B 2, 3
...
DAT #0, #10
DAT #0, #7
a 10-es értéket a 7-es P-space címre tenné, nem a
3-asra! Ebből szép kis kavarodás lehet, ha az STP maga is
indirekt címzést használ, mert ebből "dupla indirekció" lesz.
Az LDP ugyanígy működik, kivéve, hogy most a forrás egy
P-space cím, a cél pedig egy core utasítás. A 0-ás P-space cím egy
speciális csak olvasható cím. Az oda történő írási kisérletek nem lesznek
végrehajtva. Minden kör előtt speciális értéket vesz fel: ez -1, az első
körben, 0, ha a program meghalt az előző körben, egyébkent az életben
maradt programok száma. Ez annyit jelent, hogy az egy-egy elleni meccsekre
0 jelenti a vereséget, 1 a győzelmet, 2 pedig a döntetlent.
A P-space hossza sokkal rövidebb a core-énál, tipikusan a core méretének 1/16-oda. A címek a P-scape-ben körbefordulnak, akár csak a core-ban. A P-space méretének így érdemes a core-méret hányadosát választani, különben valami borzasztó történik.
Még egy különleges dolog van a pMARS-ban a P-space-ről. Mivel az volt a
cél, hogy a P-scape lassú elérésű legyen, így nem engedték meg, hogy egy
utasítással két érték legyen elérhető. Ez egy Jó Dolog, bár az eredmény
egy kicsit idétlen. Ez itt azt jelenti, hogy az
LDP.F, a .X és a .I
mind úgy működnek, mint az LDP.B! (És ugyanez érvényes az
STP-re is, természetesen.)
A P-space-t messze leggyakrabban a stratégia kiválasztására használják. Ennek legegyszerűbb formája, hogy kimentjük az előző stratégiát a P-space-be, és változtatunk rajta, ha 0-ás P-space cím azt mondja, vesztettünk. Az ilyen programokat hívják P-harcosoknak, P-agyaknak vagy P-keknek. (ejtsd: pékek)
Sajnos a P-space nem olyan privát mint a milyennek tűnik. Mert bár az
ellenfeled nem tudja közvetlenül a P-space-ed írni vagy olvasni,
elkaphatja a process-edet, és kényszerítheti, hogy az ő kódját hajtsa
végre ami pedig tele lehet STP-vel. Ezt a techikát
agymosásnak hívják, és minden P-harcosnak védekeznie kell ellene, nem
pedig bedilizni, ha a stratégia címen valami szörnyűség van.
Eddig a példaprogramjaink minden címét az aktuális utasításhoz viszonyított relatív utasításszámként adtuk meg. Ez hosszabb programokban problémás lehet, nem beszélve arról, hogy nehezen átlátható. Szerencsére nem kell így tennünk, mert a Redcode-ban lehetőségünk van címkék, szimbolikus konstansok, makrók és egyéb olyan dolgok használatára, amit az ember egy jó assembler-től elvár. Csak annyit kell tennünk, hogy megcímkézzük az utasításokat és a címkéjükre hivatkozzunk, és a fordító kiszámolja a relatív címet helyettünk. Például:
imp: mov.i imp, imp+1
Óhhhh, mi történt? Ez a program pontosan ugyanaz, amit a legelején már láttunk. Csak éppen a numerikus címeket az "imp" címkére cseréltem. Persze jelen esetben ez nem nagyon szükséges. Az egyetlen utasítás, amelyikben a címkét használtuk az "imp" maga, így a címke 0-ra cserélhető.
A végrehajtás előtt, a MARS fordítója az összes címkét
és egyéb szimbólumot a már ismert számokra változtatja.
Az ilyen "előfordított" Redcode file neve load
file, mivel minden MARS ismeri a load file formátumot,
de nem mindnek van tényleges fordítója. (load = betöltés)
Load file formátumban az előző kód MOV.I 0, 1
lesz. Ugyanezt a kódot megírhatjuk így is:
imp: mov.i imp, next next: dat 0, 0 ; vagy bármi
Ebben az esetben a "next" cíkéjű utasítás egy utasítással
az "imp" után van, így 1-re cserélődik. Emlékezzünk rá, hogy
a valódi címek továbbra is relatív számok, így az Imp azután
is MOV.I 0, 1 miután előremásolta magát a "next"-re.
A : a címkék után tulajdonképpen nem szükséges.
Azért használtam, hogy könnyebben lásd, hol vannak a címkék, de
általában nem használom őket saját programjaimban. Ez ízlés dolga.
Ja, és csak ha érdekel, a Redcode nem érzékeny a kis- és nagybetűkre. (Ez így, ebben a formában nem igaz. A címkék ugyanis kis-nagybetű érzékenyek. Az utasítások valóban nem. Megj. a fordítótól.) Én úgy szeretem, hogy kisbetűt használok a forrásokban, mert az úgy szebb, és a nagybetűket csak a "load file" formában. (Leginkább, mert ez a szokás.)
Igaz, hogy az előző fejezetek példái, általában lefordíthatók, de mégsem igazán kész programok, csak azok részei. Egy tipikus Redcode file tartalmaz néhány extra információt a MARS-ról.
;redcode-94
;name Imp
;author A.K. Dewdney
org imp
imp: mov.i imp, imp+1
end
Amint azt valószínűleg kikövetkeztetted, minden ami a ;
után van az a Redcode-ban megjegyzés. E program első sorai mégsem
szokásos megjegyzések. A MARS arra használja őket, hogy néhány
információt kiszűrjön belőlük a programról.
Az első sor, a ;redcode-94 jelenti, hogy ez
tényleg egy Redcode file. Minden, ami ez előtt a sor előtt
van, szintén megjegyzés. Igazából a MARS csak egy olyan
sort keres, ami úgy kezdődik, hogy ;redcode,
és így a sor maradék része használható a Redcode fajtájának
azonosítására. Például a KotH szerverek
beolvassák ezt a sort maguknak, és ebből azonosítják,
hogy a program melyik hegyre tart.
A ;name (=név) és az ;author (=szerző)
sorok csak információkat adnak a programról. Persze ezeket
bármilyen formában odaírhatod, de ha ezt a formát használod,
akkor a MARS megtalálja sorokat, és pl. kijelezheti az
információt, mikor a program fut.
A sor, amelyikben az END van, a program végét
jelzi. Minden, ami utána van, szintén megjegyzés. Együtt
a ;redcode-dal használahtók például arra, hogy
belerakj egy Redcode programot egy e-mail-be.
A sor, amiben az ORG van, megmutatja, hogy
hol kezdődjön a program végrehajtása. Így újabb utasításokat
tehetünk a program eleje elé. Az ORG parancs
a '94-es standard újításainak egyike. A régebbi szintaxisban
ami még mindig használahtó modern programokban is, a
kezdőcímet az END argumentumaként adták meg.
;redcode-94
;name Imp
;author A.K. Dewdney
imp: mov.i imp, imp+1
end imp
Egyszerű, tömör, kár, hogy egy kissé illogikus. Azon kívül
hosszú programoknál azok végére kell menned, hogy meglásd, hogy
hol kezdődnek. A Redcode terminológiában az ORG
és az END neve pszeudo-utasítás. Rendes
utasításoknak néznek ki, de nem fordítódnak bele a programba.
De elég az Imp-ből. Nézzük, hogy nézne ki a Dwarf modern Redcode-ban:
;redcode-94
;name Dwarf
;author A.K. Dewdney
;strategy Bombs the core at regular intervals.
;(A fenti sor magyarul: egyenlő intervallumokban bomázza a core-t.)
;(kissé módosította: Ilmari Karonen)
;assert CORESIZE % 4 == 0
org loop
loop: add.ab #4, bomb
mov.i bomb, @bomb
jmp loop
bomb: dat #0, #0
end
Ezek a címkék sokkal érthetőbbé teszik a programot, nem?
Megfigyelhető, hogy újabb két megjegyzés sort tettem hozzá a
programhoz. A ;strategy sor röviden leírja a programot.
Akárhány ilyen sor lehet a programban. A legtöbb jelenlegi
MARS kihagyja őket, szóval hagyományos megjegyzést is használhatsz
erre (mint annál a sornál, amiben a nevem van), de a Hegyek
kijelzik a ;strategy sorokat másoknak. Ha az előbbi
programot elküldjük az egyikre, valami ilyesmi lesz a többieknek
kijelezve:
A new challenger has appeared on the '94 hill! Dwarf by A.K. Dewdney: (length 4) ;strategy Bombs the core at regular intervals. [egyéb információ...]Új program jelent meg a '94 hegyen! Dwarf, írta A.K. Dewdney: (hossz 4) ;strategy Bombs the core at regular intervals. [egyéb információ...]
A másik új elem a példánkban a ;assert sor. Arra
használjuk, hogy biztosak lehessünk abban, hogy a program tényleg
működik az érvényben lévő beállításokkal. A Dwarf például megöli
magát, ha a core mérete nem osztahtó 4-gyel. Ezért használtam a
;assert CORESIZE % 4 == 0-t, hogy biztos lehessek benne,
hogy igaz.
A CORESIZE előre definiált konstans, ami a core
hosszát tartalmazza. Ekkor az n+CORESIZE
mindig ugyanaz a cím, mint az n. A %
a modulus operátor, ami az osztásnál keletkező maradékot adja.
Az ;assert sorokban és máshol a Redcode-ban
használt kifejezések szintaxisa ugyanaz, mint a C-nyelvben,
bár az operátorkészlet jelentősen korlátozott.
Azoknak, akik nem tudnak C-ben, íme egy lista a Redcode kifejezésekben használható operátorokról:
+ összeadás- kivonás (vagy negáció)* szorzás/ osztás% modulus (maradék)== egyenlő!= nem egyenlő< kisebb mint> nagyobb mint<= kisebb vagy egyenlő>= nagyobb vagy egyenlő&& és|| vagy! nem= értéket ad egy változónak Az ;assert-et egy logikai kifejezés követi. Ha ez hamis, a programot le
sem fordítják. C-ben a 0 érték hamisat jelent, minden más igazat. A logikai és az
összehasonlító operátorok minden igaz értékre 1-et adnak vissza, ami később hasznos
lesz nekünk.
Az ;assert tipikus használata, hogy leellenőrizzük, hogy a core
hossza annyi-e, mint amekkora core-ra programot terveztük, pl.
;assert CORESIZE==8000. Ha a program P-space-t
használ, akkor a ;assert PSPACESIZE > 0 paranccsal
leellenőrizhetjük, hogy van-e P-space. Minthogy a péladprogramunk,
a Dwarf, elég alkalmazkodó, így csak a CORESIZE oszthatóságát
teszteltem, nem a konkrét méretét. Az Impnek, ami bármilyen
beállítással elfut, ;assert 1-et vagy
;assert 0==0-t szokás megadni, vagy valamit, ami mindig
igaz értéket ad. Ez azért hasznos, mert különben a MARS "missing
;assert line -- warrior may not work with the current settings."
(hiányzó ;assert sor -- a harcos lehet, hogy nem működik az aktuális
beállításokkal.) üzenetet ad.
Néhány előre definiált konstansot, mint pl. a CORESIZE-ot, még a '94-es standard-ben definiáltak, utána még néhany továbbit hozzátettek. A pMARS 0.8 minimum a következőket ismeri:
Az előre definiált konstansok hasznosak, meg a címkék is, de ennyi az egész? Nem lehet változókat, vagy valami hasonlót használni?
Hát, a Redcode assembly nyelv, és nem igazán használ változókat. De van
valami, ami majdnem olyan jó, sőt, esetenként jobb is. Az EQU
pszeudo-utasítással definiálhatunk saját konstansokat, kifejezéseket, vagy
akár makrókat is. Ilyenformán:
step equ 2667
Ezután a step mindig kicserélődik 2667-re. Van azonban egy
csapdája a dolognak: a csere szöveges, nem numerikus. Ebben az esetben ez
nem okazhat gondot, ám miközben ez az EQU-t komoly eszközzé
teszi, okozhat néhány problémát, amit a C programozók már jól ismernek.
Vegyünk egy példát:
step equ 2667 target equ step-100 start mov.i target, step-target
A MOV A-mezője 2567 lesz, ahogy vártuk. Ám a B-mező
értéke 2667-2667-100 == -100 lesz, nem pedig
2667-(2667-100) == 2667-2567 == 100, ahogy azt valószínűleg akartuk
volna. A megoldás egyszerű. Tegyünk zárójelet az összes EQU-s
kifejezés köré, pl. "target equ (step-100)".
A pMARS modern verzióiban megengedett a többsoros equ
használata is, így létrehozhatunk hosszabb makrókat. Íme a módszer:
dec7 equ dat #1, #1
equ dat $1, $1
equ dat @1, @1
equ dat *1, *1
equ dat {1, {1
equ dat }1, }1
equ dat <1, <1
decoy dec7
dec7 ; 21 utasításos csapda
dec7
Van még a pMARS fordítónak néhány képessége ami eddig kimaradt.
Ami most következik talán erősebb eszköz (és bonyolultabb),
mint bármelyik másik. A FOR ROF
pszeudo-utasítások nemcsak rövidebbé teszik a forrást és
könnyebben lehet velük komplex kódsorozatot létrehozni, hanem
a segítségükkel készíthetünk feltételes kódot különböző
beállításokhoz.
A FOR blokk - igen, kitaláltad - a FOR
pszeudo-utasítással kezdődik, majd egy szám következik: az, hogy hányszor
kell a blokkot megismételni. Ha a blokk előtt címke van, akkor az
ciklusváltozóként lesz használva:
index for 7
dat index, 10-index
rof
Amint látható, a blokk ROF-fal végződik. (Szerintem sokkal jobb,
mint a régi NEXT vagy REPEAT klisék.)
Az előző blokkot a pMARS a következőnek fordítja:
DAT.F $1, $9
DAT.F $2, $8
DAT.F $3, $7
DAT.F $4, $6
DAT.F $5, $5
DAT.F $6, $4
DAT.F $7, $3
Lehetőségünk van több FOR blokk egymásba ágyazására.
A blokkok akár EQU-kat is tartalmazhatnak; ezzel néhány
nagyon érdekes kódot írhatunk. Egy másik hasznos dolog, hogy a
ciklusszámláló hosszáfűzhető egy címkéhez is a & operátor
segítségével. Ezt gyakran arra használjuk, hogy elkerüljük egy címke
többszöri deklarálását, de számos más célra is hasznos lehet.
dest01 equ 1000
dest02 equ 1234
dest03 equ 1666
dest04 equ (CORESIZE-1111)
jtable
ix for 4
jump&ix spl dest&ix
djn.b jump&ix, #ix
rof
Ez a FOR/ROF lefordítása után a következő lesz:
jtable
jump01 spl dest01
djn.b jump01, #1
jump02 spl dest02
djn.b jump02, #2
jump03 spl dest03
djn.b jump03, #3
jump04 spl dest04
djn.b jump04, #4
Hogy mire használható még, az csak a képzeletedtől függ. Az egyetlen
harcosok, amiket én ilyen komplex kifejezéseket használni láttam,
az néhány quickscanner (gyorskereső) volt. Az előredefiniált konstansok
szintén használhatók a FOR/ROF-fal, például:
; A harcos fő része itt van
decoy
foo for (MAXLENGTH-CURLINE)
dat 1, 1
rof
end
Az előzőek feltöltik a harcosod maradék részét DAT 1, 1-gyel.
Az ilyen csapda eltérítheti más harcosok támadását, ha gondoskodsz róla,
hogy a programodat elmásold (boot-old) a csapdától távolra.
Megjegyezném, hogy definiáltam egy foo nevű ciklusváltozót,
holott nem használtam semmire. Ezt azért tettem, mert különben a MARS
a decoy-t ciklusváltozónak hitte volna, pedig az címke.
;redcode-94
;name Tricky
;author Ilmari Karonen
;strategy Valami nagyon bonyolult bizbaz harcos
;strategy (A feltételes kód önmagyarázó példája)
;assert CORESIZE == 8000 || CORESIZE == 800
;assert MAXPROCESSES >= 256 && MAXPROCESSES < 10000
;assert MAXLENGTH >= 100
org start
for 0
Ez itt egy for/rof megjegyzés blokk. Nullaszor lesz ismételve,
ami annyit jelent, hogy a MARS minden itt lévő dolgot kihagy.
Ez egy tökéletes hely arra, hogy elmagyarázzuk azt a bonyolult
stratégiát, amit a harcos használ.
rof
;Természetesen hagyományos megjegyzések használata is megengedett.
;Mindig azt a módszert használod, amelyiket akarod.
for (CORESIZE == 8000)
step equ normalstep
;Mivel az igaz összehasonlítás eredménye 1, a hamisé pedig 0, ez a
;kód akkor lesz lefordítva, ha az összehasonlítás igaz.
rof
for (CORESIZE == 800)
step equ tinystep
;Ide pedig berakhatjuk a kisebb core-ra optimalizált konstansokat.
rof
for 0
;strategy Mivel a strategy és az assert sorok valódi megjegyzések,
;strategy ezért még FOR 0 / ROF blokkban is le lesznek fordítva!
rof
;[A tényleges kód helye..]
A probléma az EQU által definiált konstansokkal az, hogy, hmmm,
konstansok. Ha egyszer definiáltad őket, akkor nem tudod megváltoztatni
az értéküket. A legtöbb esetben jók, de néhány dologra szinte lehetetlen
használni őket.
Szerencsére a pMARS lehetőséget ad egy pár valódi változó használatára is. A használatuk egy kicsit trükkös, és nagyon rég nem láttam senkit használni őket, de tényleg léteznek.
A változónév mindig egybetűs, effektíve lekorlátozva a számukat
26-ra (a-tól z-ig). Az EQU használata
helyett az értékadás a = operátorral történik. A trükk az,
hogy az operátor csak kifejezésban használható. És mivel a pMARS nem
ismeri a vesszőoperátort, szükség lehet egy kamu kifejezés írására.
Persze a változók így is hasznosak. Például a következő automatikusan generált Fibonacci-sorozatot nem lehetne megírni nélkülük.
dat #1, (g=0)+(f=1)
idx for 15
dat #idx+1, (f=f+g)+((g=f-g) && 0)
rof
Megjegyzendő, hogy a (g=f-g) kifejezés a 0-val való
AND-elés miatt "rejtett" lesz. A rendszer működik, mert a pMARS nem
változtat a műveletek sorrendjén, hanem az összeadásnak mindig a bal
oldalát értékeli ki először, és mire a jobb oldalt számolja, az
f értékét már megnövelte.
Oké, majdnem elfelejtettem. Van még egy pszeudo-utasítás, ami kimaradt.
Szinte soha nem használatos, de igen, van. Ez a PIN, ami
"P-space Identification Number"-t, azaz "P-space azonosító szám"-ot jelent.
Ha két programnak ugyanaz a PIN-je, akkor a P-space-ük
közös lesz. Ez a process-ek közötti kommunikációra vagy kooperációra ad
lehetőséget. Sajnos a módszer nem tűnik eredményesnek a gyors és
hatékony kommunikáció megvalósítására. Persze ha meg akarod próbálni, hát
rajta. Sosem lehet tudni, sikerül-e...
Ha a programnak nincs PIN-ja, akkor a P-space-e
mindig privát lesz. De ha esteleg két program osztozik a P-space-en,
a speciális csak olvasható 0-ás cím mindig privát lesz.
Ha még nem tudsz róla, a King of the Hill (A Hegy Királya) szerverek folyamatos Core War versenyek az Interneten. A harcosokat e-mailben küldik a szerverre, ahol megküzdenek az összes (általában 10-30) hegyen lévő programmal. Az a program, amelyik a legkevesebb pontot gyűjti összesen, leesik a hegyről, ahol az új harcos váltja fel. (Aki jobb pontszámot ér el legyalább az egyik eredeti programnál.)
Jelenleg két KotH szerver fut, mindkettő számos különböző heggyel:
a StormKing KotH szerver a koth@koth.org-on és
az Internet Pizza KotH szerver a
pizza@ecst.csuchico.edu-on. Minkettő használ sok olyan parancsot,
amiről nem volt szó, leginkább speciális megjegyzéssorokat,
mint a ;kill vagy a ;password.
Mivel azonban ezek a parancsok szerverenként különbözőek,
érdemes átnézni a saját dokumentációikat információkért.
Megjegyezzük, hogy a hegyek, időmegtakarítás végett általában
előre lefordítják a harcosokat load file-okká és ezt a formát
tárolják, futtatják. Ez az oka annak, hogy némely előre definiált
konstans, pl. a WARRIORS, szabálytalan, ami rejtélyes
;assert hibát eredményez.
= operátorról.
Nevezhetnénk az "1. verziónak" is... (1997. május 5.)