俄罗斯方块

本文详细介绍了如何使用C++实现俄罗斯方块游戏,包括创建Win32窗口、窗口消息处理、图形绘制、游戏逻辑实现如方块下落、旋转、消除行等。此外,还涉及到定时器、键盘事件处理以及游戏结束条件判断等内容,帮助读者理解游戏开发的基本流程。

俄罗斯方块实现步骤:

1.创建Win32窗口

1.创建窗口结构体

typedef struct tagWNDCLASSEXA {
  UINT      cbSize;
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCSTR    lpszMenuName;
  LPCSTR    lpszClassName;
  HICON     hIconSm;
} WNDCLASSEXA, *PWNDCLASSEXA, *NPWNDCLASSEXA, *LPWNDCLASSEXA;

2.注册窗口结构体

ATOM RegisterClassExA(
  const WNDCLASSEXA *unnamedParam1
);

3.创建窗口

HWND CreateWindowExA(
  DWORD     dwExStyle,
  LPCSTR    lpClassName,
  LPCSTR    lpWindowName,
  DWORD     dwStyle,
  int       X,
  int       Y,
  int       nWidth,
  int       nHeight,
  HWND      hWndParent,
  HMENU     hMenu,
  HINSTANCE hInstance,
  LPVOID    lpParam
);

4.显示窗口

BOOL ShowWindow(
  HWND hWnd,
  int  nCmdShow
);

5.更新窗口

BOOL UpdateWindow(
  HWND hWnd
);

6.消息循环

// 获取消息
BOOL GetMessage(
  LPMSG lpMsg,
  HWND  hWnd,
  UINT  wMsgFilterMin,
  UINT  wMsgFilterMax
);
// 翻译消息 将虚拟键的消息转换成字符消息
BOOL TranslateMessage(
  const MSG *lpMsg
);
// 分发消息
LRESULT DispatchMessage(
  const MSG *lpMsg
);

7.回调函数

static LRESULT CALLBACK WindowProc(
   HWND hWnd,
   UINT uMsg,
   WPARAM wParam,
   LPARAM lParam 
);

2.处理回调函数参数2中当窗口发生变化之后执行的WM_PAINT消息

1.调用BeginPaint函数开始绘图,调用EndPaint函数结束绘图

HDC BeginPaint(
HWND hwnd, // 窗口的句柄
LPPAINTSTRUCT lpPaint // 绘制信息
);
BOOL EndPaint(
HWND hWnd, // 窗口句柄
CONST PAINTSTRUCT *lpPaint // 绘制窗口的数据
);

2.在BeginPaint函数和EndPaint函数之间进行窗口绘图操作

  • 画矩形函数

BOOL Rectangle(
  HDC hdc,
  int left,
  int top,
  int right,
  int bottom
);

​​​​创建兼容性DC(创建内存DC)

HDC CreateCompatibleDC(HDC hdc);

创建兼容性位图

 HBITMAP CraeteCompatibleBitmap(HDC hdc,int nWidth, int nHeight);

将DC于位图绑定在一起

 HGDIOBJ SelectObject(HDC hdc, HGDIOBJ hgdiobj);

 释放DC

BOOL DeleteDC(HDC hdc);

释放关联

BOOL DeleteObject(HGDIOBJ ho);

 将内存DC传递到窗口DC

BOOL BitBlt(
	  HDC   hdc,	// 目标HDC(窗口DC)
	  int   x,		 
	  int   y,		// 参数2,3 将内存贴到窗口DC 要从哪开始的起始位置
	  int   cx,		 
	  int   cy,		// 参数4,5目标 区域的大小 要贴的大小
	  HDC   hdcSrc,	// 原DC(创建的内存DC) 创建的兼容性DC HDC HMemDC = CreateCompatibleDC(hdc);
	  int   x1, 
	  int   y1,		// 参数6,7  截取从x1,y1开始之后的全部的内存DC
	  DWORD rop		// 参数8 以什么样的方式将内存DC传递给窗口DC
	);

创建画刷

HBRUSH CreateSolidBrush(COLORREF color);

设置字体

 

// 设置文字大小和颜色
LOGFONT logfont;	// 声明Windows内部字体逻辑结构体对象
ZeroMemory(&logfont, sizeof(LOGFONT));	// ZeroMemory作用相当与memset
// 给结构体成员赋值
logfont.lfCharSet = GB2312_CHARSET;
logfont.lfHeight = 25;	// 设置字体的大小
// 创建逻辑字体
HFONT hFont = CreateFontIndirect(&logfont);
// 将创建的逻辑字体关联起来
SelectObject(hMemDC, hFont);

定时器

// 启动定时器:
UINT_PTR
WINAPI
SetTimer(
    _In_opt_ HWND hWnd,
    _In_ UINT_PTR nIDEvent,
    _In_ UINT uElapse,
    _In_opt_ TIMERPROC lpTimerFunc);
// 参数1:窗口句柄 hWnd,	填NULL
// 参数2:定时器ID		声明一个宏 直接添这个宏 #define DEF_TIMER1 1234
// 参数3:间隔时间 毫秒
// 参数4:设置为NULL 	处理函数的地址

// 关闭定时器
BOOL
WINAPI
KillTimer(
    _In_opt_ HWND hWnd,
    _In_ UINT_PTR uIDEvent);

窗口中输入文本

 

BOOL TextOut(
  HDC hdc,           // 设备描述表句柄
  int nXStart,       // 字符串的开始位置 x坐标
  int nYStart,       // 字符串的开始位置 y坐标
  LPCTSTR lpString,  // 字符串
  int cbString       // 字符串中字符的个数
  );

 

3.随机产生方块的形状,贴到游戏背景中

3.1、在游戏背景画一个20*10(char g_arrBackGround[20][10];)的二维数组,画30*30的方块:

 3.2、设置游戏方块形状,用2*4 (char g_arrShapeBlock[2][4];)的二维数组表示,当二维数组元素值为1时,表示要画方块,其贴到游戏背景中,设置当背景二维数组元素值为1时,画方块:

4.方块下落

将当前行所在的方块赋给下一行,再将其置0

5.第一次方块停止下落

当方块到达最后一行时,将其二维数组元素值置成2

6.重新产生方块

直接调用随机创建好的7中方块,再将其贴到背景二维数组中

7.方块旋转操作

7.1、方块形状 Z、T、L 这3中形状旋转之后的规律是一样的

 7.2、长条形状的方块旋转操作

直接进行赋值和置0操作,

横转竖:将横向的方块置0(注意旋转原点是第二个方块),第二个方块不用置0,再将第二个方块所在的竖的方向的方块置1

竖转横:将竖向的方向置0,第二个方块不用置0,再将第二个方块所在的横的方向的方块置1

8. 行满消行

将当前行满的上一行赋给当前行(覆盖),之后依次将上上一行赋给上一行

9.判断游戏是否结束

当第二行的方块二维数组元素值为2,代表游戏结束

完整代码:  

elsfk.h 文件

#pragma once
#include <windows.h>
#include <winnt.h>
#include <mmsystem.h>

#pragma comment(lib, "winmm.lib")


// 声明定时器函数参数2所需的宏 宏的数字可以随便填
#define DEF_TIMER1 1234

// 封装一个绘图函数
void OnPaint(HDC hdc);

// 画方块 显示方块
void PaintBlock(HDC hMemDC);

// 重新显示方块 解决重新产生方块时底部方块消失问题
void ShowBlock2(HDC hMemDC);


// 设置方块形状并随机产生方块
int CreateRandomBlock();
// 设置不同形状的方块颜色
void SetBlockColor(HDC hMemDC, HBRUSH hOldBrush, HBRUSH hNewBrush);

// 将设置方块形状并随机产生方块贴到游戏背景中
void CopyBlockToBack();

// 初始化游戏数据 只调用一次 放到处理回调函数消息 的 WM_CREATE 中表示只初始化一次
void OnCreate();

// 设置定时器 定时器响应的事件
void OnTimer(HWND hwnd);

// 方块下落
void BlockDown();
// 方块左移
void BlockLeft();
// 方块右移
void BlockRight();
// 方块变形
void BlockChange();
// 旋转I字型方块
void BlockLongChange();


// 判断方块是否可以继续下落
int CanBlockDown();
// 判断方块是否可以继续下降 (当已有停下的方块的情况下)
int CanBlockDown2();
// 判断是否可以继续左移
int CanBlockLeft();
// 判断方块是否可以继续左移 (当左边有方块的情况下)
int CanBlockLeft2();
// 判断方块是否可以继续右移
int CanBlockRight();
// 判断方块是否可以继续右移 (当右边方块的情况下)
int CanBlockRight2();
// 判断方块是否可以继续旋转
int CanBlockChange();
// 判断长条形状的方块是否可以继续旋转 (I字型方块)
int CanBlockLongChange();


// 将停下的方块对应的二维数组元素值置成2
void ChangeOneToTwo();

// 按下回车键开启定时器
void OnReturn(HWND hwnd);
// 按下向下键 加速
void OnDwon(HWND hwnd);
// 方块左移
void OnLeft(HWND hwnd);
// 方块右移
void OnRight(HWND hwnd);
// 方块变形
void OnChangeShape(HWND hwnd);

// 行满消行处理
void DestroyLine();

// 显示游戏玩法提示 得分
void ShowScore(HDC HMemDC);
// 判断是否结束游戏
int IsGameOver();

// 再显示游戏信息处 显示下一个随机出现的方块
void ShowNextBlock(HDC HMemDC, HBRUSH hOldBrush, HBRUSH hNewBrush);

elsfk.c 文件 

 

#include "elsfk.h"
#include <time.h>

// 定义一个二维数组将游戏背景划分为一个20*10的二维数组
char g_arrBackGround[20][10];	// 20行 10列
// 定义一个用于设置俄罗斯方块形状的2*4的二维数组
char g_arrShapeBlock[2][4];	// 2行4列
// 定义两个变量记录当前方块所在的位置
int g_Row;	// 行
int g_Col;	// 列
// 定义一个用于记录随机产生方块形状的变量
int g_BlockID;
// 定义一个变量用于计算分数
int g_score;


// 实现窗口绘图函数
void OnPaint(HDC hdc)
{
	// 1.创建一张内存DC,
	// 作用:将所有的绘图全部在内存DC中进行操作,防止出现闪屏
	// 创建兼容性DC
	HDC HMemDC = CreateCompatibleDC(hdc);
	// 定义关联函数中参数2所需的位图句柄 HBITMAP
	// 创建一张纸 HBITMAP位图句柄 参数1:窗口可操作DC, 参数2,3:窗口的大小
	HBITMAP hBitmpBack = CreateCompatibleBitmap(hdc, 500, 600);	// 这里要改成 500,600
	// 将内存DC和位图句柄关联起来
	SelectObject(HMemDC, hBitmpBack);

	// 画方块
	PaintBlock(HMemDC);
	// 重画方块 解决底部方块消失问题
	ShowBlock2(HMemDC);
	// 显示分数
	ShowScore(HMemDC);
	HBRUSH hOldBrush;
	// 创建新的画刷
	HBRUSH hNewBrush = CreateSolidBrush(RGB(232, 232, 232));
	// 将画刷与可操作客户区关联起来
	hOldBrush = SelectObject(HMemDC, hNewBrush);
	// 再提示游戏玩法信息处画方块
	ShowNextBlock(HMemDC, hOldBrush, hNewBrush);
	// 用完画刷之后要将创建的画刷释放掉 将旧的画刷重新拿回来
	hNewBrush = SelectObject(HMemDC, hOldBrush);
	// 释放
	DeleteObject(hNewBrush);
	// 将内存DC传递到窗口DC
	// BitBlt函数 参数1:目标HDC(窗口DC) 参数2,3:将内存 从窗口DC什么位置开始贴起
	// 参数4,5:贴取内存DC区域的大小,参数6:内存DC, 参数7,8:从内存DC什么位置开始贴起
	// 参数9:以什么样的内存DC传递给窗口DC
	BitBlt(hdc, 0, 0, 500, 600, HMemDC, 0, 0, SRCCOPY);

	// 删除关联
	DeleteObject(hBitmpBack);
	// 删除兼容性DC
	DeleteDC(hdc);
}
// 画方块 显示方块
void PaintBlock(HDC hMemDC)
{
	// 给背景颜色
	// 声明就的画刷(默认的画刷)
	HBRUSH hOldBrush;
	// 创建新的画刷
	HBRUSH hNewBrush = CreateSolidBrush(RGB(232, 232, 232));
	// 将画刷与可操作客户区关联起来
	hOldBrush =  SelectObject(hMemDC, hNewBrush);
	// 画矩形 游戏有效区域
	Rectangle(hMemDC, 0, 0, 300, 600);
	// 设置不同形状的方块颜色
	SetBlockColor(hMemDC, hOldBrush, hNewBrush);
	// 当背景二维数组元素值为1时,画方块
	for (int i = 0; i < 20; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			// 找数组元素为1
			if (1 == g_arrBackGround[i][j])
			{
				// 画方块 从左上角开始画 右下角结束绘图
				Rectangle(hMemDC, j * 30, i * 30, j * 30 + 30, i * 30 + 30);
			}
		}
	}
	// 用完画刷之后要将创建的画刷释放掉 将旧的画刷重新拿回来
	hNewBrush = SelectObject(hMemDC, hOldBrush);
	// 释放
	DeleteObject(hNewBrush);
}
// 重新显示方块 解决重新产生方块时底部方块消失问题
void ShowBlock2(HDC hMemDC)
{
	// 给方块颜色
	// 声明旧的画刷
	HBRUSH HOldBrush;
	// 定义新的画刷
	HBRUSH HNewBrush = CreateSolidBrush(RGB(145, 31, 169));
	// 将新的画刷与可操作客户区关联起来
	HOldBrush =  SelectObject(hMemDC, HNewBrush);

	// 将方块对应的二维数组元素值为2的 重新画一遍
	// 从底行开始进行遍历
	for (int i = 19; i >= 0; i--)
	{
		for (int j = 0; j < 10; j++)
		{
			// 将元素值为2 重画方块
			if (2 == g_arrBackGround[i][j])
			{
				Rectangle(hMemDC, j * 30, i * 30, j * 30 + 30, i * 30 + 30);
			}
		}
	}
	// 将旧的画刷重新拿回来
	HNewBrush = SelectObject(hMemDC, HOldBrush);
	// 释放新的画刷
	DeleteObject(HNewBrush);
}
// 设置方块形状并随机产生方块
int CreateRandomBlock()
{
	// 获取随机数 0 - 6
	int iIndex = rand() % 7;
	// 通过获取的随机数分类设置方块的形状
	switch (iIndex)
	{	// 数组元素为1时,表示方块
	case 0:	// 设置Z字型1 的方块
		g_arrShapeBlock[0][0] = 1, g_arrShapeBlock[0][1] = 1, g_arrShapeBlock[0][2] = 0, g_arrShapeBlock[0][3] = 0,
		g_arrShapeBlock[1][0] = 0, g_arrShapeBlock[1][1] = 1, g_arrShapeBlock[1][2] = 1, g_arrShapeBlock[1][3] = 0;
		// 记录将当前方块设置到背景二维数组的初始位置
		g_Row = 0;	// 行为0
		g_Col = 3;	// 列为3
		break;
	case 1:	// 设置Z字型2 的方块
		g_arrShapeBlock[0][0] = 0, g_arrShapeBlock[0][1] = 1, g_arrShapeBlock[0][2] = 1, g_arrShapeBlock[0][3] = 0,
		g_arrShapeBlock[1][0] = 1, g_arrShapeBlock[1][1] = 1, g_arrShapeBlock[1][2] = 0, g_arrShapeBlock[1][3] = 0;
		// 记录将当前方块设置到背景二维数组的初始位置
		g_Row = 0;	// 行为0
		g_Col = 3;	// 列为3
		break;
	case 2:	// 设置L字型1 的方块
		g_arrShapeBlock[0][0] = 1, g_arrShapeBlock[0][1] = 0, g_arrShapeBlock[0][2] = 0, g_arrShapeBlock[0][3] = 0,
		g_arrShapeBlock[1][0] = 1, g_arrShapeBlock[1][1] = 1, g_arrShapeBlock[1][2] = 1, g_arrShapeBlock[1][3] = 0;
		// 记录将当前方块设置到背景二维数组的初始位置
		g_Row = 0;	// 行为0
		g_Col = 3;	// 列为3
		break;
	case 3:	// 设置L字型2 的方块
		g_arrShapeBlock[0][0] = 1, g_arrShapeBlock[0][1] = 1, g_arrShapeBlock[0][2] = 1, g_arrShapeBlock[0][3] = 0,
		g_arrShapeBlock[1][0] = 0, g_arrShapeBlock[1][1] = 0, g_arrShapeBlock[1][2] = 1, g_arrShapeBlock[1][3] = 0;
		// 记录将当前方块设置到背景二维数组的初始位置
		g_Row = 0;	// 行为0
		g_Col = 3;	// 列为3
		break;
	case 4:	// 设置T字型 的方块
		g_arrShapeBlock[0][0] = 0, g_arrShapeBlock[0][1] = 1, g_arrShapeBlock[0][2] = 0, g_arrShapeBlock[0][3] = 0,
		g_arrShapeBlock[1][0] = 1, g_arrShapeBlock[1][1] = 1, g_arrShapeBlock[1][2] = 1, g_arrShapeBlock[1][3] = 0;
		// 记录将当前方块设置到背景二维数组的初始位置
		g_Row = 0;	// 行为0
		g_Col = 3;	// 列为3
		break;
	case 5:	// 设置田字型 的方块
		g_arrShapeBlock[0][0] = 0, g_arrShapeBlock[0][1] = 1, g_arrShapeBlock[0][2] = 1, g_arrShapeBlock[0][3] = 0,
		g_arrShapeBlock[1][0] = 0, g_arrShapeBlock[1][1] = 1, g_arrShapeBlock[1][2] = 1, g_arrShapeBlock[1][3] = 0;
		// 记录将当前方块设置到背景二维数组的初始位置
		g_Row = 0;	// 行为0
		g_Col = 4;	// 列为4
		break;
	case 6:	// 设置I字型 的方块
		g_arrShapeBlock[0][0] = 1, g_arrShapeBlock[0][1] = 1, g_arrShapeBlock[0][2] = 1, g_arrShapeBlock[0][3] = 1,
		g_arrShapeBlock[1][0] = 0, g_arrShapeBlock[1][1] = 0, g_arrShapeBlock[1][2] = 0, g_arrShapeBlock[1][3] = 0;
		// 记录将当前方块设置到背景二维数组的初始位置
		g_Row = 0;	// 行为0
		g_Col = 4;	// 列为4
		break;
	default:
		break;
	}
	// 记录随机产生的方块
	g_BlockID = iIndex;
	return 0;
}
// 设置不同形状的方块颜色
void SetBlockColor(HDC hMemDC, HBRUSH hOldBrush, HBRUSH hNewBrush)
{
	// 给不同形状的方块上色
	switch (g_BlockID)
	{
	case 0:
		// 创建画刷
		hNewBrush = CreateSolidBrush(RGB(255, 255, 0));
		// 将新创建的画刷 关联到内存DC 将旧的画刷替换掉
		hOldBrush = SelectObject(hMemDC, hNewBrush);
		break;
	case 1:
		// 创建画刷
		hNewBrush = CreateSolidBrush(RGB(255, 0, 0));
		// 将新创建的画刷 关联到内存DC 将旧的画刷替换掉
		hOldBrush = SelectObject(hMemDC, hNewBrush);
		break;
	case 2:
		// 创建画刷
		hNewBrush = CreateSolidBrush(RGB(128, 255, 0));
		// 将新创建的画刷 关联到内存DC 将旧的画刷替换掉
		hOldBrush = SelectObject(hMemDC, hNewBrush);
		break;
	case 3:
		// 创建画刷
		hNewBrush = CreateSolidBrush(RGB(0, 255, 255));
		// 将新创建的画刷 关联到内存DC 将旧的画刷替换掉
		hOldBrush = SelectObject(hMemDC, hNewBrush);
		break;
	case 4:
		// 创建画刷
		hNewBrush = CreateSolidBrush(RGB(0, 128, 192));
		// 将新创建的画刷 关联到内存DC 将旧的画刷替换掉
		hOldBrush = SelectObject(hMemDC, hNewBrush);
		break;
	case 5:
		// 创建画刷
		hNewBrush = CreateSolidBrush(RGB(255, 128, 64));
		// 将新创建的画刷 关联到内存DC 将旧的画刷替换掉
		hOldBrush = SelectObject(hMemDC, hNewBrush);
		break;
	case 6:
		// 创建画刷
		hNewBrush = CreateSolidBrush(RGB(128, 64, 0));
		// 将新创建的画刷 关联到内存DC 将旧的画刷替换掉
		hOldBrush = SelectObject(hMemDC, hNewBrush);
		break;
	default:
		break;
	}
}
// 将设置方块形状并随机产生方块贴到游戏背景中
void CopyBlockToBack()
{
	// 将随机产生的方块设置到背景二维数组以0行3列为起始位置
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			// 将随机产生的方块的二维数组赋值给背景二维数组
			g_arrBackGround[i][j + 3] = g_arrShapeBlock[i][j];
		}
	}
}
// 初始化游戏数据 只调用一次 
void OnCreate()
{
	// 设置随机函数种子
	srand((unsigned int)time(NULL));
	// 设置方块形状并随机产生方块
	CreateRandomBlock();
	// 将设置方块形状并随机产生方块贴到游戏背景中
	CopyBlockToBack();
}
// 设置定时器 定时器响应的事件
void OnTimer(HWND hwnd)
{
	// 获取HDC
	HDC hdc = GetDC(hwnd);

	// 判断方块是否可以继续下降
	if (1 == CanBlockDown() && 1 == CanBlockDown2())
	{
		// 定时器开始时 方块下落
		BlockDown();
		// 方块下降 行数++
		g_Row++;
	}
	else
	{
		//// 关闭定时器
		//KillTimer(hwnd, DEF_TIMER1);
		// 将停下的方块对应的二维数组元素值置成2
		ChangeOneToTwo();
		// 判断是否结束有游戏
		if (0 == IsGameOver())
		{
			return;
		}
		DestroyLine();
		// 设置方块形状并随机产生方块
		CreateRandomBlock();
		// 将设置方块形状并随机产生方块贴到游戏背景中
		CopyBlockToBack();
	}

	// 显示方块 重新绘图
	OnPaint(hdc);

	// 释放HDC
	ReleaseDC(hwnd, hdc);
}
// 方块下落
void BlockDown()
{
	// 方块下落实现:将当前所在方块数组元素值赋给下一行,再将其置0
	for (int i = 19; i >= 0; i--)
	{
		for (int j = 0; j < 10; j++)
		{
			// 找背景二维数组元素为1
			if (1 == g_arrBackGround[i][j])
			{
				if (i + 1 < 20)
				{
					// 将当前二维数组赋给下一行
					g_arrBackGround[i + 1][j] = g_arrBackGround[i][j];
					// 置0
					g_arrBackGround[i][j] = 0;
				}
			}
		}
	}
}
// 方块左移
void BlockLeft()
{
	// 实现:找下落的方块 将对应下落的方块的左边数组元素赋值为1,再将当前下落的方块数组元素置0
	for (int i = 0; i < 20; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			// 找下落的方块
			if (1 == g_arrBackGround[i][j])
			{
				if (j - 1 >= 0)
				{
					// 将对应的下落方块的相邻左边元素赋1
					g_arrBackGround[i][j - 1] = 1;
					// 将下落的方块数组元素置0
					g_arrBackGround[i][j] = 0;	// 0 表示不画方块
				}
			}
		}
	}
}
// 方块右移
void BlockRight()
{
	// 将下落的方块相邻的右边元素赋值1 再将下落的方块元素置0
	for (int i = 0; i < 20; i++)
	{
		// 注意右移是要从右向左遍历
		for (int j = 9; j >= 0; j--)
		{
			// 找下落的方块
			if (1 == g_arrBackGround[i][j])
			{
				if (j + 1 < 10)
				{
					// 将相邻的右边的元素置1
					g_arrBackGround[i][j + 1] = 1;
					// 将找到的下落的方块元素置0
					g_arrBackGround[i][j] = 0;
				}
			}
		}
	}
}
// 方块变形
void BlockChange()
{
	// 实现:定义一个中间(3*3)数组,将下落的方块对应的二维数组复制给中间数组
	// 将中间数组进行90度顺时针旋转
	// 定义中间数组
	char temp[3][3] = { 0 };
	// 定义进行变形的二维数组的下标
	int index = 2;

	// 将下落的方块复制一份给中间数组
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			// 将下落的方块复制一份给中间数组
			temp[i][j] = g_arrBackGround[i + g_Row][j + g_Col];
		}
	}
	// 方块进行旋转
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			g_arrBackGround[i + g_Row][j + g_Col] = temp[index][i];
			index--;
		}
		index = 2;
	}
}

 

