使用游戏概念学习 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);
}