The Nano Core War


21

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 DATistruzione.

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 numeri Ae B. È importante sottolineare che un processo muore quando tenta di eseguire un'istruzione DAT.
  • MOV A B- (sposta): sposta il contenuto della posizione Adi memoria nella posizione di memoria B. 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 memoria Aalla posizione della memoria B. 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 memoria Adalla (e memorizza il risultato nella) posizione di memoria B.

    SUB 2 1
    MOV @4 #5
    JMP #1 -1
    
    SUB 2 1
    MOV @3 #6
    JMP #1 -1
    
  • JMP A B- (salta) - Salta alla posizione A, che verrà eseguita il ciclo successivo. Bdeve 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 3verrà eseguito il ciclo successivo.

  • JMZ A B- (salta se zero) - Se entrambi i campi della linea Bsono 0, il programma passa alla posizione A.

    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 istruzioni Ae Bnon 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.

  • RelativoX : il numero Xrappresenta 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 #35viene eseguita e contiene -5, #30viene 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 numero Xrappresenta 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 #35viene eseguita e il suo secondo campo è @4e il secondo campo della riga #39contiene il numero -7, #32viene 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 DATun'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 6ha l'etichetta di linea tree. Se, in qualsiasi parte del programma, è presente un campo che contiene tree #treeo @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 #labelverranno sostituite con il numero di riga dell'etichetta. Le istanze di labelo @labelsono 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 è DATun'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.


Cosa succede se due robot concorrenti tentano di utilizzare la stessa etichetta?
mbomb007

1
@ mbomb007 Le etichette sono elementi di preelaborazione e vengono calcolati durante l'analisi del file di origine del bot. Le etichette non interagiranno con le etichette della concorrenza.
PhiNotPi

1
@ mbomb007 In modo che i programmi non si sovrappongano. Inoltre, non ho intenzione di aggiungere altre funzionalità a questa versione, salvo quelle per Micro Core War.
PhiNotPi

1
@ mbomb007 L'indirizzamento indiretto fa riferimento allo stesso campo che sta creando il riferimento (1o o 2o). Non ci sono modificatori di istruzioni. Non sto basando questa sfida sullo standard del '94.
PhiNotPi

2
@Thrax Sto per dire di no, che non sei limitato a un solo invio. Si applicano le tipiche regole di sottomissione multipla (no tag-teaming, ecc.), Anche se comunque non c'è molto spazio per la cooperazione nelle guerre di base.
PhiNotPi

Risposte:


9

Ingegnere Nanico

Un nano nuovo e migliorato. Vince contro tutto il resto presentato finora. La dimensione del gradino ottimizzata dal corestep è probabilmente eccessiva qui.

        MOV bomb    @aim
aim     MOV bomb    @-6326
        SUB step    aim
step    JMZ #0      6328
        MOV 0       1
bomb    DAT 0       3164

Tra le caratteristiche degne di nota vi sono il rapido bombardamento che lancia due bombe in quattro cicli, per una velocità media di bombardamento di 0,5 c nel vecchio gergo della Core War, e l'uso di JMZper rilevare quando la corsa al bombardamento è completa ed è tempo di passare al piano B ( qui, un Imp).


Ho giocato a Core War negli anni '90 (alcuni di voi potrebbero aver visto la guida di base che ho scritto nel '97), quindi ho pensato che sarebbe stato interessante vedere quali vecchie strategie del mondo RedCode '88 / '94 potrebbero essere utile in questa variante.

