Errores frecuentes
Los tropiezos más comunes al programar un bot, con su síntoma, su causa y cómo arreglarlos.
El ADN casi nunca se queja. El cargador no rechaza un bot por estar mal escrito: toda palabra que no entiende se convierte en el número 0, y ninguna operación puede romper la ejecución (dividir por cero da 0, sacar de una pila vacía da 0). La consecuencia es que un error rara vez se ve como un error: se ve como un bot que no hace nada, o que hace otra cosa.
Esta página junta los tropiezos más comunes. Para cada uno vas a encontrar el síntoma, la causa y un ejemplo mal y bien. Algunos los detecta el editor de ADN, que te avisa mientras escribís; la mayoría no.
| Síntoma | Mirá |
|---|---|
| El bot no se mueve ni hace nada | nombre mal escrito, start o stop, .up y *.up |
| Una acción se repite cada ciclo, o se olvida | sysvars de acción |
| Un store «a veces» no escribe | condiciones en la pila |
| Una condición nunca se cumple | pila vacía, .up y *.up |
| Números raros en la memoria | división por cero, fuera de rango |
| La población se mata entre sí | disparar a la especie |
Una sysvar mal escrita
Síntoma: el bot carga, pero la acción no ocurre nunca.
Causa: un nombre con punto que no es una sysvar ni una variable de def vale 0. Un store a la dirección 0 no hace nada (y no cuesta nada), así que 30 .upp store desaparece sin dejar rastro. Lo mismo pasa si te olvidás el punto: up sin punto no es un nombre, es una palabra que vale 0.
cond
start
30 .upp store
30 up store
stopEl editor marca las dos: «¿quisiste decir .up?» y «¿falta el punto?». Bien:
cond
start
30 .up store
stopCon una variable propia pasa algo parecido si la usás antes de su def: el cargador lee el archivo de arriba abajo y en ese momento el nombre todavía no existe. Poné los def al principio. Ojo también con las mayúsculas: las sysvars se reconocen sin importar mayúsculas y minúsculas, pero los nombres de def no.
cond
start
.contador inc
stop
def contador 50def contador 50
cond
start
.contador inc
stopEl primero no cuenta nada; el segundo suma 1 por ciclo en la dirección 50.
Olvidarse del start o del stop
Síntoma: un gen no se ejecuta, o se ejecuta solo cuando se cumple la condición de otro gen.
Causa: lo único que habilita a ejecutar es un start (o un else). Lo que va entre cond y start son las condiciones: ahí un store no escribe. Y lo que queda después de un stop y antes del próximo cond o start no se ejecuta nunca. Todo eso carga sin avisos. Las reglas completas están en Genes: cond, start, else y stop.
Este bot quiere avanzar, pero le falta el start, así que se queda quieto:
cond
30 .up store
stopEste otro quiere avanzar cuando tiene más de 5 ciclos y girar siempre. Como el segundo bloque quedó después del stop sin un start propio, no gira nunca:
cond
*.robage 5 >
start
30 .up store
stop
' girar siempre
40 .aimdx storeSi en cambio borrás el stop, el giro pasa a ser parte del primer gen y solo ocurre cuando .robage supera 5. Lo correcto es darle su propio gen. Un start sin cond delante se ejecuta siempre:
cond
*.robage 5 >
start
30 .up store
stop
' girar siempre
start
40 .aimdx store
stopEsperar que una sysvar de acción conserve su valor
Síntoma: querés que algo pase una sola vez y pasa todos los ciclos, o querés acumular un valor y no crece.
Causa: muchas sysvars son órdenes para el motor: .up, .aimdx, .shoot y compañía. El motor las aplica y las vuelve a 0 en el mismo ciclo, así que al ciclo siguiente leés 0. Otras, como .nrg o .aim, las reescribe el motor en cada ciclo con el valor real: lo que guardes ahí se pierde. Ninguna de las dos sirve como memoria. En El ciclo y el orden de las acciones está el orden en que pasa cada cosa.
Este bot quiere girar una vez, «mientras .aimdx esté en 0». Como el motor lo vuelve a 0 después de cada giro, gira 100 unidades en todos los ciclos:
cond
*.aimdx 0 =
start
100 .aimdx store
stopPara recordar algo usá la memoria libre (por ejemplo la dirección 50, ver Memoria libre y epigenética). Este gira una vez y anota que ya lo hizo:
cond
*50 0 =
start
100 .aimdx store
1 50 store
stop.up no es *.up
Síntoma: una condición que debería cumplirse nunca se cumple (o se cumple siempre).
Causa: .nrg es la dirección de la energía (310), no la energía. Para leer lo que hay en esa dirección hay que poner un asterisco: *.nrg. Este bot compara 310 con 1000, así que no avanza nunca, tenga la energía que tenga:
cond
.nrg 1000 >
start
30 .up store
stopBien:
cond
*.nrg 1000 >
start
30 .up store
stopLa regla es simple: con asterisco para leer (*.eye5, *.nrg), sin asterisco para decir dónde escribir (30 .up store). Más detalles en Números y direcciones y Escribir en la memoria.
La pila vacía
Síntoma: un store escribe 0, o una comparación da siempre lo mismo.
Causa: sacar un número de la pila vacía no es un error: da 0. Si te falta un operando, la operación sigue con un 0 en su lugar. Este bot quería avanzar, pero el store no tiene valor que escribir y escribe 0 en .up:
cond
start
.up store
stopEn las condiciones pasa lo mismo. Acá falta el número contra el que comparar, así que > compara 0 > edad, que es siempre falso:
cond
*.robage >
start
30 .up store
stopBien: *.robage 0 >. Dos rarezas más que conviene conocer: dup con la pila vacía apila dos ceros, y una pila booleana vacía cuenta como verdadera, por eso un cond start sin condiciones se ejecuta siempre.
Condiciones que se quedan en la pila booleana
Síntoma: dentro de un gen, un store que no tiene condición no se ejecuta.
Causa: una condición escrita dentro del cuerpo (después del start) deja su resultado en la pila booleana, y ese resultado gobierna todos los stores que siguen hasta el stop, no solo el primero. Nada lo saca de ahí salvo que vos lo hagas. Es la idea de Condiciones en línea.
Este bot quiere disparar cuando ve algo y avanzar siempre. Como la condición de .eye5 queda en la pila, sin nada a la vista tampoco avanza:
cond
start
*.eye5 0 >
-1 .shoot store
30 .up store
stopDos arreglos: sacar el resultado con dropbool después de usarlo, o poner primero lo incondicional.
cond
start
30 .up store
*.eye5 0 >
-1 .shoot store
stopEn la sección de condiciones (entre cond y start) pasa lo contrario: todo lo que queda en la pila se une con «y». Si querés «o», tenés que escribir or.
El else después del start
En el DarwinBots 2.48.32 original, un else que venía después de un start nunca ejecutaba su cuerpo, se cumpliera o no la condición. Los autores lo documentaban como «igual que start pero se activa si la condición es falsa», pero el programa no lo hacía. Este port lo corrige: el else corre cuando las condiciones del gen son falsas.
cond
*.robage 30000 >
start
30 .up store
else
30 .dn store
stopEste bot retrocede (la condición es falsa y corre el else); en el original se quedaba quieto. Si cargás un bot viejo del foro que use start … else, tené en cuenta que acá se va a comportar distinto que en su época. Más en Genes: cond, start, else y stop y Diferencias con el 2.48.32 original.
Divisiones por cero
Síntoma: un cálculo da 0 sin motivo aparente.
Causa: div, mod y divstore con divisor 0 no fallan: dan 0. Si el divisor sale de la memoria (y la memoria arranca en 0), el resultado es 0 hasta que alguien escriba ahí. Además div no trunca: redondea al entero más cercano y, en el empate, al par, así que 7 2 div da 4 y 5 2 div da 2.
Si un 0 en el divisor tiene otro significado para vos, preguntá antes:
cond
*51 0 !=
start
1000 *51 div 50 store
stopValores fuera de rango
Síntoma: el bot no carga, o un valor guardado aparece cambiado.
Causa: hay tres límites (todos en Números y direcciones):
- Un número escrito en el ADN tiene que estar entre −32768 y 32767. Si no, el bot entero no carga.
- Lo que un store escribe en la memoria se ajusta al rango ±32000 dando la vuelta: guardar 40000 deja 8000, e
incsobre 32000 deja 1. - Las direcciones dan la vuelta cada 1000:
30 1001 storeescribe en la dirección 1, que es.up, y el bot avanza.
cond
start
40000 50 store
stopEste no carga. Si necesitás el número grande, calculalo: 20000 20000 add 50 store carga, pero deja 8000 por la vuelta de ±32000. Además, cada sysvar tiene su propio rango útil: está en su ficha de la referencia.
ADN largo que gasta energía
Síntoma: el bot pierde energía aunque casi no se mueva.
Causa: según cómo esté configurado el mundo, el ADN cuesta. Cada palabra que se ejecuta tiene un costo según su tipo, cada store que escribe cobra el suyo, y puede haber además un costo por ciclo proporcional al largo del ADN y otro al copiarlo cuando el bot se reproduce. Los costos y cómo se configuran están en Ejecución y costos.
Lo que conviene saber al escribir:
- Las condiciones de un
condse evalúan todas, todos los ciclos, aunque la primera ya sea falsa: no hay atajo. - El cuerpo de un gen que no corre no cobra, pero los
cond,start,elseystopse cobran siempre. - Un store solo cobra si escribe: los que una condición en línea frena no cuestan.
- Los genes muertos (que nunca se cumplen) siguen pagando sus condiciones y el largo. Si no los usás, borralos.
.dnalente dice cuánto mide tu ADN.
Dispararle a tu propia especie
Síntoma: la población propia se achica sola, sobre todo si hay pocos vegetales.
Causa: el disparo no distingue especies. Un bot nunca se pega con su propio disparo, pero sí a cualquier otro que se cruce, incluidos sus hermanos (un recién nacido solo está protegido de los disparos de su padre en sus primeros instantes). Un bot que dispara a todo lo que ve termina comiéndose a los suyos. Ver Disparos.
Mal: gira hasta ver algo y le dispara.
cond
*.eye5 0 =
start
50 .aimdx store
stop
cond
*.eye5 0 >
start
-1 .shoot store
stopBien: compara .refeye con .myeye, una firma que depende del ADN y que los bots de la misma especie comparten. Es el truco de Animal Minimalis, de Numsgil, uno de los bots del Bestiario:
cond
*.eye5 0 =
*.refeye *.myeye = or
start
50 .aimdx store
stop
cond
*.eye5 0 >
*.refeye *.myeye !=
start
-1 .shoot store
stopEn una prueba con cuatro bots iguales en un mundo chico, sin comida, la primera versión perdió dos en 60 ciclos; con la segunda los cuatro seguían con la energía intacta. El paso a paso está en Un bot que reconoce a su especie.