C++实战 -大鱼吃小鱼

        这是进入大学本科之后完成的第一个比较完整的程序,通过EasyX库使用CPP实现一个大鱼吃小鱼的游戏。
 
工程文件下载:
 

一.使用的库和IDE

EasyX库

        EasyX 是针对 C++ 的图形库,可以帮助 C 语言初学者快速上手图形和游戏编程。

  1. EasyX库地址https://easyx.cn/
  2. 使用版本 2018春分版;
IDE:Visual Studio 2019

二.源代码

1.Struct.h

//Struct.h

struct player
{
	int x;
	int y;
	bool live = true;
	int size = 1;
	int score = 0;
};

struct fish
{
	int x;
	int y;
	int size;
	bool live = true;
};

2.Functions.h

//Functions.h
#ifndef _Functions_H
#define _Functions_H
	
#include <easyx.h>
#include <conio.h>
#include <time.h>
#include <iostream>                //头文件
#pragma  comment(lib,"winmm.lib")        //引用静态库
#include"Struct.h"
using namespace std;
/*************************************全局变量****************************************************/
const short ScreenWidth = 480;
const short ScreenHeight = 640;
short ret = 0;
int ToBig = 10;
/**************NPC鱼的数量****************/
const int numB = 1;//大鱼数量
const int numL = 2;//小鱼数量
const int numS = 3;//虾米数量
IMAGE Fish[3];//鱼的图片
IMAGE lplayerfish[2];//玩家鱼的图片
IMAGE rplayerfish[2];//玩家鱼的图片
IMAGE background;//背景
short Formerx = 0;//用于转身
short Formery = 0;//用于转身
const int N = 50;//图片大小

//加载图片
extern void loadImages() 
{
	loadimage(&Fish[2], L"bigfish.jpg", 2 * N, 2 * N);
	loadimage(&Fish[1], L"littlefish.jpg", N, N);
	loadimage(&Fish[0], L"shrimp.jpg", N, N);
	loadimage(&background, L"background.jpg", ScreenHeight, ScreenWidth);
	loadimage(&lplayerfish[0], L"lplayerlittlefish.jpg", N, N);
	loadimage(&lplayerfish[1], L"lplayerbigfish.jpg", 2 * N, 2 * N);
	loadimage(&rplayerfish[0], L"rplayerlittlefish.jpg", N, N);
	loadimage(&rplayerfish[1], L"rplayerbigfish.jpg", 2 * N, 2 * N);
}

//制作掩码图
extern void changeImage(IMAGE imagePut, int x, int y)
{
	IMAGE image(N, N); //存储掩码
	DWORD* pmemflower = GetImageBuffer(&imagePut);
	DWORD* image1 = GetImageBuffer(&image);
	DWORD* fish1 = GetImageBuffer(&imagePut);
	COLORREF backcolor = pmemflower[0];
	for (int i = 0; i < N * N; i++)
	{
		if (pmemflower[i] >= backcolor)
			image1[i] = WHITE;
		else
			image1[i] = BLACK;
	}
	for (int i = 0; i < N * N; i++)
	{
		if (pmemflower[i] >= backcolor)
			fish1[i] = BLACK;
	}
	putimage(x, y, &image, SRCAND);
	putimage(x, y, &imagePut, SRCPAINT);
}

//制作特定规格掩码图
extern void changeImage2(IMAGE imagePut, int x, int y)
{
	IMAGE image(100, 100); //存储掩码
	DWORD* pmemflower = GetImageBuffer(&imagePut);
	DWORD* image1 = GetImageBuffer(&image);
	DWORD* fish1 = GetImageBuffer(&imagePut);
	COLORREF backcolor = pmemflower[0];
	for (int i = 0; i < 100 * 100; i++)
	{
		if (pmemflower[i] >= backcolor)
			image1[i] = WHITE;
		else
			image1[i] = BLACK;
	}
	for (int i = 0; i < 100 * 100; i++)
	{
		if (pmemflower[i] >= backcolor)
			fish1[i] = BLACK;
	}
	putimage(x, y, &image, SRCAND);
	putimage(x, y, &imagePut, SRCPAINT);
}

//玩家移动
extern void move(player* player2)
{
	if (GetAsyncKeyState(65) & 0x8000)  player2->x -= 10;	// 左边
	if (GetAsyncKeyState(87) & 0x8000)  player2->y -= 10;	// 上面
	if (GetAsyncKeyState(83) & 0x8000)  player2->y += 10;	// 下面
	if (GetAsyncKeyState(68) & 0x8000)  player2->x += 10;	// 右边
}

//NPC移动
extern void movefish(fish* fish1)
{
	fish1->x += (rand() % 21) - 10;
	fish1->y += (rand() & 21) - 10;
}

//形态升级
extern void biggerplayer(player* player2)
{
	if (player2->score > ToBig)
		player2->size = 2;
}

//NPC重生
extern void reborn(fish* fish1)
{
	fish1->x = rand() % (ScreenHeight - 50);
	fish1->y = rand() % (ScreenWidth - 50);
}

//判断玩家是否死亡
extern void ifDead(fish* fish1, player* player2)
{
	if (fish1->size < player2->size)
	{
		player2->score++;
		biggerplayer(player2);
		reborn(fish1);
	}
	else if (fish1->size >= player2->size)
	{
		player2->live = false;
	}
}

//判断玩家与NPC是否相遇
extern void iftouch(fish* fish1, player* player2)
{
	if (((abs(fish1->x - player2->x) < N)) && (abs(fish1->y - player2->y) < N))//距离小于N,则相遇
		ifDead(fish1, player2);
}

extern void iftouch2(fish* fish1, player* player2)
{
	if (((abs(fish1->x - player2->x) < N)) && (abs(fish1->y - player2->y) < N))//距离小于N,则相遇
	{
		player2->score++;
		biggerplayer(player2);
		reborn(fish1);
	}
}

//判断鱼是否出界
extern void out(fish* fish1)
{
	if ((fish1->x < 0) || (fish1->x > (ScreenHeight - 50)))
	{
		reborn(fish1);
	}
	if ((fish1->y < 0) || (fish1->y > (ScreenWidth - 50)))
	{
		reborn(fish1);
	}
}

//判断玩家是否出界
extern void playerout(player* player2)
{
	if ((player2->x < 0) || (player2->x > ScreenHeight - 50))
		player2->live = false;
	if ((player2->y < 0) || (player2->y > ScreenWidth - 50))
		player2->live = false;
}

//打印计分板
extern void ScorePrint(player* player2)
{
	setbkmode(TRANSPARENT);	//设置字体背景色为透明
	settextcolor(BLACK);		//设置字体颜色为红色
	TCHAR str[32] = { 0 };
	_stprintf_s(str, _T("击杀:%d"), player2->score);
	outtextxy(5, 5, str);
	_stprintf_s(str, _T("形态:%d"), player2->size);
	outtextxy(5, 25, str);
}

#endif // !_Functions_H

3.main.cpp

//main.cpp
#include"Functions.h"

int main()
{
	player player1;
	fish bigfish[numB];
	fish littlefish[numL];
	fish shrimp[numS];
	srand(unsigned(time(0)));
	//玩家初始位置
	player* player2 = &player1;
	player2->x = rand() % (ScreenHeight - 50);
	player2->y = rand() % (ScreenWidth - 50);
	//鱼初始位置
	fish* shrimp1[15];//shrimp指针数组
	for (int i = 0; i < numS; i++)
	{
		shrimp1[i] = &shrimp[i];
		shrimp1[i]->x = rand() % (ScreenHeight - 50);
		shrimp1[i]->y = rand() % (ScreenWidth - 50);
		shrimp1[i]->size = 0;
	}
	fish* littlefish1[numL];//littlefish指针数组
	for (int i = 0; i < numL; i++)
	{
		littlefish1[i] = &littlefish[i];
		littlefish1[i]->x = rand() % (ScreenHeight - 50);
		littlefish1[i]->y = rand() % (ScreenWidth - 50);
		littlefish1[i]->size = 1;

	}
	fish* bigfish1[numB];
	for (int i = 0; i < numB; i++)
	{
		bigfish1[i] = &bigfish[i];
		bigfish1[i]->x = rand() % (ScreenHeight - 50);
		bigfish1[i]->y = rand() % (ScreenWidth - 50);
		bigfish1[i]->size = 2;

	}

	loadImages();//加载图片
	initgraph(ScreenHeight, ScreenWidth);//加载窗口界面
	putimage(0, 0, &background);//放置窗口背景板
	mciSendString(L"open  music.mp3 ", 0, NULL, NULL);//将音乐文件放在与源文件同一目录下,alias  music的意思就是将这个音乐文件重新命名为music
	mciSendString(L"play music.mp3 repeat", NULL, NULL, NULL);//播放音乐,play意为播放,同样可换做pause:暂停,stop:停止,close:关闭,Resume:继续。Repeat意为重复播放。
	mciSendstring(“stop music repeat”, NULL, NULL, NULL);//暂停播放音乐。
	setbkmode(TRANSPARENT);	//设置字体背景色为透明
	settextcolor(BLACK);		//设置字体颜色为红色
	/*****************游戏规则*****************/
	outtextxy(290, 200, L"游戏规则:");
	outtextxy(290, 230, L"玩家操作鱼吃比自己等级低的鱼");
	outtextxy(290, 260, L"遇到比自己等级高或同等级的鱼即死亡");
	outtextxy(290, 290, L"出界即死亡");
	outtextxy(290, 320, L"按回车键开始");
	outtextxy(290, 350, L"按P键暂停");
	outtextxy(290, 380, L"按ESC键自毁");
	outtextxy(290, 410, L"持续按C开启无敌模式");
	getchar();//使得开始界面静止,当用户按下Enter键时继续执行指令
	//char s[] = "blacksheepwall";
	
	//程序运行
	do
	{

		putimage(0, 0, &background);//放置游戏界面背景板
		ScorePrint(player2);//打印计分板

		//如果用户没有按C键,按此模式运行
		if (!(GetAsyncKeyState('C') & 0x8000))
		{
			//打印NPC-BOSS鱼
			for (int i = 0; i < numS; i++)
			{
				movefish(shrimp1[i]);//NPC移动函数
				changeImage(Fish[0], shrimp1[i]->x, shrimp1[i]->y);//制作掩码图
				iftouch(shrimp1[i], player2);//判断玩家与NPC是否相遇
				out(shrimp1[i]);//判断鱼是否出界
			}
			//打印NPC-小鱼
			for (int i = 0; i < numL; i++)
			{
				movefish(littlefish1[i]);
				changeImage(Fish[1], littlefish1[i]->x, littlefish1[i]->y);
				iftouch(littlefish1[i], player2);
				out(littlefish1[i]);
			}
			//打印NPC-大鱼
			for (int i = 0; i < numB; i++)
			{
				movefish(bigfish1[i]);
				changeImage2(Fish[2], bigfish1[i]->x, bigfish1[i]->y);
				iftouch(bigfish1[i], player2);
				out(bigfish1[i]);
			}
		}
		//若用户按下C键,进入无敌模式
		else
		{
			setbkmode(TRANSPARENT);	//设置字体背景色为透明
			settextcolor(BLACK);		//设置字体颜色为红色
			TCHAR str[32] = { 0 };
			_stprintf_s(str, _T("已开始作弊模式"));//提示操作者已进入无敌模式
			outtextxy(5, 45, str);
			for (int i = 0; i < numS; i++)
			{
				movefish(shrimp1[i]);
				changeImage(Fish[0], shrimp1[i]->x, shrimp1[i]->y);
				iftouch2(shrimp1[i], player2);//若接触,NPC必死
				out(shrimp1[i]);
			}
			for (int i = 0; i < numL; i++)
			{
				movefish(littlefish1[i]);
				changeImage(Fish[1], littlefish1[i]->x, littlefish1[i]->y);
				iftouch2(littlefish1[i], player2);//若接触,NPC必死
				out(littlefish1[i]);
			}
			for (int i = 0; i < numB; i++)
			{
				movefish(bigfish1[i]);
				changeImage2(Fish[2], bigfish1[i]->x, bigfish1[i]->y);
				iftouch2(bigfish1[i], player2);//若接触,NPC必死
				out(bigfish1[i]);
			}
		}

		//玩家的移动
		move(player2);
		if (Formerx > player2->x)//是否转弯
		{
			if (player2->size == 1)
				changeImage(lplayerfish[player2->size - 1], player2->x, player2->y);
			else changeImage2(lplayerfish[player2->size - 1], player2->x, player2->y);
		}
		else
		{
			if (player2->size == 1)
				changeImage(rplayerfish[player2->size - 1], player2->x, player2->y);
			else changeImage2(rplayerfish[1], player2->x, player2->y);
		}
		Formerx = player2->x;

		playerout(player2);
		if (GetAsyncKeyState('P') & 0x8000)//若按下P,则暂停
			ret = _getwch();
		Sleep(40);//帧率
	} while (player2->live == true && !(GetAsyncKeyState(VK_ESCAPE) & 0x8000));//在没有按下ESC或者玩家未死亡时,持续执行

	//设置结束页面
	putimage(0, 0, &background);
	setbkmode(TRANSPARENT);	//设置字体背景色为透明
	settextcolor(BLACK);		//设置字体颜色为红色
	outtextxy(290, 290, L"游戏结束:");
	outtextxy(290, 320, L"按任意键退出:");
	while (_kbhit())//键盘敲击,接受到任意键后退出
		ret = _getwch();
	ret = _getwch();
	closegraph();
	return 0;
}

三.后记

        当初写这个代码前前后后约摸花了一周半的时间,主要的难点在于刚接触完CPP的基础语法,就开始去编写这样一个对当时的我来说“十分不可思议”的“大程序”。包括要去学习一个新的库的用法,程序的组织……

        其实一开始想去尝试使用类来编写这个程序,但是编写的过程中发现类的继承之类的知识都还没有掌握,基础不牢,地动山摇,所以就还是采用了面向过程的编程方式。

        总而言之,感觉最终的程序虽然做出来了,但是组织程序的过程还是挺凌乱的,有些“不知道怎么了就做出来了”的感觉。谨以此文留作纪念。