Zajφmavou kapitolou je nßvrh API pro tvorbu organism∙. Organismus je
tvo°en knihovnou DLL, kterß musφ povinn∞ obsahovat funkce
OrgGetRequieredVersion
a OrgRun
. The Life! po
svΘm spuÜt∞nφ naΦte vÜechny DLL knihovny v adresß°i Org
,
zjistφ, zda exportujφ zmφn∞nΘ funkce, a zavolß
GetRequiredVersion
. Ta musφ v souΦasnΘ verzi vrßtit hodnotu
$00050000
, co₧ znaΦφ, ₧e organismus je urΦen pro verzi API 5.0.
Z toho je vid∞t, ₧e API jsem v pr∙b∞hu v²voje upravoval a to tak, ₧e
nebylo zp∞tn∞ kompatibilnφ. P°i ka₧dΘ takovΘto ·prav∞ jsem Φφslo verze
zvedl a bude to tak pravd∞podobn∞ i v budoucφch verzφch. Organismy, kterΘ
vrßtφ sprßvnΘ Φφslo verze API, jsou pak zobrazeny v The Life! v seznamu
organism∙.
P°i ka₧dΘm kroku simulace se pro ka₧d² organismus volß procedura
OrgRun
p°φsluÜnΘho DLL. Jako parametr se jφ p°edßvß ukazatel
na rozhranφ (interface) IVLFOrganism
. Volßnφm jeho metod pak
realizuje organismus sv∙j "₧ivot".
ProΦ jsem zvolil zrovna rozhranφ? Je to ve Windows asi nejstandardn∞jÜφ metoda, jak p°edat odkaz na soubor metod manipulujφcφ s objektem (v C++ by se tomu °φkalo abstraktnφ t°φda). Podstatnou v²hodou je takΘ to, ₧e rozhranφ podporujφ prakticky vÜechna v²vojovß prost°edφ ve Windows (od Borlandu, Microsoftu i od jin²ch v²robc∙), tak₧e to usnad≥uje tvorbu organism∙ v jin²ch jazycφch.
Nev²hoda rozhranφ je, ₧e se u n∞j automaticky poΦφtajφ reference a kdy₧ jejich poΦet dosßhne nuly, zavolß se destruktor objektu, kter² rozhranφ implementuje. Nevφm, koho v Microsoftu napadlo mφchat dohromady abstraktnφ t°φdu a poΦφtßnφ referencφ, ale docela by m∞ zajφmaly d∙vody. Object Pascal poΦφtßnφ referencφ zajiÜ¥uje automaticky p°i p°i°azovßnφ, p°edßvßnφ jako parametr apod. To je sice hezkΘ, ale za urΦit²ch podmφnek (nap°.p°i p°etypovßnφ a ulo₧enφ objektu jako pointer) se mechanismus chovß nekonzistentn∞ a nenφ lehkΘ ohlφdat, aby byl poΦet referencφ sprßvn². V jin²ch jazycφch si vÜe musφ programßtor d∞lat ruΦn∞ sßm.
Ka₧dß DLL organismu je naΦtena jen jednou, nenφ tedy tvo°ena separßtnφ instance pro ka₧d² organismus (to by drasticky snφ₧ilo v²kon aplikace). To mß ten d∙sledek, ₧e pokud v souborech tvo°φcφch knihovnu deklarujeme globßlnφ prom∞nnΘ, jsou tyto p°φstupnΘ z ka₧dΘho organismu. Proto₧e by to p°φliÜ zjednoduÜovalo komunikaci mezi organismy, pova₧uji globßlnφ prom∞nnΘ v organismech za zakßzanΘ. Samoz°ejm∞ je na autorovi organismu, zda to bude dodr₧ovat, ale zdß se mi to fΘrovΘ.
Zajφmavou mo₧nostφ, jak obejφt API, je p°epsat pam∞¥ s daty o organismu.
Kdy₧ OrgRun
dostane ukazatel na rozhranφ organismu, dß se p°i
znalostech zdrojovΘho k≤du The Life! a zp∙sobu, jak Delphi zachßzφ s
pam∞tφ, zjistit adresa, kde jsou zapsßny d∙le₧itΘ prom∞nnΘ organismu, jako
je hmotnost nebo energie. Vzhledem k tomu, ₧e DLL a hlavnφ EXE soubor sdφlφ
stejn² pam∞¥ov² prostor, nenφ problΘm tyto hodnoty p°epsat. Op∞t platφ, ₧e
sluÜnΘ organismy toto ned∞lajφ, ale je to zajφmav² nßm∞t
na hranφ si s assemblerem.
P°i troÜe snahy lze zneu₧φt i u₧ivatelskΘ registry — staΦφ je p°etypovat na pointery a rßzem je m∙₧ete pou₧φvat k uchovßvßnφ dynamicky alokovan²ch struktur, Φφm₧ si m∙₧ete libovoln∞ zv∞tÜit pam∞¥ organismu. ProblΘmem je, ₧e organismus nikdy p°edem nevφ, kdy zahyne, a nemß tak kdy tyto struktury odalokovat. Proto i toto chovßnφ organism∙ pova₧uji za nesportovnφ.
ENV je formßt binßrnφ (tzn. nikoliv textov²). Dokß₧e p°esn∞ zaznamenat stav celΘ simulace. Je optimalizovßn p°edevÜφm na rychlΘ naΦφtßnφ a uklßdßnφ, ale mß v sob∞ i vestav∞nou volitelnou mo₧nost komprimace, dφky ni₧ se m∙₧e objem dat v souboru znaΦn∞ zmenÜit (b∞₧n∞ i pod 10%). Nev²hodou je znaΦnΘ zpomalenφ p°i uklßdßnφ a naΦφtßnφ. Komprimace se hodφ t°eba p°i posφlßnφ soubor∙ p°es internet apod.
Popsßna je pouze varianta formßtu pou₧φvanß verzφ M2. Variantu pou₧φvanou verzφ M1 si mohou zßjemci odvodit za domßcφ ·kol ze zdrojßk∙ (M2 umφ naΦφtat soubory M1, ale neumφ je uklßdat).
Soubor se sklßdß z hlaviΦky a vlastnφch datov²ch struktur. Obsah celΘho souboru se dß popsat pomocφ nßsledujφcφ tabulky:
Nßzev |
Typ |
DΘlka [B] |
Popis |
---|---|---|---|
ID |
Char |
8 |
identifikßtor formßtu, mß v₧dy hodnotu "MSFF0C06" |
Version |
Integer |
4 |
verze formßtu, mß v₧dy hodnotu $00050000 |
Flags |
Integer |
4 |
mß-li hodnotu 1, je veÜker² dalÜφ obsah souboru
komprimovßn pomocφ knihovny zlib (bli₧Üφ popis formßtu komprese viz RFC
1250-1252) mß-li hodnotu 0, komprese nenφ pou₧ita |
Width |
Integer | 4 | Üφ°ka prost°edφ |
Height |
Integer | 4 | v²Üka prost°edφ |
Food |
Byte | Width * Height |
data o potrav∞ v prost°edφ (ulo₧eno po °ßdcφch shora dol∙, °ßdky samotnΘ zleva doprava) |
Chem |
Byte | Width * Height |
data o chemikßliφch v prost°edφ (ulo₧eno po °ßdcφch shora dol∙, °ßdky samotnΘ zleva doprava) |
Time |
Integer | 4 | poΦet krok∙ simulace od jejφho poΦßtku |
OrgCount |
Integer | 4 | poΦet orgranism∙ |
(nßsledujφcφ polo₧ky se opakujφ
OrgCount -krßt) |
|||
Species |
Char |
64 |
nßzev organismu (ukonΦen² znakem 0, zb²vajφcφ znaky do 64.
nesjou definovßny) |
Mass |
Byte |
1 |
hmotnost organismu |
FoodIn |
Byte |
1 |
mno₧stvφ potravy v trßvicφ vakuole organismu |
Energy |
Byte |
1 |
energie organismu |
Padding |
Byte |
1 |
v²pl≥, jejφ obsah nenφ definovßn |
X |
Integer |
4 |
x-ovß sou°adnice organismu |
Y |
Integer |
4 |
y-ovß sou°adnice organismu |
Color |
Integer |
4 |
barva organismu (R = bity 0-7, G = bity 8-15, B = bity
16-23, zb²vajφcφ bity 24-31 nejsou definovßny) |
Age |
Integer |
4 |
v∞k organismu (poΦet krok∙ simulace od vytvo°enφ
organismu do okam₧iku ulo₧enφ) |
MaxAge |
Integer | 4 | maximßlnφ v∞k organismu |
GenCount |
Integer | 4 | generace organismu |
URCount |
Integer | 4 | poΦet alokovan²ch u₧ivatelsk²ch registr∙ |
UR |
Integer | URCount * 4 |
u₧ivatelskΘ registry |
(konec opakovßnφ
OrgCount -krßt) |
|||
SpeciesCount |
Integer |
4 |
poΦet druh∙ organism∙ v grafu |
SpeciesNames |
Char | SpeciesCount * 64 |
nßzvy druh∙ organism∙ v grafu (ka₧d² nßzev je ukonΦen² znakem 0, zb²vajφcφ znaky do 64. nesjou definovßny) |
OrgAmounts |
Integer | SpeciesCount * 1024 * 4 |
poΦty organism∙ v grafu (1024 polo₧ek s poΦty organism∙ se°azen²mi
stejn∞ jako SpeciesNames , 1. polo₧ka p°edstavuje poslednφ krok
simulace, 2. p°edposlednφ, atd.) |
VÜechny typy dat a konstanty jsou v Object Pascalu (Delphi). Pro cΘΦka°e a jim podobnΘ: Integer je 32bitovΘ celΘ Φφslo se znamΘnkem, Byte je 8bitovΘ celΘ Φφslo bez znamΘnka, Char je 8bitovΘ celΘ Φφslo bez znamΘnka p°edstavujφcφ jeden znak v ASCII. Konstanty zaΦφnajφcφ dolarem ("$") jsou v ÜestnßctkovΘ soustav∞.
Formßt ENV soubor∙ byl v pr∙b∞hu v²voje verze M2 podstatn∞ upraven a nenφ kompatibilnφ s verzφ M1. Verze M2 nicmΘn∞ umφ naΦφtat soubory z verze M1, ale neumφ je uklßdat.
DLL organism∙ nejsou mezi verzemi M1 a M2 v∙bec binßrn∞ kompatibilnφ —
pokud zkusφte zkopφrovat DLL soubor z jednΘ verze do adresß°e
Org
verze druhΘ, ta ho nebude v∙bec detekovat jako
organismus.
Na ·rovni zdrojov²ch k≤d∙ by nem∞l b²t problΘm s p°evodem organism∙ z
verze M1 — zm∞ny v API by nem∞ly p°φliÜ naruÜit zp∞tnou kompatibilitu. Je
pot°eba zajistit, aby soubor s implementacφ organismu pou₧φval novou verzi
unity OrgInterface
a na zaΦßtek jeho kontrolnφ procedury je
nutnΘ p°ipsat p°φkaz alokujφcφ u₧ivatelskΘ registry — nap°.
AllocUR(5)
pokud organismus pou₧φvß registry 0-4. Pak by m∞lo
vÜe fungovat.
P°i p°evodu je takΘ nutnΘ si uv∞domit, ₧e se oproti verzi M1 zm∞nil algoritmus distribuce potravy a takΘ pohyb stojφ vφc energie (v zßvislosti na hmotnosti organismu).
Ve zdrojovΘm balφΦku najdete vÜe, co pot°ebujete k vytvo°enφ ·plnΘ
distribuce The Life! — tj. vÜechny zdrojovΘ a datovΘ soubory. Nejsou zde
₧ßdnΘ zßvislosti na Φemkoliv mimo tento balφΦek. VÜechny soubory mimo
adresß° zlib
jsou moje prßce (v adresß°i zlib
se
nachßzφ Pascalsk² port kompresnφ knihovny, kter² nenφ m²m dφlem). Ve
zdrojovΘm k≤du jsem komentoval jen d∙le₧itß mφsta — triviality a k≤d
generovan² Delphi jsem vesm∞s nechal nekomentovanΘ.
The Life! je napsßn v Borland Delphi 6. Ke kompilaci by m∞lo staΦit
otev°φt v tomto nßstroji projekt Life.dpr
a zvolit
Project|Compile Life. V ni₧Üφch ani vyÜÜφch verzφch jsem
kompilaci nezkouÜel, a tak nevφm, zda se vßm povede. Ve vyÜÜφch verzφch by
ale vÜe nejspφÜ m∞lo fungovat.
Organismy by m∞ly jφt zkompilovat obdobn²m zp∙sobem. ZkouÜel jsem je kompilovat i s Delphi 3 a vÜe fungovalo.
ProblΘmem je kompilace DLLInstall.exe
. Tento soubor je
urΦen k nahrßnφ pot°ebn²ch DLL do systΘmu Windows 95, pokud je tam u₧ivatel
nemß (viz ZnßmΘ problΘmy). Pokud ho
zkompilujete v Delphi 6, bude ale sßm tyto knihovny ke svΘmu spuÜt∞nφ
vy₧adovat! Ze zaΦarovanΘho kruhu se dß vymotat tφm, ₧e DLLInstall
zkompilujete v n∞jakΘ ni₧Üφ verzi Delphi (jß pou₧φvßm Delphi 3 a funguje
to).
P°i implementaci jsem n∞kolikrßt musel °eÜit klasickΘ programßtorskΘ dilema, zda-li optimalizovat program na rychlost nebo pam∞¥ovou nßroΦnost. Ve v∞tÜin∞ p°φpad∙ jsem jednoznaΦn∞ volil rychlost.
Mφsto, kde je to asi nejvφc vid∞t, jsou metody ForeignAllocedURCount
, ForeignUR
, SetForeignUR
,
OrgAt
a Attack
t°φdy TOrg
. V nich
je pot°eba najφt organismus, kter² le₧φ na dan²ch sou°adnicφch. P°i
n∞kolika desφtkßch tisφc organism∙ je velmi neefektivnφ prochßzet cel²
seznam organism∙ a vybφrat toho, kter² mß sprßvnΘ sou°adnice. Proto jsem
p°idal pomocnΘ seznamy organism∙ na ka₧dΘm °ßdku prost°edφ. Tyto seznamy se
stßle udr₧ujφ aktußlnφ, podle toho, jak se organismy pohybujφ, rodφ a
hynou. P°i hledßnφ organismu na n∞jakΘm polφΦku pak staΦφ prohledat jen
jeden z t∞chto pomocn²ch seznam∙. V²sledkem optimalizace sice nenφ
kvalitativnφ snφ₧enφ slo₧itosti vyhledßvßnφ (stßle je lineßrnφ), ale
prohledßvan² seznam je podstatn∞ kratÜφ (v pr∙m∞ru tolikrßt, kolik je °ßdk∙
v prost°edφ — a t∞ch jsou typicky stovky). Nev²hodou je n∞kolik desφtek a₧
stovek kB pam∞ti zabran²ch navφc.
Ve verzi M2 je tato optimalizace dota₧ena jeÜt∞ dßl — seznamy organism∙ se udr₧ujφ dokonce pro ka₧dΘ polφΦko.
Zajφmav²m problΘmem bylo, jak navrhnout samotn² hlavnφ seznam organism∙. Z anal²zy vyplynulo, ₧e pot°ebuju p°edevÜφm tyto t°i operace:
Delphi mß na tyto ·Φely vyhrazen² vlastnφ t°φdu TList
, co₧
je generick² seznam pointer∙. TList
je intern∞ implementovan²
jako dynamicky alokovanΘ pole s tφm, ₧e se p°ealokovßvß v₧dy "po v∞tÜφch
kusech", tedy ne p°i p°idßnφ/odebrßnφ ka₧dΘ jednotlivΘ polo₧ky. P°φstup k
polo₧kßm je O(konst), p°idßvßnφ na konec seznamu je sice obecn∞
O(n), ale vzhledem k tomu, ₧e se p°ealokovßvß jen Φas od Φasu,
v∞tÜinou je konstantnφ. Nejv∞tÜφ nev²hodou je odebφrßnφ zevnit° seznamu,
proto₧e vÜechny polo₧ky za tou odebφranou se musφ posunout — operace mß
tedy lineßrnφ slo₧itost, ale praxe ukazuje, ₧e je velmi pomalß.
Jako °eÜenφ jsem zkusil navrhnout vlastnφ t°φdu
TDoubleLinkedList
, kterß navenek odpovφdß TList
u
(tj. implementovanΘ metody jsou shodnΘ), ale je to dvojit∞ z°et∞zen²
lineßrnφ spojov² seznam. P°idßvßnφ i je O(konst), ale je pomalejÜφ
ne₧ u TList
u. Odebφrßnφ mß stejn∞ jako u TList
u
lineßrnφ slo₧itost, ale je mnohonßsobn∞ rychlejÜφ. ProblΘmem je p°φstup k
polo₧kßm, kter² je O(n). Zkusil jsem implementovat specißlnφ
keÜovacφ mechanismus optimalizujφcφ vφcenßsobn² p°φstup k jednΘ polo₧ce a
sekvenΦnφ p°φstup, ale i tak je simulace s tφmto seznamem v pr∙m∞ru dvakrßt
pomalejÜφ, ne₧ s TListem
. Zatφm je tedy
TDoubleLinkedList
defaultn∞ vypnut (jde zapnout kompilaΦnφ
volbou), ale mo₧nß se n∞kdy jeÜt∞ k tomu dostanu a t°eba TList
trumfnu
:-)
Zajφmav²m problΘmem je takΘ prochßzenφ seznamem, kdy₧ se pro ka₧d² organismus spouÜt∞jφ kontrolnφ procedury. To znamenß, ₧e libovolnΘ organismy mohou p°ib²t nebo naopak zem°φt a zm∞nit tak prßv∞ prochßzen² seznam. Abych se vyhnul problΘm∙m s pohybliv²mi mezemi a podobn²mi jevy, zavedl jsem n∞kolik pravidel:
nil
. Tφm se zabrßnφ
zkracovßnφ seznamu. P°i p°φstupu k polo₧kßm se v tomto cyklu na
nil
testuje, aby nedoÜlo v chyb∞ p°i
p°φstupu do pam∞ti.nil
ze seznamu vyhßzφ.Vliv t∞chto pravidel na rychlost simulace nenφ skoro ₧ßdn² (pouze
testovßnφ na nil
navφc), proto₧e je ·pln∞
jedno, zda se polo₧ky odebφrajφ hned, nebo a₧ pozd∞ji. Vliv na zjednoduÜenφ
programu je znaΦn².