代码地址如下:
http://www.demodashi.com/demo/14818.html

俄罗斯方块-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 0wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);                            //背景画刷(默认白色)
#elsehWindowBrush = CreateSolidBrush((COLORREF)GDI_RGB_BACKGROUND);              //背景画刷(自定义)wcex.hbrBackground = hWindowBrush;
#endifwcex.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_Ystatic 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_XYint32 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_XYyt = y;y = x * GAME_POINT_L;x = yt * GAME_POINT_H;x = ((GAME_MAX_Y - 1) - x);//y = ((GAME_MAX_X - 1) - y);
#elsex = x * GAME_POINT_L;y = y * GAME_POINT_H;
#endifpr = ((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++;elsebreak;}DEBUG_LOG("");return ((row_cnt == GAME_MAX_X) ? TRUE : FALSE);
}

方块移动检测

根据移动方向,把方块上的每一个小格子的坐标向对应方向上移动一次,再检测移动过后的小格子的坐标位置不在方块自身上,并且判断原来该位置是否已经有小格子,如果有,则不能再移动,如果没有,表示能够继续移动.

/*****************************************************************************函 数 名  : game_shape_can_move功能描述  : 判断指定的shape是否能够往左,往右,往下移动输入参数  : game_shape_t* shape  待判断的shapetetris_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();//重新产生一个shapeif ((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语言-详注版

代码地址如下:
http://www.demodashi.com/demo/14818.html

注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

俄罗斯方块-C语言-详注版相关推荐

  1. 坦克大战-C语言-详注版

    代码地址如下: http://www.demodashi.com/demo/14259.html 坦克大战-C语言-详注版 概述 本文详述了C语言版坦克大战游戏的原理以及实现方法,对游戏代码进行了详细 ...

  2. python选择排序算法图解_简单选择排序算法(C语言详解版)

    该算法的实现思想为:对于具有 n 个记录的无序表遍历 n-1 次,第 i 次从无序表中第 i 个记录开始,找出后序关键字中最小的记录,然后放置在第 i 的位置上. 例如对无序表{56,12,80,91 ...

  3. 你是真的“C”——详解C语言实现文件版通讯录

    详解C语言实现文件版通讯录

  4. 互为质数的勾股数c语言,C语言求勾股数(详解版)

    搜索热词 问题描述 求100以内的所有勾股数. 所谓勾股数,是指能够构成直角三角形三条边的三个正整数(a,b,c). 问题分析 根据"勾股数"定义,所求三角形三边应满足条件 a2 ...

  5. 电脑连接电视方法详解_电脑如何连网?——校园宽带的连接方法(详解版)

    玉屏洲电脑联网 详解版  联网前必备!--注册好的运维云账号 如果不知道啥是运维云,可以在公众号里发消息 运维云 获取运维云账号注册方法! 注册好的运维云样板 1 第一步·宽带连接 用网线一端连接墙上 ...

  6. AT指令(中文详解版)(二)

    AT指令(中文详解版)(二) 常 用 AT 命 令 手 册   1.常用操作 1.1 AT 命令解释:检测 Module 与串口是否连通,能否接收 AT 命令: 命令格式:AT<CR> 命 ...

  7. 如何用c语言编写stm32的程序吗,STM32入门C语言详解

    <STM32入门C语言详解>由会员分享,可在线阅读,更多相关<STM32入门C语言详解(6页珍藏版)>请在人人文库网上搜索. 1.最新 料推荐阅读 flash : 芯片内部存储 ...

  8. 零基础学c语言 第2版,零基础学C语言(第2版)(1张) 康莉 等 机械工业出版社 9787111372929...

    商品描述: 基本信息 书名:零基础学C语言(第2版)(1张) 定价:59.00元 作者:康莉 等 著 出版社:机械工业出版社 出版日期:2012-04-01 ISBN:9787111372929 字数 ...

  9. c# 实现hello word 详解版

    c# 实现hello word 详解版 前言:超级适合真正零基础的人 工具:Visual Studio 2019 了解: .net/dotnet:一般指.Net Framework框架->一种平 ...

最新文章

  1. 使用idea 在springboot添加本地jar包的方法 部署的时候本地jar没有包含的解决方法
  2. 端口偷窃(Port Stealing)技术
  3. unique--求集合的单值元素
  4. 增加数组下标_数组以及ArrayList源码解析
  5. ios html清除缓存图片,iOS,如何清理缓存的图片
  6. Android中ActionBar中不显示overflow(就是三个点的那个按钮)解决办法
  7. matlab 运算子图_PHP运算子
  8. shell脚本学习总结02--数组
  9. 《土豆荣耀》重构笔记(七)控制角色移动并添加音效
  10. 51单片机基础入门教程(精华版)文末有惊喜
  11. mysql监控工具-PMM,让你更上一层楼(上)
  12. 解决局域网共享无法访问
  13. 周育如的音标口诀大全_一年级汉语音标口诀记忆方法
  14. Axure RP 介绍
  15. ROS学习【3】-----ROS通信编程:小乌龟走正方形
  16. Linux--用xmanager远程管理的设定过程
  17. 七剑下天山?七步搞定DB2查询优化!
  18. 学习日志——2019/07/04
  19. 【TcaplusDB知识库】TcaplusDB进程启动介绍
  20. 一个老鼠走迷宫问题的python解法

热门文章

  1. lwip 数据处理流程
  2. 【openMV】openMV4之sensor模块
  3. 1000以内所有同构数java算法_使用c语言求1到1000同构数的代码
  4. Linux内核网络协议栈2-socket从库函数到内核
  5. graph slam tutorial :从推导到应用2
  6. 高一计算机应用选择题,职业中学 高一《计算机应用基础》期末考试题
  7. java复习系列[1] - Java 基础
  8. C51单片机————总线与系统扩展
  9. 【LeetCode】【HOT】23. 合并K个升序链表(递归+分治)
  10. 【Step By Step】将Dotnet Core部署到Docker(中)