// 旋转I字型方块
void BlockLongChange()
{
	// 分两种情况:
	// 1.横转竖
	// 判断是不是横向的方块
	if (1 == g_arrBackGround[g_Row][g_Col - 1])
	{
		// 清零
		g_arrBackGround[g_Row][g_Col - 1] = 0;
		g_arrBackGround[g_Row][g_Col + 1] = 0;
		g_arrBackGround[g_Row][g_Col + 2] = 0;
		// 判断横向进行旋转的下一行是否有方块
		if (2 == g_arrBackGround[g_Row + 1][g_Col])
		{
			// 赋值
			g_arrBackGround[g_Row - 1][g_Col] = 1;
			g_arrBackGround[g_Row - 2][g_Col] = 1;
			g_arrBackGround[g_Row - 3][g_Col] = 1;
		}
		else if (2 == g_arrBackGround[g_Row + 2][g_Col])
		{// 判断横向进行旋转的下一行的下一行是否有方块
			// 赋值
			g_arrBackGround[g_Row - 1][g_Col] = 1;
			g_arrBackGround[g_Row - 2][g_Col] = 1;
			g_arrBackGround[g_Row + 1][g_Col] = 1;
		}
		else
		{
			// 赋值
			g_arrBackGround[g_Row - 1][g_Col] = 1;
			g_arrBackGround[g_Row + 1][g_Col] = 1;
			g_arrBackGround[g_Row + 2][g_Col] = 1;
		}
		// 处理第一行横转竖 被吃掉一个方块的情况
		if (0 == g_Row)
		{
			// 赋值
			g_arrBackGround[g_Row + 3][g_Col] = 1;
			g_arrBackGround[g_Row + 1][g_Col] = 1;
			g_arrBackGround[g_Row + 2][g_Col] = 1;
			// 旋转原点要记得改变
			g_Row += 1;
		}
		
		// 记得修改旋转原点
	}
	else
	{
		// 2.竖转横
		// 判断是不是横向的方块
		//// 清零
		g_arrBackGround[g_Row - 1][g_Col] = 0;
		g_arrBackGround[g_Row + 1][g_Col] = 0;
		g_arrBackGround[g_Row + 2][g_Col] = 0;
		if (2 == g_arrBackGround[g_Row][g_Col + 1] || 9 == g_Col)
		{
			//与下落的方块右边相邻有方块
			// 赋值
			g_arrBackGround[g_Row][g_Col - 1] = 1;
			g_arrBackGround[g_Row][g_Col - 2] = 1;
			g_arrBackGround[g_Row][g_Col - 3] = 1;
			// 重置旋转原点
			g_Col = g_Col - 2;
		}
		else if (2 == g_arrBackGround[g_Row][g_Col + 2] || 8 == g_Col)
		{
			// 下落的方块相邻的右边的右边有方块
			// 赋值
			g_arrBackGround[g_Row][g_Col + 1] = 1;
			g_arrBackGround[g_Row][g_Col - 1] = 1;
			g_arrBackGround[g_Row][g_Col - 2] = 1;
			// 重置旋转原点
			g_Col = g_Col - 1;
		}
		else if (2 == g_arrBackGround[g_Row][g_Col - 1] || g_Col == 0)
		{
			// 赋值
			g_arrBackGround[g_Row][g_Col + 1] = 1;
			g_arrBackGround[g_Row][g_Col + 2] = 1;
			g_arrBackGround[g_Row][g_Col + 3] = 1;
			// 注意要将旋转原点进行修改
			g_Col += 1;
		}
		else
		{
			// 赋值
			g_arrBackGround[g_Row][g_Col - 1] = 1;
			g_arrBackGround[g_Row][g_Col + 1] = 1;
			g_arrBackGround[g_Row][g_Col + 2] = 1;
		}		
	}
}
// 判断方块是否可以继续下落
int CanBlockDown()
{
	// 当方块到最后一行则停止方块下落 返回0表示不能继续下降
	for (int j = 0; j < 10; j++)
	{
		// 找方块 方块到底行
		if (1 == g_arrBackGround[19][j])
		{
			return 0;	// 表示不能再继续下降
		}
	}
	return 1;	// 可以继续下降
}
// 判断方块是否可以继续下降 (当已有停下的方块的情况下)
int CanBlockDown2()
{
	// 当下降的方块碰到已经停下的方块时停止继续下降
	for (int i = 0; i < 20; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			// 找下降的方块 并且下降的方块下一行已有方块(方块二维数组元素为2)
			if (1 == g_arrBackGround[i][j] && 2 == g_arrBackGround[i + 1][j])
			{
				return 0;	// 表示不能进行下降了
			}
		}
	}
	return 1;	// 表示可以继续下降
}
// 判断是否可以继续左移
int CanBlockLeft()
{
	// 当第一列数组元素为1 则返回0 表示不能继续向左移动
	for (int i = 0; i < 20; i++)
	{
		// 判断最左边元素是否为1
		if (1 == g_arrBackGround[i][0])
		{
			return 0;
		}
	}
	return 1; // 表示可以继续移动
}
// 判断方块是否可以继续左移 (当左边有方块的情况下)
int CanBlockLeft2()
{
	// 当下落的方块左边已经有方块了,则不能继续左移
	for (int i = 0; i < 20; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			if (j - 1 >= 0)
			{
				// 找下落的方块 并且 相邻的左边有方块了
				if (1 == g_arrBackGround[i][j] && 2 == g_arrBackGround[i][j - 1])
				{
					return 0;
				}
			}
		}
	}
	return 1;
}
// 判断方块是否可以继续右移
int CanBlockRight()
{
	// 当最后一列方块元素为1时则不能继续右移
	for (int i = 0; i < 20; i++)
	{
		// 找最后一列元素为1
		if (1 == g_arrBackGround[i][9])
		{
			return 0;
		}
	}
	return 1;
}
// 判断方块是否可以继续右移 (当右边方块的情况下)
int CanBlockRight2()
{
	// 当右边已经有方块的时候,不能继续右移
	for (int i = 0; i < 20; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			// 找下落的方块 相邻右边右方块
			if (1 == g_arrBackGround[i][j] && 2 == g_arrBackGround[i][j + 1])
			{
				return 0;
			}
		}
	}
	return 1;
}
// 判断方块是否可以继续旋转
int CanBlockChange()
{
	// 判断下落的方块旋转范围3*3内是否已经有方块了
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			// 判断是否有方块
			if (2 == g_arrBackGround[i + g_Row][j + g_Col])
			{
				return 0;	// 表示不能继续旋转
			}
		}
	}
	// 解决方块旋转超出游戏边界问题
	if (g_Col < 0)
	{
		// 将其置0
		g_Col = 0;
	}
	else if (2 + g_Col > 9)
	{
		g_Col = 7;
	}
	return 1;
}
// 判断长条形状的方块是否可以继续旋转 (I字型方块)
int CanBlockLongChange()
{
	// 竖转横 只有3个方块间隔,则不能进行旋转
	// 定义两个变量,判断间隔是否比3个方块大
	int left = 0;
	int right = 0;
	// 判断左边
	for (left = 1; left < 4; left++)
	{
		if (2 == g_arrBackGround[g_Row][g_Col - left] || g_Col - left < 0)
		{
			break;
		}
	}
	// 判断右边
	for (right = 1; right < 4; right++)
	{
		if (2 == g_arrBackGround[g_Row][g_Col + right] || g_Col + right > 9)
		{
			break;
		}
	}

	// 判断左右变量之和是否比3小
	if ((left - 1 + right - 1) < 3)
	{
		return 0;
	}

	return 1;
}

 

