La guerre Nano Core

21

Il s'agit d'une adaptation de Core War , une programmation KOTH datant du 20e siècle. Pour être plus précis, il utilise un ensemble d'instructions incroyablement simplifié basé principalement sur la proposition d'origine .

Contexte

Dans Core War, deux programmes se battent pour contrôler l'ordinateur. Le but de chaque programme est de gagner en localisant et en terminant le programme opposé.

La bataille se déroule dans la mémoire principale de l'ordinateur. Cette mémoire est appelée Core et contient 8192 adresses. Lorsque la bataille commence, le code de chaque compétiteur (appelé guerrier) est placé dans un morceau de mémoire aléatoire. L'exécution du programme alterne entre les guerriers, exécutant une instruction de chacun. Chaque instruction est capable de modifier une partie du Core, conduisant à la possibilité de programmes auto-modifiés.

Le but est de mettre fin au programme adverse. Un programme se termine lorsqu'il tente d'exécuter une instruction non valide, qui est n'importe quelle DATinstruction.

L'ensemble d'instructions

Chaque programme se compose d'une série d'instructions de bas niveau, chacune prenant deux champs, appelés champs A et B.

Ce jeu d'instructions s'inspire largement des spécifications d'origine. Les principaux changements sont 1) des précisions sur l'ajout / la soustraction de commandes et 2) un changement du #mode d'adressage pour permettre son utilisation n'importe où. La plupart des versions complètes de Core Wars ont plus de 20 opcodes, 8 modes d'adressage et un ensemble de "modificateurs d'instructions".

Opcodes

