使用遊戲概念學習 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);
}