// 将停下的方块对应的二维数组元素值置成2
void ChangeOneToTwo()
{
	// 将停下的方块二维数组元素值置成2
	// 从下往上遍历
	for (int i = 19; i >= 0; i--)
	{
		for (int j = 0; j < 10; j++)
		{
			// 找方块对应的数组元素值为1 将方块对应的二维数组元素值置2
			if (1 == g_arrBackGround[i][j])
			{
				// 将对应的二维数组元素值赋2
				g_arrBackGround[i][j] = 2;
			}
		}
	}
}
// 按下回车键开启定时器
void OnReturn(HWND hwnd)
{
	// 参数1:窗口句柄,参数2:定时器ID,参数3:定时器时间间隔(毫秒),
	// 参数4:回调函数 填NULL 会产生一个定时器消息 WM_TIMER
	SetTimer(hwnd, DEF_TIMER1, 500, NULL);
}
// 按下向下键 加速
void OnDwon(HWND hwnd)
{
	OnTimer(hwnd);
}
// 方块左移
void OnLeft(HWND hwnd)
{
	// 获取HDC
	HDC hdc = GetDC(hwnd);
	// 判断方块是否可以继续左移
	if (1 == CanBlockLeft() && 1 == CanBlockLeft2())
	{
		// 方块左移
		BlockLeft();
		// 方块左移列数--
		g_Col--;
	}
	
	// 显示方块 重新绘图
	OnPaint(hdc);
	// 释放HDC
	ReleaseDC(hwnd, hdc);
}
// 方块右移
void OnRight(HWND hwnd)
{
	// 获取HDC
	HDC hdc = GetDC(hwnd);
	// 判断是否可以继续右移
	if (1 == CanBlockRight() && 1 == CanBlockRight2())
	{
		// 方块右移
		BlockRight();
		// 方块右移列数++
		g_Col++;
	}
	// 重绘方块
	OnPaint(hdc);
	// 释放HDC
	ReleaseDC(hwnd, hdc);
}
// 方块变形
void OnChangeShape(HWND hwnd)
{
	// 方块变形
	// 分类处理不同形状的方块
	switch (g_BlockID)
	{
	case 0:
	case 1:
	case 2:
	case 3:
	case 4:	// Z T L
		if (1 == CanBlockChange())
		{
			BlockChange();
		}
		break;
	case 5:	//田
		break;
	case 6:	// I
		// 判断是否可以继续旋转
		if (1 == CanBlockLongChange())
		{
			BlockLongChange();
		}	
		break;
	}
}
// 行满消行处理
void DestroyLine()
{
	// 定义一个变量计算当前行的数是否==20
	int inum = 0;

	// 实现:将上一行一次赋给下一行
	for (int i = 19; i >= 0; i--)
	{
		for (int j = 0; j < 10; j++)
		{
			inum += g_arrBackGround[i][j];	
		}
		//判断inum是否==20
		if (20 == inum)
		{
			for (int ii = i; ii >= 0; ii--)
			{
				for (int jj = 0; jj < 10; jj++)
				{
					g_arrBackGround[ii][jj] = g_arrBackGround[ii - 1][jj];
				}
			}
			// 将循环空变量i 改成20
			i = 20;
			// 分数+10
			g_score += 10;
		}
		// 将计算行数变量置0
		inum = 0;
	}
}
// 显示得分
void ShowScore(HDC HMemDC)
{
	// 声明就的画刷(默认的画刷)
	HBRUSH hOldBrush;
	// 创建新的画刷
	HBRUSH hNewBrush = CreateSolidBrush(RGB(237, 234, 167));
	// 将画刷与可操作客户区关联起来
	hOldBrush = SelectObject(HMemDC, hNewBrush);
	// 画矩形
	Rectangle(HMemDC, 300, 0, 500, 600);
	// 定义一个用于记录整形转字符型的字符数组
	char str[10] = { 0 };
	// 设置文字的大小
	// 声明系统字体逻辑结构体对象
	LOGFONT logfont;
	// 清零操作
	ZeroMemory(&logfont, sizeof(LOGFONT));	// 相当于是 memset
	// 给结构体成员赋值
	logfont.lfCharSet = GB2312_CHARSET;
	logfont.lfHeight = 25;	// 设置文字大小
	// 设置字体样式为 黑体
	strcpy_s(logfont.lfFaceName,sizeof("黑体"), "黑体");
	// 创建逻辑字体
	HFONT hfont =  CreateFontIndirect(&logfont);
	// 将创建出来的逻辑字体关联起来
	SelectObject(HMemDC, hfont);
	// 将整形的分数转成字符型
	_itoa_s(g_score, str, sizeof(str), 10);	// 参数3:将变量的进制

	TextOut(HMemDC, 310, 240, "得分", strlen("得分"));
	TextOut(HMemDC, 400, 240, str, strlen(str));
	// 显示游戏玩法 操作说明
	TextOut(HMemDC, 310, 40, "Enter:开始游戏", strlen("Enter:开始游戏"));
	TextOut(HMemDC, 310, 80, "↑:旋转方块", strlen("↑:旋转方块"));
	TextOut(HMemDC, 310, 120, "↓:加速下降", strlen("↓:加速下降"));
	TextOut(HMemDC, 310, 160, "→:右移", strlen("→:右移"));
	TextOut(HMemDC, 310, 200, "←:左移", strlen("←:左移"));

	// 用完画刷之后要将创建的画刷释放掉 将旧的画刷重新拿回来
	hNewBrush = SelectObject(HMemDC, hOldBrush);
	// 释放
	DeleteObject(hNewBrush);
	// 逻辑字体用完时候记得要释放
	DeleteObject(hfont);
}
// 判断是否结束游戏
int IsGameOver()
{
	// 当第2行方块二维数组元素值为2 则结束有结束游戏
	for (int i = 0; i < 10; i++)
	{
		if (2 == g_arrBackGround[1][i])
		{
			// 弹出一个结束游戏对话框 提示
			MessageBox(NULL, "GameOver!!!", "提示", MB_OK);
			return 0;	// 代表结束游戏
		}
	}
	return 1;	// 代表没有结束游戏
}
// 再显示游戏信息处 显示随机出现的方块
void ShowNextBlock(HDC HMemDC, HBRUSH hOldBrush, HBRUSH hNewBrush)
{
	// 设置不同形状的方块颜色
	SetBlockColor(HMemDC, hOldBrush, hNewBrush);
	// 定义一个用于装随机产生方块的2*4的二维数组
	char arr_Block[2][4] = { 0 };
	// 获取随机产生的方块
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			arr_Block[i][j] = g_arrShapeBlock[i][j];
		}
	}
	// 画方块
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			// 判断当数组元素为1时 开始画方块
			if (1 == arr_Block[i][j])
			{
				Rectangle(HMemDC, j * 30 + 330, i * 30 + 350, j * 30 + 30 + 330, i * 30 + 30 + 350);
			}
		}
	}
	// 用完画刷之后要将创建的画刷释放掉 将旧的画刷重新拿回来
	hNewBrush = SelectObject(HMemDC, hOldBrush);
	// 释放
	DeleteObject(hNewBrush);
}

