用C语言写一个简单的俄罗斯方块(基于Windows窗口编程)

 

目录

 

1. 列出需要的头文件,结构体,全局变量和函数

2. 设计窗口和显示窗口

3. 消息处理函数

4. 实现俄罗斯方块的主要功能

4.1 绘制图形

4.2 创建方块

4.3 方块下落

4.4 方块左右移动

4.5 旋转方块

4.6 清除满行方块

5. 显示信息


1. 列出需要的头文件,结构体,全局变量和函数

#include <Windows.h>
#include <time.h>
#include <stdio.h>
using namespace std;
#define ID_DOWN_TIME 1
#define GAME_OVER 0

//坐标点
struct MyPoint
{
	int m_x;
	int m_y;
}curBlock[4],nextBlock[4];

//方块状态(空白、下落、落地)
enum STATE { BLANK, FALLING_BLOCKS, GROUND_BLOCKS };

//HANDLE g_hStdOutput = 0;
HINSTANCE g_hIns = 0;
int g_nScore = 0;//记录分数
int nBlockIndex = 0;//标记当前方块
int nNextBlockIndex = 0;
int nBlockSize = 20;
const int WIN_WIDTH = 600;		//窗口宽度
const int WIN_HEIGHT = 600;		//窗口高度
const int GAME_BORDER_W = 400;	//方块运动区域宽
const int GAME_BORDER_H = 600;	//方块运动区域高
const int ROW_COUNT = 30;		//纵坐标数量
const int COL_COUNT = 20;		//横坐标数量
int table[ROW_COUNT][COL_COUNT] = { 0 };//方块运动区域
//用二位数组表示方块的七种类型
int blocks[7][4] =
{
	1,3,5,7,  //I字型
	0,2,3,5,  //Z 1型
	1,3,4,5,  //Z 2型
	1,2,3,5,  //T
	0,2,4,5,  //L
	1,3,4,5,  //J
	0,1,2,3	  //田
};

//窗口回调函数
HRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

void OnPaint(HDC hdc);				//窗口绘制
void DrawBlock(HDC hdc);			//绘制方块
void CreateNextNewBlock();			//创建下一个方块
void Drop(HWND hWnd);				//方块下落
void OnTime(HWND hWnd);				//控制方块下落时间
BOOL IsLanding();					//判断方块是否正在下落
void MoveLeftOrRight(int setoff);	//左右移动方块
BOOL CheckBorder();					//检查边界
void Rotate();						//方块旋转
void ClearLine(HWND hWnd);			//方块满一行时清除
void SetCurBlocksValue();			//随机产生一个方块类型
void ShowInfor(HDC hdc);			//显示信息

2. 设计窗口和显示窗口

int CALLBACK WinMain(
	_In_		HINSTANCE	hIns,
	_In_opt_	HINSTANCE	hPreIns,
	_In_		LPSTR		lpCmdLine,
	_In_		int			nCmdShow)
{
	//显示控制台黑窗口
	//AllocConsole();
	/*g_hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	if (g_hStdOutput == INVALID_HANDLE_VALUE)
	{
		return -1;
	}*/

	g_hIns = hIns;//程序进程句柄

	//设计窗口类
	char szAppClassName[] = "Tetris";//窗口类名
	WNDCLASS wc = { 0 };			 //
	wc.cbClsExtra = 0;				 //窗口类的附加内存相当于缓冲区,一般置为0
	wc.cbWndExtra = 0;				 //窗口的附加内存相当于缓冲区,一般置为0
	wc.hbrBackground = CreateSolidBrush(RGB(0, 255, 0));//创建一个固定笔刷来填充窗口背景色
	wc.hCursor = NULL;					//鼠标指针,NULL默认样式
	wc.hIcon = NULL;					//图标指针,NULL默认样式
	wc.hInstance = hIns;				//程序的实例句柄,程序的唯一标识
	wc.lpfnWndProc = WndProc;			//由系统调用的消息处理函数
	wc.lpszClassName = szAppClassName;	//窗口类名指针
	wc.lpszMenuName = NULL;				//窗口菜单,NULL默认没有
	wc.style = CS_HREDRAW || CS_VREDRAW;//窗口显示样式
	//注册窗口
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd = CreateWindowExA(0, szAppClassName, "俄罗斯方块", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, 100, 100, WIN_WIDTH, WIN_HEIGHT, NULL, 0, g_hIns, NULL);
	//显示刷新窗口
	ShowWindow(hWnd, SW_SHOW);									
	UpdateWindow(hWnd);											
	//消息
	MSG uMsg = { 0 };	
	while(GetMessageW(&uMsg,NULL,0,0))
	{
		TranslateMessage(&uMsg);//翻译消息
		DispatchMessageW(&uMsg);//分发消息
	}

	//CloseHandle(g_hStdOutput);
	//CloseHandle(hWnd);
	return 0;
}

