Creando una aventura conversacional con Z88DK (II)

Seguimos en esta ocasión por donde lo dejamos en la entrega anterior. Recordemos que el objetivo de esta serie de artículos es practicar con las funciones de texto de la librería z88dk, por medio de un ejemplo de programa que, aun siendo posible que se desarrolle con otras herramientas, como BASIC, parsers, etc., es bastante ilustrativo, y además nos permite estar haciendo un juego desde el primer momento.

En la entrega anterior habíamos comenzado a escribir las aventuras de Guybrush Threpwood, un aspirante a pirata, que debía dirigirse a un maestro en la taberna de la isla de Melêe e impresionarle con algún truco. Habíamos dado los pasos precisos para que nuestro personaje fuera capaz de desplazarse entre las distintas habitaciones de la taberna, con las funciones de texto del z88dk, que habíamos visto que eran muy parecidas a las del C estándar de PC. También vimos como aplicar unos efectos interesantes al texto (negrita, cursiva, subrayado y texto inverso).

En el presente artículo veremos alguna técnica para incorporar objetos a nuestro mundo, incluyendo la posibilidad de interactuar con ellos, y la creación de un inventario. También veremos como aplicar efectos de color al texto (hasta ahora, todo era en un aburrido blanco y negro). Sin más dilación comenzamos.

Si seguimos la entrega anterior no tenemos que realizar ninguna preparación adicional. Todo el código que vayamos añadiendo se incluirá en los archivos aventura.c y datos.h.

Lo primero de todo es crear la estructura de datos que nos permitirá que los objetos puedan ser manejados durante el juego, igual que se hizo en el caso de las habitaciones. Esta estructura de datos para los objetos contendrá una serie de valores que serán de utilidad, como el nombre del objeto, dónde se encuentra, cuánto pesa, etc.

En concreto, podemos añadir el siguiente código a datos.h (da igual si es antes o después de la definición de THabitacion):

typedef struct
{
	char nombre[35];
	int localizacion;
	int peso;
} TObjeto;

Todos los objetos de nuestro juego serán variables del tipo TObjeto. El primer campo (nombre) contendrá el nombre con el que aparecerá el objeto en la pantalla, como por ejemplo: “una antorcha”, “un anillo”, “la espada”, etc. El campo de localizacion indicará en que posición del mapeado de juego se encuentra el objeto, aunque también puede indicar diferentes estados del objeto (como por ejemplo, que el objeto está en nuetros inventario, que está destruido, que todavía no se ha creado, que lo llevamos puesto, etc.). Por último, el peso, como es evidente, indica cuánto pesa el objeto. Es corriente en las aventuras que el protagonista sólo pueda llevar un número limitado de cosas, y el campo peso permite controlar esto.

Hablemos del campo localización. Hemos dicho que ese campo nos va a indicar en qué lugar se encuentra el objeto. Si recordamos, cada habitación de nuestra aventura tenía un identificador, que coincidía con su posición en el array de habitaciones (menos uno). Si queremos que el objeto esté en una de las habitaciones, el campo localización deberá contener el identificador de esa habitación (o la posición en el array de habitaciones). Por ejemplo, supongamos que queremos crear un objeto, una espada, que se encuentre en la habitación número 5 (la cocina). Lo que debemos hacer es almacenar en el campo localización de la variable que represente a la espada el valor 5 (más adelante veremos ejemplos).

El campo de localización es un campo dinámico; es decir, puede cambiar de valor durante el transcurso de la aventura. Por ejemplo, si la espada deja de estar en la cocina para pasar a estar en el salón (porque el personaje la haya cogido de la cocina y la haya dejado en el salón), el valor de localización pasará a ser 4.

Por lo tanto, el campo localización indica la habitación en la que se encuetra el objeto. Sin embargo, existe una serie de localizaciones especiales, que no se corresponden con habitaciones de nuestra aventura. Uno de estos valores es el -1. Si el campo localización de un objeto vale -1, significa que el objeto está en posesión del jugador (y que por lo tanto, al consultar el inventario o listado de objetos que porta, se le indicará que lo lleva). Aparte podemos inventarnos todas las localizaciones especiales que quisieramos, por ejemplo, la 254 para los objetos que tenemos puestos, la 255 para los que están destruidos, etc. (más adelante hablaremos también de ello).

