C项目实践--俄罗斯方块(2)

在VS中新建win32 Application Proj,选择Empty ,完成TetrisWin项目创建。新建tetris.c和tetris.h两个文件,打开tetris.h文件。

首先要包括的是可能要用到的头文件,那在这里要用到是什么头文件呢? 本系统是开发一个游戏,那么游戏的话就需要有和用户进行交互的游戏界面,那就需要绘图操作,那么就会用到windows的绘图函数库,所以第一步就是要包括这个windows头文件,但是要注意我们现在是在头文件tetris.h中来包含这个头文件,这里就需要注意使用#ifndef 宏来进行包含处理,因为tetris.h最终会被包含在实现文件中去,但是不确定实现文件的头部是否也会包含这个windows库头文件,如果包括了的话我们就没有必要再包括了,否则就会引发重定义错误,所以在头文件中要包括什么库的头文件时,最好用#ifndef宏先判断是否已包含了该头文件,只有在没有包括的情况下再去包含该头文件,此时才不会报重定义的错误。具体实现如下:

//header infor
#ifndef WIN_H_H
#define WIN_H_H
#include <Windows.h>
#endif

因为游戏中只有7种游戏方块,所以可以声明为一个枚举类型,同时也有利于在游戏中为了方便的直到当前方块形状和下一个方块形状。具体实现如下:

//self_definition enum 
typedef enum tetris_shape{
    ZShape = 0,
    SShape,
    LineShape,
    TShape,
    SquareShape,
    LShape,
    MirroredLShape
}shape;

接下来根据要实现的功能模块声明相应的处理函数。具体实现如下:

//function declaraction
int maxX();        //取得当前方块的最大x坐标
int minX();        //取得当前方块的最小x坐标
void turn_left();  //将当前方块逆时针旋转90度
void turn_right(); //将当前方块顺时针旋转90度
int out_of_table();//检查当前方块是否超出桌面的范围
void transform();  //旋转当前方块
int leftable();    //判断当前方块能否左移
int rightable();   //判断当前方块能否右移
int downable();    //判断当前方块能否下移
void move_left();  //向左移动当前方块
void move_right(); //向右移动当前方块
 
//operation function
int add_to_table();//将当前方块固定到桌面上,若返回0,表示游戏结束
void remove_full();//删除桌面上填满的行
 
//control function
void new_game(); //创建一个新游戏
void run_game(); //运行游戏
void next_shape(); //将下一个方块设为当前方块,并设置下一个方块
int random(int seed);//取得一个随机数,例如random(7)将返回一个0-6之间的随机数
 
//paint function
void paint();  //将内存位图输出到窗口上
void draw_table(); //绘制游戏桌面
 
//other functions
void key_down(WPARAM wParam); //处理键盘按下事件
void resize();   //改变窗口大小时调用的函数
void intialize();//初始化
void finalize(); //结束时,释放资源
 
//callback function
//回调函数,用来处理windows消息
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

上面基本上把需要用到的处理函数都已声明完毕,接下来打开tetris.c文件来实现相应的功能,首先需要包含一些头文件,1.系统中需要用到sprintf()这样函数来格式化输出一些字符到相应的变量中,所以需要包含stdio.h文件,同时需要使用当前时间作为rand()种子来获取随机数,所以需要包含time.h文件,当然还需要包含tetris.h文件,具体实现如下:

//Header Info
#include <time.h>
#include <stdio.h>
#include "tetris.h"

接下来需要定义一些常量,包括游戏开始结束时的提示信息以及相应的颜色值等,具体定义如下:

//constant definition
#define APP_NAME "TETRIS"
#define APP_TITLE "Tetris Game"
#define GAMEOVER "GAME OVER"
 
#define SHAPE_COUNT 7  //形状的个数
#define BLOCK_COUNT 4  //每个形状由几个小表格构成
#define MAX_SPEED 5   //速度级别
#define COLUMS 20     //游戏桌面表格的列数
#define ROWS 30       //游戏桌面表格的行数
 
//7种颜色
#define RED RGB(255,0,0)
#define YELLOW RGB(255,255,0)
#define GRAY RGB(128,128,128)
#define BLACK RGB(0,0,0)
#define WHITE RGB(255,255,255)
#define STONE RGB(192,192,192)
 
#define CHARS_IN_LINE 14    //提示信息的一行有多少个字符
#define SCORE "SCORE  %4d"  //得分格式化

接下来定义一些全局变量:

//global variables definition
char score_char[CHARS_IN_LINE] = {0}; //声明一个接收得分情况的字符数组
char* press_enter = "Press Enter Key...";
char* help[] =  //帮助提示信息
{
    "Press space or up key to transform shape.",
    "Press left or right key to move shape.",
    "Press down key to speed up.",
    "Press enter key to pause game.",
    "Enjoy it. :-)",
    0
};

接下来把游戏状态定义为一个枚举类型,便于对其进行操作,具体实现如下:

//enum  the state of game
enum game_state
{
    game_start,
    game_run,
    game_pause,
    game_over
}state = game_start;

之前我们把游戏的7种方块定义成了一个枚举类型,那么我们会通过这些枚举变量的相应的值来判断目前是哪种方块,同时我们要为这些方块涂上不同的颜色,所以我们最好也把这些颜色定义为一个数组,这样到时候要为某个方块涂上对应颜色的时候也只需要根据当前方块的枚举变量值做为数组的下标值来确定相应的颜色方案,所以下面来定义颜色数组:

//color of Rectangle 
COLORREF shape_color[] = 
{
    RGB(255,0,0),
    RGB(0,255,0),
    RGB(0,0,255),
    RGB(255,255,0),
    RGB(0,255,255),
    RGB(255,0,255),
    RGB(255,255,255)
};

接下来定义表示7种方形相对坐标的三维数组,具体实现如下:

//SEVEN SHAPE OF RECTANGLE
int shape_coordinate[SHAPE_COUNT][BLOCK_COUNT][2] = 
{
    {{0,-1},{0,0},{-1,0},{-1,1}},
    {{0,-1},{0,0},{1,0},{1,1}},
    {{0,-1},{0,0},{0,1},{0,2}},
    {{-1,0},{0,0},{1,0},{0,1}},
    {{0,0},{1,0},{0,1},{1,1}},
    {{-1,-1},{0,-1},{0,0},{0,1}},
    {{1,-1},{0,-1},{0,0},{0,1}}
};

接下来还需要定义一些辅助变量如记录得分的变量score,如当前方块的最左边的坐标位置等以及相应的绘图变量,具体定义和说明如下:

int score = 0;                         //得分
 
//shape next = 0;
//shape current = 0;
shape next = ZShape;                  //下一个方块
shape current = ZShape;               //当前方块
 
int current_coordinate[4][2] = {0};   //当前方块的每一部分的坐标,初始化为0
int table[ROWS][COLUMS] = {0};        //游戏桌面,初始化为全0,表示桌面上还没有方块
int shapex = 0;                       //当前方块的x坐标
int shapey = 0;                       //当前方块的y坐标
int speed = 0;                        //方块下移的速度
clock_t start = 0;                    //每一帧的开始时间
clock_t finish = 0;                   //每一帧的结束时间
 
//windows paint function
HWND  gameWND;                    //window窗口句柄
HBITMAP memBM;                    //内存位图
HBITMAP memBMOld;                 //内存原始位图
HDC memDC;                        //内存DC
RECT clientRC;                    //客户区矩形区域
HBRUSH blackBrush;                //黑色画笔
HBRUSH stoneBrush;                //深灰色画笔
HBRUSH shapeBrush[SHAPE_COUNT];   //方块画笔,7种方块,每种一个
HPEN grayPen;                     //灰色画笔
HFONT bigFont;                    //大字体,用来显示游戏名称及"GAME OVER"
HFONT smallFont;                  //小字体用来显示帮助信息等

目前,该声明的变量都已经声明完毕, 接下来就开始根据相应的功能实现其处理函数

1.取最大坐标

函数名称:maxX

函数功能:取得当前方块的最大x坐标。具体实现如下:

//main functions
//对比当前方块的BLOCK_COUNT个小块
//选择它们最大的x坐标
int maxX()
{
    int i =0;
    int x = current_coordinate[i][0];
    int m = x;
    for(i = 1; i< BLOCK_COUNT; i++)
    {
        x = current_coordinate[i][0];
        if(m < x)
        {
            m = x;
        }
    }
    return m;
}

2.取最小坐标

函数名称:minX

函数功能:取得当前方块的最小x坐标。具体实现如下:

int minX()
{
    int i = 0;
    int x = current_coordinate[i][0];
    int m = x;
    for(i = 1;i<BLOCK_COUNT;i++)
    {
        x = current_coordinate[i][0];
        if(m > x)
        {
            m = x;
        }
    }
    return m;
}

3.逆时针旋转方块

函数名称:turn_left

函数功能:将当前方块逆时针旋转90度。具体实现如下:

//逆时针旋转90度
//旋转公式 x' = y; y' =  -x
void turn_left()
{
    int i = 0;
    int x, y;
    for(i = 0; i < 4; i++)
    {
        x = current_coordinate[i][0];
        y = current_coordinate[i][1];
        current_coordinate[i][0] = y;
        current_coordinate[i][1] = -x;
    }
}

