C与C++游戏项目练习8:弹跳的小球3.0(由快速敲字母游戏改进而来)
这次游戏项目的更新,比平时来得更晚一些~~~
前情提要:在做到弹跳的小球的课后题时,我被“按空格键发射新的小球”难住了。
宝书镇楼:
于是我想起了一个之前看网课学的“快速敲字母”游戏来类比我要实现的功能。
(详情可以看我的这篇博文:https://blog.csdn.net/bailichen800/article/details/115560127?spm=1001.2014.3001.5501)
话不多说,实现的功能:
1.按空格键生成一个小球
2.每一关随机生成随机数目的砖块
3.挡板可以通过按awsd来左上下右自由移动
4.可以由玩家选择是否重复玩多关
与快速敲字母游戏一样,本项目建议使用DEV C++运行,因为VS2019会报异常!!!!
与快速敲字母游戏一样,本项目建议使用DEV C++运行,因为VS2019会报异常!!!!
与快速敲字母游戏一样,本项目建议使用DEV C++运行,因为VS2019会报异常!!!!
因为电脑坏了,之前敲的书上初级代码不幸遗失,所以这次只有我完成的升级版代码了。
资源稍后放上,可在我个人主页自取。
#include<iostream>
#include<stdlib.h>
#include<conio.h>
#include<vector>
#include<windows.h>
#include<time.h>
#define Height 15
#define Width 20
using namespace std;
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄
COORD crd;
void gotoxy(int x, int y)//移动光标函数
{
crd.X = x;
crd.Y = y;
SetConsoleCursorPosition(handle, crd);//设置光标位置为传入的参数x,y
}
struct Ball//球类
{
private:
int ball_x;
int ball_y;
int ball_vx = 1;
int ball_vy = 1;
public:
Ball(int x, int y) { ball_x = x; ball_y = y; }
int GetBall_x() { return ball_x; }
int GetBall_y() { return ball_y; }
void FallBall()
{
//获得小球原来的位置坐标,准备输出空格将其删除
gotoxy(GetBall_x(),GetBall_y());
cout << " ";//擦除当前小球的图案
ball_x += ball_vx;
ball_y += ball_vy;
}
void ChangeXWay()
{
ball_vx = -ball_vx;
}
void ChangeYWay()
{
ball_vy = -ball_vy;
}
};
class Block//砖块类
{
private:
int block_x;
int block_y;
public:
Block(int x, int y) { block_x = x; block_y = y; }
int GetBlock_x() { return block_x; }
int GetBlock_y() { return block_y; }
};
class Plate//挡板类
{
private:
int ridus;//半径
int left = position_x - ridus;//左边界
int right = position_x + ridus;//右边界
public:
int position_x, position_y;//挡板中心坐标
Plate(int x, int y, int r) { position_x = x, position_y = y; ridus = r; ChangeBound(); }//调用构造函数时就要算一次左右边界
int GetLeft() { return left; }
int GetRight() { return right; }
void ChangeBound()//在圆心坐标改变的时候改变左右边界
{
left = position_x - ridus;//左边界
right = position_x + ridus;//右边界
}
};
Plate plate((int)Width / 2, (int)Height - 1,5);
class BallController//控制小球的下落、挡板的移动
{
private:
vector<Ball> balls;//小球
vector<Block>blocks;//砖块
short delay;//保存延时时间
short score;//保存得分
public:
BallController()
{
score = 0;
delay = 100;
}//初始化得分为0
void GenerateBall()//按下空格,调用此函数,产生小球
{
Ball ball(Width/2,Height/2);//初始位置从游戏中心区域发出
balls.push_back(ball);//加入动态数组
}
void GenerateBlock()//无需输入,顶上三排每排自动生成10-19随机个数的砖块
{
for (int i = 0; i < 3; i++)//i表示纵坐标,三排
{
for (int j = 0; j < Width; j++)//j表示横坐标,哪一列
{
if (j < rand() % 10 + 10)//Width是20,j如果小于10-19的数就生成砖块,否则生成空格
{
Block block(j, i);
blocks.push_back(block);
}
else
{
printf(" ");
}
}
}
}
bool Ground(Ball* ball)
{
return ball->GetBall_y() >= Height-1;//看小球纵坐标是否大于等于高度,是则表示落地了
}
short Fall()//判断小球是否正常下落,是返回0,不是返回-1(即落地,游戏结束)
{
vector<Ball>::iterator itr;
for (itr = balls.begin(); itr != balls.end(); itr++)
{
//获得小球原来的位置坐标,准备输出空格将其删除
gotoxy(itr->GetBall_x(),itr->GetBall_y());
cout << " ";//擦除当前小球的图案
if (Ground(&(*itr)))//如果触地,游戏结束
{
crd.X = 13; crd.Y = Height + 2;
SetConsoleCursorPosition(handle, crd);
SetConsoleTextAttribute(handle, BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED);//白色背景
cout << "Game Over!" << endl;
SetConsoleTextAttribute(handle, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);//恢复默认黑色背景
printf("\a");
return -1;//返回-1告知本函数的调用块:游戏已经结束
}
if ((itr->GetBall_y() == 1)|| (itr->GetBall_y() == plate.position_y-1) && (itr->GetBall_x() >= plate.GetLeft() && itr->GetBall_x() <= plate.GetRight()))//小球的纵坐标等于0,碰到边界要反弹(如果等于Height的话就属于前面结束游戏的情况了)
{//注意碰到挡板的情况也是只有y反向,如果写成x、y都反向,小球就会不停原路返回,详见下方else if被注释的错误代码
itr->ChangeYWay();//y方向反向
}
if (itr->GetBall_x() == 0+1 || itr->GetBall_x() == Width-1)//边界是[0,Width] ,左右各自提前一格判断,这样小球输出的空格就不会影响边界,上面条件的 (itr->GetBall_y() == plate.position_y-1) 同理
{
itr->ChangeXWay();//x方向反向
}
//以下else if代码有问题,会导致小球始终原路返回
//else if ((itr->GetBall_y() == plate.position_y) && (itr->GetBall_x() >= plate.GetLeft() && itr->GetBall_x() <= plate.GetRight())|| (itr->GetBall_x() == 0 || itr->GetBall_x() == Width)|| (itr->GetBall_y() == 0))
//{//如果小球碰到挡板,那么X、Y方向都会反向(判断条件是纵坐标和挡板一样并且横坐标位于挡板左右边界(圆心+-半径)之间)
// itr->ChangeXWay();
// itr->ChangeYWay();
//}
vector<Block>::iterator blockitr;
for (blockitr = blocks.begin(); blockitr != blocks.end(); )
{
//blockitr是砖块数组的迭代器,itr是小球数组的迭代器
if (blockitr->GetBlock_x() == itr->GetBall_x() && blockitr->GetBlock_y() == itr->GetBall_y())//如果这个砖块的坐标==小球的坐标
{
gotoxy(blockitr->GetBlock_x(), blockitr->GetBlock_y());//坐标移到砖块处
cout << " ";//砖块被消除,变成空格
blocks.erase(blockitr);//消除这个砖块(此刻迭代器自动指向下一个元素了
score++;//得分+1
}
else
{
blockitr++;//这个分支说明没消除砖块,迭代器手动后移
}
}
gotoxy(itr->GetBall_x(), itr->GetBall_y());
cout << "o";//在新位置画出小球
itr->FallBall();//只要未触地就继续下落,y坐标变化
ShowBall();
}
Wait();
return 0;
}
void MovePlate(char input)
{
if (input == 'a' && plate.GetLeft() > 0)//挡板左移
{
plate.position_x--;
plate.ChangeBound();//重新设置左右边界
}
else if (input == 'd' && plate.GetRight() < Width)//挡板右移
{
plate.position_x++;
plate.ChangeBound();//重新设置左右边界
}
else if (input == 'w' && plate.position_y > 0)//挡板上移
{
plate.position_y--;
plate.ChangeBound();//重新设置左右边界
}
else if (input == 's' && plate.position_y < Height-1)//挡板下移
{
plate.position_y++;
plate.ChangeBound();//重新设置左右边界
}
}
void ShowBlock()//绘制砖块
{
vector<Block>::iterator itr;
for (itr = blocks.begin(); itr != blocks.end(); itr++)
{
gotoxy(itr->GetBlock_x(),itr->GetBlock_y());
cout << "#";
}
}
void ClearPlate()//绘制挡板
{
gotoxy(plate.GetLeft(), plate.position_y);
for (int i = plate.GetLeft(); i != plate.GetRight(); i++)
{
printf(" ");
}
}
void ShowPlate()//绘制挡板
{
gotoxy(plate.GetLeft(), plate.position_y);
for (int i = plate.GetLeft(); i != plate.GetRight(); i++)
{
printf("*");
}
}
void ShowBall()//绘制小球
{
vector<Ball>::iterator itr;
for (itr = balls.begin(); itr != balls.end(); itr++)
{
gotoxy(itr->GetBall_x(), itr->GetBall_y());
cout << "o";
}
}
void SetDelay(short d)//设置延迟时间
{
delay = d;
}
void Wait()
{
Sleep(delay);//按SetDelay中设定好的时间休眠
}
void ShowScore()//在边界的下面两个单位处输出得分
{
crd.X = 1;
crd.Y = Height + 2;
SetConsoleCursorPosition(handle, crd);
cout << " Score: " << score << " ";//多输出几位空格来,防止上一次得分位数较多没有被完全遮挡
}
void CleanAll()//清空上一局得分、砖块、小球的数组,平板移到原处
{
score=0;
blocks.clear();
balls.clear();
for(int i=0;i<Height;i++)
{
for(int j=0;j<Width;j++)
{
cout<<" ";
}
}
}
};
void ShowBound()//绘制边框
{
//绘制竖边框
for (int i = 0; i < Height; i++)
{
gotoxy(Width,i );
printf("|\n");
}
//绘制横边框
for (int j = 0; j < Width; j++)
{
gotoxy(j, Height);
printf("-");
}
}
int main()
{
BallController ballctrl;
srand((unsigned)time(NULL));//产生随机种子,程序时间作为参数传入
//char choice;
char input;//接受用户的控制输入
char choice='n';
do
{
//每次开始新一局游戏要做的事情
system("cls");//清除欢迎界面显示文字以及之前游戏遗留的字母
ballctrl. CleanAll();//清空上一局得分
ShowBound();
ballctrl.GenerateBlock();//生成并打印砖块
ballctrl.ShowBlock();
ballctrl.ShowPlate();//生成并展示类中包含的挡板
ballctrl.SetDelay(100);//设置时间间隔为100s
do//初始化完成之后小球的运动和按键检测
{
ballctrl.ShowScore();//输出得分
if (ballctrl.Fall() == -1)
{
break;//如果不想要外层do while的多次玩游戏入口,可以在此直接exit(0)
}
if (_kbhit())//检测到键盘输入,输入只有3种情况:1.生成小球(空格),2.挡板移动(awsd)
{
input = _getch();
if (input == ' ')
{
ballctrl.GenerateBall();//空格生成小球
ballctrl.ShowBall();//绘制小球
}
else if (input == 'a' || input == 'd' || input == 'w' || input == 's')//根据输入的是a或者d来决定挡板的移动
{
ballctrl.ClearPlate();//清除原来的挡板
ballctrl.MovePlate(input);//根据按键移动挡板
ballctrl.ShowPlate();//展示类中包含的挡板移动之后
}
}
}while (true);
cout << " Try Again? (y or n)";
cin>>choice; //choice=_getch();这种写法不用输入回车
}while (choice == 'y' || choice == 'Y');
return 0;
}
下一道课后题是实现“接金币游戏”,emmm·····又要秃头了。(似乎就是把小球的挡板拿来接下落的字母,只不过把字母换成金币而已····?)
感谢你能看到这里,一起成为更好的自己吧~~~