Antes de seguir programando, hemos de pensar: ¿qué objetos va a tener nuestra aventura? En nuestro caso concreto vamos a tener tres:

  • Una espada, que encontraremos en la cocina de la taberna.
  • Una jarra, que portará el jugador consigo desde el comienzo de la aventura.
  • Una antorcha, que podremos recoger en el callejón.

De momento vamos a incluir la espada y la jarra en nuestra aventura. La antorcha es un tipo de objeto más complicado que trataremos más adelante. En el archivo aventura.c, en el método main, justo después de la inicialización de las habitaciones, podemos incluir el siguiente código (el código marcado como “nuevo” es el que se añade):

THabitacion habitaciones[6];
TObjeto objetos[4];                      // nuevo
inicializarHabitaciones(habitaciones);
inicializarObjetos(objetos);             // nuevo

Como en el caso de las habitaciones, creamos un array que contendrá todos los objetos de nuestra aventura. En nuestro caso, este array tendrá tamaño 4, para la espada, la jarra, y dos para la antorcha (no os impacientéis, luego entenderéis por qué se usan dos objetos para la antorcha). Evidentemente, si ahora compilamos no va a funcionar, porque falta por crear la función inicializarObjetos que, como en el caso de inicializarHabitaciones, se encarga de rellenar el array de objetos. Nuestra función inicializarObjetos podría tener la siguiente forma (se debe recordar que tiene que estar antes de la función main):

void inicializarObjetos(TObjeto objetos[])
{
	strcpy(objetos[0].nombre,"una espada");
	objetos[0].localizacion = 5;
	objetos[0].peso = 5;
 
	strcpy(objetos[1].nombre,"una jarra");
	objetos[1].localizacion = -1;
	objetos[1].peso = 3;
}

(la antorcha todavía no la hemos creado… sí, somos pesados con la antorcha). El primer objeto es la espada, y al principio de la aventura se encontrará en la habitación cuyo identificador sea el 5 (la cocina); decimos al principio porque, como hemos comentado antes, eso puede cambiar. La espada tiene un peso de 5. El siguiente objeto es la jarra, cuya localización inicial es la -1, que hemos dicho que era la localización especial que usábamos para indicar que el objeto estaba en posesión del protagonista de la aventura.

El último paso es que, junto a la descripción de las habitaciones, se nos indiquen los objetos que podemos encontrar en ellas. Eso, evidentemente, es necesario hacerlo en la función escribirDescripcion, y se hará de forma similar a como se escribían las posibles direcciones; lo único es que tendremos que recorrer el array de objetos comprobando cuáles están en la habitación. Por lo tanto, tenemos que pasar como parámetro el array de objetos. La función podría quedar de la siguiente forma (el código marcado como “Nuevo” es nuevo, además de la inclusión de “TObjeto objetos” en la definición de la función):

void escribirDescripcion(THabitacion habitaciones, int habitacion, TObjeto objetos )
{
	int hayObjetos = 0;
	int i;
 
	printf(habitaciones[habitacion].descripcion);
	printf("\n\n");
	printf("Salidas:");
	if (habitaciones[habitacion].direcciones[0] != 0)
		printf(" %c[4mNorte%c[24m",27,27);
	if (habitaciones[habitacion].direcciones[1] != 0)
		printf(" %c[4mEste%c[24m",27,27);
	if (habitaciones[habitacion].direcciones[2] != 0)
		printf(" %c[4mSur%c[24m",27,27);
	if (habitaciones[habitacion].direcciones[3] != 0)
		printf(" %c[4mOeste%c[24m",27,27);
	printf("\n\n");
 
        // Nuevo (principio)
	printf("En la habitacion puedes ver:");
	for (i=0;i<4;i++)
		if (objetos[i].localizacion == habitaciones[habitacion].id)
		{
			printf(" %s",objetos[i].nombre);
			hayObjetos = 1;
		}
	if (hayObjetos == 0)
		printf(" nada");
        // Nuevo (fin)
 
	printf("\n\n");	
}

Como hemos añadido un parámetro de entrada más, en todas las líneas a las que se llame a la función se debe añadir también ese parámetro. Estas líneas (en la función main) quedarían de la siguiente forma:

escribirDescripcion(habitaciones,habitacion,objetos)

El código que hemos añadido recorre el array de objetos comprobando para cada uno si su localización corresponde con el identificador de la habitación actual. En el caso de que sea así escribe su nombre (fíjate en el espacio en blanco antes del %s). La variable hayObjetos nos sirve para determinar si hay algún objeto en la habitación. En un principio vale 0, y si la localización de algún objeto se corresponde con el identificador de la habitación actual, valdrá 1. Solo en el caso de que valga 0 se mostrará el mensaje “nada”. Ahora ya podemos compilar y ejecutar la aventura, dirigiéndonos hacia la cocina para comprobar que la espada está allí. No podemos saber nada de la jarra, porque todavía no hemos implementado el inventario, lo haremos en la siguiente sección.

¿Quién se habrá dejado esta espada aquí tirada en la cocina?

A partir de este momento, el secreto para poder realizar acciones con los objetos durante la aventura consiste nada más que en añadir posibles frases a usar por el jugador en el intérprete de comandos. Si recordamos, el intérprete de comandos era la parte del código que leía la cadena de texto introducida por el jugador y comparaba con el vocabulario conocido con el programa. Para incluir los comandos de inventario y coger y dejar objetos debemos añadir código a esta parte.

Implementemos primero el inventario. Cuando el jugador teclee “inventario” o “i”, se deberán mostrar por pantalla todos los objetos que porta. Podemos añadir el siguiente código al intérprete de comandos, dentro del método main:

		}
		else
			printf("\n\nNo puedo ir en esa direccion\n\n");
	}
 
        // Nuevo (principio)
	else if (strcmp(comando,"i") == 0 || strcmp(comando,"inventario") == 0)
	{
		hayObjetos = 0;
		printf("\n\nLlevas:");
		for (i = 0; i<4;i++)
			if (objetos[i].localizacion == -1)
			{
				printf(" %s",objetos[i].nombre);
				hayObjetos = 1;
			}
		if (hayObjetos == 0)
			printf(" nada");
		printf("\n\n");	
	}
        // Nuevo (fin)
 
	else
		printf("\n\nNo entiendo lo que dices\n\n");
 
}

La variable hayObjetos se debe definir al comienzo de la función main y cumple el mismo objetivo que en la función describirHabitacion. Lo único que se hace en el caso del inventario es recorrer el array de objetos escribiendo el nombre de aquéllos para los que el valor de localización sea -1, o “nada” en el caso de que no haya ninguno que cumpla esta condición. Si compilamos y ejecutamos, por fin veremos la jarra en nuestro juego al teclear “i” o “inventario”.

Esperemos que esta jarra nos sirva de algo en nuestra aventura

Para que el jugador pueda coger y dejar objetos, lo único que tenemos que hacer, nuevamente, es añadir los comandos adecuados al intérprete de comandos. La implementación es tan simple como hacer que cuando cojamos un objeto, la localización del mismo pase a ser -1, y que cuando dejemos un objeto, la localización del mismo pase de ser -1 a ser el identificador de la habitación actual. Veamos primero cómo implementar la parte de coger objetos:

else if (strcmp(comando,"i") == 0 || strcmp(comando,"inventario") == 0)
{
	hayObjetos = 0;
	printf("\n\nLlevas:");
	for (i = 0; i<4;i++)
		if (objetos[i].localizacion == -1)
		{
			printf(" %s",objetos[i].nombre);
			hayObjetos = 1;
		}
	if (hayObjetos == 0)
		printf(" nada");
	printf("\n\n");	
}
// Nuevo (principio)
else
// Comandos con más de una palabra
{
	strcpy(palabra,strtok(comando," "));
	if (strcmp(comando,"coger") == 0)
	{
		strcpy(palabra,strtok(0,"\0"));
		if (palabra == 0)
			printf("\n\nNecesito que me digas que tengo que coger\n\n");
		else
		{
			hayObjetos = 0;
			i = 0;
			while (hayObjetos == 0 && i<4)
			{
				if (strcmp(objetos[i].nombre,palabra) == 0 && 
					objetos[i].localizacion == habitacion+1)
				{
					objetos[i].localizacion = -1;
					hayObjetos = 1;
					printf("\n\nHe cogido %s\n\n",palabra);
				}
				i++;
			}
			if (hayObjetos == 0)
				printf("\n\nNo puedo hacer eso\n\n");
		}
	}
	else
		printf("\n\nNo entiendo lo que dices\n\n");
}
// Nuevo (fin)

