y_iterate
Introducción
¿Han escuchado alguna vez de foreach? Espero que sí porque todo parte de ahí, Y_Less en su momento creó un include independiente llamado así. En pocas palabras, este include permite iterar arrays fácilmente, sin tener que recorrer celdas inválidas. Menciono que un iterador, en nuestro caso, es como una lista a la que le podemos aplicar foreach. Podemos crear nuestros propios iteradores para añadir los elementos que deseemos.
y_iterate es una especie de foreach, pero está mucho más integrado dentro de YSI y conecta con otros elementos como y_groups, y_commands, y_bit, etc. Iremos por partes porque este include es muy grande.
Por ejemplo, a día de hoy lo más normal es encontrarnos con algo como esto:
PHP Code:
for(new i = 0, t = GetPlayerPoolSize(); i <= t; i++)
{
if(!IsPlayerConnected(i)) continue;
// Nuestro código.
}
PHP Code:
foreach(new i : Player)
{
/*
Itera a través de la lista de jugadores reales válidos. No hace falta comprobar si están
conectados porque la lista que se itera contiene solamente a los jugadores conectados.
*/
}
Obsolescencia
Si van a utilizar el antiguo formato de foreach con y_iterate obtendrán un warning, el formato correcto es el que vimos en el ejemplo de la introducción. Lo siguiente todavía funciona pero lo ideal es actualizar.
PHP Code:
foreach(Player, i) /* Warning: using_deprecated_foreach_syntax */
El include trae ciertos iteradores ya definidos para simplificarnos un poco las cosas, podemos desactivarlos si lo deseamos y el include deja de rastrear dichos elementos. Primero veamos todos los iteradores que tenemos disponibles:
- Player
Este iterador nos sirve para iterar a través de los jugadores conectados.
PHP Code:
foreach(new i : Player)
{
SendClientMessage(i, 0xFF0000FF, "Bienvenido, estás conectado y eres un jugador real.");
}
- Character
Aquí iteraremos a través de los jugadores reales y los NPC conectados.
PHP Code:
foreach(new i : Character)
{
if(IsPlayerNPC(i)) SendClientMessage(i, 0xFFFFFFFF, "Eres un NPC y estás conectado.");
else SendClientMessage(i, 0xFFFFFFFF, "Eres un jugador real y estás conectado.");
}
- Bot
Con esto solamente iteraremos a través de los NPC conectados.
PHP Code:
foreach(new i : Bot)
{
SendClientMessage(i, 0xFFFFFFFF, "Eres un NPC y estás conectado.");
}
- Actor
Con esto podemos recorrer las IDs de todos los actors que se han creado en el servidor.
PHP Code:
foreach(new i : Actor)
{
printf("La ID del actor es: %d.", i);
}
/*
Pueden usar LocalActor en lugar de Actor para iterar solamente a través de los actors
que se han creado exclusivamente desde el mismo script en el que se ha incluido y_iterate.
*/
- Vehicle
Aquí iteramos a través de todos los vehículos que se han creado en el servidor.
PHP Code:
foreach(new i : Vehicle)
{
printf("La ID del vehículo es: %d.", i);
}
/*
Pueden usar LocalVehicle en lugar de Vehicle para iterar solamente a través de los vehículos
que se han creado exclusivamente desde el mismo script en el que se ha incluido y_iterate.
*/
- FOREACH_NO_PLAYERS - Desactiva todos los iteradores relacionados a los jugadores, NPC y actors.
- FOREACH_NO_BOTS - Desactiva los iteradores Character y Bot.
- FOREACH_NO_VEHICLES - Desactiva los iteradores Vehicle y LocalVehicle.
- FOREACH_NO_ACTORS - Desactiva los iteradores Actor y LocalActor.
- FOREACH_NO_LOCALS - Desactiva los iteradores LocalVehicle y LocalActor.
PHP Code:
#include <a_samp>
#define FOREACH_NO_BOTS
#include <YSI-Includes\YSI\y_iterate>
Todo lo anterior representa solamente una pequeña parte de y_iterate, todavía quedan muchas cosas por ver. Si crees que lo único que vas a utilizar son los iteradores predefinidos puedes ignorar todo lo que sigue. Bueno, podemos crear nuestros propios iteradores, podemos añadir los elementos que deseemos y recorrerlos de diversas formas.
- Iterador unidimensional
A continuación vamos a crear un iterador, como veréis vamos a especificar el número máximo de elementos que puede albergar, que pueda albergar 500 elementos no significa que se repita 500 veces. Un iterador se repite tantas veces como elementos contenga, es decir, los que nosotros añadamos.
PHP Code:
new Iterator:Ejemplo<10>; /* Este iterador puede albergar 10 elementos */
/* Añadimos elementos, atentos a los valores que añadimos */
Iter_Add(Ejemplo, 5);
Iter_Add(Ejemplo, 0);
Iter_Add(Ejemplo, 9);
/* Iteramos */
foreach(new x : Ejemplo)
{
printf("%d", x);
}
/*
Resultado:
0
5
9
*/
- Iterador multidimensional
Un iterador multidimensional es un iterador en el que existen múltiples listas sobre el mismo conjunto de elementos.
PHP Code:
/* Creamos cinco iteradores, cada uno puede albergar un máximo de 10 elementos (0 - 9) */
new Iterator:Ejemplo[5]<10>;
/* Este tipo de iterador debe inicializarse, por ejemplo, en OnGameModeInit */
Iter_Init(Ejemplo);
/* En algún lugar */
Iter_Add(Ejemplo[2], 9);
Iter_Add(Ejemplo[2], 2);
foreach(new x : Ejemplo[2])
{
printf("%d", x); /* Resultado: 2 9 */
}
Iter_Add(Ejemplo[0], 5);
Iter_Add(Ejemplo[0], 0);
foreach(new x : Ejemplo[0])
{
printf("%d", x); /* Resultado: 0 5 */
}
PHP Code:
new Iterator:MisCoches[MAX_PLAYERS]<MAX_VEHICLES>;
/* OnGameModeInit */
Iter_Init(MisCoches);
/* En algún sitio. */
Iter_Add(MisCoches[2], 50); // Añadimos el coche con la ID 50 al iterador del jugador con la ID 2.
Iter_Add(MisCoches[5], 50); // Añadimos el coche con la ID 50 al iterador del jugador con la ID 5.
/*
Lo anterior no cumple la condición "cada vehículo puede tener como máximo un propietario", además
tener 500 iteradores con 2000 slots cada uno no es nada óptimo (MAX_PLAYERS * MAX_VEHICLES).
*/
PHP Code:
new Iterator:MisCoches<MAX_PLAYERS, MAX_VEHICLES>
/*
Lo anterior reduce el número de slots, dado que equivale a MAX_PLAYERS + MAX_VEHICLES.
Por otro lado, un vehículo solo puede aparecer en un iterador, no se puede repetir una misma
ID en un iterador diferente. Por ende, esto cumple la condición impuesta, un vehículo solo puede
ser comprado por un jugador. No hace falta inicializar este tipo de iterador.
*/
Iter_Add(MisCoches<2>, 50); // Asignamos el vehículo con la ID 50 al jugador con la ID 2.
Iter_Add(MisCoches<5>, 50); // Esto fallaría, dado que la ID 50 ya está en uso.
/* Iteramos a través de los vehículos adquiridos por un jugador. */
foreach(new vid : MisCoches<playerid>)
{
printf("vehicleid: %d", vid);
}
PHP Code:
new Iterator:Coches<MAX_PLAYERS, MAX_VEHICLES>;
/* Comprobar si un valor está en un iterador en concreto */
if(Iter_Contains(Coches<playerid>, 10))
{
print("El coche con la ID 10 lo tiene playerid.");
}
/* Comprobar si un valor está en alguno de los iteradores */
if(Iter_Contains(Coches<>, 10))
{
print("El coche con la ID 10 ha sido comprado.");
}
- Iteradores YSI
Si están utilizando otros elementos de YSI, seguramente esto os vendrá bien en alguna ocasión.
PHP Code:
/* y_bit */
new BitArray:Ejemplo<100>;
/* Obtener las celdas que son "true". */
foreach(new i : Bits(Ejemplo))
{
printf("La celda %d está en true.", i);
}
/* Obtener las celdas que son "false". */
foreach(new i : Blanks(Ejemplo))
{
printf("La celda %d está en false.", i);
}
PHP Code:
/* y_commands */
/* Iteramos a través de todos los comandos creados. */
foreach (new i : Command())
{
}
/* Iteramos a través de todos los comandos que puede utilizar un jugador. */
foreach (new i : PlayerCommand(playerid))
{
}
PHP Code:
/* y_groups */
/* Iteramos a través de todos los grupos creados. */
foreach (new Group:i : CreatedGroup())
{
}
/* Iteramos a través de todos los sub-grupos de un grupo. */
foreach (new Group:i : GroupChild(g))
{
}
/* Iteramos a través de todos los miembros que posee un grupo. */
foreach (new i : GroupMemeber(g))
{
}
/* Iteramos a través de todos los grupos en los que se encuentra un jugador. */
foreach (new Group:i : PlayerGroups(playerid))
{
}
Tenemos una cantidad de funciones que nos permiten interactuar con los iteradores que declaramos, vamos a ver cómo funciona cada una de ellas:
- Iter_Init(iter)
Code:PARÁMETROS
iter -> nombre del iterador que vamos a inicializar.
PHP Code:
new Iterator:Casas[MAX_PLAYERS]<50>;
/* OnGameModeInit */
Iter_Init(Casas);
/* En algún lugar */
Iter_Add(Casas[playerid], 5);
- Iter_Add(iter, value)
Code:PARÁMETROS
iter -> nombre del iterador.
value -> valor que vamos a añadir en el iterador.
VALORES DEVUELTOS
value -> se devuelve el valor que se ha añadido si se ha ejecutado correctamente.
cellmin -> si el valor que intentamos añadir es inválido o si ya existe dentro del iterador.
PHP Code:
new Iterator:Lista<5>;
/* En algún lugar */
Iter_Add(Lista, 0);
/* En otro lugar */
if(Iter_Add(Lista, 2) == 2)
{
print("El valor 2 se ha añadido correctamente.");
}
- Iter_Remove(iter, value)
Code:PARÁMETROS
iter -> nombre del iterador.
value -> valor que vamos a eliminar del iterador.
VALORES DEVUELTOS
value -> se devuelve el valor que se ha eliminado si se ha ejecutado correctamente.
cellmin -> si el valor que intentamos eliminar es inválido o si no existe dentro del iterador.
PHP Code:
new Iterator:Lista<5>;
/* En algún lugar */
Iter_Add(Lista, 0);
/* En otro lugar */
if(Iter_Remove(Lista, 0) == 0)
{
print("El valor 0 se ha eliminado correctamente.");
}
- Iter_SafeRemove(iter, value, &prev)
Code:PARÁMETROS
iter -> nombre del iterador.
value -> valor que vamos a eliminar del iterador.
&prev -> valor que usaremos como base para seguir iterando.
VALORES DEVUELTOS
value -> se devuelve el valor que se ha eliminado si se ha ejecutado correctamente.
cellmin -> si la función ha fallado.
PHP Code:
new Iterator:Lista<5>;
Iter_Add(Lista, 0);
Iter_Add(Lista, 1);
Iter_Add(Lista, 2);
Iter_Add(Lista, 3);
/* En algún lugar */
foreach(new x : Lista)
{
/* Supongamos una condición. */
if(x == 2)
{
new base;
Iter_SafeRemove(Lista, x, base);
i = base;
}
}
/*
Mucho cuidado, el valor que eliminamos debería ser el valor que contenga "x" en el momento que
llamamos a Iter_SafeRemove. La variable "base" toma el primer valor disponible que se encuentra
antes de "x", así se tiene una referencia para poder seguir iterando. Si usáramos Iter_Remove,
al eliminar el valor, el script no sabrá por donde iba y se perderá, ¡puede resultar en un bucle infinito!
*/
- Iter_Clear(iter)
Code:PARÁMETROS
iter -> nombre del iterador que vamos a vaciar.
PHP Code:
new Iterator:Lista<5>;
Iter_Add(Lista, 0);
Iter_Add(Lista, 1);
Iter_Add(Lista, 4);
/* Vaciamos el iterador "Lista". */
Iter_Clear(Lista);
- Iter_Free(iter)
Code:PARÁMETROS
iter -> nombre del iterador.
VALORES DEVUELTOS
valor -> el primer valor libre que se encuentre en el iterador.
cellmin -> si no se encuentra ningún valor libre en el iterador.
PHP Code:
new Iterator:Lista<5>;
/* En algún lugar */
Iter_Add(Lista, 0);
Iter_Add(Lista, 1);
/* En otro lugar */
printf("Primer valor libre - %d", Iter_Free(Lista)); // Resultado: Primer valor libre - 2
/*
Cuidado, esta función no añade dicho valor al iterador, solo
nos informa. Si queremos podemos hacer algo como esto:
*/
Iter_Add(Lista, Iter_Free(Lista));
- Iter_Alloc(iter)
Code:PARÁMETROS
iter -> nombre del iterador.
VALORES DEVUELTOS
valor -> el primer valor libre que se ha añadido al iterador.
cellmin -> si no se encuentra ningún valor libre en el iterador.
PHP Code:
new Iterator:Lista<2>;
Iter_Add(Lista, 0);
printf("Se ha añadido - %d", Iter_Alloc(Lista)); // Resultado: Se ha añadido - 1.
- Iter_Count(iter)
Code:PARÁMETROS
iter -> nombre del iterador.
VALORES DEVUELTOS
valor -> la cantidad de elementos que hay dentro de un iterador.
PHP Code:
new Iterator:Lista<5>;
Iter_Add(Lista, 0);
Iter_Add(Lista, 1);
printf("%d", Iter_Count(Lista)); // Resultado: 2
- Iter_Random(iter)
Code:PARÁMETROS
iter -> nombre del iterador.
VALORES DEVUELTOS
valor -> un valor aleatorio del iterador.
cellmin -> en caso de que el iterador esté vacío.
PHP Code:
new Iterator:Lista<5>;
Iter_Add(Lista, 0);
Iter_Add(Lista, 1);
printf("%d", Iter_Random(Lista)); // Resultado: 0 o 1
- Iter_Contains(iter, value)
Code:PARÁMETROS
iter -> nombre del iterador.
value -> valor que buscamos.
VALORES DEVUELTOS
0 -> el iterador no contiene dicho valor.
1 -> el iterado contiene dicho valor.
PHP Code:
new Iterator:Lista<5>;
Iter_Add(Lista, 3);
if(Iter_Contains(Lista, 3) == 1)
{
print("Existe.");
}
Existen unas cuantas funciones más que te pueden dar más control sobre los bucles creados con este include, por ejemplo:
- Iter_First(iter)
Code:PARÁMETROS
iter -> nombre del iterador.
VALORES DEVUELTOS
valor -> devuelve el primer valor válido de un iterador.
- Iter_Last(iter)
Code:PARÁMETROS
iter -> nombre del iterador.
VALORES DEVUELTOS
valor -> devuelve el último valor válido de un iterador.
- Iter_Next(iter, cur)
Code:PARÁMETROS
iter -> nombre del iterador.
cur -> valor actual.
VALORES DEVUELTOS
valor -> el primer valor válido que se encuentra después del valor actual "cur".
- Iter_Prev(iter, cur)
Code:PARÁMETROS
iter -> nombre del iterador.
cur -> valor actual.
VALORES DEVUELTOS
valor -> el primer valor válido que se encuentra antes del valor actual "cur".
- Iter_Begin(iter)
Code:PARÁMETROS
iter -> nombre del iterador.
VALORES DEVUELTOS
valor -> un valor inválido que se encuentra antes del comienzo del iterador.
- Iter_End(iter)
Code:PARÁMETROS
iter -> nombre del iterador.
VALORES DEVUELTOS
valor -> un valor inválido que se encuentra después del último valor válido del iterador.
PHP Code:
new Iterator:Ejemplo<5>;
Iter_Add(Ejemplo, 2);
Iter_Add(Ejemplo, 0);
for(new i = Iter_First(Ejemplo); i != Iter_End(Ejemplo); i = Iter_Next(Ejemplo, i))
{
printf("%d", i); //Resultado: 0 2
}
Podemos aplicar algunas funciones directamente al iterar, veamos algunas de ellas.
- Reverse
En un iterador inverso iteramos desde el valor más alto hacia el más pequeño.
PHP Code:
new Iterator:Ejemplo<10>;
Iter_Add(Ejemplo, 5);
Iter_Add(Ejemplo, 0);
Iter_Add(Ejemplo, 10);
foreach(new x : Reverse(Ejemplo)) /* Aplicamos Reverse */
{
printf("%d", x); // Resultado: 10 5 0
}
- Range
En este iterador establecemos un intervalo y lo podemos recorrer de diferentes formas.
PHP Code:
/* Iterar en un intervalo. */
foreach(new x : Range(4, 8))
{
printf("%d", x); // Resultado: 4 5 6 7
}
/* Iterar en un intervalo aplicando un salto. */
foreach(new x : Range(0, 6, 2))
{
printf("%d", x); // Resultado: 0 2 4
}
/* Iterar en un intervalo al revés. */
foreach(new x : Range(10, 5, -1))
{
printf("%d", x); // Resultado: 10 9 8 7 6
}
- Random
Esta función podemos usarla de diferentes modos, veamos:
PHP Code:
/* Itera una cantidad de veces devolviendo valores aleatorios. */
foreach(new x : Random(5))
{
// Se repite 5 veces, "x" puede valer cualquier cosa entre [-2147483648, 2147483647].
}
/* Itera una cantidad de veces devolviendo valores aleatorios comprendidos en un rango. */
foreach(new x : Random(5, -2, 6))
{
// Se repite 5 veces, "x" puede valer cualquier cosa entre [-2, 6).
}
Mucha gente no entiende cómo funciona realmente y_iterate, hay diferencias importantes entre una array y un iterador. Este include no está pensado para almacenar IDs que devuelven las funciones que nosotros hagamos por ahí, más bien está hecho para llevar un rastreo de los índices válidos de una array.
Imaginen que creamos un sistema de casas dinámico, digamos que cada casa tiene una ID única que nos sirve para identificarla en una base de datos MySQL. Por ejemplo:
PHP Code:
#define MAX_HOUSES 1000
enum e_house_info
{
SQL_ID,
Precio
}
new HouseInfo[MAX_HOUSES][e_house_info];
/* Declaramos nuestro iterador para llevar la cuenta de las casas. */
new Iterator:Houses<MAX_HOUSES>;
/* Cargamos las casas */
CargarCasas()
{
// ...
HouseInfo[x][SQL_ID] = cache_get_value_index_int(...);
HouseInfo[x][Precio] = cache_get_value_index_int(...);
Iter_Add(Houses, x); // ¡OJO! Añadimos al iterador el índice de la array HouseInfo, ¡no SQL_ID!
// ...
printf("Se han cargado %d casas.", Iter_Count(Houses));
return 1;
}
/* Alguien usa un comando y quiere que todas las casas tengan un checkpoint en el mapa. */
foreach(new id : Houses)
{
SetPlayerCheckpoint(playerid, HouseInfo[id][X], HouseInfo[id][Y], HouseInfo[id][Z], 1.0);
}
/*
Si se dan cuenta, al utilizar foreach solamente recorremos las celdas válidas de HouseInfo.
Si hay tres casas cargadas, el bucle se repite tres veces, no mil. Esto es mucho más rápido
que utilizar un bucle for y comprobar si cada celda de HouseInfo es una casa válida.
*/
Ya que estamos con esto, vamos a explicar uno de los ejemplos de más arriba para que entiendan bien por qué lo hemos hecho así:
PHP Code:
new Iterator:Coches<MAX_PLAYERS, MAX_VEHICLES>;
/*
¿Por qué MAX_PLAYERS y MAX_VEHICLES?
Si lo piensan bien MAX_PLAYERS viene definido como 500 y MAX_VEHICLES como 2000. Las IDs de
los jugadores están comprendidas en el intervalo [0, 499] y las de los vehículos entre [1, 1999].
¿Qué significa <MAX_PLAYERS, MAX_VEHICLES>?
Significa que se van a crear 500 iteradores, pero los 2000 elementos se repartirán entre todos ellos.
Un iterador puede tener varios elementos en su interior, pero todos son únicos, ninguno se repite.
Por su parte, los elementos pueden existir solamente una vez, es decir, no pueden estar en varios
iteradores a la vez. Un elemento puede estar en el iterador, o bien, libre.
Sabiendo esto, tiene sentido asignar la ID in-game de un vehículo a un jugador porque cada
jugador tiene su iterador "Coches<playerid>" y cada coche puede estar solamente en un iterador.
De esta forma conseguimos que un vehículo solo pueda ser "comprado" una vez por un único jugador.
*/
if(Iter_Contains(Coches<playerid>, 50))
{
printf("El jugador con la ID %d posee el vehículo con la ID 50.", playerid);
}
Créditos
Si tienen alguna duda respecto a la autoría de alguno de los tutoriales que he escrito en relación a YSI os invito a leer el tema principal. Esto es todo, espero que os sirva. Por favor, comenten cualquier duda o error de tipografía (o conceptual) que encuentren.