Muy buenas a todos los usuarios de esta hermosa comunidad.
Como muchos sabemos, tenemos por aquí una amplia gama de tutoriales sobre scripting, entre ellos han sido dos los destacados: el de Ciro y el de xabier2012.
En mi opinión y sin perjuicio de la calidad que tengan, necesitamos y merecemos una guía más completa y detallada que no sea simplemente una lista de comandos, sino una explicación tanto práctica como teórica que nos permita conocer realmente el sistema.
Partiendo con este objetivo, empezaré este humilde tutorial en el que trataré de transmitir todos los conocimientos que tengo sobre scripting.
Por otro lado, espero que ésta sea la primera guía definitiva del scripting en la que usaremos el editor de scripts de nuestro querido @cosarara97 ; cuyas ventajas no explicaré por ahora. Sólo pruébenlo y las verán por ustedes mismos.
El formato estará dividido en dos partes: una teórica sobre la lógica y una más práctica sobre el sistema de scripting en sí en el que aplicaremos lo aprendido.
Por supuesto, me llevará un tiempo terminarlo, pero iré actualizando a medida que termine la explicación sobre cada eje. Como pretendo explayar todo lo que sé, no tengo idea del orden en que lo haré por lo que puede ser modificado sobre la marcha y no pondré un índice hasta que esté terminado.
Herramientas necesarias
Parte I: Lógica
En el scripting, al igual que en cualquier tipo de programación, nos es necesario desarrollar un pensamiento lógico. Creo, por lo tanto, que la mejor manera de empezar es por allí mismo.
Parte II: Scripting
A medida que avancemos con los conceptos de lógica, también desarrollaremos la parte práctica: cómo escribir scripts e implementarlos en el juego.
No obstante, creo más que importante dar una repasada técnica sobre el sistema.
Como muchos sabemos, tenemos por aquí una amplia gama de tutoriales sobre scripting, entre ellos han sido dos los destacados: el de Ciro y el de xabier2012.
En mi opinión y sin perjuicio de la calidad que tengan, necesitamos y merecemos una guía más completa y detallada que no sea simplemente una lista de comandos, sino una explicación tanto práctica como teórica que nos permita conocer realmente el sistema.
Partiendo con este objetivo, empezaré este humilde tutorial en el que trataré de transmitir todos los conocimientos que tengo sobre scripting.
Por otro lado, espero que ésta sea la primera guía definitiva del scripting en la que usaremos el editor de scripts de nuestro querido @cosarara97 ; cuyas ventajas no explicaré por ahora. Sólo pruébenlo y las verán por ustedes mismos.
El formato estará dividido en dos partes: una teórica sobre la lógica y una más práctica sobre el sistema de scripting en sí en el que aplicaremos lo aprendido.
Por supuesto, me llevará un tiempo terminarlo, pero iré actualizando a medida que termine la explicación sobre cada eje. Como pretendo explayar todo lo que sé, no tengo idea del orden en que lo haré por lo que puede ser modificado sobre la marcha y no pondré un índice hasta que esté terminado.
Herramientas necesarias
- Red Alien: Descarga directa. Recomiendo, no obstante, pasarte por su blog porque la versión que tenga aquí puede quedar desactualizada.
- A-Map: 1.95 1.92
- La ROM base sobre la que quieran trabajar.
Parte I: Lógica
En el scripting, al igual que en cualquier tipo de programación, nos es necesario desarrollar un pensamiento lógico. Creo, por lo tanto, que la mejor manera de empezar es por allí mismo.
Definición y relación con el scripting
Su objeto de estudio son las proposiciones o, mejor dicho, los procesos por los que, a partir de ellas, llegamos al conocimiento.
En la segunda definición tenemos un sentido de uso más cotidiano, la lógica entendida como sensatez: sentido común. Notarán a medida que avancemos, que lo que explicaré no será más que una formalización que nos permita desarrollar un razonamiento capaz de asegurar la validez de las conclusiones halladas.
Retomando la idea, es posible que se hayan preguntado a esta altura: ¿qué es una proposición? Simple, una oración que puede ser caracterizada como verdadera o falsa.
Por ejemplo:
¿Entonces todas las oraciones son proposiciones? No, claro que no. Veamos unos ejemplos:
Ahora se preguntarán: ¿qué utilidad tiene la lógica en el scripting?
Nos servirá tanto para armar estructuras condicionales (compare, checkflag, if) como, en un sentido mucho más amplio, para saber cómo y cuándo usar los comandos según su función.
Podemos afirmar que la lógica es una ciencia pero no una ciencia fáctica sino una formal. Es decir, no se centra en los hechos como la biología, la economía o la química, sino en sistemas de ideas. Es decir, es completamente abstracta por sí misma pero tiene una función auxiliar al ser aplicada en todas las demás ciencias.DLE RAE dijo:
- Ciencia que expone las leyes, modos y formas de las proposiciones en relación con su verdad o falsedad.
- Modo de pensar y de actuar sensato, de sentido común.
Su objeto de estudio son las proposiciones o, mejor dicho, los procesos por los que, a partir de ellas, llegamos al conocimiento.
En la segunda definición tenemos un sentido de uso más cotidiano, la lógica entendida como sensatez: sentido común. Notarán a medida que avancemos, que lo que explicaré no será más que una formalización que nos permita desarrollar un razonamiento capaz de asegurar la validez de las conclusiones halladas.
Retomando la idea, es posible que se hayan preguntado a esta altura: ¿qué es una proposición? Simple, una oración que puede ser caracterizada como verdadera o falsa.
Por ejemplo:
- Está lloviendo.
- Mañana es martes.
- El sol dista, aproximadamente, 149,6 millones de kilómetros de la Tierra.
- Whack a Hack! es un foro de Saint Seiya.
- Acerquémonos a una ventana: ¿es verdad que llueve? Eso significa que podemos definir si es verdadera o es falsa.
- En este momento puntual es falsa, mañana es jueves. Sin embargo, sería verdadera si hoy fuera lunes.
- En este caso, es cierto. Sin embargo, no hace falta saberlo pues o la Tierra está efectivamente a esa distancia del sol (lo que la convierte en verdadera) o no lo está (haciéndola falsa).
- Esta oración, a menos que se decida hacer un cambio en la temática, es falsa. Sin embargo, exactamente igual que en el caso anterior, no nos importa saberlo para definir si es o no una proposición.
¿Entonces todas las oraciones son proposiciones? No, claro que no. Veamos unos ejemplos:
- ¿Qué día es hoy?
- ¡Cierra la puerta!
- No sé si vendrán mañana.
Ahora se preguntarán: ¿qué utilidad tiene la lógica en el scripting?
Nos servirá tanto para armar estructuras condicionales (compare, checkflag, if) como, en un sentido mucho más amplio, para saber cómo y cuándo usar los comandos según su función.
¿Qué es la lógica proposicional?
La última vez terminamos por definir y dar ejemplos de las proposiciones. Como bien lo indica su nombre, podemos entenderla como la rama de esta ciencia que se encarga de estudiarlas. Se preguntarán, llegado este punto, ¿por qué nos interesa a nosotros? Simple: nos permitirá analizar las relaciones entre distintas proposiciones y centrarnos más en el proceso lógico que en la veracidad o la estructura de éstas.
Por ejemplo, pensemos que queremos dejar entrar al jugador a una ruta recién cuando obtenga su primer pokémon. Entonces, vamos a plantear lo siguiente: "[PLAYER] tiene al menos un pokémon en su equipo". Sabemos que puede ser verdadera o falsa y es eso lo único que nos importa pues pretendemos que, de ser verdadera, pueda pasar y, en caso contrario no lo haga, nada más.
Operadores lógicos
Hace un momento habrán leído que la lógica proposicional nos ayuda a analizar las relaciones entre proposiciones. Con esto me refería a identificar la veracidad de esta relación en función de la veracidad de cada proposición.
Bueno, bueno, ¡ya! ¿Qué quieres decir con "relación"?
¡Buena pregunta!Apaga la luz y se escapa rápidamente tras un manto de humo...
Aquí es cuando entran en juego los operadores. Vamos a poder plantear distintas relaciones entre dos o más proposiciones.
Conjunción
La última vez terminamos por definir y dar ejemplos de las proposiciones. Como bien lo indica su nombre, podemos entenderla como la rama de esta ciencia que se encarga de estudiarlas. Se preguntarán, llegado este punto, ¿por qué nos interesa a nosotros? Simple: nos permitirá analizar las relaciones entre distintas proposiciones y centrarnos más en el proceso lógico que en la veracidad o la estructura de éstas.
Por ejemplo, pensemos que queremos dejar entrar al jugador a una ruta recién cuando obtenga su primer pokémon. Entonces, vamos a plantear lo siguiente: "[PLAYER] tiene al menos un pokémon en su equipo". Sabemos que puede ser verdadera o falsa y es eso lo único que nos importa pues pretendemos que, de ser verdadera, pueda pasar y, en caso contrario no lo haga, nada más.
Operadores lógicos
Hace un momento habrán leído que la lógica proposicional nos ayuda a analizar las relaciones entre proposiciones. Con esto me refería a identificar la veracidad de esta relación en función de la veracidad de cada proposición.
Bueno, bueno, ¡ya! ¿Qué quieres decir con "relación"?
¡Buena pregunta!
Aquí es cuando entran en juego los operadores. Vamos a poder plantear distintas relaciones entre dos o más proposiciones.
¿Qué relaciones u operaciones podemos plantear?Ejemplo útil dijo:Pensemos en matemáticas. Supongo que todos, o al menos la gran mayoría de los que estén leyendo esto, han aprendido a sumar y a restar; quizás incluso a multiplicar y dividir. ¿Recuerdan cómo se llama cada uno de esos procesos? ¡Eso es! ¡Operaciones!
Espera, espera, espera... ¿Me estás diciendo que vamos a "sumar" o "restar" proposiciones? ¡No! Para nada. Esas son operaciones matemáticas. La lógica tiene unas operaciones diferentes pero su función es similar. Si sirve de ejemplo, pensemos que son a las proposiciones lo que las operaciones matemáticas a los números.
Conjunción
- Explicación: Suele simbolizarse como "^" y equivale a decir "y", "también" o incluso, en ocasiones, "pero".
- Valor de verdad: Es verdadera únicamente cuando todas las proposiciones lo son y falsa en cualquier otra ocasión.
- Ejemplo: Hoy es jueves y llueve. Esta conjunción será verdadera solamente si la leen un día jueves y está lloviendo. Es decir, si ambas proposiciones que forman la conjunción lo son.
Disyunción inclusivaNota para programadores dijo:Es probable que la conozcan con el nombre de "AND".
- Explicación: Equivale a decir "o" y se simboliza, generalmente, con "v".
- Valor de verdad: Es verdadera siempre y cuando al menos una de las proposiciones lo sea. Eso quiere decir que, incluso si ambas lo son, se comprueba la disyunción inclusiva. Análogamente, es falsa si y sólo si todas las proposiciones lo son.
- Ejemplo: Un día tiene 24 horas o 2000 minutos. Esta frase es verdadera porque un día tiene 24 horas, aún sabiendo que no tiene 2000 minutos. ¿Por qué? Simple: es una disyunción inclusiva en la que una de las proposiciones es verdadera. Por lo tanto, no importa la otra.
Disyunción exclusivaNota para programadores dijo:¿Han oído hablar del "OR"?
- Explicación: Lo encontramos en frases como "o bien (...), o bien (...)", "o (...) o (...) pero no ambos" y expresiones similares. Se simboliza, generalmente, con "v" o "w".
- Valor de verdad: Como una disyunción inclusiva, es verdadera si alguna de las dos proposiciones lo es, pero se diferencia precisamente porque si ambas son verdaderas, la disyunción exclusiva no lo es. Entonces, es falsa cuando las dos proposiciones tienen el mismo valor de verdad (sean ambas falsas o ambas verdaderas).
- Ejemplo: Un día tiene o bien 24 horas o bien 1440 minutos pero no ambas. Planteada como disyunción exclusiva, es falsa. ¿Por qué? Porque el día tiene 24 horas que expresadas en minutos son 1440. Entonces ambas proposiciones son verdaderas por lo que esta operación es falsa.
CondicionalNota para programadores dijo:Quizás la han empleado en alguna ocasión con el nombre de "XOR".
- Explicación: Tal y como su nombre lo indica, es lo que necesitaremos para plantear estructuras condicionales (valga la redundancia). Lo vemos usualmente en oraciones como "si (...) entonces (...)" o "si (...), (...)". Está formado por dos partes esencialmente diferentes: antecedente y consecuente.
- Valor de verdad: Es lógicamente falso sólo cuando el antecedente es falso y el consecuente verdadero. En cualquier otro caso, el condicional es verdadero.
- Ejemplo: Si llueve, te mojas. Para que esta condición sea verdadera, debe cumplirse que siempre que llueva te mojes. Es decir, si el antecedente (llueve) es cierto, entonces el consecuente (te mojas) debe serlo sin excepción alguna.
NegaciónNota para programadores dijo:Supongo que han planteado estructuras condicionales en un sinfín de ocasiones con la palabra clave "IF".
- Explicación: Este operador niega una proposición. Suele representarse con los símbolos "-" o "~".
- Valor de verdad: Al negarla lo que hacemos es cambiar su valor de verdad. Si la proposición original era verdadera se vuelve falsa y viceversa.
- Ejemplo: Hoy no es jueves. Estamos negando la proposición "hoy es jueves". Por lo tanto, si fuera jueves la negación sería falsa y si no lo fuera, verdadera.
Nota para programadores dijo:Es el famosísimo operador "NOT".
Más ejemplos dijo:Está soleado y hace más de 30º grados. Conjunción
Está soleado pero hace menos de 30º grados. Conjunción
Si tiene sed, toma agua. Condicional
Llegarás tarde si no te apuras. Condicional
Hoy es miércoles o jueves. Disyunción exclusiva
Allí hay cemento o ladrillos. Disyunción inclusiva
No compré el sobre. Negación
Negación
Conjunción
Disyunción inclusiva
Disyunción exclusiva
Condicional
Conjunción
Disyunción inclusiva
Disyunción exclusiva
Condicional
¿Cómo representamos las proposiciones?
Recordemos, chicos, que el propósito de la lógica no es evaluar la veracidad de una proposición sino la validez de un razonamiento. Es decir, debemos asegurarnos que a través de nuestro pensamiento lleguemos a conclusiones "verdaderas". Por lo tanto, más que el contenido de las proposiciones nos importará evaluar la estructura de un razonamiento.
Notaremos entonces que tener que pensarlo con cada premisa escrita completa es una pérdida de tiempo y, además, nos limita. De hacerlo así estaríamos observando solamente un caso puntual y no la estructura en sí. Por eso, representaremos cada proposición con una letra. Además, las desvincularemos de todo operador lógico. Es decir, si hubiera una negación la proposición que representaríamos sería sin el "no".
Muchos habrán pensado: "¡Bien! Eso es fácil pero, ¿qué pasa con los operadores que nos dejamos en el camino? ¿No son importantes?" A ustedes les digo:¡ESPEREN, IMPACIENTES!
Cuando expliqué cada uno de los operadores dije cómo suelen representarse. En ese momento podrán haber pensado: "¿Y eso para qué sirve?" ¡Pues para esto que hacemos ahora!¿Ven que son impacientes?
Retomemos los ejemplos y esta vez no sólo haremos el diccionario sino que escribiremos la proposición en forma simbólica.
Recordemos, chicos, que el propósito de la lógica no es evaluar la veracidad de una proposición sino la validez de un razonamiento. Es decir, debemos asegurarnos que a través de nuestro pensamiento lleguemos a conclusiones "verdaderas". Por lo tanto, más que el contenido de las proposiciones nos importará evaluar la estructura de un razonamiento.
Notaremos entonces que tener que pensarlo con cada premisa escrita completa es una pérdida de tiempo y, además, nos limita. De hacerlo así estaríamos observando solamente un caso puntual y no la estructura en sí. Por eso, representaremos cada proposición con una letra. Además, las desvincularemos de todo operador lógico. Es decir, si hubiera una negación la proposición que representaríamos sería sin el "no".
¿Y qué pasa con los operadores?Ejemplos dijo:
- Si no te despiertas temprano, perderás el turno.
Diccionario
p = Despertarse temprano
q = Perder el turno- Viajaré en tren o en colectivo.
Diccionario
p = viajar en tren
q = viajar en colectivo- No iré mañana.
Diccionario
p = iré mañana
Muchos habrán pensado: "¡Bien! Eso es fácil pero, ¿qué pasa con los operadores que nos dejamos en el camino? ¿No son importantes?" A ustedes les digo:
Cuando expliqué cada uno de los operadores dije cómo suelen representarse. En ese momento podrán haber pensado: "¿Y eso para qué sirve?" ¡Pues para esto que hacemos ahora!
Retomemos los ejemplos y esta vez no sólo haremos el diccionario sino que escribiremos la proposición en forma simbólica.
Ejemplos dijo:
- Si no te despiertas temprano, perderás el turno.
Diccionario
p = Despertarse temprano
q = Perder el turno
~p -> q- Viajaré en tren o en colectivo.
Diccionario
p = viajar en tren
q = viajar en colectivo
p v q- No iré mañana.
Diccionario
p = iré mañana
~p- Compraré un auto o una camioneta pero no ambas.
Diccionario
p = Compraré un auto
q = Compraré una camioneta
p w q
¿Cuándo una estructura racional es lógicamente válida?
Podemos asegurar que lo es si partiendo de premisas verdaderas no llegamos nunca a una conclusión falsa. Entendemos que es una definición vaga y negativa; es decir, lo definimos por aquello que no es.
Un razonamiento inválido es aquel en el que partiendo de premisas verdaderas podemos llegar a conclusiones falsas. En oposición, uno válido es aquel en que eso no sucede NUNCA.
Ahora debemos asimilar un concepto complejo: no es cierto que, por contraposición, un razonamiento válido es aquel en que partiendo de premisas verdaderas llegamos a conclusiones verdaderas. ¿Por qué? Simple, en un razonamiento inválido bien puede ocurrir también que, siendo verdaderas las premisas, sea verdadera la conclusión.
En el primer caso, la conclusión es verdadera. ¿Alcanza eso para afirmar que la estructura es válida? Ya hemos dicho que no. Miremos el segundo caso: la conclusión es falsa.
Entonces podemos concluir que el razonamiento "Algunos A son B y todos los C son A; entonces todos los C son B" es inválido. Podemos dar con una conclusión cierta pero también podríamos llegar a una falsa.
Ahora la pregunta del millón... ¿Cómo haremos para saber si un razonamiento es válido o no? Usaremos un método práctico y sencillo: las tablas de verdad.
Lo que haremos será plantear las proposiciones en una tabla y combinar los valores de verdad posibles de todas las formas. Es decir, si tenemos dos proposiciones (p y q) formaremos las siguientes combinaciones de valores (donde V es verdadero y F, falso):
¡A ponerlo en práctica!
La mejor manera de entender esto, es ponerlo en práctica. Vamos a evaluar la siguiente deducción: "Miguel tiene fiebre si su temperatura corporal supera los 38ºC. Actualmente su temperatura es inferior a 38ºC, por lo tanto no tiene fiebre".
Primero que nada, vamos a pasarla al lenguaje simbólico:
Diccionario
p: Miguel tiene fiebre.
q: La temperatura corporal de Miguel supera los 38ºC.
[(q -> p) ^ ~q] -> ~p
Nota: Los operadores lógicos son binarios, es decir que unen las proposiciones de a dos. Por lo tanto, usamos los paréntesis y corchetes para estar completamente seguros de que no se malinterprete.
Recomiendo releer las explicaciones de cada operador y luego ver el apéndice donde se incluyen las tablas de verdad de cada uno para entender cómo se completa en el ejemplo siguiente.
Planteamos la tabla y completamos los posibles valores para p y q. Han de ser coherentes entre ellos, es decir, si en un renglón p es verdadera, ha de serlo todas las veces que aparezca en ese renglón.
Pero... A nosotros no nos ha quedado una tautología, ¿cierto? Porque hay un cuadro que tiene una F. ¡Muy bien! Esto se llama verdad contingente y quiere decir que el modo de razonar no es válido por sí mismo.
Un tercer caso podría ser que todas fueran "F"s. En ese caso se le llama contradicción y muestra que cualquier razonamiento que tenga esa forma, será inválido.
Veamos un segundo ejemplo: "Miguel tiene fiebre si su temperatura corporal supera los 38ºC. Actualmente no tiene fiebre, por lo tanto su temperatura corporal no supera los 38ºC".
Diccionario
p: Miguel tiene fiebre.
q: La temperatura corporal de Miguel supera los 38ºC.
[(q -> p) ^ ~p] -> ~q
Veamos la tabla:
Básicamente nos dice que partiendo de un condicional, la negación del consecuente equivale a la negación del antecedente.
Recordatorio: Antecedente es la parte del condicional que va antes de la flecha y consecuente la que va después.
Conclusión
Un razonamiento es válido independientemente de toda variable si y sólo si todos los cuadros de la tabla (en la columna final) resultan verdaderos.
Podemos asegurar que lo es si partiendo de premisas verdaderas no llegamos nunca a una conclusión falsa. Entendemos que es una definición vaga y negativa; es decir, lo definimos por aquello que no es.
Un razonamiento inválido es aquel en el que partiendo de premisas verdaderas podemos llegar a conclusiones falsas. En oposición, uno válido es aquel en que eso no sucede NUNCA.
Ahora debemos asimilar un concepto complejo: no es cierto que, por contraposición, un razonamiento válido es aquel en que partiendo de premisas verdaderas llegamos a conclusiones verdaderas. ¿Por qué? Simple, en un razonamiento inválido bien puede ocurrir también que, siendo verdaderas las premisas, sea verdadera la conclusión.
Aquí tenemos dos razonamientos que siguen exactamente la misma estructura y en ambas las premisas son verdaderas.Ejemplo dijo:
- Algunos animales son mamíferos.
Todos los perros son animales.
Todos los perros son mamíferos.
- Algunas palabras son verbos.
Todos los números son palabras.
Todos los números son verbos.
En el primer caso, la conclusión es verdadera. ¿Alcanza eso para afirmar que la estructura es válida? Ya hemos dicho que no. Miremos el segundo caso: la conclusión es falsa.
Entonces podemos concluir que el razonamiento "Algunos A son B y todos los C son A; entonces todos los C son B" es inválido. Podemos dar con una conclusión cierta pero también podríamos llegar a una falsa.
Ahora la pregunta del millón... ¿Cómo haremos para saber si un razonamiento es válido o no? Usaremos un método práctico y sencillo: las tablas de verdad.
Lo que haremos será plantear las proposiciones en una tabla y combinar los valores de verdad posibles de todas las formas. Es decir, si tenemos dos proposiciones (p y q) formaremos las siguientes combinaciones de valores (donde V es verdadero y F, falso):
- p = V; q = F
- p = V; q = V
- p = F; q = F
- p = F; q = V
¡A ponerlo en práctica!
La mejor manera de entender esto, es ponerlo en práctica. Vamos a evaluar la siguiente deducción: "Miguel tiene fiebre si su temperatura corporal supera los 38ºC. Actualmente su temperatura es inferior a 38ºC, por lo tanto no tiene fiebre".
Primero que nada, vamos a pasarla al lenguaje simbólico:
Diccionario
p: Miguel tiene fiebre.
q: La temperatura corporal de Miguel supera los 38ºC.
[(q -> p) ^ ~q] -> ~p
Nota: Los operadores lógicos son binarios, es decir que unen las proposiciones de a dos. Por lo tanto, usamos los paréntesis y corchetes para estar completamente seguros de que no se malinterprete.
Recomiendo releer las explicaciones de cada operador y luego ver el apéndice donde se incluyen las tablas de verdad de cada uno para entender cómo se completa en el ejemplo siguiente.
Planteamos la tabla y completamos los posibles valores para p y q. Han de ser coherentes entre ellos, es decir, si en un renglón p es verdadera, ha de serlo todas las veces que aparezca en ese renglón.
En el paso siguiente, vamos a completar los cuadros de negación. Recordemos que ha de ser contrario al valor que tenía originalmente la proposición. Habría de quedar algo así:
Ahora sigamos el orden: vamos a completar el condicional que está dentro del paréntesis:
Luego la primer conjunción (que esá entre corchetes). Recordemos que debemos comparar el cuadro del condicional con el de la negación:
Por último, habremos de comparar la columna que acabamos de completar con la última negación. Así estaremos operando con el segundo condicional:
¡Listo! Ahora, ¿cómo sabemos si la forma de razonar es válida o no mirando la tabla? Enfoquen la vista en la columna verde (que es el resultado final de todas las operaciones). Si todos los cuadros tienen una V, se dice que el razonamiento es una tautología y significa que es válido en todos los casos, sin importar cuales sean las proposiciones.Pero... A nosotros no nos ha quedado una tautología, ¿cierto? Porque hay un cuadro que tiene una F. ¡Muy bien! Esto se llama verdad contingente y quiere decir que el modo de razonar no es válido por sí mismo.
Un tercer caso podría ser que todas fueran "F"s. En ese caso se le llama contradicción y muestra que cualquier razonamiento que tenga esa forma, será inválido.
Veamos un segundo ejemplo: "Miguel tiene fiebre si su temperatura corporal supera los 38ºC. Actualmente no tiene fiebre, por lo tanto su temperatura corporal no supera los 38ºC".
Diccionario
p: Miguel tiene fiebre.
q: La temperatura corporal de Miguel supera los 38ºC.
[(q -> p) ^ ~p] -> ~q
Veamos la tabla:
"¡Es una tautología!" ¡Exacto! En realidad tiene una forma lógica preestablecida que se llama "Modus Tollendo Tollens" y es cierta siempre.Básicamente nos dice que partiendo de un condicional, la negación del consecuente equivale a la negación del antecedente.
Recordatorio: Antecedente es la parte del condicional que va antes de la flecha y consecuente la que va después.
Conclusión
Un razonamiento es válido independientemente de toda variable si y sólo si todos los cuadros de la tabla (en la columna final) resultan verdaderos.
Parte II: Scripting
A medida que avancemos con los conceptos de lógica, también desarrollaremos la parte práctica: cómo escribir scripts e implementarlos en el juego.
No obstante, creo más que importante dar una repasada técnica sobre el sistema.
Concepto y funcionamiento del sistema
Empecemos: ¿a qué llamamos scripting? Tú que has disfrutado por horas de los juegos de pokémon habrás salteado millones de textos. Cada uno de ellos forma parte de un script.
En un principio, podemos decir que gran parte de los eventos del juego se desarrollan mediante los scripts.
Cuando hablas con tu madre antes de irte; cada vez que vuelves y tus pokémons se curan con un descanso; cuando recibes tu primer pokémon; cuando entras a la casa del Profesor Abedul y su esposa te detiene en la puerta; cuando intentas salir de Pueblo Paleta y el Profesor Oak te detiene y te lleva a su laboratorio; el hombre que da consejos en la entrada de los gimnasios... Todo eso y mucho más son ejemplos prácticos de scripts a lo largo del juego.
Intentemos explicar cómo funciona:
El juego está programado para leer un valor que se le indique y tomarlo como un índice en base al cual llama a un algoritmo" predeterminado.
Cada uno de estos "subprogramas" se asemeja a lo que, en general, llamamos "función" en el ámbito de la programación. Puede o no tanto recibir parámetros que utiliza para cumplir con cometido como devolverlos.
Por supuesto, a esta altura no andamos creando ni editando estos eventos con un editor hexadecimal: han sido creadas herramientas y, con ellas, "lenguajes". Escribimos una serie de comandos (palabras) que la propia herramienta compila; es decir, los convierte a su equivalente numérico tras lo cual el script es insertado en la ROM.
Cada una de las palabras que reconoce nuestro editor de scripts se corresponde con un número en particular (el índice mencionado anteriormente).
Por ejemplo, el comando "end" que termina el script lo veremos en un editor hexadecimal como "02".
Empecemos: ¿a qué llamamos scripting? Tú que has disfrutado por horas de los juegos de pokémon habrás salteado millones de textos. Cada uno de ellos forma parte de un script.
En un principio, podemos decir que gran parte de los eventos del juego se desarrollan mediante los scripts.
Cuando hablas con tu madre antes de irte; cada vez que vuelves y tus pokémons se curan con un descanso; cuando recibes tu primer pokémon; cuando entras a la casa del Profesor Abedul y su esposa te detiene en la puerta; cuando intentas salir de Pueblo Paleta y el Profesor Oak te detiene y te lleva a su laboratorio; el hombre que da consejos en la entrada de los gimnasios... Todo eso y mucho más son ejemplos prácticos de scripts a lo largo del juego.
Intentemos explicar cómo funciona:
El juego está programado para leer un valor que se le indique y tomarlo como un índice en base al cual llama a un algoritmo" predeterminado.
Cada uno de estos "subprogramas" se asemeja a lo que, en general, llamamos "función" en el ámbito de la programación. Puede o no tanto recibir parámetros que utiliza para cumplir con cometido como devolverlos.
Por supuesto, a esta altura no andamos creando ni editando estos eventos con un editor hexadecimal: han sido creadas herramientas y, con ellas, "lenguajes". Escribimos una serie de comandos (palabras) que la propia herramienta compila; es decir, los convierte a su equivalente numérico tras lo cual el script es insertado en la ROM.
Cada una de las palabras que reconoce nuestro editor de scripts se corresponde con un número en particular (el índice mencionado anteriormente).
Por ejemplo, el comando "end" que termina el script lo veremos en un editor hexadecimal como "02".
Términos empleados en el scripting
Compilar
Convertir los comandos en su equivalente numérico. Por lo general, los editores de scripts pueden, también, insertarlo en la ROM.
Descompilar
Es el camino inverso. Consiste en tomar los números y convertirlos en las palabras correspondientes.
Offset
Como la ROM es un conjunto ordenado de información (bytes) y necesitamos saber dónde se encuentra cada uno, llamamos offset a la posición de un determinado byte. No es más que un número ordinal.
Por ejemplo, si tenemos el conjunto: {1; 2; 3; 4; 5; 6} y empezamos a numerar las posiciones desde el 0 (es decir, asignamos 0 al primer elemento, 1 al segundo, 2 al tercero y así sucesivamente) podemos decir que:
En la ROM pasa lo mismo, con la particularidad de que cada espacio es exactamente un byte y la cantidad de elementos contenidos es mucho mayor.
Offset estático
Al momento de hacer un script e insertarlo en la ROM tenemos dos opciones. La primera es decirle al programa que lo inserta el lugar exacto en que lo queremos.
Ese offset recibe el nombre de éstatico.
La segunda opción consiste en darle un nombre para identificarlo. El programa que lo compila buscará espacio libre y lo insertará allí; diciéndonos luego dónde está.
Debemos indicarle a partir de qué parte empezar a buscar ese espacio libre. A ésto lo llamamos offset dinámico.
Las etiquetas dinámicas pueden contener letras y números mas nunca espacios.
Existen dos tipos de etiquetas dinámicas, aquellas que escribimos con @ y aquellas que llevan : (o inline labels).
La principal diferencia es que en el primero, cada parte del script se compila donde haya lugar suficiente mientras que, al usar inline labels, se compila seguido de lo que venía antes.
Compilar
Convertir los comandos en su equivalente numérico. Por lo general, los editores de scripts pueden, también, insertarlo en la ROM.
Descompilar
Es el camino inverso. Consiste en tomar los números y convertirlos en las palabras correspondientes.
Offset
Como la ROM es un conjunto ordenado de información (bytes) y necesitamos saber dónde se encuentra cada uno, llamamos offset a la posición de un determinado byte. No es más que un número ordinal.
Por ejemplo, si tenemos el conjunto: {1; 2; 3; 4; 5; 6} y empezamos a numerar las posiciones desde el 0 (es decir, asignamos 0 al primer elemento, 1 al segundo, 2 al tercero y así sucesivamente) podemos decir que:
- En el offset 0 tenemos al número 1.
- La posición del número 4 dentro del conjunto es 3.
- El 6 ocupa el offset 5.
En la ROM pasa lo mismo, con la particularidad de que cada espacio es exactamente un byte y la cantidad de elementos contenidos es mucho mayor.
Offset estático
Al momento de hacer un script e insertarlo en la ROM tenemos dos opciones. La primera es decirle al programa que lo inserta el lugar exacto en que lo queremos.
Ese offset recibe el nombre de éstatico.
Offset dinámicoEjemplo dijo:Si quiero que mi script empiece en la posición 0x08800000, debería escribir:
#org 0x08800000
La segunda opción consiste en darle un nombre para identificarlo. El programa que lo compila buscará espacio libre y lo insertará allí; diciéndonos luego dónde está.
Debemos indicarle a partir de qué parte empezar a buscar ese espacio libre. A ésto lo llamamos offset dinámico.
El programa le asignará a @inicio el primer offset con suficiente espacio libre que encuentre, desde la posición 0x08800000 en adelante.Ejemplo dijo:#dynamic 0x08800000
#org @inicio
Las etiquetas dinámicas pueden contener letras y números mas nunca espacios.
Existen dos tipos de etiquetas dinámicas, aquellas que escribimos con @ y aquellas que llevan : (o inline labels).
La principal diferencia es que en el primero, cada parte del script se compila donde haya lugar suficiente mientras que, al usar inline labels, se compila seguido de lo que venía antes.
Ejemplos válidos dijo:#org @inicio
#org @start
#org @empiezaquiel1erscript
#org @1111
:start
:empiezaaquiel1erscript
:1111
Ejemplos inválidos dijo:#org @empieza aqui el 1er script
#org @a b
#org @super script
#org @192 21
#dynamic
Función: Indicarle al editor dónde comenzar a buscar el espacio libre suficiente para insertar el script en la ROM.
Parámetros: 1.
Función: Indicarle al editor dónde comenzar a buscar el espacio libre suficiente para insertar el script en la ROM.
Parámetros: 1.
- El offset en cuestión.
Ejemplo dijo:#dynamic 0x800000
#org
Función: Cada uno de éstos es un offset predeterminado. Es el comienzo del script y, además, cada subestructura de comandos o mensajes.
Parámetros: 1.
Función: Cada uno de éstos es un offset predeterminado. Es el comienzo del script y, además, cada subestructura de comandos o mensajes.
Parámetros: 1.
- Etiqueta dinámica (@etiqueta) o estática (0x800000)
Ejemplos dijo:#org @comienzo
#org 0x812000
lock
Función: Parar el movimiento del npc al que le hablas. Solamente se debe usar en un script de ese tipo.
Parámetros: No.
Función: Parar el movimiento del npc al que le hablas. Solamente se debe usar en un script de ese tipo.
Parámetros: No.
Ejemplo dijo:
loadpointer
Función: Cargar en la WRAM el offset que le indiquemos para que pueda ser, luego, utilizado. Por ejemplo, para mostrar un mensaje debemos primero cargar su posición y luego recién mostrarlo en pantalla.
Parámetros: 1.
Función: Cargar en la WRAM el offset que le indiquemos para que pueda ser, luego, utilizado. Por ejemplo, para mostrar un mensaje debemos primero cargar su posición y luego recién mostrarlo en pantalla.
Parámetros: 1.
- El offset a cargar. Tanto sea como etiqueta dinámica o estática.
Ejemplos dijo:loadpointer @texto
loadpointer 0x930000
callstd
Función: Llamar a una rutina preestablecida.
Parámetros: 1.
Función: Llamar a una rutina preestablecida.
Parámetros: 1.
- El índice de la rutina. Tanto en decimal como en hexadecimal.
Tipos de callstdEjemplo dijo:callstd 2
callstd 0x6
- 0: Mensaje de objeto recibido.
- 1: Mensaje de objeto encontrado (pokéballs).
- 2: Mensaje cuando le hablas a alguien que, además, lo fuerza a mirarte.
- 3: Carteles.
- 4: Abre la caja de mensajes y la mantiene así hasta que apretas A.
- 5: Muestra un mensaje en el que puedes decidir "SI" o "NO".
- 6: Similar al 2 pero sin que el mini mire en tu dirección.
- 10 (0xA): Llamada del PokéNav (Emerald).
release
Función: Liberar al npc permitiéndolo moverse nuevamente. Debe incluirse al final del script únicamente cuando hayas usado un lock al inicio.
Parámetros: No.
Función: Liberar al npc permitiéndolo moverse nuevamente. Debe incluirse al final del script únicamente cuando hayas usado un lock al inicio.
Parámetros: No.
Ejemplo dijo:release
Línea de texto
Función: No es un comando en realidad pero al ingresar texto, debemos comenzar la línea con un signo "=".
Parámetros: No es comando.
Función: No es un comando en realidad pero al ingresar texto, debemos comenzar la línea con un signo "=".
Parámetros: No es comando.
Ejemplos dijo:= ¡Hey! ¿Qué tal?
= ¡Hola Mundo!
= Pokémons rulz
if
Función: Sirve para las estructuras condicionales. Según se comprueba verdadera o falsa la condición, realizará una acción u otra.
Parámetros: 2
Función: Sirve para las estructuras condicionales. Según se comprueba verdadera o falsa la condición, realizará una acción u otra.
Parámetros: 2
- La condición: es un número entero entre 0 y 5, cada uno con un significado diferente.
- Otro comando: Puede ser un call o un goto. Lo que hacen es saltar a otra parte del script si se verifica la condición.
- 0: Significa: "El primer valor es menor al segundo".
- 1: Significa: "Ambos valores son iguales".
- 2: Significa: "El primer valor es mayor al segundo".
- 3: Significa: "El primer valor es menor o igual al segundo".
- 4: Significa: "El primer valor es mayor o igual al segundo".
- 5: Significa: "Los valores son distintos".
Ejemplo dijo:if 0x5 goto @esdistinto
if 1 goto @esigual
if 0x3 call @esmenoroigual
goto
Función: Salta a un offset determinado. Es decir, ejecuta lo que hay en otra parte del script. NO tiene retorno
Parámetros: 1.
Función: Salta a un offset determinado. Es decir, ejecuta lo que hay en otra parte del script. NO tiene retorno
Parámetros: 1.
- El offset al que debe ir. Tanto sea en forma de etiqueta estática o dinámica.
Ejemplo dijo:goto @aOtraCosaMariposa
goto 0x89000
if
Función: Igual que el anterior, plantear estructuras condicionales.
Parámetros: 2
Tipos de condiciones
Función: Igual que el anterior, plantear estructuras condicionales.
Parámetros: 2
- La condición.
- Los comandos que deben ejecutarse si se comprueba la condición.
Tipos de condiciones
- <: Menor
- ==: Igual
- >: Mayor
- <=: Menor o igual
- >=: Mayor o igual
- !=: Distinto
Ejemplo dijo:if (0x40FF != 1) {
release
end
}
if (0x4040 >= 2) {
end
}
if (0x4000 == 5) {
loadptr :mensaje
callstd 2
}
else
Función: Si la condición de un if (del spoiler anterior) no se cumple (es falsa), este comando ejecuta las instrucciones que desees.
Parámetros: 1
Función: Si la condición de un if (del spoiler anterior) no se cumple (es falsa), este comando ejecuta las instrucciones que desees.
Parámetros: 1
- Comandos a ejecutar.
Ejemplos dijo:if (0x40FF < 5) {
addvar 0x40FF 1
}
else {
release
end
}
if (0x4000 == 5) {
release
end
}
else {
loadptr @mensaje
callstd 6
}
addvar
Función: Suma un número al valor de una variable.
Parámetros: 2
Función: Suma un número al valor de una variable.
Parámetros: 2
- El número de variable a la que queremos sumar.
- El número que queremos sumarle.
Ejemplo dijo:addvar 0x40FF 5
addvar 0x4000 1
addvar 0x40F4 0xFF
Creando, compilando e insertando nuestro primer script
Todos aquellos que aprendimos a scriptear con el tutorial de Ciro recordaremos cómo la primera lección consistía en crear un script que no hiciera nada.
Más allá de lo anecdótico, no le veo utilidad alguna y, en todo caso, una vez que aprendan los conceptos teóricos y comprendan su funcionamiento, deberían ser totalmente capaces de hacerlo sin guía alguna si es que quieren.
Este tutorial, en cambio, comenzará de un modo mucho más trillado y tradicional; al igual que la mayoría de los cursos de programación, vamos a crear nuestro primer "¡Hola Mundo!".
¡Me encanta! Emm... ¿Y qué es eso?
Consiste en mostrar en pantalla el texto "¡Hola Mundo!". En nuestro caso, lo que haremos será que un npc nos diga eso al hablarle.
Entendiendo el editor
Lo primero que haremos será abrir Red Alien. Nos encontraremos con algo así:
#include "stdlib/std.rbh": Lo que hay enmarcado en azul es un conjunto de instrucciones. Lo que hacen es decirle al programa que cargue los archivos de la carpeta "stdlib", forma abreviada de "standard library" o, en español, "librería estándar". Cada uno de estos archivos contiene una lista de constantes, es decir, de palabras que podremos usar en lugar del valor numérico.
Una vez terminado el tutorial deberían ser capaces de entenderlo lo suficiente como para saber cuándo incluir cada archivo.
Por ejemplo, incluirlo nos permite escribir "EM_BADGE1" para referirnos a la primera medalla del pokémon Emerald en lugar de su equivalente numérico.
#dynamic 0x800000: Está explicado ya en el glosario; le indica al programa dónde comenzar a buscar espacio libre para insertar el script.
#org @main: Es un ejemplo de etiqueta dinámica; desde aquí empezarían a aparecer los comandos que conforman el script ingame.
Números: Es el número de línea contando de arriba a abajo y comenzando por el 1. Nos servirá si tenemos algún error que impida compilar.
El código
Bueno, empecemos con lo nuestro.
Lo primero será cargar la ROM, simplemente pulsamos "File" y luego "Load ROM".
Ahora debemos introducir el siguiente código:
¡Genial! Pero, ¿qué hemos hecho?
Llegó el momento de explicar cada uno de los comandos usados.
#dynamic 0x800000: Como dije ya dos veces, le indica desde dónde comenzar a buscar el espacio libre.
#org @main: Es nuestra etiqueta dinámica, todo lo que haya debajo de ella se insertará en una posición libre de la ROM.
lock: Habrán notado que, dentro del juego, algunos npcs suelen caminar, correr o girar en el lugar. Cuando les hablamos, no obstante, sería bueno que dejaran su movimiento para no darte la impresión de que hablas con el aire. Éste comando hace la magia; tras usarlo, el personaje al que le hablemos se quedará quieto en el lugar.
loadpointer @mensajehm: @mensajehm es el nombre que le dimos a nuestro texto; al usarlo estamos refiriéndonos al offset donde éste se insertará.
El comando loadpointer carga en la memoria WRAM el texto que le indiquemos para que pueda ser mostrado en pantalla luego. Entonces, ésta línea se encarga de cargar el mensaje: "¡Hola Mundo!".
callstd 2: es la forma abreviada de las palabras inglesas "call standard"; "llamar estándar". Lo que hace es ejecutar una función predefinida. En éste caso, es una de las que muestra el mensaje. Para más información sobre los tipos de mensaje, revisar la lista de comandos.
release: Cada vez que pongamos un lock para frenar el movimiento, tenemos que recordar usar el release al final para que vuelva a comenzar.
end: Es el comando que finaliza el script. Siempre debe estar presente.
#org @mensajehm: Es otra etiqueta dinámica; debajo de ella irá nuestro mensaje.
= ¡Hola Mundo!: Éste es el mensaje que saldrá en pantalla. Para escribirlo tenemos que comenzar la línea con un signo "=".
Compilando e insertando el script
Una vez que ya está terminado, hacemos click en "ROM" y luego en "Compile".
Si todo fue bien, nos saldrá un mensaje indicando que el script fue compilado e insertado correctamente.
Al darle a "Ok" se abrirá otro cuadro que nos dará el offset de cada uno de las etiquetas dinámicas.
Asignando el script a un npc
Habiendo tomado nota del offset que nos dio para el inicio del script (@main, en mi caso 0xC18FFC), abrimos A-Map, nos vamos al mapa que queremos y en la pestaña de eventos seleccionamos al overworld o, en su defecto, al cuadro verde con una P encima:
Veremos que en la barra de la derecha saldrá un campo que dice: "Script offset". Ahí escribimos el que nos dio RA. Si usamos A-Map 1.95 tendremos 8 dígitos en lugar de 6, simplemente dejamos los dos primeros como 0.
Guardamos la ROM (Archivo->Guardar) y voilà, sólo falta probarlo...
Todos aquellos que aprendimos a scriptear con el tutorial de Ciro recordaremos cómo la primera lección consistía en crear un script que no hiciera nada.
Más allá de lo anecdótico, no le veo utilidad alguna y, en todo caso, una vez que aprendan los conceptos teóricos y comprendan su funcionamiento, deberían ser totalmente capaces de hacerlo sin guía alguna si es que quieren.
Este tutorial, en cambio, comenzará de un modo mucho más trillado y tradicional; al igual que la mayoría de los cursos de programación, vamos a crear nuestro primer "¡Hola Mundo!".
¡Me encanta! Emm... ¿Y qué es eso?
Consiste en mostrar en pantalla el texto "¡Hola Mundo!". En nuestro caso, lo que haremos será que un npc nos diga eso al hablarle.
Entendiendo el editor
Lo primero que haremos será abrir Red Alien. Nos encontraremos con algo así:
#include "stdlib/std.rbh": Lo que hay enmarcado en azul es un conjunto de instrucciones. Lo que hacen es decirle al programa que cargue los archivos de la carpeta "stdlib", forma abreviada de "standard library" o, en español, "librería estándar". Cada uno de estos archivos contiene una lista de constantes, es decir, de palabras que podremos usar en lugar del valor numérico.
Una vez terminado el tutorial deberían ser capaces de entenderlo lo suficiente como para saber cuándo incluir cada archivo.
Por ejemplo, incluirlo nos permite escribir "EM_BADGE1" para referirnos a la primera medalla del pokémon Emerald en lugar de su equivalente numérico.
#dynamic 0x800000: Está explicado ya en el glosario; le indica al programa dónde comenzar a buscar espacio libre para insertar el script.
#org @main: Es un ejemplo de etiqueta dinámica; desde aquí empezarían a aparecer los comandos que conforman el script ingame.
Números: Es el número de línea contando de arriba a abajo y comenzando por el 1. Nos servirá si tenemos algún error que impida compilar.
El código
Bueno, empecemos con lo nuestro.
Lo primero será cargar la ROM, simplemente pulsamos "File" y luego "Load ROM".
Ahora debemos introducir el siguiente código:
Código:
#dynamic 0x800000
#org @main
lock
loadpointer @mensajehm
callstd 2
release
end
#org @mensajehm
= ¡Hola Mundo!
Debería quedarles algo así:
Las líneas en blanco pueden dejarlas o no, es una cuestión estética simplemente que en nada modifica al script.¡Genial! Pero, ¿qué hemos hecho?
Llegó el momento de explicar cada uno de los comandos usados.
#dynamic 0x800000: Como dije ya dos veces, le indica desde dónde comenzar a buscar el espacio libre.
#org @main: Es nuestra etiqueta dinámica, todo lo que haya debajo de ella se insertará en una posición libre de la ROM.
lock: Habrán notado que, dentro del juego, algunos npcs suelen caminar, correr o girar en el lugar. Cuando les hablamos, no obstante, sería bueno que dejaran su movimiento para no darte la impresión de que hablas con el aire. Éste comando hace la magia; tras usarlo, el personaje al que le hablemos se quedará quieto en el lugar.
loadpointer @mensajehm: @mensajehm es el nombre que le dimos a nuestro texto; al usarlo estamos refiriéndonos al offset donde éste se insertará.
El comando loadpointer carga en la memoria WRAM el texto que le indiquemos para que pueda ser mostrado en pantalla luego. Entonces, ésta línea se encarga de cargar el mensaje: "¡Hola Mundo!".
callstd 2: es la forma abreviada de las palabras inglesas "call standard"; "llamar estándar". Lo que hace es ejecutar una función predefinida. En éste caso, es una de las que muestra el mensaje. Para más información sobre los tipos de mensaje, revisar la lista de comandos.
release: Cada vez que pongamos un lock para frenar el movimiento, tenemos que recordar usar el release al final para que vuelva a comenzar.
end: Es el comando que finaliza el script. Siempre debe estar presente.
#org @mensajehm: Es otra etiqueta dinámica; debajo de ella irá nuestro mensaje.
= ¡Hola Mundo!: Éste es el mensaje que saldrá en pantalla. Para escribirlo tenemos que comenzar la línea con un signo "=".
Compilando e insertando el script
Una vez que ya está terminado, hacemos click en "ROM" y luego en "Compile".
Si todo fue bien, nos saldrá un mensaje indicando que el script fue compilado e insertado correctamente.
Al darle a "Ok" se abrirá otro cuadro que nos dará el offset de cada uno de las etiquetas dinámicas.
Asignando el script a un npc
Habiendo tomado nota del offset que nos dio para el inicio del script (@main, en mi caso 0xC18FFC), abrimos A-Map, nos vamos al mapa que queremos y en la pestaña de eventos seleccionamos al overworld o, en su defecto, al cuadro verde con una P encima:
Veremos que en la barra de la derecha saldrá un campo que dice: "Script offset". Ahí escribimos el que nos dio RA. Si usamos A-Map 1.95 tendremos 8 dígitos en lugar de 6, simplemente dejamos los dos primeros como 0.
Guardamos la ROM (Archivo->Guardar) y voilà, sólo falta probarlo...
¡El jugador puede tomar decisiones!
Con todo lo que hemos avanzado en la parte teórica de la lógica, dimos los primeros pasos para plantear estructuras condicionales. La forma más simple de hacerlo es con la famosa cajita que todos los que hayan jugado pokémon conocen; esa que nos permite tomar una decisión muy importante... ¡Decir que sí o que no!
Hoy vamos a preparar un script similar al anterior pero con una diferencia: plantearemos una estructura condicional en la que podremos elegir sí o no.
Paso a enunciar el programa que queremos lograr: "Hablamos con un NPC que nos de a elegir entre si queremos que nos salude o no. En caso de respuesta positiva, dirá "¡Hola! Un gusto saludarte."; si negamos, no dirá nada.
Ahora lo escribiré en lenguaje simbólico.
Diccionario
p: Quiero que me salude.
q: Me saluda.
Muy bien, la cosa es simple: si le decimos que sí, saluda. No hay ninguna otra condición y por tanto no aparecerán más operaciones lógicas.
Scripteando
Vamos a ir por partes porque aquí he hecho muchas cosas nuevas que, si no lo toman con calma y paciencia, no entenderán. Alternativamente, para aquellos que necesiten ir un poco más lento dejaré debajo un spoiler con el script escrito de una forma más directa.
¡Alto ahí! En el primero no dice "loadpointer" sino "loadptr", ¿por qué? Son exactamente lo mismo. De hecho, "loadptr" es un alias, otra forma de llamar al comando loadpointer. En resumen, si queremos ahorrarnos milésimas de segundos para escribirlo abreviado, podemos usar loadptr que es lo mismo.
¿Y por qué "preguntasaludo" tiene un ":" y no un "@"? Porque podemos. Ya explicaré la diferencia cuando hable de :siquiero y :noquiero. Digamos que su función es similar a la del @, indica un "offset dinámico".
¿Y por qué un callstd 5 en lugar del 2 que usamos antes? ¡Sencillo! La función predefinida en el standard 5 es, precisamente, la que abre la caja con las opciones "YES" y "NO". Para más información revisen el apéndice de comandos.
¿Y luego por qué un callstd 6? Porque ya usamos el faceplayer antes, entonces no necesitamos el standard 2 que viene con eso incluído sino el 6 (que no lo trae).
¿Y si quiero ponerle 2? Da igual, sólo están incluyendo una operación extra e innecesaria en el procesador. No pasará nada, funcionará.
En este ejemplo, le pusimos el valor "0" a la expresión "dijo_no". Entonces, cada vez que escribamos más abajo dijo_no, lo cambiará por un 0 al compilar.
Nota: El número puede estar en decimal o en hexadecimal, da igual.
LASTRESULT es una variable que almacena (como su nombre lo indica) el último resultado.
Entonces, comparamos el resultado del msgbox con el número 1. Recordemos que los "resultados" son siempre valores numéricos.
Como expliqué recién, ese dijo_no equivale a un 0. Eso quiere decir que es lo mismo escribir if 0x0 goto :noquiero.
"if" significa "si" y es lo que marca nuestra estructura condicional. Comprueba una condición.
En este caso lo que nos fijamos es si la variable LASTRESULT tiene un valor menor a 1. Si lo tiene, significa que el usuario respondió negativamente. En caso contrario, el valor será exactamente 1.
Ahora pasemos al goto. Del inglés "go to" significa "ir a". Al estar después de un "if", lo que hace es saltar al offset :noquiero si y sólo si la condición es veradera. En este caso, si el valor efectivamente es menor que 1.
Notemos que noquiero también es una inline label (que lleva :). Eso quiere decir que, si le dices que te salude, luego de hacerlo continuará ejecutando esta parte.
Entonces, usamos los mismos release y end tanto si saluda como si no. Es una forma efectiva de ahorrar espacio.
En cuanto al $$ del final, es para indicarle que allí termina el texto. De no ponerlo, podría ocurrir algún problema con el texto que intentaría seguir leyendo a continuación bytes que no corresponden.
Con todo lo que hemos avanzado en la parte teórica de la lógica, dimos los primeros pasos para plantear estructuras condicionales. La forma más simple de hacerlo es con la famosa cajita que todos los que hayan jugado pokémon conocen; esa que nos permite tomar una decisión muy importante... ¡Decir que sí o que no!
Hoy vamos a preparar un script similar al anterior pero con una diferencia: plantearemos una estructura condicional en la que podremos elegir sí o no.
Paso a enunciar el programa que queremos lograr: "Hablamos con un NPC que nos de a elegir entre si queremos que nos salude o no. En caso de respuesta positiva, dirá "¡Hola! Un gusto saludarte."; si negamos, no dirá nada.
Ahora lo escribiré en lenguaje simbólico.
Diccionario
p: Quiero que me salude.
q: Me saluda.
p -> q
Muy bien, la cosa es simple: si le decimos que sí, saluda. No hay ninguna otra condición y por tanto no aparecerán más operaciones lógicas.
Scripteando
Código:
#include "stdlib/std.rbh"
#define dijo_no 0
#dyn 0x800000
#org @inicio
lock
faceplayer
loadptr :preguntasaludo
callstd 5
compare LASTRESULT 1
if dijo_no goto :noquiero
:siquiero
loadpointer [/PLAIN][PLAIN]@[/PLAIN][PLAIN]Saludomensaje
callstd 6
:noquiero
release
end
:preguntasaludo
= ¿Quieres que te salude?$$
#org [/PLAIN][PLAIN]@[/PLAIN][PLAIN]Saludomensaje
= ¡Hola!
= Un gusto saludarte.$$
Vamos a ir por partes porque aquí he hecho muchas cosas nuevas que, si no lo toman con calma y paciencia, no entenderán. Alternativamente, para aquellos que necesiten ir un poco más lento dejaré debajo un spoiler con el script escrito de una forma más directa.
Hasta aquí (excluyendo el define que no he escrito) no deberían tener problemas en entenderlo. Ya lo expliqué en la parte anterior del tutorial.
#include "stdlib/std.rbh"
(...)
#dyn 0x800000
#org @inicio
lock
Bueno, este comando lo que hace es que el npc al que le hablas se voltee hacia tu lado porque... Bueno, cuando hablamos con alguien nos giramos para quedar de frente.faceplayer
Si entendieron el otro script, se habrán dado cuenta que aquí lo que hacemos es mostrar dos mensajes en pantalla (uno para preguntar y el otro es el saludo luego de haber dicho que sí).
loadptr :preguntasaludo
callstd 5
(...)
loadpointer @Saludomensaje
callstd 6
¡Alto ahí! En el primero no dice "loadpointer" sino "loadptr", ¿por qué? Son exactamente lo mismo. De hecho, "loadptr" es un alias, otra forma de llamar al comando loadpointer. En resumen, si queremos ahorrarnos milésimas de segundos para escribirlo abreviado, podemos usar loadptr que es lo mismo.
¿Y por qué "preguntasaludo" tiene un ":" y no un "@"? Porque podemos. Ya explicaré la diferencia cuando hable de :siquiero y :noquiero. Digamos que su función es similar a la del @, indica un "offset dinámico".
¿Y por qué un callstd 5 en lugar del 2 que usamos antes? ¡Sencillo! La función predefinida en el standard 5 es, precisamente, la que abre la caja con las opciones "YES" y "NO". Para más información revisen el apéndice de comandos.
¿Y luego por qué un callstd 6? Porque ya usamos el faceplayer antes, entonces no necesitamos el standard 2 que viene con eso incluído sino el 6 (que no lo trae).
¿Y si quiero ponerle 2? Da igual, sólo están incluyendo una operación extra e innecesaria en el procesador. No pasará nada, funcionará.
¡Llegamos! Esta es una parte muy interesante, ya veremos más adelante que nos ayuda mucho. Básicamente nos permite darle un valor a una palabra.#define dijo_no 0
En este ejemplo, le pusimos el valor "0" a la expresión "dijo_no". Entonces, cada vez que escribamos más abajo dijo_no, lo cambiará por un 0 al compilar.
Nota: El número puede estar en decimal o en hexadecimal, da igual.
El compare es un comando que explicaré en detalle cuando veamos las variables. Por ahora, digamos que compara dos valores.compare LASTRESULT 1
if dijo_no goto :noquiero
LASTRESULT es una variable que almacena (como su nombre lo indica) el último resultado.
Entonces, comparamos el resultado del msgbox con el número 1. Recordemos que los "resultados" son siempre valores numéricos.
Como expliqué recién, ese dijo_no equivale a un 0. Eso quiere decir que es lo mismo escribir if 0x0 goto :noquiero.
"if" significa "si" y es lo que marca nuestra estructura condicional. Comprueba una condición.
En este caso lo que nos fijamos es si la variable LASTRESULT tiene un valor menor a 1. Si lo tiene, significa que el usuario respondió negativamente. En caso contrario, el valor será exactamente 1.
Ahora pasemos al goto. Del inglés "go to" significa "ir a". Al estar después de un "if", lo que hace es saltar al offset :noquiero si y sólo si la condición es veradera. En este caso, si el valor efectivamente es menor que 1.
Bueno, esto me sirve para explicar la principal diferencia entre las etiquetas dinámicas con : y las que tienen @. Digamos que al usar : se compila en el lugar siguiente. Es decir, siempre seguirá ejecutándose allí. En cambio, al usar el @ lo compila donde haya lugar suficiente, no necesariamente seguido de lo anterior.:siquiero
(...)
Entonces debemos tener cuidado porque si hiciéramos esto:
En cambio, si lo pusiéramos así:
Recuerden, si usan @ de este modo, deben agregar un goto.
Funcionaría, porque siempre luego del lock se ejecutaría el loadpointer.#org @inicio
lock
:mensaje
loadpointer @texto
callstd 2
(etc)
En cambio, si lo pusiéramos así:
Podría suceder que funcione si el compilador encuentra espacio vació junto y quiere compilarlo allí. Pero es altamente probable que no lo haga. En ese caso, quedaríamos atrapados en un bucle sin fin pues el script no terminaría nunca y el juego se congelaría.#org @inicio
lock
#org @mensaje
loadpointer @texto
callstd 2
(etc)
Recuerden, si usan @ de este modo, deben agregar un goto.
Ejemplo dijo:#org @inicio
lock
goto @mensaje
#org @mensaje
loadpointer @texto
callstd 2
(etc)
Bueno, estos comandos ya están explicados. Libera al NPC y termina el script.:noquiero
release
end
Notemos que noquiero también es una inline label (que lleva :). Eso quiere decir que, si le dices que te salude, luego de hacerlo continuará ejecutando esta parte.
Entonces, usamos los mismos release y end tanto si saluda como si no. Es una forma efectiva de ahorrar espacio.
Bueno, para los textos también podemos usar los :.
:preguntasaludo
= ¿Quieres que te salude?$$
En cuanto al $$ del final, es para indicarle que allí termina el texto. De no ponerlo, podría ocurrir algún problema con el texto que intentaría seguir leyendo a continuación bytes que no corresponden.
#org [/noparse]@Saludomensaje
= ¡Hola!
= Un gusto saludarte.$$
[/QUOTE]
Otra cosa que no había mostrado hasta ahora es la capacidad de poder ecribir el mensaje en varias líneas de texto.
Para eso sólo debemos comenzar cada renglón con un "=".
Pero notemos una cosa: esto no significa que haga el salto de línea ingame. Para eso debemos aclararlo con el carácter "\n".
[QUOTE=Ejemplo]
= ¡Hola!\n
= Saludo segunda línea.
[/QUOTE]
Bueno, compilamos el script y lo asignamos a un mini tal como lo expliqué en la sección anterior y... voilà, sólo falta probarlo...
[CENTER][IMG]https://i.imgur.com/Ay6WuNu.png[/IMG][IMG]https://i.imgur.com/gOfq5dt.png[/IMG][/CENTER]
[SPOILER=Script simplificado]
[CODE][noparse]
#include "stdlib/std.rbh"
#dyn 0x800000
#org @inicio
lock
loadptr :preguntasaludo
callstd 5
compare LASTRESULT 1
if 0 goto :noquiero
loadpointer @Saludomensaje 'Esto se ejecuta si dices que sí
callstd 6
:noquiero
release
end
:preguntasaludo
= ¿Quieres que te salude?$$
#org @Saludomensaje
= ¡Hola! Un gusto saludarte.$$
[/CODE]
Aquí he dejado las inline labels (etiquetas dinámicas con [b]:[/b]) porque, aunque no lo crean, son más que útiles y un rasgo distintivo del RA. Esfuércense por comprenderlas. Si no pueden, pregunten, ¡no muerdo! [s]mientras explico[/s]
[/SPOILER]
[SPOILER=Variables]
[COLOR="DarkSlateBlue"][SIZE="3"][B][U]¡Se pueden almacenar datos dinámicamente![/U][/B][/SIZE][/COLOR]
Una vez llegado a este punto nos preguntaremos si la única manera de plantear una condición es dando a elegir al jugador entre sí y no. Después de todo, hay veces en que lo que deba ocurrir no depende de su decisión.
Necesitamos algo que nos permita administrar el avance del juego: marcar que algunos eventos ya han sucedido y otros aún no. Aquí es donde entran en juego las variables.
¿Y qué son las variables? Estructuras que nos permiten almacenar datos.
La mejor analogía que conozco sobre ellas la hizo Cheve al afirmar que una variable es como una alcancía en la que podemos guardar o quitar monedas. Esto es correcto precisamente porque nuestras variables sólo pueden contener números y, con más exactitud, números enteros positivos y el cero. Es decir, representaremos los datos en forma numérica.
[QUOTE=Nota para programadores]
Las variables que usaremos son [B]enteras[/B], ni float, ni string ni de ningún otro tipo.
[/QUOTE]
[QUOTE=Ejemplo explicativo]
Supongamos que queremos marcar el avance de la historia: iremos guardando en nuestra "alcancía" una "moneda" por cada evento que suceda. Entonces, pensemos en FR: bajamos del cuarto, hablamos con nuestra madre y ponemos una moneda en la alcancía. En el laboratorio nos dan nuestro primer pokémon, guardamos otra.
Y así sucesivamente.
¿Y cómo usamos eso? Pensemos: al querer salir del pueblo, si aún no nos han dado nuestro primer pokémon habrá una sola moneda en la alcancía y, en caso contrario, habrá dos o más.
Pensando en eso podemos hacer un script que nos impida el paso si tenemos menos de 2 monedas.[/QUOTE]
[B]"Entiendo pero... ¿No todo en el ROM son datos? ¿Qué diferencia a las variables de cualquier otra parte de la ROM o de la RAM?[/B]
[s]¡Cállate, listillo![/s] Vamor por partes: las variables se diferencian de la ROM porque son parte de la RAM. Eso quiere decir que podemos editar su valor ingame (cosa que no podemos hacer con los datos de la ROM).
¿Y qué las diferencia de cualquier otra parte de la RAM? No mucho. Solamente en que el sistema de scripting tiene comandos específicos para trabajar con ellas.
[COLOR="DarkSlateBlue"][SIZE="3"][B][U]Usando las variables: ejemplo práctico[/U][/B][/SIZE][/COLOR]
Habiendo entendido las bases, es hora de ponernos... [b]¡Manos a la obra![/b]
Planteemos este ejercicio: Hagamos un script en que un NPC nos salude pero sólo la primera vez que le hablamos. Teniendo en cuenta lo que expliqué recién, es muy sencillo. Sólo tenemos que contar las monedas que haya en nuestra alcancía (o dicho de otra forma, revisar el valor de nuestra variable). Si no hay monedas en la alcancía, nos dice hola; en caso contrario, nos dirá que ya nos saludó.
Tenemos dos formas de proceder:
[list=1][*]Comparamos que el valor de la variable sea 0 y, de serlo, nos saludará.
[*]Usamos como condición que el valor de la variable sea distinto a 0 y, en caso de que se cumpla, evitamos que nos salude.
[/list]
Haremos el script para ambos casos, la diferencia, de hecho, es ínfima.
El código es el siguiente:
[B][U]Comparar la variable a 0 y saludar[/U][/B]
[CODE]
#include "stdlib/std.rbh"
#dyn 0x800000
#org @inicio
lock
faceplayer
if (0x40FF == 0) {
loadptr @Saludo
callstd 6
addvar 0x40FF 1 'Luego de esto, la variable 0x40FF pasa a tener el valor 1
}
else {
loadptr @Yatesalude
callstd 6
}
release
end
#org @Saludo
= ¡Hola!
= Este es un saludo.$$
#org @Yatesalude
= ¿Qué me miras?\n
= Si ya te he saludado.$$
[/CODE]
Expliquemos un poco este código antes de proseguir. Ya sabemos qué hacen el [I]include[/I], el [I]dyn[/I], el [I]org[/I], el [I]lock[/I] y el [I]faceplayer[/I].
Lo que sigue es una estructura condicional. La crearemos a partir de la palabra clave [I]if[/I].
[QUOTE]
if (0x40FF == 0) {
loadptr [/noparse][PLAIN]@Saludo
callstd 6
addvar 0x40FF 1
release
end
}
[/QUOTE]
Encontraremos principalmente dos partes:
[list=1][*][B]if (0x40FF == 0)[/B]: Lo que hay entre paréntesis es la condición que queremos evaluar.
[*][B]{ comandos }[/B]: Todo lo que hay entre llaves se ejecutará si la condición anterior se cumple, es verdadera. En este caso, si la variable 0x40FF tiene un valor de 0.
[/list]
[QUOTE]
else {
loadptr @Yatesalude
callstd 6
release
end
}
[/QUOTE]
Todo este bloque de código lo pondremos solamente si queremos que se ejecute algo al no cumplirse la condición. En nuestro caso, queremos que nos diga un mensaje pero si no quisiéramos eso, podríamos ahorrarnos esto y escribir solamente el if.
[B]¿Cómo funciona esta parte?[/B] Muy sencillo: todo lo que haya entre las llaves ({}) se ejecutará si la condición del if es falsa, es decir, si no se cumple. En este caso en particular, si la variable 0x40FF tiene un valor distinto a 0.
Esto es una estructura condicional básica.
[QUOTE=Nota para programadores]
Sí, es algo que seguro conocen. Lo han visto en prácticamente todo lenguaje de programación.
[/QUOTE]
[B]Evitar saludar si el valor de la variable es distinto a 0 (mayor)[/B]
[CODE][noparse]
#include "stdlib/std.rbh"
#dyn 0x800000
#org @inicio
lock
faceplayer
if (0x40FF > 0) {
loadptr @Yatesalude
callstd 6
release
end
}
else {
loadptr @Saludo
callstd 6
addvar 0x40FF 1 'Luego de esto, la variable 0x40FF pasa a tener el valor 1
release
end
}
#org @Saludo
= ¡Hola!
= Este es un saludo.$$
#org @Yatesalude
= ¿Qué me miras?\n
= Si ya te he saludado.$$
[/CODE]
Como verán, solamente cambiamos la condición, el resto es el mismo código.
[B]¿Y por qué ha cambiado el [I]==[/I] por [I]>[/I]?[/B]
¡Sencillo! Antes queríamos ver si la variable 0x40FF tenía el valor [I]IGUAL[/I] a 0, entonces usamos el comparador [I]==[/I]; mientras que ahora lo que hacemos es ver si su valor es [I]MAYOR[/I] a 0, por eso usamos el símbolo [I]>[/I].
[B]Entiendo... Entonces, ¿cuántos símbolos distintos hay?[/B]
[QUOTE=Operadores comparativos]
[B]==[/B]: Compara si el valor de una variable es [B]IGUAL[/B] a un número.
[B]<[/B]: Compara si el valor de una variable es [B]MENOR[/B] a un número.
[B]>[/B]: Compara si el valor de una variable es [B]MAYOR[/B] a un número.
[B]<=[/B]: Compara si el valor de una variable es [B]MENOR O IGUAL[/B] a un número.
[B]>=[/B]: Compara si el valor de una variable es [B]MAYOR O IGUAL[/B] a un número.
[B]!=[/B]: Compara si el valor de una variable es [B]DISTINTO[/B] a un número.
[/QUOTE]
Llegado este punto y habiendo aclarado a grandes rasgos cómo funcionan las condiciones, quiero mencionar un comando nuevo que aún no he explicado:
[QUOTE]
addvar 0x40FF 1
[/QUOTE]
[B][U]addvar[/U][/B]
Como su nombre indica, lo que hace es sumar al valor de la variable el número que le indiquemos.
Este comando lleva dos parámetros que son los siguientes:
[list=1][*]Número de la variable.
[*]Valor a sumarle.
[/list]
[QUOTE=Nota para programadores]
La sintaxis equivalente en algún lenguaje de programación podría ser la siguiente:
[list][*]variable++
[*]variable += 1
[*]variable = variable + 1
[/list]
[/QUOTE]
[COLOR="DarkSlateBlue"][SIZE="3"][B][U]Resultado[/U][/B][/SIZE][/COLOR]
Una vez insertado en la ROM, lo probamos y obtendremos este resultado, al hablarle la primera vez (izquierda) y al hablarle todas las demás veces (derecha):
[IMG]https://i.imgur.com/wAwtHiS.png[/IMG][IMG]https://i.imgur.com/FQj1Xk1.png[/IMG]
[/SPOILER]
Última edición por un moderador: