Learning C With Game Concepts/Designing A Roleplaying Game

From Wikibooks, open books for an open world
Jump to: navigation, search

Now that we've covered the basics of compilation and modularity (we'll refine that section later) lets move on to game design. Our game will be implemented using the terminal and will not rely on any third party libraries, save for the ones that come with C by default. We'll take a Bottom-Up approach to creating our role playing game (hereafter referred to as an RPG).

Specifications[edit]

When undergoing a new project of some complexity, it is important to do some brainstorming about what your program will do and how you'll implement it. While that may sound painfully obvious, it is often tempting to jump right into coding and implement ideas as they pop into your head. It isn't until the code becomes hundreds of lines long does it become clear that organizing your thoughts is necessary. The initial brainstorming phase takes the main idea, an RPG in this case, and breaks it down into more elementary pieces. For each element, we either break it down into still smaller elements, or give a short summary of how we might implement it.

At the heart of every RPG, we have players.

Players shouldː

  • Have stats that tell us about their abilities.
  • Be able to interact with each other (i.e. Talking, Fighting)
  • Be able to move from one location to another.
  • Be able to carry items.

Statsː Easy. Just a variable telling us what the stat is for (like, health) and containing an integer value.

Talkingː To facilitate conversation, players need scripted dialog. This dialog could be stored with the main character, or the person with whom the main character will interact, and in the case of the latter, must be accessible by the main character.

Fightingː A function, that when given a player (to attack) initiates a battle sequence, that persists until someone retreats or a player's health is reduced to 0.