image

image

4.顺时针旋转方块

函数名称:turn_right

函数功能:将当前方块顺时针旋转90度。旋转公式:x' = –y , y' = x; 具体实现如下:

//顺时针旋转
void turn_right()
{
    int i = 0;
    int x, y;
    for(i = 0; i< 4; i++)
    {
        x = current_coordinate[i][0];
        y = current_coordinate[i][1];
        current_coordinate[i][0] = -y;
        current_coordinate[i][1] = x;
    }
}

5.检测方块是否越界

函数名称:out_of_table

函数功能:检查当前方块是否超出桌面范围。具体实现如下:

//检测是否越界
int out_of_table()
{
    int i = 0;
    int x , y;
    for(i = 0; i < 4; i++)
    {
        //shapex,shapey的值在生成一个新的方块时设定,它们是方块生成时最左边的初始边界坐标点值
        //current_coordinate[i][]值是相对应shapex,shapey的偏移值
        //所以直接检测shapex + current_coordiante[][]值就是其目前在游戏桌面上的坐标值
        x = shapex + current_coordinate[i][0];
        y = shapey + current_coordinate[i][1];
        //如果x值为负值,则表示游戏方块已经超出了游戏桌面的最左边的表示边界
        //如果x值大于COLUMS-1表示超出了游戏桌面的最右边表示边界
        //如果y值大于游戏桌面最下面的表示边界,则表示超出了游戏边界
        if(x < 0 || x > (COLUMS-1)|| y > (ROWS - 1))
        {
            return 1;
        }
        //table[ROWS][COLUMS]表示为table[y][x]本身是初始化为全0的,如果由值不是0 表示
        //当前方形已经运行到某个方形上面了,则也表示越界
        if(table[y][x])
        {
            return 1;
        }
    }
    //方形没有越界则返回0,否则返回1
    return 0;
}

6.旋转方块

函数名称:transform

函数功能:旋转当前方块。具体实现如下:

//旋转当前方块
void transform()
{
    //如果是田字形的方块则不需要旋转变化
    if(current == SquareShape)
    {
        return ;
    }
    //默认顺时针旋转
    turn_right();
    //如果顺时针旋转出现越界情况则
    //进行逆时针旋转
    if(out_of_table())
    {
        turn_left();
    }
}

7.判断方块能否向左移动

函数名称:leftable

函数功能:判断当前方块能否向左移动,能移动则返回1,否则返回0.具体实现如下:

//判断能否向左移动
int leftable()
{
    int i = 0;
    int x , y;
    for(i = 0; i < 4; i++)
    {
        //shapex,shapey 是初始化生成方形时最左边的x,y坐标值
        //current_coordinate是方形的相对偏移位置
        //x,y为目前方形中某个方块的实际坐标值
        x = shapex + current_coordinate[i][0];
        y = shapey + current_coordinate[i][1];
        //如果发生越界则返回0,否则返回1
        //x <= 0 判断x是否越过左边界
        //判断table[y][x-1] == 1,表示以当前坐标值x,y所在点为基准
        //向左探测一小方块,看这个小方块是否被已有的方形占用了
        //如果占用了则它的值为1,否则为0,如果为1,则表示目前方形已经
        //落到了另一个已经存在的方形上了,发生了重叠,那这也是越界了
        if(x <= 0 || table[y][x-1]  == 1)
        {
            return 0;
        }
    }
    return 1;
}

8.判断方块能否向右移动

函数名称:rightable

函数功能:判断当前方块能否向右移动,能移动则返回1,否则返回0.具体实现如下:

//判断能否向右移动
int rightable()
{
    int i = 0; 
    int x, y ;
    for(i = 0; i < 4; i++)
    {
        x = shapex + current_coordinate[i][0];
        y = shapey + current_coordinate[i][1];
        //x>=(COLUMS -1)判断是否越过右边界
        //判断table[y][x+1]    表示以当前坐标值x,y所在点为基准
        //向右探测一小方块,看这个小方块是否被已有的方形占用了
        //如果占用了则它的值为1,否则为0,如果为1,则表示目前方形已经
        //落到了另一个已经存在的方形上了,发生了重叠,那这也是越界了
        if(x >= (COLUMS -1) || table[y][x+1] == 1)
        {
            return 0;
        }
    }
    return 1;
}

9.判断方块能否向下移动

函数名称:downable

函数功能:判断当前方块能否向下移动,能移动则返回1,否则返回0.具体实现如下:

int downable()
{
    int i = 0;
    int x , y;
    for(i = 0; i<4;i++)
    {
        x = shapex + current_coordinate[i][0];
        y = shapey + current_coordinate[i][1];
        if(y >= (ROWS -1) || table[y+1][x] == 1)
        {
            return 0;
        }
    }
    return 1;
}

10.向左或右移动当前方块

函数名称:move_left /move_right

函数功能:向左/右移动当前方块。具体实现如下:

void move_left()
{
    if(leftable())
    {
        //shapex是方块最左边的坐标位置点x的值,--表示
        //当前方形整体左移一个单位
        shapex--;
    }
}
 
void move_right()
{
    if(rightable())
    {
        //shapex是方块最左边的坐标位置点x的值,++表示
        //当前方形整体右移一个单位
        shapex++;
    }
}

11.向下移动当前方块

函数名称:move_down

函数功能:向下移动当前方块。具体实现如下:

void move_down()
{
    //如果可以向下移动,则
    //shapey++,表示当前方形整体下移一个单位
    if(downable())
    {
        shapey++;
    }else{ //如果不能往下移动则继续判断
        //如果是可以添加到当前游戏桌面上,则自己添加
        if(add_to_table())
        {  //添加完毕之后调用remove_full()函数来检测是否
            //有满行的,如果有则清除掉
            //然后继续生成下一个方形,将其作为当前方形
            remove_full();
            next_shape();
        }else{
            //如果既不能往下移动,也不能把他添加到游戏桌面上
            //则表示越界,那就结束游戏
            state = game_over;
        }
    }
}

12.将当前方块固定到桌面上

函数名称:add_to_table
函数功能:将当前方块固定到桌面上,若返回0,则表示游戏结束。具体实现如下:

//将当前方块添加到桌面上,若返回0则表示游戏结束,否则添加成功
int add_to_table()
{
    int i = 0; 
    int x ,y;
    for(i = 0; i< 4; i++)
    {
        //获取当前方形其中的小方块的具体坐标点值
        x = shapex + current_coordinate[i][0];
        y = shapey + current_coordinate[i][1];
        //判断是否越界或已经有其它方块存在,如果没有则置1表示添加成功
        if(y < 0 || table[y][x] == 1)
        {
            return 0;
        }
        table[y][x] = 1; 
    }
 
    return 1;
}

13.删除填满的行

函数名称:remove_full

函数功能:删除桌面上填满的行。具体实现如下:

//删除桌面上填满的行
void remove_full()
{
    int c = 0;
    int i,j;
    //首先定位到最底下那一行
    for(i = ROWS -1; i>0;i--)
    {
        c = 0; 
        //然后将当前行的所有值相加
        for(j = 0; j < COLUMS;j++)
        {
            c += table[i][j];
        }
        //如果所有值相加的结果等于列数目,则表示当前行是满行
        if(c == COLUMS)
        {
            //void *memmove( void* dest, void* src,count );
            //memmove是从src所指内存区域复制count个字节到dest所指内存区域
            //memmove有个特性,如果目标区域dest和源区域src有重叠的话,memmove
            //能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中
           //这里我们正是利用了memmove的这个特性完成满行的删除工具和删除后其上面的未满行
            //向下移动的工作
            memmove(table[1],table[0],sizeof(int)*COLUMS*i);
            //将table[0]清空
            memset(table[0],0,sizeof(int)*COLUMS);
            score++;//分数加1
            speed = (score /100)%MAX_SPEED; //变速
            i++;
        }
        else if(c == 0)
        {
            break;
        }
    }
}

删除满行中利用了<windows.h>中的库函数memmove保证叠加区域复制正确的特性,例如

#define ROWS 5

#define COLUMS 2

int table[ROWS][COLUMS] = {

{0,0},

{1,1},

{0,1},

{1,1},

{1,0}

};

利用memmove(table[1],table[0],sizeof(int)*COLUMS*i)它完成的功能是,是从table[0]地址处开始复制COLUMS*i个int字节个数据将其放到以table[1]地址开始的地方,具体操作如图:

image

memset(table[0],0,sizeof(int)*COLUMS); 即把table[0]那一行清空,因为它是最上面那一行,所以只要有满行被删除,那么最上面那一行永远都应该是空的。

14.创建新游戏

函数名称:new_game

函数功能:创建一个新游戏。具体实现如下:

//创建新游戏
void new_game()
{
    //将桌面表格全部置零,清空桌面上残余的方形
    memset(table,0,sizeof(int)*COLUMS*ROWS);
    start = clock(); //初始化时钟
    next = (shape)random(SHAPE_COUNT); //初始化下一个方形
    score = 0; //得分初始化为0
    speed = 0; //速度初始化为0
}