Para que este código funcione, se debe crear una nueva variable (al inicio de la función main):

char palabra[50];

¿Qué significa todo lo que hemos añadido? En primer lugar, se ha realizado una modificación importante al intérprete de comandos. hasta ahora, solo era capaz de interpretar comandos de una única palabra. Ahora le hemos añadido la posibildad de comprender comandos de más de una palabra. Simplemente, primero comprobamos si el comando introducido por el jugador se corresponde con alguno de los de una palabra. y al final introducimos el código que interpreta más de una. Este código empieza con la instrucción strcpy(palabra,strtok(comando,“ ”)).

La función strtok es otra de las que nos ofrece z88dk y su uso es exactamente igual al del estándar en el PC; strtok(cadena1,cadena2) devuelve la primera subcadena de cadena1, estando todas las subcadenas de cadena1 delimitadas por el carácter especificado en cadena2. Así pues, si cadena2 vale “ ” (espacio en blanco) como en el código anterior, la llamada a esta función devolverá los primeros carácteres de cadena1 hasta llegar al primer espacio en blanco (o el final de la cadena), es decir, la primera palabra. A partir de este momento, si vamos llamando a strtok pasando el valor 0 como cadena1, se nos irán devolviendo subcadenas sucesivas a partir de la cadena inicial. Hay que destacar que esta cadena inicial queda modificada; además, el código anterior, al compilarlo, nos mostrará una serie de warnings, que pueden ser ignorados (el programa funciona correctamente). Como lo que se devuelve es una cadena, es necesario utilizar strcpy para almacenar el valor devuelto en una variable de tipo char[].

Si la primera palabra es “coger”, entramos en la parte del código que interpreta este comando. Se utiliza de nuevo strtok para almacenar en palabra el resto del comando (pues se indica como delimitador el símbolo de final de cadena \0). Si el comando solo se compone de la palabra coger se mostrará un mensaje de error adecuado. Lo siguiente es inicializar las variables i, que se utilizará como contador para recorrer el array de objetos, y hayObjetos, que en este caso se utilizará para saber si se ha encontrado un objeto con el mismo nombre que el tecleado por el jugador. A continuación, se recorre el array de objetos buscando un objeto cuyo nombre se corresponda con el introducido por el jugador. El principal inconveniente es que el jugador tiene que introducir el nombre completo, incluyendo el artículo, en el caso de que se hubiera puesto. Por ejemplo, si el nombre del objeto es “una espada”, el jugador deberá teclear “coger una espada” para poder obtenerla. Se deja como ejercicio al lector arreglar este pequeño fallo.

Al recorrer el array no sólo se comprueba si existe algún objeto cuyo nombre se corresponda con el introducido, sino que también el objeto debe encontrarse en la misma habitación que el jugador. Coger el objeto consiste nada más en cambiar el valor del campo localización del objeto a -1.

Obsérvese que, tanto si el objeto existe como si no, tanto si el objeto está en la misma habitación como si no, se muestra el mismo mensaje de error (“No puedo hacer eso”). Esto es básico para no darle pistas al jugador sobre los objetos que existen en nuestro juego.

El código para dejar objetos es prácticamente igual; la única diferencia es que comprobamos que para dejar un objeto éste se encuentre en el inventario (localizacion = -1) y que dejar un objeto significa cambiar el valor del campo localizacion al del identificador de la habitación actual:

			if (hayObjetos == 0)
				printf("\n\nNo puedo hacer eso\n\n");
		}
	}
        // Nuevo (principio)
	else if (strcmp(comando,"dejar") == 0)
	{
		strcpy(palabra,strtok(0,"\0"));
		if (palabra == 0)
			printf("\n\nNecesito que me digas que tengo que dejar\n\n");
		else
		{
			hayObjetos = 0;
			i = 0;
			while (hayObjetos == 0 && i<4)
			{
				if (strcmp(objetos[i].nombre,palabra) == 0 
					&& objetos[i].localizacion == -1)
				{
					objetos[i].localizacion = habitacion+1;
					hayObjetos = 1;
					printf("\n\nHe dejado %s\n\n",palabra);
				}
				i++;
			}
			if (hayObjetos == 0)
			    printf("\n\nNo puedo hacer eso\n\n");
		}
	}
        // Nuevo (fin)
	else
		printf("\n\nNo entiendo lo que dices\n\n");
}
char palabra[50];

