1. 项目背景与设计思路
作为一个资深程序员,我最近接手了一个影院管理系统的课程设计项目。但说实话,传统的影院管理系统实在太无聊了——无非就是电影排期、座位管理、票务系统这些老套的功能。于是我决定给它来个"铠甲勇士"主题的魔改,把枯燥的影院管理变成酷炫的铠甲勇士管理系统。
这个系统的核心思路是将传统影院管理系统的各个模块进行趣味化改造:
- 电影 → 铠甲
- 影厅 → 战斗场景
- 座位 → 怪兽
- 用户 → 召唤人
- 管理员 → 铠甲指挥官
通过这种映射关系,原本枯燥的增删改查操作就变成了铠甲勇士世界观下的各种有趣操作。比如添加电影变成了添加新铠甲,修改座位状态变成了修改怪兽封印状态等等。
2. 系统架构与数据结构设计
2.1 核心数据结构
系统主要使用了链表来管理各种数据,这是C语言项目中最常用的动态数据结构。我们定义了三种主要的结构体:
c复制// 铠甲(电影)结构体
typedef struct movie {
int id; // 铠甲ID
char title[50]; // 铠甲名称
float price; // 授权能量值
char type[20]; // 铠甲类型(全能型/爆发型等)
MonsterAttr attr; // 铠甲属性(火/水/雷等)
ArmorStatus status; // 铠甲状态(闲置/战斗中/维修中)
struct movie* next; // 下一个节点指针
} movie;
// 怪兽(座位)结构体
typedef struct monster {
int id; // 怪兽ID
char name[50]; // 怪兽名称
int level; // 怪兽等级
MonsterAttr attr; // 怪兽属性
int danger; // 危险等级(1-10)
int isDefeated; // 是否被封印
MonsterScene scene; // 出没地点(场景)
struct monster* next; // 下一个节点指针
} monster;
// 召唤人(用户)结构体
typedef struct user {
int id; // 用户ID
char name[50]; // 用户名
int isAdmin; // 是否是管理员
struct user* next; // 下一个节点指针
} user;
2.2 枚举类型定义
为了增加代码可读性,我们定义了几个枚举类型:
c复制// 铠甲/怪兽属性枚举
typedef enum {
ATTR_FIRE = 0, // 火属性
ATTR_WATER, // 水属性
ATTR_THUNDER, // 雷属性
ATTR_EARTH, // 土属性
ATTR_WIND // 风属性
} MonsterAttr;
// 铠甲状态枚举
typedef enum {
ARMOR_IDLE = 0, // 闲置
ARMOR_IN_BATTLE, // 战斗中
ARMOR_REPAIRING, // 维修中
ARMOR_DAMAGED // 破损
} ArmorStatus;
// 怪兽出没场景枚举
typedef enum {
SCENE_KAI1_CITY = 0, // 铠一-城市市区
SCENE_KAI1_MOUNTAIN, // 铠一-郊外山林
SCENE_KAI1_OCEAN, // 铠一-沿海区域
SCENE_KAI1_ANCIENT, // 铠一-远古遗迹
SCENE_KAI2_HOPE_CITY, // 铠二-希望市
SCENE_KAI2_ARES, // 铠二-阿瑞斯星球
SCENE_KAI2_OTHER // 铠二-其他区域
} MonsterScene;
3. 核心功能实现详解
3.1 铠甲(电影)管理功能
3.1.1 添加新铠甲
c复制void addMovie() {
clearScreen();
movie* newmovie = (movie*)malloc(sizeof(movie));
if (newmovie == NULL) {
printf("分配内存失败\n");
getchar(); getchar();
clearScreen();
return;
}
// 自动生成ID(当前最大ID+1)
int maxid = 0;
movie* p = movieList;
while (p != NULL) {
if (p->id > maxid) maxid = p->id;
p = p->next;
}
newmovie->id = maxid + 1;
// 输入铠甲信息
printf("添加铠甲,请输入新铠甲的名字\n");
char name[50];
scanf("%s", name);
strcpy(newmovie->title, name);
printf("添加铠甲,请输入新铠甲的授权能量值\n");
float money;
scanf("%f", &money);
newmovie->price = money;
printf("请输入铠甲类型(全能型/爆发型/速度型/防御型):");
scanf("%s", newmovie->type);
printf("请输入铠甲属性(1=火属性 2=水属性 3=雷属性 4=土属性 5=风属性):");
int attr;
scanf("%d", &attr);
newmovie->attr = (MonsterAttr)(attr - 1);
// 设置默认状态为闲置
newmovie->status = ARMOR_IDLE;
// 头插法插入链表
newmovie->next = movieList;
movieList = newmovie;
printf("铠甲添加成功\n");
save_to_file(); // 保存到文件
printf("按任意键继续。。。。。\n");
getchar(); getchar();
clearScreen();
}
提示:使用头插法可以保证插入操作的时间复杂度为O(1),但会导致铠甲列表的显示顺序与添加顺序相反。如果需要按添加顺序显示,应该改用尾插法。
3.1.2 删除铠甲
c复制void deleteMovie() {
clearScreen();
int id;
printf("请输入要销毁的铠甲id\n");
scanf("%d", &id);
movie* cur = movieList;
movie* pre = NULL;
while (cur != NULL) {
if (cur->id == id) {
if (pre == NULL) {
// 要删除的是头节点
movieList = movieList->next;
} else {
// 要删除的是中间或尾部节点
pre->next = cur->next;
}
free(cur); // 释放内存
save_to_file();
printf("铠甲销毁成功,按任意键继续。。。。。\n");
getchar(); getchar();
clearScreen();
return;
}
pre = cur;
cur = cur->next;
}
printf("未找到id为%d的铠甲\n", id);
printf("按任意键继续\n");
getchar(); getchar();
clearScreen();
}
注意:删除节点后一定要记得释放内存,否则会造成内存泄漏。同时要处理好头节点的特殊情况。
3.1.3 修改铠甲信息
c复制void updateMovie() {
clearScreen();
printf("===== 可修复的铠甲列表 =====\n");
movie* p = movieList;
int hasRepairable = 0;
printf("铠甲ID\t名称\t\t当前状态\n");
while (p != NULL) {
if (p->status != ARMOR_IDLE) {
hasRepairable = 1;
printf("%d\t%s\t\t%s\n",
p->id,
p->title,
p->status == ARMOR_IN_BATTLE ? "战斗中" :
(p->status == ARMOR_REPAIRING ? "维修中" : "破损"));
}
p = p->next;
}
if (!hasRepairable) {
printf("暂无需要修复的铠甲(所有铠甲均为闲置状态)\n");
printf("按任意键继续...\n");
getchar(); getchar();
clearScreen();
return;
}
int id;
printf("\n请输入要调校的铠甲ID: ");
scanf("%d", &id);
p = movieList;
while (p != NULL) {
if (p->id == id) {
// 显示当前信息
printf("当前铠甲名: %s\n", p->title);
printf("当前授权能量: %.2f\n", p->price);
printf("当前铠甲类型: %s\n", p->type);
printf("当前铠甲属性: %s\n",
p->attr == ATTR_FIRE ? "火属性" :
(p->attr == ATTR_WATER ? "水属性" :
(p->attr == ATTR_THUNDER ? "雷属性" :
(p->attr == ATTR_EARTH ? "土属性" : "风属性"))));
printf("当前铠甲状态: %s\n",
p->status == ARMOR_IDLE ? "闲置" :
(p->status == ARMOR_IN_BATTLE ? "战斗中" :
(p->status == ARMOR_REPAIRING ? "维修中" : "破损")));
// 输入新信息
printf("请输入新铠甲名: ");
scanf("%s", p->title);
printf("请输入新授权能量: ");
scanf("%f", &p->price);
printf("请输入新铠甲类型: ");
scanf("%s", p->type);
printf("请输入新铠甲属性(1-5): ");
int attr;
scanf("%d", &attr);
p->attr = (MonsterAttr)(attr - 1);
printf("请输入新铠甲状态(1=闲置 2=战斗中 3=维修中 4=破损): ");
int status;
scanf("%d", &status);
p->status = (ArmorStatus)(status - 1);
printf("铠甲调校成功!\n");
save_to_file();
printf("按任意键继续...");
getchar(); getchar();
clearScreen();
return;
}
p = p->next;
}
printf("未找到ID为%d的铠甲\n", id);
printf("按任意键继续...\n");
getchar(); getchar();
clearScreen();
}
3.2 召唤人(用户)管理功能
3.2.1 查看所有召唤人
c复制void view_all_armor() {
clearScreen();
user* p = head;
if (p == NULL) {
printf("暂无召唤人\n");
printf("按任意键继续。。。。\n");
getchar(); getchar();
clearScreen();
return;
}
printf("召唤人ID\t召唤人名称\t身份\n");
while (p != NULL) {
printf("%d\t%s\t%s\n",
p->id,
p->name,
p->isAdmin == 1 ? "管理员" : "普通用户");
p = p->next;
}
printf("按任意键继续。。。。。\n");
getchar(); getchar();
clearScreen();
}
3.3 怪兽(座位)管理功能
3.3.1 浏览怪兽信息
c复制void browseMonster() {
clearScreen();
monster* p = monsterList;
if (p == NULL) {
printf("暂无怪兽\n");
printf("按任意键继续。。。。\n");
getchar(); getchar();
clearScreen();
return;
}
printf("怪兽ID\t怪兽名称\t等级\t属性\t危险等级\t封印状态\t出没地点\n");
while (p != NULL) {
printf("%d\t%s\t%d\t%s\t%d\t%s\t%s\n",
p->id,
p->name,
p->level,
p->attr == ATTR_FIRE ? "火属性" :
(p->attr == ATTR_WATER ? "水属性" :
(p->attr == ATTR_THUNDER ? "雷属性" :
(p->attr == ATTR_EARTH ? "土属性" : "风属性"))),
p->danger,
p->isDefeated ? "已封印" : "未封印",
sceneToString(p->scene));
p = p->next;
}
printf("按任意键继续。。。。。\n");
getchar(); getchar();
clearScreen();
}
3.3.2 新增怪兽
c复制void addMonster() {
clearScreen();
monster* newMonster = (monster*)malloc(sizeof(monster));
if (newMonster == NULL) {
printf("内存分配失败!无法新增怪兽\n");
getchar(); getchar();
clearScreen();
return;
}
// 自动生成ID
int maxId = 0;
monster* p = monsterList;
while (p != NULL) {
if (p->id > maxId) maxId = p->id;
p = p->next;
}
newMonster->id = maxId + 1;
printf("\n===== 新增怪兽(新增座位)=====\n");
printf("请输入怪兽名称:");
scanf("%s", newMonster->name);
printf("请输入怪兽等级:");
scanf("%d", &newMonster->level);
printf("请输入怪兽属性(1=火 2=水 3=雷 4=土 5=风):");
int attr;
scanf("%d", &attr);
newMonster->attr = (MonsterAttr)(attr - 1);
printf("请输入怪兽危险等级(1-10):");
scanf("%d", &newMonster->danger);
printf("请输入怪兽封印状态(0=未封印 1=已封印):");
scanf("%d", &newMonster->isDefeated);
printf("请选择怪兽出没地点(场次):\n");
printf("1: 铠一-城市市区\n2: 铠一-郊外山林\n3: 铠一-沿海区域\n");
printf("4: 铠一-远古遗迹\n5: 铠二-希望市\n6: 铠二-阿瑞斯星球\n7: 铠二-其他区域\n");
int scene;
scanf("%d", &scene);
newMonster->scene = (MonsterScene)(scene - 1);
// 尾插法插入链表
newMonster->next = NULL;
if (monsterList == NULL) {
monsterList = newMonster;
} else {
p = monsterList;
while (p->next != NULL) p = p->next;
p->next = newMonster;
}
printf("怪兽新增成功!ID:%d\n", newMonster->id);
save_to_file();
printf("按任意键返回...\n");
getchar(); getchar();
clearScreen();
}
3.3.3 修改怪兽封印状态
c复制void updateMonsterStatus() {
clearScreen();
printf("\n===== 修改怪兽封印状态(修改座位)=====\n");
printf("请输入要修改的怪兽ID:");
int id;
scanf("%d", &id);
monster* p = monsterList;
while (p != NULL) {
if (p->id == id) {
printf("当前怪兽:%s | 封印状态:%s\n",
p->name, p->isDefeated ? "已封印" : "未封印");
printf("请输入新的封印状态(0=未封印 1=已封印):");
scanf("%d", &p->isDefeated);
printf("怪兽封印状态修改成功!\n");
save_to_file();
getchar(); getchar();
clearScreen();
return;
}
p = p->next;
}
printf("未找到ID为%d的怪兽!\n", id);
getchar(); getchar();
clearScreen();
}
3.3.4 按场景搜索怪兽
c复制void searchMonsterByScene() {
clearScreen();
int s;
printf("\n===== 选择出没地点(电影场次)=====\n");
printf("1: 铠一-城市市区\n2: 铠一-郊外山林\n3: 铠一-沿海区域\n");
printf("4: 铠一-远古遗迹\n5: 铠二-希望市\n6: 铠二-阿瑞斯星球\n7: 铠二-其他区域\n");
printf("请选择:");
scanf("%d", &s);
MonsterScene target;
switch (s) {
case 1: target = SCENE_KAI1_CITY; break;
case 2: target = SCENE_KAI1_MOUNTAIN; break;
case 3: target = SCENE_KAI1_OCEAN; break;
case 4: target = SCENE_KAI1_ANCIENT; break;
case 5: target = SCENE_KAI2_HOPE_CITY; break;
case 6: target = SCENE_KAI2_ARES; break;
case 7: target = SCENE_KAI2_OTHER; break;
default:
printf("输入错误!\n");
getchar(); getchar();
clearScreen();
return;
}
printf("\n===== 地点「%s」的怪兽 =====\n", sceneToString(target));
printf("ID\t名称\t\t等级\t属性\t\t是否封印\n");
printf("------------------------------------------------------------\n");
monster* p = monsterList;
int found = 0;
while (p) {
if (p->scene == target) {
found = 1;
printf("%d\t%-15s\t%d\t%s\t%s\n",
p->id,
p->name,
p->level,
attrToString(p->attr),
p->isDefeated ? "已封印" : "可封印");
}
p = p->next;
}
if (!found) printf("该地点暂无怪兽\n");
printf("按回车返回...\n");
getchar(); getchar();
clearScreen();
}
4. 项目经验与优化建议
4.1 开发过程中的经验总结
-
链表操作的注意事项:
- 头插法和尾插法各有优缺点,要根据实际需求选择
- 删除节点时一定要记得释放内存
- 遍历链表时要注意处理空指针情况
-
用户交互设计:
- 每个功能执行后都添加了
getchar(); getchar();来暂停程序,防止界面一闪而过 - 使用
clearScreen()函数清屏,保持界面整洁 - 输入错误时有相应的提示和重新输入的机会
- 每个功能执行后都添加了
-
数据持久化:
- 每次数据修改后都调用
save_to_file()保存到文件 - 程序启动时需要从文件加载数据
- 每次数据修改后都调用
4.2 可能的优化方向
-
性能优化:
- 当前查找操作都是线性搜索,时间复杂度为O(n),可以考虑使用哈希表优化
- 频繁的文件IO会影响性能,可以改为定时保存或退出时保存
-
功能扩展:
- 添加铠甲与怪兽的战斗功能
- 实现召唤人与铠甲的绑定关系
- 增加任务系统,让召唤人完成特定任务
-
界面美化:
- 使用ncurses库实现更漂亮的命令行界面
- 添加颜色和特殊符号使界面更生动
-
代码重构:
- 将重复的代码提取为函数,如输入验证、链表遍历等
- 使用更合理的数据结构,如将链表改为平衡二叉树
这个铠甲勇士主题的影院管理系统项目通过趣味化的方式实现了基本的数据管理功能,既完成了课程要求,又增加了编程的趣味性。在实际开发中,这种将枯燥项目趣味化的思路可以大大提高开发者的积极性和创造力。