C++ ASCII 3D无尽跑酷游戏

操作系统 兼容性

Windows11 完全兼容

Windows10 完全兼容

Windows7 待测存疑

键位 操作

w,s,a,d 前后左右移动

Space 跳跃

Shift 冲刺

R 重开

Esc 退出游戏

鼠标 改变视角

许可证简释#

本项目采用 PolyForm Noncommercial License 1.0.0。

商业用途(包括出售、付费SaaS、集成到商业产品)需获取单独授权

商业授权请联系:eod_ilff7k0eh@dingtalk.com

作为版权所有者,我保留未来自行商用的权利。

完整代码:#

#define UNICODE

#define _UNICODE

#include <windows.h>

#include

#include

#include

#include

#include

#include

#include

#include

#include

struct vec2 { float x,y; vec2(float v):x(v),y(v){} vec2(float a,float b):x(a),y(b){} vec2 operator+(vec2 o)const{return {x+o.x,y+o.y};} vec2 operator-(vec2 o)const{return {x-o.x,y-o.y};} vec2 operator*(vec2 o)const{return {xo.x,yo.y};} };

struct vec3 {

float x,y,z;

vec3():x(0),y(0),z(0){}

vec3(float v):x(v),y(v),z(v){}

vec3(float a,float b,float c):x(a),y(b),z©{}

vec3(float a,vec2 v):x(a),y(v.x),z(v.y){}

vec3 operator+(vec3 o)const{return {x+o.x,y+o.y,z+o.z};}

vec3 operator-(vec3 o)const{return {x-o.x,y-o.y,z-o.z};}

vec3 operator*(vec3 o)const{return {xo.x,y o.y,zo.z};}
vec3 operator/(vec3 o)const{return {x/o.x,y/o.y,z/o.z};}
vec3 operator-()const{return {-x,-y,-z};}
vec3 operator
(float s) const { return {xs, y s, zs}; }
friend vec3 operator
(float s, const vec3& v) { return v * s; }

};

inline float clamp(float v,float a,float b){ return fmaxf(fminf(v,b),a); }

inline float sign(float a){ return (0<a)-(a<0); }

inline float length(vec3 v){ return sqrtf(v.xv.x+v.y v.y+v.zv.z); }
inline vec3 norm(vec3 v){
float len = length(v);
if(len < 1e-8f) return {0,1,0};
return v/len;
}
inline float dot(vec3 a,vec3 b){ return a.x
b.x+a.yb.y+a.z b.z; }

inline vec3 cross(vec3 a,vec3 b){ return {a.yb.z-a.z b.y, a.zb.x-a.x b.z, a.xb.y-a.yb.x}; }

vec2 box(vec3 ro,vec3 rd,vec3 halfSize,vec3& n){

vec3 inv=vec3(1.0f)/rd;

vec3 t1=(-ro-halfSize)*inv, t2=(-ro+halfSize)*inv;

vec3 tMin={fminf(t1.x,t2.x),fminf(t1.y,t2.y),fminf(t1.z,t2.z)};

vec3 tMax={fmaxf(t1.x,t2.x),fmaxf(t1.y,t2.y),fmaxf(t1.z,t2.z)};

float tn=fmaxf(fmaxf(tMin.x,tMin.y),tMin.z);

float tf=fminf(fminf(tMax.x,tMax.y),tMax.z);

if(tn>tf||tf<0.0f)return vec2(-1.0f);

if(tMin.x>tMin.y&&tMin.x>tMin.z)n=vec3(-sign(rd.x),0,0);

else if(tMin.y>tMin.x&&tMin.y>tMin.z)n=vec3(0,-sign(rd.y),0);

else n=vec3(0,0,-sign(rd.z));

return vec2(tn,tf);

}

void HideCursor(){

HANDLE H=GetStdHandle(STD_OUTPUT_HANDLE);

CONSOLE_CURSOR_INFO ci;

GetConsoleCursorInfo(H,&ci);

ci.bVisible=FALSE;

SetConsoleCursorInfo(H,&ci);

}