Chaque instruction doit avoir l'un des sept opcodes différents.

  • DAT A B- (données) - Cela contient simplement les chiffres Aet B. Surtout, un processus meurt lorsqu'il tente d'exécuter une instruction DAT.
  • MOV A B- (déplacer) - Ceci déplace le contenu de l'emplacement Amémoire vers l'emplacement mémoire B. Voici une démonstration d'avant-après:

    MOV 2 1
    ADD @4 #5
    JMP #1 -1
    
    MOV 2 1
    JMP #1 -1
    JMP #1 -1
    
  • ADD A B- (ajouter) - Ceci ajoute le contenu de l'emplacement Amémoire à l'emplacement mémoire B. Les deux premiers champs des deux sont ajoutés et les seconds champs sont ajoutés.

    ADD 2 1
    MOV @4 #5
    JMP #1 -1
    
    ADD 2 1
    MOV @5 #4
    JMP #1 -1
    
  • SUB A B- (soustraire) - Ceci soustrait le contenu de l'emplacement mémoire Ade (et stocke le résultat dans) l'emplacement mémoire B.

    SUB 2 1
    MOV @4 #5
    JMP #1 -1
    
    SUB 2 1
    MOV @3 #6
    JMP #1 -1
    
  • JMP A B- (saut) - Aller à l'emplacement A, qui sera exécuté au cycle suivant. Bdoit être un nombre mais ne fait rien (vous pouvez cependant l'utiliser pour stocker des informations).

    JMP 2 1337
    ADD 1 2
    ADD 2 3
    

    Le saut signifie que ADD 2 3sera exécuté le cycle suivant.

  • JMZ A B- (saut si zéro) - Si les deux champs de ligne Bsont à 0, alors le programme saute à l'emplacement A.

    JMZ 2 1
    SUB 0 @0
    DAT 23 45
    

    Comme les deux champs de l'instruction 1 sont 0, la commande DAT sera exécutée au tour suivant, entraînant une mort imminente.

  • CMP A B- (comparer et ignorer s'ils ne sont pas égaux) - Si les champs des instructions Aet Bne sont pas égaux, ignorer l'instruction suivante.

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

    Étant donné que les deux champs des instructions 1 et 2 ont une valeur égale, la commande ADD n'est pas ignorée et est exécutée au tour suivant.

Lorsque deux instructions sont ajoutées / soustraites, les deux champs (A et B) sont ajoutés / soustraits par paire. Le mode d'adressage et l'opcode ne sont pas modifiés.

Modes d'adressage

Il existe trois types de modes d'adressage. Chacun des deux champs d'une instruction possède l'un de ces trois modes d'adressage.

  • Immédiat#X - Xest la ligne à utiliser directement dans le calcul. Par exemple, #0est la première ligne du programme. Les lignes négatives font référence aux lignes du noyau avant le début du programme.

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

    Cela ajoutera la première des deux lignes DAT à la seconde, car celles-ci se trouvent respectivement sur les lignes 3 et 4. Cependant, vous ne voudriez pas utiliser ce code, car le DAT va tuer votre bot au cycle suivant.

  • RelativeX - Le nombre Xreprésente l'emplacement d'une adresse de mémoire cible, par rapport à l'adresse actuelle. Le nombre à cet emplacement est utilisé dans le calcul. Si la ligne #35est en cours d'exécution et contient -5, alors la ligne #30est utilisée.

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

    Cela ajoutera la deuxième ligne DAT à la première.

  • Indirect@X - Le nombre Xreprésente une adresse relative. Le contenu à cet emplacement est temporairement ajouté au numéro X pour former une nouvelle adresse relative, à partir de laquelle le numéro est récupéré. Si la ligne #35est en cours d'exécution et que son deuxième champ l'est @4et que le deuxième champ de la ligne #39contient le nombre -7, alors la ligne #32est utilisée.

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

    Cela ajoutera le premier DAT au second, mais d'une manière plus compliquée. Le premier champ est @ 1, qui obtient les données de cette adresse relative, qui est le premier champ du premier DAT, un 0. Ceci est interprété comme une deuxième adresse relative de cet emplacement, donc 1 + 0 = 1 donne le total décalage par rapport à l'instruction d'origine. Pour le deuxième champ, @ 1 obtient la valeur de cette adresse relative (le 1 dans le deuxième champ du premier DAT) et l'ajoute à lui-même de la même manière. Le décalage total est alors 1 + 1 = 2. Ainsi, cette instruction est exécutée de manière similaire à ADD 1 2.

Chaque programme peut contenir jusqu'à 64 instructions.

Lorsqu'un tour commence, les deux programmes sont placés de manière aléatoire dans une banque de mémoire avec 8192 emplacements. Le pointeur d'instruction pour chaque programme commence au début du programme et est incrémenté après chaque cycle d'exécution. Le programme meurt une fois que son pointeur d'instruction a tenté d'exécuter une DATinstruction.

Paramètres du noyau

La taille de base est 8192, avec un délai d'attente de 8192 * 8 = 65536 ticks. Le cœur est cyclique, donc l'écriture à l'adresse 8195 équivaut à l'écriture à l'adresse 3. Toutes les adresses inutilisées sont initialisées à DAT #0 #0.

Chaque concurrent ne doit pas dépasser 64 lignes. Les entiers seront stockés sous forme d'entiers signés 32 bits.

Analyse

Afin de faciliter la programmation pour les concurrents, j'ajouterai une fonction d'étiquette de ligne à l'analyseur. Tous les mots qui apparaissent sur une ligne avant un opcode seront interprétés comme des étiquettes de ligne. Par exemple, tree mov 4 6a l'étiquette de ligne tree. Si, n'importe où dans le programme, un champ contient tree #treeou @tree, un nombre sera substitué. De plus, la capitalisation est ignorée.

Voici un exemple de substitution d'étiquettes de ligne:

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

Ici, les étiquettes A, B et C sont sur les lignes 0, 1 et 2. Les instances de #labelseront remplacées par le numéro de ligne de l'étiquette. Les instances de labelou @labelsont remplacées par l'emplacement relatif de l'étiquette. Les modes d'adressage sont conservés.

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

Notation

Pour chaque paire de concurrents, chaque bataille possible est effectuée. Étant donné que l'issue d'une bataille dépend des décalages relatifs des deux programmes, chaque décalage possible (environ 8 000 d'entre eux) est essayé. De plus, chaque programme a la possibilité de se déplacer en premier dans chaque décalage. Le programme qui remporte la majorité de ces offsets est le vainqueur de la paire.

Pour chaque paire gagnée par un guerrier, il obtient 2 points. Pour chaque égalité, un guerrier reçoit 1 point.

Vous êtes autorisé à soumettre plus d'un guerrier. Les règles typiques pour les soumissions multiples s'appliquent, telles que pas de tag-teaming, pas de coopération, pas de création de roi, etc.

Le controlle

Le code du contrôleur, avec deux exemples de robots simples, se trouve ici . Étant donné que cette compétition (lorsqu'elle est exécutée à l'aide des paramètres officiels) est complètement déterministe, le classement que vous créez sera exactement le même que le classement officiel.

Exemple Bot

Voici un exemple de bot qui montre certaines fonctionnalités du langage.

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

Ce bot fonctionne en effaçant lentement toutes les autres mémoires du noyau en le remplaçant par une "bombe". Puisque la bombe est une DATinstruction, tout programme qui atteint une bombe sera détruit.

Il y a deux étiquettes de ligne, "principale" et "bombe" qui remplacent les numéros. Après le prétraitement, le programme ressemble à ceci:

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

La première ligne copie la bombe sur la ligne immédiatement au-dessus du programme. La ligne suivante ajoute la valeur de la bombe ( 0 -1) à la commande de déplacement, et elle montre également une utilisation du @mode d'adressage. Cet ajout fait que la commande de déplacement pointe vers une nouvelle cible. La commande suivante retourne inconditionnellement au début du programme.


Classement actuel

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

Résultats par paire:

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 dernière mise à jour (nouvelles versions de Turbo et Paranoid) a pris environ 5 minutes pour fonctionner sur un ancien ordinateur portable. Je tiens à remercier Ilmari Karonen pour ses améliorations au contrôleur . Si vous avez une copie locale du contrôleur, vous devez mettre à jour vos fichiers.

PhiNotPi
la source
Que se passe-t-il si deux robots concurrents tentent d'utiliser le même label?
mbomb007
1
@ mbomb007 Les étiquettes sont des éléments de prétraitement et sont calculées lorsque le fichier source du bot est analysé. Vos étiquettes n'interagiront pas avec les étiquettes de concurrents.
PhiNotPi
1
@ mbomb007 Pour que les programmes ne se chevauchent pas. De plus, je ne prévois pas d'ajouter d'autres fonctionnalités à cette version, sauf celles de Micro Core War.
PhiNotPi
1
@ mbomb007 L'adressage indirect fait référence au même champ qui fait la référence (1er ou 2e). Il n'y a pas de modificateurs d'instructions. Je ne fonde pas ce défi sur la norme '94.
PhiNotPi
2
@Thrax, je vais dire non, que vous n'êtes pas limité à une seule soumission. Les règles typiques de soumission multiple s'appliquent (pas de tag-teaming, etc.), bien qu'il n'y ait de toute façon pas beaucoup de place pour la coopération dans les guerres principales.
PhiNotPi

Réponses:

9

Ingénieur nain

Un nain nouveau et amélioré. Victoires contre tout le reste soumis jusqu'à présent. La fantaisie taille de pas optimisée corestep optimisée est probablement exagérée ici.

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

Les fonctionnalités notables incluent la boucle de bombardement rapide qui lance deux bombes en quatre cycles, pour une vitesse de bombardement moyenne de 0,5 c dans l'ancien jargon de Core War, et l'utilisation de JMZpour détecter lorsque le bombardement est terminé et qu'il est temps de passer au plan B ( ici, un lutin).


Je jouais à Core War dans les années 90 (certains d'entre vous ont peut-être vu guide de j'ai écrit en 1997), donc j'ai pensé qu'il serait intéressant de voir quelles anciennes stratégies du monde RedCode '88 / '94 pourraient être utile dans cette variante.

Mes premières réflexions ont été:

  • Il n'y a pas SPL , donc pas de réplicateurs (et pas d'anneaux / spirales d'imp). Cela devrait renforcer les bombardiers. (Aussi, toutes ces stratégies de bombardement sophistiquées conçues pour faire face aux réplicateurs et aux spirales de lutin? Totalement inutiles et inutiles ici. Il suffit de bombarder avec n'importe quel DATart.)

  • Là encore, la CMPnumérisation est toujours potentiellement plus rapide que le bombardement, donc un scanner rapide pourrait avoir une chance.

  • L'absence d'entrées / descentes rend les effacements de cœur très lents. En fait, un noyau clair dans cette variante est à peu près juste un bombardier avec une taille de pas (sous-optimale) de ± 1. Encore une fois, cela nuit également aux scanners; un scanner à un coup → stratégie de bombardier pourrait cependant fonctionner.

  • Les scanneurs rapides / bombardiers rapides (une stratégie de début de jeu utilisant une boucle de balayage / bombardement déroulée, pour ceux qui ne connaissent pas trop le jargon de Core War) sont toujours potentiellement utiles, mais uniquement contre les programmes longs (ce qu'ils sont eux-mêmes, donc il y a une sorte de rétroaction effet ici). Difficile de dire si cela en vaut vraiment la peine.

  • Le système de notation est intéressant. Les égalités marquent la moitié autant de points qu'une victoire (plutôt que 1/3, comme dans Core War traditionnel), ce qui les rend plus attrayantes. Là encore, le seul programme susceptible de marquer beaucoup de liens selon ces règles est un imp. (De plus, l'absence de dé / incréments rend les portes des lutins difficiles, donc même les simples lutins ont en fait une chance de marquer une égalité s'ils atteignent leur adversaire vivant.)

  • De plus, parce que le classement final ne dépend que des programmes que vous battez, et non de la fréquence à laquelle vous les battez, il a tendance à favoriser les entrées généralistes. Il est préférable de battre à peine tous vos adversaires, que de détruire totalement la moitié d'entre eux et de perdre à peine le reste.

  • Parce que le code est public, il est toujours possible de trouver un programme qui peut battre n'importe quelle soumission antérieure donnée - peut-être même plusieurs d'entre eux - peu importe leur qualité en général. De telles astuces (comme ajuster la taille de votre pas pour frapper votre adversaire juste avant de vous frapper) peuvent facilement sembler bon marché. Et, bien sûr, le joueur cible pourrait toujours simplement soumettre une nouvelle version avec différentes constantes.

Quoi qu'il en soit, le résultat de tout cela est que j'ai décidé d'essayer d'écrire soit un bombardier rapide soit un scanner très rapide, et peut être de coller un scanner rapide / bombardier dessus. Parmi ces options, un bombardier rapide semblait le plus simple et le plus susceptible de fonctionner.

À ce stade, j'ai passé beaucoup trop de temps à peaufiner et à optimiser le code de l'interpréteur de PhiNotPi, parce que je pensais que je ferais probablement beaucoup d'essais de force brute pour optimiser les constantes. En l'occurrence, je n'ai jamais eu à le faire - le code ci-dessus est à peu près la première version qui a réellement fonctionné (après quelques tentatives infructueuses qui se sont suicidées à cause de bugs stupides).


L'astuce qui rend mon bombardier rapide est d'utiliser l'adressage indirect pour lancer deux bombes pour chacune ADD. Voici comment cela fonctionne:

  1. Au premier cycle, nous exécutons MOV bomb @aim. Cela copie l' bombinstruction à l'endroit où se trouve dans le noyau le champ B des aimpoints (initialement, exactement 6326 instructions avant aim, ou 6328 instructions avantstep ; vous verrez pourquoi ces nombres importent plus tard).

  2. À l'étape suivante, nous exécutons l' aiminstruction elle-même! Au premier passage, il ressemble à ceci: MOV bomb @-6326. Ainsi, il copie bombà l'emplacement vers lequel pointe le champ B de l'instruction à 6326 lignes avant lui-même.

    Alors, qu'y a-t-il à 6326 lignes avant aim? Eh bien, c'est la copie de bombnous venons de placer là un cycle plus tôt! Et nous nous sommes juste arrangés pour que le champ B debomb ait une valeur non nulle, de sorte que la nouvelle bombe ne sera pas copiée sur l'ancienne, mais à une certaine distance (en fait, ici, la distance est 3164, qui est la moitié de notre taille de pas nominale 6328; mais d'autres décalages pourraient fonctionner, peut-être même mieux).

  3. Au cycle suivant, nous ajustons notre objectif avec SUB step aim, ce qui soustrait les valeurs de l' stepinstruction (qui se trouve également être le saut que nous allons exécuter ensuite, bien que cela aurait pu être juste un simple DATquelque part) deaim .

    (Un détail à noter ici est que nous voulons en quelque sorte que la valeur A stepsoit nulle, de sorte que nous lancerons toujours les mêmes bombes à la prochaine itération. Cependant, cela n'est pas strictement nécessaire; seules les bombes lancées par la première instruction doit avoir leur champ B égal à 3164, le reste peut être n'importe quoi.)

  4. Ensuite, la JMZvérification que l'instruction 6328 s'éloigne d'elle est toujours nulle, et si c'est le cas, revient en haut du code. Maintenant, 6328 est la taille de pas de notre bombardier, et est divisible par 8 (mais pas 16); ainsi, si nous continuions à lancer des bombes toutes les 6328 étapes, nous finirions par revenir à notre point de départ, après avoir bombardé chaque huitième instruction dans le noyau (et avec les bombes supplémentaires compensées par 3163 = 6328/2 ≡ 4 (mod 8) , nous aurions touché une instruction sur quatre ).

    Mais nous avons commencé notre bombardement à 6328 instructions avant le JMZ, et avons reculé de -6328 à chaque itération, nous allons donc bombarder l'emplacement 6328 pas après la JMZseule itération avant de toucher le JMZlui - même. Donc, lorsque le JMZdétecte une bombe à 6328 instructions après cela, c'est un signe que nous avons couvert autant de noyau que possible sans nous toucher et que nous devrions passer à une stratégie de sauvegarde avant de nous tuer.

  5. Quant à la stratégie de sauvegarde, c'est juste un vieux MOV 0 1démon, car je ne pouvais pas penser à mieux pour l'instant. À mon avis, si nous avons bombardé chaque quatrième emplacement du noyau et que nous n'avons toujours pas gagné, nous combattons probablement quelque chose de très petit ou de très défensif, et nous pourrions aussi bien essayer de survivre et de nous contenter d'une égalité. Ce n'est pas grave, parce que de tels programmes petits ou défensifs ne sont généralement pas très bons pour tuer quoi que ce soit d'autre, et donc même si nous ne gagnons que quelques combats par hasard, nous devrons probablement continuer.


Ps. Au cas où quelqu'un d'autre le voudrait, voici ma fourchette légèrement améliorée du code de tournoi de PhiNotPi . Il est environ deux fois plus rapide, enregistre les anciens résultats de bataille afin que vous n'ayez pas besoin de les réexécuter et corrige ce que je pense être un bug mineur dans le calcul des résultats de bataille. Les modifications ont été fusionnées dans la version principale par PhiNotPi. Merci!

Ilmari Karonen
la source
1
Juste pour que vous le sachiez, le score teste TOUTES les combinaisons possibles d'emplacements de départ du programme et le programme marquant le plus de points gagne. Cela rend les liens impossibles ou complètement défavorables car tant qu'un programme ne se tue jamais et ne bombarde pas au moins une adresse une fois, il battra un lutin, ayant une victoire, et le reste sera lié.
mbomb007
9

Vue graphique

Cela peut être utilisé comme un outil de débogage. Il affiche le noyau et indique l'emplacement du lecteur. Pour l'utiliser, vous devez l'appeler à partir du code. J'ai également fourni un modifed Game.javaqui affiche automatiquement le GraphView.

PhiNotPi et Ilmari Karonen ont récemment changé de contrôleur. Ilmari Karonen a eu la gentillesse de fournir une GameView mise à jour à cet endroit .

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 modifié:

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.");
        }
    }*/
}
Le numéro un
la source
Il semble que vous ayez également modifié le lecteur. Je reçois./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 Désolé à ce sujet. Je devrais commenter la printCore()méthode.
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

