< Learning C With Game Concepts

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

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. In a commercial environment, this brainstorming session produces a Specification Document.

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

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ː


DeclarationWhat it means.
char *variablePointer to char
int *variablePointer to int
float *variablePointer to float
player *variablePointer to player
player **variablePointer 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;
    tempPlayer->class = WARRIOR;
    break;
  case RANGER:
    tempPlayer->health = 35;
    tempPlayer->mana = 0;
    tempPlayer->class = RANGER;
    break;
  case MAGE:
    tempPlayer->health = 20;
    tempPlayer->mana = 60;
    tempPlayer->class = MAGE;
    break;
  case ACCOUNTANT:
    tempPlayer->health = 100;
    tempPlayer->mana = 100;
    tempPlayer->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

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 file, 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;
        tempPlayer->class = WARRIOR;
        break;
    case RANGER:
        tempPlayer->health = 35;
        tempPlayer->mana = 0;
        tempPlayer->attack = 3;
        tempPlayer->defense = 2;
        tempPlayer->class = RANGER;
        break;
    case MAGE:
        tempPlayer->health = 20;
        tempPlayer->mana = 60;
        tempPlayer->attack = 5;
        tempPlayer->defense = 0;
        tempPlayer->class = MAGE;
        break;
    case ACCOUNTANT:
        tempPlayer->health = 100;
        tempPlayer->mana = 100;
        tempPlayer->attack = 5;
        tempPlayer->defense = 5;
        tempPlayer->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 folder 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 "gameProperties.h"
#include "players.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ː

Name: Sir Jenkins
Health: 35
Mana: 0
Name: Sir Jenkins
Health: 34         // An impressive 1 damage dealt. 
Mana: 0

TODOː Adjust class stats to something more diverse.

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

Fight() shouldː

  • Loop until someones' health reaches zero, retreats, 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 act. 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 list rotation.
      • Games that use speed as a factor (Agility), probably build lists based on stats before the fight to determine who goes first.

I'll refrain from posting the whole program when possible but I encourage you to continue making incremental changes and compiling/running the main program as we go along. For the battle sequence, we will modify the Fight function to loop until the Target's health reaches 0, whereupon a winner is named. A user interface will be provided by a "Fight Menu" which will pair a number with an action. It is our responsibility to modify this menu as new actions are added and make sure each individual action works when called.

When the User chooses an action, the associated number 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);
            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 problem of autonomy 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, when modified by us to check for 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.

Add the following line underneath int defense in your playerStructure from the "gameProperties.h".

 bool autoPilot;

Next, add this snippet of code to the NewPlayer function from the "Players.h" below the call to SetName.

static int PlayersCreated = 0; // Keep track of players created.
if (PlayersCreated > 0) {
    tempPlayer->autoPilot = true;
} else {
    tempPlayer->autoPilot = false;
}
++PlayersCreated;

The above code creates a persistent variable using the keyword static. Normally, once a function is called, the local variables disappear. By contrast, static variables maintain their value beyond the life of the function, and when a function starts again, it's value isn't reset. Autopilot is only turned on for players created after the first, main character.

That done, 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.

player4.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "gameProperties.h"
#include "players.h"

// Function Prototype
void DisplayStats(player Target);
int Fight(player *Attacker, player *Target);
void DisplayFightMenu(void);

// Global Variables
char line[50];	  // This will contain our input.

int main () {
    player *Hero = NewPlayer(WARRIOR, "Sir Leeroy");
    player *Villain = NewPlayer(RANGER, "Sir Jenkins");
 
    DisplayStats(Villain);   // Before the fight.
    Fight(Hero, Villain);    // FIGHTǃ

  return(0);
}