Movingː A player should contain a node for a linked list or binary tree. The first location node tells us where the player came from, where he is currently, and where he can go from there. "Where we can go" is a memory address (think, RAM) that tells the computer where another location node can be found. This new node will be structured the same as the first, but contain different information. Moving will involve changing the memory address of the current node (Swamp, let's say), to the memory address of another node (Forest). If that sounds confusing, don't worry, I'll make pictures to illustrate the concept. ː)

Inventoryː Inventory will start out as a doubly linked list. An item node contains an item (Health Potion), the number of that item, a description of the item, and two links. One link to the previous item in the list, and a second link to the next item in the list.

This preliminary specification acts as a blueprint for the next phase, the actual coding portion. Now that we've broken the main idea into smaller elements, we can focus on creating separate modules that enable these things. As an example, we will implement the player and player functions in the Main file for testing. Once we're positive that our code is working properly, we can migrate our datatypes and functions into a Header file, which we'll call whenever we want to create and manipulate players. Doing this will significantly reduce the amount of code to look at in the main file, and keeping player functions in the player header file will give us a logical place to look for, add, remove, and improve player functions. As we progress, we may think of new things to add to our specification.

Player Implementation[edit]

Because a player is too complex to represent with a single variable, we must create a Structure. Structures are complex datatypes that can hold several datatypes at once. Below, is a rudimentary example of a player structure.

struct playerStructure {
    char name[50];
    int health;
    int mana;
};

Using the keyword struct, we declare a complex datatype called playerStructure. Within the curly braces, we define it with all the datatypes needs to hold for us. This structure can be used to create new structures just like it. Let's use it to make a Hero and display his stats.

player.c

#include <stdio.h>
#include <string.h>
 
struct playerStructure {
    char name[50];
    int health;
    int mana;
} Hero;
 
// Function Prototype
void DisplayStats (struct playerStructure Target);
 
int main() {
    // Assign stats
    strcpy(Hero.name, "Sir Leeroy");
    Hero.health = 60;
    Hero.mana = 30;
 
    DisplayStats(Hero);
    return(0);
}
 
// Takes a player as an argument and prints their name, health, and mana. Returns nothing.
void DisplayStats (struct playerStructure Target) {
    // We don't want to keep retyping all this.
    printf("Name: %s\nHealth: %d\nMana: %d\n", Target.name, Target.health, Target.mana);
}

Let's review what our code does. We've included a new standard library called <string.h> which contains functions that are helpful in working with strings. Next, we define the complex datatype playerStructure and immediately declare a playerStructure called Hero right after it. Be aware, the semicolon is always necessary after defining the struct. Unlike higher level languages, strings cannot be assigned in C using the assignment operator =, only the individual characters that make up the string can be assigned. Since name is 50 characters long, imagine that we have 50 blank spaces. To assign "Sir Leeroy" to our array, we must assign each character to a blank space, in order, like so:

name[0] = 'S'

name[1] = 'i'

name[2] = 'r'

name[3] = ' '

name[4] = 'L'

name[5] = 'e'

name[6] = 'e'

name[7] = 'r'

name[8] = 'o'

name[9] = 'y'

name[10] = '\0' // End of string marker

The function Strcpy() essentially loops through the array until it reaches the end of string marker for either arguments and assigns characters one at a time, filling the rest with blanks if the string is smaller than the size of the array we're storing it in.

The variables in our structure, Player, are called members, and they are accessed via the syntax struct.member.

Now our game would be boring, and tranquil, if it just had a Hero and no enemies. In order to do add more players, we would need to type "struct playerStructure variableName" to declare new players. That's tedious and prone to mistakes. Instead, it would be much better if we had a special name for our player datatype that we could call as wisfully as char or int or float. That's easily done using the keyword typedefǃ Like before, we define the complex datatype playerstructure, but instead of declaring a playerStructure afterward, we create a keyword that can declare them whenever we want.

player2.c

#include <stdio.h>
#include <string.h>
 
typedef struct playerStructure {
    char name[50];
    int health;
    int mana;
} player;
 
// Function Prototype
void DisplayStats (player Target);
 
int main () {
    player Hero, Villain;
 
    // Hero
    strcpy(Hero.name, "Sir Leeroy");
    Hero.health = 60;
    Hero.mana = 30;
 
    // Villain
    strcpy(Villain.name, "Sir Jenkins");
    Villain.health = 70;
    Villain.mana = 20;
 
    DisplayStats(Hero);
    DisplayStats(Villain);
    return(0);
}
 
// Takes a player as an argument and prints their name, health, and mana. Returns nothing.
void DisplayStats (player Target) {
    printf("Name: %s\nHealth: %d\nMana: %d\n", Target.name, Target.health, Target.mana);
}

There is still the problem of creating players. We could define every single player who will make an appearance in our game at the start of our program. As long as the list of players is short, that might be bearable, but each of those players occupies memory whether they are used or unused. Historically, this would be problematic due to the scarcity of memory on old computers. Nowadays, memory is relatively abundant, but for the sake of scalability, and because users will have other applications running in the background, we'll want to be efficient with our memory usage and use it dynamically.

Dynamically allocating memory is accomplished through the use of the malloc, a function included in <stdlib.h>. Given a number of bytes to return, malloc finds unused memory and hands us the address to it. To work with this memory address, we use a special datatype called a pointer, that is designed to hold memory addresses. Pointers are declared like any other datatype, except we put an asterisk (*) in front of the variable name. Consider this line of codeː

player *Hero = malloc(sizeof(player));

This is the standard way of declaring a pointer and assigning it a memory address. The asterisk tells us that instead of declaring a player, with a fixed, unchangeable address in memory, we want a variable that can point to any player's address. Uninitialized pointers have NULL as their value, meaning they don't point to an address. Since it would be difficult to memorize how many bytes are in a single datatype, let alone our player structure, we use the sizeof function to figure that out for us. Sizeof returns the number of bytes in player to malloc, which finds enough free memory for a player structure and returns the address to our pointer.

If malloc returns the memory address 502, Hero will now point to a player who exists at 502. Pointers to structures have a unique way of calling members. Instead of a period, we now use an arrow (->).

player *Hero = malloc(sizeof(player));
strcpy(Hero->name, "Leeroy");
Hero->health = 60;
Hero->mana = 30;

Remember, pointers don't contain values like integers and chars, they just tell the computer where to find those values. When we change a value our pointer points to, we're telling the computer "Hey, the value I want you to change lives at this address (502), I'm just directing traffic." So when you think of pointers, think "Directing Traffic". Here's a table to show what pointer declarations of various types meanː


Declaration What it means.
char *variable Pointer to char
int *variable Pointer to int
float *variable Pointer to float
player *variable Pointer to player
player **variable Pointer to a pointer to player

Now that we're using pointers, we can write a function to dynamically allocate players. And while we're at it, let's add some new ideas to our specification.

Players shouldː

  • Have stats that tell us about their abilities. DONE
  • Be able to interact with each other (i.e. Talking, Fighting)
  • Be able to move from one location to another.
  • Be able to carry items.
  • Have classes (Warrior, Mage, Ranger, Accountant) NEW
    • Classes have unique stats when they are created. Exampleː Warriors have high health, mages have low health. NEW

dynamicPlayers.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
// Classes are enumerated. WARRIOR = 0; RANGER = 1, etc.
typedef enum ClassEnum  {
  WARRIOR,
  RANGER,
  MAGE,
  ACCOUNTANT
} class;
 
typedef struct playerStructure {
  char name[50];
  class class;
  int health;
  int mana;
} player;
 
// Function Prototypes
void DisplayStats(player *target);
int SetName(player *target, char name[50]);
player* NewPlayer(class class, char name[50]);    // Creates player and sets class.
 
int main() {
  player *Hero = NewPlayer(WARRIOR, "Sir Leeroy");
  player *Villain = NewPlayer(RANGER, "Sir Jenkins");
 
  DisplayStats(Hero);
  DisplayStats(Villain);
  return(0);
}
 
// Creates player and sets class.
player* NewPlayer(class class, char name[50]) {
  // Allocate memory to player pointer.
  player *tempPlayer = malloc(sizeof(player));
  SetName(tempPlayer, name);
 
  // Assign stats based on the given class.
  switch(class) {
  case WARRIOR:
    tempPlayer->health = 60;
    tempPlayer->mana = 0;
    class = WARRIOR;
    break;
  case RANGER:
    tempPlayer->health = 35;
    tempPlayer->mana = 0;
    class = RANGER;
    break;
  case MAGE:
    tempPlayer->health = 20;
    tempPlayer->mana = 60;
    class = MAGE;
    break;
  case ACCOUNTANT:
    tempPlayer->health = 100;
    tempPlayer->mana = 100;
    class = ACCOUNTANT;
    break;
  default:
    tempPlayer->health = 10;
    tempPlayer->mana = 0;
    break;
  }
 
  return(tempPlayer); // Return memory address of player.
}
 
void DisplayStats(player *target)  {
  printf("%s\nHealth: %d\nMana: %d\n\n", target->name, target->health, target->mana);
}
 
int SetName(player *target, char name[50]) {
  strcpy(target->name, name);
  return(0);
}

Before we move on to the next major development, you'll want to modularize what you've written. Start by making two header files, one named "gameProperties.h" and another called "players.h". In the game properties file, place your playerStructure and classEnum typedefs. The datatypes defined here will have the possibility of appearing in any other headers we may create. Therefore, this will always be the first header we call. Next, all functions related to creating and modifying players, as well as their prototypes, will go in our players header file.

Fight System[edit]

Rome wasn't built in a day and neither are good fight systems, but we'll try our best. Now that we have an enemy we are obligated to engage him in a friendly bout of fisticuffs. For our players to fight, we'll need to include two additional stats in our player structure, Attack and Defense. In our specification, all that our Fight function entailed was an argument of two players, but with further thought, lets do damage based on an EffectiveAttack, which is Attack minus Defense.

In the gameProperties header, modify playerStructure for two more integer variables, attack and defense.

gameProperties.h

// Classes are enumerated. WARRIOR = 0; RANGER = 1, etc.
typedef enum ClassEnum  {
    WARRIOR,
    RANGER,
    MAGE,
    ACCOUNTANT
} class;
 
// Player Structure
typedef struct playerStructure {
    char name[50];
    class class;
    int health;
    int mana;
    int attack;    // NEWː Attack power.
    int defense;   // NEWː Resistance to attack.
} player;

In the players header, modify the case statements to assign values to the attack and defense attributes.

players.h

// Creates player and sets class.
player* NewPlayer(class class, char name[50]) {
    // Allocate memory to player pointer.
    player *tempPlayer = malloc(sizeof(player));
    SetName(tempPlayer, name);
 
    // Assign stats based on the given class.
    switch(class) {
    case WARRIOR:
        tempPlayer->health = 60;
        tempPlayer->mana = 0;
        tempPlayer->attack = 3;
        tempPlayer->defense = 5;
        class = WARRIOR;
        break;
    case RANGER:
        tempPlayer->health = 35;
        tempPlayer->mana = 0;
        tempPlayer->attack = 3;
        tempPlayer->defense = 2;
        class = RANGER;
        break;
    case MAGE:
        tempPlayer->health = 20;
        tempPlayer->mana = 60;
        tempPlayer->attack = 5;
        tempPlayer->defense = 0;
        class = MAGE;
        break;
    case ACCOUNTANT:
        tempPlayer->health = 100;
        tempPlayer->mana = 100;
        tempPlayer->attack = 5;
        tempPlayer->defense = 5;
        class = ACCOUNTANT;
        break;
    default:
      tempPlayer->health = 10;
        tempPlayer->mana = 0;
        tempPlayer->attack = 0;
        tempPlayer->defense = 0;
        break;
    }
 
    return(tempPlayer); // Return memory address of player.
}
 
void DisplayStats(player *target)  {
  printf("%s\nHealth: %d\nMana: %d\n\n", target->name, target->health, target->mana);
}
 
int SetName(player *target, char name[50]) {
  strcpy(target->name, name);
  return(0);
}

Finally, include your header files in your main program. Instead of sharp brackets <> we use quotation marks instead. If the headers are located in the same file as the executable, you only need to provide the name. If your header file is in a folder somewhere else, you'll need to provide the full path of the file location.

Let's also develop a rudimentary fight system to make use the attack and defense attributes.

player3.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "players.h"
#include "gameProperties.h"
 
// Function Prototype
int Fight (player *Attacker, player ̈*Target);
 
int main () {
    player *Hero = NewPlayer(WARRIOR, "Sir Leeroy");
    player *Villain = NewPlayer(RANGER, "Sir Jenkins");
 
    DisplayStats(Villain);   // Before the fight.
    Fight(Hero, Villain);    // FIGHTǃ
    DisplayStats(Villain);   // After the fight.
    return(0);
}
 
int Fight (player *Attacker, player *Target) {
    int EffectiveAttack; // How much damage we can deal is the difference between the attack of the attacker
                         // And the defense of the target. In this case 5 - 1 = 4 = EffectiveAttack.
 
    EffectiveAttack = Attacker->attack - Target->defense;
    Target->health = Target->health - EffectiveAttack;
    return(0);
}

If we run compile and run this we get this outputː

Run the above to make sure we get expected output...

Continue updating from here


TODOː Drawing Showing Memory Addresses

TODOː Reword this because we're implementing pointers in the Player implementation section. If we want to make changes to Hero using the Attacker variable, we need to pass the memory address of Hero to Attacker. This is accomplished through the use of a datatype called pointers, that are designed to hold memory addresses. Pointers are declared like any other datatype, except we put an asterisk (*) in front of the variable name. If Hero exists at address 502 in RAM, the value of our pointer will be 502. When we change the value of our pointer, we're telling the computer "Hey, the value I want you to change lives at this address (502), I'm just directing traffic." So when you think of pointers, think "Directing Traffic". Here's a table to show what pointer declarations of various types meanː

To fix the shortcoming in the previous program we must make a few changes. First, the arguments for the Fight function must become pointers. Don't forget to change the prototype for Fight also. Next, pointers to structures have a special way of calling members. Instead of using a period (.) we instead use an arrow (->). The syntax is Pointer->Member. We must change all the member calls to use the arrow sign. Finally, in our Main function, when we call Fight, we must change the arguments so that Hero and Villain are preceeded by an Ampersand (&). The ampersand is a special sign that yields the memory address of the variable that follows it. When we call Fight, we're passing the memory address of Hero and Villain to the pointers Attacker and Target.

player4.c

#include <stdio.h>
#include <string.h>
 
typedef struct playerStructure {
    char name[50];
    int health;
    int mana;
    int attack;
    int defense;
} player;
 
// Function Prototype
void DisplayStats (player Target);
int Fight (player *Attacker, player ̈*Target);
 
int main () {
    player Hero, Villain;
 
    // Hero
    strcpy(Hero.name, "Sir Leeroy");
    Hero.health = 60;
    Hero.mana = 30;
    Hero.attack = 5;
    Hero.defense = 1; 
 
    // Villain
    strcpy(Villain.name, "Sir Jenkins");
    Villain.health = 70;
    Villain.mana = 20;
    Villain.attack = 5;
    Villain.defense = 1; 
 
    DisplayStats(Villain);   // Before the fight.
    Fight(&Hero, &Villain);    // FIGHTǃ
    DisplayStats(Villain);   // After the fight.
    return(0);
}
 
// Takes a player as an argument and prints their name, health, and mana. Returns nothing.
void DisplayStats (player Target) {
    printf("Name: %s\nHealth: %d\nMana: %d\n", Target.name, Target.health, Target.mana);
}
 
int Fight (player *Attacker, player *Target) {
    int EffectiveAttack; // How much damage we can deal is the difference between the attack of the attacker
                         // And the defense of the target. In this case 5 - 1 = 4 = EffectiveAttack.
 
    EffectiveAttack = Attacker->attack - Target->defense;
    Target->health = Target->health - EffectiveAttack;
    return(0);
}

Compiling and running this version of the program gives us the desired outputː

Name: Sir Jenkins
Health: 70
Mana: 20
Name: Sir Jenkins
Health: 66
Mana: 20

Now that we've figured out how to deal damage, lets expand on our earlier specificationː

Fight() shouldː

  • Loop until someones' health reaches zero or surrenders.
  • Display a menu of options to the user before getting input.
    • Attack, Defend, Use Item, and Run should be basic choices.
  • Tell us if we gave the wrong input.
  • Give both sides a chance to turn. Possibly by swapping the memory address of the Attacker and Target.
    • This means that we'll need to distinguish between the User's player and non-user players.
    • We may need to revise the swapping idea later if fights involve more than two characters. Then we might use some kind of rotation.
      • Games that use speed as a factor, probably build lists based on stats before the fight.

Let's incrementally add functionality. I'll refrain from posting the whole program when possible but I encourage you to continue making changes and compiling as we go along. For the battle sequence, we wrap the part of the Fight function we need to repeat and have it loop until the Target's health reaches 0, whereupon a winner is named. The user needs to know how to interact with our game and the fight menu easily accomplishes this task by pairing a number with an action. It is our responsibility to modify this menu as new actions are added.

The User selects an action based on the menu and the number they chose is handed to a Switch, which compares a given variable to a series of Cases. Each case has a number or character (strings aren't allowed) that is used for the aforementioned comparison. If Switch finds a match, it evaluates all the statements in that Case. We must use the keyword break to tell the switch to stop evaluating commands, or else it will move the to next case and execute those statements (sometimes that's useful, but not for our purpose). If Switch cannot match a variable to a case, then it looks for a special case called default and evalutes it instead. We'll always want to have a default present to handle unexpected input.

int Fight(player *Attacker, player *Target) {
    int EffectiveAttack = Attacker->attack - Target->defense;
 
    while (Target->health > 0) {
        DisplayFightMenu();
 
        // Get input.
        int choice;
        printf(">> "); // Indication the user should type something.
        fgets(line, sizeof(line), stdin);
        sscanf(line, "%d", &choice);
 
        switch (choice) {
        case 1:
            Target->health = Target->health - EffectiveAttack;
            printf("%s inflicted %d damage to %s.\n", Attacker->name, EffectiveAttack, Target->name);
            DisplayStats(*Target); // Deferenced pointer passes value. No traffic direction.
            break;
        case 2:
            printf("Running away!\n");
            return(0);
        default:
            printf("Bad input. Try again.\n");
            break;
        }
    }
 
   // Victoryǃ
   if (Target->health <= 0) {
     printf("%s has bested %s in combat.\n", Attacker->name, Target->name) ;
   }
 
      return(0);
}
 
void DisplayFightMenu () {
printf("1) Attack\n2) Run\n");
}

Testing the integrity of the program requires running it a few times after compilation. First we can see that if we enter random input like "123" or "Fish" we invoke the default case and are forced to pick another answer. Second, entering 2 will cause us to run away from the fight. Third, if we continue to enter 1, eventually Sir Leeroy will widdle down all of Sir Jenkin's health and be declared winner. Modifying the attack value on Sir Leeroy can help if you're impatient ː)

However, Sir Jenkins is still unable to defend himself, which makes for a very unsporting match. Even if Sir Jenkins was given a turn, the user would still be prompted to act on his behalf. The turn-based problem is solved by the idea proposed in the specification, that we swap the memory addresses of the Attacker and Target pointers on each loop. The solution to the second problem is to add a new property to our player structure, namely, a bool. A bool has a binary value, true or false, and, for us, it answers the simple question "To Autopilot or not to autopilot?". With the autopilot bool set to true, the Fight function, and other functions like it, will know that they must automate the actions of these characters. To use bool datatypes, we need to include a new header called <stdbool.h>. Bools are declared with the bool keyword and can only be assigned true or false values.

Consider the following program. We've added our bools and IF statements to determine whether the player needs automating or prompting. The victory if is moved inside the while loop, and declares victory if the condition is met, else, it swaps players for the next loop.

player5.c

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
 
typedef struct playerStructure {
   char name[50];
   int health;
   int mana;
   int attack;
   int defense;
   bool autoPilot;
} player;
 
// Function Prototype
void DisplayStats(player Target);
int Fight(player *Attacker, player *Target);
void DisplayFightMenu();
 
// Global Variables
char line[50];	  // This will contain our input.
 
int main () {
  player Hero, Villain;
  // Hero
  strcpy(Hero.name, "Sir Leeroy");
  Hero.health = 60;
  Hero.mana = 30;
  Hero.attack = 5;
  Hero.defense = 1;
  Hero.autoPilot = false;
 
  // Villain
  strcpy(Villain.name, "Sir Jenkins");
  Villain.health = 70;
  Villain.mana = 20;
  Villain.attack = 5;
  Villain.defense = 1;
  Villain.autoPilot = true;
 
  DisplayStats(Villain);
  Fight(&Hero, &Villain);
  DisplayStats(Villain);
 
  return(0);
}
 
void DisplayStats(player Target) {
   printf("Name: %s\nHealth: %d\nMana: %d\n", Target.name, Target.health, Target.mana);
}
 
int Fight(player *Attacker, player *Target) {
   int EffectiveAttack = Attacker->attack - Target->defense;
 
   while (Target->health > 0) {
      if (Attacker->autoPilot == false) {
	 DisplayFightMenu();
	 // Get input.
	 int choice;
	 printf(">> "); // Indication the user should type something.
	 fgets(line, sizeof(line), stdin);
	 sscanf(line, "%d", &choice);
 
	 switch (choice) {
	   case 1:
	     Target->health = Target->health - EffectiveAttack;
	     printf("%s inflicted %d damage to %s.\n", Attacker->name, EffectiveAttack, Target->name);
	     DisplayStats(*Target); // Deferenced pointer passes value. No traffic direction.
	     break;
	   case 2:
	     printf("Running away!\n");
	     return(0);
	   default:
	     printf("Bad input. Try again.\n");
	     break;
	 }
      } else {
	 Target->health = Target->health - EffectiveAttack;
	 printf("%s inflicted %d damage to %s.\n", Attacker->name, EffectiveAttack, Target->name);
	 DisplayStats(*Target);
      }
 
      if (Target->health <= 0) {
	printf("%s has bested %s in combat.\n", Attacker->name, Target->name) ;
      } else {
	 // Swap attacker and target.
	 player *tmp = Attacker;
	 Attacker = Target;
	 Target = tmp;
      }
   }
 
   return(0);
}
 