Ma deuxième tentative CoreWar. Conçu pour battre Nain. Scanne par 3 pour les données, puis met une bombe tous les 2. Chaque étape se déroule en seulement 3 instructions, dans l'espoir que les bombes des nains le manquent.

NOUVEAU Turbo ++ : désormais amélioré. Il balaye en arrière jusqu'à ce qu'il trouve des données, puis s'y déplace, puis bombarde en arrière. L'espoir est que le mouvement soit tapote l'adversaire, soit soit vers un endroit déjà bombardé et donc sûr (ish).

... Et un montage pour le faire balayer plus clairsemement le fait battre tout le monde!

AShelly
la source
Semble battre bien plus que juste un nain. Toutes nos félicitations! Je pense que vous pourriez atteindre la troisième place si vous pouviez seulement battre Imp.
Ilmari Karonen
J'ai mis à jour celui-ci, mais c'est en fait une évolution assez importante par rapport à la précédente. Aurais-je dû faire une nouvelle entrée à la place?
AShelly
Je ne prétends pas parler au nom de PhiNotPi, mais je suppose que cela dépend de vous. Faire une mise à jour sur place signifie simplement retirer votre ancienne entrée. Quoi qu'il en soit, encore plus de félicitations pour avoir réussi à esquiver votre bombe jusqu'à la troisième place! Je pense que la vôtre est la seule entrée jusqu'ici à battre DwarvenEngineer par paire.
Ilmari Karonen
Bien joué ;). vous êtes le seul à battre maintenant!
Hit
8

