La guerra de Nano Core

21

Esta es una adaptación de Core War , una programación KOTH que data del siglo XX. Para ser más específicos, está utilizando un conjunto de instrucciones increíblemente simplificado basado principalmente en la propuesta original .

Fondo

En Core War, hay dos programas que luchan por el control de la computadora. El objetivo de cada programa es ganar localizando y terminando el programa contrario.

La batalla tiene lugar dentro de la memoria principal de la computadora. Esta memoria se llama Core y contiene 8192 direcciones. Cuando comienza la batalla, el código para cada competidor (llamado guerrero) se coloca en un fragmento aleatorio de memoria. La ejecución del programa alterna entre guerreros, realizando una instrucción de cada uno. Cada instrucción es capaz de modificar una parte del Core, lo que lleva a la posibilidad de programas auto modificables.

El objetivo es terminar el programa contrario. Un programa finaliza cuando intenta ejecutar una instrucción no válida, que es cualquier DATinstrucción.

El conjunto de instrucciones

Cada programa consta de una serie de instrucciones de bajo nivel, cada una de las cuales toma dos campos, llamados campos A y B.

Este conjunto de instrucciones se basa en gran medida de la especificación original. Los principales cambios son 1) aclaraciones sobre la suma / resta de comandos, y 2) un cambio del #modo de direccionamiento para permitir que se use en cualquier lugar. La mayoría de las versiones completas de Core Wars tienen más de 20 códigos de operación, 8 modos de direccionamiento y un conjunto de "modificadores de instrucciones".

Opcodes

Cada instrucción debe tener uno de los siete códigos de operación diferentes.

  • DAT A B- (datos) - Esto simplemente contiene los números Ay B. Es importante destacar que un proceso muere cuando intenta ejecutar una instrucción DAT.
  • MOV A B- (mover): mueve el contenido de la ubicación Ade la memoria a la ubicación de la memoria B. Aquí hay una demostración de antes y después:

    MOV 2 1
    ADD @4 #5
    JMP #1 -1
    
    MOV 2 1
    JMP #1 -1
    JMP #1 -1
    
  • ADD A B- (agregar): agrega el contenido de la ubicación Ade la memoria a la ubicación de la memoria B. Se agregan los dos primeros campos de ambos, y se agregan los segundos campos.

    ADD 2 1
    MOV @4 #5
    JMP #1 -1
    
    ADD 2 1
    MOV @5 #4
    JMP #1 -1
    
  • SUB A B- (restar): resta el contenido de la ubicación de la memoria A(y almacena el resultado en) la ubicación de la memoria B.

    SUB 2 1
    MOV @4 #5
    JMP #1 -1
    
    SUB 2 1
    MOV @3 #6
    JMP #1 -1
    
  • JMP A B- (saltar): salta a la ubicación A, que se ejecutará el próximo ciclo. Bdebe ser un número pero no hace nada (sin embargo, puede usarlo para almacenar información).

    JMP 2 1337
    ADD 1 2
    ADD 2 3
    

    El salto significa que ADD 2 3se ejecutará el próximo ciclo.

  • JMZ A B- (saltar si es cero) - Si ambos campos de línea Bson 0, entonces el programa salta a la ubicación A.

    JMZ 2 1
    SUB 0 @0
    DAT 23 45
    

    Como los dos campos de la instrucción 1 son 0, el comando DAT se ejecutará el próximo turno, lo que provocará la muerte inminente.

  • CMP A B- (comparar y omitir si no es igual) - Si los campos en las instrucciones Ay Bno son iguales, omita la siguiente instrucción.

    CMP #1 2
    ADD 2 #3
    SUB @2 3
    

    Como los dos campos de las instrucciones 1 y 2 tienen el mismo valor, el comando AGREGAR no se omite y se ejecuta el próximo turno.

Cuando se suman / restan dos instrucciones, los dos campos (A y B) se suman / restan por pares. El modo de direccionamiento y el código de operación no cambian.

Modos de direccionamiento