15.运行游戏

函数名称:run_game

函数功能:运行游戏。具体实现如下:

//运行游戏
void run_game()
{
    finish = clock();
    if((finish - start) > (MAX_SPEED-speed)*100)//设定方形跳动的时间间隔
    {
        move_down(); //向下移动
        start = clock();//重新记录下一次跳动的开始时间
        InvalidateRect(gameWND,NULL,TRUE);//刷新整个客户区,重新跳动之后的图形
    }
}

16.操作当前方块

函数名称:next_shape

函数功能:将下一个方形设为当前的方块,并随机生成下一个方块。具体实现如下:

void next_shape()
{
    current = next;
    //将下一个方形设置为当前方形
    memcpy(current_coordinate,shape_coordinate[next],sizeof(int)*BLOCK_COUNT*2);
    //shapex的值为方块放在游戏桌面正中央位置时其最左边的x值
    //shapey值为0
    shapex = (COLUMS - ((maxX()-minX())))/2;
    shapey = 0;
    //随机生成下一个方块
    next = (shape)random(SHAPE_COUNT);
}

17.取随机数

函数名称:random

函数功能:取得一个随机数,例如,random(7)将返回一个0-6之间的随机数。具体实现如下:

int random(int seed)
{
    if( 0 == seed)
    {
        return 0;
    }
    srand((unsigned)time(NULL));//以当前时间作为srand的seed
    //srand()函数就是给rand()提供种子seed。如果srand()中的形参每次都是同一个值
    //那么每次运行rand()产生的随机数也是一样的, 所以为了rand()每次产生不一样的随机数
    //通常把当前时间作为srand()的参数,这样就可以得到不一样的srand()参数,从而rand()也
    //就产生不一样的种子
    return (rand() % seed);
}

18.绘图

函数名称:paint

函数功能:将内存位图输出到窗口上。具体实现如下:

//paint func
void paint()
{
    PAINTSTRUCT ps;
    HDC hdc;
    draw_table();
    /*
    *BeginPaint函数为指定窗口做绘图工作前的相关准备工作,将绘图有关的信息填充到
    *一个PAINTSTRUCT结构中。原型如下:
    *   HDC BeginPaint(
    *      HWND hwnd, //[输入]被重绘的窗口句柄
    *      LPPAINTSTRUCT lpPaint  //[输出]指向一个用来接收绘图信息的PAINTSTRUCT结构
    *      )
    *   返回值: 如果函数成功,则返回值是指定窗口的"显示设备描述表"句柄。
    *            否则返回值为NULL,表明没有得到显示设备的内容。
    *   [注]
    *   BeginPaint函数自动设置显示设备内容的剪切区域,而排除任何更新区域外的区域。该
    * 更新区域可以通过 InvalidateRect 或  InvalidateRgn 函数来设置,也可以是系统在改
    * 变大小,移动,创建,滚动后设置的。如果更新区域被标记为可擦除的,BeginPaint将发
    * 生一个WM_ERASEBKGND消息给窗口。如果窗口类有一个自己的背景刷,那么BeginPaint将
    * 使用这个刷子来擦除更新区域的背景。
    * BeginPaint与EndPaint只能配对使用,不能单独使用,BeginPaint返回一个用来绘图的客
    * 户区的显示设备内容的HANDLE, 而EndPaint则终止绘画请求,并释放设备内容。
    * 需要注意的是BeginPaint和 EndPaint只能在响应WM_PAINT消息时使用,而且只能调用一次
    * 
    * 如果被绘画的客户区中有一个caret(caret:插入符。是窗口客户区中的一个闪烁的线,块,
    * 或位图。插入符通常表示文本或图形将被插入的地方。即一闪一闪的光标),BeginPaint将
    * 自动隐藏该符号,而保证它不被擦除。
    */
    hdc = BeginPaint(gameWND,&ps);
    //BOOL BitBlt(HDC hdcDest, int nXDest,int nYDest, int nWidth,int nHeight, HDC hdcSrc
    //          ,int nXSrc, int nYSrc,DWORD dwRop); 如果dwRop 是SRCCOPY 则表示将源hdcSrc
    //内存位图原样拷贝到目标hdcDest处。
    BitBlt(hdc,clientRC.left,clientRC.top,clientRC.right,clientRC.bottom,memDC,0,0,SRCCOPY);
    EndPaint(gameWND,&ps);
}

19.绘制游戏桌面

函数名称:draw_table

函数功能:绘制游戏桌面