Nain

Un programme commun et simple qui représente un nain jetant des pierres. Il place une DATinstruction toutes les quatre adresses.

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

EDIT: corrige l'adressage. Apparemment, les modes d'adressage sont différents de la spécification à laquelle l'OP est lié.

mbomb007
la source
Je pense que c'est "ajouter # 3 3" pour la première ligne, n'est-ce pas?
Hit
@Hit Nope. Je veux frapper toutes les 4 adresses. Je pourrais utiliser add 3 3, mais cela doublerait chaque boucle au lieu de l'ajouter, et cela ne serait pas utile. #4est un immédiat, il ajoute donc le nombre 4à la 2e valeur de l'adresse qui se trouve 3après l'adresse actuelle.
mbomb007
Je pense que vous interprétez mal le #mode d'adressage dans le défi. Comme indiqué dans la spécification, j'ai apporté une modification au #mode d'adressage.
PhiNotPi
Vous devriez aller comme: "ajouter 2 3 mov 2 @ 2 jmp -2 4 dat 0 4"
Hit
Avec le bon comportement, même les défaites ont évolué
Hit
7

Évolué

Honnêtement, je ne comprends pas comment cela fonctionne. Il semble construire son code source avant de faire quoi que ce soit. J'adorerais que quelqu'un me donne une explication de son fonctionnement.