Hay tres tipos de modos de direccionamiento. Cada uno de los dos campos de una instrucción tiene uno de estos tres modos de direccionamiento.

  • Inmediato#X : Xes la línea que se utilizará directamente en el cálculo. Por ejemplo, #0es la primera línea del programa. Las líneas negativas se refieren a líneas en el núcleo antes del inicio del programa.

    ... //just a space-filler
    ...
    ADD #3 #4
    DAT 0 1
    DAT 2 4
    

    Esto agregará la primera de las dos líneas DAT a la segunda, ya que están en las líneas 3 y 4, respectivamente. Sin embargo, no querrá usar este código porque el DAT matará a su bot en el próximo ciclo.

  • RelativoX : el número Xrepresenta la ubicación de una dirección de memoria de destino, en relación con la dirección actual. El número en esta ubicación se usa en el cálculo. Si la línea #35se está ejecutando y contiene -5, entonces #30se usa la línea .

    ... //just a space-filler
    ...
    ADD 2 1
    DAT 0 1
    DAT 2 4
    

    Esto agregará la segunda línea DAT a la primera.

  • Indirecta@X : el número Xrepresenta una dirección relativa. Los contenidos en esa ubicación se agregan temporalmente al número X para formar una nueva dirección relativa, de la que se recupera el número. Si #35se está ejecutando la línea, y su segundo campo es @4, y el segundo campo de la línea #39contiene el número -7, entonces #32se usa la línea .

    ... //just a space-filler
    ...
    ADD @1 @1
    DAT 0 1
    DAT 2 4
    

    Esto agregará el primer DAT al segundo, pero de una manera más complicada. El primer campo es @ 1, que obtiene los datos de esa dirección relativa, que es el primer campo del primer DAT, un 0. Esto se interpreta como una segunda dirección relativa de esa ubicación, por lo que 1 + 0 = 1 da el total desplazamiento de la instrucción original. Para el segundo campo, @ 1 obtiene el valor de esa dirección relativa (el 1 en el segundo campo del primer DAT) y lo agrega a sí mismo de la misma manera. El desplazamiento total es entonces 1 + 1 = 2. Entonces, esta instrucción se ejecuta de manera similar a ADD 1 2.

Cada programa puede contener hasta 64 instrucciones.

Cuando comienza una ronda, los dos programas se colocan aleatoriamente en un banco de memoria con 8192 ubicaciones. El puntero de instrucción para cada programa comienza al comienzo del programa y se incrementa después de cada ciclo de ejecución. El programa muere una vez que su puntero de instrucción intenta ejecutar una DATinstrucción.

Parámetros del núcleo

El tamaño del núcleo es 8192, con un tiempo de espera de 8192 * 8 = 65536 ticks. El núcleo es cíclico, por lo que escribir en la dirección 8195 es lo mismo que escribir en la dirección 3. Todas las direcciones no utilizadas se inicializan en DAT #0 #0.

Cada competidor no debe tener más de 64 líneas. Los enteros se almacenarán como enteros con signo de 32 bits.

Analizando

Para facilitar la programación a los competidores, agregaré una función de etiqueta de línea al analizador. Cualquier palabra que aparezca en una línea antes de un código de operación se interpretará como etiquetas de línea. Por ejemplo, tree mov 4 6tiene la etiqueta de línea tree. Si, en cualquier parte del programa, hay un campo que contiene tree #treeo @tree, se sustituirá un número. Además, se ignora la capitalización.

Aquí hay un ejemplo de cómo se sustituyen las etiquetas de línea:

labelA add labelB @labelC
labelB add #labelC labelC
labelC sub labelA @labelB

Aquí, las etiquetas A, B y C están en las líneas 0, 1 y 2. Las instancias de #labelserán sustituidas por el número de línea de la etiqueta. Las instancias de labelo @labelse sustituyen con la ubicación relativa de la etiqueta. Los modos de direccionamiento se conservan.

ADD 1 @2
ADD #2 1
SUB -2 @-1

Tanteo

Para cada par de concursantes, se realiza cada batalla posible. Dado que el resultado de una batalla depende de las compensaciones relativas de los dos programas, se intenta cada posible desplazamiento (aproximadamente 8000 de ellos). Además, cada programa tiene la oportunidad de moverse primero en cada desplazamiento. El programa que gana la mayoría de estas compensaciones es el ganador de la pareja.

Por cada pareja que gana un guerrero, se le otorgan 2 puntos. Por cada empate, un guerrero recibe 1 punto.

Puedes enviar más de un guerrero. Se aplican las reglas típicas para los envíos múltiples, como no trabajar en equipo, no cooperar, no hacer reyes, etc. De todos modos, en realidad no hay espacio para esto en Core War, por lo que no debería ser un gran problema.

El controlador