void DisplayFightMenu () {
  printf("1) Attack\n2) Run\n");
}

If you've left the stats of Sir Leeroy and Jenkins unmodified, you'd find that Sir Jenkins always comes out on top because he has the most Health and Leeroy cannot do enough damage to compensate for that. We'll solve this problem in the next section Inventory.

Inventory System[edit]

TODOː Introduction.

An Inventory Shouldː

  • Contain a list of items.
  • We should be able to ADD and REMOVE items from the list, as well as FIND items.
  • Similar items should be aggregated. When a similar item is added, quantity is increased, not list size.


TODOː Breakup program into smaller pieces for instruction.

NOTEː Putting the program here ahead of time because I don't have a backup anywhere other than my computer and someone else might suggest an improved way of doing things. ː)

inventoryFinished.c

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
 
// 0 based.
enum itemNumber {
   HEALTH_POTION,
   MANA_POTION
};
 
typedef struct ItemStructure {
   char name[50];
   char description [100];
   int health;
   int mana;
   int quantity;
   int usesLeft;
   int id;
} item;
 
// Doubly linked list for items.
typedef struct itemNodeStructure {
   item *current;	 // Pointer to current item.
   struct itemNodeStructure *previous;   // Pointer to previous item in the list.
   struct itemNodeStructure *next;	 // Pointer to the next item in the list.
} itemNode;
 
