Project 9 — 农场生态模拟系统 (Farm Ecosystem Simulator)

103次阅读
没有评论

这是一个基于 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 退出游戏

游戏规则

  1. 食物链: 兔子吃草 → 狼吃兔子 → 老虎吃狼和兔子
  2. 饥饿机制: 每种动物有最大能承受的无食物天数
  3. 繁衍: 兔子可通过捕食快速增加,捕食者需要消耗资源
  4. 时间: 每秒游戏时间推进一天
  5. 平衡: 生态系统会逐渐达到动态平衡

关键概念解释

面向对象编程 (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);  // 删除

扩展功能建议

  1. 添加不同的捕食者优先级
  2. 实现动物繁殖机制
  3. 添加季节变化
  4. 统计种群数量变化
  5. 保存游戏状态到文件
  6. 添加声音效果
  7. 更复杂的 AI 行为

常见问题

Q: 编译出错说找不到 ncurses
A: 确保已安装 ncurses。Windows 系统不支持 ncurses 库,请使用 Linux/macOS 进行编译

Q: 程序运行后界面乱码
A: 确保终端大小至少为 80×25,建议全屏运行

学习资源

正文完
 1
评论(没有评论)

YanQS's Blog