专栏文章

迷宫2

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miorutqb
此快照首次捕获于
2025/12/03 00:07
3 个月前
此快照最后确认于
2025/12/03 00:07
3 个月前
查看原文
CPP
#include <bits/stdc++.h>
#include <graphics.h> // EasyX 图形库

// 额外的 Windows API 和其他功能所需的头文件
#include <windows.h> // 包含 Windows API,如 GetAsyncKeyState, GetCursorPos, SetCursorPos
#include <cmath>     // 数学函数,如 cos, sin, fabs
#include <fstream>   // 文件输入/输出,用于地图加载
#include <functional> // std::function,用于 generateChunk 中的 lambda 递归
#include <random>    // 随机数生成,用于 generateChunk

// 如果需要声音,取消注释以下行并确保链接 winmm.lib
 #pragma comment(lib, "winmm.lib") // Visual Studio
 #include <mmsystem.h> // For PlaySound

#define ll long long
using namespace std;

// --- 常量和枚举 ---
// EasyX 窗口尺寸
static const int SCREEN_WIDTH = 150;
static const int SCREEN_HEIGHT = 80;

const double PI = acos(-1.0); // 更精确的 PI 值

// 游戏相关常量
constexpr int CHUNK_SIZE = 20; // 每个迷宫块的大小
const double PLAYER_HEIGHT = 0.5; // 玩家离地面的高度 (0 为地面)
const double GRAVITY = 0.008;     // 重力加速度 (为 EasyX 调整,帧率更高)
const double JUMP_FORCE = 0.1;    // 跳跃的初始向上速度 (为 EasyX 调整)
const double MOVE_SPEED = 0.05;   // 玩家移动速度 (为 EasyX 调整)
const double MOUSE_SENSITIVITY = 0.002; // 鼠标相机灵敏度 (为 EasyX 调整)
const double KEY_SENSITIVITY = 0.03;    // 键盘相机灵敏度 (为 EasyX 调整)
const double PITCH_SENSITIVITY = 0.03;  // 键盘俯仰灵敏度 (为 EasyX 调整)
const double MAX_PITCH_ANGLE = PI / 2.0 - 0.1; // 限制俯仰角度,防止翻转

// 方块类型枚举
enum BlockType {
    EMPTY = 0,
    WALL = 1,
    TREASURE = 2,
    EXIT = 3,
    MONSTER = 4,
    TRAP = 5
};

// 地图分块结构体
struct Chunk {
    int map[CHUNK_SIZE][CHUNK_SIZE];
};
using ChunkCoord = pair<int,int>;

// 用于 unordered_map 的 pair 哈希函数
struct pair_hash {
    size_t operator()(const ChunkCoord& p) const {
        return std::hash<int>()(p.first) ^ (std::hash<int>()(p.second)<<1);
    }
};

unordered_map<ChunkCoord,Chunk,pair_hash> chunkPool; // 全局分块池

// 粒子结构体 (为 EasyX 调整颜色类型)
struct Particle {
    double x,y,z,vx,vy,vz;
    int life;
    COLORREF color; // EasyX 使用 COLORREF
};

// 玩家结构体
struct Player {
    double x=1.5,y=1.5,z=PLAYER_HEIGHT,angle=0,pitch=0,vz=0; // z, pitch, vz 为新增
    bool onGround=true; // 是否在地面上
    int health=100, treasures=0; // 生命值和宝藏数量
    double cosA,sinA,cosP,sinP; // 角度 (yaw) 和俯仰角 (pitch) 的余弦/正弦
    inline void updateTrig(){ cosA=cos(angle); sinA=sin(angle); cosP=cos(pitch); sinP=sin(pitch); }
} player;
static vector<Particle> particles; // 全局粒子列表

// 怪物结构体和列表
struct Monster {
    double x, y; // 精确的浮点坐标 (瓷砖中心)
    double moveCooldown; // 移动冷却计时器
    int id; // 唯一标识符
};
static vector<Monster> monsters; // 全局活跃怪物列表

// 怪物常量
const double MONSTER_MOVE_DELAY = 0.5; // 怪物每 0.5 秒移动一次
const double MONSTER_DETECTION_RANGE = 40.0; // 怪物在 40 单元内检测到玩家

static BlockType selectedBlockType = WALL; // 当前选择的放置方块类型

// 粒子常量
const double PARTICLE_GRAVITY = 0.008; // 调整后的粒子重力
const double PARTICLE_FRICTION = 0.98;

bool showMiniMap=false, mouseMode=false; // 小地图显示和鼠标模式开关
bool backroomsMode=false; // “后室”模式开关
POINT lastMousePos; // 鼠标上次位置,用于鼠标模式
bool gameOver = false; // 游戏结束标志
long long startTime; // 游戏开始时间
int gameEndReason = 0; // 0: 手动退出, 1: 胜利, 2: 失败

// 伤害闪屏变量
static double damageFlashTimer = 0.0;    // 剩余闪屏时间 (秒)
const double DAMAGE_FLASH_DURATION = 0.2; // 总闪屏持续时间 (秒)

// 地图模式声明
enum class MapMode { Infinite, Existing };
static MapMode mapMode = MapMode::Infinite; // 默认为无限生成

// 小地图缩放变量
static int minimap_radius = 7; // 默认显示半径为 7 (15x15 瓷砖)
const int MIN_MINIMAP_RADIUS = 3; // 最小小地图半径 (7x7)
const int MAX_MINIMAP_RADIUS = 20; // 最大小地图半径 (41x41)

// 小地图翻转变量
static bool minimap_flipped = false; // false = 正常, true = 水平翻转

// 函数前向声明
int getWorldAt(int x, int y);
void setWorldAt(int x, int y, int value);
// void playSound(const char* filename); // 如果需要声音,取消注释此行

// 怪物移动和挖掘的辅助函数
// 如果怪物成功移动 (或通过挖掘移动) 返回 true
bool tryMoveMonster(Monster& m, int old_mx, int old_my, int new_mx, int new_my) {
    // 检查目标方块是否被其他怪物占用
    for (const auto& other_m : monsters) {
        if (&other_m == &m) continue; // 跳过自己
        if (static_cast<int>(other_m.x) == new_mx && static_cast<int>(other_m.y) == new_my) {
            return false; // 目标单元格被其他怪物占用,无法移动
        }
    }

    int target_block_type = getWorldAt(new_mx, new_my);

    if (target_block_type == WALL || target_block_type == TREASURE || target_block_type == EXIT || target_block_type == TRAP) {
        // 是障碍物,怪物将挖掘。
        // 挖掘目标周围 3x3 区域
        for (int dy_dig = -1; dy_dig <= 1; ++dy_dig) {
            for (int dx_dig = -1; dx_dig <= 1; ++dx_dig) {
                int dig_x = new_mx + dx_dig;
                int dig_y = new_my + dy_dig;
                // 只清除非空方块,并且不清除地图上的其他怪物
                if (getWorldAt(dig_x, dig_y) != EMPTY && getWorldAt(dig_x, dig_y) != MONSTER) {
                    setWorldAt(dig_x, dig_y, EMPTY);
                }
            }
        }
        // 挖掘后,目标瓷砖现在是空的,怪物可以移动过去
        setWorldAt(old_mx, old_my, EMPTY); // 清除怪物旧的地图位置
        m.x = new_mx + 0.5; // 更新怪物精确位置到新瓷砖中心
        m.y = new_my + 0.5;
        setWorldAt(static_cast<int>(m.x),static_cast<int>(m.y), MONSTER); // 设置怪物新的地图位置
        // playSound("dig.wav"); // 怪物挖掘音效 (如果启用)
        return true;
    } else if (target_block_type == EMPTY) {
        // 是空地,直接移动
        setWorldAt(old_mx, old_my, EMPTY); // 清除怪物旧的地图位置
        m.x = new_mx + 0.5; // 更新怪物精确位置
        m.y = new_my + 0.5;
        setWorldAt(static_cast<int>(m.x),static_cast<int>(m.y), MONSTER); // 设置怪物新的地图位置
        return true;
    }
    return false; // 无法移动到此类型的方块
}