El código para el controlador, junto con dos bots de ejemplo fáciles, se encuentra aquí . Dado que esta competencia (cuando se ejecuta con la configuración oficial) es completamente determinista, la tabla de clasificación que cree será exactamente la misma que la tabla de clasificación oficial.

Ejemplo de bot

Aquí hay un ejemplo de bot que muestra algunas características del lenguaje.

main mov bomb #-1
     add @main main
     jmp #main 0
bomb dat 0 -1

Este bot funciona borrando lentamente toda la otra memoria en el núcleo al reemplazarlo con una "bomba". Como la bomba es una DATinstrucción, cualquier programa que llegue a una bomba será destruido.

Hay dos etiquetas de línea, "principal" y "bomba" que sirven para reemplazar los números. Después del preprocesamiento, el programa se ve así:

MOV 3 #-1
ADD @-1 -1
JMP #0 0
DAT 0 -1

La primera línea copia la bomba en la línea inmediatamente superior al programa. La siguiente línea agrega el valor de la bomba ( 0 -1) al comando de movimiento, y también demuestra el uso del @modo de direccionamiento. Esta adición hace que el comando de movimiento apunte a un nuevo objetivo. El siguiente comando salta incondicionalmente al inicio del programa.


Tabla de clasificación actual

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

Resultados por pares:

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

La última actualización (nuevas versiones de Turbo y Paranoid) tardó aproximadamente 5 minutos en ejecutarse en una computadora portátil vieja. Me gustaría agradecer a Ilmari Karonen por sus mejoras en el controlador . Si tiene una copia local del controlador, debe actualizar sus archivos.

PhiNotPi
fuente
¿Qué sucede si dos robots competidores intentan usar la misma etiqueta?
mbomb007
1
@ mbomb007 Las etiquetas son un proceso previo y se calculan a medida que se analiza el archivo fuente del bot. Sus etiquetas no interactuarán con ninguna etiqueta de la competencia.
PhiNotPi
1
@ mbomb007 Para que los programas no se superpongan. Además, no planeo agregar más funciones a esta versión, excepto aquellas para Micro Core War.
PhiNotPi
1
@ mbomb007 El direccionamiento indirecto hace referencia al mismo campo que hace la referencia (1º o 2º). No hay modificadores de instrucciones. No estoy basando este desafío fuera del estándar '94.
PhiNotPi
2
@Thrax Voy a decir que no, que no está limitado a una presentación. Se aplican las reglas típicas de presentación múltiple (sin equipo de etiquetas, etc.), aunque de todos modos no hay mucho espacio para la cooperación en las guerras centrales.
PhiNotPi

Respuestas:

9

Ingeniero enano

Un nuevo y mejorado enano. Victorias contra todo lo demás presentado hasta ahora. El elegante tamaño de paso optimizado de corestep es probablemente excesivo aquí.

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

Las características notables incluyen el ciclo de bombardeo rápido que arroja dos bombas en cuatro ciclos, para una velocidad de bombardeo promedio de 0.5c en la antigua jerga de la Guerra del Núcleo, y el uso de JMZdetectar cuándo se completa la ejecución del bombardeo y es hora de cambiar al plan B ( aquí, un diablillo).


