跳轉到內容

使用遊戲概念學習 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);
}
華夏公益教科書