// 获取分块坐标和分块内的局部偏移
inline ChunkCoord getChunkPos(int x, int y) {
    int cx = (x >= 0) ? (x/CHUNK_SIZE) : ((x+1-CHUNK_SIZE)/CHUNK_SIZE);
    int cy = (y >= 0) ? (y/CHUNK_SIZE) : ((y+1-CHUNK_SIZE)/CHUNK_SIZE);
    return ChunkCoord(cx, cy);
}
inline void getLocalPos(int x, int y, int& lx, int& ly) {
    lx = x % CHUNK_SIZE; if(lx<0) lx+=CHUNK_SIZE;
    ly = y % CHUNK_SIZE; if(ly<0) ly+=CHUNK_SIZE;
}

// 生成迷宫分块
void generateChunk(const ChunkCoord& coord) {
    Chunk& chunk = chunkPool[coord];
    // 初始化为墙壁
    for(int y=0; y<CHUNK_SIZE; ++y)
    for(int x=0; x<CHUNK_SIZE; ++x)
        chunk.map[y][x] = WALL;

    // 随机引擎,以分块坐标为种子,保证一致性生成
    uint64_t seed = coord.first * 73856093ull ^ coord.second * 19349663ull;
    mt19937 rng(seed);

    // DFS 生成迷宫路径
    function<void(int,int)> dfs = [&](int x,int y){
        chunk.map[y][x] = EMPTY;
        // 打乱方向
        vector<int> dir = {0,1,2,3};
        shuffle(dir.begin(), dir.end(), rng);
        static const int dx[] = {1,-1,0,0}, dy[] = {0,0,1,-1};
        for(int d:dir) {
            int nx = x + dx[d]*2, ny = y + dy[d]*2;
            if(nx > 0 && nx < CHUNK_SIZE-1 && ny > 0 && ny < CHUNK_SIZE-1 && chunk.map[ny][nx] == WALL) {
                chunk.map[y+dy[d]][x+dx[d]] = EMPTY; // 挖路
                dfs(nx, ny);
            }
        }
    };
    dfs(1, 1); // 从 (1,1) 开始 DFS

    // 放置动态元素 (宝藏, 出口, 怪物, 陷阱)
    auto place_element = [&](int type, int chance_inv, int min_dist_border) {
        if (rng() % chance_inv == 0) {
            int tx = rng() % (CHUNK_SIZE - 2 * min_dist_border) + min_dist_border;
            int ty = rng() % (CHUNK_SIZE - 2 * min_dist_border) + min_dist_border;
            // 只有在空地上放置,并且怪物避免在玩家出生点附近生成
            if (chunk.map[ty][tx] == EMPTY) {
                // 大致距离检查,避免怪物在玩家出生点 (0,0 块,1.5, 1.5) 附近生成
                int world_x = coord.first * CHUNK_SIZE + tx;
                int world_y = coord.second * CHUNK_SIZE + ty;
                if (type == MONSTER && sqrt(pow(world_x - player.x, 2) + pow(world_y - player.y, 2)) < MONSTER_DETECTION_RANGE + 5) {
                    return; // 避免怪物在初始时离玩家太近
                }

                chunk.map[ty][tx] = type;
                // 如果是怪物,添加到全局怪物列表
                if (type == MONSTER) {
                    monsters.push_back({
                        (double)coord.first * CHUNK_SIZE + tx + 0.5, // 怪物位于瓷砖中心
                        (double)coord.second * CHUNK_SIZE + ty + 0.5,
                        MONSTER_MOVE_DELAY, // 初始冷却时间
                        (int)monsters.size() // 简单 ID
                    });
                }
            }
        }
    };

    place_element(TREASURE, 10, 2); // 1/10 几率, 离边界最小 2 单元
    place_element(EXIT, 30, 3);    // 1/30 几率, 离边界最小 3 单元
    place_element(MONSTER, 40, 2); // 1/40 几率, 离边界最小 2 单元
    place_element(TRAP, 25, 2);    // 1/25 几率, 离边界最小 2 单元

    // 确保与相邻分块的连通性
    // 左侧: 如果左邻居存在且其右侧有路径,则在此处打开路径
    ChunkCoord leftC = {coord.first - 1, coord.second};
    if (chunkPool.count(leftC)) {
        Chunk& lc = chunkPool[leftC];
        for (int y = 3; y < CHUNK_SIZE - 3; y += 2) { // 遍历潜在路径点
            if (lc.map[y][CHUNK_SIZE - 2] == EMPTY) { // 如果左侧分块的右边缘路径是开放的
                chunk.map[y][0] = EMPTY; // 打开此分块的左边缘
                chunk.map[y][1] = EMPTY; // 以及分块内的下一个单元格
            }
        }
    }
    // 顶部: 与顶部邻居相同
    ChunkCoord upC = {coord.first, coord.second - 1};
    if (chunkPool.count(upC)) {
        Chunk& uc = chunkPool[upC];
        for (int x = 3; x < CHUNK_SIZE - 3; x += 2) {
            if (uc.map[CHUNK_SIZE - 2][x] == EMPTY) {
                chunk.map[0][x] = EMPTY;
                chunk.map[1][x] = EMPTY;
            }
        }
    }
    // 打开右侧/底部路径: 这些点将被未来的相邻分块考虑
    for (int y = 3; y < CHUNK_SIZE - 3; y += 2) {
        if (chunk.map[y][CHUNK_SIZE - 3] == EMPTY) { // 如果内部路径到达右侧倒数第三列
            chunk.map[y][CHUNK_SIZE - 2] = EMPTY; // 打开到倒数第二列
        }
    }
    for (int x = 3; x < CHUNK_SIZE - 3; x += 2) {
        if (chunk.map[CHUNK_SIZE - 3][x] == EMPTY) { // 如果内部路径到达底部倒数第三行
            chunk.map[CHUNK_SIZE - 2][x] = EMPTY; // 打开到倒数第二行
        }
    }
}

// 地图访问封装
int getWorldAt(int x, int y) {
    ChunkCoord c = getChunkPos(x, y);
    int lx, ly;
    getLocalPos(x, y, lx, ly);

    if (chunkPool.count(c)) {
        return chunkPool[c].map[ly][lx];
    } else {
        if (mapMode == MapMode::Infinite) {
            // 如果分块不存在且在无限模式下,生成它
            generateChunk(c);
            return chunkPool[c].map[ly][lx]; // 返回新生成的方块类型
        } else { // MapMode::Existing
            // 如果在现有地图模式下且分块未加载,则超出定义地图范围。视为墙壁。
            return WALL;
        }
    }
}

// 设置世界方块类型
void setWorldAt(int x, int y, int value) {
    ChunkCoord c = getChunkPos(x, y);
    int lx, ly;
    getLocalPos(x, y, lx, ly);

    // 只有在分块池中存在时才修改 (已加载或已生成)
    if(chunkPool.count(c)) {
        chunkPool[c].map[ly][lx] = value;
    }
}