POINT GetConsoleCenter(){

HWND h=GetConsoleWindow();

if(!h){POINT c;c.x=GetSystemMetrics(SM_CXSCREEN)/2;c.y=GetSystemMetrics(SM_CYSCREEN)/2;return c;}

RECT wa;SystemParametersInfo(SPI_GETWORKAREA,0,&wa,0);

int ww=(wa.right-wa.left)*0.7, wh=(wa.bottom-wa.top)*0.7;

MoveWindow(h,(wa.left+wa.right-ww)/2,(wa.top+wa.bottom-wh)/2,ww,wh,TRUE);

RECT wr;GetWindowRect(h,&wr);

return {(wr.left+wr.right)/2,(wr.top+wr.bottom)/2};

}

void GetConsoleSize(HANDLE H, int& w, int& h){

CONSOLE_SCREEN_BUFFER_INFO csbi;

GetConsoleScreenBufferInfo(H, &csbi);

w = csbi.srWindow.Right - csbi.srWindow.Left + 1;

h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;

}

void FitBufferToWindow(HANDLE H, int w, int h){

COORD bufSize = { (SHORT)w, (SHORT)h };

SetConsoleScreenBufferSize(H, bufSize);

SMALL_RECT windowRect = { 0, 0, (SHORT)(w-1), (SHORT)(h-1) };

SetConsoleWindowInfo(H, TRUE, &windowRect);

}

struct BoxCol{ vec3 c; vec3 hs; };

const float PR=0.4f;

const int CI=8;

std::vector ground, walls;

std::mt19937 rng(std::random_device{}());

float nextZ=0;

float lastWallZ = -1e9f;

float lastNeoZ = -100.0f;

int neoCooldown = 0;

bool neoActive = false;

const float GRAVITY = -18.0f;

const float JUMP_VEL = 9.5f;

const float GROUND_ACC = 18.0f;

const float AIR_ACC = 0.5f;

const float MAX_SPEED = 8.0f;

const float GROUND_FRICT = 12.0f;

const float AIR_FRICT = 0.5f;

const float WALL_BOUNCE = 0.2f;

const float WALL_BRAKE = 1.00f;

const float MIN_WALL_GAP = 3.0f;

const size_t MAX_OBJECTS = 300;

void resolveSphereBox(vec3& p, vec3& vel, float r, const BoxCol& b, float bounce=0.0f, bool isWall=false){

vec3 cl;

cl.x=clamp(p.x, b.c.x-b.hs.x, b.c.x+b.hs.x);

cl.y=clamp(p.y, b.c.y-b.hs.y, b.c.y+b.hs.y);

cl.z=clamp(p.z, b.c.z-b.hs.z, b.c.z+b.hs.z);

vec3 d = p-cl;

float l = length(d);

if(l < r){

vec3 n;

if(l < 0.001f){

vec3 tc = p - b.c;

float ax = fabsf(tc.x), ay = fabsf(tc.y), az = fabsf(tc.z);

if(ax >= ay && ax >= az) n = vec3(sign(tc.x), 0, 0);

else if(ay >= ax && ay >= az) n = vec3(0, sign(tc.y), 0);

else n = vec3(0, 0, sign(tc.z));

if(length(n) < 0.5f) n = vec3(0,1,0);

n = norm(n);

} else {

n = d / l;

}

p = cl + n * r;

float vdotn = dot(vel, n);

if(vdotn < 0){

vel = vel - (1.0f+bounce)*vdotn * n;

if(isWall && fabs(n.y) < 0.1f){

vel.x *= WALL_BRAKE;

vel.z *= WALL_BRAKE;

}

}

}

}

void resolveAll(vec3& p, vec3& vel){

for(int i=0;i<CI;++i){

for(auto&g:ground) resolveSphereBox(p,vel,PR,g,0.0f, false);

for(auto&w:walls) resolveSphereBox(p,vel,PR,w,WALL_BOUNCE, true);

}

}

