欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

实战训练9:轻松入门——C与C++打造接金币小游戏基础版本

最编程 2024-02-29 15:05:25
...
#include<iostream> #include<time.h> #include<conio.h> #include<windows.h> #include<vector> HANDLE handle= GetStdHandle(STD_OUTPUT_HANDLE); COORD crd; using namespace std; const short HEALTH = 5;//满血总血量为5 const short STD_WIDTH = 60;//记录宽度,便于输出一整排的边框等 const short GROUND = 27;//地面水平线的纵坐标 void gotoxy(int x, int y)//光标移到(x,y)位置处(封装了SetConsoleCursorPosition(handle, crd)函数,减少代码重复) { crd.X = x; crd.Y = y; SetConsoleCursorPosition(handle, crd); } void ShowBound()//绘制边框 { //绘制竖边框 for (int i = 0; i < GROUND; i++) { gotoxy(STD_WIDTH, i); printf("|\n"); } //绘制横边框 for (int j = 0; j < STD_WIDTH; j++) { gotoxy(j, GROUND); printf("-"); } } class Vitality//血量 { private: int vitality; public: Vitality() { vitality = HEALTH; }//每次把血量恢复到满血 void ShowVitality() { gotoxy(STD_WIDTH * 0.6, 0); SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);//设为黄色字体,高亮 cout << "HP: ";//hit points血量 for (int i = 0; i < HEALTH; i++) { cout<<(i < vitality ? "■" : "□");//如果小于实际血量vitality,输出实心方框,反之输出空心方框 } cout<<"Vitality: "<<vitality; SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);//恢复黑底白字 } short GetVitality()//获取当前血量 { return vitality; } short GetThing(short n = 0)//如果没有传入参数,默认每次不加不减血量 { vitality += n;//接到炸弹-1,接到血包+0-3点血 if(vitality>5) { vitality=5; } ShowVitality();//更新扣血之后的血量 return vitality; } short GetRestore() { vitality = HEALTH; ShowVitality();//更新满血复活之后的血量 return vitality; } }; Vitality vitality;//控制血量的全局变量 class FallThings//所有掉落物的基类 { protected://不能写成private,否则子类无法修改 int x; int y; public: bool active; string Name; int grade; int num;//随机生成掉落物用 char letter;//符号默认是金币 FallThings(int x=rand() % STD_WIDTH - 1,int y=1)//rand() % STD_WIDTH - 1生成0-STD_WIDTH-2的随机数,因为最上面要输出血槽,所以要y=1 { this->x = x; this->y = y; active=true; grade = 1; num = rand() % 7;//0-4金币,5炸弹,6礼物,这样金币概率最大 if(num==5) { Name = "Boom"; letter = '@';//炸弹 grade=-1; } else if(num==6)//血包 { Name = "Vitality"; letter = '+'; grade=rand()%4; } else//金币 { Name = "Coin"; letter = 'o'; grade=1; } } int GetX()//返回x坐标 { return x; } int GetY()//返回y坐标 { return y; } // 提供接口框架的纯虚函数 void Fall()//实现下落,沿着垂直方向往下掉就行 { y++; } }; class Plate//盘子类,用来接掉落物 { private: int x; int y ;//默认位置是屏幕正中间,离地面一格远 int ridus;//半径 int left;//左边界 int right;//右边界 public: Plate(int x=STD_WIDTH / 2,int y=GROUND - 1,int r=5) { this->x = x; this->y = y; this->ridus = r; left = x - ridus;//左边界 right = x + ridus;//右边界 ChangeBound();//确定左右边界 } int GetX()//返回x坐标 { return x; } int GetY()//返回y坐标 { return y; } int GetLeft() { return left; } int GetRight() { return right; } void ChangeBound()//在圆心坐标改变的时候改变左右边界 { left = x - ridus;//左边界 right = x + ridus;//右边界 } void MovePlate(char input)//根据键盘输入移动盘子(一次一格,好控制) { if (input == 'a'&& GetLeft() > 0)//接收到a,并且不抵在左边界,盘子左移 { x--; ChangeBound();//更改盘子边界 } else if (input == 'd'&& GetRight()< STD_WIDTH)//接收到d,并且不抵在右边界,盘子右移 { x++; ChangeBound();//更改盘子边界 } } void MovePlate(int distance)//重载成员函数,通过鼠标移动距离来移动盘子(一次一段,不好控制) { distance %= STD_WIDTH;//防止距离超出范围,控制在游戏界面内 if (GetLeft() > distance && GetRight() + distance < STD_WIDTH)//在可移动范围内,最左边要至少能移动一个distance的距离,右边同理 { x += distance; ChangeBound();//更改盘子边界 } else//避免x卡在边界处无法移动 { if (x <= 0) { x = 1+ridus;//起点加半径 ChangeBound(); } else { x = STD_WIDTH - 1-ridus; ChangeBound(); } } } void ShowPlate() { if(GetLeft()>0&&GetY()<STD_WIDTH)//只在规定范围内画盘子 (不然因为鼠标的移动可能盘子会出现在奇奇怪怪的地方哦) { gotoxy(GetLeft(), GetY());//注意横坐标从plate左边界开始,纵坐标是plate的y坐标 for (int i = GetLeft(); i != GetRight(); i++)//整个输出的范围是plate从左边界到右边界 { printf("*"); } } } void ClearPlate()//清除盘子 { gotoxy(GetLeft(), GetY());//原来是盘子的地方输出空格 for (int i = GetLeft(); i != GetRight(); i++) { printf(" "); } } void RestorePlate() { x = STD_WIDTH / 2;//恢复盘子默认位置 y = GROUND - 1; } }; Plate plate;//全局变量盘子 class GameCtroller//游戏管理类,控制游戏运作 { public: vector<FallThings> fallthings;//用于管理掉落物的动态数组 int score;//本轮得分 GameCtroller() { score = 0; } short delay;//设置休眠时间 void GameRestore()//重复游戏时用,重置游戏初始数据 { fallthings.clear();//清空动态数组 vitality.GetRestore();//恢复血量 score = 0;//清空分数 } void GenerateFallingThings()//每隔一段时间调用,自动生成掉落物 { gotoxy(rand()%STD_WIDTH ,1);//从最顶端随机列(0-STD_WIDTH-1)生成 for (int i = 0; i < rand() % 5; i++)//每次随机生成0-5个掉落物 { fallthings.push_back(FallThings());//生成一个匿名的FallThings对象加入vector里 } } short Fall() { vector < FallThings>::iterator itr;//迭代器,遍历掉落物数组fallthings for (itr = fallthings.begin(); itr != fallthings.end(); itr++) { if (itr->active == true)//活跃状态为true才判断执不执行以下代码 { gotoxy(itr->GetX(), itr->GetY());//光标移动到当前位置,实施擦除 cout << " "; //当前元素被接到了或者掉到了地平线以下,需要消除 if (itr->GetY() == plate.GetY() - 1)//y高度=盘子y高度-1,表示被接到了 { if (itr->num>=0&&itr->num<=4)//接到金币 { score+=itr->grade;//接到一个金币,得分+1 } else if (itr->num==5)//接到炸弹 { vitality.GetThing(itr->grade);//把炸弹对应的扣血量(-1)作为参数传入Vitality,实现接到炸弹血量-1 } else if (itr->num==6)//接到血包 { vitality.GetThing(itr->grade);//把礼物对应的加血量(+0-3)作为参数传入Vitality,实现接到血包血量+1 } if (vitality.GetVitality() <= 0)//本轮扣血加分处理完了,如果发现没血了,就输出游戏结束 { crd.X = 13; crd.Y = GROUND + 2;//GROUND+1是输出的边框,所以提示语可以GROUND+2输出 SetConsoleCursorPosition(handle, crd); //SetConsoleTextAttribute(handle, BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED);//白色背景 不注释的话最后输出GameOver就会黑底白字和白底黑子鱼龙混杂,不知道为什么会这样 cout << "Game Over!" << endl; SetConsoleTextAttribute(handle, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);//恢复默认黑色背景 return -1; } itr->active = false;//活跃设置为false,不再掉落 vitality.ShowVitality();//展示血量 ShowScore();//展示分数 } else if (itr->GetY() >= GROUND)//当前元素落到地平线以下都没有被接到,直接消除但不扣血(这也是为什么不能和被接到的情况合并处理的原因) { itr->active = false;//活跃设置为false,不再掉落 } else//当前元素还在下落阶段,就让其继续下落 { itr->Fall();//纵坐标继续加1 gotoxy(itr->GetX(), itr->GetY());//光标移到该元素的新位置(x横坐标不变,y纵坐标+1) cout << itr->letter;//输出该迭代器对应符号,金币--o,炸弹--@,血包—+ //itr++;//因为erase会让迭代器自动移到下一个,这里没有erase,就要手动移动到下一个元素 } } } return 0;//正常下落返回0 } void SetDelay(int d) { delay = d; } void Wait() { Sleep(delay); } void ShowScore() { gotoxy(1, GROUND + 2); cout << " Score: " << score << " ";//多输出几位空格来,防止上一次得分位数较多没有被完全遮挡 } }; int main() { GameCtroller gamectroller;//实例化游戏控制类 char choice = 'n'; char input; gamectroller.SetDelay(300); srand((unsigned)time(NULL));//产生随机种子,程序时间作为参数传入 do { system("cls");//清屏 plate.RestorePlate();//盘子恢复默认位置 ShowBound();//绘制边框 gamectroller.GameRestore();//重置游戏 vitality.GetRestore();//恢复满血 vitality.ShowVitality();//展示血量 POINT last_p;//每次开始游戏都重新定义,保证从初始位置开始捕捉鼠标 POINT p; do { gamectroller.ShowScore();//输出得分 plate.ShowPlate();//更新盘子位置 GetCursorPos(&last_p);//获取鼠标坐标 last_p.y = GROUND - 1;//防止光标随鼠标移动,影响盘子的位置 Sleep(50); //休息0.05s间隔必须短,因为鼠标移动很快,间隔长了会很卡 GetCursorPos(&p);//再次获取鼠标坐标 p.y = GROUND - 1; if (p.x != last_p.x)//现在x与之前不一样,说明在0.1s内移动了鼠标,就处理移动挡板 {//鼠标在范围内移动才行 plate.ClearPlate();//清除原来的挡板 plate.MovePlate(int(p.x-last_p.x));//根据按键移动挡板,强制转换为int,不然会报错“对重载调用不明确” plate.ShowPlate();//展示类中包含的挡板移动之后 } if (_kbhit())//检测到键盘输入,可能挡板移动(ad) { input = _getch(); if (input == 'a' || input == 'd')//根据输入的是a或者d来决定挡板的移动 { plate.ClearPlate();//清除原来的挡板 plate.MovePlate(input);//根据按键移动挡板 plate.ShowPlate();//展示类中包含的挡板移动之后 } if (input == 27) { break;//按ESC退出 } } gamectroller.GenerateFallingThings(); if (gamectroller.Fall() == -1) { break;//-1表示游戏结束,跳出内层循环询问玩家是否开始下一局 } gamectroller.Wait();//每300毫秒自动生成下一轮字母 } while (true); cout << " Try Again? (y or n)"; cin >> choice; } while (choice == 'y' || choice == 'Y'); return 0; }