Questo è un adattamento di Core War , una programmazione KOTH risalente al 20 ° secolo. Per essere più specifici, utilizza un set di istruzioni incredibilmente semplificato basato principalmente sulla proposta originale .
sfondo
In Core War, ci sono due programmi in lotta per il controllo del computer. L'obiettivo di ciascun programma è vincere individuando e terminando il programma avversario.
La battaglia si svolge nella memoria principale del computer. Questa memoria si chiama Core e contiene 8192 indirizzi. Quando inizia la battaglia, il codice per ogni concorrente (chiamato guerriero) viene inserito in un pezzo casuale di memoria. L'esecuzione del programma si alterna tra guerrieri, eseguendo un'istruzione di ciascuno. Ogni istruzione è in grado di modificare una parte del Core, portando alla possibilità di programmi di auto-modifica.
L'obiettivo è terminare il programma avversario. Un programma termina quando tenta di eseguire un'istruzione non valida, che è qualsiasi DAT
istruzione.
Il set di istruzioni
Ogni programma è costituito da una serie di istruzioni di basso livello, ognuna delle quali occupa due campi, chiamati campi A e B.
Questa serie di istruzioni si basa fortemente sulle specifiche originali. Le principali modifiche sono 1) chiarimento sull'aggiunta / sottrazione di comandi e 2) una modifica della #
modalità di indirizzamento per consentirne l'utilizzo ovunque. La maggior parte delle versioni complete di Core Wars hanno oltre 20 codici operativi, 8 modalità di indirizzamento e una serie di "modificatori di istruzioni".
opcodes
Ogni istruzione deve avere uno dei sette diversi codici operativi.
DAT A B
- (dati) - Questo contiene semplicemente i numeriA
eB
. È importante sottolineare che un processo muore quando tenta di eseguire un'istruzione DAT.MOV A B
- (sposta): sposta il contenuto della posizioneA
di memoria nella posizione di memoriaB
. Ecco una dimostrazione di prima e dopo:MOV 2 1 ADD @4 #5 JMP #1 -1
MOV 2 1 JMP #1 -1 JMP #1 -1
ADD A B
- (aggiungi): consente di aggiungere il contenuto della posizione della memoriaA
alla posizione della memoriaB
. Vengono aggiunti i primi due campi di entrambi e vengono aggiunti i secondi campi.ADD 2 1 MOV @4 #5 JMP #1 -1
ADD 2 1 MOV @5 #4 JMP #1 -1
SUB A B
- (sottrai): sottrae il contenuto della posizione di memoriaA
dalla (e memorizza il risultato nella) posizione di memoriaB
.SUB 2 1 MOV @4 #5 JMP #1 -1
SUB 2 1 MOV @3 #6 JMP #1 -1
JMP A B
- (salta) - Salta alla posizioneA
, che verrà eseguita il ciclo successivo.B
deve essere un numero ma non fa nulla (puoi usarlo per archiviare informazioni, però).JMP 2 1337 ADD 1 2 ADD 2 3
Il salto significa che
ADD 2 3
verrà eseguito il ciclo successivo.JMZ A B
- (salta se zero) - Se entrambi i campi della lineaB
sono 0, il programma passa alla posizioneA
.JMZ 2 1 SUB 0 @0 DAT 23 45
Poiché i due campi dell'istruzione 1 sono 0, il comando DAT verrà eseguito il turno successivo, portando alla morte imminente.
CMP A B
- (confronta e salta se non uguale) - Se i campi nelle istruzioniA
eB
non sono uguali, salta l'istruzione successiva.CMP #1 2 ADD 2 #3 SUB @2 3
Poiché i due campi delle istruzioni 1 e 2 hanno lo stesso valore, il comando ADD non viene ignorato e viene eseguito il turno successivo.
Quando vengono aggiunte / sottratte due istruzioni, i due campi (A e B) vengono aggiunti / sottratti in coppia. La modalità di indirizzamento e il codice operativo non vengono modificati.
Modalità di indirizzamento
Esistono tre tipi di modalità di indirizzamento. Ciascuno dei due campi di un'istruzione ha una di queste tre modalità di indirizzamento.
Immediata
#X
:X
è la linea da utilizzare direttamente nel calcolo. Ad esempio,#0
è la prima riga del programma. Le linee negative si riferiscono alle linee nel core prima dell'inizio del programma.... //just a space-filler ... ADD #3 #4 DAT 0 1 DAT 2 4
Ciò aggiungerà la prima delle due linee DAT alla seconda, poiché quelle sono rispettivamente nelle linee 3 e 4. Non vorrai usare questo codice, tuttavia, perché il DAT ucciderà il tuo bot nel ciclo successivo.
Relativo
X
: il numeroX
rappresenta la posizione di un indirizzo di memoria di destinazione, rispetto all'indirizzo corrente. Il numero in questa posizione viene utilizzato nel calcolo. Se la riga#35
viene eseguita e contiene-5
,#30
viene utilizzata la riga .... //just a space-filler ... ADD 2 1 DAT 0 1 DAT 2 4
Ciò aggiungerà la seconda riga DAT alla prima.
Indiretto
@X
: il numeroX
rappresenta un indirizzo relativo. I contenuti in quella posizione vengono temporaneamente aggiunti al numero X per formare un nuovo indirizzo relativo, dal quale viene recuperato il numero. Se la riga#35
viene eseguita e il suo secondo campo è@4
e il secondo campo della riga#39
contiene il numero-7
,#32
viene utilizzata la riga .... //just a space-filler ... ADD @1 @1 DAT 0 1 DAT 2 4
Ciò aggiungerà il primo DAT al secondo, ma in un modo più contorto. Il primo campo è @ 1, che ottiene i dati da quell'indirizzo relativo, che è il primo campo del primo DAT, uno 0. Questo viene interpretato come un secondo indirizzo relativo da quella posizione, quindi 1 + 0 = 1 fornisce il totale offset dall'istruzione originale. Per il secondo campo, @ 1 ottiene il valore da quell'indirizzo relativo (l'1 nel secondo campo del primo DAT) e lo aggiunge a se stesso allo stesso modo. L'offset totale è quindi 1 + 1 = 2. Quindi, questa istruzione viene eseguita in modo simile a
ADD 1 2
.
Ogni programma può contenere fino a 64 istruzioni.
Quando inizia un round, i due programmi vengono posizionati in modo casuale in un banco di memoria con 8192 posizioni. Il puntatore alle istruzioni per ciascun programma inizia all'inizio del programma e viene incrementato dopo ogni ciclo di esecuzione. Il programma muore quando il puntatore dell'istruzione tenta di eseguire DAT
un'istruzione.
Parametri del core
La dimensione del nucleo è 8192, con un timeout di 8192 * 8 = 65536 tick. Il nucleo è ciclico, quindi scrivere all'indirizzo 8195 equivale a scrivere all'indirizzo 3. Tutti gli indirizzi non utilizzati vengono inizializzati DAT #0 #0
.
Ogni concorrente non deve superare le 64 linee. I numeri interi verranno memorizzati come numeri interi con segno a 32 bit.
parsing
Al fine di facilitare la programmazione per i concorrenti, aggiungerò al parser una funzione line-label. Qualsiasi parola presente su una riga prima di un codice operativo verrà interpretata come etichetta di riga. Ad esempio, tree mov 4 6
ha l'etichetta di linea tree
. Se, in qualsiasi parte del programma, è presente un campo che contiene tree
#tree
o @tree
, verrà sostituito un numero. Inoltre, la capitalizzazione viene ignorata.
Ecco un esempio di come vengono sostituite le etichette di linea:
labelA add labelB @labelC
labelB add #labelC labelC
labelC sub labelA @labelB
Qui, le etichette A, B e C si trovano sulle righe 0, 1 e 2. Le istanze di #label
verranno sostituite con il numero di riga dell'etichetta. Le istanze di label
o @label
sono sostituite con la posizione relativa dell'etichetta. Le modalità di indirizzamento vengono mantenute.
ADD 1 @2
ADD #2 1
SUB -2 @-1
punteggio
Per ogni coppia di concorrenti, viene eseguita ogni possibile battaglia. Poiché il risultato di una battaglia dipende dagli offset relativi dei due programmi, viene tentato ogni possibile offset (circa 8000 di essi). Inoltre, ogni programma ha la possibilità di muoversi per primo in ogni offset. Il programma che vince la maggior parte di questi offset è il vincitore della coppia.
Per ogni coppia che vince un guerriero, vengono assegnati 2 punti. Per ogni pareggio, a un guerriero viene assegnato 1 punto.
Puoi inviare più di un guerriero. Si applicano le regole tipiche per più invii, come nessun tag-teaming, nessuna collaborazione, nessuna creazione di re, ecc. In Core War non c'è comunque spazio per questo, quindi non dovrebbe essere un grosso problema.
Il controller
Il codice per il controller, insieme a due semplici robot di esempio, si trova qui . Poiché questa competizione (quando si esegue utilizzando le impostazioni ufficiali) è completamente deterministica, la classifica che crei sarà esattamente la stessa della classifica ufficiale.
Esempio di Bot
Ecco un esempio di bot che mostra alcune caratteristiche del linguaggio.
main mov bomb #-1
add @main main
jmp #main 0
bomb dat 0 -1
Questo bot funziona cancellando lentamente tutta la memoria nel nucleo sostituendola con una "bomba". Poiché la bomba è DAT
un'istruzione, qualsiasi programma che raggiunge una bomba verrà distrutto.
Ci sono due etichette di linea, "principale" e "bomba" che servono a sostituire i numeri. Dopo la preelaborazione, il programma è simile al seguente:
MOV 3 #-1
ADD @-1 -1
JMP #0 0
DAT 0 -1
La prima riga copia la bomba sulla linea immediatamente sopra il programma. La riga successiva aggiunge il valore della bomba ( 0 -1
) al comando di spostamento e dimostra anche l'uso della @
modalità di indirizzamento. Questa aggiunta fa sì che il comando di spostamento punti a un nuovo target. Il comando successivo torna incondizionatamente all'inizio del programma.
Classifica attuale
24 - Turbo
22 - DwarvenEngineer
20 - HanShotFirst
18 - Dwarf
14 - ScanBomber
10 - Paranoid
10 - FirstTimer
10 - Janitor
10 - Evolved
6 - EasterBunny
6 - CopyPasta
4 - Imp
2 - Slug
Risultati a coppie:
Dwarf > Imp
CopyPasta > Imp
Evolved > Imp
FirstTimer > Imp
Imp > Janitor
Imp > ScanBomber
Slug > Imp
DwarvenEngineer > Imp
HanShotFirst > Imp
Turbo > Imp
EasterBunny > Imp
Paranoid > Imp
Dwarf > CopyPasta
Dwarf > Evolved
Dwarf > FirstTimer
Dwarf > Janitor
Dwarf > ScanBomber
Dwarf > Slug
DwarvenEngineer > Dwarf
HanShotFirst > Dwarf
Turbo > Dwarf
Dwarf > EasterBunny
Dwarf > Paranoid
Evolved > CopyPasta
FirstTimer > CopyPasta
Janitor > CopyPasta
ScanBomber > CopyPasta
CopyPasta > Slug
DwarvenEngineer > CopyPasta
HanShotFirst > CopyPasta
Turbo > CopyPasta
CopyPasta > EasterBunny
Paranoid > CopyPasta
Evolved > FirstTimer
Evolved > Janitor
ScanBomber > Evolved
Evolved > Slug
DwarvenEngineer > Evolved
HanShotFirst > Evolved
Turbo > Evolved
EasterBunny > Evolved
Paranoid > Evolved
Janitor > FirstTimer
ScanBomber > FirstTimer
FirstTimer > Slug
DwarvenEngineer > FirstTimer
HanShotFirst > FirstTimer
Turbo > FirstTimer
FirstTimer > EasterBunny
FirstTimer > Paranoid
ScanBomber > Janitor
Janitor > Slug
DwarvenEngineer > Janitor
HanShotFirst > Janitor
Turbo > Janitor
Janitor > EasterBunny
Janitor > Paranoid
ScanBomber > Slug
DwarvenEngineer > ScanBomber
HanShotFirst > ScanBomber
Turbo > ScanBomber
ScanBomber > EasterBunny
ScanBomber > Paranoid
DwarvenEngineer > Slug
HanShotFirst > Slug
Turbo > Slug
EasterBunny > Slug
Paranoid > Slug
DwarvenEngineer > HanShotFirst
Turbo > DwarvenEngineer
DwarvenEngineer > EasterBunny
DwarvenEngineer > Paranoid
Turbo > HanShotFirst
HanShotFirst > EasterBunny
HanShotFirst > Paranoid
Turbo > EasterBunny
Turbo > Paranoid
Paranoid > EasterBunny
L'ultimo aggiornamento (nuove versioni di Turbo e Paranoid) ha richiesto circa 5 minuti per essere eseguito su un vecchio laptop. Vorrei ringraziare Ilmari Karonen per i miglioramenti apportati al controller . Se si dispone di una copia locale del controller, è necessario aggiornare i file.