I miei primi pensieri sono stati:

  • Non c'è SPL, quindi nessun replicatore (e nessun anello / spirale imp). Questo dovrebbe rendere i bombardieri forti. (Inoltre, tutte quelle fantasiose strategie di bombardamento progettate per affrontare replicatori e spirali di diavoleria? Totalmente inutili e inutili qui. Basta bombardare con qualsiasi DATs.)

  • Inoltre, la CMPscansione è ancora potenzialmente più veloce dei bombardamenti, quindi uno scanner veloce potrebbe avere una possibilità.

  • L'assenza di in / decrementi rende molto lenta la cancellazione dei core. In effetti, un nucleo chiaro in questa variante è praticamente solo un bombardiere con un gradino (non ottimale) di ± 1. Ancora una volta, questo fa male anche agli scanner; uno scanner one-shot → strategia bombardiere potrebbe funzionare, però.

  • Quickscanner / quickbombers (una strategia di gioco iniziale che utilizza un ciclo di scansione / bombardamento srotolato, per coloro che non sono così familiari con il gergo di Core War) sono ancora potenzialmente utili, ma solo contro programmi lunghi (come loro stessi, quindi c'è una specie di feedback effetto qui). Difficile dire se vale davvero la pena.

  • Il sistema di punteggio è interessante. I pareggi segnano la metà dei punti rispetto a una vittoria (anziché 1/3, come nella tradizionale Guerra del nucleo), rendendoli più attraenti. Inoltre, l'unico programma che può segnare molti legami in base a queste regole è un imp. (Inoltre, l'assenza di de / incrementi rende cancelli imp disco, così anche semplici imp effettivamente fare avere una possibilità di aver realizzato cravatta se raggiungono il loro avversario vivo.)

  • Inoltre, poiché la classifica finale dipendono solo quali programmi si batte, e non come molto di loro si batte da, esso tende a favorire le voci generalisti. È meglio battere a malapena tutti i tuoi avversari, piuttosto che distruggerne totalmente la metà e perdere a malapena il resto.

  • Poiché il codice è pubblico, è sempre possibile trovare un programma in grado di battere qualsiasi precedente invio - possibilmente anche alcuni di essi - non importa quanto siano buoni in generale. Tuttavia, questi trucchi (come regolare la dimensione del tuo passo per colpire il tuo avversario appena prima che ti colpiscano) possono facilmente sembrare economici. E, naturalmente, il giocatore target potrebbe sempre presentare una nuova versione con costanti diverse.

Ad ogni modo, il risultato di tutto ciò è che ho deciso che avrei dovuto provare a scrivere un bombardiere veloce o uno scanner molto veloce, e magari attaccare uno scanner rapido / bombardiere su di esso. Tra queste opzioni, un bombardiere veloce sembrava il più semplice e molto probabilmente funzionante.

A quel punto, ho trascorso troppo tempo a modificare e ottimizzare il codice dell'interprete di PhiNotPi, perché immaginavo che avrei probabilmente eseguito molte prove di forza bruta per ottimizzare le costanti. In realtà, non ho mai dovuto farlo: il codice sopra è praticamente la prima versione che ha funzionato (dopo un paio di tentativi falliti che si sono suicidati a causa di stupidi bug).


