跳转到内容

使用游戏概念学习 C 语言/物品系统

来自维基教科书,开放的书籍,开放的世界

物品系统

[编辑 | 编辑源代码]

没有什么比装备自己的角色更令人满意了。大多数时候,你从 1 级开始,穿着破布和镣铐。从当地地牢被释放出来后,你遇到了一个杂货商,他有一颗金子般的心。他给你一些奇怪的工作,这样你就可以赚到足够的铜币吃饭。经过几周的拳打脚踢、取物、逃跑,仍然没有两个铜币可以凑在一起,你终于遇到了“毫无特色的锈迹斑斑的宝剑”。万岁!它与城市守卫使用的相比简直是微不足道,但现在那些下水道老鼠将感受到你的怒火。让我们开始吧!

物品清单应该:

  • 包含物品列表。
  • 我们应该能够从列表中添加和删除物品,以及查找物品。
  • 类似的物品应该被聚合。当添加类似的物品时,数量增加,而不是列表大小。
  • 玩家应该有自己的物品清单,箱子和其他容器也应该有。

物品应该:

  • 有名称和描述。
  • 生命值和法力值。
    • 这些是药水的恢复属性。20 生命值在消耗后恢复 20 生命值。
  • 类似的物品应该被聚合。当添加类似的物品时,数量增加,而不是列表大小。
  • 有多种用途。
  • 有一个唯一标识它的 ID。

将以上规范付诸实践,我们首先通过枚举来定义可用物品的种类。将来,我们可能希望从文件中导入物品列表,而不是将所有物品与源代码一起保存。但是,为了测试目的,最好保持代码简洁。

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


我们创建的每个物品都将嵌入到一个物品节点中。节点是一个单独的单元,可以与其他节点链接以形成数据结构,例如列表和树。在本例中,数据结构是双向链表。如你所见,itemNodeStructure 指向前、后以及它自己的物品。在定义 itemNodeStructure 时,需要使用“struct itemNodeStructure”来声明指针,因为 itemNode typedef 尚未生效,编译器将无法理解。


回到旧的 playerStructure 数据类型,我们向其中添加了一个新值。

 itemNode *inventory;

由于基本物品设置所需的函数有些复杂,无法将其分解成更易于理解的部分。我的代码很可能不是解决这个问题的最优雅的方案,因此还有改进清晰度的空间。现在,如果你想了解物品的函数是如何工作的,你需要阅读注释,直到你能识别出每段代码的作用。


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);
}
华夏公益教科书