bool onGround(const vec3& p, const vec3& vel){

vec3 t=p; t.y -= PR+0.05f;

for(auto&g:ground){

vec3 c;c.x=clamp(t.x,g.c.x-g.hs.x,g.c.x+g.hs.x);

c.y=clamp(t.y,g.c.y-g.hs.y,g.c.y+g.hs.y);

c.z=clamp(t.z,g.c.z-g.hs.z,g.c.z+g.hs.z);

if(length(t-c)<PR) return true;

}

return false;

}

void clearScene(){ ground.clear(); walls.clear(); nextZ=0; lastWallZ = -1e9f; lastNeoZ = -100.0f; neoCooldown = 0; neoActive = false; }

void genUntil(float targetZ){

std::uniform_real_distribution gap(0,1), wallCh(0,1), wallH(0.8f,2.5f), wallT(0.25f,0.45f);

while(nextZ < targetZ && ground.size() + walls.size() < MAX_OBJECTS){

if (neoCooldown == 0 && gap(rng) < 0.05f && nextZ - lastNeoZ > 15.0f) {

neoActive = true;

lastNeoZ = nextZ;

}

复制代码
    if (neoActive) {
        float frontZ = nextZ - 1.0f;
        bool hasFront = false;
        for (auto& g : ground) {
            if (g.c.z >= frontZ && g.c.z <= frontZ + 1.0f) { hasFront = true; break; }
        }
        if (!hasFront) {
            ground.push_back({ vec3(0, -0.5f, frontZ + 0.5f), {1.5f, 0.5f, 0.5f} });
        }
        for (int i = 0; i < 1; ++i) {
            float wallZ = nextZ + 0.5f + i;
            float h = 10.0f;
            float t = 0.1f;
            walls.push_back({vec3(0, h*0.5f, wallZ), {1.45f, h*0.5f, t*0.5f}});
            lastWallZ = wallZ;
            ground.push_back({vec3(0, -0.5f, wallZ), {1.5f, 0.5f, 0.5f}});
        }
        float backZ = nextZ + 1.0f;
        bool hasBack = false;
        for (auto& g : ground) {
            if (g.c.z >= backZ && g.c.z <= backZ + 1.0f) { hasBack = true; break; }
        }
        if (!hasBack) {
            ground.push_back({ vec3(0, -0.5f, backZ + 0.5f), {1.5f, 0.5f, 0.5f} });
        }
        nextZ += 3;
        neoActive = false;
        neoCooldown = 20;
        continue;
    }

    if (neoCooldown > 0) neoCooldown--;

    if(gap(rng) < 0.22f){ nextZ += 1.0f; continue; }
    ground.push_back({vec3(0, -0.5f, nextZ+0.5f), {1.5f, 0.5f, 0.5f}});
    if(wallCh(rng) < 0.32f){
        float newWallZ = nextZ+0.5f;
        if(newWallZ - lastWallZ >= MIN_WALL_GAP){
            float h = wallH(rng);
            float t = wallT(rng);
            walls.push_back({vec3(0, h*0.5f, newWallZ), {1.45f, h*0.5f, t*0.5f}});
            lastWallZ = newWallZ;
        }
    }
    nextZ += 1.0f;
}

}

void updateScene(const vec3& pp){

if(!std::isfinite(pp.x) || !std::isfinite(pp.y) || !std::isfinite(pp.z)){

clearScene();

return;

}

float cl = pp.z - 45.0f;

ground.erase(std::remove_if(ground.begin(), ground.end(),

cl(BoxCol& g){ return g.c.z < cl; }), ground.end());

walls.erase(std::remove_if(walls.begin(), walls.end(),

cl(BoxCol& w){ return w.c.z < cl; }), walls.end());

genUntil(pp.z + 18.0f);

}

float g_bestDist = 0.0f;

const char* HIGHSCORE_FILE = "highscore.dat";

void loadHighScore() {

std::ifstream ifs(HIGHSCORE_FILE);

if (ifs.is_open()) {

ifs >> g_bestDist;

ifs.close();

}

}

