首页 > 其他分享 >俄罗斯方块-C语言-详注版

俄罗斯方块-C语言-详注版

时间:2023-06-02 13:32:49浏览次数:35  
标签:int32 MAX GAME C语言 shape game 详注 TRUE 方块


俄罗斯方块-C语言-详注版

概述

本文详述了C语言版俄罗斯方块游戏的原理以及实现方法,对游戏代码进行了详细的分析和注释,通过本文能够让大家对WIN32编程框架有一个大致了解,对C语言运用有一定提高,同时也能给大家提供一个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;
}

俄罗斯方块

方块形状库

俄罗斯方块-C语言-详注版_双缓冲_02

如上图所示,每个坦克由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语言-详注版_游戏_03

项目文件截图

俄罗斯方块-C语言-详注版

标签:int32,MAX,GAME,C语言,shape,game,详注,TRUE,方块
From: https://blog.51cto.com/u_7583030/6401886

相关文章

  • C语言——指针
    指针的优点:使程序更简洁、紧凑、高效有效的表达更复杂的数据结构动态分配内存得到多于一个数的函数返回值1.指针的基本用法1.1指针的概念内存地址:内存中每个字节单位都有一个编号(一般用十六进制表示)指针:指针就是内存地址指针变量:用于存放地址的变量就叫做指针变量指针变量画图展......
  • C语言——运算符和表达式
    所谓表达式就是指由运算符、运算量和标点符号组成的有效序列,其目的是说明一个计算过程。表达式可以独立成语句:表达式;运算符按功能分为:算术运算、赋值运算、关系运算、逻辑运算、位运算以及其他运算符1.算术运算符:+-*/%++–(1)/:整数相除,向下取整。inta=3/2;floatb=3/2;fl......
  • C语言数组
    数组概念在C语言中,数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。从内存角度,是一片连续的内存空间数组初始化://在编译时明确指定......
  • c语言结构体
    定义结构体变量structStudent{char*name;intage;intscore;};voidmain(){structStudentst1;system("pause");}#include<stdlib.h>#include<string.h>#include<stdio.h>typedefstructStudent{char*nam......
  • C语言链表
    #define_CRT_SECURE_NO_WARNINGS#include<stdlib.h>#include<string.h>#include<stdio.h>/*structTeacher{charname[64];intid;char*p;char**p2;};typedefstructTeacherTeacher;*/typedefstructStudent......
  • 单元测试及C语言的几个例子
     一、单元测试介绍单元测试是软件开发中的一种测试方式,它主要是对代码中最小可测试单元进行检查和验证。通常来说,单元测试的实施应该在整个软件开发周期的早期就开始,最好是在代码编写过程中就边写边测试,以及在执行集成和系统测试之前启动。下面是单元测试的详解:单元测试的目的:单元......
  • Linux系统下C语言的编程技巧
    Linux系统能够为人们提供更加安全实用的效果,保证计算机系统能够稳定的运行。利用Linux系统下首先要进行C语言的编程,掌握编程的技巧能够更好的发挥计算机的作用。如何掌握Linux系统下计算机C语言的编程技巧是计算机发展的关键要素。本文对Linux系统下计算机C语言的编程技巧进行相......
  • c语言值得注意的知识
    1.说明下列每对scanf格式串是否等价?如果不等价,请指出它们的差异。(c)"%f"与"%f "。在`scanf`函数中,`"%f"`和`"%f"`这两种格式的区别在于后面的空格。1.`scanf("%f",&variable);`这种情况下,`scanf`会读取并解析用户输入的浮点数,然后将解析的值存入`variable`中。......
  • 【c&c++】erase怎么用c语言,C++ erase()函数使用时的注意点
    遇见的场景删除vector容器指定元素时;erase()函数的用法vector::erase():从指定容器删除指定位置的元素或某段范围内的元素。具体用法如下:iteratorerase(iterator_Where);删除指定位置的元素,返回值是一个迭代器,指向删除元素的下一个元素;iteratorerase(iterator_First,i......
  • 关于c语言习题(529)
    1、从字符数组中读出相应的整数、实数。(写的有点可怕,先找第一个数字就会简单很多)//从一个字符数组中读出相应的整数、实数#include<stdio.h>#include<math.h>#include<string.h>intmain(){voidatoif(chara[]);chara[30];fgets(a,30,stdin);atoif(a......