Registrarse

Aprendiendo a programar en C!

Berserker1523

2-Intentando discernir qué es lo más importante...
C (orientado al ROM Hacking)​

Muy buenas a todos gente, esta es la traducción a la escuela de FBI de RH con C que podrán encontrar aquí: https://discord.gg/sEufJrN
Hago esto como complemento a mi guía de programación que encontrarán aquí: https://whackahack.com/foro/t-51817/fundamentos-programacion-orientada-objetos tened en cuenta que muchas cosas de las aquí mostradas son muy parecidas a las de la POO (Programación Orientada a Objetos), pero se utilizan conceptos del ROM Hacking que necesitarán saber previamente como el Scripting, tutorial de Scripting: https://whackahack.com/foro/t-42542/megatutorial-scripting-desde-cero-finalizado, también es útil aprender sobre el sistema de numeración hexadecimal, tutorial: https://whackahack.com/foro/t-34687/guia-todo-sobre-sistema-hexadecimal-orientado-al-rom-hacking.
Sin más dilación vamos allá:

Objetivos del curso:

Objetivo Principal:
1. Enseñar a programar.

Objetivos secundarios:
2. Enseñar a programar con C.
3. Enseñar habilidades de programación que se podrán aplicar tanto al ROM Hacking como a tareas de la vida real.



Una de las mayores concepciones erróneas que tiene la gente sin conocimientos de programación es que esta es mágica y que sólo alguien con una gran lógica puede realizarla, bueno, esto no es exactamente cierto. En informática existe un concepto llamado “refinamiento por etapas” (Step-wise Refinement). Este es un proceso que consiste en romper una tarea en muchas más pequeñas y fáciles. Ustedes ya realizan esto cuando Scriptean. Por ejemplo: El código de un Script se puede leer en pequeñas partes como “activar flags para completar el evento”, “añadir Pokémon”, “activar el special para asignarle un nombre”, “mostrarle al jugador un mensaje”, etc. Lo mismo sucede cuando programamos, dividimos una tarea o problema en partes y luego solucionamos cada una de esas partes para obtener una solución más grande. El resultado final puede ser complejo, pero los pasos que damos son muy simples y lógicos.

Teniendo todo eso en mente, necesito introducirles unos pocos conceptos. Vamos a aprender primero sobre funciones, y lo haremos echando un vistazo a algo que se les debería hacer familiar: Scripting

Código:
#dynamic 0x800000
Compare 0x8000 0x2
end
Este es un Script bastante simple. La pregunta es ¿Cómo creen que el “compare 0x8000 0x2” funciona? ¿Cómo hace la herramienta de Scripting para saber cómo hacer esta operación? ¿Cómo piensan que el compare 0x8000 0x2 puede funcionar y al mismo tiempo puedo hacer compare 0x4015 0x27 y también funciona?

He aquí la respuesta: La herramienta usa algo llamado “función”, que es básicamente algo que realiza una o varias series de operaciones. Cada comando de scripting que conocen tiene una funcionalidad asignado a algo llamado “función”.

Siempre que usamos una función, esperamos obtener la misma funcionalidad (valga la redundancia), incluso si cambiamos sus argumentos (parámetros). En el ejemplo, la función es llamada compare, el primer parámetro es una variable (0x8000) y el segundo parámetro es el valor a comparar (0x2, 2 en decimal). Pero eso no es todo, queremos que la función “compare” nos devuelva el resultado de la comparación, llamamos a este resultado el “valor de retorno”.

En el caso de “compare”, el valor de retorno puede ser cualquiera de estos:

0x00 Si el valor de la variable es menor (<) que el valor comparado.
0x01 Si el valor de la variable es igual (=) al del valor comparado.
0x2 Si el valor de la variable es mayor (>) que el valor comparado.
0x3 Si el valor de la variable es menor o igual (<=) que el valor comparado.
0x4 Si el valor de la variable es mayor o igual (>=) que el valor comparado.
0x5 Si el valor de la variable es diferente (no es igual, !=) que el valor comparado.

En cada caso, el valor de retorno es un valor de 8 bits sin signo. Denota 1 byte (1 byte puede almacenar un valor entre 0 y 255), y sin signo quiere decir que sólo puede ser positivo.

Recapitulando, las funciones realizan una operación usando los parámetros que se le han pasado y tienen un valor de retorno que es el resultado de la operación. (Con operación no me refiero solamente a las de matemáticas, por ejemplo: cambiar el nombre de un Pokémon es una operación). Sin embargo, no todas las funciones tienen parámetros o valor de retorno. Consideren el comando “faceplayer” en scripting, no se le debe pasar ningún parámetro y no devuelve (retorna) ningún valor. Entonces, las funciones no siempre deben tener tales cosas, sólo es necesario que tengan alguna funcionalidad para serlo.

