这是一个基于 C++ 的交互式生态模拟游戏。模拟草地、兔子、狼和老虎的食物链关系,展示自然界的生态平衡。
项目概述
- 语言: C++
- 依赖: ncurses 库(用于终端 UI)
- 平台: macOS/Linux/Unix
- 架构: 面向对象设计,使用继承和多态
项目结构
Project9/
- main.C # 主程序入口
- Game.h/Game.C # 游戏核心逻辑
- Gui.h/Gui.C # 图形用户界面(ncurses)
- Item.h/Item.C # 基础物体类(位置管理)
- Grass.h/Grass.C # 草类
- Animal.h/Animal.C # 动物基类
- Rabbit.h/Rabbit.C # 兔子类(草食动物)
- Predator.h/Predator.C # 捕食者基类
- Wolf.h/Wolf.C # 狼类(捕食兔子)
- Tiger.h/Tiger.C # 老虎类(捕食狼和兔子)
- makefile # 编译配置
继承关系图
Item (基类:位置信息)
├── Grass (草)
└── Animal (动物基类)
└── Rabbit (兔子 - 草食性)
└── Predator (捕食者基类)
├── Wolf (狼)
└── Tiger (老虎)
从零开始完整写出这个项目
第1步:创建基础类 – Item
概念: Item 是所有游戏物体的基类,管理 x、y 坐标。
Item.h – 定义类:
#ifndef ITEM_H
#define ITEM_H
class Item {
public:
int x, y;
Item(int x, int y);
virtual ~Item();
};
#endif
Item.C – 实现类:
#include "Item.h"
Item::Item(int x, int y) : x(x), y(y) {}
Item::~Item() {}
第2步:创建草类 – Grass
Grass.h:
#ifndef GRASS_H
#define GRASS_H
#include "Item.h"
class Grass : public Item {
public:
Grass(int x, int y);
virtual ~Grass();
char getChar() const;
};
#endif
Grass.C:
#include "Grass.h"
Grass::Grass(int x, int y) : Item(x, y) {}
Grass::~Grass() {}
char Grass::getChar() const {
return '.'; // 草用 . 表示
}
第3步:创建动物基类 – Animal
Animal.h – 定义公共行为:
#ifndef ANIMAL_H
#define ANIMAL_H
#include "Item.h"
#include
<vector>
class Game;
class Animal : public Item {
protected:
int daysSinceLastMeal; // 距上次进食的天数
int maxDaysWithoutFood; // 最多能忍受的天数
public:
Animal(int x, int y, int maxDays);
virtual ~Animal();
virtual void update(Game& game) = 0; // 纯虚函数,子类必须实现
virtual char getChar() const = 0; // 返回显示的字符
virtual bool isDead() const;
void incrementHunger(); // 增加饥饿度
void eat(); // 进食,饥饿度归零
bool isHungry() const; // 检查是否饥饿
};
#endif
Animal.C:
#include "Animal.h"
Animal::Animal(int x, int y, int maxDays)
: Item(x, y), daysSinceLastMeal(0), maxDaysWithoutFood(maxDays) {}
Animal::~Animal() {}
bool Animal::isDead() const {
return daysSinceLastMeal >= maxDaysWithoutFood;
}
void Animal::incrementHunger() {
daysSinceLastMeal++;
}
void Animal::eat() {
daysSinceLastMeal = 0;
}
bool Animal::isHungry() const {
return daysSinceLastMeal > 2;
}
第4步:创建草食动物 – Rabbit
Rabbit.h:
#ifndef RABBIT_H
#define RABBIT_H
#include "Animal.h"
class Rabbit : public Animal {
private:
static int populationCount;
public:
Rabbit(int x, int y);
virtual ~Rabbit();
void update(Game& game) override;
char getChar() const override;
static void resetPopulation();
static int getPopulationCount();
};
#endif
Rabbit.C:
#include "Rabbit.h"
#include "Game.h"
#include "Grass.h"
#include
<cstdlib>
int Rabbit::populationCount = 0;
Rabbit::Rabbit(int x, int y) : Animal(x, y, 10) {
populationCount++;
}
Rabbit::~Rabbit() {
populationCount--;
}
char Rabbit::getChar() const {
return 'R'; // 兔子用 R 表示
}
void Rabbit::update(Game& game) {
incrementHunger();
if (isDead()) {
return; // 死亡,不再移动
}
// 寻找附近的草
Grass* food = nullptr;
int searchRadius = 2;
for (int dx = -searchRadius; dx <= searchRadius; dx++) {
for (int dy = -searchRadius; dy <= searchRadius; dy++) {
if (dx == 0 && dy == 0) continue;
int nx = x + dx;
int ny = y + dy;
if (game.isValid(nx, ny)) {
Grass* g = game.getGrassAt(nx, ny);
if (g) {
food = g;
break;
}
}
}
if (food) break;
}
// 移向食物或随机移动
if (food && isHungry()) {
x = food->x;
y = food->y;
eat();
// 删除吃掉的草
auto it = game.grassList.begin();
while (it != game.grassList.end()) {
if (*it == food) {
delete food;
it = game.grassList.erase(it);
break;
} else {
++it;
}
}
} else {
// 随机移动
int dx = (rand() % 3) - 1; // -1, 0, 1
int dy = (rand() % 3) - 1;
if (game.isValid(x + dx, y + dy)) {
x += dx;
y += dy;
}
}
}
void Rabbit::resetPopulation() {
populationCount = 0;
}
int Rabbit::getPopulationCount() {
return populationCount;
}
第5步:创建捕食者基类 – Predator
Predator.h:
#ifndef PREDATOR_H
#define PREDATOR_H
#include "Animal.h"
class Predator : public Animal {
protected:
char preyType; // 捕食的动物类型
public:
Predator(int x, int y, int maxDays, char prey);
virtual ~Predator();
char getPreyType() const;
};
#endif
Predator.C:
#include "Predator.h"
Predator::Predator(int x, int y, int maxDays, char prey)
: Animal(x, y, maxDays), preyType(prey) {}
Predator::~Predator() {}
char Predator::getPreyType() const {
return preyType;
}
第6步:创建具体捕食者 – Wolf
Wolf.h:
#ifndef WOLF_H
#define WOLF_H
#include "Predator.h"
class Wolf : public Predator {
private:
static int populationCount;
public:
Wolf(int x, int y);
virtual ~Wolf();
void update(Game& game) override;
char getChar() const override;
static void resetPopulation();
static int getPopulationCount();
};
#endif
Wolf.C:
#include "Wolf.h"
#include "Game.h"
#include "Rabbit.h"
#include
<cstdlib>
int Wolf::populationCount = 0;
Wolf::Wolf(int x, int y) : Predator(x, y, 15, 'R') { // 捕食兔子,15天无食物会死
populationCount++;
}
Wolf::~Wolf() {
populationCount--;
}
char Wolf::getChar() const {
return 'W'; // 狼用 W 表示
}
void Wolf::update(Game& game) {
incrementHunger();
if (isDead()) {
return;
}
// 寻找猎物
Animal* prey = nullptr;
int searchRadius = 3;
for (int dx = -searchRadius; dx <= searchRadius; dx++) {
for (int dy = -searchRadius; dy <= searchRadius; dy++) {
if (dx == 0 && dy == 0) continue;
int nx = x + dx;
int ny = y + dy;
if (game.isValid(nx, ny)) {
Animal* a = game.getAnimalAt(nx, ny, 'R');
if (a) {
prey = a;
break;
}
}
}
if (prey) break;
}
if (prey && isHungry()) {
x = prey->x;
y = prey->y;
eat();
prey->die(); // 猎物死亡
} else {
// 随机移动
int dx = (rand() % 3) - 1;
int dy = (rand() % 3) - 1;
if (game.isValid(x + dx, y + dy)) {
x += dx;
y += dy;
}
}
}
void Wolf::resetPopulation() {
populationCount = 0;
}
int Wolf::getPopulationCount() {
return populationCount;
}
第7步:创建另一个捕食者 – Tiger
Tiger.h:
#ifndef TIGER_H
#define TIGER_H
#include "Predator.h"
class Tiger : public Predator {
private:
static int populationCount;
public:
Tiger(int x, int y);
virtual ~Tiger();
void update(Game& game) override;
char getChar() const override;
static void resetPopulation();
static int getPopulationCount();
};
#endif
Tiger.C – 类似 Wolf.C,但捕食范围更大,可捕食狼和兔子
第8步:创建游戏核心 – Game
Game.h:
#ifndef GAME_H
#define GAME_H
#include
<vector>
#include "Animal.h"
#include "Grass.h"
class Game {
public:
int width, height;
int day;
std::vector<Animal*> animals;
std::vector<Grass*> grassList;
Game(int w, int h);
~Game();
void setup(int numRabbits, int numWolves, int numTigers);
void update();
void addRandomRabbit();
void addRandomPredator();
Grass* getGrassAt(int x, int y);
Animal* getAnimalAt(int x, int y, char type);
bool isValid(int x, int y);
};
#endif
Game.C:
#include "Game.h"
#include "Rabbit.h"
#include "Wolf.h"
#include "Tiger.h"
#include
<cstdlib>
#include
<algorithm>
Game::Game(int w, int h) : width(w), height(h), day(0) {}
Game::~Game() {
for (auto a : animals) delete a;
for (auto g : grassList) delete g;
}
void Game::setup(int numRabbits, int numWolves, int numTigers) {
// 生成草
for (int i = 0; i < 100; i++) {
int x = rand() % width;
int y = rand() % height;
grassList.push_back(new Grass(x, y));
}
// 生成兔子
for (int i = 0; i < numRabbits; i++) {
animals.push_back(new Rabbit(rand() % width, rand() % height));
}
// 生成狼
for (int i = 0; i < numWolves; i++) {
animals.push_back(new Wolf(rand() % width, rand() % height));
}
// 生成老虎
for (int i = 0; i < numTigers; i++) {
animals.push_back(new Tiger(rand() % width, rand() % height));
}
}
void Game::update() {
day++;
// 更新所有动物
for (auto animal : animals) {
animal->update(*this);
}
// 生成新的草
if (rand() % 2 == 0) {
grassList.push_back(new Grass(rand() % width, rand() % height));
}
// 移除死亡的动物
auto it = animals.begin();
while (it != animals.end()) {
if ((*it)->isDead()) {
delete *it;
it = animals.erase(it);
} else {
++it;
}
}
}
bool Game::isValid(int x, int y) {
return x >= 0 && x < width && y >= 0 && y < height;
}
Grass* Game::getGrassAt(int x, int y) {
for (auto g : grassList) {
if (g->x == x && g->y == y) return g;
}
return nullptr;
}
Animal* Game::getAnimalAt(int x, int y, char type) {
for (auto a : animals) {
if (a->x == x && a->y == y && a->getChar() == type) return a;
}
return nullptr;
}
void Game::addRandomRabbit() {
animals.push_back(new Rabbit(rand() % width, rand() % height));
}
void Game::addRandomPredator() {
if (rand() % 2 == 0) {
animals.push_back(new Wolf(rand() % width, rand() % height));
} else {
animals.push_back(new Tiger(rand() % width, rand() % height));
}
}
第9步:创建图形界面 – Gui
Gui.h:
#ifndef GUI_H
#define GUI_H
#include <ncurses.h>
#include "Game.h"
class Gui {
public:
Gui();
~Gui();
void init();
void draw(const Game& game);
void stop();
int getInput();
};
#endif
Gui.C:
#include "Gui.h"
Gui::Gui() {}
Gui::~Gui() {
stop();
}
void Gui::init() {
initscr(); // 初始化 ncurses
cbreak(); // 关闭行缓冲
noecho(); // 不显示输入
keypad(stdscr, TRUE); // 启用功能键
curs_set(0); // 隐藏光标
timeout(0); // 非阻塞输入
}
void Gui::stop() {
endwin(); // 结束 ncurses
}
void Gui::draw(const Game& game) {
clear();
// 绘制草
for (const auto& grass : game.grassList) {
mvaddch(grass->y, grass->x, '.');
}
// 绘制动物
for (const auto& animal : game.animals) {
mvaddch(animal->y, animal->x, animal->getChar());
}
// 绘制信息栏
mvprintw(24, 0, "Day: %d | Animals: %lu | 'a': Add Predator | 'd': Add Rabbit | 'q': Quit",
game.day, (unsigned long)game.animals.size());
refresh();
}
int Gui::getInput() {
return getch();
}
第10步:主程序 – main.C
#include "Game.h"
#include "Gui.h"
#include
<cstdlib>
#include
<ctime>
#include
<iostream>
#include <ncurses.h>
int main(int argc, char* argv[]) {
if (argc < 4) {
std::cout << "Usage: " << argv[0] << " <num_rabbits> <num_wolves> <num_tigers>" << std::endl;
return 1;
}
int numRabbits = std::atoi(argv[1]);
int numWolves = std::atoi(argv[2]);
int numTigers = std::atoi(argv[3]);
srand(time(NULL));
Game game(80, 25);
game.setup(numRabbits, numWolves, numTigers);
Gui gui;
gui.init();
bool running = true;
time_t lastUpdate = time(NULL);
while (running) {
gui.draw(game);
int ch = gui.getInput();
if (ch == 'q') {
running = false;
} else if (ch == 'a') {
game.addRandomPredator();
} else if (ch == 'd') {
game.addRandomRabbit();
}
// 每1秒更新一次游戏状态(一天)
time_t now = time(NULL);
if (now - lastUpdate >= 1) {
game.update();
lastUpdate = now;
}
napms(50); // 50毫秒
}
gui.stop();
return 0;
}
第11步:编写 Makefile
CXX = g++
CXXFLAGS = -Wall -g
LDFLAGS = -lncurses
SRCS = main.C Item.C Grass.C Animal.C Rabbit.C Predator.C Wolf.C Tiger.C Game.C Gui.C
OBJS = $(SRCS:.C=.o)
TARGET = mygame
all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) $(OBJS) -o $(TARGET) $(LDFLAGS)
%.o: %.C
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
编译和运行
安装依赖 (macOS)
brew install ncurses
编译
make clean
make
运行
./mygame 50 30 10
参数说明:
- 第1个数字: 初始兔子数量
- 第2个数字: 初始狼数量
- 第3个数字: 初始老虎数量
游戏操作
| 按键 | 功能 |
|---|---|
a |
添加一个随机捕食者(狼或老虎) |
d |
添加一个兔子 |
q |
退出游戏 |
游戏规则
- 食物链: 兔子吃草 → 狼吃兔子 → 老虎吃狼和兔子
- 饥饿机制: 每种动物有最大能承受的无食物天数
- 繁衍: 兔子可通过捕食快速增加,捕食者需要消耗资源
- 时间: 每秒游戏时间推进一天
- 平衡: 生态系统会逐渐达到动态平衡
关键概念解释
面向对象编程 (OOP)
- 继承: Animal 继承 Item,Wolf 继承 Predator
- 多态: 每个动物都有自己的
update()和getChar() - 虚函数: 使用
virtual关键字定义接口
内存管理
// 动态分配
animals.push_back(new Rabbit(x, y));
// 动态释放
for (auto a : animals) delete a;
STL 容器
std::vector<Animal*> animals; // 动物列表
animals.push_back(new Wolf(x, y)); // 添加
animals.erase(it); // 删除
扩展功能建议
- 添加不同的捕食者优先级
- 实现动物繁殖机制
- 添加季节变化
- 统计种群数量变化
- 保存游戏状态到文件
- 添加声音效果
- 更复杂的 AI 行为
常见问题
Q: 编译出错说找不到 ncurses
A: 确保已安装 ncurses。Windows 系统不支持 ncurses 库,请使用 Linux/macOS 进行编译
Q: 程序运行后界面乱码
A: 确保终端大小至少为 80×25,建议全屏运行
学习资源
- C++ 面向对象编程: https://www.cplusplus.com/doc/
- ncurses 文档: https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/
- STL 容器: https://en.cppreference.com/w/cpp/container
正文完