typedef struct playerStructure {
   char name[50];
   int health;
   int mana;
   int attack;
   int defense;
   bool autoPilot;
   itemNode *inventory;
} player;
 
// Function Prototype
void DisplayStats(player *target);
int DisplayInventory(itemNode *node);
int AddItem(itemNode *inventory, enum itemNumber number);
int RemoveItem(itemNode *inventory, enum itemNumber number);
itemNode* findItem(itemNode *node, enum itemNumber number);
 
// MAIN
int main () {
 
  player *Hero = malloc(sizeof (player));
 
  // Hero
  strcpy(Hero->name, "Sir Leeroy");
  Hero->health = 60;
  Hero->mana = 30;
  Hero->attack = 5;
  Hero->defense = 1;
  Hero->autoPilot = false;
  Hero->inventory = malloc(sizeof(itemNode)); // It's necessary to initialize the inventory property with a
                                              // memory address. That way we can pass the address instead of the pointer address.
					      // Then our function would need to accept a pointer to a pointer to an itemNode
					      // as an argument and that's too much overhead.
 
  AddItem(Hero->inventory, HEALTH_POTION); 
  AddItem(Hero->inventory, HEALTH_POTION); 
  AddItem(Hero->inventory, MANA_POTION);
  RemoveItem(Hero->inventory, MANA_POTION);
  DisplayInventory(Hero->inventory);
 
  return(0);
}
 