void saveHighScore(float newDist) {

if (newDist > g_bestDist) {

g_bestDist = newDist;

std::ofstream ofs(HIGHSCORE_FILE);

if (ofs.is_open()) {

ofs << g_bestDist;

ofs.close();

}

}

}

void writeHUD(wchar_t* buffer, int screenW, float dist, float bestDist, bool dead,

float sprintTimer, float sprintCooldown)

{

wchar_t line240;

const wchar_t* stateStr = L"";

if (sprintTimer > 0.0f) stateStr = L"SPRINT!";

else if (sprintCooldown > 0.0f) {

swprintf(line, 240, L"COOL:%.1f", sprintCooldown);

stateStr = line;

}

else stateStr = L"READY";

复制代码
int len = swprintf(line, 240, L"  Distance: %.0f m   Best: %.0f m   %s %s",
                     dist, bestDist, dead ? L"DEAD" : L"", stateStr);
for (int i = 0; i < screenW; ++i) {
    if (i < len)
        buffer[i] = line[i];
    else
        buffer[i] = L' ';
}

}

int main(){

HideCursor();

HANDLE H=CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE,0,NULL,CONSOLE_TEXTMODE_BUFFER,NULL);

SetConsoleActiveScreenBuffer(H);

复制代码
int w, h;
Sleep(100);
GetConsoleSize(H, w, h);
if (w <= 0 || h <= 0) { w = 240; h = 60; }

FitBufferToWindow(H, w, h);

float asp=(float)w/h;
float pa = 0.4583f;
char grad[]=" .:!/r(l1Z4H9W8$@"; int gs=sizeof(grad)-2;
wchar_t* scr=new wchar_t[w*h];
DWORD dw=0;

POINT c=GetConsoleCenter(); SetCursorPos(c.x,c.y);

ground.reserve(256);
walls.reserve(128);

loadHighScore();

vec3 pos(0, 1.5f, -8);
vec3 vel(0,0,0);
float yaw=0.0f, pitch=0, ms=0.0025f;
bool grounded = false;
enum{PLAYING,LOST} st=PLAYING;
bool run=true;
auto lt=std::chrono::steady_clock::now();

vec3 ld=norm(vec3(-0.5f, 0.8f, -1.0f));

clearScene();
nextZ = pos.z;
lastWallZ = -1e9f;
genUntil(pos.z+18.f);

float sprintTimer = 0.0f;
float sprintCooldown = 0.0f;

int frameCounter = 0;