// 从文件加载现有地图
void loadExistingMap(const string& filename) {
    ifstream fin(filename);
    if (!fin) {
        cerr << "错误:无法打开地图文件: " << filename << endl;
        mapMode = MapMode::Infinite; // 回退到无限生成模式
        cout << "已切换到无限自动生成模式。\n";
        return;
    }

    // 清除之前运行/无限模式的动态元素
    chunkPool.clear();
    monsters.clear();

    int chunkX, chunkY;
    int loaded_chunks = 0;
    while (fin >> chunkX >> chunkY) {
        ChunkCoord coord = {chunkX, chunkY};
        Chunk c;
        for (int y = 0; y < CHUNK_SIZE; ++y) {
            for (int x = 0; x < CHUNK_SIZE; ++x) {
                int v_int;
                if (!(fin >> v_int)) {
                    cerr << "错误:地图文件格式不正确,在读取区块 (" << chunkX << "," << chunkY << ") 的块数据时出错。\n";
                    fin.close();
                    mapMode = MapMode::Infinite; // 回退
                    cout << "已切换到无限自动生成模式。\n";
                    return;
                }
                BlockType v = static_cast<BlockType>(v_int);
                c.map[y][x] = v_int; // 直接存储整数值

                // 如果是怪物,添加到动态列表
                if (v == MONSTER) {
                    monsters.push_back({
                        (double)coord.first * CHUNK_SIZE + x + 0.5, // 怪物位于瓷砖中心
                        (double)coord.second * CHUNK_SIZE + y + 0.5, // 修复: 将ty改为y
                        MONSTER_MOVE_DELAY, // 初始冷却时间
                        (int)monsters.size() // 简单 ID
                    });
                }
            }
        }
        chunkPool[coord] = c;
        loaded_chunks++;
    }
    fin.close();
    cout << "已加载 " << loaded_chunks << " 个区块。\n";

    // 设置玩家位置到任何已加载分块中的第一个可用空地
    bool found_spawn_location = false;
    for (const auto& pair : chunkPool) {
        ChunkCoord current_chunk_coord = pair.first;
        const Chunk& current_chunk = pair.second;

        int spawn_x_offset = current_chunk_coord.first * CHUNK_SIZE;
        int spawn_y_offset = current_chunk_coord.second * CHUNK_SIZE;

        for (int dy = 0; dy < CHUNK_SIZE; ++dy) {
            for (int dx = 0; dx < CHUNK_SIZE; ++dx) {
                if (current_chunk.map[dy][dx] == EMPTY) {
                    player.x = spawn_x_offset + dx + 0.5; // 居中于瓷砖
                    player.y = spawn_y_offset + dy + 0.5;
                    found_spawn_location = true;
                    goto end_spawn_search; // 使用 goto 跳出嵌套循环
                }
            }
        }
    }

end_spawn_search:; // goto 标签

    if (!found_spawn_location) {
        cout << "警告:在所有加载的区块中未找到空地作为玩家出生点,玩家可能出生在墙内。\n";
    }

    if (chunkPool.empty()) {
        cout << "警告:没有加载任何区块,请检查地图文件。\n";
        mapMode = MapMode::Infinite; // 如果没有加载任何分块,回退
        cout << "已切换到无限自动生成模式。\n";
    }
}

// 检查按键状态 (EasyX 代码中已有,这里重用)
inline bool keyDown(int v) { return GetAsyncKeyState(v) & 0x8000; }

// 播放音效函数 (已注释掉,因为需要额外的库和音效文件)
// void playSound(const char* filename) {
//     PlaySoundA(filename, NULL, SND_FILENAME | SND_ASYNC);
// }

// 游戏结束/胜利时显示消息的函数 (使用 EasyX)
void displayGameEndScreen(const string& message) {
    cleardevice(); // 清除 EasyX 窗口
    setbkcolor(BLACK); // 黑色背景
    settextcolor(WHITE); // 白色文本
    settextstyle(30, 0, _T("Consolas")); // 更大的字体

    // 计算文本位置以居中显示
    // 注意: textwidth 和 textheight 接受 TCHAR* 参数,所以需要转换为 _T() 字符串
    int textWidth = textwidth(_T(message.c_str())); // 这里直接用 string::c_str() 转换可能会导致问题,
                                                  // 如果 EasyX 使用多字节字符集,string 的内容可能是 UTF-8
                                                  // 如果使用宽字节字符集,string 的内容应该转换为 wstring
                                                  // 最保险的做法是创建一个 TCHAR 数组
    TCHAR t_message[256]; // 假设消息长度不会超过 255
    _tcscpy_s(t_message, _countof(t_message), _T(message.c_str())); // 复制到 TCHAR 数组

    int textHeight = textheight(t_message); // _T(message.c_str())

    outtextxy((SCREEN_WIDTH - textWidth) / 2, (SCREEN_HEIGHT - textHeight) / 2 - 20, t_message); // _T(message.c_str())

    string prompt_str = "Press ENTER to exit."; // 这里保持 string,因为这是英文提示
    TCHAR t_prompt[64];
    _tcscpy_s(t_prompt, _countof(t_prompt), _T(prompt_str.c_str())); // 同样处理

    int promptWidth = textwidth(t_prompt);
    int promptHeight = textheight(t_prompt);
    outtextxy((SCREEN_WIDTH - promptWidth) / 2, (SCREEN_HEIGHT - promptHeight) / 2 + 20, t_prompt);

    FlushBatchDraw(); // 确保绘制可见

    // 通过控制台等待 ENTER 键 (因为 EasyX 窗口可能在游戏结束后被隐藏)
    // 或者可以使用 EasyX 的按键检测循环来等待,但这需要保持 EasyX 窗口的活跃。
    // 这里选择重新显示控制台并等待输入。
    ShowWindow(GetConsoleWindow(), SW_SHOW); // 重新显示控制台窗口
    cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 清除任何剩余的输入缓冲区
    cin.get();
}

