0% encontró este documento útil (0 votos)
131 vistas8 páginas

Core Wars Tutorial

1. El documento describe la estructura y funcionamiento del core en CoreWar. El core es una memoria circular donde se almacenan las instrucciones de los programas de forma lineal. 2. Explica las estrategias más comunes utilizadas por los programas como replicación, bombardeo y escaneo. 3. Menciona algunos Mars y IDEs populares para CoreWar y describe brevemente su funcionamiento.

Cargado por

Marcos F. M.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como TXT, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
131 vistas8 páginas

Core Wars Tutorial

1. El documento describe la estructura y funcionamiento del core en CoreWar. El core es una memoria circular donde se almacenan las instrucciones de los programas de forma lineal. 2. Explica las estrategias más comunes utilizadas por los programas como replicación, bombardeo y escaneo. 3. Menciona algunos Mars y IDEs populares para CoreWar y describe brevemente su funcionamiento.

Cargado por

Marcos F. M.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como TXT, PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 8

2.

Estructura del Core Para entender la estructura del core conviene entender que
no es más que una "simulación" de un ordenador cualquiera. De hecho se llama core
por reminiscencia de un antiguo término para llamar a la memoria de un ordenador.

El core no es más que un array lineal de memoria (Normalmente con un tamaño de 8000
instrucciones aunque puede variar) que a su vez se componen de un opcode (La
instrucción en sí) y dos campos numéricos, cada uno con su propio modo de
direccionamiento.

Aunque antes he dicho que la memoria en el core es lineal, en realidad es circular.


Cuando acaba, vuelve al principio, por lo que en un core de tamaño 8000, la
instrucción 8001 es en realidad la instrucción 1.

Otra de las características más importantes del core es que los programas que se
ejecutan en él no saben la dirección absoluta en la que están, ni pueden acceder a
ninguna dirección de forma absoluta. Toda referencia a otras direcciones de memoria
se hace de forma relativa a la instrucción que se está ejecutando, siendo 1 la
siguiente instrucción, 0 la intrucción que se está ejecutando en este momento y -1
la instrucción anterior (Y por supuesto, 5 es la instrucción 5 pasos más adelante.)

3. Guía de introducción a Redcode ¡Sección movida a #2 !

4. Estrategias básicas En esta sección voy a tratar de dar una pequeña explicación
de algunas estrategias básicas que siguen los luchadores. Al ser sólo una
descripción breve del comportamiento no importa que no se sepa todavía usar
Redcode, es sólo para que se vea el potencial de CoreWar. La mayoría de programas
usan una o dos de estas estrategias en combinación, ¡O incluso más! Algunos incluso
se modifican a sí mismos para pasar de usar una estrategia a otra. Obviamente los
programas no están limitados a estas estrategias. Incluso de vez en cuando aparece
una estrategia nueva completamente diferente que revoluciona todo.

Las tres estrategias más comunes (Bomber,replicator y scanner) también se conocen


como "Piedra, papel y tijeras", porque cuando luchan entre sí normalmente actúan
como en el juego: Papel gana a piedra, tijeras a papel, piedra a tijeras.

* Un replicator (papel) hace diversas copias de sí mismo y las ejecuta en paralelo,


llenando el core con copias de su código. Son difíciles de matar, pero tampoco son
muy buenos matando, así que los empates son muy comunes, sobre todo con otros de su
misma especie. Un tipo especial de replicante, los Silk replicators usan la
ejecución paralela para copiarse enteros con tan sólo una instrucción, empezando a
ejecutarse al mismo tiempo.

