俄罗斯方块实现步骤:
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);
}
程序运行结果:

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

被折叠的 条评论
为什么被折叠?



