Manual CoreWar (Juego de programación)

elkaoD

ÍNDICE

  1. Introducción
  2. ¿Qué es CoreWars?
  3. Estructura del Core
  4. Guía de introducción a Redcode (Movido a #2)
  5. 3.1 Introducción básica
  6. 3.2 Lista básica de opcodes
  7. 3.3 Mi primer contacto con un warrior: el Imp
  8. 3.4 Modos de direccionamiento
  9. 3.5 Avanzando un poco: el Dwarf
  10. 3.6 Modificadores de instrucción
  11. Estrategias básicas
  12. MARSes e IDEs
  13. KOTH (Rey de la pista)
    Z. Enlaces[/b]

0. Introducción
Buenas a todos. Con esta guía sólo pretendo compartir en castellano y como mejor pueda los pocos conocimientos que he ido adquiriendo poco a poco sobre CoreWar, para ahorrar a la gente el trabajo que yo he hecho, y a ver si con suerte alguien se anima y podemos pasarlo bien :) Tampoco pretendo hacer la guía perfecta, ni una guía completa. Sólo quiero una pequeña introducción, para animar a la gente a que investigue por su cuenta.

Aviso, todavía sé muy poco sobre corewars, prácticamente acabo de empezar, pero me ha parecido un juego increíble, y espero que a vosotros también. Por cierto, los avisos de erratas o cosas que no se entiendan se agradecerán enormemente.

1. ¿Qué es CoreWars?
CoreWar o CoreWars es un divertido juego de programación inventado en 1984 por D.G. Jones y A.K. Dewdney, en el que dos o más luchadores programados por los jugadores pelean por el control de un ordenador virtual (Memory Array Redcode Simulator, MARS) ejecutando una instrucción tras otra alternativamente. El objetivo del juego es hacer que todos los procesos de los programas enemigos se cierren al ejecutar una instrucción ilegal, dejando a tu programa como único habitante del MARS. Los luchadores se programan en RedCode, un dialecto del lenguaje ensamblador con algunas peculiaridades que explicaré más adelante.


Partida de CoreWar a través del interfaz gráfico de pMARS

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)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)scanners. Dwarf (Uno de los ejemplos de #2) es un bomber.
* Un scanner (tijeras)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)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 :(


nMars debugeando

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.

Z. Enlaces
Casi todas las guías que puedes encontrar están en esta recopilación de tutoriales de corewar. También es muy recomendable leer esta estupenda guía para principantes, aunque está en inglés, de donde he sacado bastantes de los ejemplos de esta mini-guía.

http://corewar.co.uk y http://www.corewar.info/ también son buenas páginas de consulta.

1
elkaoD

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

3.6 Modificadores de instrucción

elkaoD

Iré actualizando #1 y #2 poco a poco. Espero que a alguien le pique el gusanillo.

bLaKnI

Ondia... Interesante de cojones!
Pero realmente se ve "ufff"! xDD

elkaoD

#4 pensé exactamente lo mismo, pero empezar a hacer programas competentes no es tan difícil. De hecho si sabes algún otro dialecto de asm en un par de días te sabes el juego de instrucciones completo y como usar todas las extensiones. También hay que decir que como más he aprendido es con la prática, tirando de nMars para ver gráficamente qué pasaba con mis programas.

Lo difícil es hacer programas competentes que ganen a muchos otros programas xD Aunque cuando termine un par de cosillas que me quedan de la guía me pongo a expicar estrategias básicas y algunas técnicas que revolucionaron corewar. De hecho, si os animáis a jugar estaría de puta madre porque empezamos todos desde cero más o menos, así que sería todo más competitivo que intentar entrar en el KOTH ahora, que los novatos no tenemos ni una mínima oportunidad xD

B

Parece bastante complicado, pero también tiene pinta de ser de estas cosas que, si te metes, no paras. Desgraciadamente yo no tengo tiempo para estas cosas :(

YiTaN

Joder, tiene una pinta wapísima xDD La putada es que no he hecho mucho de programación en ensamblador, y me costaría la de Dios empezar :(

Pero bueno, a ver si cuando tenga tiempo me pongo a mirar ejemplos y tal, y como tú dices, probando gráficamente pueden empezar a sacarse cosas.

Pinta muy, muy interesante, gracias por el curro tío ;)

NeB1

lo que no entiendo es "que hacer para ganar" hacer programas en ensamblador vale, pero...wat?

no sé, que tienes que obligar a que el otro escriba fuera de memoria o haga un overflow?

elkaoD

#8 ahora lo explicaré un poco mejor en la guía, pero en resumen, tienes que obligar al otro programa a que cierre todos sus procesos al ejecutar una instrucción ilegal. Que yo sepa, instrucciones ilegales están DAT (Que sólo sirve para guardar datos) y una división entre cero.

BLZKZ

#1 sabiendo programar para maquina rudimentaria, costaria mucho hacer algo decente? xD

Edit: por lo que veo es bastante parecido, y ademas me parece divertido programar en ensamblador. Si en los proximos dias me aburro ya se lo que hare xD

Kenny

Asi a priori lo veo como al que puede ser interesante pero muuuuy complicado xD

En fin, estos dias le echare un ojo ahora que tengo tiempo libre de vacaciones.

Usuarios habituales