int main() {
    // 初始控制台交互 (选择地图模式)
    SetConsoleTitle(_T("迷迷世界")); // 设置控制台标题
    srand((unsigned)time(NULL)); // 初始化随机数生成器
    ios::sync_with_stdio(false); // 关闭 C++ 标准流与 C 标准流的同步

    system("cls"); // 清屏以显示菜单
    cout << "\n\n";
    cout << "  --------------------------------------------------\n";
    cout << "  |             欢迎来到 迷迷世界             |\n";
    cout << "  |             (Maze Runner 3D)                   |\n";
    cout << "  --------------------------------------------------\n";
    cout << "\n";
    cout << "  请选择地图模式:\n";
    cout << "  1. 无限自动生成地图 (Infinite procedural map)\n";
    cout << "  2. 加载已有地图 (Load existing map from file)\n";
    cout << "  --------------------------------------------------\n";
    cout << "  输入编号并回车:";
    
    int choice_input = 1; // 默认选择
    cin >> choice_input;
    cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 清除行尾的换行符

    if (choice_input == 2) {
        mapMode = MapMode::Existing;
		cout<<"请选择地图:\n";
		cout<<"1.空旷的小房间 (map1.txt)\n"; // 假设文件名为 map1.txt
		cout<<"2.石墙林立 (map2.txt)\n";   // 假设文件名为 map2.txt
		string fname_choice_str;
		getline(cin, fname_choice_str);
		int fname_choice = 0;
		try {
			fname_choice = stoi(fname_choice_str);
		} catch (const std::invalid_argument& ia) {
			cerr << "无效输入, 默认为 1.\n";
			fname_choice = 1;
		} catch (const std::out_of_range& oor) {
			cerr << "输入超出范围, 默认为 1.\n";
			fname_choice = 1;
		}

		string fname_base;
		if (fname_choice == 1) {
			fname_base = "map1"; 
		} else if (fname_choice == 2) {
			fname_base = "map2";
		} else {
			cout << "无效选择,默认为 'map1.txt'.\n";
			fname_base = "map1";
		}
		loadExistingMap(fname_base + ".txt");
		
    } else {
        mapMode = MapMode::Infinite;
        cout << "  已选择无限自动生成模式。\n";
    }
    // 初始化 EasyX 窗口 (在控制台交互后)
    initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
    SetWindowText(GetHWnd(), _T("迷迷世界 - Maze Runner 3D")); // 设置 EasyX 窗口标题
    // 隐藏控制台窗口,现在 EasyX 窗口是主要的显示界面
    ShowWindow(GetConsoleWindow(), SW_HIDE);

    // 设置鼠标初始位置,以便鼠标模式开启时,鼠标光标在屏幕中心
    RECT r_easyx; GetWindowRect(GetHWnd(),&r_easyx);
    lastMousePos={ (r_easyx.left+r_easyx.right)/2, (r_easyx.top+r_easyx.bottom)/2 };
    SetCursorPos(lastMousePos.x,lastMousePos.y);

    // 使用 EasyX 显示游戏介绍和操作指南
    cleardevice(); // 清屏
    setbkcolor(BLACK); // 背景色
    settextcolor(WHITE); // 文本颜色
    settextstyle(20, 0, _T("Consolas")); // 字体样式

    // 以下是游戏介绍和操作指南的 EasyX 文本绘制,调整 Y 坐标以适应窗口高度
    int current_y = 30; // 初始 Y 坐标
    const int line_height = 20; // 每行行高

    outtextxy(50, current_y, _T("--------------------------------------------------")); current_y += line_height;
    outtextxy(50, current_y, _T("|             欢迎来到 迷迷世界             |")); current_y += line_height;
    outtextxy(50, current_y, _T("|             (Maze Runner 3D)                   |")); current_y += line_height;
    outtextxy(50, current_y, _T("--------------------------------------------------")); current_y += line_height * 2;
    outtextxy(50, current_y, _T("**游戏概述:**")); current_y += line_height;
    outtextxy(50, current_y, _T("《迷迷世界3D》是一款独特的、在EasyX中运行的3D第一人称迷宫冒险游戏。")); current_y += line_height;
    outtextxy(50, current_y, _T("你将身处一个无限生成、不断变化的像素化迷宫中。你的目标是探索、收集宝藏、")); current_y += line_height;
    outtextxy(50, current_y, _T("生存下来并找到出口!")); current_y += line_height * 2;
    outtextxy(50, current_y, _T("**核心玩法与特色:**")); current_y += line_height;
    outtextxy(50, current_y, _T("- 无限迷宫: 程序生成,每次体验都不同。")); current_y += line_height;
    outtextxy(50, current_y, _T("- 动态元素: 收集'$'宝藏,寻找'E'出口。小心'M'怪物和'T'陷阱,它们会扣除生命!")); current_y += line_height;
    outtextxy(50, current_y, _T("- 自由互动: 按'X'挖掘任何方块,按'1'放置方块,改变迷宫。")); current_y += line_height;
    outtextxy(50, current_y, _T("- 辅助模式: 按'M'切换小地图,按'K'切换独特的“后室”视觉模式。")); current_y += line_height * 2;
    outtextxy(50, current_y, _T("**操作指南:**")); current_y += line_height;
    outtextxy(50, current_y, _T("- WASD: 移动 (按住SHIFT冲刺)")); current_y += line_height;
    outtextxy(50, current_y, _T("- 空格键 (SPACE): 跳跃")); current_y += line_height;
    outtextxy(50, current_y, _T("- 左/右箭头键: 水平转向")); current_y += line_height;
    outtextxy(50, current_y, _T("- 上/下箭头键: 垂直俯仰")); current_y += line_height;
    outtextxy(50, current_y, _T("- ALT 键: 换鼠标视角模式")); current_y += line_height; // 鼠标模式改为"换"以区分
    outtextxy(50, current_y, _T("- M 键: 切换小地图显示")); current_y += line_height;
    outtextxy(50, current_y, _T("- K 键: 切换“后室”模式")); current_y += line_height;
    outtextxy(50, current_y, _T("- X 键: 挖掘前方的任何方块 (除了空地)")); current_y += line_height;
    outtextxy(50, current_y, _T("- 1 键: 在前方空地放置当前选定的方块类型")); current_y += line_height;
    outtextxy(50, current_y, _T("- [ 键 (左方括号): 循环选择上一个要放置的方块类型")); current_y += line_height;
    outtextxy(50, current_y, _T("- ] 键 (右方括号): 循环选择下一个要放置的方块类型")); current_y += line_height;
    outtextxy(50, current_y, _T("- ESC 键: 退出游戏")); current_y += line_height;
    outtextxy(50, current_y, _T("- + 键: 增大迷你地图尺寸")); current_y += line_height; 
    outtextxy(50, current_y, _T("- - 键: 减小迷你地图尺寸")); current_y += line_height;
    outtextxy(50, current_y, _T("- R 键: 左右翻转迷你地图显示")); current_y += line_height * 2;
    outtextxy(50, current_y, _T("--------------------------------------------------")); current_y += line_height;
    outtextxy(50, current_y, _T("准备好了吗?按下 ENTER 键开始冒险..."));

    FlushBatchDraw(); // 刷新显示

    // 等待用户在 EasyX 窗口中按下 Enter 键开始游戏
    while(!keyDown(VK_RETURN)) {
        Sleep(10); // 短暂延迟,避免 CPU 占用过高
    }
    cleardevice(); // 清屏
    FlushBatchDraw(); // 刷新

    startTime = GetTickCount(); // 记录游戏开始时间

    BeginBatchDraw(); // 开启批量绘制,避免闪烁
    
    // 按键状态变量 (用于检测按键按下/释放,避免重复触发)
    bool altPrev=false, mPrev=false, xPrev=false, kPrev=false, rPrev=false; 
    bool onePrev=false, spacePrev=false;
    bool cyclePrevPrev = false, cycleNextPrev = false; 
    bool plusPrev = false, minusPrev = false; 

    long long lastFrameTime = GetTickCount(); // 上一帧的时间

    // 游戏主循环
    while(!gameOver){
        long long currentFrameTime = GetTickCount();
        double deltaTime = (currentFrameTime - lastFrameTime) / 1000.0; // 计算帧时间 (秒)
        lastFrameTime = currentFrameTime;

        // 更新伤害闪屏计时器
        damageFlashTimer = max(0.0, damageFlashTimer - deltaTime);

        // --- 输入处理 ---
        bool alt=keyDown(VK_MENU);
        if(alt&&!altPrev) {
            mouseMode=!mouseMode;
            // 切换到鼠标模式时,重新将鼠标光标定位到窗口中心
            if(mouseMode) {
                RECT r_easyx_current; GetWindowRect(GetHWnd(),&r_easyx_current);
                lastMousePos={ (r_easyx_current.left+r_easyx_current.right)/2, (r_easyx_current.top+r_easyx_current.bottom)/2 };
                SetCursorPos(lastMousePos.x,lastMousePos.y);
            }
        }
        altPrev=alt;

        if(mouseMode){ 
            POINT p; GetCursorPos(&p); 
            int dx=p.x-lastMousePos.x; 
            int dy=p.y-lastMousePos.y; // 用于俯仰角
            player.angle+=dx*MOUSE_SENSITIVITY;
            player.pitch-=dy*MOUSE_SENSITIVITY; // 负号用于 Y 轴反转
            SetCursorPos(lastMousePos.x,lastMousePos.y); // 将鼠标光标重新定位到中心
        } 
        
        // 水平视角旋转 (偏航)
        if(keyDown(VK_LEFT)) player.angle-=KEY_SENSITIVITY; 
        if(keyDown(VK_RIGHT)) player.angle+=KEY_SENSITIVITY; 

        // 垂直视角旋转 (俯仰)
        if(keyDown(VK_UP)) player.pitch+=PITCH_SENSITIVITY; 
        if(keyDown(VK_DOWN)) player.pitch-=PITCH_SENSITIVITY; 
        player.pitch = max(-MAX_PITCH_ANGLE, min(MAX_PITCH_ANGLE, player.pitch)); // 限制俯仰角度

        // 更新玩家的三角函数值 (在角度/俯仰角改变后必须更新)
        player.updateTrig();

        // 移动 (WASD)
        double nx=player.x, ny=player.y;
        double currentMoveSpeed = MOVE_SPEED;
        if (keyDown(VK_SHIFT)) currentMoveSpeed *= 1.5; // 按住 SHIFT 冲刺

        if(keyDown('W')){ // 前进
            nx+=player.cosA*currentMoveSpeed; 
            ny+=player.sinA*currentMoveSpeed; 
        }
        if(keyDown('S')){ // 后退
            nx-=player.cosA*currentMoveSpeed; 
            ny-=player.sinA*currentMoveSpeed; 
        }
        if(keyDown('A')){ // 横向左移
            nx+=player.sinA*currentMoveSpeed; 
            ny-=player.cosA*currentMoveSpeed; 
        }
        if(keyDown('D')){ // 横向右移
            nx-=player.sinA*currentMoveSpeed; 
            ny+=player.cosA*currentMoveSpeed; 
        }
        
        // 简单的水平碰撞检测
        // 检查玩家包围盒的四个角
        double player_half_width = 0.2; // 玩家的有效半宽用于碰撞
        
        // 仅检查 X 方向的移动是否会导致碰撞
        bool can_move_x = (
            getWorldAt(static_cast<int>(nx - player_half_width), static_cast<int>(player.y - player_half_width)) != WALL &&
            getWorldAt(static_cast<int>(nx + player_half_width), static_cast<int>(player.y - player_half_width)) != WALL &&
            getWorldAt(static_cast<int>(nx - player_half_width), static_cast<int>(player.y + player_half_width)) != WALL &&
            getWorldAt(static_cast<int>(nx + player_half_width), static_cast<int>(player.y + player_half_width)) != WALL
        );

        // 仅检查 Y 方向的移动是否会导致碰撞
        bool can_move_y = (
            getWorldAt(static_cast<int>(player.x - player_half_width), static_cast<int>(ny - player_half_width)) != WALL &&
            getWorldAt(static_cast<int>(player.x + player_half_width), static_cast<int>(ny - player_half_width)) != WALL &&
            getWorldAt(static_cast<int>(player.x - player_half_width), static_cast<int>(ny + player_half_width)) != WALL &&
            getWorldAt(static_cast<int>(player.x + player_half_width), static_cast<int>(ny + player_half_width)) != WALL
        );

        // 如果没有碰撞,则应用移动
        if (can_move_x) player.x = nx;
        if (can_move_y) player.y = ny;


        // 跳跃
        bool space = keyDown(VK_SPACE);
        if(space && !spacePrev && player.onGround){
            player.vz = JUMP_FORCE;
            player.onGround = false;
            // playSound("jump.wav");
        }
        spacePrev = space;

        // 应用重力
        if(!player.onGround) {
            player.vz -= GRAVITY;
            player.z += player.vz;
        }

        // 与地面的碰撞 (z=0)
        if(player.z <= PLAYER_HEIGHT){
            player.z = PLAYER_HEIGHT; // 将玩家 Z 限制在地面以上
            player.vz = 0; // 垂直速度归零
            player.onGround = true; // 标记为在地面上
        }

        // 小地图开关
        if(keyDown('M')&&!mPrev) showMiniMap=!showMiniMap; 
        mPrev=keyDown('M');

        // “后室”模式开关
        if(keyDown('K')&&!kPrev) backroomsMode=!backroomsMode;
        kPrev=keyDown('K');

        // 小地图翻转开关 (R 键)
        bool r = keyDown('R');
        if(r && !rPrev) minimap_flipped = !minimap_flipped;
        rPrev = r;

        // 循环选择放置方块类型 ([ 和 ] 键)
        bool cyclePrev = keyDown(VK_OEM_4); // VK_OEM_4 是 '['
        bool cycleNext = keyDown(VK_OEM_6); // VK_OEM_6 是 ']'
        
        const int min_placeable_type = WALL; // BlockType::WALL (1)
        const int max_placeable_type = TRAP; // BlockType::TRAP (5)
        const int num_placeable_types = max_placeable_type - min_placeable_type + 1; // 总共 5 种可放置类型

        if(cyclePrev && !cyclePrevPrev){
            selectedBlockType = (BlockType)(((selectedBlockType - min_placeable_type - 1 + num_placeable_types) % num_placeable_types) + min_placeable_type);
            // playSound("change_block.wav");
        }
        if(cycleNext && !cycleNextPrev){
            selectedBlockType = (BlockType)(((selectedBlockType - min_placeable_type + 1) % num_placeable_types) + min_placeable_type);
            // playSound("change_block.wav");
        }
        cyclePrevPrev = cyclePrev;
        cycleNextPrev = cycleNext;

        // 小地图尺寸调整 (+ 和 - 键)
        bool plus = keyDown(VK_OEM_PLUS); // 用于 '+' 键
        bool minus = keyDown(VK_OEM_MINUS); // 用于 '-' 键

        if (plus && !plusPrev) {
            minimap_radius = min(MAX_MINIMAP_RADIUS, minimap_radius + 1);
        }
        if (minus && !minusPrev) {
            minimap_radius = max(MIN_MINIMAP_RADIUS, minimap_radius - 1);
        }
        plusPrev = plus;
        minusPrev = minus;


        // --- 交互逻辑 ---
        // 玩家当前所在的瓷砖
        int current_tile_x = static_cast<int>(player.x);
        int current_tile_y = static_cast<int>(player.y);
        int current_tile_type = getWorldAt(current_tile_x, current_tile_y);

        if (current_tile_type == TREASURE) {
            player.treasures++;
            setWorldAt(current_tile_x, current_tile_y, EMPTY); // 消耗宝藏
            // playSound("collect.wav");
        } else if (current_tile_type == EXIT) {
            gameOver = true; // 游戏结束,玩家胜利
            gameEndReason = 1; // 设置胜利原因
        } else if (current_tile_type == MONSTER || current_tile_type == TRAP) {
            player.health -= 10; // 受到伤害
            damageFlashTimer = DAMAGE_FLASH_DURATION; // 触发红色闪屏

            if (player.health < 0) player.health = 0; // 生命值最低为 0
            setWorldAt(current_tile_x, current_tile_y, EMPTY); // 伤害后移除怪物/陷阱
            // playSound("hit.wav");
            // 如果是怪物,也从动态列表中移除
            if (current_tile_type == MONSTER) {
                for (int i = monsters.size() - 1; i >= 0; --i) {
                    if (static_cast<int>(monsters[i].x) == current_tile_x && static_cast<int>(monsters[i].y) == current_tile_y) {
                        monsters.erase(monsters.begin() + i);
                        break;
                    }
                }
            }
        }
        
        if (player.health <= 0) {
            gameOver = true; // 游戏结束,玩家失败
            gameEndReason = 2; // 设置失败原因
        }

        // 计算挖掘/放置的目标瓷砖 (前方 1.5 单元距离)
        int target_tile_x = static_cast<int>(player.x + player.cosA * 1.5);
        int target_tile_y = static_cast<int>(player.y + player.sinA * 1.5);

        // 防止 targeting 玩家所在的瓷砖
        if (target_tile_x == current_tile_x && target_tile_y == current_tile_y) {
            // 不与玩家所在瓷砖进行挖掘/放置交互
        } else {
            // 挖掘 (X 键) - 现在可以破坏任何非空方块
            if(keyDown('X') && !xPrev){
                int block_to_destroy = getWorldAt(target_tile_x, target_tile_y);
                if(block_to_destroy != EMPTY){ // 破坏任何非空方块
                    setWorldAt(target_tile_x, target_tile_y, EMPTY);

                    // 如果是怪物,也从动态列表中移除
                    if (block_to_destroy == MONSTER) {
                        for (int i = monsters.size() - 1; i >= 0; --i) {
                            if (static_cast<int>(monsters[i].x) == target_tile_x && static_cast<int>(monsters[i].y) == target_tile_y) {
                                monsters.erase(monsters.begin() + i);
                                break;
                            }
                        }
                    }

                    // 添加粒子效果
                    COLORREF p_color = RGB(128,128,128); // 默认灰色 (墙壁)
                    switch (block_to_destroy) {
                        case WALL:      p_color = RGB(128,128,128); break;
                        case TREASURE:  p_color = RGB(255,255,0); break;
                        case EXIT:      p_color = RGB(0,255,0); break;
                        case MONSTER:   p_color = RGB(255,0,0); break;
                        case TRAP:      p_color = RGB(139,0,0); break;
                    }

                    for(int i=0; i<10; ++i){ // 生成 10 个粒子
                        double px = target_tile_x + 0.5;
                        double py = target_tile_y + 0.5;
                        double pz = player.z; // 粒子在玩家高度附近生成

                        double angle = (double)rand()/RAND_MAX * PI * 2;
                        double speed = (double)rand()/RAND_MAX * 0.2 + 0.05;
                        double vz_p = (double)rand()/RAND_MAX * 0.1 + 0.05;

                        particles.push_back({
                            px, py, pz,
                            cos(angle) * speed, sin(angle) * speed, vz_p,
                            30, p_color // 粒子生命周期 30 帧
                        });
                    }
                    // playSound("dig.wav");
                }
            }
            // 放置选定的方块类型 (1 键)
            if (keyDown('1') && !onePrev) {
                if (getWorldAt(target_tile_x, target_tile_y) == EMPTY) { // 只能在空地上放置
                    // 如果放置怪物,也添加到动态列表中
                    if (selectedBlockType == MONSTER) {
                        monsters.push_back({
                            (double)target_tile_x + 0.5,
                            (double)target_tile_y + 0.5,
                            MONSTER_MOVE_DELAY,
                            (int)monsters.size()
                        });
                    }
                    setWorldAt(target_tile_x, target_tile_y, selectedBlockType);
                    // playSound("place.wav");
                }
            }
        }
        xPrev = keyDown('X');
        onePrev = keyDown('1');

        // --- 怪物 AI 更新 ---
        for (int i = 0; i < monsters.size(); ++i) {
            Monster& m = monsters[i];
            m.moveCooldown -= deltaTime; // 减少冷却时间
			int old_mx = static_cast<int>(m.x); // 怪物当前所在瓷砖
			int old_my = static_cast<int>(m.y);
            double dx = player.x - m.x;
            double dy = player.y - m.y;
            double dist_to_player_sq = dx*dx + dy*dy; // 使用平方距离提高效率

            // 只有在检测范围内且冷却时间结束时才移动
            if (dist_to_player_sq <= MONSTER_DETECTION_RANGE * MONSTER_DETECTION_RANGE && m.moveCooldown <= 0) {
                m.moveCooldown = MONSTER_MOVE_DELAY; // 重置冷却时间

                int dir_x = 0;
                if (dx > 0.1) dir_x = 1; // 阈值避免 dx 恰好为 0 导致的问题
                else if (dx < -0.1) dir_x = -1;

                int dir_y = 0;
                if (dy > 0.1) dir_y = 1;
                else if (dy < -0.1) dir_y = -1;

                bool moved_this_turn = false;

                // 优先移动策略 (斜向 -> X轴 -> Y轴)
                if (dir_x != 0 && dir_y != 0) {
                    moved_this_turn = tryMoveMonster(m, old_mx, old_my, old_mx + dir_x, old_my + dir_y);
                }

                if (!moved_this_turn && dir_x != 0) {
                    moved_this_turn = tryMoveMonster(m, old_mx, old_my, old_mx + dir_x, old_my);
                }
                
                if (!moved_this_turn && dir_y != 0) {
                    moved_this_turn = tryMoveMonster(m, old_mx, old_my, old_mx, old_my + dir_y);
                }
            }
        }

        // --- 粒子更新 ---
        for (int i = particles.size() - 1; i >= 0; --i) {
            Particle& p = particles[i];
            
            p.x += p.vx;
            p.y += p.vy;
            p.z += p.vz;
            p.vz -= PARTICLE_GRAVITY; // 应用重力
            
            p.vx *= PARTICLE_FRICTION; // 应用摩擦力
            p.vy *= PARTICLE_FRICTION;
            p.vz *= PARTICLE_FRICTION;

            p.life--;
            if (p.life <= 0) {
                particles.erase(particles.begin() + i); // 生命周期结束,移除粒子
            }
        }

        // --- 渲染帧 (EasyX) ---
        cleardevice(); // 清除整个窗口,绘制新帧

        // 根据俯仰角和玩家 Z 高度调整“地平线”
        int verticalOffset = int(player.pitch * (SCREEN_HEIGHT / PI / 2.0));
        verticalOffset += int((player.z - PLAYER_HEIGHT) * SCREEN_HEIGHT * 0.5); // 根据玩家 Z 高度调整,0.5 因子使效果不那么极端
        int halfH_shifted = (SCREEN_HEIGHT>>1) + verticalOffset; // 新的地平线 Y 坐标

        // 天空 / 天花板
        COLORREF skyBaseColor = backroomsMode ? RGB(255, 230, 100) : RGB(135,206,235); // 后室模式为黄色调,否则为天蓝色
        setfillcolor(skyBaseColor);
        solidrectangle(0, 0, SCREEN_WIDTH, halfH_shifted);
        
        // 后室模式: 模拟天花板灯光 (简单的矩形)
        if(backroomsMode){
            int light_grid_x_count = 5;
            int light_grid_y_count = 3;
            int light_size = 15; // 方形灯的大小

            for (int lx = 0; lx < light_grid_x_count; ++lx) {
                for (int ly = 0; ly < light_grid_y_count; ++ly) {
                    int center_x = (SCREEN_WIDTH / light_grid_x_count) * (lx + 0.5);
                    int center_y = (halfH_shifted / light_grid_y_count) * (ly + 0.5);
                    setfillcolor(WHITE);
                    solidrectangle(center_x - light_size/2, center_y - light_size/2,
                                   center_x + light_size/2, center_y + light_size/2);
                }
            }
        }

        // 地面
        COLORREF groundBaseColor = backroomsMode ? RGB(180, 160, 50) : RGB(110,110,110); // 后室模式为深黄色调,否则为灰色
        setfillcolor(groundBaseColor);
        solidrectangle(0, halfH_shifted, SCREEN_WIDTH, SCREEN_HEIGHT);
        
        // 射线投射
        for(int x=0; x<SCREEN_WIDTH; ++x) {
            double cameraX = 2 * x / double(SCREEN_WIDTH) - 1; // 屏幕 x 坐标在 [-1, 1] 范围内的映射
            double rayDirX = player.cosA - player.sinA * cameraX; // 射线方向 X 分量
            double rayDirY = player.sinA + player.cosA * cameraX; // 射线方向 Y 分量
            
            int mapX = int(player.x), mapY = int(player.y); // 射线当前所在的地图块
            double deltaX = fabs(1 / rayDirX), deltaY = fabs(1 / rayDirY); // 射线在 X/Y 方向前进一格所需距离
            
            double sideX, sideY; // 射线到下一个 X/Y 方向边界的距离
            int stepX, stepY; // 步进方向 (+1 或 -1)

            if (rayDirX < 0) { stepX = -1; sideX = (player.x - mapX) * deltaX; }
            else { stepX = 1; sideX = (mapX + 1.0 - player.x) * deltaX; }
            if (rayDirY < 0) { stepY = -1; sideY = (player.y - mapY) * deltaY; }
            else { stepY = 1; sideY = (mapY + 1.0 - player.y) * deltaY; }
            
            int side; // 击中的是 X 边 (0) 还是 Y 边 (1)
            int hitType=WALL; // 默认击中墙壁
            
            // DDA 算法 (数字差分分析)
            while(true) {
                if(sideX < sideY) { sideX += deltaX; mapX += stepX; side = 0; }
                else          { sideY += deltaY; mapY += stepY; side = 1; }
                hitType = getWorldAt(mapX, mapY); // 获取当前瓷砖类型
                if(hitType) break; // 击中墙壁或物体则退出
            }
            
            // 计算垂直距离
            double perp = side==0 ? (mapX - player.x + (1-stepX)/2.0)/rayDirX
                                  : (mapY - player.y + (1-stepY)/2.0)/rayDirY;
            perp = max(perp, 0.1); // 避免除以零或过小的数

            // 计算墙壁在屏幕上的顶部和底部 Y 坐标
            // 假设墙壁高度为 1 (从 Z=0 到 Z=1)
            int wall_top_y = int(halfH_shifted - (1.0 - player.z) * SCREEN_HEIGHT / perp);
            int wall_bottom_y = int(halfH_shifted - (0.0 - player.z) * SCREEN_HEIGHT / perp);
            
            int drawStartY = max(0, wall_top_y); // 实际绘制的起始 Y 坐标 (屏幕范围内)
            int drawEndY = min(SCREEN_HEIGHT, wall_bottom_y); // 实际绘制的结束 Y 坐标 (屏幕范围内)
            
            // 墙壁着色和颜色
            double shade_factor = 1.0 / (1.0 + perp * perp * 0.01); // 距离衰减着色
            shade_factor = max(0.0, min(1.0, shade_factor)); // 限制在 0-1 之间
            
            COLORREF col;
            int baseR, baseG, baseB;

            switch (hitType) {
                case WALL:
                    // 后室模式为黄色调,否则 Y 边 (侧面) 更暗,X 边 (正面) 稍亮
                    baseR = backroomsMode ? 255 : (side ? 100 : 150);
                    baseG = backroomsMode ? 230 : (side ? 100 : 150);
                    baseB = backroomsMode ? 100 : (side ? 100 : 150);
                    break;
                case TREASURE:  baseR = 255; baseG = 255; baseB = 0; break; // 黄色
                case EXIT:      baseR = 0; baseG = 255; baseB = 0; break; // 绿色
                case MONSTER:   baseR = 255; baseG = 0; baseB = 0; break; // 红色
                case TRAP:      baseR = 139; baseG = 0; baseB = 0; break; // 暗红色
                default:        baseR = 0; baseG = 0; baseB = 0; break; // 不应出现非空方块
            }
            
            // 应用距离着色
            col = RGB(static_cast<int>(baseR * shade_factor), 
                      static_cast<int>(baseG * shade_factor), 
                      static_cast<int>(baseB * shade_factor));
            
            setlinecolor(col); // 设置线条颜色
            line(x, drawStartY, x, drawEndY); // 绘制垂直的墙壁线段
        }
        
        // 粒子渲染 (在墙壁之后绘制,使其显示在墙壁之上)
        for (const auto& p : particles) {
            double px_rel = p.x - player.x; // 粒子相对于玩家的 X 坐标
            double py_rel = p.y - player.y; // 粒子相对于玩家的 Y 坐标
            double pz_rel = p.z - player.z; // 粒子相对于玩家的 Z 坐标

            // 根据玩家偏航角旋转相对坐标
            double rotX = px_rel * player.cosA + py_rel * player.sinA;
            double rotY_depth = py_rel * player.cosA - px_rel * player.sinA; // 深度 (旋转前的 Y 分量)

            // 应用俯仰角旋转到垂直分量
            double final_pz = pz_rel * player.cosP - rotY_depth * player.sinP; // 旋转后的有效 Z
            double final_depth = rotY_depth * player.cosP + pz_rel * player.sinP; // 旋转后的有效深度
            
            if (final_depth > 0.1) { // 粒子在玩家前方
                double inv_depth = 1.0 / final_depth;
                int screenX = int(SCREEN_WIDTH / 2 + rotX * SCREEN_HEIGHT * inv_depth); // 计算屏幕 X 坐标
                int screenY = int(SCREEN_HEIGHT / 2 - final_pz * (SCREEN_HEIGHT/2) * inv_depth); // 计算屏幕 Y 坐标

                // 限制屏幕坐标范围并绘制小圆点
                if (screenX >= 0 && screenX < SCREEN_WIDTH && screenY >= 0 && screenY < SCREEN_HEIGHT) {
                    int particle_size = max(1, int(5.0 / final_depth)); // 粒子大小随距离变化
                    setfillcolor(p.color); // 设置粒子颜色
                    solidcircle(screenX, screenY, particle_size); // 绘制填充圆
                }
            }
        }

        // 怪物渲染 (与粒子渲染类似)
        for (const auto& m : monsters) {
            double px_rel = m.x - player.x;
            double py_rel = m.y - player.y;
            double pz_rel = PLAYER_HEIGHT - player.z; // 怪物假定在玩家高度 (离地 0.5)

            // 根据玩家偏航角旋转相对坐标
            double rotX = px_rel * player.cosA + py_rel * player.sinA;
            double rotY_depth = py_rel * player.cosA - px_rel * player.sinA;

            // 应用俯仰角旋转
            double final_pz = pz_rel * player.cosP - rotY_depth * player.sinP;
            double final_depth = rotY_depth * player.cosP + pz_rel * player.sinP;
            
            if (final_depth > 0.1) { // 怪物在玩家前方
                double inv_depth = 1.0 / final_depth;
                int screenX = int(SCREEN_WIDTH / 2 + rotX * SCREEN_HEIGHT * inv_depth);
                int screenY = int(SCREEN_HEIGHT / 2 - final_pz * (SCREEN_HEIGHT/2) * inv_depth); 

                // 限制屏幕坐标范围
                if (screenX >= 0 && screenX < SCREEN_WIDTH && screenY >= 0 && screenY < SCREEN_HEIGHT) {
                    // 绘制一个简单的红色矩形作为怪物
                    int monster_width = max(5, int(50.0 / final_depth)); // 怪物大小随距离变化
                    int monster_height = max(5, int(70.0 / final_depth));
                    setfillcolor(RGB(255,0,0)); // 红色
                    solidrectangle(screenX - monster_width/2, screenY - monster_height/2,
                                   screenX + monster_width/2, screenY + monster_height/2);
                }
            }
        }

        // HUD (信息显示)
        settextstyle(16, 0, _T("Consolas")); // 字体大小
        setbkmode(TRANSPARENT); // 文本背景透明

        settextcolor(player.health > 50 ? GREEN : RED); // 生命值根据高低显示不同颜色
        // 注意: to_string() 返回的是 std::string,需要转换为 TCHAR*。
        // 最简单的方法是构建 std::wstring,然后获取其 c_str()。
        // 或者使用 _stprintf_s 或 _snwprintf 等函数。
        // 考虑到兼容性,这里使用 _T() 和 string::c_str(),但这依赖于项目字符集设置正确。
        outtextxy(5, 5, _T(("HP:" + to_string(player.health)).c_str())); // 显示生命值
        
        settextcolor(YELLOW);
        outtextxy(SCREEN_WIDTH - 150, 5, _T(("TREASURES:" + to_string(player.treasures)).c_str())); // 显示宝藏数量
        
        long long elapsedTime = (currentFrameTime - startTime) / 1000; // 游戏已进行时间 (秒)
        settextcolor(WHITE);
        outtextxy(5, 25, _T(("TIME:" + to_string(elapsedTime) + "s").c_str())); // 显示游戏时间

        string selected_block_str_base = "PLACE: ";
        string selected_block_name;
        switch (selectedBlockType) {
            case WALL:      selected_block_name = "墙壁"; break;
            case TREASURE:  selected_block_name = "宝藏"; break;
            case EXIT:      selected_block_name = "出口"; break;
            case MONSTER:   selected_block_name = "怪物"; break;
            case TRAP:      selected_block_name = "陷阱"; break;
            default:        selected_block_name = "未知"; break;
        }
        string full_selected_block_str = selected_block_str_base + selected_block_name;
        settextcolor(CYAN);
        outtextxy(SCREEN_WIDTH - 200, 25, _T(full_selected_block_str.c_str())); // 显示当前选择的放置方块类型

        // 准星
        setlinecolor(WHITE); // 白色
        line(SCREEN_WIDTH/2 - 5, SCREEN_HEIGHT/2, SCREEN_WIDTH/2 + 5, SCREEN_HEIGHT/2); // 水平线
        line(SCREEN_WIDTH/2, SCREEN_HEIGHT/2 - 5, SCREEN_WIDTH/2, SCREEN_HEIGHT/2 + 5); // 垂直线

        // 小地图
        if(showMiniMap){
            int V = 2 * minimap_radius + 1; // 小地图总宽度/高度 (瓷砖数量)
            int tile_size = max(2, min(20, (SCREEN_WIDTH / 4) / V)); // 瓷砖大小,限制在合理范围

            int minimap_draw_width = V * tile_size;
            int minimap_draw_height = V * tile_size;

            int sx = SCREEN_WIDTH - minimap_draw_width - 10; // 小地图绘制起始 X (右侧留边 10 像素)
            int sy = 50; // 小地图绘制起始 Y (在 HUD 下方)

            // 绘制小地图边框
            setlinecolor(RGB(150,150,150)); // 修复: 将GREY替换为RGB值
            rectangle(sx - 1, sy - 1, sx + minimap_draw_width + 1, sy + minimap_draw_height + 1);

            int cx=static_cast<int>(player.x), cy=static_cast<int>(player.y); // 玩家当前所在地图块
            for(int dy_idx=0;dy_idx<V;++dy_idx){ 
                for(int dx_idx=0;dx_idx<V;++dx_idx){
                    int map_dx = dx_idx - minimap_radius; // 相对于玩家的 X 偏移
                    int map_dy = dy_idx - minimap_radius; // 相对于玩家的 Y 偏移

                    int wx = cx + map_dx;
                    int wy = cy + map_dy;
                    
                    if (minimap_flipped) { // 如果小地图翻转,则水平翻转 X 坐标
                        wx = cx - map_dx;
                    }

                    int v=getWorldAt(wx,wy); // 获取该位置的方块类型
                    COLORREF col;
                    switch(v){
                        case WALL:     col=RGB(100,100,100); break; // 墙壁 (深灰色)
                        case TREASURE: col=RGB(255,255,0);    break; // 宝藏 (黄色)
                        case EXIT:     col=RGB(0,255,0);     break; // 出口 (绿色)
                        case MONSTER:  col=RGB(255,0,0);       break; // 怪物 (红色)
                        case TRAP:     col=RGB(139,0,0);  break; // 陷阱 (暗红色)
                        default:       col=backroomsMode ? RGB(255, 230, 100) : RGB(135,206,235); break; // 空地颜色与天空颜色匹配
                    }
                    setfillcolor(col); // 设置填充颜色
                    solidrectangle(sx + dx_idx * tile_size, sy + dy_idx * tile_size,
                                   sx + (dx_idx + 1) * tile_size -1, sy + (dy_idx + 1) * tile_size -1); // 绘制填充矩形
                }
            }
            // 绘制玩家在小地图中心
            setfillcolor(RED);
            solidcircle(sx + minimap_radius * tile_size + tile_size/2, sy + minimap_radius * tile_size + tile_size/2, tile_size/3);

            // 绘制玩家方向箭头
            char arrow_char_raw = ' ';
            double normalized_angle = fmod(player.angle, 2 * PI); // 归一化角度到 [0, 2*PI)
            if (normalized_angle < 0) normalized_angle += 2 * PI;

            // 根据角度选择箭头字符 (8 个方向)
            if (normalized_angle >= (15 * PI / 8.0) || normalized_angle < (PI / 8.0)) arrow_char_raw = '>';
            else if (normalized_angle >= (PI / 8.0) && normalized_angle < (3 * PI / 8.0)) arrow_char_raw = '/'; // 右上
            else if (normalized_angle >= (3 * PI / 8.0) && normalized_angle < (5 * PI / 8.0)) arrow_char_raw = '^';
            else if (normalized_angle >= (5 * PI / 8.0) && normalized_angle < (7 * PI / 8.0)) arrow_char_raw = '\\'; // 左上
            else if (normalized_angle >= (7 * PI / 8.0) && normalized_angle < (9 * PI / 8.0)) arrow_char_raw = '<';
            else if (normalized_angle >= (9 * PI / 8.0) && normalized_angle < (11 * PI / 8.0)) arrow_char_raw = '/'; // 左下
            else if (normalized_angle >= (11 * PI / 8.0) && normalized_angle < (13 * PI / 8.0)) arrow_char_raw = 'v';
            else arrow_char_raw = '\\'; // 右下
            
            settextcolor(RED);
            settextstyle(12, 0, _T("Consolas")); // 箭头使用较小的字体
            // 箭头字符是ASCII,不需要_T(),但是 outtextxy 期望 TCHAR*。
            // 简单处理:将 char 转换为 string,再用 _T()。
            string arrow_str(1, arrow_char_raw);
            outtextxy(sx + minimap_radius * tile_size + tile_size/2 - textwidth(_T("M"))/2, 
                      sy + minimap_radius * tile_size + tile_size/2 - textheight(_T("M"))/2, 
                      _T(arrow_str.c_str()));

            // 恢复 HUD 字体样式
            settextstyle(16, 0, _T("Consolas"));
        }

        // 伤害闪屏效果
        if (damageFlashTimer > 0.0) {
            // EasyX 直接的 alpha 混合比较复杂,这里使用简单粗暴的方案:
            // 直接在整个屏幕上绘制一个实心红色矩形,模拟闪烁效果
            setfillcolor(RGB(255, 0, 0)); // 纯红色
            solidrectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
        }

        // 刷新并准备下一帧
        FlushBatchDraw();
        Sleep(10); // 控制帧率,避免过高
    }
    
    // 结束批量绘制并关闭窗口
    EndBatchDraw();
    closegraph();

    // 根据游戏结束原因显示最终消息
    if (gameEndReason == 2) { // 玩家失败
        displayGameEndScreen("GAME OVER! You ran out of health. Time: " + to_string((GetTickCount() - startTime) / 1000) + "s");
    } else if (gameEndReason == 1) { // 玩家胜利
        displayGameEndScreen("YOU ESCAPED! Treasures collected: " + to_string(player.treasures) + ". Time: " + to_string((GetTickCount() - startTime) / 1000) + "s");
    } else { // 手动退出
        displayGameEndScreen("Thanks for playing!");
    }

    return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...