处理流程:首先用黑色矩形填充桌面背景区,接着判断游戏的状态,如果是开始状态,用黄色字显示游戏开始画面,如果是结束状态,用红色字显示 GAME OVER ; 如果是游戏运行状态,则依次绘制游戏桌面,当前方块,下一个方块,得分和游戏帮助等。具体实现如下:

//绘制游戏桌面
void draw_table()
{
    HBRUSH hBrushOld;
    HPEN hPenOld;
    HFONT hFontOld;
    RECT rc;
    int x0,y0,w;
    int x,y,i,j;
    char* str;
 
    w = clientRC.bottom /(ROWS + 2); //设置一个方块的宽度
    x0 = y0 = w;
    //用黑色矩形填充桌面背景区
    FillRect(memDC,&clientRC,blackBrush);
    if(state == game_start || state == game_over)
    {
        // 绘制游戏开始或结束的标题界面
        memcpy(&rc,&clientRC,sizeof(RECT));
        rc.bottom = rc.bottom / 2;
        hFontOld = (HFONT)SelectObject(memDC,bigFont);
        SetBkColor(memDC,BLACK);
        //如果游戏是开始状态,则用黄色字显示游戏开始界面
        if(state == game_start)
        {
            str = APP_TITLE;
            SetTextColor(memDC,YELLOW);
        }else{ //如果游戏是结束状态,则用红色字显示 GAME OVER 
            str = GAMEOVER;
            SetTextColor(memDC,RED);
        }
 
        DrawText(memDC,str,strlen(str),&rc,DT_SINGLELINE | DT_CENTER| DT_BOTTOM);
        SelectObject(memDC,hFontOld);
 
        //提示信息
        hFontOld = (HFONT)SelectObject(memDC,smallFont);
        rc.top = rc.bottom;
        rc.bottom = rc.bottom*2;
        if(state == game_over)
        {
            SetTextColor(memDC,YELLOW);
            sprintf(score_char,SCORE,score);
            DrawText(memDC,score_char,strlen(score_char),&rc,DT_SINGLELINE|DT_CENTER|DT_TOP);
        }
 
        SetTextColor(memDC,STONE);
        DrawText(memDC,press_enter,strlen(press_enter),&rc,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
        SelectObject(memDC,hFontOld);
 
        return;
    }
 
    //画桌面上残留的方块
    hBrushOld = (HBRUSH)SelectObject(memDC,stoneBrush);
    for(i = 0; i < ROWS; i++)
    {
        for(j = 0; j < COLUMS; j++)
        {
            if(table[i][j] == 1)
            {
                x = x0 + j*w;
                y = y0 + i*w;
                Rectangle(memDC,x,y,x+w+1,y+w+1);
            }
        }
    }
 
    SelectObject(memDC,hBrushOld);
 
    //画当前的方块
    hBrushOld = (HBRUSH)SelectObject(memDC,shapeBrush[current]);
    for(i = 0; i < 4; i++)
    {    //取得当前方形中某一单元在桌面上的实际坐标值
        x = x0 + (current_coordinate[i][0] + shapex)*w;
        y = y0 + (current_coordinate[i][1] + shapey)*w;
        if(x < x0 || y < y0)
        {
            continue;
        }
        //绘制实心矩形
        Rectangle(memDC,x,y,x+w+1,y+w+1);
    }
    SelectObject(memDC,hBrushOld);
    //画桌面上的表格线条
    hPenOld = (HPEN)SelectObject(memDC,grayPen);
    for(i = 0; i<= ROWS; i++)
    {
        /*MoveToEx(memDC,x0+i*w,y0,NULL);*/
        MoveToEx(memDC,x0,y0+i*w,NULL);
        LineTo(memDC,x0+COLUMS*w,y0+i*w);
    }
    for(i = 0; i <= COLUMS; i++)
    {
        MoveToEx(memDC,x0+i*w,y0,NULL);
        LineTo(memDC,x0+i*w,y0+ROWS*w);
    }
    SelectObject(memDC,hPenOld);
 
    //画玩家得分
    x0= x0+COLUMS*w +3*w;//设置得分字样在桌面上的偏移位置
    y0=y0+w;
    hFontOld = (HFONT)SelectObject(memDC,smallFont);//选择字体
    SetTextColor(memDC,YELLOW);//设置字体颜色
    sprintf(score_char,SCORE,score);
    /*sprintf(score_char,SCORE,shapex);*/
    /*sprintf(score_char,SCORE,table[29][0]);*/
    TextOut(memDC,x0,y0,score_char,strlen(score_char));//绘制得分
    //画下一个方块
    y0 += w;
    SetTextColor(memDC,STONE);
    TextOut(memDC,x0,y0,"NEXT",4);
    x0 = x0 + w;
    y0 += 2*w;
    hBrushOld = (HBRUSH)SelectObject(memDC,shapeBrush[next]);
    for(i = 0; i < 4; i++)
    {
        x = x0 + shape_coordinate[next][i][0]*w;
        y = y0 + shape_coordinate[next][i][1]*w;
        Rectangle(memDC,x,y,x+w+1,y+w+1);
    }
 
    SelectObject(memDC,hBrushOld);
    //打印帮助信息
    x0 = (COLUMS + 2)*w;
    y0 += 4*w;
    SetTextColor(memDC,GRAY);
    i = 0;
    while(help[i])
    {
        TextOut(memDC,x0,y0,help[i],strlen(help[i]));
        y0 += w;
        i++;
    }
    SelectObject(memDC,hFontOld);
}

20.处理按键

函数名称:key_down

函数功能:处理键盘按下事件。

处理流程:1.如果游戏状态不是运行状态,按回车键是进行暂停/开始游戏的切换键。2.游戏运行状态,按向上键或空格键旋转当前方块,按向左键左移当前方块,按向右键右移当前方块,按向下键下移当前方块。按回车键,来回切换暂停/开始游戏。具体实现如下:

//按键事件处理
void key_down(WPARAM wParam)
{
    //如果游戏状态处理非运行状态,按下回车键,则开始游戏
    if(state != game_run)
    {
        if(wParam == VK_RETURN)
        {
            switch(state)
            {
            case game_start:
                next_shape();
                state = game_run;
                break;
            case game_pause:
                state = game_run;
                break;
            case game_over:
                new_game();
                next_shape();
                state = game_run;
                break;
            }
        }
    }
    else //在游戏运行状态下,响应键盘事件
    {
        switch(wParam)
        {
        case VK_SPACE:  //空格键/向上键则旋转当前方块
        case VK_UP:
            transform();
            break;
        case VK_LEFT:  //左向键左移当前方块
            move_left();
            break;
        case VK_RIGHT: //右向键右移当前方块
            move_right();
            break;
        case VK_DOWN: //下向键下移当前方块
            move_down();
            break;
        case VK_RETURN://回车键暂停当前游戏
            state = game_pause;
            break;
        }
    }
    //重绘客户区
    InvalidateRect(gameWND,NULL,TRUE);
}

21.改变窗口大小

函数名称:resize

函数功能:改变窗口大小时调用的函数。具体实现如下:

void resize()
{
    HDC hdc;
    LOGFONT lf;
    hdc = GetDC(gameWND);
    GetClientRect(gameWND,&clientRC);
    SelectObject(memDC,memBMOld);
    DeleteObject(memBM);
    //重新创建合适的内存位图
    memBM = CreateCompatibleBitmap(hdc,clientRC.right,clientRC.bottom);
    memBMOld = (HBITMAP)SelectObject(memDC,memBM);
    //重新创建合适的大字体和小字体
    DeleteObject(bigFont);
    memset(&lf,0,sizeof(LOGFONT));
    lf.lfWidth = (clientRC.right - clientRC.left) / CHARS_IN_LINE;
    lf.lfHeight = (clientRC.bottom - clientRC.top) /4;
    lf.lfItalic = 1;
    lf.lfWeight = FW_BOLD;
    bigFont = CreateFontIndirect(&lf);
 
    DeleteObject(smallFont);
    lf.lfHeight = clientRC.bottom / (ROWS + 2);
    lf.lfWidth = lf.lfHeight / 2;
    lf.lfItalic = 0;
    lf.lfWeight = FW_NORMAL;
    smallFont = CreateFontIndirect(&lf);
 
    ReleaseDC(gameWND, hdc);
}

22.处理消息

函数名称:WndProc

函数功能:回调函数,用来处理Windows消息。具体实现如下:

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
    case WM_SIZE: //响应改变窗口大小的消息
        resize();
        return 0;
    case WM_ERASEBKGND: //响应重绘背景的消息
        return 0;
    case WM_PAINT: //相应绘制消息
        paint();
        return 0;
    case WM_KEYDOWN: //相应按键消息
        key_down(wParam);
        return 0;
    case WM_DESTROY: //响应销毁窗口的消息
        PostQuitMessage(0);
        return 0;
    }
    //其它消息用Windows默认的消息处理函数处理
    return DefWindowProc(hwnd, message,wParam,lParam);
}

