俄罗斯方块-C语言-详注版
俄罗斯方块-C语言-详注版
概述
本文详述了C语言版俄罗斯方块游戏的原理以及实现方法,对游戏代码进行了详细的分析和注释,通过本文能够让大家对WIN32编程框架有一个大致了解,对C语言运用有一定提高,同时也能给大家提供一个C语言小游戏编程的思路,也能完全够通过自己的实力去编写一个属于自己的游戏.
游戏体验
代码框架
俄罗斯方块游戏代码框架如下
在 main.c 中,创建应用窗口,并初始化一些系统资源,然后初始化gdi,初始化俄罗斯方块游戏.
在 gdi.c 中,对系统的HDC及绘图接口进行了一次封装,使用WIN32系统提供的绘图接口来实现我们自己的图形绘制API.这里使用到了双缓冲技术,下文会简单介绍.
在 Tetris.c 中,实现了俄罗斯方块的游戏逻辑,包括贴图系统,地图系统,方块生成,方块移动,方块变形,满行检测,等等.
下面将对代码进行详细的分析.
代码主函数
在 main.c 中,创建应用窗口,并初始化一些系统资源,然后初始化gdi,初始化俄罗斯方块游戏.
main.c
// GameTetris.c: 定义应用程序的入口点。
//
#include "Main.h"
/*......*/
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
HACCEL hAccelTable;
MSG msg;
DWORD dwTetrisRunTId;
HANDLE hTetrisRunT;
// 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_GAMETETRIS, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
DebugOpen();
gdi_init(hWnd);//显示初始化
gdi_clear(GDI_RGB_BACKGROUND);//显示初始化
GameCtrlInit();
//GamePlaySound(IDR_WAVE_BACKG);
hTetrisRunT = CreateThread(NULL, 0, TetrisRun, NULL, 0, &dwTetrisRunTId);
//创建线程失败
if (hTetrisRunT == NULL)
{
ExitProcess(0);//主程序退出
}
//快捷键
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_GAMETETRIS));
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
//TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
HBRUSH hWindowBrush;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW; //如果大小改变了重绘窗口
wcex.lpfnWndProc = WndProc; //窗口消息处理函数
wcex.cbClsExtra = 0; //无附加窗口类内存
wcex.cbWndExtra = 0; //无附加窗口内存
wcex.hInstance = hInstance; //应用程序实例
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_GAMETETRIS)); //图标
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); //鼠标指针
#if 0
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //背景画刷(默认白色)
#else
hWindowBrush = CreateSolidBrush((COLORREF)GDI_RGB_BACKGROUND); //背景画刷(自定义)
wcex.hbrBackground = hWindowBrush;
#endif
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_GAMETETRIS); //菜单资源
wcex.lpszClassName = szWindowClass; //窗口类名
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); //小图标
return RegisterClassExW(&wcex); //注册窗口类
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
/*......*/
/*HWND*/hWnd = CreateWindowW(
szWindowClass, //窗口类名
szTitle, //窗口名
/*WS_OVERLAPPEDWINDOW*/dwStyle, //窗口样式
CW_USEDEFAULT, //水平位置,默认
CW_USEDEFAULT, //垂直位置,默认
wndRect.right - wndRect.left, //宽
wndRect.bottom - wndRect.top, //高
NULL, //无父窗口
(HMENU)/*NULL*/LoadMenu(hInst, MAKEINTRESOURCE(IDC_GAMETETRIS)), //菜单
hInstance, //应用程序实例
NULL); //无窗口创建数据
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
//case WM_CREATE:
// break;
case WM_COMMAND:
{
wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
/*......*/
case IDM_ADDSPEED://加速
tetris_add_speed();
break;
case IDM_SUBSPEED://减速
tetris_sub_speed();
break;
case IDM_TETRISFIRE://变形
tetris_shape_deform();
break;
/*......*/
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
gdi_update();
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
gdi_dinit();
DebugClose();
PostQuitMessage(0);
break;
case WM_KEYDOWN:
switch (wParam)
{
/*......*/
case VK_DOWN://下
tetris_shape_move(DR_DOWN);
break;
case VK_LEFT://左
tetris_shape_move(DR_LEFT);
break;
case VK_RIGHT://右
tetris_shape_move(DR_RIGHT);
break;
case VK_RETURN://回车键变形
tetris_shape_deform();
break;
/*......*/
default:
break;
}
break;
//case WM_CHAR:
// break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
/*......*/
}
// “帮助”框的消息处理程序。
INT_PTR CALLBACK Readme(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
/*......*/
}
//游戏运行线程
DWORD WINAPI TetrisRun(LPVOID lpProgram)
{
tetris_game_init();
while (TRUE)
{
if (GAME_CTRL.run)//暂停游戏
{
if (tetris_game_run(GAME_CTRL.debug) != LF_LIVE)
{
//DEBUG_LOG("ERR");
MessageBox(/*NULL*/hWnd, TEXT("继续战斗吧!"), TEXT("你输了!"), MB_OK);
GameCtrlInit();
tetris_game_init();
}
Sleep(1100 - (tetris_get_speed() * 100));
}
}
return 0;
}
GDI绘图
在 gdi.c 中,对系统的HDC及绘图接口进行了一次封装,使用WIN32系统提供的绘图接口来实现我们自己的图形绘制API.这里使用到了双缓冲技术.
双缓冲技术
俄罗斯方块的每一次运行,坦克和炮弹的每一次移动,我们都要不断的清除屏幕上原来的内容,然后重新绘制新的内容,假如我们每一次更改显示内容都直接调用WIN32的api实时的刷新到屏幕上,一旦游戏运行速度很快的时候,就会造成屏幕上的内容一直在闪烁,非常影响游戏体验.
双缓冲技术主要是为了解决俄罗斯方块实时刷新造成的屏幕闪烁问题.其原理就是,加入地图上共有10辆坦在战斗,每辆坦克都在移动,每移动一步我们都需要重新计算并显示10辆坦克的位置,但是我们不必在计算每一辆坦克的时候都把他刷新到屏幕上,而是先把这辆坦克绘制到内存上,等到所有的坦克都计算并完成移动之后,再同一把内存上的内容刷新到屏幕上,这样做就大大减少了刷新屏幕的次数,也就可以避免实时刷新造成的屏幕闪烁问题.
更详细的介绍,请参考下面这篇文章:
gdi.c
#include "Gdi.h"
HPEN hGdiPen = NULL; //画笔
HBRUSH hGdiBrush = NULL; //画刷
HDC mGdiHdc; //内存设备(双缓冲技术)
HDC hGdiHdc; //硬件设备
HWND hGdiWnd; //窗口
RECT hGdiWndRect; //窗口客户区大小
HBITMAP mGdiBmp;
HBITMAP mGdiBmpOld;
#define maxX SCREEN_X
#define maxY SCREEN_Y
static void _gdi_clr_pencol(HPEN _hGdiPen)
{
DeleteObject(_hGdiPen);//释放资源
SelectObject(mGdiHdc, hGdiPen);//恢复初始画刷
}
static HPEN _gdi_set_pencol(int32 color)
{
HPEN _hGdiPen;
COLORREF color_t = (COLORREF)color;
_hGdiPen = CreatePen(PS_SOLID, 1, color_t);//画笔
hGdiPen = SelectObject(mGdiHdc, _hGdiPen);//为缓存DC选择画笔
return _hGdiPen;
}
static void _gdi_clr_brushcol(HBRUSH _hGdiBrush)
{
DeleteObject(_hGdiBrush);//释放资源
SelectObject(mGdiHdc, hGdiBrush);//恢复初始画刷
}
static HBRUSH _gdi_set_brushcol(int32 color)
{
HBRUSH _hGdiBrush;
COLORREF color_t = (COLORREF)color;
_hGdiBrush = CreateSolidBrush(color_t);//画刷
hGdiBrush = SelectObject(mGdiHdc, _hGdiBrush);//为缓存DC选择画刷
return _hGdiBrush;
}
/*
* gdi_clear:
* Clear the display to the given colour.
*******************************************************************************
*/
void gdi_clear(int32 colour)
{
gdi_rectangle(0, 0, maxX, maxY, colour, TRUE);
}
/*
* gdi_set_point:
* Plot a pixel.
*******************************************************************************
*/
void gdi_set_point(int32 x, int32 y, int32 colour)
{
x = ((x < 0) ? 0 : ((x > (maxX - 1)) ? (maxX - 1) : x));
y = ((y < 0) ? 0 : ((y > (maxY - 1)) ? (maxY - 1) : y));
HPEN hPen = _gdi_set_pencol(colour);
SetPixel(mGdiHdc, x, y, colour);
_gdi_clr_pencol(hPen);
}
/*
* gdi_get_point:
* Plot a pixel.
*******************************************************************************
*/
int32 gdi_get_point(int32 x, int32 y)
{
x = ((x < 0) ? 0 : ((x > (maxX - 1)) ? (maxX - 1) : x));
y = ((y < 0) ? 0 : ((y > (maxY - 1)) ? (maxY - 1) : y));
COLORREF col = GetPixel(mGdiHdc, x, y);
return (int32)col;
}
......
/*
* gdi_triangle:
* A triangle is a spoilt days fishing
*******************************************************************************
*/
void gdi_triangle(int32 x1, int32 y1, int32 x2, int32 y2, int32 colour, int32 filled)
{
HPEN _hPen;
HBRUSH _hBrush;
POINT triangle[3] = { 0 };
int32 halfx = 0;
halfx = ((x2 - x1 + 1) / 2);
triangle[0].x = x1 + halfx;
triangle[0].y = y1;
triangle[1].x = x1;
triangle[1].y = y2;
triangle[2].x = x2;
triangle[2].y = y2;
if (filled)
{
_hPen = _gdi_set_pencol(colour);
_hBrush = _gdi_set_brushcol(colour);
Polygon(mGdiHdc, triangle, 3);
_gdi_clr_pencol(_hPen);
_gdi_clr_brushcol(_hBrush);
}
else
{
_hPen = _gdi_set_pencol(colour);
Polygon(mGdiHdc, triangle, 3);
_gdi_clr_pencol(_hPen);
}
}
......
/*
* gdi_init:
* Initialise the display and GPIO.
*******************************************************************************
*/
int32 gdi_init(HWND hWnd)
{
int32 hGdiWndWidth = 0;//窗口客户区宽度
int32 hGdiWndHeight = 0;//窗口客户区高度
hGdiWnd = hWnd;
hGdiHdc = GetDC(hGdiWnd); //获取硬件设备
mGdiHdc = CreateCompatibleDC(hGdiHdc); //创建软件设备,双缓冲技术
GetClientRect(hGdiWnd, &hGdiWndRect);
hGdiWndWidth = hGdiWndRect.right - hGdiWndRect.left;
hGdiWndHeight = hGdiWndRect.bottom - hGdiWndRect.top;
//双缓冲技术核心:先创建一个软件绘图设备HDC,为这个软件HDC选择一个内存画布,
//所有的绘图都通过这个软件HDC来完成,都被绘制到了这个内存画布之上
//当所有的绘图工作都完成之后,就通过BitBlt把内存画布上的内容拷贝到硬件绘图设备HDC上,
//这样就完成了显示(gdi_update)
mGdiBmp = CreateCompatibleBitmap(hGdiHdc, hGdiWndWidth, hGdiWndHeight);//创建BMP画布
mGdiBmpOld = SelectObject(mGdiHdc, mGdiBmp);//为软件HDC设备选择BMP画布
return OK;
}
int32 gdi_dinit(void)
{
DeleteObject(mGdiBmp);//删除BMP画布
DeleteObject(mGdiHdc);//删除软件设备
DeleteDC(hGdiHdc);//删除硬件设备
return OK;
}
int32 gdi_update(void)
{
int32 hGdiWndWidth = 0;//窗口客户区宽度
int32 hGdiWndHeight = 0;//窗口客户区高度
hGdiWndWidth = hGdiWndRect.right - hGdiWndRect.left;
hGdiWndHeight = hGdiWndRect.bottom - hGdiWndRect.top;
//把软件设备上的内容拷贝到硬件设备上
BitBlt(hGdiHdc, 0, 0, hGdiWndWidth, hGdiWndHeight, mGdiHdc, 0, 0, SRCCOPY);
return OK;
}
俄罗斯方块
方块形状库
如上图所示,每个坦克由6个小方块组成,把这6个小方块按顺序标号.每个坦克有上下左右四个方向.为了简化程序操作,把这四个方向的坦克放在一个(二维)数组 TANK_SHAPE_BOX 中,即TANK_SHAPE_BOX[4][6],4表示共有四种形状的坦克(四个方向),6表示每种坦克由6个小方块. 数组的每个元素保存的是这6个小方块的相对坐标.坦克的每个方块的实际坐标可以通过坦克左上角的坐标与这6个相对坐标计算得到.
如上图所示,俄罗斯方块共有七种基本形状,每个基本形状又可以有四种变换,所以共有28种形状(包括变换后重复的).每种形状由4个小方块组成.这里假设把每种基本形状放在一个4x4的矩阵里,用一个坐标去记录每个小方块相对于矩阵左上角的位置.
为了简化代码,这里把这28种形状的相对坐标放在一个二维数组 GAME_SHAPE_BOX 中.
同时用另一个数组 GAME_SHAPE_SIZE 记录每种形状的最大长度和宽度.
game_point_t GAME_SHAPE_BOX[MAX_SHAPE_CNT][SHAPE_PNT_CNT] =
{
//I///
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 2, 0, TRUE }, { 3, 0, TRUE } },//I0
{ { 0, 0, TRUE }, { 0, 1, TRUE }, { 0, 2, TRUE }, { 0, 3, TRUE } },//I1
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 2, 0, TRUE }, { 3, 0, TRUE } },//I2
{ { 0, 0, TRUE }, { 0, 1, TRUE }, { 0, 2, TRUE }, { 0, 3, TRUE } }, //I3
///J///
{ { 0, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE }, { 2, 1, TRUE } },//J0
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 0, 1, TRUE }, { 0, 2, TRUE } },//J1
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 2, 0, TRUE }, { 2, 1, TRUE } },//J2
{ { 1, 0, TRUE }, { 1, 1, TRUE }, { 0, 2, TRUE }, { 1, 2, TRUE } }, //J3
///L///
{ { 2, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE }, { 2, 1, TRUE } },//L0
{ { 0, 0, TRUE }, { 0, 1, TRUE }, { 0, 2, TRUE }, { 1, 2, TRUE } },//L1
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 2, 0, TRUE }, { 0, 1, TRUE } },//L2
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 1, 1, TRUE }, { 1, 2, TRUE } }, //L3
///O///
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE } },//O0
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE } },//O1
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE } },//O2
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE } }, //O3
///S
{ { 1, 0, TRUE }, { 2, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE } },//S0
{ { 0, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE }, { 1, 2, TRUE } },//S1
{ { 1, 0, TRUE }, { 2, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE } },//S2
{ { 0, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE }, { 1, 2, TRUE } }, //S3
///T
{ { 1, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE }, { 2, 1, TRUE } },//T0
{ { 0, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE }, { 0, 2, TRUE } },//T1
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 2, 0, TRUE }, { 1, 1, TRUE } },//T2
{ { 1, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE }, { 1, 2, TRUE } }, //T3
///Z
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 1, 1, TRUE }, { 2, 1, TRUE } },//Z0
{ { 1, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE }, { 0, 2, TRUE } },//Z1
{ { 0, 0, TRUE }, { 1, 0, TRUE }, { 1, 1, TRUE }, { 2, 1, TRUE } },//Z2
{ { 1, 0, TRUE }, { 0, 1, TRUE }, { 1, 1, TRUE }, { 0, 2, TRUE } } //Z3
};
int32 GAME_SHAPE_SIZE[MAX_SHAPE_CNT][2] =
{
{4, 1}, {1, 4}, {4, 1}, {1, 4}, //I
{3, 2}, {2, 3}, {3, 2}, {2, 3}, //J
{3, 2}, {2, 3}, {3, 2}, {2, 3}, //L
{2, 2}, {2, 2}, {2, 2}, {2, 2}, //O
{3, 2}, {2, 3}, {3, 2}, {2, 3}, //S
{3, 2}, {2, 3}, {3, 2}, {2, 3}, //T
{3, 2}, {2, 3}, {3, 2}, {2, 3} //Z
};
游戏地图系统
代码中使用了一个二维数组 TETRIS_GAME_MAP 来当作游戏地图,二维数组的大小就是屏幕的长度和宽度,游戏中的每一个方块(小格子)的信息都保存在地图中.游戏方块(小格子)绘制系统会把每一个方块(小格子)的信息写入到地图中.游戏图形绘制系统会把地图中的每一个方块(小格子)绘制到屏幕上.
//调用GDI绘制一个形状
int32 map_draw_point(int32 x, int32 y, int32 col, int32 pr)
{
#ifdef GAME_MIRROR_XY
int32 yt = 0;
#endif
//x = ((x < 0) ? 0 : ((x >= GAME_MAX_X) ? (GAME_MAX_X - 1) : x));
//y = ((y < 0) ? 0 : ((y >= GAME_MAX_Y) ? (GAME_MAX_Y - 1) : y));
if ((x < 0) || (x >= GAME_MAX_X) ||
(y < 0) || (y >= GAME_MAX_Y))
{
return RTN_ERR;
}
#ifdef GAME_MIRROR_XY
yt = y;
y = x * GAME_POINT_L;
x = yt * GAME_POINT_H;
x = ((GAME_MAX_Y - 1) - x);
//y = ((GAME_MAX_X - 1) - y);
#else
x = x * GAME_POINT_L;
y = y * GAME_POINT_H;
#endif
pr = ((pr >= PR_MAX) ? PR_NULL : ((pr <= PR_NULL) ? PR_NULL : pr));
if (col)
{
col = TETRIS_PR_COLOUR[pr];
}
else
{
col = TETRIS_PR_COLOUR[PR_NULL];
}
if (GAME_POINT_SIZE != 1) //画一个矩形代表缩放过后的点
{
gdi_rectangle(x, y, x + GAME_POINT_L - 1, y + GAME_POINT_H - 1, col, 1);
}
else//为了加快速度,宽度为1不缩放,直接画点
{
gdi_set_point(x, y, col);
}
return RTN_OK;
}
/*****************************************************************************
函 数 名 : game_draw_point
功能描述 : 往地图上添加一个形状
输入参数 : game_point_t* s_point
输出参数 : 无
返 回 值 : 成功返回0,失败返回1
函数说明 :
*****************************************************************************/
int32 game_draw_point(const game_point_t* const s_point)
{
//pr属性用于控制显示的形状
int32 pr = PR_NULL;
int32 col = FALSE;
int32 x = 0, y = 0;
if (s_point == NULL)
return RTN_ERR;
x = s_point->x;
y = s_point->y;
//x = ((x < 0) ? 0 : ((x >= GAME_MAX_X) ? (GAME_MAX_X - 1) : x));
//y = ((y < 0) ? 0 : ((y >= GAME_MAX_Y) ? (GAME_MAX_Y - 1) : y));
if ((x < 0) || (x >= GAME_MAX_X) || (y < 0) || (y >= GAME_MAX_Y))
{
return RTN_ERR;
}
if (s_point->col != FALSE)
{
col = TRUE;
pr = s_point->col;
}
else
{
col = FALSE;
pr = PR_NULL;
}
pr = ((pr >= PR_MAX) ? PR_NULL : ((pr <= PR_NULL) ? PR_NULL : pr));
TETRIS_GAME_MAP[x][y].col = col;
TETRIS_GAME_MAP[x][y].pr = pr;
return RTN_OK;
}
//从地图上获取一个形状
/*****************************************************************************
函 数 名 : game_get_point
功能描述 : 获取地图上某个点的颜色
输入参数 : game_point_t* s_point
输出参数 : 无
返 回 值 : 返回指定的点的颜色(1或0)
函数说明 :
*****************************************************************************/
int32 game_get_point(const game_point_t* const s_point)
{
int32 x = 0, y = 0;
int32 col = 0, pr = 0;
int32 ret = PR_NULL;
if (s_point == RTN_NULL)
{
return RTN_ERR;
}
x = s_point->x;
y = s_point->y;
//x = ((x < 0) ? 0 : ((x >= GAME_MAX_X) ? (GAME_MAX_X - 1) : x));
//y = ((y < 0) ? 0 : ((y >= GAME_MAX_Y) ? (GAME_MAX_Y - 1) : y));
if ((x < 0) || (x >= GAME_MAX_X) ||
(y < 0) || (y >= GAME_MAX_Y))
{
return FALSE;
}
return TETRIS_GAME_MAP[x][y].col;
}
int32 game_get_pntpr(const game_point_t* const s_point)
{
int32 x = 0, y = 0;
int32 col = 0, pr = 0;
int32 ret = PR_NULL;
if (s_point == RTN_NULL)
{
return RTN_ERR;
}
x = s_point->x;
y = s_point->y;
//x = ((x < 0) ? 0 : ((x >= GAME_MAX_X) ? (GAME_MAX_X - 1) : x));
//y = ((y < 0) ? 0 : ((y >= GAME_MAX_Y) ? (GAME_MAX_Y - 1) : y));
if ((x < 0) || (x >= GAME_MAX_X) ||
(y < 0) || (y >= GAME_MAX_Y))
{
return PR_NULL;
}
return TETRIS_GAME_MAP[x][y].pr;
}
/*****************************************************************************
函 数 名 : game_clear_screen
功能描述 : 指定颜色清除屏幕
输入参数 : int32 col 指定颜色
输出参数 : 无
返 回 值 : 无
*****************************************************************************/
void game_clear_screen(int32 col)
{
int32 x = 0, y = 0;
//col = FALSE;
for (x = 0; x < GAME_MAX_X; x++)
{
for (y = 0; y < GAME_MAX_Y; y++)
{
TETRIS_GAME_MAP[x][y].col = col;
TETRIS_GAME_MAP[x][y].pr = PR_NULL;
}
}
//return RTN_OK;
}
//把地图绘制到屏幕窗口上
int32 game_update_screen(void)
{
int32 x = 0, y = 0;
gdi_clear(TETRIS_PR_COLOUR[PR_NULL]);
for (x = 0; x < GAME_MAX_X; x++)
{
for (y = 0; y < GAME_MAX_Y; y++)
{
map_draw_point(x, y, TETRIS_GAME_MAP[x][y].col, TETRIS_GAME_MAP[x][y].pr);
}
}
gdi_update();
return RTN_OK;
}
方块绘制系统
游戏中的每一个方块,都会被写入到地图上,再由图形绘制系统绘制到屏幕上.
/*****************************************************************************
函 数 名 : game_show_shpe
功能描述 : 显示一个shape
输入参数 : game_shape_t* _shape
输出参数 : 无
返 回 值 : RTN_OK:成功, RTN_ERR:失败
*****************************************************************************/
int32 game_show_shpe(game_shape_t* _shape)
{
int i = 0;
game_point_t shape_pnt;
for (i = 0; i < SHAPE_PNT_CNT; i++)
{
shape_pnt.x = (GAME_SHAPE_BOX[_shape->index][i]).x + (_shape->point).x;
shape_pnt.y = (GAME_SHAPE_BOX[_shape->index][i]).y + (_shape->point).y;
//shape_pnt.col = /*(_shape->point).col*/TRUE;
shape_pnt.col = (((_shape->index) / MAX_EXT_SHAPE_CNT) + 1);
if ((shape_pnt.x < 0) || (shape_pnt.x >= GAME_MAX_X) ||
(shape_pnt.y < 0) || (shape_pnt.y >= GAME_MAX_Y))
{
//return RTN_ERR;
continue;
}
if (game_draw_point(&shape_pnt) != RTN_OK)
{
//return RTN_ERR;
continue;
}
}
return RTN_OK;
}
/*****************************************************************************
函 数 名 : game_clear_shpe
功能描述 : 清除指定的shape
输入参数 : game_shape_t* _shape 要清除的shape的指针
输出参数 : 无
返 回 值 : RTN_OK:成功, RTN_ERR:失败
*****************************************************************************/
int32 game_clear_shpe(game_shape_t* _shape)
{
int i = 0;
game_point_t shape_pnt;
for (i = 0; i < SHAPE_PNT_CNT; i++)
{
shape_pnt.x = (GAME_SHAPE_BOX[_shape->index][i]).x + (_shape->point).x;
shape_pnt.y = (GAME_SHAPE_BOX[_shape->index][i]).y + (_shape->point).y;
shape_pnt.col = FALSE;
if ((shape_pnt.x < 0) || (shape_pnt.x >= GAME_MAX_X) ||
(shape_pnt.y < 0) || (shape_pnt.y >= GAME_MAX_Y))
{
//return RTN_ERR;
continue;
}
if (game_draw_point(&shape_pnt) != RTN_OK)
{
//return RTN_ERR;
continue;
}
}
return RTN_OK;
}
满行检测
通过遍历地图上的每一行,如果某一行上的所有点都为方块而不是空白,则认为该行是满行,可以消除该行.
/*****************************************************************************
函 数 名 : game_clear_line
功能描述 : 清除指定行
输入参数 : int32 row 要清除的行
输出参数 : 无
返 回 值 : RTN_OK:成功, RTN_ERR:失败
*****************************************************************************/
int32 game_clear_line(int32 row)
{
int32 x = 0;
game_point_t pnt;
//row = ((row < 0) ? 0 : ((row >= GAME_MAX_Y) ? (GAME_MAX_Y - 1) : row));
if ((row < 0) || (row >= GAME_MAX_Y))
{
return RTN_ERR;
}
pnt.y = row;
pnt.col = FALSE;
for (x = 0; x < GAME_MAX_X; x++)
{
pnt.x = x;
if (game_draw_point(&pnt) != RTN_OK)
{
//return RTN_ERR;
continue;
}
}
return RTN_OK;
}
/*****************************************************************************
函 数 名 : game_line_isfull
功能描述 : 判断某一行是不是已经满了
输入参数 : int32 row 要判断的行
输出参数 : 无
返 回 值 : TRUE:已满, FALSE:未满
*****************************************************************************/
int32 game_line_isfull(int32 row)
{
int32 x = 0, row_cnt = 0;
game_point_t pnt;
//row = ((row < 0) ? 0 : ((row >= GAME_MAX_Y) ? (GAME_MAX_Y - 1) : row));
if ((row < 0) || (row >= GAME_MAX_Y))
{
return FALSE;
}
pnt.y = row;
pnt.col = TRUE;
for (x = 0; x < GAME_MAX_X; x++)
{
pnt.x = x;
if (game_get_point(&pnt) == TRUE)
row_cnt++;
else
break;
}
DEBUG_LOG("");
return ((row_cnt == GAME_MAX_X) ? TRUE : FALSE);
}
方块移动检测
根据移动方向,把方块上的每一个小格子的坐标向对应方向上移动一次,再检测移动过后的小格子的坐标位置不在方块自身上,并且判断原来该位置是否已经有小格子,如果有,则不能再移动,如果没有,表示能够继续移动.
/*****************************************************************************
函 数 名 : game_shape_can_move
功能描述 : 判断指定的shape是否能够往左,往右,往下移动
输入参数 : game_shape_t* shape 待判断的shape
tetris_dir_t dir shape移动的方向
输出参数 : 无
返 回 值 : TRUE:可以移动, FALSE:不可以移动
*****************************************************************************/
int32 game_shape_can_move(game_shape_t* shape, tetris_dir_t dir)
{
int32 i = 0, dx = 0, dy = 0;
game_point_t pnt_t = { 0 };
DEBUG_LOG("");
switch (dir)
{
case DR_DOWN:
dx = 0;
dy = 1;
break;
case DR_LEFT:
dx = -1;
dy = 0;
break;
case DR_RIGHT:
dx = 1;
dy = 0;
break;
default:
return FALSE;
}
for (i = 0; i < SHAPE_PNT_CNT; i++)
{
//遍历shape中的四个点
//shape的下一个点
pnt_t.x = (shape->point).x + (GAME_SHAPE_BOX[shape->index][i]).x + dx;
pnt_t.y = (shape->point).y + (GAME_SHAPE_BOX[shape->index][i]).y + dy;
pnt_t.col = (shape->point).col;
if ((pnt_t.x < 0) || (pnt_t.x >= GAME_MAX_X) ||
(pnt_t.y < 0) || (pnt_t.y >= GAME_MAX_Y))
{
return FALSE;
}
//如果shape的某个点的下一个点不是空的,并且这个点没在shape中,
//则表明已经没有下落的空间,否则可以下落
if ((game_get_point(&pnt_t) != FALSE) &&
(game_pnt_is_shape(&pnt_t, shape) != TRUE))
{
DEBUG_LOG("pnt_t,i=%d, x=%d,y=%d,col=%d\n", i, pnt_t.x, pnt_t.y, pnt_t.col);
return FALSE;
}
}
DEBUG_LOG("");
return TRUE;
}
/*****************************************************************************
函 数 名 : game_shape_down
功能描述 : 判断全局shape是否能够继续下移,如果不能直接显示,如果能则继续下移
输入参数 : game_shape_t* _shape
输出参数 : 无
返 回 值 : RTN_OK:成功, RTN_ERR:失败
*****************************************************************************/
int32 game_shape_down(game_shape_t* _shape)
{
if (_shape == NULL)
{
return RTN_ERR;
}
DEBUG_LOG("idx=%d,x=%d,y=%d,col=%d\n",
_shape->index, _shape->point.x,
_shape->point.y, _shape->point.col);
if (game_shape_can_move(_shape, DR_DOWN) != TRUE)
{
//已经没有下落空间,直接显示
DEBUG_LOG("game_shape_down FALSE\n");
game_show_shpe(_shape);//显示
return RTN_ERR;
}
else
{
//仍有下落空间,更新坐标后显示
game_clear_shpe(_shape);
_shape->point.y += 1;//坐标下移
game_show_shpe(_shape);
}
DEBUG_LOG("idx=%d,x=%d,y=%d,col=%d\n",
_shape->index, _shape->point.x,
_shape->point.y, _shape->point.col);
return RTN_OK;
}
方块变形
每个方块又四种形状变化.每变化一次,只需要改变该形状在形状库中的索引.变换形状之前先要判断该形状是否能够继续下移.
/*****************************************************************************
函 数 名 : tetris_shape_deform
功能描述 : 全局shape变形
输入参数 : void
输出参数 : 无
返 回 值 : RTN_OK:成功
*****************************************************************************/
int32 tetris_shape_deform(void)
{
int32 shape_idx = 0;
if ((game_shape_can_move(&glGameShape, DR_DOWN) != TRUE)/* ||
(game_shape_can_move(&glGameShape, DR_RIGHT) != TRUE) ||
(game_shape_can_move(&glGameShape, DR_LEFT) != TRUE)*/)
{
//如果已经不能下落,则禁止变形
return RTN_ERR;
}
game_clear_shpe(&glGameShape);
shape_idx = glGameShape.index;
if ((glGameShape.index % 4) < (MAX_EXT_SHAPE_CNT - 1))
{
glGameShape.index += 1;
}
else
{
glGameShape.index = ((int32)(glGameShape.index / 4) * 4);
}
game_show_shpe(&glGameShape);
if ((game_shape_can_move(&glGameShape, DR_DOWN) != TRUE)/* ||
(game_shape_can_move(&glGameShape, DR_RIGHT) != TRUE) ||
(game_shape_can_move(&glGameShape, DR_LEFT) != TRUE)*/)
{
//如果变形之后不能下落,则变形失败,还原
game_clear_shpe(&glGameShape);
glGameShape.index = shape_idx;
game_show_shpe(&glGameShape);
return RTN_ERR;
}
game_play_sound(IDR_WAVE_LIFE);
return RTN_OK;
}
俄罗斯方块游戏运行
游戏运行过程中,要判断新生成的方块能否继续移动,如果能够继续移动则移动方块位置,如果不能够移动,则直接显示该方块,并生成新的方块.然后会判断地图上是否有满行,如果有满行,则消除该行,并把该行之上的所有行下移.
/*****************************************************************************
函 数 名 : tetris_game_run
功能描述 : 游戏开始运行,并处理满行,消行,分数
输入参数 : void
输出参数 : 无
返 回 值 : RTN_OK:成功
*****************************************************************************/
int32 tetris_game_run(int32 debug)
{
//shape一直下降,直到无法下降为止
int32 x = 0, y = 0, y1 = 0;
int32 max_stackh = 0;
int32 max_y = 0;
game_point_t pnt;
int32 oldcol = FALSE;
if (game_shape_down(&glGameShape) != RTN_OK)
{
//不可下落的情况下,计算分数,并产生一个新的shape,重新开始
max_stackh = game_get_maxh_stack();
DEBUG_LOG("max_stackh=%d\n", max_stackh);
if (max_stackh >= (GAME_MAX_Y - 1))
{
//game_clear_screen(FALSE);
//tetris_game_init();//屏幕满,游戏结束
//game_shape_init();
//glGameSCore = 0;
//glGameSpeed = GAME_MIN_SPEED;
glGameLife = LF_DIE;
return glGameLife;
}
max_y = (GAME_MAX_Y - 1) - max_stackh;
for (y = (GAME_MAX_Y - 1); y >= max_y; y--)
{
//寻找是否有满行
if (game_line_isfull(y) == TRUE)
{
//如果当前行是满行,则分数加,消除当前行,所有行下移
DEBUG_LOG("line %d isfull\n", y);
glGameSCore++;
if ((glGameSCore % GAME_SPEED_STEP) == 0)
{
if (glGameSpeed < GAME_MAX_SPEED)
glGameSpeed++;
}
game_clear_line(y);//消除满行
//如果当前行不是第一行,则上层所有点下移
if (y > 0)
{
for (y1 = y - 1; y1 >= max_y; y1--)
{
for (x = 0; x < GAME_MAX_X; x++)
{
//把当前坐标点的值搬移到下一个点
pnt.x = x;
pnt.y = y1;
if (game_get_point(&pnt) == TRUE)
{
//保存点的颜色
oldcol = game_get_pntpr(&pnt);
//清除当前坐标点
pnt.col = FALSE;
game_draw_point(&pnt);
//pnt.col = TRUE;
pnt.col = oldcol;
pnt.y = y1 + 1;
game_draw_point(&pnt);
}
}
}
//因为上层所有行都下移了一行,所以要把
//坐标重新定位到当前行再次进行判断
y += 1;
game_play_sound(IDR_WAVE_WEAPON);
}
}
}
game_shape_init();//重新产生一个shape
if ((game_get_shape_size(&glGameShape, 1) - 1) >
(GAME_MAX_Y - 1 - max_stackh))//已经没有空间了
{
//game_clear_screen(FALSE);
//tetris_game_init();//屏幕满,游戏结束
//glGameSCore = 0;
//glGameSpeed = GAME_MIN_SPEED;
glGameLife = LF_DIE;
return glGameLife;
}
}
DEBUG_LOG("glGameShape,idx=%d,x=%d,y=%d,col=%d\n\n",
glGameShape.index, glGameShape.point.x,
glGameShape.point.y, glGameShape.point.col);
DEBUG_LOG("GAME_MAX_X[%d],GAME_MAX_Y[%d]\r\n", GAME_MAX_X, GAME_MAX_Y);
game_update_screen();
if (debug)
{
game_debug_out();
}
return glGameLife;
}
程序运行截图
项目文件截图
俄罗斯方块-C语言-详注版
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权