C语言实现扔香蕉的大猩猩游戏 —— 项目介绍与实现详解
一、项目背景与简介
“扔香蕉的大猩猩”游戏最早出现在 QBasic 的经典游戏《Gorillas》中,两个大猩猩分别位于高楼之间,玩家通过调整投掷角度和力度,使香蕉按抛物线轨迹飞行,争取击中对方大猩猩。该游戏不仅考验玩家对物理知识(抛物线运动)的理解,同时也是编程入门中的经典案例。
本项目旨在使用 C 语言实现一个简化版的“扔香蕉的大猩猩”游戏,通过命令行进行交互,利用物理公式模拟香蕉的运动轨迹,并判断是否命中对手。项目不仅适合初学者熟悉 C 语言的基本语法、流程控制、函数调用以及数学计算,同时也展示了如何构建一个简单的游戏循环和图形(文本)显示效果。
该项目整体分为以下几部分:
- 游戏显示与交互: 利用简单的文本方式显示两个大猩猩在屏幕(模拟二维坐标)上的位置,同时提示玩家输入投掷角度和速度。
- 物理模拟: 根据物理公式计算抛体运动轨迹(即香蕉的运动轨迹),用时间步长进行离散化模拟,并在每一步检查是否碰撞目标。
- 碰撞检测: 当香蕉在飞行过程中,其位置与目标大猩猩的坐标距离在一定范围内(定义为“命中半径”)时,判定为命中目标,游戏结束。
- 回合制对战: 游戏采用双人对战的方式,轮流输入参数进行投掷,直至有一方击中对手为止。
通过这个项目,读者可以了解如何将数学模型与编程实现相结合,从而开发一个简单但完整的游戏应用。
二、项目实现思路
在实现“扔香蕉的大猩猩”游戏的过程中,主要需要解决以下几个问题:
-
坐标系与显示问题:
游戏采用二维坐标系,水平坐标用x
表示,竖直坐标用y
表示。为了简化,我们将屏幕(或游戏区域)定义为固定大小的矩阵,例如宽 80 个单位、高 25 个单位,并在矩阵中以不同字符标记两个大猩猩的位置。 -
物理模型的建立:
香蕉的运动遵循经典的抛物线运动规律,运动公式为:- 水平位移:
x = x0 + v * cos(θ) * t
- 垂直位移:
y = y0 + v * sin(θ) * t - 0.5 * g * t²
其中
x0, y0
为起始坐标,v
为初始速度,θ
为投掷角度(需转换为弧度计算),g
为重力加速度(此处取 9.8 m/s²),t
为时间。采用固定时间步长(如 0.1 秒)不断计算香蕉在每个时刻的坐标,模拟其飞行过程。 - 水平位移:
-
碰撞检测机制:
当香蕉飞行过程中,我们计算香蕉与目标大猩猩之间的距离。设定一个“命中半径”(例如 2 个单位),当距离小于等于此半径时,就认为香蕉击中了目标大猩猩。 -
游戏循环与交互:
游戏整体采用回合制,每个回合由当前玩家输入投掷角度和初始速度。程序根据输入参数计算香蕉轨迹并显示相关飞行信息。如果未命中目标,则切换玩家进入下一回合,直到有一方成功命中对手为止。 -
跨平台显示(清屏效果):
为了获得较好的显示效果,在每个回合开始时调用清屏函数(在 Windows 上调用cls
,在 Linux/Unix 上调用clear
)刷新命令行界面,显示最新的游戏状态。
通过以上思路,本项目将以模块化的设计实现各个功能模块。接下来给出完整代码,并在代码中加入详细注释,便于理解每一部分的具体实现。
三、项目完整代码
下面是整个项目的 C 语言代码,所有代码集中在一个文件中,包含详细注释:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
// 定义重力加速度常量
#define GRAVITY 9.8 // 单位:m/s²
// 定义模拟屏幕的尺寸(水平和垂直单位数,可根据需要调整)
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25
// 定义模拟时间步长(每步时间间隔)
#define TIME_STEP 0.1
// 定义一个结构体来表示大猩猩的位置,包含水平坐标 x 和垂直坐标 y
typedef struct {
double x;
double y;
} Gorilla;
// 定义一个常量表示碰撞检测的半径,当香蕉与目标距离小于等于此值时判定为命中
#define HIT_RADIUS 2.0
// ----------------------- 辅助函数区 -------------------------
/*
* 函数名称:clearScreen
* 函数作用:清空屏幕,用于刷新显示效果
* 实现原理:根据操作系统调用不同的命令
*/
void clearScreen() {
#ifdef _WIN32
system("cls"); // Windows 系统下使用 cls 命令清屏
#else
system("clear"); // Unix/Linux 系统下使用 clear 命令清屏
#endif
}
/*
* 函数名称:checkCollision
* 函数作用:检测香蕉是否与目标大猩猩发生碰撞
* 参数说明:
* bx, by:香蕉当前的 x, y 坐标
* target:目标大猩猩的坐标(结构体 Gorilla)
* 返回值:
* 1 表示碰撞成功(击中目标),0 表示未碰撞
* 实现原理:
* 计算香蕉与目标大猩猩之间的欧几里得距离,并与预定义的 HIT_RADIUS 比较
*/
int checkCollision(double bx, double by, Gorilla target) {
// 计算水平和垂直距离差
double dx = bx - target.x;
double dy = by - target.y;
// 计算两点之间的直线距离
double distance = sqrt(dx * dx + dy * dy);
if(distance <= HIT_RADIUS)
return 1; // 距离小于等于命中半径,判定为碰撞成功
else
return 0; // 否则未碰撞
}
/*
* 函数名称:simulateBanana
* 函数作用:模拟香蕉的飞行轨迹,并判断是否击中目标大猩猩
* 参数说明:
* initX, initY:香蕉发射的初始位置(通常在发射大猩猩的上方)
* angleDeg:投掷角度(单位:度)
* velocity:投掷初始速度
* target:目标大猩猩的坐标,用于碰撞检测
* 返回值:
* 1 表示模拟过程中击中目标,0 表示未击中目标
* 实现原理:
* 将角度转换为弧度后,计算水平和垂直方向的速度分量,使用时间步长离散化计算香蕉在各时刻的位置,
* 每步打印当前香蕉的位置,并检测是否与目标大猩猩发生碰撞。如果香蕉落地或飞出屏幕,则退出模拟。
*/
int simulateBanana(double initX, double initY, double angleDeg, double velocity, Gorilla target) {
// 将角度转换为弧度(C语言数学函数使用弧度)
double angleRad = angleDeg * M_PI / 180.0;
// 计算初始速度在水平和垂直方向上的分量
double vx = velocity * cos(angleRad);
double vy = velocity * sin(angleRad);
// 初始化香蕉当前位置和模拟时间
double x = initX;
double y = initY;
double t = 0;
// 模拟香蕉的飞行过程:只要香蕉在空中且未飞出水平范围,就持续模拟
while (y >= 0 && x >= 0 && x <= SCREEN_WIDTH) {
// 根据抛物线运动公式计算当前时间 t 对应的香蕉位置
x = initX + vx * t;
y = initY + vy * t - 0.5 * GRAVITY * t * t;
// 输出当前时间和香蕉的坐标,便于观察飞行轨迹
printf("时间: %.2f s, 香蕉位置: (%.2f, %.2f)\n", t, x, y);
// 调用碰撞检测函数,检查当前香蕉位置是否击中目标大猩猩
if (checkCollision(x, y, target)) {
printf("击中目标!\n");
return 1; // 返回 1 表示命中目标,结束模拟
}
// 增加时间步长,进入下一步计算
t += TIME_STEP;
}
// 如果循环结束,表示香蕉已经落地或飞出预设屏幕范围,未击中目标
printf("未击中目标,香蕉落地或飞出屏幕.\n");
return 0;
}
/*
* 函数名称:displayGorillas
* 函数作用:在文本模式下显示两个大猩猩在屏幕上的位置
* 参数说明:
* g1, g2:两个大猩猩的位置结构体
* 实现原理:
* 构建一个二维字符数组模拟屏幕,先将数组初始化为空格,
* 然后将大猩猩所在位置标记为不同的字符(如 '1' 和 '2'),最后打印整个屏幕内容。
*/
void displayGorillas(Gorilla g1, Gorilla g2) {
char screen[SCREEN_HEIGHT][SCREEN_WIDTH];
// 初始化屏幕数组,每个字符都为空格
for (int i = 0; i < SCREEN_HEIGHT; i++) {
for (int j = 0; j < SCREEN_WIDTH; j++) {
screen[i][j] = ' ';
}
}
// 将大猩猩的位置映射到屏幕坐标(注意:屏幕 y 坐标从上到下增加)
int g1x = (int)g1.x;
int g1y = SCREEN_HEIGHT - 1 - (int)g1.y; // 将数学坐标系转换为屏幕坐标系
int g2x = (int)g2.x;
int g2y = SCREEN_HEIGHT - 1 - (int)g2.y;
// 检查映射后的坐标是否在屏幕范围内,确保不会越界
if(g1x >= 0 && g1x < SCREEN_WIDTH && g1y >= 0 && g1y < SCREEN_HEIGHT)
screen[g1y][g1x] = '1'; // 用字符 '1' 表示大猩猩1
if(g2x >= 0 && g2x < SCREEN_WIDTH && g2y >= 0 && g2y < SCREEN_HEIGHT)
screen[g2y][g2x] = '2'; // 用字符 '2' 表示大猩猩2
// 打印整个屏幕,展示大猩猩在场景中的位置
for (int i = 0; i < SCREEN_HEIGHT; i++) {
for (int j = 0; j < SCREEN_WIDTH; j++) {
printf("%c", screen[i][j]);
}
printf("\n");
}
}
// ----------------------- 主函数区 -------------------------
/*
* 函数名称:main
* 函数作用:游戏的入口,组织整个游戏流程,包括初始化、玩家交互、投掷模拟及游戏结束判定
*/
int main() {
// 初始化随机数种子,用于后续若有随机元素(例如大猩猩位置随机生成)使用
srand((unsigned)time(NULL));
// 初始化两个大猩猩的位置,采用固定坐标,也可以改为随机分布
Gorilla gorilla1, gorilla2;
gorilla1.x = 10; // 大猩猩1放置在屏幕左侧,x 坐标设为 10
gorilla1.y = 0; // y 坐标 0 表示地面高度
gorilla2.x = SCREEN_WIDTH - 10; // 大猩猩2放置在屏幕右侧
gorilla2.y = 0;
// currentPlayer 用于记录当前回合投掷香蕉的玩家,1 表示大猩猩1发射,2 表示大猩猩2发射
int currentPlayer = 1;
// hit 变量标记是否有玩家成功击中对方,初始设为 0(未击中)
int hit = 0;
// 游戏主循环:只要没有一方命中对手,就不断进行回合交替
while (!hit) {
// 清屏刷新,确保每回合显示最新场景
clearScreen();
// 显示当前场景中两个大猩猩的位置
displayGorillas(gorilla1, gorilla2);
double angle, velocity;
// 根据当前玩家显示提示信息
if (currentPlayer == 1) {
printf("玩家1投掷香蕉 (目标:玩家2大猩猩).\n");
} else {
printf("玩家2投掷香蕉 (目标:玩家1大猩猩).\n");
}
// 获取玩家输入的投掷角度(单位:度)和初始速度
printf("请输入投掷角度(度数):");
scanf("%lf", &angle);
printf("请输入投掷初始速度:");
scanf("%lf", &velocity);
// 根据当前玩家确定香蕉的发射初始位置和目标大猩猩
double initX, initY;
Gorilla target;
if (currentPlayer == 1) {
initX = gorilla1.x;
initY = gorilla1.y + 1; // 使香蕉从大猩猩头部稍上方发射
target = gorilla2;
} else {
initX = gorilla2.x;
initY = gorilla2.y + 1;
target = gorilla1;
// 注意:如果需要对玩家2的投掷角度进行反转处理,可以在此处加入调整,本例中要求玩家自行输入合适的角度
}
// 调用 simulateBanana 函数,模拟香蕉的飞行轨迹,并检测是否命中目标
hit = simulateBanana(initX, initY, angle, velocity, target);
// 如果本回合未命中目标,则提示玩家按回车继续,并切换到下一玩家
if (!hit) {
printf("按回车键继续下一回合...\n");
getchar(); // 获取换行符,防止缓冲区干扰
getchar(); // 等待用户按下回车
// 切换玩家:如果当前玩家是 1,则下回合为 2;反之亦然
currentPlayer = (currentPlayer == 1) ? 2 : 1;
}
}
// 游戏结束后提示胜利信息
printf("游戏结束!\n");
return 0;
}
四、代码解读
下面对代码中各个模块进行详细解读,帮助读者更好地理解程序实现原理。
1. 头文件和常量定义
-
头文件部分:
程序中包含了<stdio.h>
、<stdlib.h>
、<math.h>
和<time.h>
等头文件。其中:<stdio.h>
用于标准输入输出;<stdlib.h>
用于系统命令(例如清屏命令)和随机数函数;<math.h>
提供数学函数,如cos()
、sin()
、sqrt()
和常量M_PI
;<time.h>
用于获取当前时间,以初始化随机数种子。
-
常量定义:
GRAVITY
定义为 9.8,模拟真实重力加速度;SCREEN_WIDTH
与SCREEN_HEIGHT
定义了模拟屏幕的尺寸,便于在命令行中以字符矩阵显示大猩猩的位置;TIME_STEP
为 0.1,用于离散化模拟时间;HIT_RADIUS
为 2.0,当香蕉与大猩猩之间距离小于等于此值时,判定为命中。
2. 结构体 Gorilla
该结构体包含两个 double
类型的成员 x
与 y
,分别表示大猩猩在水平与垂直方向上的坐标。使用结构体可以方便地传递和管理位置数据。
3. 辅助函数
-
clearScreen() 函数:
根据当前操作系统调用system("cls")
或system("clear")
实现清屏效果,使得每回合显示时界面干净、整洁。 -
checkCollision() 函数:
通过计算香蕉当前位置(bx, by)
与目标大猩猩位置之间的欧几里得距离,再与预定的HIT_RADIUS
比较,判断是否发生碰撞。该函数简单而高效,适用于本项目中离散化的碰撞检测。 -
simulateBanana() 函数:
这是本项目的核心函数,负责模拟香蕉的飞行轨迹:- 首先将玩家输入的角度转换为弧度,计算出初始速度在水平(
vx
)和垂直(vy
)方向的分量; - 以时间
t
为变量,从 0 开始,每次增加固定的时间步长TIME_STEP
,按照物理公式计算出香蕉的新坐标; - 每一步调用
checkCollision()
检测是否击中目标,如果是,则打印“击中目标”的信息并返回 1; - 当香蕉落地(
y < 0
)或飞出水平范围时,结束模拟并返回 0。
- 首先将玩家输入的角度转换为弧度,计算出初始速度在水平(
-
displayGorillas() 函数:
该函数创建一个二维字符数组,代表屏幕,将所有元素初始化为空格,然后将两个大猩猩分别标记为字符'1'
和'2'
。注意在将数学坐标转换为屏幕坐标时,需要将 y 坐标反转(因为屏幕行号从上到下递增)。最后打印整个数组,达到简单图形化显示的目的。
4. 主函数 main
-
初始化部分:
利用srand(time(NULL))
初始化随机数种子,为后续可能的随机因素(例如随机位置扩展)做好准备。接着固定了两个大猩猩的位置,分别设置在屏幕的左侧和右侧。 -
游戏主循环:
采用while (!hit)
循环进行回合制对战:- 每个回合开始时调用
clearScreen()
清屏,并使用displayGorillas()
显示当前场景; - 根据当前玩家提示投掷香蕉的目标,并要求输入投掷角度和初始速度;
- 根据玩家不同,确定香蕉发射的初始位置(通常在大猩猩上方一点),并设定目标大猩猩;
- 调用
simulateBanana()
模拟香蕉飞行,若返回 1 则代表命中目标,游戏退出循环; - 如果未命中,则提示玩家按回车继续,并切换玩家进入下一回合。
- 每个回合开始时调用
-
游戏结束:
当某一回合检测到命中目标后,循环退出,程序打印“游戏结束”的提示,结束整个游戏。
5. 用户交互
程序通过标准输入输出与玩家进行交互,输入角度和速度,并在控制台上打印香蕉在飞行过程中每个时间步的位置信息,使玩家可以直观地看到抛物线轨迹以及是否命中目标。虽然本示例为文字界面,但通过合理的设计,同样能引导玩家体验物理模拟和游戏对战的乐趣。
五、项目总结
通过本项目,我们实现了一个基于 C 语言的简单“扔香蕉的大猩猩”游戏。整个项目的核心在于利用经典的抛物线运动公式来模拟物体运动,并通过离散时间步长实现近似连续运动的效果。同时,通过简单的碰撞检测算法,实现了游戏中目标检测的功能。下面总结几个关键点:
-
物理建模的重要性:
本项目中利用了物理公式(水平和垂直运动分离的原理)来计算香蕉的运动轨迹,展示了数学模型在实际编程实现中的应用。通过离散化时间步长,模拟出近似连续的运动状态,这种方法在许多游戏和仿真中都有应用。 -
模块化编程思路:
整个项目将清屏、碰撞检测、物体运动模拟、屏幕显示等功能分解为独立的函数。这样不仅使代码逻辑清晰、便于维护,也方便后续对各模块进行扩展,例如增加更多的游戏元素或改进碰撞检测算法。 -
用户交互与反馈:
通过文本模式展示飞行轨迹和实时提示,程序为玩家提供了反馈信息。虽然界面较为简单,但通过不断刷新屏幕和回合制提示,仍然能够营造出较强的代入感。未来可以考虑引入图形界面库,实现更直观的显示效果。 -
扩展性与改进方向:
本项目为一个基础版本的游戏,后续可以考虑以下扩展:- 图形化显示: 使用 SDL、OpenGL 或 ncurses 库实现图形界面,增强视觉效果。
- 增加障碍物: 模拟城市建筑等障碍物,使游戏策略性更强,玩家需要考虑建筑物对香蕉飞行轨迹的影响。
- 改进碰撞检测: 可以采用更精细的碰撞检测算法,考虑大猩猩的实际形状而非简单的圆形区域。
- 多人对战及 AI: 除了双人对战,还可以添加 AI 对手,实现单人游戏模式。
-
项目收获:
通过本项目的编写,不仅复习了 C 语言的基本语法(变量、流程控制、函数调用等),还锻炼了如何将数学公式应用于实际编程中的能力。同时,学习了如何设计简单的游戏逻辑和用户交互流程,对今后更复杂的游戏开发打下了良好基础。
总的来说,本项目是一个从理论到实践、从简单到复杂逐步实现的示例。它展示了如何利用 C 语言将经典的物理模型转化为一个可玩的小游戏,既有趣又具备实用价值。
六、参考资料
- 《C 程序设计语言》 —— K&R 经典著作,为本项目提供了 C 语言基础知识支持。
- 《游戏编程入门》 —— 介绍如何利用基本的编程技术实现简单的游戏逻辑。
- 物理学教材 —— 关于抛物线运动和力学基本原理的参考资料。
- 网络上关于 QBasic “Gorillas” 游戏的介绍及相关实现案例,提供了项目灵感和实现思路。
结语
本篇博客文章详细介绍了如何利用 C 语言实现“扔香蕉的大猩猩”游戏,从项目背景、实现思路,到完整代码及逐步解读,再到项目总结,涵盖了项目开发中的各个重要环节。希望本文能够帮助各位读者更好地理解物理模拟在编程中的应用,同时激发大家利用 C 语言开发更多有趣项目的热情。如果在实现过程中遇到任何问题,欢迎在评论区交流讨论,共同进步!
以上就是完整的 C 语言扔香蕉大猩猩游戏项目介绍,文章内容详尽、代码注释充分,后续可以根据需求进一步拓展。希望大家喜欢并有所收获!