23.初始化

函数名称:initialize

函数功能:初始化内存位图,画笔,字体等资源。具体实现如下:

void initialize()
{
    LOGFONT lf;
    HDC hdc;
    int i;
 
    hdc = GetDC(gameWND);
    GetClientRect(gameWND,&clientRC); //取得窗口客户区大小
    memDC = CreateCompatibleDC(hdc);  //创建内存DC
    //创建内存位图
    memBM = CreateCompatibleBitmap(hdc, clientRC.right,clientRC.bottom);
    //将内存位图选入到内存DC中
    memBMOld = (HBITMAP)SelectObject(memDC,memBM);
    //创建黑色画笔和深灰色画笔
    blackBrush = CreateSolidBrush(BLACK);
    stoneBrush = CreateSolidBrush(STONE);
    //创建每个方块所对应颜色的画笔
    for(i = 0; i < SHAPE_COUNT;i++)
    {
        shapeBrush[i] = CreateSolidBrush(shape_color[i]);
    }
    grayPen = CreatePen(PS_SOLID,1,GRAY);//创建灰色画笔
    memset(&lf,0,sizeof(LOGFONT));
    //创建大字体
    lf.lfWidth = (clientRC.right - clientRC.left) / CHARS_IN_LINE;
    lf.lfHeight = (clientRC.bottom - clientRC.top) /4;
    lf.lfItalic = 1;
    lf.lfWeight = FW_BOLD;
    bigFont = CreateFontIndirect(&lf);
    //创建小字体
    lf.lfHeight = clientRC.bottom / (ROWS + 2);
    lf.lfWidth = lf.lfHeight / 2;
    lf.lfItalic = 0;
    lf.lfWeight = FW_NORMAL;
    smallFont = CreateFontIndirect(&lf);
    
    ReleaseDC(gameWND,hdc);
}