while(run){
    auto nt=std::chrono::steady_clock::now();
    float dt=std::chrono::duration<float>(nt-lt).count(); lt=nt;
    if(dt>0.033f) dt=0.033f;
    if(GetAsyncKeyState(VK_ESCAPE)&0x8000) run=false;

    if(GetAsyncKeyState('R')&0x8000){
        float curDist = pos.z + 8.0f;
        if (curDist > g_bestDist) saveHighScore(curDist);
        pos=vec3(0,1.5f,-8); vel=vec3(0,0,0); yaw=0.0f; pitch=0;
        st=PLAYING;
        clearScene(); nextZ=pos.z; lastWallZ = -1e9f; genUntil(pos.z+18.f);
        sprintTimer = 0.0f;
        sprintCooldown = 0.0f;
    }

    POINT mp; GetCursorPos(&mp); int dx=mp.x-c.x, dy=mp.y-c.y;
    if(dx||dy){
        if(st==PLAYING){ yaw -= dx*ms; pitch -= dy*ms; pitch=clamp(pitch,-1.4f,1.4f); }
        SetCursorPos(c.x,c.y);
    }
    vec3 fwd(cosf(pitch)*sinf(yaw), sinf(pitch), cosf(pitch)*cosf(yaw));
    vec3 rgt=norm(cross(fwd,vec3(0,1,0)));
    vec3 up=cross(rgt,fwd);

    updateScene(pos);

    if(st==PLAYING){
        vec3 moveDir(0,0,0);
        if(GetAsyncKeyState('W')&0x8000) moveDir = moveDir + vec3(fwd.x,0,fwd.z);
        if(GetAsyncKeyState('S')&0x8000) moveDir = moveDir - vec3(fwd.x,0,fwd.z);
        if(GetAsyncKeyState('A')&0x8000) moveDir = moveDir - rgt;
        if(GetAsyncKeyState('D')&0x8000) moveDir = moveDir + rgt;
        float lenM = length(moveDir);
        if(lenM>0.001f) moveDir = moveDir/lenM; else moveDir=vec3(0,0,0);

        grounded = onGround(pos, vel);

        if (sprintTimer > 0) {
            sprintTimer -= dt;
            if (sprintTimer < 0) sprintTimer = 0.0f;
        }
        if (sprintCooldown > 0) {
            sprintCooldown -= dt;
            if (sprintCooldown < 0) sprintCooldown = 0.0f;
        }

        if ((GetAsyncKeyState(VK_LSHIFT) & 0x8000) || (GetAsyncKeyState(VK_RSHIFT) & 0x8000)) {
            if (sprintTimer <= 0.0f && sprintCooldown <= 0.0f && grounded) {
                sprintTimer = 0.6f;
                sprintCooldown = 1.2f;
            }
        }

        float currentMaxSpeed = MAX_SPEED;
        float currentGroundAcc = GROUND_ACC;
        if (sprintTimer > 0.0f) {
            currentMaxSpeed = MAX_SPEED * 2.0f;
            currentGroundAcc = GROUND_ACC * 1.5f;
        }

        float accScale = grounded ? currentGroundAcc : AIR_ACC;
        vec3 acc = moveDir * accScale;
        acc.y += GRAVITY;
        vel = vel + acc * dt;

        if(!grounded){
            vel.x *= (1.0f - AIR_FRICT*dt);
            vel.z *= (1.0f - AIR_FRICT*dt);
        }else{
            float spdHor = sqrtf(vel.x*vel.x + vel.z*vel.z);
            if(spdHor > 0.01f){
                float newSpd = fmaxf(0.0f, spdHor - GROUND_FRICT*dt);
                vel.x *= newSpd/spdHor;
                vel.z *= newSpd/spdHor;
            }else vel.x=vel.z=0;
        }

        float horSpeed = sqrtf(vel.x*vel.x + vel.z*vel.z);
        if(horSpeed > currentMaxSpeed){
            vel.x *= currentMaxSpeed/horSpeed;
            vel.z *= currentMaxSpeed/horSpeed;
        }

        if(GetAsyncKeyState(VK_SPACE)&0x8000 && grounded){
            vel.y = JUMP_VEL;
            grounded = false;
        }

        vec3 newPos = pos + vel * dt;
        resolveAll(newPos, vel);
        pos = newPos;

        grounded = onGround(pos, vel);
        if(grounded && vel.y < 0) vel.y = 0;

        if(pos.y < -5.0f) {
            st = LOST;
            float curDist = pos.z + 8.0f;
            if (curDist > g_bestDist) saveHighScore(curDist);
        }

        if(!std::isfinite(pos.x) || !std::isfinite(pos.y) || !std::isfinite(pos.z)){
            pos=vec3(0,1.5f,-8); vel=vec3(0,0,0); yaw=0.0f; pitch=0;
            st=PLAYING;
            clearScene(); nextZ=pos.z; lastWallZ=-1e9f; genUntil(pos.z+18.f);
            sprintTimer = sprintCooldown = 0.0f;
        }
    }

    float dist = pos.z + 8.0f; if(dist < 0) dist = 0;
    if(++frameCounter % 10 == 0){
        std::string title=st==PLAYING ? "无尽跑酷 | NEO挑战 | 距离:"+std::to_string((int)dist)+"m  |  R重置" :
                                      "坠落! 距离:"+std::to_string((int)dist)+"m |  R重新开始";
        SetConsoleTitleA(title.c_str());
    }

    CONSOLE_SCREEN_BUFFER_INFO csbi;
    GetConsoleScreenBufferInfo(H, &csbi);
    int visW = csbi.srWindow.Right - csbi.srWindow.Left + 1;
    int visH = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
    int left = csbi.srWindow.Left;
    int top  = csbi.srWindow.Top;

    if (visW != w || visH != h) {
        w = visW;
        h = visH;
        FitBufferToWindow(H, w, h);
        delete[] scr;
        scr = new wchar_t[w * h];
        asp = (float)w / h;
    }

    float viewZ = pos.z;
    for(int i=0;i<w;++i){
        for(int j=0;j<h;++j){
            vec2 uv((float)i/w,(float)j/h); uv=uv*2.f-1.f; uv.x*=asp*pa; uv.y*=-1;
            vec3 rd=norm(fwd + rgt*uv.x + up*uv.y);
            float tmin=1e9f; vec3 n; float alb=1; bool hit=false;

            for(auto&g:ground){
                if(g.c.z < viewZ - 1.5f) continue;
                vec3 bn; vec2 t=box(pos-g.c, rd, g.hs, bn);
                if(t.x>0 && t.x<tmin){ tmin=t.x; n=bn; alb=0.75f; hit=true; }
            }
            for(auto&w:walls){
                if(w.c.z < viewZ - 1.5f) continue;
                vec3 bn; vec2 t=box(pos-w.c, rd, w.hs, bn);
                if(t.x>0 && t.x<tmin){ tmin=t.x; n=bn; alb=1.0f; hit=true; }
            }

            float diff=0;
            if(hit){
                float amb=0.2f, ndl=fmaxf(0.f,dot(n,ld));
                diff=(amb+ndl)*alb;
                diff=clamp(diff,0.f,1.f);
            }
            int idx=(int)(diff*gs); idx=(int)clamp((float)idx,0.0f,(float)gs);
            scr[i+j*w]=grad[idx];
        }
    }

    writeHUD(scr, w, dist, g_bestDist, st == LOST, sprintTimer, sprintCooldown);

    for (int j = 0; j < h; ++j) {
        COORD pos = { (SHORT)left, (SHORT)(top + j) };
        DWORD written;
        WriteConsoleOutputCharacterW(H, scr + j * w, w, pos, &written);

        int bufW = csbi.dwSize.X;
        if (bufW > left + w) {
            int clearLen = bufW - (left + w);
            std::wstring spaces(clearLen, L' ');
            COORD fillPos = { (SHORT)(left + w), (SHORT)(top + j) };
            WriteConsoleOutputCharacterW(H, spaces.c_str(), clearLen, fillPos, &written);
        }
    }
}

saveHighScore(pos.z + 8.0f);
delete[]scr;
CloseHandle(H);
return 0;

}

编译时加入命令:

-std=c++11 -lpthread -O3 -fopenmp

相关推荐
杜子不疼.1 小时前
【C++ 在线五子棋对战】- 会话管理模块实现
开发语言·c++
有点。1 小时前
C++深度优先搜索(DFS)的概念(一)
开发语言·c++·深度优先
旖-旎1 小时前
《LeetCode 978 最长湍流子数组 || LeetCode 139 单词拆分》
c++·算法·leetcode·动态规划
aaPIXa6222 小时前
C++大型项目模块化拆分实战记录
开发语言·c++
石山代码2 小时前
C++23 新特性在 CLion 中的实战体验
开发语言·c++·c++23
在线OJ的阿川2 小时前
仿muduo库实现高并发服务器
linux·服务器·c++
资源分享交流2 小时前
TripoSplat 本地整合包:一张图片生成 3D Gaussian 模型
3d·ai
坚定学代码2 小时前
C++ SOLID 原则(上):单一职责、开闭原则与里氏替换
c++
ysa0510302 小时前
【板子】欧拉序
c++·笔记·算法·深度优先·图论