Après l'avoir étudié, j'ai découvert qu'il s'agissait simplement d'un nain modifié avec un garde-lutin. Au lieu de bombarder les ennemis avec des DATinstructions, il mélange le code des ennemis. Il bombarde également tous les deux registres au lieu de tous les quatre registres. Avec suffisamment de temps, il se détruirait sans aucun doute.

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
Le numéro un
la source
1
Alors où l'avez-vous trouvé?
PyRulez
4
@PyRulez Il est généré par ordinateur via un algorithme génétique.
TheNumberOne
1
Il semble que l'exécution ne progresse pas réellement plus loin que la ligne # 6, car là, elle revient en arrière dans le programme. Je crois que la raison pour laquelle il réussit est qu'il y a plus de mouvements / boucles que ses concurrents.
PhiNotPi
6

FirstTimer

Si cela fonctionne, il devrait essayer de prendre position au début du noyau et de créer une défense

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
la source
Cela ne fonctionne pas tout à fait comme je pense que vous l'auriez supposé: #0fait référence au début de votre programme (c'est-à-dire le même que #main), pas au début du noyau (qui n'est pas vraiment un concept significatif de toute façon - le noyau est circulaire, votre code ne peut pas dire où il commence ou se termine). Ce qui se passe, c'est que votre première instruction ( main) se remplace par le MOV #data #100, après quoi votre code se transforme efficacement en un noyau avant clair de 0,25c (= une instruction par quatre cycles).
Ilmari Karonen
@IlmariKaronen Oh, merci pour l'explication. Je me suis trompé #0pour le début du core. Les 5 premières instructions sont alors totalement inutiles.
Thrax
6