Solía ​​jugar Core War en los años 90 (algunos de ustedes habrán visto la guía básica que escribí en el '97), así que pensé que sería interesante ver qué viejas estrategias del mundo RedCode '88 / '94 podrían Ser útil en esta variante.

Mis primeros pensamientos fueron:

  • No hay SPL, por lo tanto, no hay replicadores (y no hay anillos de imp / espirales). Esto debería hacer que los bombarderos sean fuertes. (Además, todas aquellas estrategias de bombardeo de lujo diseñados para hacer frente a los replicadores e IMP espirales? Totalmente innecesario e inútil aquí. Sólo con cualquier bomba DATs.)

  • Por otra parte, el CMPescaneo sigue siendo potencialmente más rápido que el bombardeo, por lo que un escáner rápido podría tener una oportunidad.

  • La ausencia de entradas / decrementos hace que los borrados de núcleo sean muy lentos. De hecho, un núcleo claro en esta variante es prácticamente un bombardero con un tamaño de paso (subóptimo) de ± 1. De nuevo, esto también perjudica a los escáneres; Sin embargo, una estrategia de escáner de un solo disparo → bombardero podría funcionar.

  • Los Quickscanners / quickbombers (una estrategia de juego temprano que usa un ciclo de escaneo / bombardeo desenrollado, para aquellos que no están tan familiarizados con la jerga de Core War) siguen siendo potencialmente útiles, pero solo contra programas largos (que ellos mismos son, por lo que hay una especie de retroalimentación efecto aquí). Es difícil decir si realmente vale la pena.

  • El sistema de puntuación es interesante. Los empates suman la mitad de puntos que una victoria (en lugar de 1/3, como en la Guerra del Núcleo tradicional), lo que los hace más atractivos. Por otra parte, el único programa que probablemente tenga muchos lazos bajo estas reglas es un diablillo. (Además, la ausencia de de / incrementos puertas hace imp duro, por lo que incluso simples diablillos realmente hacer tener la oportunidad de marcar un empate si llegan a su oponente con vida.)

  • Además, debido a la clasificación final sólo dependen de qué programas le ganas, y no como mucho les ganas por, tiende a favorecer las entradas generalistas. Es mejor vencer apenas a todos tus oponentes, que destruir totalmente a la mitad de ellos y apenas perder al resto.

  • Debido a que el código es público, siempre es posible encontrar un programa que pueda superar cualquier envío anterior, posiblemente incluso varios de ellos, sin importar cuán buenos sean en general. Sin embargo, tales trucos (como ajustar el tamaño de tu paso para golpear a tu oponente justo antes de que te golpeen) pueden parecer fácilmente baratos. Y, por supuesto, el jugador objetivo siempre puede enviar una nueva versión con diferentes constantes.

De todos modos, el resultado de todo esto es que decidí que debería tratar de escribir un bombardero rápido o un escáner muy rápido, y tal vez agregarle un escáner / bombardero rápido. Fuera de esas opciones, un bombardero rápido parecía el más simple y más probable que funcionara.

En ese momento, pasé demasiado tiempo ajustando y optimizando el código de intérprete de PhiNotPi, porque pensé que probablemente estaría ejecutando muchas pruebas de fuerza bruta para optimizar las constantes. Como sucede, nunca tuve que hacer eso: el código anterior es más o menos la primera versión que realmente funcionó (después de un par de intentos fallidos que se suicidaron debido a errores tontos).


El truco que hace que mi bombardero sea rápido es usar direccionamiento indirecto para lanzar dos bombas por cada uno ADD. Así es como funciona:

  1. En el primer ciclo, ejecutamos MOV bomb @aim. Esto copia la bombinstrucción a donde sea en el núcleo del campo B de aimpuntos (inicialmente, exactamente 6326 instrucciones antes aim, o 6328 instrucciones antes step; verá por qué esos números importan más adelante).

  2. ¡En el siguiente paso, ejecutamos la aiminstrucción en sí! En la primera pasada, se parece a esto: MOV bomb @-6326. Por lo tanto, se copia bomben la ubicación a la que apunta el campo B de la instrucción en 6326 líneas antes.

    Entonces, ¿qué hay antes en 6326 líneas aim? ¡Por qué, es la copia bombque acabamos de colocar allí un ciclo antes! Y acabamos de organizar las cosas para que el campo B bombtenga un valor distinto de cero, de modo que la nueva bomba no se copie encima de la anterior, sino a cierta distancia (de hecho, aquí la distancia es 3164, que es la mitad de nuestro tamaño de paso nominal 6328; pero otras compensaciones podrían funcionar, tal vez incluso mejor).

  3. En el próximo ciclo, ajustamos nuestro objetivo con SUB step aim, que resta los valores de la stepinstrucción (que también resulta ser el salto que vamos a ejecutar a continuación, aunque podría haber sido un simple en DATalguna parte) aim.

    (Un detalle a tener en cuenta es que tipo de queremos la A-valor de stepser cero, por lo que todavía vamos a lanzar las mismas bombas en la siguiente iteración Aun que no es estrictamente necesario, sin embargo;. Sólo tiran las bombas por la primera instrucción necesita tener su campo B igual a 3164, el resto puede ser cualquier cosa).

  4. A continuación, la JMZcomprobación de que la instrucción 6328 se aleja de ella sigue siendo cero y, de ser así, vuelve a la parte superior del código. Ahora, 6328 es el tamaño de paso de nuestro bombardero, y es divisible por 8 (pero no 16); por lo tanto, si seguimos lanzando bombas cada 6328 pasos, eventualmente volveríamos a donde comenzamos, bombardeando cada octava instrucción en el núcleo (y con las bombas adicionales compensadas por 3163 = 6328/2 ≡ 4 (mod 8) , habríamos acertado cada cuarta instrucción).

    Pero comenzamos nuestra carrera de bombardeo con 6328 instrucciones antes de la JMZ, y retrocedimos -6328 en cada iteración, por lo que vamos a bombardear la ubicación 6328 pasos después de la JMZúnica iteración antes de golpearnos JMZ. Entonces, cuando JMZdetecta una bomba a las 6328 instrucciones posteriores, es una señal de que hemos cubierto la mayor parte del núcleo posible sin golpearnos, y debería cambiar a una estrategia de respaldo antes de matarnos.

  5. En cuanto a la estrategia de respaldo, es simplemente un viejo MOV 0 1diablillo, porque no puedo pensar en nada mejor por ahora. Desde mi punto de vista, si hemos bombardeado cada cuarta ubicación del núcleo y aún no hemos ganado, probablemente estamos luchando contra algo muy pequeño o muy defensivo, y también podríamos tratar de sobrevivir y conformarnos con un empate. Está bien, porque los programas tan pequeños o defensivos generalmente no son muy buenos para matar cualquier otra cosa, por lo que incluso si solo ganamos algunas peleas por casualidad, probablemente todavía saldremos adelante.


PD. En caso de que alguien más lo quiera, aquí está mi bifurcación ligeramente mejorada del código de torneo de PhiNotPi . Es aproximadamente el doble de rápido, guarda los viejos resultados de la batalla para que no tengas que volver a ejecutarlos y corrige lo que creo que es un error menor en el cálculo de los resultados de la batalla. Los cambios se han fusionado en la versión principal por PhiNotPi. ¡Gracias!

Ilmari Karonen
fuente
1
Para que lo sepas, la puntuación evalúa CADA combinación posible de ubicaciones de inicio del programa, y ​​el programa que obtiene la mayor cantidad de puntos gana. Esto hace que los lazos sean imposibles o completamente desfavorables, ya que mientras un programa nunca se mate a sí mismo y bombardee al menos una dirección una vez, vencerá a un diablillo, ganando una, y el resto empata.
mbomb007
9

Vista gráfica

Esto se puede usar como una herramienta de depuración. Muestra el núcleo y muestra la ubicación del jugador. Para usarlo debes llamarlo desde el código. También he proporcionado una modificación Game.javaque muestra automáticamente GraphView.

PhiNotPi e Ilmari Karonen cambiaron recientemente el controlador. Ilmari Karonen ha tenido la amabilidad de proporcionar un GameView actualizado en esta ubicación .

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 modificado:

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.");
        }
    }*/
}
El numero uno
fuente
Parece que también hiciste una modificación en Player. Obtengo./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
@AShelly Lo siento por eso. Debería haber comentado el printCore()método.
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