int Fight(player *Attacker, player *Target) {
   int EffectiveAttack = Attacker->attack - Target->defense;

   while (Target->health > 0) {

      // Get user input if autopilot is set to false.
      if (Attacker->autoPilot == false) {
	 DisplayFightMenu();

	 int choice;
	 printf(">> "); // Sharp brackets indicate that 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);
	     break;
	   case 2:
	     printf("Running away!\n");
	     return(0);
	   default:
	     printf("Bad input. Try again.\n");
	     break;
	 }
      } else {
         // Autopilot. Userless player acts independently.
	 Target->health = Target->health - EffectiveAttack;
	 printf("%s inflicted %d damage to %s.\n", Attacker->name, EffectiveAttack, Target->name);
	 DisplayStats(Target);
      }

      // Once turn is finished, check to see if someone has one, otherwise, swap and continue.
      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 (void) {
  printf("1) Attack\n2) Run\n");
}

Now that we've created a very rudimentary system for fighting, it is time once again to modularize. Take the Fight and DisplayFightMenu functions and put them in a new header file called "fightSys.h". This new header will contain all functions related to fighting and will be included in the next iteration of our Main program.

Inventory System

There is nothing quite so satisfying as equipping one's character. Most of the time, you start out at level 1, wearing rags and shackles. After being released from the local dungeon for one reason or another, you meet a Haberdasher with a heart of gold. He gives you some odd jobs so you can earn enough copper to eat. After weeks of punching things, fetching things, running from things, and still without two copper pieces to scratch together, you at last come across the "Unremarkable Sword of Rusty Might". Huzzahǃ It nothing compared to what the City Guard is using, but now those sewer rats shall feel your wrath. So let's get to itǃ

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.
  • Players should have their own inventory, as should chests and other containers.

An Item Shouldː

  • Have a name and description.
  • Health and mana.
    • These are the restorative properties of potions. 20 Health heals 20 health points if consumed.
  • Similar items should be aggregated. When a similar item is added, quantity is increased, not list size.
  • Have the option of multiple uses.
  • Have an ID that uniquely identifies it.

Putting the above specification into practice, we start by defining the kinds of items available, via an enumeration. In the future, we may want to import a list of items from a file, rather than keeping all of them with our source code. However, it's best to keep things small for testing purposes.

// 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;


Each item that we create will be embedded in an item node. A node is a single unit which can be linked to other nodes in order to form a data structures such as lists and trees. In this case, the data structure is a doubly linked list. As you can see, the itemNodeStructure points forwards, backwards, and to its own item. When defining the itemNodeStructure, it is necessary to use "struct itemNodeStructure" to declare the pointers because the itemNode typedef is not yet in effect and the compiler will not understand.


Going back to ye olde playerStructure datatype, we add a new value to it.

 itemNode *inventory;

Because the functions required for a basic inventory setup are somewhat involved, it cannot be broken up into more digestible pieces. My code is very likely not the most elegant solution to the problem, so there is room for improving the clarity. For now, if you want to understand how the functions for the inventory work, you'll need to read through the comments until you can identify what each piece of code is doing.


inventory.c

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include "gameProperties.h"
#include "players.h"
#include "fightSys.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;

// Function Prototypes
int DisplayInventory(itemNode *node);
int AddItem(itemNode *inventory, enum itemNumber number);

int main () {
  player *Hero = NewPlayer(WARRIOR, "Sir Leeroy");
  player *Villain = NewPlayer(WARRIOR, "Sir Jenkins");

  AddItem(Hero->inventory, HEALTH_POTION); 
  AddItem(Hero->inventory, HEALTH_POTION); 
  AddItem(Hero->inventory, MANA_POTION);
  DisplayInventory(Hero->inventory);  

  return(0);
}

// Exampleː DisplayInventory(Player->inventory);
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 the node points to another node, go to it and print it's item. Otherwise, end loop.
    if (node->next != NULL) {
      node = node->next;   // Move to next node.
    } else {
      return(0);           // Loop ends
    }
  }
  // Inventory pointer is NULL, there are no items.
  printf("Inventory is empty.\n");
  return(0);
}

// FIND ITEM
// Used in the functionsː AddItem and RemoveItem.
// 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 the node is NULL, it's an empty list. End function.
  if (node == NULL) {
    return(NULL);
  }

  // While the current node has an item.
  while (node->current != NULL) {
     
     // Compare that item's id to our number.
     // If it is a match, return the memory address of that node.
     if (node->current->id == number) {
       return(node);
     } 

     // If the current item doesn't match and there
     // is another node to look examine, move to it.
     if (node->next != NULL) {
       node = node->next;
     } else {
       return(NULL);   // List ends.
     }
  }
  return(NULL);  // List is empty.
}

// Use Exampleː AddItem(Hero->inventory, HEALTH_POTION);
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;    // Increase quantity by one and end function.
    return(0);
  }
  
  // Generate item if it doesn't exist.
  // This requires allocating memory and increasing
  // the size of the linked list.
  item *object = malloc(sizeof(item));          // Allocating memory for item.

  // Just like our class enumeration, our item names are variables
  // that stand for numbers. Because cases in C can't use strings,
  // this method makes them much more readable.
  switch(number) {
  case HEALTH_POTION:
    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;          // ID and ItemNumber are the same.
    break;
  case MANA_POTION:
    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;
  }

  // Now that our object has been created, we must find free space for it.

  // If the current node is unused allocate memory and assign item.
  if (node->current == NULL) {
    node->current = object;

  // If the current node is occupied, check the next node.
  // If the next node doesn't exist, then we must allocate memory
  // to the next pointer.  
  } else if (node->next == NULL) {
    node->next = malloc(sizeof(itemNode));        // Allocate memory to the next pointer.
    previousNode = node;                          // Store location of current node.
    node = node->next;                            // Move to the next node.
    node->previous = previousNode;                // Link the current node to the previous one.
    node->current = object;                       // Assign item to the current node.
  } else {
    // If current and next node are occupied, search for the last node.
    // The last node will have an empty "next" spot.
    while (node->next != NULL) {
      node = node->next;
    }

    node->next = malloc(sizeof(itemNode));        // Allocate memory to the next pointer.
    previousNode = node;                          // Store location of current node.
    node = node->next;                            // Move to the next node.
    node->previous = previousNode;                // Link the current node to the previous one.
    node->current = object;                       // Assign item to the current node.
  }
  return(0);
}




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);
}
This article is issued from Wikibooks. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.