* El bomber (piedra) copia a ciegas una "bomba" a intervalos regulares por el core,
esperando alcanzar al enemigo. La bomba suele ser una instrucción ilegal DAT,
aunque puede usar otras instrucciones o incluso bombas de varias instrucciones.
Suelen ser pequeños y rápidos, y además sus bombas son una conveniente distracción
para los scanners. Dwarf (Uno de los ejemplos de #2 ) es un bomber.

* Un scanner (tijeras) está diseñado para matar replicators. En lugar de atacar a


ciegas intenta encontrar a su enemigo antes de lanzar el ataque hacia su objetivo.
Esto lo hace especialmente bueno contra enemigos difíciles de matar, como los
replicators,pero también lo hace muy vulnerable ante distracciones. Suelen ser más
complejos, y por tanto de mayor tamaño y más frágiles que otros tipos de warriors.
Normalmente acaban el juego con bombas que ralentizan al enemigo, creando procesos
que sólo crean otros procesos de nuevo, ralentizando los procesos útiles. Una vez
que el enemigo es tan lento que deja de ser útil, se procede a bombardear con
instrucciones ilegales para matarlo.

Aparte de estas tres distinciones básicas podemos encontrar otras estrategias menos
comunes, o que se usan de apoyo para las tres principales:

* Un core clear es un programa que de forma secuencial sobreescribe cada


instrucción del core,incluso a veces a sí mismo. No es muy común encontrar core
clears puros, pero sí como estrategia para acabar el juego en muchos bombers y
scanners.

* El bomb-dodger (esquiva-bombas) es una estrategia especial contra bombers que


consiste en escanear el core hasta encontrar una bomba que haya puesto el programa
enemigo. Entonces el bomb-dodger se copia a sí mismo (O parte de su código) en esa
dirección, asumiendo que el bomber no va a volver a atacar la misma posición en
bastantes ciclos.

5. MARSes e IDEs En mi corta experiencia con CoreWar me he enontrado con un par de


MARSes interesantes. Para un montón de sistemas operativos, se encuentra pMARS, que
tiene incluso una versión que tira de SDL para mostrar el core gráficamente. Es el
más utilizado. Para Windows, CoreWin es una buena opción. Un buen MARS y bastante
fácil de utilizar.

En cuanto a IDEs no puedo recomendar mucho. Para Windows he probado nMars, que me
encanta para ver gráficamente mis programas, y A.R.E.S.,algo más complejo pero con
muchas más facilidades a la hora de debuggear. Para otros SOs, lo siento,no puedo
ayudar de momento :(

Algunos de estos MARSes utilizan una extensión del Redcode semi-estándar en la que
se interpretan ciertos comentarios, como por ejemplo ;name <Nombre del programa>
o ;author <Nombre del programador> en la cabecera del programa para sacar
información extra. ;redcode-94 al principio del programa le indica al MARS que debe
utilizar la extensión ICWS-94.

Un programa típico que recibiría un MARS puede parecerse a lo siguiente:


;redcode-94
;name Imp
;author A.K. Dewdney
;strategy The first imp.

imp: MOV imp, imp+1


END imp

Algunos torneos o MARSes interpretan la linea ;strategy para explicar la estrategia


que usa el programa.

6. KOTH (Rey de la pista) ¿Qué sería de CoreWars sin un sistema de torneos online?
Efectivamente, hay varios torneos KOTH (King of the Hill) gratuítos en internet que
operan por e-mail tras un pequeño registro. Incluso hay hills para principiantes
que borran los programas clasificados cada cierto tiempo de vida.

Algunos de los más populares son KOTH y KOTH@SAL,([email protected] es la


dirección a la que hay que enviar el código.)

Para elegir la hill en la que quieres que compita tu programa, la primera línea de
este no debe ser ;redcode sino ;redcode-<ID_DE_LA_HILL>. Por ejemplo, para entrar
en la Beginners' Hill de KOTH@SAL tiene que comenzar por ;redcode-94b. Lógicamente
también parsean los comentarios de nombre, autor, estrategia, etc. El resto de
comentarios que parsean se encuentran explicados en las webs de cada KOTH.

3. Guía de introducción a Redcode

3.1 Introducción básica Redcode es un lenguaje diseñado, al igual que el entorno


MARS, para ofrecer una plataforma abstracta lo suficientemente simple. Como
dialecto de ensamblador es parecido al de CISC, pero tiene ciertas peculiaridades.

En primer lugar, el conjunto de instrucciones es muy pequeño. Desde 1986, la ICWS


(International Core Wars Society) ha ido desarrollando diferentes documentos
oficiales donde explica los requisitos para programar un MARS, añadiendo opcnodes y
otras diferencias. En la versión ICWS-88 sólo había 10 instrucciones. En el
documento de diseño oficial más reciente, el ICWS-94,hay 18. Aparte de estos
documentos oficiales, hay ciertas extensiones personales del Redcode, aunque
durante casi todo este manual voy a tratar sólo el estándar ICWS-94.

Como expliqué antes, cada instrucción de Redcode se divide en un opcode y dos


campos númericos (Enteros, no hay otro tipo de datos en Redcode), cada uno con un
tipo de direccionamiento diferente. El opcode no adquiere un valor númerico y va
asociado a la instrucción junto a los dos campos, por lo que no puedes comparar una
instrucción con otra aislando su opcode. De esta forma "MOV 15, 0" nunca es lo
mismo que "MOV 14, 0", pero tampoco puedes saber que se trata del mismo opcode con
diferentes valores. Debes entender cada instrucción como un bloque de opcode+campos
del cuál sólo se pueden aislar los valores númericos de los campos o la instrucción
como conjunto.

La sintaxis básica para cada instrucción es:


<opcode> <modo de direccionamiento><campo-A>, <modo de direccionamiento><campo-B>

MOV 0, @-1 ;Aquí la @ indica direccionamiento indirecto en el campo B

Todas las instrucciones ocupan lo mismo en memoria (Una instrucción) y tardan lo


mismo en ejecutarse (Un ciclo.) La unidad básica no es el byte, sino la
instrucción.

Otra de las grandes diferencias es que no existen números negativos, aunque puedes
utilizarlos. Por ejemplo, puedes usar -1 para referirte a la instrucción anterior a
la que se está ejecutando en este momento, pero el MARS se ocupará de cambiar el
número a entero. De esta forma, como el core es circular, el MARS lo que hace es
acceder a la posición de memoria 7999 (Siendo la 8000 la dirección de memoria
actual al dar una vuelta completa.) Por esta misma razón, 0 es el menor número
posible, y -1 no evalúa como menor que cero, sino como 7999, obviamente mayor que
0. De la misma forma, toda la matemática de los programas se hace módulo el tamaño
del core, así que 8005 para el MARS es simplemente 5. Hay una correspondencia
unívoca entre números y direcciones de memoria.

Desde las primeras versiones de CoreWar se pensó en añadir multitarea, y


efectivamente así se hizo. Hay un opcode especial que divide un programa en dos
procesos: el proceso que ha ejecutado la instrucción, que continúa su camino, y uno
nuevo que comienza en la dirección especificada. A pesar de esto, la multitarea no
añade velocidad necesariamente. Imaginemos un programa A, que se subdivide en A y
A', y un programa B que no se divide. El orden de ejecución sería el siguiente: A B
A' B A B A' B... Es decir, cada proceso se reparte el tiempo de proceso que le
corresponde al programa padre. Por otro lado, un programa no muere hasta que no
mueren todos y cada uno de sus procesos. Además, si optimizas tu código lo
suficiente, puede ser muy beneficioso dividir tu programa para ahorrar en tiempo de
ejecución.

Por último, desde el ICWS-94, aparte del opcode y de los dos campos númericos, se
permite que cada instrucción tenga un modificador que define en qué campo actúa. De
esta forma podemos actuar en los campos números de otras instrucciones
individualmente.

3.2. Lista básica de opcodes ICWS-94


* DAT -- data Sólo vale para almacenar información en sus campos numéricos. Si se
ejecuta, el programa que lo hace se cierra, al ser una instrucción ilegal
* MOV -- move Copia datos de una dirección de memoria a otra
* ADD -- add Suma un número a otro
* SUB -- subtract Resta un número a otro
* MUL -- multiply Multiplicación
* DIV -- divide División. Si se divide por 0, el programa que lo ejecuta termina,
al ser una instrucción ilegal
* MOD -- modulus Módulo: divide un número por otro y devuelve el resto
* JMP -- jump Salta y continúa la ejecución en otra dirección de memoria
* JMZ -- jump if zero Comprueba un número y si es 0, funciona como JMP
* JMN -- jump if not zero Lo mismo pero salta si no es 0
* DJN -- decrement and jump if not zero Decrementa un número por uno y si ese
número no es 0, funciona como JMP
* SPL -- split Crea un nuevo proceso hijo en otra dirección
* CMP -- compare Ver SEQ
* SEQ -- skip if equal Compara dos números y si son iguales se salta la instrucción
siguiente
* SNE -- skip if not equal Compara dos números y si difieren se salta la
instrucción siguiente
* SLT -- skip if lower than Compara dos números y si el primero es menor que el
segundo pasa por alto la instrucción siguiente
* LDP -- load from p-space Carga un número del espacio privado de almacenamiento
(P-space)
* STP -- save to p-space Guarda un número al P-space
* NOP -- no operation No hace nada

Aparte de esta lista de opcodes, existen pseudo-ops, que no se compilan pero


indican cosas al MARS, como cuál es la primera instrucción o donde acaba el
programa. De estos hablaremos más adelante.

3.3. Mi primer contacto con un warrior: el Imp Cuando A.K. Dewdney publicó en
Scientific American el artículo sobre CoreWar, incluyó algún luchador de ejemplo.
En este caso reproduzco el que probablemente fuera el primer programa hecho en
Redcode.

MOV 0, 1

Sí, eso es todo. Parece simple. De hecho, lo es... ¿Cómo funciona? Echemos un
vistazo al código como quedaría compilado en el core aunque sin ejecutar:

...
DAT 0, 0 ;Normalmente la memoria se inicializa sola a DAT 0, 0
DAT 0, 0 ;aunque depende de la configuración del MARS.
MOV 0, 1 ;Aqui empieza nuestro Imp
DAT 0, 0
DAT 0, 0
...

MOV es un opcode que copia datos desde un sitio de la memoria (El campo-A) a otro
sitio de la memoria (El campo-B.) En este caso el campo-A vale 0 (Es decir, es la
instrucción que se está ejecutando) y el campo-B vale 1 (Es decir, la instrucción
inmediatamente siguiente a la que está siendo ejecutada.) así que copiaría la
instrucción actual justo a la siguiente posición, quedando:

...
DAT 0, 0
DAT 0, 0
MOV 0, 1 ;Aqui empezó el imp
[b]MOV 0, 1[/b] ;Ahora toca ejecutar esto
DAT 0, 0
...

¿Sospechas qué pasará después de un rato? En efecto, el imp se va abriendo paso


instrucción a instrucción por el core. Por sí mismo este programa no vale de nada,
sólo se abre paso por el core, pero no genera instrucciones DAT en el código
enemigo, así que sólo será capaz de ganar una partida con MUCHA suerte. Por otro
lado, es tan robusto que la única forma de morir es que el programa enemigo ponga
un DAT u otra instrucción ilegal justo en la instrucción que acabamos de modificar
y que va a ser ejecutada en el siguiente ciclo.

En Redcode moderno y con etiquetas bonitas, nuestro imp acaba así:

start MOV start, next


next END

3.4. Modos de direccionamiento Aquí viene una de las partes más complicadas pero
potentes a la vez del Redcode, los modos de direccionamiento de los campos
númericos. Se indican con un carácter, que se antepone al valor numérico de los
campos de cada instrucción, y cambian su comportamiento. Al igual que la lista de
opcodes ha aumentado con las diferentes revisiones del Redcode, los modos de
direccionamiento igual. En ICWS-88 había 4 y en el estándar del 94 tenemos 8:
* # -- inmediato
* $ -- directo (modo por defecto, el $ se puede omitir)
* * -- indirecto del campo A
* @ -- indirecto del campo B
* { -- indirecto del campo A con predecremento
* < -- indirecto del campo B con predecremento
* } -- indirecto del campo A con postincremento
* > -- indirecto del campo B con postincremento

Si estás familiarizado con algunos otros dialectos de ensamblador, puede ser que
reconozcas alguno de estos símbolos, pero si no, no desesperes que es más fácil de
lo que parece. Voy a comenzar explicando el marcador de direccionamiento inmediato,
el #, y lo voy a explicar ayudado por nuestro pequeño amigo, el Imp (MOV 0, 1) Si
recuerdas, este pequeño programa lo único que hace es copiar la instrucción actual
(0) a la instrucción en la posición siguiente (1). Si en lugar de MOV 0, 1 fuera,
MOV 5, 1 movería la instrucción que está 5 posiciones más alante a la instrucción
inmediatamente siguiente. ¿A que no te imaginas qué hace MOV #5 ,1? Veamos como
quedaría el código compilado en memoria pero sin ejecutar ningún ciclo.

...
DAT 0, 0
DAT 0, 0
MOV #5 , 1
DAT 0, 0
DAT 0, 0
...

¿Qué pasará según se ejecute el MOV?

...
DAT 0, 0
DAT 0, 0
MOV #5 , 1
DAT 0, [b]5[/b] ;Sorpresa!
DAT 0, 0
...

Creo que se entiende más o menos, ¿No? Lo que hace el operador # es que no se tome
el número como una dirección relativa de memoria, sino que tome como dirección de
memoria a sí mismo (Dirección 0) con el dato numérico 5. De esta forma el MOV copia
desde la dirección 0 el dato 5 hasta la dirección 1. Si fuera MOV #5 ,#1 según se
ejecutara la instrucción, ¿Qué pasaría? El MOV copiaría desde la dirección 0 el
dato 5, pero no hasta la dirección 1... sino hasta la dirección 0, quedando MOV
#5 ,#5 ¿Se entiende? A partir de ahora, estos programas que se automodifican a sí
mismos no van a ser la excepción, sino probablemente la regla... pero bueno, ya lo
explicaré más adelante, de momento, vamos a explicar el modo indirecto.

El modo indirecto es, por así decirlo, una forma de usar punteros. MOV 0, @2 no
copia la instrucción actual dos instrucciones por delante, sino que copia la
instrucción actual al número que apunta el campo B (@ es el indirecto del campo B)
de la instrucción que está dos posiciones por delante. ¿Muy complicado? Vamos a
verlo compilado en el core y con algo de "sabor" añadido:

...
DAT 0, 0
DAT 0, 3 ;DAT es muy útil para guardar variables
MOV 0, @-1 ;Imaginemos que se va a ejecutar esta instrucción
DAT 0, 0
JMP 15
...

Como vemos, el campo A del MOV (El campo origen del MOV) apunta a sí mismo, y el
campo B (Destino) apunta al DAT 0, 3. Como tiene @ como modo de direccionamiento,
lo que hace en realidad es tomar el dato del campo B de la instrucción a la que
apunta (En este caso el DAT 0, 3) ¿Significa eso que sería lo mismo que poner
directamente MOV 0, 3? No. En realidad no. Hay que tener en cuenta que todo el
direccionamiento en CoreWar es relativo. Para el DAT 0, 3 , 3 posiciones más alante
es el JMP 15 y con el @ lo que accedemos es a la dirección a la que apunta. Como el
JMP está en una posición relativa de 3 con respecto al DAT 0, 3,al ejecutarse el
MOV quedaría así:

...
DAT 0, 0
DAT 0, 3
MOV 0, @-1
DAT 0, 0
[b]MOV 0, @-1[/b]
...

Actúa como si en realidad hubiera puesto MOV 0, 2 ¿Por qué? El DAT 0, 3 está en una
posición relativa a la instrucción que se estaba ejecutando de -1, y -1 más 3 (Que
es el valor del campo B del DAT) es igual a 2.

Si queremos usar el campo-A como puntero, hay que usar * en lugar de @. El resto de
modos que quedan sin explicar funcionan igual que el indirecto, pero incrementando
o decrementando el campo al que se apunta. Por ejemplo:

...
DAT 0, 0
DAT 0, 3
MOV 0, >-1
DAT 0, 0
JMP 15
...
Según se ejecuta queda como:

...
DAT 0, 0
DAT 0, [b]4[/b]
MOV 0, >-1
DAT 0, 0
[b]MOV 0, >-1[/b]
...

3.5 Avanzando un poco: el Dwarf (Sí, a A.K.Dewdney le molaba la literatura


fantástica) Con los conocimientos que tenemos ahora mismo ya se puede entender otro
de los guerreros más básicos: el Dwarf. Fue el primer bomber que existió. Bastante
más ofensivo que el imp, pero también a la vez más vulnerable:

start ADD #4 ,bomb


MOV bomb, @bomb
JMP start
bomb DAT #0 ,#0

No os preocupéis si no entendéis nada, porque ya veréis que simulando paso a paso


el comportamiento del programa empieza a cobrar sentido. En primer lugar, ¿Cómo se
ve compilado en el core?

ADD #4 ,3;Comienza aqui


MOV 2, @2
JMP -2
DAT #0 , #0

Una vez se ejecuta la primera línea:

ADD #4 ,3
MOV 2, @2 ;Vamos por aqui
JMP -2
DAT #0 , #4

Ahora el MOV copia lo que hay 2 posiciones por delante (DAT #0 ,#4 ) a donde apunta
el campo B (Por la @) de la instrucción que hay 2 posiciones por delante (El #4 del
DAT) así que acaba quedando tal que así:

ADD [b]#4[/b],3
MOV 2, @2
JMP -2
DAT #0 ,[b]#4[/b]
...
...
...
[b]DAT #0 ,#4[/b]

Ahora se ejecuta el JMP que salta de nuevo al ADD, así que el DAT se convierte en
DAT #0 ,#8 y sigue el bucle infinitamente. Como el core es circular, lo que hace es
depositar bombas (Instrucciones ilegales, en este caso DAT) por todo el core, de 4
en 4 espacios. Es más rápido que el imp (1 posición por cada ciclo, mientras que el
Dwarf bombardea una distancia de 4 cada 3 ciclos.) Sin embargo, imp no se deja
ningún hueco de por medio, mientras que Dwarf sí. Esto suele pasar con todos los
programas, tienen sus ventajas y desventajas. Por cierto, ¿Os habéis planteado que
pasa cuando el Dwarf vuelve a llegar a sí mismo? Pues en un core de 8000, nada,
pero en otro core, el Dwarf se bombardea a sí mismo... ¿Por qué? 8000 es divisible
entre 4 por lo que en cada vuelta bombardea las mismas posiciones. La última
posición que bombardea es:

[b]DAT #0 ,#7996[/b] ;Esta posición acaba de ser bombardeada


ADD #4 , 3 MOV 2, @2 ;Acaba de hacerse el MOV
JMP -2 ;El programa va a ejecutar esta instrucción ahora
DAT #0 ,#7996

En el siguiente ciclo del bucle, se suman 4, quedando DAT #0 ,#8000 o lo que es lo


mismo (Al ser la matemática hecha módulo tamaño del core) DAT #0 ,#0 . ¡Vuelve al
estado inicial! Como podéis ver el Dwarf se esquiva a sí mismo.

También podría gustarte