Mi segundo intento de CoreWar. Diseñado para vencer a los enanos. Escanea por 3 en busca de datos, luego coloca una bomba cada 2. Cada etapa se ejecuta en solo 3 instrucciones, con la esperanza de que las bombas de Dwarf la pierdan.

NUEVO Turbo ++ : ahora mejorado. Escanea hacia atrás hasta que encuentra datos, luego se mueve allí, luego bombardea hacia atrás. La esperanza es que el movimiento apunte al oponente o sea a un lugar ya bombardeado y, por lo tanto, seguro (ish).

... Y una edición para escanear más escasamente hace que venza a todos.

AShelly
fuente
Parece vencer mucho más que solo Dwarf. ¡Felicidades! Creo que podrías alcanzar el tercer lugar si solo pudieras vencer a Imp.
Ilmari Karonen
Actualicé este, pero en realidad es una evolución bastante grande con respecto al anterior. ¿Debería haber hecho una nueva entrada en su lugar?
AShelly
No pretendo hablar por PhiNotPi, pero supongo que depende de ti. Hacer una actualización en el lugar básicamente significa retirar su entrada anterior. De todos modos, ¡incluso más felicitaciones por haber logrado esquivar con éxito el tercer lugar! Creo que la tuya es la única entrada hasta ahora para vencer a DwarvenEngineer por parejas.
Ilmari Karonen
Bien hecho ;). usted es el que debe vencer ahora!
Hit
8

Enano

Un programa común y simple que representa a un enano arrojando piedras. Coloca una DATinstrucción cada cuatro direcciones.

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