Un último detalle que nos queda por comentar es el del peso de los objetos. Recordemos que uno de los campos de TObjeto era el peso. Supongamos que el peso máximo que puede llevar un jugador es de 6. Lo que tenemos que añadir para poder controlar el peso en nuestro juego es lo siguiente:

  • Crear una variable que almacene el peso de los objetos portados por el jugador.
  • Controlar que el peso total al coger un objeto no supere el peso máximo que puede llevar un jugador. En caso de no ser así sumar el peso del objeto recogido al peso total.
  • Al dejar un objeto, restar su peso al peso total.

Para resolver el primer punto, creamos la variable pesoTransportado, al inicio de la función main:

	int pesoTransportado;

Y la inicializamos con el peso del objeto que porta el jugador nada más empezar. Esto lo podemos hacer justo después de inicializar los objetos:

inicializarHabitaciones(habitaciones);
inicializarObjetos(objetos);
pesoTransportado = objetos[1].peso;

Para resolver el segundo punto, añadimos el siguiente código a la hora de manejar que el jugador coja objetos:

int pesoTransportado;

Y para el tercer punto introducimos el siguiente código:

if (strcmp(comando,"coger") == 0)
{
	strcpy(palabra,strtok(0,"\0"));
	if (palabra == 0)
		printf("\n\nNecesito que me digas que tengo que coger\n\n");
	else
	{
	    hayObjetos = 0;
	    i = 0;
	    while (hayObjetos == 0 && i<4)
	    {
		if (strcmp(objetos[i].nombre,palabra) == 0 
			&& objetos[i].localizacion == habitacion+1)
		{
			hayObjetos = 1;
 
                        // Nuevo (principio)
			if (objetos[i].peso + pesoTransportado <= 6)
			{
				objetos[i].localizacion = -1;
				printf("\n\nHe cogido %s\n\n",palabra);
				pesoTransportado += objetos[i].peso;
			}
			else
				printf("\n\nNo puedo transportar mas peso\n\n");
                        // Nuevo (Fin)
 
			i++;
		}
		if (hayObjetos == 0)
			printf("\n\nNo puedo hacer eso\n\n");
	}
}

Hasta ahora muy pocas características de z88dk nuevas hemos introducido. Aprovechamos este apartado para indicar cómo añadir colores a nuestras aventuras conversacionales gracias a esta librería. Para ello introducimos un nuevo objeto, la antorcha, que es especial, porque vamos a poder encenderla y apagarla. Antes habíamos comentado que este tipo de objetos que se encienden y se apagan tienen que ser creados como dos objetos distintos. En el caso de la antorcha, podemos introducir el siguiente código en la función inicializarObjetos:

c[0] = 27;
c[1] = '\0';
strcpy(objetos[2].nombre,"una antorcha ");
strcat(objetos[2].nombre,c);
strcat(objetos[2].nombre,"[44mapagada"); 
strcat(objetos[2].nombre,c);
strcat(objetos[2].nombre,"[47m");
objetos[2].localizacion = 2;
objetos[2].peso = 2;
 
strcpy(objetos[3].nombre,"una antorcha ");
strcat(objetos[3].nombre,c);
strcat(objetos[3].nombre,"[43mencendida"); 
strcat(objetos[3].nombre,c);
strcat(objetos[3].nombre,"[47m");
objetos[3].localizacion = -2;
objetos[3].peso = 2;

Para que funcione tenemos que declarar la variable c al principio de la función como:

char c[2];

Se han creado dos objetos. El objeto de índice 2 se corresponde con la antorcha apagada y el objeto de índice 3 con la antorcha encendida. La antorcha apagada en un principio se encuentra en la localización con identificador 2 (el callejón) y tiene peso 2. La antorcha encendida, evidentemente, tiene el mismo peso, y al campo localizacion se le ha asignado un valor -2; vamos a usar el valor -2 en la localización para designar a aquellos objetos que no existen en el juego por alguna razón (por ejemplo, porque se hayan destruido, porque se tengan que contruir, objetos que representan diversos estados de un objeto, como por ejemplo una antorcha encendida apagada o encendida o un baúl abierto o cerrado, etc.). Esto quiere decir que si nos desplazamos a la localización 2, encontraremos la antorcha apagada, pero será imposible encontrar la antorcha encendida en ninguna localización de la aventura, y tampoco en nuestro inventario.

Es en el nombre de los objetos donde encontramos la dificultad. Si recordamos en la entrega anterior, cuando escribíamos texto con formato (subrayado, en negrita, etc.), se usaba printf de la siguiente manera:

printf("%c[4mTexto subrayado%c[24m",27,27);

Es decir, escribimos el carácter número 27, el símbolo [, y 4m indicando, por ejemplo, formato subrayado (o 24m, indicando texto sin subrayar). Como el carácter 27 es no imprimible, recurríamos a la facilidad que nos presentaba printf de escribir caracteres mediante %c. Para crear una cadena con color, el procedimiento es el mismo; sin embargo, la función strcpy no permite usar %c, así que lo que hacemos es ir concatenando, mediante el uso de strcat (que funciona igual que en el C del PC). Por ejemplo, en el caso de la antorcha apagada, deseamos que aparezca por pantalla la palabra 'apagada' con color de fondo azul. Copiamos en el nombre del objeto la cadena “una antorcha ”, le concatenamos el carácter 27 (utilizando el pequeño truco visto en el código anterior), concatenamos el formato ([44m se corresponde con color de fondo azul), concatenamos la palabra “apagada”, volvemos a concatenar el carácter 27 y el formato ([47m hace que el fondo vuelva a ser blanco); por lo tanto, entre ambos formatos especificados, el fondo aparecerá de color azul. Lo mismo se ha realizado con la antorcha encendida, pero usando el color amarillo.

Por lo tanto, si en una cadena escribimos el carácter 27, el símbolo [, un código de color xx, y la m, a partir de ese momento, si xx se encuentra entre los valores de la siguiente lista, el texto tendrá el color de fondo correspondiente al texto:

Codigo Color
40 negro
41 rojo
42 verde
43 amarillo
44 azul
45 magenta
46 cyan
47 blanco

Para el color del texto propiamente dicho, empleamos la misma táctica, pero utilizando los siguientes códigos de formato:

Codigo Color
30 negro
31 rojo
32 verde
33 amarillo
34 azul
35 magenta
36 cyan
37 blanco

Si compilamos y cargamos el juego en el emulador, veremos que si nada más empezar nos desplazamos hacia el este, la antorcha estará allí, pero no la podremos coger. Esto se debe a que para coger un objeto teníamos que introducir el nombre exacto del mismo, y eso es imposible, porque el nombre exacto de la antorcha apagada es: “una antorcha {27}[44mapagada{27}[47m”, que contiene un carácter que no se puede escribir (el 27).

Esto lo vamos a tener que solucionar añadiendo dos nuevos comandos al intérprete de comandos, “coger antorcha” y “dejar antorcha”. El código se muestra a continuación:

else if (strcmp(comando,"i") == 0 || strcmp(comando,"inventario") == 0)
{
	hayObjetos = 0;
	printf("\n\nLlevas:");
	for (i = 0; i<4;i++)
		if (objetos[i].localizacion == -1)
		{
			printf(" %s",objetos[i].nombre);
			hayObjetos = 1;
		}
	if (hayObjetos == 0)
		printf(" nada");
	printf("\n\n");	
}
// Nuevo (principio)
else if (strcmp(comando,"coger una antorcha") == 0)
{
	if (objetos[2].localizacion == habitacion + 1)
	{
		if (objetos[2].peso + pesoTransportado <= 6)
		{
			objetos[2].localizacion = -1;
			printf("\n\nHe cogido %s\n\n",objetos[2].nombre);
			pesoTransportado += objetos[2].peso;
		}
		else
			printf("\n\nNo puedo transportar mas peso\n\n");
	}
	else if (objetos[3].localizacion == habitacion + 1)
	{
		if (objetos[3].peso + pesoTransportado <= 6)
		{
			objetos[3].localizacion = -1;
			printf("\n\nHe cogido %s\n\n",objetos[3].nombre);
			pesoTransportado += objetos[3].peso;
		}
		else
			printf("\n\nNo puedo transportar mas peso\n\n");
	}
	else
		printf("\n\nNo puedo hacer eso\n\n");
}
else if (strcmp(comando,"dejar una antorcha") == 0)
{
	if (objetos[2].localizacion == -1)
	{
		objetos[2].localizacion = habitacion + 1;
		pesoTransportado -= objetos[2].peso;
		printf("\n\nHe dejado %s\n\n",objetos[2].nombre);
	}
	else if (objetos[3].localizacion == -1)
	{
		objetos[3].localizacion = habitacion + 1;
		pesoTransportado -= objetos[3].peso;
		printf("\n\nHe dejado %s\n\n",objetos[3].nombre);
	}
	else
		printf("\n\nNo puedo hacer eso\n\n");
}
else
// Nuevo (Fin)
 
// Comandos con más de una palabra
{

Lo que se ha hecho es aplicar el código general de coger y dejar objetos a la antorcha de tal forma que tan sólo sea necesario escribir “una antorcha” a la hora de coger o dejar el objeto, esté encendida o apagada.

Por útlimo introducimos dos comandos más, “encender antorcha” y “apagar antorcha”. Para encender la antorcha es necesario que la antorcha apagada (objeto de índice 2) se encuentre en nuestro inventario (el valor de su campo localizacion debe ser -1). Lo que se hará será cambiar su valor de localizacion a -2, para que el objeto desaparezca del juego, y cambiar el de la antorcha encendida (objeto de índice 3) a -1, para que aparezca en nuestro inventario. Para apagar la antorcha se sigue el proceso contrario. Se observa que al cambiar de estado la antorcha lo único que pasa es que un objeto desaparece del juego y otro es introducido. El código se muestra a continuación:

}
else if (strcmp(comando,"encender antorcha") == 0)
{
	if (objetos[2].localizacion == -1)
	{
		objetos[2].localizacion = -2;
		objetos[3].localizacion = -1;
		printf("\n\nHe encendido la antorcha\n\n");
	}
	else
		printf("\n\nNo puedo hacer eso\n\n");
}
else if (strcmp(comando,"apagar antorcha") == 0)
{
	if (objetos[3].localizacion == -1)
	{
		objetos[3].localizacion = -2;
		objetos[2].localizacion = -1;
		printf("\n\nHe apagado la antorcha\n\n");
	}
	else
		printf("\n\nNo puedo hacer eso\n\n");
}
else
// Comandos con más de una palabra
{

La antorcha en acción

¿Qué es lo que hemos visto en esta entrega? Resumimos:

  • Cómo añadir objetos a nuestro juego, y permitir manejarlos (coger, dejar, inventario, etc.).
  • Cómo añadir efectos de color a nuestro juego de texto (lo hemos visto de forma muy limitada, ya practicaremos más en posteriores entregas).
  • El uso de strtok y strcat.

Está claro que si queremos permitir que el jugador realice más acciones lo único que tenemos que hacer es introducir nuevos comandos en el intérprete de comandos tal cómo se ha visto hasta ahora.

Hemos hablado de objetos normales y objetos que se pueden encender/apagar, pero en una aventura podemos encontrar otro tipo de objetos, como objetos que se pueden llevar puestos (una chaqueta, un sombrero), objetos que pueden contener a otros (un baúl, una petaca), etc. Para crear este tipo de objetos podemos jugar, como en el caso de la antorcha, con valores especiales del campo localizacion y con varios objetos para distintos estados de un mismo objeto (antorcha encendida/apagada, baúl abierto/cerrado, etc.). Se deja como ejercicio al lector implementar este tipo de objetos

Por último, una curiosidad que el autor de este texto ha podido comprobar mientras realizaba este tutorial; el tema de las violaciones de segmento, con respecto a las cadenas. Por lo que se ha visto hasta ahora, no parece producirse una violación de segmento en ejecución si hay algún problema de memoria con punteros que se nos escape; simplemente se mostrarán caracteres extraños por pantalla… además, ¿cómo sería una violación de segmento en un Spectrum? ¿Similar a un RANDOMIZE USR 0?