CopyPasta

Jamais participé à un CoreWar, ce programme simple essaie juste de se copier-coller puis d'exécuter la copie. Il peut ne pas avoir le bon comportement, veuillez me dire si c'est le cas.

C'est trop pacifiste et ne peut pas gagner en fait.

MOV 6 0
MOV @-1 @-1
CMP @-2 3
JMP 4242 0
SUB -3 -4
JMP -4 0
DAT 0 4244
Frappé
la source
Cette modification actuelle ne sera probablement pas dans la prochaine mise à jour du classement (je gère le tournoi en ce moment). Cependant, l'ancienne version remportait les résultats préliminaires (petite taille de base).
PhiNotPi
D'accord :) L'ancienne version ne sort pas de la boucle1, ce n'est pas vraiment le comportement recherché, j'essaye de corriger ça.
Hit
La version actuelle semble cassée. Je ne sais pas encore pourquoi.
PhiNotPi
1
J'ai remanié les outils de débogage, alors maintenant je peux diagnostiquer le problème. Ce qui se passe, c'est que le programme ne copie que la seconde moitié de lui-même (à partir de JMP loop 0). Puis, quand il saute à l'endroit où le début de la copie devrait être, c'est juste de l'espace vide et il perd.
PhiNotPi
2
Veuillez ignorer mon commentaire précédent (maintenant supprimé); J'avais testé une version incorrecte de votre code (ironiquement, à cause d'une erreur de copier-coller), c'est pourquoi cela a si mal fonctionné pour moi.
Ilmari Karonen
6