int DisplayInventory(itemNode *node) {
  // While there is an item present, print said item.
  while (node->current != NULL)  {
    printf("Name: %s\n", node->current->name);
    printf("Description: %s\n", node->current->description);
    printf("Health: %d\n", node->current->health);
    printf("Mana: %d\n", node->current->mana);
    printf("Uses Left: %d\n", node->current->usesLeft);
    printf("Quantity: %d\n\n", node->current->quantity);
 
    // If there is another item in the list, go to it, else, stop.
    if (node->next != NULL) {
      node = node->next;
    } else {
      return(0);
    }
  }
  printf("Inventory is empty.\n");
  return(0);
}
 
// FIND ITEM
// Can't return 0 because it's interpreted as an int. Return NULL for functions
// that are supposed to return pointers.
itemNode* findItem (itemNode *node, enum itemNumber number) {
  if (node == NULL) {
    return(NULL);
  }
 
  // Avoid unitialized or unassigned nodes.
  while (node->current != NULL) {
     if (node->current->id == number) {
       return(node);
     } 
 
     if (node->next != NULL) {
       node = node->next;
     } else {
       return(NULL);
     }
  }
  return(NULL);
}
 
int AddItem(itemNode *node, enum itemNumber number) {
  itemNode *previousNode;
  itemNode *searchResult;
 
  // See if item already exists.
  searchResult = findItem(node, number);
  if (searchResult != NULL) {
    searchResult->current->quantity += 1;
    return(0);
  }
 
  // Generate item if it doesn't exist.
  item *object = malloc(sizeof(item)); // Item.
  switch(number) {
  case 0:
    strcpy(object->name, "Health Potion");
    strcpy(object->description, "Drinkable item that heals 20 health points.");
    object->health = 20;
    object->usesLeft = 1;
    object->quantity = 1;
    object->id = number;
    break;
  case 1:
    strcpy(object->name, "Mana Potion");
    strcpy(object->description, "Drinkable item that heals 20 mana.");
    object->usesLeft = 1;
    object->quantity = 1;
    object->mana = 20;
    object->id = number;
    break;
  }
 
  // If node is unused allocate memory and assign item.
  if (node->current == NULL) {
    node->current = object;
  // If node is occupied, check next node.
  } else if (node->next == NULL) {
    node->next = malloc(sizeof(itemNode));
    previousNode = node;
    node = node->next;
    node->previous = previousNode;
    node->current = object;
  // If current and next node are occupied, search for the last node.
  // The last node will have an empty "next" spot.
  } else {
    while (node->next != NULL) {
      node = node->next;
    }
 
    node->next = malloc(sizeof(itemNode));
    previousNode = node;
    node = node->next;
    node->previous = previousNode;
    node->current = object;
  }
  return(0);
}
 
int RemoveItem(itemNode *node, enum itemNumber number) {
  itemNode *searchResult;
  itemNode *previous;
  itemNode *next;
 
  // See if item already exists.
  searchResult = findItem(node, number);
 
  // If item exists, and reduce quantity by 1.
  if (searchResult != NULL) {
    searchResult->current->quantity -= 1;
 
    // If reduction results in 0 quantity, remove item entirely.
    if (searchResult->current->quantity <= 0) {
      previous = searchResult->previous;
      next = searchResult->next;
 
      // Free the item and then the node containing it.
      free(searchResult->current);
      free(searchResult);
 
      // Switch linked list together.
      // We can't assign the next/previous members if the itemNode is null.
      if (previous != NULL) {
        searchResult = previous;
        searchResult->next = next;
      }
      if (next != NULL) {
        searchResult = next;
	searchResult->previous = previous;
      }
    }
  }  
  return(0);
}