EDITAR: corrige el direccionamiento. Aparentemente, los modos de direccionamiento son diferentes de la especificación a la que está vinculado el OP.

mbomb007
fuente
Creo que es "agregar # 3 3" para la primera línea, ¿no?
Hit
@ Golpe Nope. Quiero golpear cada 4ta dirección. Podría usar add 3 3, pero luego duplicaría cada ciclo en lugar de agregar, y eso no sería útil. #4es inmediato, por lo que agrega el número 4al segundo valor en la dirección que está 3después de la dirección actual.
mbomb007
Creo que estás malinterpretando el #modo de direccionamiento en el desafío. Como se indica en la especificación, hice un cambio en el #modo de direccionamiento.
PhiNotPi
Debería ir como: "agregar 2 3 mov 2 @ 2 jmp -2 4 dat 0 4"
Hit
Con el comportamiento correcto, incluso las derrotas evolucionaron
Hit
7

Evolucionado

Sinceramente, no entiendo cómo funciona. Parece construir su código fuente antes de hacer nada. Me encantaría que alguien me diera una explicación de cómo funciona.

Después de estudiarlo, descubrí que es simplemente un enano modificado con un guardia de diablillos. En lugar de bombardear a los enemigos con DATinstrucciones, baraja el código de los enemigos. También bombardea cada dos registros en lugar de cada cuatro registros. Con suficiente tiempo, sin duda se destruiría a sí mismo.

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
El numero uno
fuente
1
Entonces, ¿de dónde lo sacaste?
PyRulez
44
@PyRulez Es generado por computadora a través del algoritmo genético.
TheNumberOne
1
Parece que la ejecución en realidad no progresa más allá de la línea # 6, porque allí salta al programa. Creo que la razón por la que tiene éxito es que hay más movimientos / bucles que sus competidores.
PhiNotPi
6

FirstTimer

Si funciona, debería tratar de tomar posición al comienzo del núcleo y crear defensas

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
Thrax
fuente
No funciona de la manera que creo que suponía que sería: se #0refiere al inicio de su programa (es decir, lo mismo que #main), no al inicio del núcleo (que de todos modos no es realmente un concepto significativo; el núcleo es circular, su código no puede decir dónde comienza o dónde termina). Lo que sucede es que su primera instrucción ( main) se sobrescribe con la MOV #data #100, después de lo cual su código efectivamente se convierte en 0.25c (= una instrucción por cuatro ciclos) hacia adelante y hacia el centro.
Ilmari Karonen
@IlmariKaronen Oh, gracias por la explicación. Me equivoqué #0al comienzo del núcleo. Las 5 primeras instrucciones son completamente inútiles entonces.
Thrax
6

CopyPasta

Nunca participó en un CoreWar, este simple programa solo está tratando de copiar y pegar él mismo y luego ejecutar la copia. Puede que no tenga el comportamiento correcto, por favor dígame si es el caso.

Es demasiado pacifista y, de hecho, no puede ganar.

MOV 6 0
MOV @-1 @-1
CMP @-2 3
JMP 4242 0
SUB -3 -4
JMP -4 0
DAT 0 4244
Golpear
fuente
Esta edición actual probablemente no estará en la próxima actualización de la tabla de clasificación (estoy ejecutando el torneo ahora). Sin embargo, la versión anterior estaba ganando los resultados preliminares (tamaño de núcleo pequeño).
PhiNotPi
Bien :) La versión anterior no sale del bucle1, no es realmente el comportamiento deseado, estoy tratando de corregir eso.
Hit
La versión actual parece rota. No sé por qué, todavía.
PhiNotPi
1
He renovado las herramientas de depuración, por lo que ahora puedo diagnosticar el problema. Lo que está sucediendo es que el programa solo copia la segunda mitad de sí mismo (comenzando en JMP loop 0). Luego, cuando salta a donde debería estar el inicio de la copia, es solo un espacio vacío y pierde.
PhiNotPi
2
Ignore mi comentario anterior (ahora eliminado); Había probado una versión incorrecta de su código (irónicamente, debido a un error de copiar y pegar), por lo que funcionó tan mal para mí.
Ilmari Karonen
6

Portero

Debe verificar si las siguientes direcciones están vacías y, de lo contrario, las limpia (por lo tanto, con suerte, borrando el bot oponente).

Editar: esta nueva versión debería ser más rápida (ahora que entendí el JMZcomando y la @referencia correctamente).