Concierge

Il devrait vérifier si les adresses suivantes sont vides et sinon il les nettoie (donc, espérons-le, effacer le bot adverse).

Edit: Cette nouvelle version devrait être plus rapide (maintenant que j'ai bien compris la JMZcommande et la @référence).

JMZ 2 6
MOV 4 @-1
ADD 2 -2
JMP -3 0
DAT 0 1
DAT 0 0
plannapus
la source
Le concierge ne se suicide-t-il pas avec la première JMZ? Cela devrait être au moins JMZ 2 8. En passant, en utilisant @, vous pouvez réduire les deux à un seul. Quelque chose comme: "JMZ 2 @ 5 MOV 5 @ 4 ADD 2 3 JMP -3 0 DAT 0 1 DAT 0 2 DAT 0 0" (non testé)
Hit
@Hit Il ne saute pas, car l'adresse 2 à partir de là est ADD 3 -2, mais tu as raison qu'il devrait la changer, je pense.
mbomb007
Oui, j'ai mal lu l'instruction JMZet j'ai pensé JMZ A BvérifierA et sauter à B0 alors qu'apparemment c'est le contraire. Merci de l'avoir remarqué car je ne l'ai pas fait :)
plannapus
5

ScanBomber

Supprimer mes commentaires avant de compiler. Analyse pendant un certain temps, puis bombarde lorsqu'il trouve un programme. Mais il perdra probablement encore contre mon Nain.

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
la source
L'OP défini #complètement différemment de la spécification (lire le lien auquel il a lié), je n'ai pas encore fixé ce programme pour cela.
mbomb007
@TheBestOne Je pense que je l'ai corrigé. Est-ce que cela semble logique maintenant? Ou dois-je mettre #avant chaque référence zero? Ouais, je pense que je dois ...
mbomb007
Cela fonctionne bien maintenant. Il bat tous les robots sauf Dwarf et Imp.
TheNumberOne
@TheBestOne Dwarf est trop petit et ne serait détecté que dans 50% des emplacements de programme possibles. Il ne perd probablement que contre Imp parce qu'il se bombarde après avoir parcouru l'intégralité de la mémoire.
mbomb007
5

Han Shot First (v2)

J'ai pensé que la compétition pourrait utiliser un peu plus de diversité, alors voici ma deuxième entrée: un CMPscanner à un coup .

Il s'agit de la version 2 , avec des défenses anti-Imp améliorées - elle peut désormais battre Imp, ne serait-ce que par un seul point. Il perd toujours contre Dwarven Engineer, mais bat tout le reste jusqu'à présent, le plaçant actuellement en première place à égalité.

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

Il fonctionne en comparant les emplacements de noyau adjacents à 5 étapes d'intervalle, à des intervalles de 10 étapes, jusqu'à ce qu'il trouve une différence. Quand il le fait, il commence à lancer des bombes à des intervalles de 2 étapes jusqu'à ce qu'il tue son adversaire ou fasse une boucle tout autour du noyau pour se rejoindre.

Si l'analyse ne pas trouver quoi que ce soit d' autre, ce sera autour de la boucle et finira par trouver son propre code et l' attaquer. Ce serait suicidaire, mais pour la coïncidence heureuse que la première bombe atterrit carrément sur la aimligne, provoquant le lancement de la prochaine bombe 12 positions (plutôt que les 2 habituelles) dans le noyau, sautant commodément le code. (Cela se produit également avec une probabilité de 50% si le scan trouve quelque chose, mais ne parvient pas à tuer l'adversaire.) Étant donné que la taille du noyau est un multiple de deux, cela se produira également si le bombardement tourne en boucle, éliminant le besoin d'un stratégie de sauvegarde supplémentaire.

(Cette astuce d'auto-bombardement était à l'origine une pure coïncidence - j'avais prévu une façon complètement différente de passer du mode de numérisation au mode de bombardement si rien n'était trouvé, mais lorsque j'ai testé le code pour la première fois, les constantes avaient juste raison de le faire travailler de cette façon, et j'ai décidé de m'en tenir à cela.)

Ilmari Karonen
la source
4

Lutin

MOV 0 1

Parcourez simplement le programme.

Le numéro un
la source
4

Limace

     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

Parcourt l'espace mémoire vers l'arrière. Jette parfois une bombe loin.

AShelly
la source
3

lapin de Pâques

Il aime sauter en arrière :)

loop mov 0 -10
     add data loop
     cmp -7 data
     jmp -13 0
     jmp loop 0
data dat 1 1
Le numéro un
la source
3

Paranoïaque

Une sorte de copie de pâtes mais il vérifiera si le code a été modifié par bombardement. Si c'est le cas, copiez devant un nain et exécutez-le. Si je parviens à refaire la GameView, j'essaierai de changer certaines des 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
Frappé
la source
D'accord, en fait, cela fonctionne et j'étais juste en train de messign, merci pour la nouvelle manche;)
Hit