game.c 文件

/*
	项目:俄罗斯方块
	工程:Win32桌面应用程序
	已将当前项目改成多字符集

	实现俄罗斯方块:
		1.创建一个窗口

*/

#include "elsfk.h"

// 声明回调函数
LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);

// WINAPI:调用约定
// 窗口的主函数时 WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE PrehInstance, LPSTR lpCmdLine, int nCmdShow)
{
	// 1.创建窗口结构体
	WNDCLASSEX ws;
	// 给窗口结构体赋值
	ws.cbClsExtra = 0;	// 窗口类额外空间
	ws.cbSize = sizeof(ws);	// 窗口结构体大小
	ws.lpfnWndProc = WindowProc;	// 回调函数
	ws.hInstance = hInstance;	// 当前窗口实例句柄
	ws.hCursor = NULL;			// 光标
	ws.lpszClassName = "Tetris";	// 窗口类名
	ws.cbWndExtra = 0;	// 窗口额外空间
	ws.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));	// 窗口背景颜色
	ws.hIcon = NULL;		//  窗口左上角图标
	ws.hIconSm = NULL;		// 窗口图标
	ws.lpszMenuName = NULL;	// 窗口菜单名
	ws.style = CS_HREDRAW | CS_VREDRAW;	// 移动窗口时进行垂直刷新和水平刷新

	// 2.注册窗口结构体
	RegisterClassEx(&ws);

	// 3.创建窗口
	HWND hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, "Tetris", "俄罗斯方块游戏", WS_OVERLAPPEDWINDOW
		, 300, 20, 520, 650, NULL, NULL, hInstance, NULL);
	// 判断窗口是否成功创建出来
	if (NULL == hwnd)
	{
		return -1;
	}
	// 4.显示窗口
	ShowWindow(hwnd, nCmdShow);

	// 5.更新窗口
	UpdateWindow(hwnd);

	MSG msg;
	// 6.消息循环
	while (GetMessage(&msg, NULL, 0, 0))
	{
		// 翻译消息 将虚拟键的消息转换成字符消息
		TranslateMessage(&msg);
		// 分发消息
		DispatchMessage(&msg);
	}

	return 0;
}

