Core Wars Tutorial
Core Wars Tutorial
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.
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.)
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.
* 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.
Aparte de estas tres distinciones básicas podemos encontrar otras estrategias menos
comunes, o que se usan de apoyo para las tres principales:
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.
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.
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.
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.
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.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
...
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
...
...
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]
...
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: