C++入门——实现十字消除游戏

参考

  1. 《C和C++游戏趣味编程》 童晶

十字消除游戏

用户点击空白方块,沿其上、下、左、右方向寻找一个彩色方块,如果有两个或两个以上颜色一致,就将其消除。在进度条时间结束前消除足够的方块,可以进入下一关

红色方块的表示与绘制

定义Block结构体,利用Block类型的二维数组存储画面中所有小方块的信息。在startup()中将所有方块设置为红色填充、白色线条,在show()中绘制出所有方块:

在这里插入图片描述

#include <graphics.h>
#include <conio.h>
#include <stdio.h>
#define BlockSize 40           // 小方块的边长
#define RowNum 13              // 画面一共RowNum行
#define ColNum 21              // 画面一共ColNum列

struct Block
{
	int x, y;                  // 在画面中的x, y坐标
	int i, j;                  // 在二维数组中的i, j下标
};

// 全局变量
Block blocks[RowNum][ColNum];

void startup()
{
	int i, j;

	int width = BlockSize * ColNum;
	int height = BlockSize * RowNum;
	initgraph(width, height);
	setbkcolor(RGB(220, 220, 220));
	setfillcolor(RGB(255, 0, 0));
	setlinestyle(PS_SOLID, 2);  // 设置线型、线宽
	cleardevice();              // 以背景颜色清屏
	BeginBatchDraw();

	for (i = 0; i < RowNum; i++)
	{
		for (j = 0; j < ColNum; j++)
		{
			blocks[i][j].x = j * BlockSize;
			blocks[i][j].y = i * BlockSize;
			blocks[i][j].i = i;
			blocks[i][j].j = j;
		}
	}
}

void show()
{
	cleardevice();
	setlinecolor(RGB(255, 255, 255));
	int i, j;
	for (i = 0; i < RowNum; i++)
	{
		for (j = 0; j < ColNum; j++)
		{
			fillrectangle(blocks[i][j].x, blocks[i][j].y, blocks[i][j].x + BlockSize, blocks[i][j].y + BlockSize);
		}
	}
	FlushBatchDraw();
}

int main()
{
	startup();
	while (1)
	{
		show();
	}
	return 0;
}

随机颜色方块的实现

定义colors数组记录所有可能的颜色,其中第0种为灰白色,其他为彩色:

COLORREF colors[ColorTypeNum + 1];

在startup()中对colors数组进行初始化:

colors[0] = RGB(220, 220, 220);
for (i = 1; i < ColorTypeNum + 1; i++)
{
	colors[i] = HSVtoRGB((i - 1) * 40, 0.6, 0.8);
}

在结构体Block中添加成员变量colorId:

struct Block
{
	int x, y;                  // 在画面中的x, y坐标
	int i, j;                  // 在二维数组中的i, j下标
	int colorId;               // 对应颜色下标
};

在startup()中对blocks初始化,设置其颜色序号为[0, ColorTypeNum]的随机数:

int t = rand() % (ColorTypeNum + 1);
blocks[i][j].colorId = t;

为了让空白比例更高,可以调整一下:

int t = rand() % (int(ColorTypeNum * 1.5));
if (t < ColorTypeNum + 1)
{
	blocks[i][j].colorId = t;
}
else
{
	blocks[i][j].colorId = 0;
}

在show()中绘制对应颜色的小方块:

setfillcolor(colors[blocks[i][j].colorId);
fillrectangle(blocks[i][j].x + BlockSize, blocks[i][j].y + BlockSize);

在这里插入图片描述

鼠标点击与十字消除

添加updateWithInput()函数,根据鼠标点击位置(e.x, e.y)计算点中的小方块在二维数组中的行列号(clicked_i, clicked_j)

void updateWithInput()
{
	ExMessage e;
	if (peekmessage(&e))
	{
		if (e.message == WM_LBUTTONDOWN)
		{
			int clicked_i = int(e.y) / BlockSize;
			int clicked_j = int(e.x) / BlockSize;
		}
	}
}

先判断被鼠标点击的方块是否为空白方块,如果不是则直接返回:

if (blocks[clicked_i][clicked_j].colorId != 0)
{
	return;
}

定义数组fourBlocks[4]存储上下左右4个方向找到的第一个不是空白的方块:

Block fourBlocks[4] = { blocks[clicked_i][clicked_j] };  // 初始化为点击的方块

首先向上寻找,找到第一个不是空白的方块,存储到fourBlocks[4]中:

int search;
for (search = 0; clicked_i - search >= 0; search++)      // 向上搜索
{
	if (blocks[clicked_i - search][clicked_j].colorId != 0)
	{
		fourBlocks[0] = blocks[clicked_i - search][clicked_j];
		break;
	}
}

同理,向下、左、右分别找到第一个不是空白的方块:

for (search = 0; clicked_i + search < RowNum; search++)  // 向下搜索
{
	if (blocks[clicked_i + search][clicked_j].colorId != 0)
	{
		fourBlocks[1] = blocks[clicked_i + search][clicked_j];
		break;
	}
}

for (search = 0; clicked_j - search >= 0; search++)      // 向左搜索 
{
	if (blocks[clicked_i][clicked_j - search].colorId != 0)
	{
		fourBlocks[2] = blocks[clicked_i][clicked_j - search];
		break;
	}
}

for (search = 0; clicked_j + search < ColNum; search++)
{
	if (blocks[clicked_i][clicked_j + search].colorId != 0)
	{
		fourBlocks[3] = blocks[clicked_i][clicked_j + search];
		break;
	}
}

进一步,遍历fourBlocks,统计对应颜色方块的个数。如果某种颜色的方块个数colorStatistics[i] >= 2,则将对应方块的颜色序号设为0:

int colorStatistics[ColorTypeNum + 1] = { 0 };
for (i = 1; i <= ColorTypeNum; i++)
{
	for (j = 0; j < 4; j++)
	{
		if (fourBlocks[j].colorId == i)
		{
			colorStatistics[i]++;
		}
	}
	if (colorStatistics[i] >= 2)
	{
		for (j = 0; j < 4; j++)
		{
			if (fourBlocks[j].colorId == i)
			{
				blocks[fourBlocks[j].i][fourBlocks[j].j].colorId = 0;
			}
		}
	}
}

方块提示框的绘制

首先定义绘制提示框的函数:

void drawBlockHint(int i, int j, COLORREF color, int isfill)
{
	setlinecolor(color);
	setfillcolor(color);
	if (isfill == 1)           // 画填充方块
	{
		fillrectangle(blocks[i][j].x, blocks[i][j].y, blocks[i][j].x + BlockSize, blocks[i][j].y + BlockSize);
	}
	if (isfill == 0)           // 画非填充的方块线框
	{
		rectangle(blocks[i][j].x, blocks[i][j].y, blocks[i][j].x + BlockSize, blocks[i][j].y + BlockSize);
	}
}

在updateWithInput()中,如果点击的是空白方块,则执行:

show();                                                  // 先绘制其他方块
drawBlockHint(clicked_i, clicked_j, RGB(100, 100, 100), 1); // 对被点击的空白方块,绘制填充灰色的空白方块

如果十字区域有要消除的彩色方块,则执行:

if (fourBlocks[j].colorId == i)
{
	drawBlockHint(fourBlocks[j].i, fourBlocks[j].j, RGB(0, 0, 0), 0);  // 要消除的方块绘制提示框
	blocks[fourBlocks[j].i][fourBlocks[j].j].colorId = 0;
}

倒计时与进度条

添加全局变量:

float maxTime;                 // 游戏允许的总时长
float remainTime;              // 游戏剩余时长startup()中,加大窗口高度用于显示倒计时进度条:

int height = BlockSize * (RowNum + 2);

在updateWithoutInput()中,定义静态变量start,每次运行时获得当前时刻now,计算程序已运行的时间duration,求出游戏剩余时间:

void updateWithoutInput()
{
	static clock_t start = clock();                               // 记录第一次运行时刻
	clock_t now = clock();                                        // 获取当前时刻
	double duration = double(now - start) / CLOCKS_PER_SEC;       // 程序运行时间
	remainTime = maxTime - duration;
}

在show()中绘制倒计时进度条:

setlinecolor(RGB(255, 0, 0));                                 // 设置进度条颜色
setfillcolor(RGB(255, 0, 0));
fillrectangle(0, BlockSize * (RowNum + 0.2), remainTime * BlockSize * ColNum / maxTime, BlockSize * (RowNum + 0.8)); // 绘制进度条

得分计算与胜负判断

定义score记录玩家消去的方块个数,noZeroBlockNum记录游戏开始时彩色砖块的总数,设定当score >= 0.9 * noZeroBlockNum时游戏胜利:

int score;
int noZeroBlockNum;			   // 彩色方块个数

在startup()中统计彩色砖块的总数:

if (blocks[i][j].colorId != 0)
{
	noZeroBlockNum++;
}

在updateWithInput()中,更新十字区域消除的方块个数:

score += colorStatistics[i];

在show()中,显示当前得分score:

TCHAR s[80];                                                  // 定义字符数组
setbkmode(TRANSPARENT);
settextcolor(RGB(0, 0, 0));
settextstyle(25, 0, _T("宋体"));
swprintf_s(s, _T("当前%d分,达到%d分游戏胜利"), level, score, int(0.9 * noZeroBlockNum));
outtextxy(BlockSize * (ColNum / 4.5), BlockSize * (RowNum + 1.1), s);

多关卡与增加游戏难度

在startup()中,随着level的增加,当前关的游戏总时长越来越短:

maxTime = 200 - level * 10;

在show()中,显示当前为第几关、已得分数、得到多少分可以进入下一关:

swprintf_s(s, _T("当前第%d关,已得%d分,达到%d分进入下一关"), level, score, int(0.9 * noZeroBlockNum));

在updateWithoutInput()中,如果得分达到要求,则将level加1,重新计时;否则重新开始:

if (score >= int(0.9 * noZeroBlockNum))
{
	level++;                                                  // 进入下一关
	start = clock();                                          // 重新开始计时
	startup();
}
else if (remainTime <= 0)
{
	start = clock();
	startup();
}

完整代码

#include <graphics.h>
#include <conio.h>
#include <stdio.h>
#include <time.h>
#define BlockSize 40           // 小方块的边长
#define RowNum 13              // 画面一共RowNum行
#define ColNum 21              // 画面一共ColNum列
#define ColorTypeNum 9         // 方块彩色颜色的个数

struct Block
{
	int x, y;                  // 在画面中的x, y坐标
	int i, j;                  // 在二维数组中的i, j下标
	int colorId;               // 对应颜色下标
};

// 全局变量
Block blocks[RowNum][ColNum];
COLORREF colors[ColorTypeNum + 1];
float maxTime;                 // 游戏允许的总时长
float remainTime;              // 游戏剩余时长
float punishTime;              // 点错扣除的时间
int score;
int noZeroBlockNum;			   // 彩色方块个数
int level = 1;                 // 当前关卡序号


void drawBlockHint(int i, int j, COLORREF color, int isfill)
{
	setlinecolor(color);
	setfillcolor(color);
	if (isfill == 1)           // 画填充方块
	{
		fillrectangle(blocks[i][j].x, blocks[i][j].y, blocks[i][j].x + BlockSize, blocks[i][j].y + BlockSize);
	}
	if (isfill == 0)           // 画非填充的方块线框
	{
		rectangle(blocks[i][j].x, blocks[i][j].y, blocks[i][j].x + BlockSize, blocks[i][j].y + BlockSize);
	}
}

void startup()
{
	int i, j;

	int width = BlockSize * ColNum;
	int height = BlockSize * (RowNum + 2);
	initgraph(width, height);
	setbkcolor(RGB(220, 220, 220));
	setfillcolor(RGB(255, 0, 0));
	setlinestyle(PS_SOLID, 2);  // 设置线型、线宽
	cleardevice();              // 以背景颜色清屏
	BeginBatchDraw();
	srand(time(0));
	maxTime = 300 - level * 10;
	remainTime = maxTime;
	punishTime = 0;

	colors[0] = RGB(220, 220, 220);
	for (i = 1; i < ColorTypeNum + 1; i++)
	{
		colors[i] = HSVtoRGB((i - 1) * 40, 0.6, 0.8);
	}

	noZeroBlockNum = 0;

	for (i = 0; i < RowNum; i++)
	{
		for (j = 0; j < ColNum; j++)
		{
			int t = rand() % (int(ColorTypeNum * 1.5));
			if (t < ColorTypeNum + 1)
			{
				blocks[i][j].colorId = t;
			}
			else
			{
				blocks[i][j].colorId = 0;
			}
			blocks[i][j].x = j * BlockSize;
			blocks[i][j].y = i * BlockSize;
			blocks[i][j].i = i;
			blocks[i][j].j = j;
			if (blocks[i][j].colorId != 0)
			{
				noZeroBlockNum++;
			}
		}
	}
	score = 0;
}

void show()
{
	cleardevice();
	setlinecolor(RGB(255, 255, 255));

	int i, j;
	for (i = 0; i < RowNum; i++)
	{
		for (j = 0; j < ColNum; j++)
		{
			setfillcolor(colors[blocks[i][j].colorId]);
			fillrectangle(blocks[i][j].x, blocks[i][j].y, blocks[i][j].x + BlockSize, blocks[i][j].y + BlockSize);
		}
	}
	setlinecolor(RGB(255, 0, 0));                                 // 设置进度条颜色
	setfillcolor(RGB(255, 0, 0));
	fillrectangle(0, BlockSize * (RowNum + 0.2), remainTime * BlockSize * ColNum / maxTime, BlockSize * (RowNum + 0.8)); // 绘制进度条

	TCHAR s[80];                                                  // 定义字符数组
	setbkmode(TRANSPARENT);
	settextcolor(RGB(0, 0, 0));
	settextstyle(25, 0, _T("宋体"));
	swprintf_s(s, _T("当前第%d关,已得%d分,达到%d分进入下一关"), level, score, int(0.9 * noZeroBlockNum));
	outtextxy(BlockSize * (ColNum / 4.5), BlockSize * (RowNum + 1.1), s);
	FlushBatchDraw();
}

void updateWithoutInput()
{
	static clock_t start = clock();                               // 记录第一次运行时刻
	clock_t now = clock();                                        // 获取当前时刻
	double duration = double(now - start) / CLOCKS_PER_SEC;       // 程序运行时间
	remainTime = maxTime - duration - punishTime;

	if (score >= int(0.9 * noZeroBlockNum))
	{
		level++;                                                  // 进入下一关
		start = clock();                                          // 重新开始计时
		startup();
	}
	else if (remainTime <= 0)
	{
		start = clock();
		startup();
	}
}

void updateWithInput()
{
	if (remainTime <= 0)
	{
		return;
	}
	int i, j;
	ExMessage e;
	if (peekmessage(&e))
	{
		if (e.message == WM_LBUTTONDOWN)
		{
			int clicked_i = int(e.y) / BlockSize;
			int clicked_j = int(e.x) / BlockSize;

			if (blocks[clicked_i][clicked_j].colorId != 0)
			{
				return;
			}

			show();                                                  // 先绘制其他方块
			drawBlockHint(clicked_i, clicked_j, RGB(100, 100, 100), 1); // 对被点击的空白方块,绘制填充灰色的空白方块

			Block fourBlocks[4] = { blocks[clicked_i][clicked_j] };  // 初始化为点击的方块

			int search;
			for (search = 0; clicked_i - search >= 0; search++)      // 向上搜索
			{
				if (blocks[clicked_i - search][clicked_j].colorId != 0)
				{
					fourBlocks[0] = blocks[clicked_i - search][clicked_j];
					break;
				}
			}

			for (search = 0; clicked_i + search < RowNum; search++)  // 向下搜索
			{
				if (blocks[clicked_i + search][clicked_j].colorId != 0)
				{
					fourBlocks[1] = blocks[clicked_i + search][clicked_j];
					break;
				}
			}

			for (search = 0; clicked_j - search >= 0; search++)      // 向左搜索 
			{
				if (blocks[clicked_i][clicked_j - search].colorId != 0)
				{
					fourBlocks[2] = blocks[clicked_i][clicked_j - search];
					break;
				}
			}

			for (search = 0; clicked_j + search < ColNum; search++)  // 向右搜索
			{
				if (blocks[clicked_i][clicked_j + search].colorId != 0)
				{
					fourBlocks[3] = blocks[clicked_i][clicked_j + search];
					break;
				}
			}

			int colorStatistics[ColorTypeNum + 1] = { 0 };
			int isBadClick = 1;
			for (i = 1; i <= ColorTypeNum; i++)
			{
				for (j = 0; j < 4; j++)
				{
					if (fourBlocks[j].colorId == i)
					{
						colorStatistics[i]++;
					}
				}
				if (colorStatistics[i] >= 2)
				{
					isBadClick = 0;
					for (j = 0; j < 4; j++)
					{
						if (fourBlocks[j].colorId == i)
						{
							drawBlockHint(fourBlocks[j].i, fourBlocks[j].j, RGB(0, 0, 0), 0);
							blocks[fourBlocks[j].i][fourBlocks[j].j].colorId = 0;
						}
					}
					score += colorStatistics[i];
				}
			}

			if (isBadClick == 1)     // 如果错误点击
			{
				punishTime += 10;
			}

			FlushBatchDraw();
			Sleep(300);
		}
	}
}

int main()
{
	startup();
	while (1)
	{
		show();
		updateWithoutInput();
		updateWithInput();
	}
	return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值