Il trucco che rende veloce il mio bombardiere sta usando l'indirizzamento indiretto per lanciare due bombe per ciascuno ADD. Ecco come funziona:

  1. Al primo ciclo, eseguiamo MOV bomb @aim. Questo copia l' bombistruzione in qualunque punto del core il punto B aimpunti (inizialmente, esattamente 6326 istruzioni prima aimo 6328 istruzioni prima step; vedrai perché quei numeri contano in seguito).

  2. Nel passaggio successivo, eseguiamo le aimistruzioni stesse! Al primo passaggio, sembra che questo: MOV bomb @-6326. Quindi, copia bombnella posizione a cui punta il campo B dell'istruzione a 6326 righe prima di se stesso.

    Quindi, cosa c'è prima alle 6326 linee aim? Perché, è la copia di cui bombabbiamo appena effettuato un ciclo prima! E ci è capitato di sistemare le cose in modo che il campo B bombabbia un valore diverso da zero, quindi la nuova bomba non verrà copiata sopra quella vecchia, ma a una certa distanza (in effetti, qui la distanza è 3164, che è la metà del nostro passo nominale 6328; ma altri offset potrebbero funzionare, forse anche meglio).

  3. Nel ciclo successivo, regoliamo il nostro obiettivo con SUB step aim, che sottrae i valori stepdell'istruzione (che è anche il salto che eseguiremo dopo, anche se potrebbe essere stato un semplice DATda qualche parte) da aim.

    (Un dettaglio da notare qui è che tipo di vogliamo la A-valore steppari a zero, in modo che ci sarà ancora gettare le stesse bombe sulla prossima iterazione Anche questo non è strettamente necessario, anche se,. Solo le bombe gettate dalla prima istruzione è necessario che il loro campo B sia uguale a 3164, il resto può essere qualsiasi cosa.)

  4. Successivamente, JMZverifica che l'istruzione 6328 si allontani da essa sia ancora zero e, in tal caso, torna all'inizio del codice. Ora, 6328 è la dimensione del gradino del nostro bombardiere, ed è divisibile per 8 (ma non 16); quindi, se continuassimo a lanciare bombe ogni 6328 passi, alla fine torneremmo da dove siamo partiti, dopo aver bombardato ogni ottava istruzione nel nucleo (e con l'offset di bombe extra di 3163 = 6328/2 ≡ 4 (mod 8) , avremmo colpito ogni quarta istruzione).

    Ma abbiamo iniziato la nostra corsa ai bombardamenti con 6328 istruzioni prima del JMZe abbiamo fatto un passo indietro di -6328 ad ogni iterazione, quindi bombarderemo la posizione 6328 passi dopo la JMZsola iterazione prima di colpire la JMZstessa. Quindi, quando JMZrileva una bomba dopo 6328 istruzioni, questo è un segno che abbiamo coperto il più possibile il nucleo senza colpire noi stessi e dovremmo passare a una strategia di backup prima di ucciderci.

  5. Per quanto riguarda la strategia di backup, è solo un semplice vecchio MOV 0 1imp, perché per ora non potevo pensare a niente di meglio. Per come la vedo io, se abbiamo bombardato ogni quattro posizioni del nucleo e ancora non abbiamo vinto, probabilmente stiamo combattendo qualcosa di molto piccolo o molto difensivo, e potremmo anche provare a sopravvivere e accontentarci di un pareggio. Va bene, perché programmi così piccoli o difensivi in ​​genere non sono molto bravi a uccidere qualcos'altro, e quindi anche se vinciamo solo alcuni combattimenti per caso, probabilmente usciremo comunque avanti.


Ps. Nel caso in cui qualcun altro lo voglia, ecco il mio fork leggermente migliorato del codice del torneo di PhiNotPi . È circa due volte più veloce, salva i vecchi risultati della battaglia in modo da non doverli ripetere, e corregge quello che credo sia un bug minore nel calcolo dei risultati della battaglia. Le modifiche sono state unite nella versione principale da PhiNotPi. Grazie!


1
Solo per questo, i punteggi segnano OGNI possibile combinazione di posizioni iniziali del programma e il punteggio del programma ottiene il maggior numero di punti. Questo rende i legami impossibili o completamente sfavorevoli poiché finché un programma non uccide mai se stesso e bombarda almeno un indirizzo una volta, batterà un imp, avendo una vittoria e il resto dei legami.
mbomb007,

9

Vista grafico

Questo può essere usato come strumento di debug. Visualizza il nucleo e mostra la posizione del giocatore. Per usarlo devi chiamarlo dal codice. Ho anche fornito un modificato Game.javache visualizza automaticamente GraphView.

PhiNotPi e Ilmari Karonen hanno recentemente cambiato il controller. Ilmari Karonen è stato così gentile da fornire un GameView aggiornato in questa posizione .

import javax.swing.*;
import java.awt.*;

public class GameView extends JComponent{

    final static Color[] commandColors = new Color[]{
            Color.black, //DAT
            Color.blue,  //MOV
            Color.blue,  //ADD
            Color.blue,  //SUB
            Color.blue,  //JMP
            Color.blue,  //JMZ
            Color.blue,  //CMP
    };

    final static Color[] specialColors = new Color[]{
            new Color(0,0,0),
            new Color(190, 255, 152),
            Color.yellow,
            new Color(0, 93, 14),
            new Color(96, 92, 4),
            new Color(0, 93, 14),
            new Color(96, 92, 4),
            new Color(0, 93, 14),
            new Color(96, 92, 4)
    };

    final static Color playerOneColor = Color.green;
    final static Color playerTwoColor = Color.white;

    final Game game;

    int playerOneLocation;
    int playerTwoLocation;

    final static int width = 128;
    final static int height = 64;

    public GameView(Game game) {
        this.game = game;
    }

    @Override
    public void paint(Graphics g) {
        int pixelWidth = getSize().width;
        int pixelHeight = getSize().height;
        if (width > pixelWidth){
            pixelWidth = width;
            setSize(width, pixelHeight);
        }
        if (height > pixelHeight){
            pixelHeight = height;
            setSize(pixelWidth, height);
        }
        int squareWidth = Math.min(pixelWidth / width, pixelHeight / height);
        for (int x = 0; x < squareWidth * width; x += squareWidth){
            for (int y = 0; y < squareWidth * height; y += squareWidth){
                int index = (y / squareWidth) * width + (x / squareWidth);
                Color color = commandColors[game.core[index][0]];
                if (game.coreData[index] != 0){
                    color = specialColors[game.coreData[index]];
                }
                if (index == playerOneLocation){
                    color = playerOneColor;
                }
                if (index == playerTwoLocation){
                    color = playerTwoColor;
                }
                g.setColor(color);
                g.fillRect(x, y, squareWidth, squareWidth);
            }
        }
    }

    public void setLocations(int p1loc, int p2loc){
        this.playerOneLocation = p1loc;
        this.playerTwoLocation = p2loc;
    }
}

Game.java modificato:

import javax.swing.*;
import java.util.Random;
import java.util.ArrayList;
import java.util.Arrays;
/**
 * This runs a game of Core Wars between two players.  It can be called mutiple times.
 * 
 * @author PhiNotPi 
 * @version 3/10/15
 */
public class Game
{
    final Player p1;
    final Player p2;
    final int coreSize;
    final int coreSizeM1;
    final int maxTime;
    final int debug;
    public int[][] core;
    public int[] coreData; //Used in debugging.
    int offset1;
    int offset2;
    Random rand;
    ArrayList<int[]> p1code;
    ArrayList<int[]> p2code;
    int p1size;
    int p2size;
    GameView gameView;
    int time = 1000000; //Time in nanoseconds between frames
    public Game(Player A, Player B, int coreSize, int maxTime, int debug)
    {
        p1 = A;
        p2 = B;

        coreSize--;
        coreSize |= coreSize >> 1;
        coreSize |= coreSize >> 2;
        coreSize |= coreSize >> 4;
        coreSize |= coreSize >> 8;
        coreSize |= coreSize >> 16;
        coreSize++;

        this.coreSize = coreSize;
        this.coreSizeM1 = coreSize - 1;
        this.maxTime = maxTime / 2;
        this.debug = debug;
        core = new int[coreSize][5];
        rand = new Random();
        p1code =  p1.getCode();
        p1size = p1code.size();
        p2code =  p2.getCode();
        p2size = p2code.size();
        if (debug == 1){
            gameView = new GameView(this);
            JFrame frame = new JFrame("Game");
            frame.add(gameView);
            frame.setVisible(true);
            frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            frame.setSize(128, 64);
            coreData = new int[coreSize];
        }
    }

    public int runAll()
    {
        int sum = 0;
        for(int i = 0; i < coreSize - p1size - p2size; i++)
        {
            sum += run(i) - 1;
        }
        if(sum > 0)
        {
            return 1;
        }
        if(sum < 0)
        {
            return -1;
        }
        return 0;
    }

    public int run()
    {
        return run(rand.nextInt(coreSize - p1size - p2size + 1));
    }

    public int run(int deltaOffset)
    {
        core = new int[coreSize][5];
        //offset1 = rand.nextInt(coreSize);
        offset1 = 0;
        for(int i = 0; i != p1size; i++)
        {
            //System.arraycopy(p1.getCode().get(i), 0, core[(offset1 + i) % coreSize], 0, 5 );
            int[] line = p1code.get(i);
            int loc = (offset1 + i) & coreSizeM1;
            core[loc][0] = line[0];
            core[loc][1] = line[1];
            core[loc][2] = line[2];
            core[loc][3] = line[3];
            core[loc][4] = line[4];
            if (debug != 0){
                coreData[loc] = 1;
            }
        }
        offset2 = offset1 + p1size + deltaOffset;
        for(int i = 0; i != p2size; i++)
        {
            //System.arraycopy(p2.getCode().get(i), 0, core[(offset2 + i) % coreSize], 0, 5 );
            int[] line = p2code.get(i);
            int loc = (offset2 + i) & coreSizeM1;
            core[loc][0] = line[0];
            core[loc][1] = line[1];
            core[loc][2] = line[2];
            core[loc][3] = line[3];
            core[loc][4] = line[4];
            if (debug != 0){
                coreData[loc] = 2;
            }
        }

        int p1loc = offset1 & coreSizeM1;
        int p2loc = offset2 & coreSizeM1;
        for(int time = 0; time != maxTime; time++)
        {
            if(debug != 0)
            {
                //printCore(p1loc,p2loc);
                //System.out.println("p1loc " + p1loc);
                //System.out.println("offset " + offset1);
                gameView.setLocations(p1loc, p2loc);
                gameView.repaint();
                try {
                    Thread.sleep(time / 1000000, time % 1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            if(core[p1loc][0] == 0)
            {
                return 0;
            }
            p1loc = execute(p1loc, offset1, 1);

            if(debug != 0)
            {
                //printCore(p1loc,p2loc);
                //System.out.println("p2loc " + p2loc);
                //System.out.println("offset " + offset2);
                gameView.setLocations(p1loc, p2loc);
                gameView.repaint();
                /*try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            }
            if(core[p2loc][0] == 0)
            {
                return 2;
            }
            p2loc = execute(p2loc, offset2, 2);

        }
        return 1;
    }
    public int execute(int ploc, int offset, int player)
    {
        int line1 = offset + core[ploc][3];
        if(core[ploc][1] != 0)
        {
            line1 += ploc - offset;
        }
        if(core[ploc][1] == 2)
        {
            line1 += core[line1 & coreSizeM1][3];
        }
        int line2 = offset + core[ploc][4];
        if(core[ploc][2] != 0)
        {
            line2 += ploc - offset;
        }
        if(core[ploc][2] == 2)
        {
            line2 += core[line2 & coreSizeM1][4];
        }
        line1 = line1 & coreSizeM1;
        line2 = line2 & coreSizeM1;
        int opcode = core[ploc][0];
        ploc = (ploc + 1) & coreSizeM1;
        //String opDescription = "";
        if(opcode == 1)
        {
            core[line2][0] = core[line1][0];
            core[line2][1] = core[line1][1];
            core[line2][2] = core[line1][2];
            core[line2][3] = core[line1][3];
            core[line2][4] = core[line1][4];
            if (debug != 0) {
                coreData[line2] = player + 2;
            }
            return ploc;
            //opDescription = "Moved from " + line1 + " to " + line2;
        }
        if(opcode == 2)
        {
            core[line2][3] += core[line1][3];
            core[line2][4] += core[line1][4];
            if (debug != 0) {
                coreData[line2] = player + 4;
            }
            return ploc;
            //opDescription = "Added " + line1 + " to " + line2;
        }
        if(opcode == 3)
        {
            core[line2][3] -= core[line1][3];
            core[line2][4] -= core[line1][4];
            if (debug != 0) {
                coreData[line2] = player + 6;
            }
            return ploc;
                //opDescription = "Subtracted " + line1 + " to " + line2;
        }
        if(opcode == 4)
        {
            ploc = line1;
            return ploc;
                //opDescription = "Jumped to " + line1;
        }
        if(opcode == 5)
        {
                if(core[line2][3] == 0 && core[line2][4] == 0)
                {
                    ploc = line1;
                    //opDescription = "Jumped to " + line1;
                }
                else
                {
                    //opDescription = "Did not jump to " + line1;
                }
                return ploc;
        }
        if(opcode == 6)
        {
            if(core[line1][3] == core[line2][3] && core[line1][4] == core[line2][4])
            {
                //opDescription = "Did not skip because " + line1 + " and " + line2 + " were equal.";
            }
            else
            {
                ploc = (ploc + 1) & coreSizeM1;
                //opDescription = "Skipped because " + line1 + " and " + line2 + " were not equal.";
            }
            return ploc;
        }
        if(debug != 0)
        {
            //System.out.println(opDescription);
        }
        return ploc;
    }
    /*public void printCore(int p1loc, int p2loc)
    {
        int dupCount = 0;
        int[] dupLine = new int[]{0,0,0,0,0};
        for(int i = 0; i < core.length; i++)
        {
            int[] line = core[i];
            if(Arrays.equals(line, dupLine) && i != p1loc && i != p2loc)
            {
                if(dupCount == 0)
                {
                    System.out.println(Player.toString(line));
                }
                dupCount++;
            }
            else
            {
                if(dupCount == 2)
                {
                    System.out.println(Player.toString(dupLine));
                }
                else if(dupCount > 2)
                {
                    System.out.println("    " + (dupCount - 1) + " lines skipped.");
                }
                System.out.println(Player.toString(line));
                if(i == p1loc)
                {
                    System.out.print(" <- 1");
                }
                if(i == p2loc)
                {
                    System.out.print(" <- 2");
                }
                dupLine = line;
                dupCount = 1;
            }
        }
        if(dupCount == 2)
        {
            System.out.println(Player.toString(dupLine));
        }
        else if(dupCount > 2)
        {
            System.out.println("    " + (dupCount - 1) + " lines skipped.");
        }
    }*/
}

Sembra che tu abbia apportato una modifica anche a Player. Ricevo./Game.java:275: error: method toString in class Object cannot be applied to given types; System.out.println(Player.toString(line)); ^ required: no arguments found: int[]
AShelly il

@AShelly Mi dispiace per quello. Dovrei commentare il printCore()metodo.
TheNumberOne

9

Turbo

main   add three target
test   jmz -1 @target
bomb   mov three @target
       sub j1 target 
       mov jump @target
       sub j1 target 
       mov copy @target
       sub j1 target
two    mov decr @target
j1     jmp @target 1
target dat -8 -8   
decr   sub #two 3
copy   mov 2 @2
jump   jmp -2 0
three dat -9 -9

Il mio secondo tentativo in assoluto di CoreWar. Progettato per battere Nano. Esegue la scansione dei dati di 3, quindi inserisce una bomba ogni 2. Ogni livello viene eseguito in sole 3 istruzioni, nella speranza che le bombe di Dwarf manchino.

NUOVO Turbo ++ : ora migliorato. Esegue la scansione all'indietro fino a quando non trova i dati, quindi si sposta lì, quindi bombe all'indietro. La speranza è che la mossa sia un ostacolo all'avversario, o sia in un luogo già bombardato e quindi sicuro (ish).

... E una modifica per renderla più scarsamente scossa fa battere tutti!


Sembra battere molto di più di un semplice Nano. Congratulazioni! Penso che potresti raggiungere il terzo posto se solo riuscissi a battere l'Imp.
Ilmari Karonen,

Ho aggiornato questo, ma in realtà è una grande evoluzione rispetto al precedente. Avrei dovuto invece inserire una nuova voce?
AShelly,

Non presumo di parlare per PhiNotPi, ma immagino dipenda da te. Fare un aggiornamento sul posto significa praticamente ritirare la vecchia voce. Ad ogni modo, ancora più congratulazioni per aver schivato con successo le bombe fino al terzo posto! Penso che la tua sia l'unica voce finora a battere DwarvenEngineer in coppia.
Ilmari Karonen,

Molto bene ;). sei tu quello da battere ora!
Hit

8

Nano

Un programma comune e semplice che rappresenta un nano che lancia pietre. Pone DATun'istruzione ogni quattro indirizzi.

add 2 3
mov 2 @2
jmp -2 #4
dat #0 #4

EDIT: risolve l'indirizzamento. Apparentemente le modalità di indirizzamento sono diverse dalle specifiche a cui l'OP era collegato.


Penso che sia "aggiungi # 3 3" per la prima riga, non è vero?
Hit

@Hit Nope. Voglio colpire ogni 4 indirizzo. Potrei usare add 3 3, ma poi raddoppierebbe ogni ciclo invece di aggiungere, e non sarebbe utile. #4è immediato, quindi aggiunge il numero 4al secondo valore nell'indirizzo che si trova 3dopo l'indirizzo corrente.
mbomb007,

Penso che tu stia interpretando male la #modalità di indirizzamento nella sfida. Come indicato nelle specifiche, ho apportato una modifica alla #modalità di indirizzamento.
PhiNotPi

Si dovrebbe andare come: "aggiungere 2 3 mov 2 @ 2 JMP -2 4 dat 0 4"
Hit

Con il comportamento corretto, anche le sconfitte si sono evolute
Hit

7

Evolved

Onestamente non capisco come funziona. Sembra costruire il suo codice sorgente prima di fare qualsiasi cosa. Mi piacerebbe se qualcuno mi desse una spiegazione su come funziona.

Dopo averlo studiato, ho scoperto che è semplicemente un nano modificato con una guardia imp. Invece di bombardare i nemici con le DATistruzioni, mescola il codice dei nemici. Bombe anche ogni due registri anziché ogni quattro registri. Dato abbastanza tempo, si distruggerebbe senza dubbio.

MOV -2 #-1
MOV #4 -9
SUB -5 #6
MOV #1 1
MOV #-6 #4
SUB @8 @7
JMP -3 @4
DAT #-4 8
JMP -1 9
JMP 5 #-10
CMP @-1 #0
SUB 3 #-10
JMP @10 #-9
JMZ #1 10
MOV #3 2
ADD @9 @-3
CMP #-3 @7
DAT @0 @-2
JMP @-7 #6
DAT @-8 -6
MOV @0 #9
MOV #2 1
DAT @6882 #-10
JMP @3 4
CMP @8 2
ADD -7 @11
ADD @1 #-9
JMZ @-5 7
CMP 11 5526
MOV @8 6
SUB -6 @0
JMP 1 11
ADD @-3 #-8
JMZ @-14 @-5
ADD 0 @-8
SUB #3 @9
JMP #-1 5
JMP #9 @1
CMP -9 @0
SUB #4 #-2
JMP #-8 5
DAT -1 @-10
MOV 6 #2
CMP @-11 #-14
ADD @4 @-3
MOV @5 #-6
SUB -3 -2
DAT @-10 #-1
MOV #-13 #-6
MOV #1 5
ADD 5 #-5
MOV -8 @-1
DAT 0 10
DAT #5 #7
JMZ 6 -5
JMZ -12 -11
JMP 5 @-7
MOV #7 -3
SUB #-7 @-3
JMP -4 @-11
CMP @-5 #-2
JMZ @-1 #0
ADD #3 #2
MOV #5 @-6

1
Allora dove l'hai preso?
PyRulez,

4
@PyRulez È generato dal computer tramite algoritmo genetico.
TheNumberOne l'

1
Sembra che l'esecuzione non progredisca effettivamente oltre la riga 6, perché lì salta indietro nel programma. Credo che il motivo per cui abbia successo sia che ci sono più mosse / loop dei suoi concorrenti.
PhiNotPi

6

Novizio

Se funziona, dovrebbe cercare di prendere posizione all'inizio del core e creare un difensore

main MOV 5 #0
     ADD #data #main
     CMP #main #max
     JMP #0 0
     JMP #main 0
     MOV #data #100
     ADD #data -1
     JMP -2 0
data DAT 1 1
max  DAT 8 3

Non funziona esattamente come penso che tu abbia supposto che avrebbe fatto: si #0riferisce all'inizio del tuo programma (vale a dire lo stesso di #main), non all'inizio del core (che comunque non è in realtà un concetto significativo - il core è circolare, il tuo codice non può dire dove inizia o finisce). Quello che succede è che la tua prima istruzione ( main) si sovrascrive con la MOV #data #100, dopodiché il tuo codice si trasforma effettivamente in un clear core forward di 0.25c (= un'istruzione per quattro cicli).
Ilmari Karonen,

@IlmariKaronen Oh, grazie per la spiegazione. Ho sbagliato #0per l'inizio del core. Le prime 5 istruzioni sono quindi completamente inutili.
Thrax,

6

CopyPasta

Non ha mai partecipato a una CoreWar, questo semplice programma sta solo cercando di copiare e incollare se stesso e quindi eseguire la copia. Potrebbe non avere il comportamento corretto, per favore dimmi se è il caso.

È troppo pacifista e in realtà non può vincere.

MOV 6 0
MOV @-1 @-1
CMP @-2 3
JMP 4242 0
SUB -3 -4
JMP -4 0
DAT 0 4244

Questa modifica attuale non sarà probabilmente nel prossimo aggiornamento della classifica (sto correndo il torneo in questo momento). La vecchia versione, tuttavia, stava vincendo i risultati preliminari (dimensioni del nucleo ridotto).
PhiNotPi

Ok :) La versione precedente non esce dal loop1, non è proprio il comportamento desiderato, sto cercando di correggerlo.
Hit

La versione attuale sembra rotta. Non so ancora perché.
PhiNotPi

1
Ho rinnovato gli strumenti di debug, quindi ora posso diagnosticare il problema. Quello che sta accadendo è che il programma copia solo la seconda metà di se stesso (a partire da JMP loop 0). Quindi, quando salta dove dovrebbe essere l'inizio della copia, è solo uno spazio vuoto e perde.
PhiNotPi

2
Si prega di ignorare il mio commento precedente (ora eliminato); Avevo testato una versione errata del tuo codice (ironicamente, a causa di un errore di copia e incolla), motivo per cui ha funzionato così male per me.
Ilmari Karonen,

6

Bidello

Dovrebbe verificare se i seguenti indirizzi sono vuoti e in caso contrario li pulisce (quindi, si spera, cancellando il bot avversario).

Modifica: questa nuova versione dovrebbe essere più veloce (ora che ho capito correttamente il JMZcomando e il @riferimento).

JMZ 2 6
MOV 4 @-1
ADD 2 -2
JMP -3 0
DAT 0 1
DAT 0 0

Il bidello non si sta suicidando con la prima JMZ? Dovrebbe essere almeno JMZ 2 8. A proposito, usando @ potresti ridurre i due aggiungere a uno solo. Qualcosa del tipo: "JMZ 2 @ 5 MOV 5 @ 4 ADD 2 3 JMP -3 0 DAT 0 1 DAT 0 2 DAT 0 0" (non testato)
Hit

@Hit Non salta, perché l'indirizzo 2 da lì è ADD 3 -2, ma hai ragione che dovrebbe cambiarlo, penso.
mbomb007,

Sì, ho letto male le istruzioni JMZe ho pensato che JMZ A Bstesse controllando Ae saltando a Bse 0 quando apparentemente è il contrario. Grazie per averlo notato perché non l'ho fatto :)
plannapus

5

ScanBomber

Rimuovi i miei commenti prima di compilare. Esegue la scansione per un po ', quindi bombe quando trova un programma. Probabilmente perderà comunque per il mio Nano.

scan add #eight #range  ; scan
jmz #scan @range
sub #six #range
fire mov #zero @range   ; bombs away! (-6)
add #two #range
mov #zero @range
add #two #range
mov #zero @range
add #two #range
mov #zero @range        ; (+0)
add #two #range
mov #zero @range
add #two #range
mov #zero @range
add #two #range
mov #zero @range
add #two #range
mov #zero @range        ; (+8)
range jmp #scan 6
two dat 0 2
six dat 0 6
zero dat 0 0
eight dat 0 8

L'OP definito in modo #completamente diverso rispetto alle specifiche (leggi il link a cui si è collegato), devo ancora risolvere questo programma per questo.
mbomb007,

@TheBestOne Penso di averlo corretto. Sembra che abbia senso adesso? O devo mettere #prima di ogni riferimento zero? Sì, penso di dover ...
mbomb007,

Funziona bene ora. Batte ogni robot tranne Dwarf e Imp.
TheNumberOne

@TheBestOne Dwarf è troppo piccolo e verrebbe rilevato solo nel 50% del possibile posizionamento del programma. Probabilmente perde solo Imp perché si bombarda dopo aver girato per intero la memoria.
mbomb007,

5

Han Shot First (v2)

Ho pensato che la concorrenza potesse usare un po 'più di diversità, quindi ecco la mia seconda voce: uno CMPscanner one-shot .

Questa è la versione 2 , con difese anti-Imp migliorate - ora può battere Imp, anche se solo di un punto. Perde ancora all'ingegnere nanico, ma batte tutto il resto finora, mettendolo attualmente al primo posto in parità.

scan    ADD bomb    aim
aim     CMP 17      12
        JMZ scan    #-3
loop    MOV bomb    @aim
        ADD step    aim
step    JMP loop    #2
bomb    DAT 10      10

Funziona confrontando le posizioni centrali adiacenti a 5 passi l'uno dall'altro, a intervalli di 10 passi, fino a trovare una differenza. Quando lo fa, inizia a lanciare bombe a intervalli di 2 passaggi fino a quando non uccide il suo avversario o gira tutto attorno al nucleo per raggiungere se stesso.

Se la scansione fa non trova nient'altro, alla fine si ripeterà e troverà il proprio codice e lo attaccherà. Questo sarebbe un suicidio, ma per la fortunata coincidenza che la prima bomba atterra esattamente sulaim linea, facendo sì che la bomba successiva venga lanciata di 12 posizioni (anziché le solite 2) lungo il nucleo, saltando convenientemente il codice. (Ciò accade anche con una probabilità del 50% se la scansione trova qualcosa, ma non riesce a uccidere l'avversario.) Dato che la dimensione del nucleo è un multiplo di due, ciò continuerà anche a succedere se il bombardamento scorre attorno, eliminando la necessità di un ulteriore strategia di backup.

(Questo trucco di auto-bombardamento era originariamente una pura coincidenza: avevo pianificato un modo completamente diverso di passare dalla modalità di scansione alla modalità di bombardamento se non fosse stato trovato nulla, ma quando ho testato il codice per la prima volta, le costanti erano appena giuste per farlo lavorare in questo modo, e ho deciso di attenermi.)



4

lumaca

     mov    ones    @-1024
     mov    from    -3
     mov    here    -3
loop mov    @-5 @-4
     add    ones  -5
     jmz    -17 -6
     add    ones  -8    
     jmp    loop    42
ones dat    1   1
from dat    2   2
here dat    -11 -11

Scorre indietro lo spazio di memoria. Occasionalmente lancia una bomba lontano.


3

coniglietto di Pasqua

Gli piace saltare all'indietro :)

loop mov 0 -10
     add data loop
     cmp -7 data
     jmp -13 0
     jmp loop 0
data dat 1 1

3

Paranoico

Una specie di copia-pasta ma controllerà se il codice è stato modificato dai bombardamenti. In tal caso, copia oltre un nano ed eseguilo. Se riesco a creare nuovamente GameView, proverò a modificare alcune delle costanti.

copy    MOV data copy
loop    MOV @-1 @-1
    CMP @copy end
out JMP check 0
    SUB loop copy
    JMP loop 0
data    DAT 0 4109
check   MOV data copy
loop2   CMP @copy @copy
    JMP ok 0
    MOV aah 2
ok  CMP @copy end
    JMP 4098 0
    SUB loop copy
    JMP loop2 0
panic   MOV end copy
    MOV jump out
    JMP loop 0
jump    JMP 4124 0
dwarf   ADD 2 bomb
    MOV bomb @bomb
    JMP dwarf 4
bomb    DAT 0 4
aah JMP 3 0
end DAT 19 4127

Va bene, in effetti funziona e stavo solo scherzando, grazie per la nuova corsa;)
Hit
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.