3. 消息处理函数

​
HRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_USER://自定义消息
		//游戏结束时关闭定时器,并发出提示游戏结束,关闭窗口进程
		KillTimer(hWnd, ID_DOWN_TIME);//关闭定时器
		MessageBox(NULL, "游戏结束", "提示:", MB_OK);
		PostQuitMessage(0);
		break;

	case WM_TIMER://定时器消息
		switch (wParam)
		{
			case ID_DOWN_TIME:
				//控制方块下落速度
				PostMessage(hWnd, WM_PAINT, 0, 0);
				OnTime(hWnd);
				break;
			default:
				break;
			}
		break;
	case WM_CREATE://在显示窗口前加载部分游戏数据
		CreateNextNewBlock();//显示下一个方块类型
		SetCurBlocksValue();//将下一个方块类型变为当前下落方块
		SetTimer(hWnd, ID_DOWN_TIME, 300, NULL);//启动定时器
		//重新创建下一个方块,要求下一个方块类型与当前类型不同
		do {
			CreateNextNewBlock();
		} while (nBlockIndex == nNextBlockIndex);
		
		break;

	case WM_DESTROY://结束进程
		PostQuitMessage(0);
		break;

	case WM_CLOSE://同上
		DestroyWindow(hWnd);
		break;

	case WM_KEYUP://按键消息
		
		switch (wParam)
		{
		case VK_RIGHT:
		{
			int count = 0;
			for (int i = 0; i < 4; i++)
			{
				if (curBlock[i].m_x + 1 >= COL_COUNT || table[curBlock[i].m_y][curBlock[i].m_x + 1] == GROUND_BLOCKS)
				{
					count++;
				}
			}
			if (count > 0)
			{
				break;
			}
			else
			{
				MoveLeftOrRight(1);
			}
		}
			
			break;
		case VK_LEFT:
		{
			int count = 0;
			for (int i = 0; i < 4; i++)
			{
				if (curBlock[i].m_x - 1 < 0 || table[curBlock[i].m_y][curBlock[i].m_x - 1] == GROUND_BLOCKS)
				{
					count++;
				}
			}
			if (count > 0)
			{
				break;
			}
			else
			{
				MoveLeftOrRight(-1);
			}
		}
			break;

		case VK_DOWN:
			PostMessage(hWnd, WM_TIMER, ID_DOWN_TIME, 0);
			break;

		case VK_UP:
			Rotate();
			break;
		default:
			break;
		}
		InvalidateRect(hWnd, NULL, TRUE);
		PostMessage(hWnd, WM_PAINT, 0, 0);
		break;

	case WM_PAINT: //刷新窗口
	{
		PAINTSTRUCT ps = { 0 };
		HDC hdc = BeginPaint(hWnd, &ps);
		OnPaint(hdc);
		EndPaint(hWnd, &ps);
		ClearLine(hWnd);
	}
		break;

	default:
		break;
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

​

4. 实现俄罗斯方块的主要功能

4.1 绘制图形

在游戏运行时,每个图形都是个画个的,窗口画面会出现闪烁现象,消除这一现象,需要利用双缓冲技术。

在内存中先创建一个与窗口区域大小一致的对象,将图形先绘制在内存对象上,再将这个对象一次性拷贝到窗口上,让窗口画面变得完整。

