And some code. Drop this into your favourite hello world program and just call go4e_15112().
As you see it's all fairly straight forward, there's just a lot of it.
Actually I didn't use benefits[], I just coded them directly. If you have a look at the way I process commands like NORTH, SOUTH, EAST and WEST you'll see what I mean by using generic code and implementing the details in the data structure. The code handles the possibility of going in any of the four directions from any point(which is what is meant by generic); it's the contents of the data structure that determines which way you can go. So let's say you have a new position 7 south of 3 with again "nothing interesting". Add "nothing interesting" to descriptions[] and change directions to:
char *directions="0:12X3 1:X0X3 2:0XXX 3:X704 4:5X3Y 5:X4XX 6:XX4X 7:3XXX"; // NSWE
So in this way we have extended the game with no code changes at all (well, ok, technically we have but if directions and descriptions were read in from a file instead then all we would have changed was that file).

Code:
void init()
{
	for (int i=0; i<3; i++) monster_dead[i]=0;
	for (int i=0; i<2; i++) solved[i]=0;
}

void fight_monster(int monster_id,int *player_strength, int *player_skill)
{
	printf("Strength: YOU: %d  MONSTER: %d\n",*player_strength,monster_strength[monster_id]);
	printf("Skill: YOU: %d MONSTER: %d\n",*player_skill,monster_skill[monster_id]);

	// stub: expand this
	*player_strength/=2;
	if (!*player_strength)
	{
		printf("Whoops you just died\n");
	}
	else
	{
		printf("A nasty battle ensues and you are injured but win\n");
		printf("As you depart you notice the monster rematerialise.  Oh dear...\n");
	}
}

// strip trailing whitespace
void strip_trws(char *cmd)
{
	int i;
	for (i=0; cmd[i]; i++) ;
	for (i--; isspace(cmd[i]); i--) cmd[i]=0;
}

// convert to uppercase
void upcase(char *str)
{
	for (int i=0; str[i]; i++)
		if (str[i]>='a' && str[i]<='z')
			str[i]+='A'-'a';
}

void go4e_15112()
{
	int game_exit=0;
	int pos=0; // player position
	int strength=10, skill=20;
	init();
	while (!game_exit)
	{
		// display the description
		printf("%s",descriptions[pos]);

		// any monsters to kill?
		int mid=monsters[pos];
		if (mid && !monster_dead[mid])
			fight_monster(mid, &strength, &skill);

		// any traps?
		switch (traps[pos])
		{
		case 0: // nothing
			break;

		case 1: // bottomless pit. Text has been handled in the description
			strength=0;
			break;
		}

		// any puzzles?
		switch (puzzles[pos])
		{
		case 0: // nothing
			break;

		case 1: // MENE MENE TEKEL PARSIN. Name and two numbers?  (Daniel, 5, 25)
			{
				char name[32], num1[32], num2[32];
				printf("Enter name: ");
				fgets(name,30,stdin); strip_trws(name);
				printf("Enter first number: ");
				fgets(num1,30,stdin);
				printf("Enter second number: ");
				fgets(num2,30,stdin);

				if (!strcmp(name,"Daniel") && (atoi(num1)==5) && (atoi(num2)==25))
				{
					printf("Correct!\n");
					solved[1]=1;
				}
				else printf("Sorry, try again next time you're here\n");
			}
			break;
		}

		// Don't bother displaying directions and taking input if we're dead.
		if (strength<=0) game_exit=1;
		else
		{
			// Display available directions
			// char *directions="0:12X3 1:X0X3 2:0XXX 3:XX04 4:5X3Y 5:X4XX 6:XX4X"; // NSWE
			char *dir=&directions[pos*7+2];
			printf("Exits are to the ");
			if (dir[0]>='0' && dir[0]<='6') printf("North ");
			if (dir[1]>='0' && dir[1]<='6') printf("South ");
			if (dir[2]>='0' && dir[2]<='6') printf("West ");
			if (dir[3]>='0' && dir[3]<='6') printf("East ");
			if (pos==4 && solved[1]) printf("East "); // maybe a better way of handling this generically?

			// Handle player input
			printf("State your command: ");
			char cmd[1024];
			fgets(cmd,1020,stdin);
			strip_trws(cmd);
			upcase(cmd);
			int newpos=-1;
			// Very simple parser
			if (pos==0 && !strcmp(cmd, "TAKE AXE"))
			{
				// we should check (a) if we've already got the axe; (b) for inventory overflow
				// If you just keep taking axes you'll end up with lots of axes and a crash.
				inv[inv_next++]=1; // We should really use a symbolic constant here.  inv[inv_next]=AXE; is more readable.
			}
			else if (pos==6 && !strcmp(cmd,"ENTER TELEPORT"))
			{
				printf("You enter the teleport, blah blah, you win!\n");
				game_exit=1;
			}
			else if (!strcmp(cmd,"NORTH") || !strcmp(cmd,"N"))
			{
				char c=dir[0];
				if (c>='0' && c<='6') newpos=c-'0';
				else printf("Sorry, no exit in that direction\n");
			}
			else if (!strcmp(cmd,"SOUTH") || !strcmp(cmd,"S"))
			{
				char c=dir[1];
				if (c>='0' && c<='6') newpos=c-'0';
				else printf("Sorry, no exit in that direction\n");
			}
			else if (!strcmp(cmd,"WEST") || !strcmp(cmd,"W"))
			{
				char c=dir[2];
				if (c>='0' && c<='6') newpos=c-'0';
				else printf("Sorry, no exit in that direction\n");
			}
			else if (!strcmp(cmd,"EAST") || !strcmp(cmd,"E"))
			{
				char c=dir[3];
				if (c>='0' && c<='6') newpos=c-'0';
				else if (pos==4 && solved[1]) newpos=6;
				else printf("Sorry, no exit in that direction\n");
			}
			else if (!strcmp(cmd,"INV"))
			{
				if (!inv_next) printf("You have no items\n");
				else 
				{
					printf("You have the following items:\n");
					for (int i=0; i<inv_next; i++)
					{
						switch (inv[i])
						{
						case 1: printf("An axe\n"); break;
						default: printf("A bug in the software\n"); break;
						}
					}
				}
			}
			// Did we move?
			if (newpos!=-1) pos=newpos;
		}
	}
}