基于DEVC++实现一个控制台的赛车游戏-02-实现赛车游戏

控制台赛车躲避游戏




控制台赛车躲避游戏 ------ 设计与实现

适用对象 :已掌握 C++ 基础、循环、分支、数组、自定义函数的学生
最终效果:可移动小车、自动下落敌人、计分、碰撞、菜单界面


一、课程目标

  1. 学会使用自定义函数进行模块化程序设计
  2. 掌握控制台游戏的核心原理:定位 + 擦除 + 重绘 + 延时
  3. 理解按键控制、动画、随机数、碰撞检测的简单实现
  4. 完成一个可独立运行的图形化小游戏

二、游戏功能说明

  • 玩家通过 A/D 键 控制小车左右移动
  • 上方不断有敌方车辆向下行驶
  • 躲避车辆,不发生碰撞
  • 每成功躲过一辆车 得分+1
  • 发生碰撞 → 游戏结束,返回主菜单
  • 包含:主菜单、游戏说明、游戏界面、得分显示

三、整体程序结构(模块化设计)

程序由 9 类函数 + 全局数据构成,一个函数只完成一件事:

  1. gotoxy 光标定位(核心)
  2. setcursor 隐藏光标
  3. drawBorder 绘制赛道边框
  4. drawCar 绘制玩家小车
  5. drawEnemy 绘制敌方车辆
  6. eraseCar 擦除玩家(动画用)
  7. eraseEnemy 擦除敌人(动画用)
  8. collision 碰撞检测
  9. play 游戏主循环
  10. main 菜单与程序入口

四、知识点回顾(学生已掌握)

  • 变量、数组、循环、判断
  • 自定义函数定义与调用
  • while / for 循环
  • 简单输入输出 cin / cout

五、游戏新增必备知识点

1. 光标定位 gotoxy(x, y)

作用:把光标跳到指定坐标输出文字,是游戏画面的基础。

需要:

  • HANDLE 控制台句柄
  • COORD 坐标结构体
  • SetConsoleCursorPosition

2. 隐藏光标 setcursor

作用:让游戏画面不闪烁、更整洁。

3. 按键检测

  • kbhit() 判断是否有按键按下
  • getch() 获取按键(不回显)

4. 延时 Sleep(ms)

作用:控制游戏速度,实现流畅动画。

5. 随机数 rand() / srand()

作用:让敌人每次在不同车道出现。

6. 动画原理

擦除旧位置 → 更新坐标 → 绘制新位置 → 延时

循环执行 → 看起来在动。


六、分步实现过程

第 1 步:定义头文件与全局数据

用于存放:屏幕大小、车辆坐标、分数、图案。

cpp 复制代码
// 1. 输入输出流头文件:用于控制台显示文字和图形
#include<iostream>
// 2. 控制台输入头文件:用于检测按键 kbhit() 和获取按键 getch()
#include<conio.h>
// 3. Windows系统API:用于控制控制台窗口(光标定位、隐藏光标等)
#include <windows.h>
// 4. 时间头文件:用于设置随机数种子,让敌人每次出现位置不同
#include <time.h>

// 宏定义:定义游戏屏幕的总宽度(固定数值,方便修改)
#define SCREEN_WIDTH 90
// 宏定义:定义游戏屏幕的总高度
#define SCREEN_HEIGHT 26
// 宏定义:定义游戏车道的宽度(小车行驶的区域)
#define WIN_WIDTH 70 

// 使用标准命名空间:简化代码,不用写 std::cout
using namespace std; 

// 控制台句柄:相当于控制台的"遥控器",用来控制光标和输出
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
// 光标坐标结构体:用来存储光标的 X、Y 坐标
COORD CursorPosition;

// 敌人车辆的 Y 坐标数组(存储3个敌人的垂直位置)
int enemyY[3];
// 敌人车辆的 X 坐标数组(存储3个敌人的水平位置)
int enemyX[3];
// 敌人状态标记:1=显示敌人,0=隐藏敌人
int enemyFlag[3];

// 玩家小车的图案:用 4行×4列 的字符数组拼成小车形状
char car[4][4] = {
    ' ','#','#',' ',
    '#','#','#','#',
    ' ','#','#',' ',
    '#','#','#','#'
};

// 玩家小车的水平位置(初始在车道正中间)
int carPos = WIN_WIDTH/2;
// 游戏得分变量,初始为0,躲避成功后加分
int score = 0; 

第 2 步:实现工具函数

(1)光标定位

cpp 复制代码
// 函数功能:将光标移动到控制台的 (x, y) 坐标位置
// 参数 x:水平坐标(列)
// 参数 y:垂直坐标(行)
void gotoxy(int x, int y){
    // 设置光标要移动到的 X 坐标(水平位置)
    CursorPosition.X = x;
    // 设置光标要移动到的 Y 坐标(垂直位置)
    CursorPosition.Y = y;
    // 调用Windows系统API,执行光标移动
    SetConsoleCursorPosition(console, CursorPosition);
}

(2)隐藏光标

cpp 复制代码
// 函数功能:设置控制台光标的显示状态和大小
// 参数 visible:true=显示光标,false=隐藏光标
// 参数 size:光标的大小(取值 1-100),传入 0 则使用默认值
void setcursor(bool visible, DWORD size) {

    // 如果传入的尺寸为0,设置为默认大小 20(固定写法,不用改)
    if(size == 0) 
        size = 20;

    // 定义一个 光标信息结构体 变量,用来存放光标的属性
    CONSOLE_CURSOR_INFO lpCursor;

    // 设置光标是否可见(true显示 / false隐藏)
    lpCursor.bVisible = visible;

    // 设置光标的大小(1~100,数字越大光标越粗)
    lpCursor.dwSize = size;

    // 把设置好的光标信息应用到控制台(生效)
    SetConsoleCursorInfo(console,&lpCursor);
}

第 3 步:绘制函数

(1)绘制赛道边框

cpp 复制代码
// 函数功能:绘制游戏的边界线(车道左右边框 + 最右侧边框)
void drawBorder(){
    // 外层循环:遍历屏幕的每一行(垂直方向 i 代表行号)
    for(int i=0; i<SCREEN_HEIGHT; i++){
        // 内层循环:绘制左右两侧的双线边框(j 控制边框粗细)
        for(int j=0; j<17; j++){
            // 在第 i 行、左侧 0+j 列 绘制左边界符号
            gotoxy(0+j,i); 
            cout<<"±";
            // 在第 i 行、右侧 WIN_WIDTH-j 列 绘制右边界符号
            gotoxy(WIN_WIDTH-j,i); 
            cout<<"±";
        }
    }

    // 额外绘制最右侧的单条边框(整个屏幕的右边缘)
    for(int i=0; i<SCREEN_HEIGHT; i++){
        gotoxy(SCREEN_WIDTH,i); 
        cout<<"±";
    }
}

(2)绘制玩家小车

cpp 复制代码
// 函数功能:在控制台中绘制玩家控制的小车
void drawCar(){
    // 外层循环:遍历小车的 4 行(i 代表行号)
    for(int i=0; i<4; i++){
        // 内层循环:遍历小车的 4 列(j 代表列号)
        for(int j=0; j<4; j++){
            // 定位到小车对应字符的控制台坐标
            // j+carPos:水平位置 = 小车初始X坐标 + 字符在图案中的列
            // i+22:垂直位置 = 固定在屏幕下方 22 行的位置
            gotoxy(j+carPos, i+22);
            
            // 输出二维数组中存储的对应字符(# 或 空格),拼成小车图案
            cout << car[i][j];
        }
    }
}

(3)绘制敌人小车

cpp 复制代码
// 函数功能:绘制敌方车辆
// 参数 ind:要绘制的敌人编号(0代表第一个敌人,1代表第二个敌人)
void drawEnemy(int ind){
    // 判断:只有当前敌人处于激活状态(enemyFlag[ind]为true)时,才进行绘制
    if(enemyFlag[ind]==true){
        // 定位到敌人的 (X,Y) 坐标,绘制敌人第一行
        gotoxy(enemyX[ind], enemyY[ind]);   
        cout<<"****";
        // 定位到敌人的下一行,绘制敌人第二行
        gotoxy(enemyX[ind], enemyY[ind]+1); 
        cout<<" ** ";
        // 定位到敌人的下一行,绘制敌人第三行
        gotoxy(enemyX[ind], enemyY[ind]+2); 
        cout<<"****";
        // 定位到敌人的下一行,绘制敌人第四行
        gotoxy(enemyX[ind], enemyY[ind]+3); 
        cout<<" ** ";
    }
}

第 4 步:擦除函数(实现动画)

cpp 复制代码
// 函数功能:擦除玩家小车(用空格覆盖原来的位置)
// 作用:实现动画效果 ------ 先擦除,再画新位置
void eraseCar(){
    // 遍历小车的 4 行(i 是行)
    for(int i=0; i<4; i++){
        // 遍历小车的 4 列(j 是列)
        for(int j=0; j<4; j++){
            // 定位到小车原来的位置
            gotoxy(j+carPos, i+22); 
            
            // 输出空格 → 把原来的 # 覆盖掉(擦除)
            cout<<" ";
        }
    }
}

// 函数功能:擦除指定编号的敌人车辆
// 参数 ind:敌人的编号(0=第一个敌人,1=第二个敌人)
void eraseEnemy(int ind){
    // 只有敌人处于显示状态时,才需要擦除
    if(enemyFlag[ind]==true){
        // 定位到敌人第一行 → 输出4个空格擦除
        gotoxy(enemyX[ind], enemyY[ind]);   
        cout<<"    ";
        
        // 定位到敌人第二行 → 擦除
        gotoxy(enemyX[ind], enemyY[ind]+1); 
        cout<<"    ";
        
        // 定位到敌人第三行 → 擦除
        gotoxy(enemyX[ind], enemyY[ind]+2); 
        cout<<"    ";
        
        // 定位到敌人第四行 → 擦除
        gotoxy(enemyX[ind], enemyY[ind]+3); 
        cout<<"    ";
    }
}

擦除函数 = 动画的关键

控制台不能直接移动图形,必须:

→ 先擦除旧位置

→ 再更新坐标

→ 再画新位置

擦除方法

用空格覆盖原来的 # 或 *

位置完全对应

擦除的坐标 必须和绘制时一模一样,否则画面会乱。

第 5 步:游戏逻辑函数

(1)随机生成敌人位置

cpp 复制代码
// 函数功能:生成敌人的随机 X 坐标(让敌人在车道里随机位置出现)
// 参数 ind:敌人编号(0表示第一个敌人,1表示第二个敌人)
void genEnemy(int ind){
    // 17:保证敌人出现在**车道内部**(不会卡在左边界)
    // rand()%33:生成 0~32 的随机数,控制敌人在车道内左右随机分布
    // 最终 enemyX[ind] 范围:17 ~ 49,正好在游戏车道内
    enemyX[ind] = 17 + rand()%33;
}
  • rand()%33 → 生成0~32的随机数
  • 加上17 → 让敌人只在车道中间区域出现,不会跑到边框外
  • 每次调用,敌人都会换一个横向位置,实现随机车道效果

(2)重置敌人位置

cpp 复制代码
// 函数功能:重置敌人车辆(当敌人开到屏幕底部后,重新回到顶部)
// 参数 ind:要重置的敌人编号(0=第一个敌人,1=第二个敌人)
void resetEnemy(int ind){
    // 1. 先擦除当前敌人在屏幕底部的旧图像
    eraseEnemy(ind);
    
    // 2. 把敌人的Y坐标设置为1(回到屏幕最上方)
    enemyY[ind] = 1;
    
    // 3. 重新生成一个随机的X坐标(让敌人换个车道出现)
    genEnemy(ind);
}

这个函数就是让敌人无限循环出现的关键:

  1. 敌人跑到屏幕下面
  2. 调用 resetEnemy
  3. 擦除旧位置 → 放回顶部 → 换个随机车道
  4. 游戏就可以一直玩下去

(3)碰撞检测

cpp 复制代码
// 函数功能:碰撞检测
// 作用:判断玩家小车和敌人小车是否撞上了
// 返回值:1 = 碰撞,0 = 未碰撞
int collision(){
    // 第一层判断:敌人的Y坐标 + 自身高度(4行) 到达 玩家小车的Y坐标(23行)
    // 意思是:敌人已经下落到玩家所在的高度了
    if(enemyY[0]+4 >= 23){
        // 第二层判断:敌人和玩家的 X 坐标区间发生重叠
        // enemyX[0]+4 是敌人的右侧位置,carPos 是玩家的左侧位置
        // 两者差值在 0~9 之间,说明两车宽度重叠,判定为相撞
        if(enemyX[0]+4 - carPos >=0 && enemyX[0]+4 - carPos <9)
            return 1;  // 两个条件都满足,发生碰撞,返回 1
    }
    return 0;  // 不满足条件,没有碰撞,返回 0
}
  1. 先看高度:敌人是不是掉到玩家的高度了
  2. 再看左右:敌人和玩家的左右位置是不是重叠了
  3. 两个条件都满足撞车 → 返回 1
  4. 否则 → 没撞 → 返回 0

(4)更新分数

cpp 复制代码
// 函数功能:更新并显示游戏得分
void updateScore(){
    // 定位光标到屏幕右侧固定位置
    // WIN_WIDTH + 7:车道右侧区域(不遮挡游戏画面)
    // 5:垂直第5行
    gotoxy(WIN_WIDTH + 7, 5);
    
    // 输出当前得分(中文显示)
    cout<<"得分: "<<score<<endl;
}

第 6 步:界面函数

(1)游戏结束

cpp 复制代码
void gameover(){
    system("cls");
    cout<<endl;
    cout<<"\t\t--------------------------"<<endl;
    cout<<"\t\t------- 游戏结束 -------"<<endl;
    cout<<"\t\t--------------------------"<<endl<<endl;
    cout<<"\t\t按任意键返回主菜单.";
    getch();
}

(2)游戏说明

cpp 复制代码
void instructions(){
    system("cls");
    cout<<"游戏说明";
    cout<<"\n----------------";
    cout<<"\n 通过左右移动躲避车辆。 ";
    cout<<"\n\n 按 A 键向左移动";
    cout<<"\n 按 D 键向右移动";
    cout<<"\n 按 ESC 键退出游戏";
    cout<<"\n\n按任意键返回主菜单";
    getch();
}

第 7 步:游戏主循环 play()

游戏核心运行逻辑:

  • 按键控制
  • 绘制与擦除
  • 敌人移动
  • 碰撞与计分
cpp 复制代码
// 函数功能:游戏主逻辑函数(游戏核心运行代码)
void play(){
    // 1. 初始化玩家小车位置:居中显示
    carPos = -1 + WIN_WIDTH/2;
    // 2. 初始化游戏得分:0分
    score = 0;
    // 3. 第一个敌人默认激活(显示)
    enemyFlag[0] = 1;
    // 4. 第二个敌人初始未激活(隐藏)
    enemyFlag[1] = 0;
    // 5. 两个敌人都从屏幕最顶部开始
    enemyY[0] = enemyY[1] = 1;

    // 清空控制台屏幕
    system("cls");
    // 绘制游戏赛道边框
    drawBorder();
    // 显示初始得分(0分)
    updateScore();
    // 随机生成第一个敌人的X坐标
    genEnemy(0);
    // 随机生成第二个敌人的X坐标
    genEnemy(1);

    // 在屏幕右侧显示游戏标题
    gotoxy(WIN_WIDTH + 7, 2);cout<<"赛车游戏";
    // 显示操作说明:A键左移
    gotoxy(WIN_WIDTH + 2, 14);cout<<" A 键 - 左移";
    // 显示操作说明:D键右移
    gotoxy(WIN_WIDTH + 2, 15);cout<<" D 键 - 右移";

    // 提示玩家按任意键开始
    gotoxy(18, 5);cout<<"按任意键开始游戏";
    // 等待玩家按一个键
    getch();
    // 清空提示文字(用空格覆盖)
    gotoxy(18, 5);cout<<"                      ";

    // 游戏主死循环(每一帧执行一次)
    while(1){
        // 检测是否有按键按下
        if(kbhit()){
            // 获取按下的字符(不显示在屏幕上)
            char ch = getch();
            // 如果按 A 或 a 键,向左移动
            if(ch=='a' || ch=='A'){
                // 限制左边界,不能超出赛道
                if(carPos >18) carPos -=4;
            }
            // 如果按 D 或 d 键,向右移动
            if(ch=='d' || ch=='D'){
                // 限制右边界,不能超出赛道
                if(carPos <50) carPos +=4;
            }
            // 如果按 ESC 键,退出游戏
            if(ch==27) break;
        }

        // 绘制玩家小车
        drawCar();
        // 绘制第一个敌人
        drawEnemy(0);
        // 绘制第二个敌人
        drawEnemy(1);

        // 碰撞检测:如果返回1,表示撞车
        if(collision()==1){
            // 调用游戏结束函数
            gameover();
            // 退出play函数,返回菜单
            return;
        }

        // 延时50毫秒,控制游戏速度
        Sleep(50);

        // 擦除玩家小车(准备下一帧重绘)
        eraseCar();
        // 擦除第一个敌人
        eraseEnemy(0);
        // 擦除第二个敌人
        eraseEnemy(1);

        // 当第一个敌人下落到第10行时,激活第二个敌人
        if(enemyY[0]==10 && enemyFlag[1]==0)
            enemyFlag[1]=1;

        // 如果第一个敌人激活,Y坐标+1(向下移动)
        if(enemyFlag[0]==1) enemyY[0] +=1;
        // 如果第二个敌人激活,Y坐标+1(向下移动)
        if(enemyFlag[1]==1) enemyY[1] +=1;

        // 如果第一个敌人超出屏幕底部
        if(enemyY[0] > SCREEN_HEIGHT-4){
            resetEnemy(0);    // 重置敌人到顶部
            score++;          // 得分+1
            updateScore();    // 更新分数显示
        }
        // 如果第二个敌人超出屏幕底部
        if(enemyY[1] > SCREEN_HEIGHT-4){
            resetEnemy(1);    // 重置敌人到顶部
            score++;          // 得分+1
            updateScore();    // 更新分数显示
        }
    }
}

第 8 步:主函数 main()

菜单入口:开始游戏、查看说明、退出。

cpp 复制代码
// 主函数:程序入口(整个游戏从这里开始运行)
int main()
{
    // 隐藏控制台光标,让游戏画面更整洁
    // 参数 0 对应 false(隐藏),0 代表使用默认光标大小
    setcursor(0, 0);
    
    // 设置随机数种子
    // 用系统当前时间作为种子,保证每次运行游戏,敌人出现的位置都不同
    srand((unsigned)time(NULL));

    // 主菜单循环:死循环,保证游戏结束后能回到菜单
    do
    {
        // 清空屏幕,为绘制菜单做准备
        system("cls");
        
        // 以下代码:在控制台指定位置绘制菜单界面
        gotoxy(10, 5); cout<<" -------------------------- "; 
        gotoxy(10, 6); cout<<" |       赛车游戏        | "; 
        gotoxy(10, 7); cout<<" --------------------------";
        gotoxy(10, 9); cout<<"1. 开始游戏";         // 选项1
        gotoxy(10, 10); cout<<"2. 游戏说明";        // 选项2
        gotoxy(10, 11); cout<<"3. 退出";            // 选项3
        gotoxy(10, 13); cout<<"请选择选项: ";       // 提示输入

        // 获取用户输入的选项(getche:输入字符后立即生效,不回车)
        char op = getche();

        // 根据用户选择执行不同功能
        if(op == '1') 
            play();             // 选择1:调用 play() 函数,开始游戏
        if(op == '2') 
            instructions();     // 选择2:调用 instructions() 函数,查看游戏说明
        if(op == '3') 
            exit(0);            // 选择3:调用 exit(0),退出程序

    // 无限循环,回到菜单界面
    } while(1);

    // 程序正常结束(这里理论上永远不会执行到)
    return 0;
}

  1. setcursor(0, 0)
    游戏一开始就隐藏光标,画面更美观。
  2. srand((unsigned)time(NULL))
    随机数初始化,必须写在最前面,否则敌人每次都会出现在同一个位置。
  3. do-while(1) 循环
    保证菜单永远存在,玩完一局、游戏结束后,都会回到主菜单。
  4. 菜单逻辑
    根据用户按的键(123),调用对应的函数:
    • 1 → 玩游戏
    • 2 → 看说明
    • 3 → 退出

七、游戏运行原理(重点讲解)

  1. 动画怎么动?

    擦除旧图像 → 坐标变化 → 重画 → 延时

  2. 敌人怎么向下走?

    每帧 enemyY++

  3. 碰撞怎么判断?

    敌人到达小车高度 + X 区间重叠

  4. 为什么要用全局变量?

    多个函数需要共用坐标、分数、开关


八、测试与调试

  1. A/D 能否移动
  2. 是否跑出边界
  3. 敌人是否随机出现
  4. 碰撞是否正确触发
  5. 得分是否正常增加
  6. 游戏结束是否返回菜单

九、课程总结

本项目综合使用:

  • 函数模块化设计
  • 二维数组绘制图形
  • 光标定位实现画面布局
  • 按键控制角色移动
  • 简单碰撞与计分系统

只用 C++ 基础语法,即可完成有趣的小游戏。


完整代码

cpp 复制代码
// 1. 输入输出流头文件:用于控制台显示文字和图形
#include<iostream>
// 2. 控制台输入头文件:用于检测按键 kbhit() 和获取按键 getch()
#include<conio.h>
// 3. Windows系统API:用于控制控制台窗口(光标定位、隐藏光标等)
#include <windows.h>
// 4. 时间头文件:用于设置随机数种子,让敌人每次出现位置不同
#include <time.h>

// 宏定义:定义游戏屏幕的总宽度(固定数值,方便修改)
#define SCREEN_WIDTH 90
// 宏定义:定义游戏屏幕的总高度
#define SCREEN_HEIGHT 26
// 宏定义:定义游戏车道的宽度(小车行驶的区域)
#define WIN_WIDTH 70 

// 使用标准命名空间:简化代码,不用写 std::cout
using namespace std; 

// 控制台句柄:相当于控制台的"遥控器",用来控制光标和输出
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
// 光标坐标结构体:用来存储光标的 X、Y 坐标
COORD CursorPosition;

// 敌人车辆的 Y 坐标数组(存储3个敌人的垂直位置)
int enemyY[3];
// 敌人车辆的 X 坐标数组(存储3个敌人的水平位置)
int enemyX[3];
// 敌人状态标记:1=显示敌人,0=隐藏敌人
int enemyFlag[3];

// 玩家小车的图案:用 4行×4列 的字符数组拼成小车形状
char car[4][4] = {
    ' ','#','#',' ',
    '#','#','#','#',
    ' ','#','#',' ',
    '#','#','#','#'
};

// 玩家小车的水平位置(初始在车道正中间)
int carPos = WIN_WIDTH/2;
// 游戏得分变量,初始为0,躲避成功后加分
int score = 0; 

// 函数功能:将光标移动到控制台的 (x, y) 坐标位置
// 参数 x:水平坐标(列)
// 参数 y:垂直坐标(行)
void gotoxy(int x, int y){
    // 设置光标要移动到的 X 坐标(水平位置)
    CursorPosition.X = x;
    // 设置光标要移动到的 Y 坐标(垂直位置)
    CursorPosition.Y = y;
    // 调用Windows系统API,执行光标移动
    SetConsoleCursorPosition(console, CursorPosition);
}

// 函数功能:设置控制台光标的显示状态和大小
// 参数 visible:true=显示光标,false=隐藏光标
// 参数 size:光标的大小(取值 1-100),传入 0 则使用默认值
void setcursor(bool visible, DWORD size) {

    // 如果传入的尺寸为0,设置为默认大小 20(固定写法,不用改)
    if(size == 0) 
        size = 20;

    // 定义一个 光标信息结构体 变量,用来存放光标的属性
    CONSOLE_CURSOR_INFO lpCursor;

    // 设置光标是否可见(true显示 / false隐藏)
    lpCursor.bVisible = visible;

    // 设置光标的大小(1~100,数字越大光标越粗)
    lpCursor.dwSize = size;

    // 把设置好的光标信息应用到控制台(生效)
    SetConsoleCursorInfo(console,&lpCursor);
}

// 函数功能:绘制游戏的边界线(车道左右边框 + 最右侧边框)
void drawBorder(){
    // 外层循环:遍历屏幕的每一行(垂直方向 i 代表行号)
    for(int i=0; i<SCREEN_HEIGHT; i++){
        // 内层循环:绘制左右两侧的双线边框(j 控制边框粗细)
        for(int j=0; j<17; j++){
            // 在第 i 行、左侧 0+j 列 绘制左边界符号
            gotoxy(0+j,i); 
            cout<<"±";
            // 在第 i 行、右侧 WIN_WIDTH-j 列 绘制右边界符号
            gotoxy(WIN_WIDTH-j,i); 
            cout<<"±";
        }
    }

    // 额外绘制最右侧的单条边框(整个屏幕的右边缘)
    for(int i=0; i<SCREEN_HEIGHT; i++){
        gotoxy(SCREEN_WIDTH,i); 
        cout<<"±";
    }
}

// 函数功能:在控制台中绘制玩家控制的小车
void drawCar(){
    // 外层循环:遍历小车的 4 行(i 代表行号)
    for(int i=0; i<4; i++){
        // 内层循环:遍历小车的 4 列(j 代表列号)
        for(int j=0; j<4; j++){
            // 定位到小车对应字符的控制台坐标
            // j+carPos:水平位置 = 小车初始X坐标 + 字符在图案中的列
            // i+22:垂直位置 = 固定在屏幕下方 22 行的位置
            gotoxy(j+carPos, i+22);
            
            // 输出二维数组中存储的对应字符(# 或 空格),拼成小车图案
            cout << car[i][j];
        }
    }
}


// 函数功能:绘制敌方车辆
// 参数 ind:要绘制的敌人编号(0代表第一个敌人,1代表第二个敌人)
void drawEnemy(int ind){
    // 判断:只有当前敌人处于激活状态(enemyFlag[ind]为true)时,才进行绘制
    if(enemyFlag[ind]==true){
        // 定位到敌人的 (X,Y) 坐标,绘制敌人第一行
        gotoxy(enemyX[ind], enemyY[ind]);   
        cout<<"****";
        // 定位到敌人的下一行,绘制敌人第二行
        gotoxy(enemyX[ind], enemyY[ind]+1); 
        cout<<" ** ";
        // 定位到敌人的下一行,绘制敌人第三行
        gotoxy(enemyX[ind], enemyY[ind]+2); 
        cout<<"****";
        // 定位到敌人的下一行,绘制敌人第四行
        gotoxy(enemyX[ind], enemyY[ind]+3); 
        cout<<" ** ";
    }
}

// 函数功能:擦除玩家小车(用空格覆盖原来的位置)
// 作用:实现动画效果 ------ 先擦除,再画新位置
void eraseCar(){
    // 遍历小车的 4 行(i 是行)
    for(int i=0; i<4; i++){
        // 遍历小车的 4 列(j 是列)
        for(int j=0; j<4; j++){
            // 定位到小车原来的位置
            gotoxy(j+carPos, i+22); 
            
            // 输出空格 → 把原来的 # 覆盖掉(擦除)
            cout<<" ";
        }
    }
}

// 函数功能:擦除指定编号的敌人车辆
// 参数 ind:敌人的编号(0=第一个敌人,1=第二个敌人)
void eraseEnemy(int ind){
    // 只有敌人处于显示状态时,才需要擦除
    if(enemyFlag[ind]==true){
        // 定位到敌人第一行 → 输出4个空格擦除
        gotoxy(enemyX[ind], enemyY[ind]);   
        cout<<"    ";
        
        // 定位到敌人第二行 → 擦除
        gotoxy(enemyX[ind], enemyY[ind]+1); 
        cout<<"    ";
        
        // 定位到敌人第三行 → 擦除
        gotoxy(enemyX[ind], enemyY[ind]+2); 
        cout<<"    ";
        
        // 定位到敌人第四行 → 擦除
        gotoxy(enemyX[ind], enemyY[ind]+3); 
        cout<<"    ";
    }
}

// 函数功能:生成敌人的随机 X 坐标(让敌人在车道里随机位置出现)
// 参数 ind:敌人编号(0表示第一个敌人,1表示第二个敌人)
void genEnemy(int ind){
    // 17:保证敌人出现在**车道内部**(不会卡在左边界)
    // rand()%33:生成 0~32 的随机数,控制敌人在车道内左右随机分布
    // 最终 enemyX[ind] 范围:17 ~ 49,正好在游戏车道内
    enemyX[ind] = 17 + rand()%33;
}

// 函数功能:重置敌人车辆(当敌人开到屏幕底部后,重新回到顶部)
// 参数 ind:要重置的敌人编号(0=第一个敌人,1=第二个敌人)
void resetEnemy(int ind){
    // 1. 先擦除当前敌人在屏幕底部的旧图像
    eraseEnemy(ind);
    
    // 2. 把敌人的Y坐标设置为1(回到屏幕最上方)
    enemyY[ind] = 1;
    
    // 3. 重新生成一个随机的X坐标(让敌人换个车道出现)
    genEnemy(ind);
}

// 函数功能:碰撞检测
// 作用:判断玩家小车和敌人小车是否撞上了
// 返回值:1 = 碰撞,0 = 未碰撞
int collision(){
    // 第一层判断:敌人的Y坐标 + 自身高度(4行) 到达 玩家小车的Y坐标(23行)
    // 意思是:敌人已经下落到玩家所在的高度了
    if(enemyY[0]+4 >= 23){
        // 第二层判断:敌人和玩家的 X 坐标区间发生重叠
        // enemyX[0]+4 是敌人的右侧位置,carPos 是玩家的左侧位置
        // 两者差值在 0~9 之间,说明两车宽度重叠,判定为相撞
        if(enemyX[0]+4 - carPos >=0 && enemyX[0]+4 - carPos <9)
            return 1;  // 两个条件都满足,发生碰撞,返回 1
    }
    return 0;  // 不满足条件,没有碰撞,返回 0
}

// 函数功能:更新并显示游戏得分
void updateScore(){
    // 定位光标到屏幕右侧固定位置
    // WIN_WIDTH + 7:车道右侧区域(不遮挡游戏画面)
    // 5:垂直第5行
    gotoxy(WIN_WIDTH + 7, 5);
    
    // 输出当前得分(中文显示)
    cout<<"得分: "<<score<<endl;
}

void gameover(){
    system("cls");
    cout<<endl;
    cout<<"\t\t--------------------------"<<endl;
    cout<<"\t\t------- 游戏结束 -------"<<endl;
    cout<<"\t\t--------------------------"<<endl<<endl;
    cout<<"\t\t按任意键返回主菜单.";
    getch();
}

void instructions(){
    system("cls");
    cout<<"游戏说明";
    cout<<"\n----------------";
    cout<<"\n 通过左右移动躲避车辆。 ";
    cout<<"\n\n 按 A 键向左移动";
    cout<<"\n 按 D 键向右移动";
    cout<<"\n 按 ESC 键退出游戏";
    cout<<"\n\n按任意键返回主菜单";
    getch();
}

// 函数功能:游戏主逻辑函数(游戏核心运行代码)
void play(){
    // 1. 初始化玩家小车位置:居中显示
    carPos = -1 + WIN_WIDTH/2;
    // 2. 初始化游戏得分:0分
    score = 0;
    // 3. 第一个敌人默认激活(显示)
    enemyFlag[0] = 1;
    // 4. 第二个敌人初始未激活(隐藏)
    enemyFlag[1] = 0;
    // 5. 两个敌人都从屏幕最顶部开始
    enemyY[0] = enemyY[1] = 1;

    // 清空控制台屏幕
    system("cls");
    // 绘制游戏赛道边框
    drawBorder();
    // 显示初始得分(0分)
    updateScore();
    // 随机生成第一个敌人的X坐标
    genEnemy(0);
    // 随机生成第二个敌人的X坐标
    genEnemy(1);

    // 在屏幕右侧显示游戏标题
    gotoxy(WIN_WIDTH + 7, 2);cout<<"赛车游戏";
    // 显示操作说明:A键左移
    gotoxy(WIN_WIDTH + 2, 14);cout<<" A 键 - 左移";
    // 显示操作说明:D键右移
    gotoxy(WIN_WIDTH + 2, 15);cout<<" D 键 - 右移";

    // 提示玩家按任意键开始
    gotoxy(18, 5);cout<<"按任意键开始游戏";
    // 等待玩家按一个键
    getch();
    // 清空提示文字(用空格覆盖)
    gotoxy(18, 5);cout<<"                      ";

    // 游戏主死循环(每一帧执行一次)
    while(1){
        // 检测是否有按键按下
        if(kbhit()){
            // 获取按下的字符(不显示在屏幕上)
            char ch = getch();
            // 如果按 A 或 a 键,向左移动
            if(ch=='a' || ch=='A'){
                // 限制左边界,不能超出赛道
                if(carPos >18) carPos -=4;
            }
            // 如果按 D 或 d 键,向右移动
            if(ch=='d' || ch=='D'){
                // 限制右边界,不能超出赛道
                if(carPos <50) carPos +=4;
            }
            // 如果按 ESC 键,退出游戏
            if(ch==27) break;
        }

        // 绘制玩家小车
        drawCar();
        // 绘制第一个敌人
        drawEnemy(0);
        // 绘制第二个敌人
        drawEnemy(1);

        // 碰撞检测:如果返回1,表示撞车
        if(collision()==1){
            // 调用游戏结束函数
            gameover();
            // 退出play函数,返回菜单
            return;
        }

        // 延时50毫秒,控制游戏速度
        Sleep(50);

        // 擦除玩家小车(准备下一帧重绘)
        eraseCar();
        // 擦除第一个敌人
        eraseEnemy(0);
        // 擦除第二个敌人
        eraseEnemy(1);

        // 当第一个敌人下落到第10行时,激活第二个敌人
        if(enemyY[0]==10 && enemyFlag[1]==0)
            enemyFlag[1]=1;

        // 如果第一个敌人激活,Y坐标+1(向下移动)
        if(enemyFlag[0]==1) enemyY[0] +=1;
        // 如果第二个敌人激活,Y坐标+1(向下移动)
        if(enemyFlag[1]==1) enemyY[1] +=1;

        // 如果第一个敌人超出屏幕底部
        if(enemyY[0] > SCREEN_HEIGHT-4){
            resetEnemy(0);    // 重置敌人到顶部
            score++;          // 得分+1
            updateScore();    // 更新分数显示
        }
        // 如果第二个敌人超出屏幕底部
        if(enemyY[1] > SCREEN_HEIGHT-4){
            resetEnemy(1);    // 重置敌人到顶部
            score++;          // 得分+1
            updateScore();    // 更新分数显示
        }
    }
}

// 主函数:程序入口(整个游戏从这里开始运行)
int main()
{
    // 隐藏控制台光标,让游戏画面更整洁
    // 参数 0 对应 false(隐藏),0 代表使用默认光标大小
    setcursor(0, 0);
    
    // 设置随机数种子
    // 用系统当前时间作为种子,保证每次运行游戏,敌人出现的位置都不同
    srand((unsigned)time(NULL));

    // 主菜单循环:死循环,保证游戏结束后能回到菜单
    do
    {
        // 清空屏幕,为绘制菜单做准备
        system("cls");
        
        // 以下代码:在控制台指定位置绘制菜单界面
        gotoxy(10, 5); cout<<" -------------------------- "; 
        gotoxy(10, 6); cout<<" |       赛车游戏        | "; 
        gotoxy(10, 7); cout<<" --------------------------";
        gotoxy(10, 9); cout<<"1. 开始游戏";         // 选项1
        gotoxy(10, 10); cout<<"2. 游戏说明";        // 选项2
        gotoxy(10, 11); cout<<"3. 退出";            // 选项3
        gotoxy(10, 13); cout<<"请选择选项: ";       // 提示输入

        // 获取用户输入的选项(getche:输入字符后立即生效,不回车)
        char op = getche();

        // 根据用户选择执行不同功能
        if(op == '1') 
            play();             // 选择1:调用 play() 函数,开始游戏
        if(op == '2') 
            instructions();     // 选择2:调用 instructions() 函数,查看游戏说明
        if(op == '3') 
            exit(0);            // 选择3:调用 exit(0),退出程序

    // 无限循环,回到菜单界面
    } while(1);

    // 程序正常结束(这里理论上永远不会执行到)
    return 0;
}
相关推荐
老虎06272 小时前
Java基础面试题(08)—Java(集合—HashMap的使用和实现原理红黑树)
java·开发语言
云边散步2 小时前
godot2D游戏教程系列二(22)
笔记·学习·游戏
guygg882 小时前
基于数据驱动的模型预测控制电力系统机组组合优化MATLAB实现
开发语言·matlab
lly2024062 小时前
组合模式:深入理解与实际应用
开发语言
2501_908329852 小时前
C++中的备忘录模式
开发语言·c++·算法
qq_416018723 小时前
C++与机器学习框架
开发语言·c++·算法
Bert.Cai3 小时前
Python模块简介
开发语言·python
忘忧记3 小时前
Fixture详解
开发语言·python
赵谨言3 小时前
地球磁场干扰噪声减弱声波对抗测量系统研究进展:近十年中英文文献综述
大数据·开发语言·经验分享