void OnPaint(HDC hdc)
{
	//双缓冲绘图(在内存中重新开辟一个与当前窗口大小一样的窗口,先将图形绘制在内存中的窗口上)
	HDC hMemDC = CreateCompatibleDC(hdc);//创建一个内存DC
	HBITMAP hBackBmp = CreateCompatibleBitmap(hdc, WIN_WIDTH, WIN_HEIGHT);//在这个内存DC上创建画布
	HGDIOBJ hOldBit = SelectObject(hMemDC, hBackBmp);//将这画布交给系统来绘制图形
	//将图形绘制在内存DC中
	RECT rect = { 0,0,WIN_WIDTH,WIN_HEIGHT };
	FillRect(hMemDC, &rect, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
	//打印出分数
	SetBkMode(hMemDC, TRANSPARENT);
	ShowInfor(hMemDC);
	HFONT hFont = CreateFont(40, 0, 0, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0, NULL);//用于更改字体样式
	HFONT hOldFont = (HFONT)SelectObject(hMemDC, hFont);
	char szText[255] = { 0 };
	sprintf(szText, "得分:%d", g_nScore);
	TextOut(hMemDC, 420, 200, szText, strlen(szText));//文字输出位置
	SelectObject(hMemDC, hOldFont);
	DeleteObject(hFont);//删除掉创建出的字体样式

	//显示出下一个方块
	for (int i = 0; i < 4; i++)
	{
		int xx = (nextBlock[i].m_x + 24) * nBlockSize;
		int yy = (nextBlock[i].m_y + 2) * nBlockSize;
		Rectangle(hMemDC, xx, yy, xx + nBlockSize, yy + nBlockSize);
	}
	
	Rectangle(hMemDC,0,0,GAME_BORDER_W,GAME_BORDER_H);
	DrawBlock(hMemDC);
	//将绘制完的内存图像复制到窗口上
	BitBlt(hdc, 0, 0, WIN_WIDTH, WIN_HEIGHT, hMemDC, 0, 0, SRCCOPY);
	//删除内存DC和画布
	SelectObject(hMemDC, hOldBit);
	DeleteObject(hBackBmp);
	DeleteDC(hMemDC);
	DeleteObject(hBackBmp);
}

//绘制方块
void DrawBlock(HDC hdc)
{
	//将下落的方块涂成红色,落地的方块涂成绿色
	HBRUSH hRedBrush = CreateSolidBrush(RGB(255, 0, 0));
	HBRUSH hGreenBrush = CreateSolidBrush(RGB(0, 255, 0));
	HBRUSH hPreBrush;
	
	for (int i = 0; i < ROW_COUNT; i++)
	{
		for (int j = 0; j < COL_COUNT; j++)
		{
			if (table[i][j] == FALLING_BLOCKS)
			{
				hPreBrush = (HBRUSH)SelectObject(hdc, hRedBrush);
				Rectangle(hdc, j * nBlockSize + 1, i * nBlockSize + 1, j * nBlockSize + nBlockSize - 1, i * nBlockSize + nBlockSize - 1);
				SelectObject(hdc, hPreBrush);
				table[i][j] = BLANK;
			}
			else if (table[i][j] == GROUND_BLOCKS)
			{
				hPreBrush = (HBRUSH)SelectObject(hdc, hGreenBrush);
				Rectangle(hdc, j * nBlockSize + 1, i * nBlockSize + 1, j * nBlockSize + nBlockSize - 1, i * nBlockSize + nBlockSize - 1);
				SelectObject(hdc, hPreBrush);
			}
		}
	}
	DeleteObject(hGreenBrush);
	DeleteObject(hRedBrush);
}

4.2 创建方块

七种方块类型直接用二位数组表示可以节省空间,避免每种类型都要用一个二维数组。

用纵坐标表示类型,用横坐标表示四个小块的位置。

void CreateNextNewBlock()
{
	//随机产生方块
	srand((UINT)time(NULL));
	nNextBlockIndex = 1 + rand() % 7; //从1~7中类型中任意产生一个类型
	int n = nNextBlockIndex - 1;
	//方块类型保存在二维数组中
	for (int i = 0; i < 4; i++)
	{
		nextBlock[i].m_x = blocks[n][i] % 2;//方块中每个小块x坐标
		nextBlock[i].m_y = blocks[n][i] / 2;//方块中每个小块y坐标
	}

	// 1 0
	// 1 0
	// 1 1  -> { 0, 2, 4, 5 },一个一维数组表示一个方块类型
	// 0 0
}

//给当前下落方块赋值
void SetCurBlocksValue()
{
	//将提示的下一个方块类型标记给当前需要显示的方块
	nBlockIndex = nNextBlockIndex;
	for (int i = 0; i < 4; i++)
	{
		curBlock[i].m_x = nextBlock[i].m_x + 8;
		curBlock[i].m_y = nextBlock[i].m_y;
		table[curBlock[i].m_y][curBlock[i].m_x] = FALLING_BLOCKS;//在地图上标记方块
	}
}

4.3 方块下落

在下落过程中要判断是否越界

//方块自由下落
void Drop(HWND hWnd)
{
	//判断方块是在下落且没有碰到其他方块或者地面,方块下落+1
	if (!IsLanding())
	{
		for (int i = 3; i >= 0; i--)
		{
			curBlock[i].m_y += 1;
			table[curBlock[i].m_y][curBlock[i].m_x] = FALLING_BLOCKS;
		}
	}
	else
	{
		//将方块标记已落地
		for (int i = 3; i >= 0; i--)
		{
			table[curBlock[i].m_y + 1][curBlock[i].m_x] = GROUND_BLOCKS;
		}
		SetCurBlocksValue();
		//判断方块是否碰到顶部,碰到顶部不产生新方块游戏结束
		if (table[2][8] != GROUND_BLOCKS && table[2][9] != GROUND_BLOCKS)
		{
			CreateNextNewBlock();
		}
		else
		{
			SendMessage(hWnd, WM_USER, GAME_OVER, NULL);
		}
	}
}

//刷新方块位置
void OnTime(HWND hWnd)
{
	Drop(hWnd);
	InvalidateRect(hWnd,NULL,TRUE);
}

//判断方块下落是否到底或者是否接触其他方块
BOOL IsLanding()
{
	int count = 0;
	//判断方块的四个小块是否有接触,如果有一个接触就返回true
	for (int i = 3; i >= 0; i--)
	{
		if (curBlock[i].m_y + 4 >= ROW_COUNT || table[curBlock[i].m_y + 2][curBlock[i].m_x] == GROUND_BLOCKS)
		{
			count++;
		}
	}
	if (count > 0)
	{
		return true;
	}
	return false;
}

4.4 方块左右移动

//方块左右移动
void MoveLeftOrRight(int setoff)
{
	for (int i = 0; i < 4; i++)
	{
		curBlock[i].m_x += setoff;
		table[curBlock[i].m_y][curBlock[i].m_x] = FALLING_BLOCKS;
	}
}

4.5 旋转方块

利用数学公式:在坐标中(x,y)围绕某点旋转90°。

//旋转方块
void Rotate()
{
	if (nBlockIndex == 7)//表示田字型方块不用旋转
	{
		return;
	}
	MyPoint bakBlock[4] = { 0 };
	for (int i = 0; i < 4; i++)
	{
		bakBlock[i] = curBlock[i];
	}
	MyPoint midPoint = curBlock[1];

	for (int i = 3; i >= 0; i--)
	{
		//根据坐标旋转公式将方块顺时针旋转
		MyPoint tempPoint = curBlock[i];
		curBlock[i].m_x = midPoint.m_x - tempPoint.m_y + midPoint.m_y;
		curBlock[i].m_y = tempPoint.m_x - midPoint.m_x + midPoint.m_y;
		table[curBlock[i].m_y][curBlock[i].m_x] = FALLING_BLOCKS;
	}
	//旋转时检查边界,不满足时返回原样
	if (CheckBorder())
	{
		for (int i = 3; i >= 0; i-- )
		{
			table[curBlock[i].m_y][curBlock[i].m_x] = BLANK;
			curBlock[i] = bakBlock[i];
			table[curBlock[i].m_y][curBlock[i].m_x] = FALLING_BLOCKS;
		}
	}
}

//检测是否超界
BOOL CheckBorder()
{
	int count = 0;
	for (int i = 0; i < 4; i++)
	{
		if (curBlock[i].m_x < 0 || curBlock[i].m_x >= COL_COUNT
			|| curBlock[i].m_y >= ROW_COUNT
			|| table[curBlock[i].m_y][curBlock[i].m_x] == GROUND_BLOCKS)
		{
			count++;
		}
	}
	if (count > 0)
	{
		return true;
	}
	return false;
}

4.6 清除满行方块

将方块区域从下到上遍历每行,如果满行标记为k,将k的上一行移至k行上,进行消除满行过程。

在将未满行逐行下移。

//某行上方块满时进行清除
void ClearLine(HWND hWnd)
{
	int k = ROW_COUNT - 1;//标记k行满时,将它上一行位置移动到k行
	//由下往上判断是否满足条件
	for (int i = ROW_COUNT - 1; i >= 0; i--)
	{
		int count = 0;
		for (int j = 0; j < COL_COUNT; j++)
		{

			if (table[i][j] == GROUND_BLOCKS)
			{
				count++;
			}
			table[k][j] = table[i][j];//将i行上的方块移到k行
			if (table[0][j] == GROUND_BLOCKS)
			{
				SendMessage(hWnd, WM_USER, GAME_OVER, NULL);
			}
		}
		//当k行方块已满,k留在原行上,不满时,k移至上一行
		if (count < COL_COUNT)
		{
			k--;
		}
		//删掉一行分数+10
		if (count == COL_COUNT)
		{
			g_nScore += 10;
		}
	}
}

5. 显示信息

//显示信息
void ShowInfor(HDC hdc)
{
	char szInfor[100] = { 0 };
	SetBkMode(hdc, TRANSPARENT);
	HFONT hFont = CreateFont(20, 0, 0, 0, 600, 0, 0, 0, 0, 0, 0, 0, 0, NULL);
	HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
	sprintf(szInfor, "↑ 旋转方块");
	TextOut(hdc, 420, 300, szInfor, strlen(szInfor));
	sprintf(szInfor, "← 向左移动");
	TextOut(hdc, 420, 350, szInfor, strlen(szInfor));
	sprintf(szInfor, "→ 向右移动");
	TextOut(hdc, 420, 400, szInfor, strlen(szInfor));
	sprintf(szInfor, "↓ 向下加速");
	TextOut(hdc, 420, 450, szInfor, strlen(szInfor));

	SelectObject(hdc, hOldFont);
	DeleteObject(hFont);
}

  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我无法提供完整的C语言俄罗斯方块游戏代码。但是,我可以给你一些关于C语言俄罗斯方块游戏代码的思路和方法。 在C语言中实现俄罗斯方块游戏,你可以使用控制台来显示游戏界面和方块。以下是一些实现俄罗斯方块游戏的基本步骤和思路: 1. 定义方块的形状和类型:你可以使用二维数组来表示不同类型的方块,每个方块由4个小方块组成。可以使用数字或字符来表示方块的形状。 2. 初始化游戏界面:使用二维数组来表示游戏界面,每个元素代表一个方块的状态(是否被占据)。可以使用空字符或特殊字符来表示空白位置和已占据位置。 3. 生成新的方块:随机选择一个方块类型,并将其放置在游戏界面的顶部中央位置。 4. 移动方块:根据用户的输入(例如按下键盘上的方向键),移动当前方块的位置。可以通过改变方块的坐标来实现移动。 5. 碰撞检测:在移动方块之前,检查方块是否与已占据的位置或游戏界面的边界发生碰撞。如果发生碰撞,则停止移动方块。 6. 方块固定和消除:当方块无法继续下落时,将方块固定在游戏界面上,并检查是否有完整的行。如果有完整的行,则将其消除,并更新游戏界面。 7. 游戏结束判断:当方块无法放置在游戏界面的顶部中央位置时,游戏结束。 以上是实现俄罗斯方块游戏的基本思路和步骤。你可以根据这些思路来编自己的C语言俄罗斯方块游戏代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值