JMZ 2 6
MOV 4 @-1
ADD 2 -2
JMP -3 0
DAT 0 1
DAT 0 0
plannapus
fuente
¿No se está suicidando el conserje con el primer JMZ? Debería ser al menos JMZ 2 8. Por cierto, usando @ podría reducir las dos adiciones a una sola. Algo así como: "JMZ 2 @ 5 MOV 5 @ 4 ADD 2 3 JMP -3 0 DAT 0 1 DAT 0 2 DAT 0 0" (no probado)
Hit
@Hit No salta, porque la dirección 2 a partir de ahí es ADD 3 -2, pero tienes razón en que debería cambiarla, creo.
mbomb007
Sí, leí mal las instrucciones JMZy pensé que JMZ A Bestaba comprobando Ay saltando a Bsi 0 cuando aparentemente es lo contrario. Gracias por notarlo porque no lo hice :)
plannapus
5

ScanBomber

Eliminar mis comentarios antes de compilar. Escanea por un tiempo, luego bombardea cuando encuentra un programa. Sin embargo, probablemente todavía perderá contra mi Enano.

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
mbomb007
fuente
El OP se definió de manera #completamente diferente a la especificación (lea el enlace al que se vinculó), todavía tengo que arreglar este programa.
mbomb007
@TheBestOne Creo que lo arreglé. ¿Parece que tiene sentido ahora? ¿O necesito poner #antes de cada referencia zero? Sí, creo que necesito ...
mbomb007
Funciona bien ahora. Le gana a todos los bot excepto Dwarf e Imp.
TheNumberOne
@TheBestOne Dwarf es demasiado pequeño y solo se detectaría en el 50% de la posible ubicación del programa. Es probable que solo pierda ante Imp porque se bombardea después de recorrer la totalidad de la memoria.
mbomb007
5

Han disparó primero (v2)

Pensé que la competencia podría usar más diversidad, así que aquí está mi segunda entrada: un CMPescáner de una sola vez .

Esta es la versión 2 , con defensas anti-Imp mejoradas: ahora puede vencer a Imp, aunque solo sea por un punto. Todavía pierde ante Dwarven Engineer, pero supera todo lo demás hasta el momento, colocándolo actualmente en primer lugar empatado.

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

Funciona comparando ubicaciones centrales adyacentes con 5 pasos de diferencia, a intervalos de 10 pasos, hasta que encuentre una diferencia. Cuando lo hace, comienza a lanzar bombas a intervalos de 2 pasos hasta que mata a su oponente o recorre todo el núcleo para alcanzarlo.

Si el escaneo no encuentra nada más, eventualmente dará vueltas y encontrará su propio código y lo atacará. Esto sería suicida, pero por la afortunada coincidencia de que la primera bomba aterriza directamente en elaim línea, causando que la siguiente bomba sea arrojada 12 posiciones (en lugar de las 2 habituales) por el núcleo, omitiendo convenientemente el código. (Esto también ocurre con un 50% de probabilidad si el escaneo encuentra algo, pero no logra matar al oponente). Dado que el tamaño del núcleo es un múltiplo de dos, esto también seguirá sucediendo si la ejecución del bombardeo gira, eliminando la necesidad de un estrategia de respaldo adicional.

(Este truco de auto-bombardeo fue originalmente una pura coincidencia: había planeado una forma completamente diferente de cambiar del modo de escaneo al de bombardeo si no se encontraba nada, pero cuando probé el código por primera vez, las constantes eran correctas para hacerlo trabajar de esta manera, y decidí seguir con eso)

Ilmari Karonen
fuente
4

Diablillo

MOV 0 1

Simplemente pulgadas a través del programa.

El numero uno
fuente
4

Babosa

     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

Se arrastra por el espacio de la memoria hacia atrás. Ocasionalmente arroja una bomba muy lejos.

AShelly
fuente
3

conejo de Pascua

Le gusta saltar hacia atrás :)

loop mov 0 -10
     add data loop
     cmp -7 data
     jmp -13 0
     jmp loop 0
data dat 1 1
El numero uno
fuente
3

Paranoico

Es una especie de copia de pasta, pero comprobará si el código ha sido modificado por bombardeos. Si es así, copia más allá de un enano y ejecútalo. Si logro volver a hacer GameView, intentaré cambiar algunas de las constantes.

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
Golpear
fuente
Bien, de hecho está funcionando y solo estaba haciendo un mensaje, gracias por la nueva carrera;)
Golpeé el