24.释放资源

函数名称:finalize

函数功能:游戏结束时调用该函数释放initialize中创建的资源。具体实现如下:

void finalize()
{
    int i = 0;
    DeleteObject(blackBrush);
    DeleteObject(stoneBrush);
    for(i = 0; i < SHAPE_COUNT; i++)
    {
        DeleteObject(shapeBrush[i]);
    }
 
    DeleteObject(grayPen);
    DeleteObject(bigFont);
    DeleteObject(smallFont);
    SelectObject(memDC,memBMOld);
    DeleteObject(memBM);
    DeleteDC(memDC);
}

25.系统入口函数

函数名称:WinMain

函数功能:Windows程序入口,类似于DOS程序的main函数。具体实现如下:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance,PSTR szCmdLine,int iCmdShow)
{
    MSG msg; //声明一个消息结构体变量
    WNDCLASS wndclass; //声明一个窗口类变量
 
    //初始化窗口信息
    wndclass.style  = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL,IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = APP_NAME;
    //注册窗口类
    RegisterClass(&wndclass);
    //创建窗口
    gameWND = CreateWindow(APP_NAME,
        APP_TITLE,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,NULL,
        hInstance,NULL);
    //初始化游戏基本资源
    initialize();
    //显示窗口
    ShowWindow(gameWND, iCmdShow);
    UpdateWindow(gameWND); //刷新窗口
    //创建游戏
    new_game();
    for(;;)//进入消息循环
    {
        if(state == game_run)
        {
            run_game();
        }
 
        if(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
        {
            if(GetMessage(&msg,NULL,0,0))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }else{
                break;
            }
        }
    }
 
    finalize();
 
    return msg.wParam;
}
至此游戏的基本功能已经实现。

5.系统操作过程

F5游戏运行后,首先进入欢迎主界面,如图:

image

在欢迎主界面中按任意键进入游戏,游戏界面如图:

image

游戏结束界面如图:

image

6.总结与Bug记录

Bug.1

//画桌面上的表格线条
    hPenOld = (HPEN)SelectObject(memDC,grayPen);
    for(i = 0; i<= ROWS; i++)
    {
        /*MoveToEx(memDC,x0+i*w,y0,NULL);*/
        MoveToEx(memDC,x0,y0+i*w,NULL);
        LineTo(memDC,x0+COLUMS*w,y0+i*w);
    }

Bug.2

显示出了SCORE字样但是没有显示得分情况,

将#define SCORE "SCORE  %4"改成:#define SCORE "SCORE  %4d"

Bug.3

方块旋转有问题

void turn_right()
{
    int i = 0;
    int x, y;
    for(i = 0; i< 4; i++)
    {
        x = current_coordinate[i][0];
        y = current_coordinate[i][1];
        current_coordinate[i][0] = -y; //没有写
        current_coordinate[i][1] = x;
    }
}

posted @ 2013-11-17 22:41  AI Algorithms  阅读(1014)  评论(0编辑  收藏  举报