// 实现回调函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	// 通过参数2 uMsg 分类处理事件
	switch (uMsg)
	{
	case WM_CREATE:	// 值初始化一次
		// 初始化游戏数据
		OnCreate();
	
		break;
	case WM_TIMER:	// 定时器函数SetTimer 产生定时器消息
		// 设置定时器
		OnTimer(hwnd);
		break;
	case WM_PAINT:	// 当窗口发生变化之后就会执行WM_PAINT
	{
		// 声明BeginPaint函数中参数2所需的结构体
		PAINTSTRUCT pt;
		// 定义Rectangle函数的参数1所需的HDC 可操作的用户区
		HDC hdc = BeginPaint(hwnd, &pt);
		
		// 封装一个绘图函数
		OnPaint(hdc);

		// 结束绘图
		EndPaint(hwnd, &pt);
	}
	break;
	case WM_KEYDOWN:	// 处理用户按下键盘按键产的事件
	{
		// 通过回调函数 参数3 wParam 区分键盘键产生的事件消息
		// 分类处理按下键盘产生的消息 事件
		switch (wParam)
		{
		case VK_RETURN:	// 按下回车键
			// 按下回车键开启定时器
			OnReturn(hwnd);
			break;
		case VK_DOWN:	// 按下向下键 加速
			OnDwon(hwnd);
			break;
		case VK_LEFT:	// 方块左移
			OnLeft(hwnd);
			break;
		case VK_RIGHT:	// 方块右移
			OnRight(hwnd);
			break;
		case VK_UP:	// 方块变形
			OnChangeShape(hwnd);
			break;
		default:
			break;
		}
		break;
	}

	case WM_DESTROY:	//销毁窗口
		// 关闭定时器
		KillTimer(hwnd, DEF_TIMER1);
		PostQuitMessage(0);	// 将退出消息直接发送给GetMessage
		break;
	}

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

程序运行结果:

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值