Un ejemplo de una función que tiene parámetros pero no retorna ningún valor es el comando “setvar”. setvar 0x8000 0x1, por ejemplo, necesita que le digamos una variable y un valor a ser guardado en esa variable, pero no retorna nada, solamente realiza este trabajo y termina.

Antes de continuar, necesito explicar algo importante: C es denominado un lenguaje de programación “fuertemente tipado” (e interactúa muy estrechamente con el Hardware. Ser fuertemente tipado quiere decir que la programación con C espera que a cada entidad presente se le debe asignar una categorización denominada “tipo”.

Hay 2 principales categorías de tipos: los tipos primitivos y los tipos definidos por el programador. Los tipos primitivos son tipos definidos por el lenguaje de programación. Hay más categorías pero estas son las principales.

Algunos ejemplos de tipos primitivos (tipos definidos por el lenguaje) en C incluyen:

char: valor de 1 byte (8 bits).
short: valor de 2 bytes (16 bits).
int: valor de 4 bytes (32 bits).

De nuevo, existen muchos más en C, pero estos son los más básicos.
En cuando programamos con C para ROM Hacking, usamos algunos tipos definidos (por el usuario / programador). Algunos de los más importantes son:

u8 un valor de 8 bits (1 byte) sin signo. Lo mismo que un char sin signo.
u16 un valor de 16 bits (2 bytes) sin signo. Lo mismo que un short sin signo.
u32 un valor de 32 bits (4 bytes) sin signo. Lo mismo que un int sin signo.

Sin signo quiere decir que sólo puede ser un valor positivo, i.e. (i.e. = es decir), 5 es un valor sin signo pero -27 es un número negativo y por lo tanto un valor con signo (es más que eso, pero por ahora esta explicación es suficiente).

El prefijo “u” denomina que es un valor sin signo (un-signed), del mismo modo el prefijo “s” de usa para denotar valores con signo (signed), ejemplo, s8, s16, s32. Recordad que estos son tipos definidos (por el usuario/ programador).

Mucho tiempo atrás, antes de que C fuera inventado la gente usaba los tipos en sistemas operativos como Linux. Esos viejos computadores requerían que programaras muy estrechamente (cercano) al Hardware (y en muchos casos, directamente con el hardware si no tenían un sistema operativo).
La gente necesitaba nombres para los tipos para que fueran más específicos, y lo más específico que se les ocurrió es “Cuántos bits tiene un valor”.
Nosotros como ROM Hackers usamos también esa notación. ¿Por qué adoptamos / la seguimos usando? Pues porque el chico que escribió lib.gba la usó y simplemente la usamos también.

Veamos ahora cómo luce una función en C: (en realidad aquí solo ponía “RIP” xD)

Código:
u16 suma (u8 a, u8 b)
{
   u16 suma;
   suma = a + b;
   return suma;
}
Esta es una función simple llamada “suma” y su nombre indica que suma dos números y retorna el resultado. Explicaré algunas cosas, línea por línea:

Código:
 u16 suma (u8 a, u8 b)
Esta parte es llamada el “encabezado de la función” (También “signatura”). Aquí se declara el tipo de retorno (u16), el nombre de la función (suma) y cuales sean los parámetros que tendrá la función (lo que está dentro de los paréntesis).

Esta función tiene 2 parámetros: u8 a que denota una variable “a” de tipo u8. Entonces, nos podemos referir al valor sin signo de 1 byte llamado “a”. u8 b es lo mismo, solo que en vez de llamarse “a” se llama “b”.

Lo siguiente a explicar es
Código:
u16 sum;
De esta forma es como declaramos una “variable local”. Estamos asignando un espacio en el stack o tal vez usando un registro (esto se refiere un poco a ASM) para guardar un valor de 16 bits sin signo. Nos podemos referir a ese valor usando el nombre “suma”.

La siguiente línea
Código:
suma = a + b;
parece bastante sencilla. Esencialmente, estamos tomando el primer parámetro “a” y lo sumamos con el segundo “b” mediante el operador “+”, luego, almacenamos el resultado de la adición en la variable “suma”.

Finalmente, la última línea
Código:
 return suma;
como se puede leer, retorna el calor que contiene la variable “suma”.

Ahora, ¿por qué la suma retorna un u16? Pues como a y b son ambos de 1 byte (u8, 8 bits) y el máximo valor de un byte es 255, si a y b fueran 255 cada uno, la suma no puede ser almacenada en 1 byte, necesitamos 2. Ejemplo: a = 255, b = 252, a +b = 510 > que 8 bits.

Todo este pequeño código que escribimos lo hicimos con la “sintaxis” del lenguaje C, todos los idioma la usan (El inglés tiene su gramática especial, por ejemplo), C también.

En C, todas las líneas de código deben terminar con un punto y coma (;). Todas las funciones deben escribirse de esta manera:

Código:
tipo_de_retorno nombre_de_la_funcion (parámetro1, parámetro2, parámetro3, …)
{
//Aquí la operación a realizar.
}
Ahora, será que podrán convertir esta función:

Código:
u16 suma (u8 a, u8 b)
{
   u16 suma;
   suma = a + b;
   return suma;
}
Para que trabaje con 3 valores con signo (sumar 3 parámetros con signo)? (Inténtenlo por ustedes mismos antes de ver la respuesta, si quieren, pueden mandarme su intento por mensaje de perfil o privado y yo les explicaré si hay errores).

Código:
s16 suma (s8 a, s8 b, s8 c )
{
   s16 suma;
   suma = a + b + c;
   return suma;
}
Se declara el tipo de retorno s16 (valor de 16 bits con signo), se declara el parámetro “c” de tipo s8. La suma cabe perfectamente en 16 bits ya que para hallar el máximo valor sin signo se halla (2^16) -1= 65535 (se le resta 1 porque se empieza a contar desde 0), y con signo es aproximadamente la mitad para negativos y positivos. Luego, a, b y c pueden ser máximo 255 ((2^8)-1, si fueran u8, siendo s8 el máximo es aproximadamente la mitad) y su suma daría 765.

Ahora, introduciremos los llamados a funciones! Tomen un vistazo al siguiente ejemplo de scripting:

Código:
#dynamic 0x740000
#org @start
lock
faceplayer
call @set_value
buffernumber 0x0 0x8000
msgbox @text 0x6
release
end

#org @set_value
setvar 0x8000 0x1
return

#org @text
= Var 08000 is 01 now! look\n[Buffer1]
Están familiarizados con el comando “call”, cierto? Esencialmente, su funcionamiento es llevarte a otro script y llevarte devuelta (retornarte) al script original. El otro script puede ser una función. Un ejemplo con la función suma:

Código:
 u16 suma (u8 a, u8 b)
{
    return a + b;
}


u16 multiplicacion_por_3 (u8 a)
{
    u16 resultado = suma(a, a);
    resultado = suma(resultado, a);
    return resultado;
}
Aquí “suma” y “multiplicación_por_3” son ambas funciones, recuerden que x*3 = x + x + x. Usamos la suma para implementar la multiplicación! La cosa importante para explicar aquí es:
Código:
u16 resultado = suma (a,a)
Tomen nota de cómo una función es llamada: nos referimos a la función por su nombre y proveemos todos los parámetros que la función requiere dentro de los paréntesis. “suma” requiere 2 “u18” para sumarlos, y eso fue lo que le pasamos.

Noten que declaramos la variable resultado y de una vez le asignamos un valor (el que retorna la función suma), esto es llamado la precedencia de operadores en C y sirve para tener más legibilidad y/o libertad al escribir. Por ejemplo:

a + b *c + d / f * g
Es igual a escribir:
a + ((b *c) + ((d / f) * g))

Debido a la regla de precedencia de operadores de las matemáticas, pero el usar “()” hace más legible nuestra operación.


Pienso que he cubierto suficientes conceptos básicos sobre las funciones y explicado los tipos. Pero antes de dar por terminada la lección tengo un problema de desafío para ustedes!

¿Pueden escribir una función que calcule el perímetro de un rectángulo solamente sumando números? La cabecera de la función (signatura) tiene que ser esta:

Código:
u16 perimetroRectangulo (u8 ancho, u8 alto)
Que refiere a que devuelve un valor de tipo u16 y requiere de dos u8 los cuales son el ancho y el alto del rectángulo. Pueden enviarme la solución por mensaje de perfil o privado, igualmente pondré la solución cuando postee la próxima lección. Así mismo pueden enviarme las dudas que tengan. Eso es todo por ahora, gracias por leer.

Lección 2: atando cabos sueltos
Lección 3: Aplicando lo aprendido.
Lección 4: Estructuras y repaso.
Lección 1-4: Repaso.
Lección 5: Ciclos y punteros vacíos (void pointers).
Lección 6: Instrucciones for y switch.
 
Última edición:

kakarotto

Leyenda de WaH
La programacion es un mundo fascinante pero es una arma de doble filo. Es muy importante aprender bien desde el principio y creo que tu próposito fue ese. Bien hecho
 
Arriba