贪吃蛇游戏涉及的问题

先上效果图,源码见github:https://github.com/xianshuihu/GreedySnake,此篇文章主要分析在编码时涉及到的相关问题:

 

 

1. Windows API 函数

实现这类游戏的基础是学会调用windows API函数,具体使用可查看Microsoft给出的文档Console Functions

 

GetStdHandle

Retrieves a handle to the specified standard device (standard input, standard output, or standard error).

按文档解释即为“从特定设备(标准输入、标准输出、标准错误)中取得一个句柄”,而句柄是用来标识不同设备的数值,下面给出函数原型:

HANDLE WINAPI GetStdHandle(
  _In_ DWORD nStdHandle
);

其中参数nStdHandle可取值为STD_INPUT_HANDLE,STD_OUTPUT_HANDLE,STD_ERROR_HANDLE,事实上是通过宏定义,三者分别代表DWORD类型的-10,-11,-12。在贪吃蛇游戏中,要获取输出设备的句柄,以作为下面两个函数的参数时,执行了如下操作:

HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);

 

SetConsoleTextAttribute

Sets the attributes of characters written to the console screen buffer by the WriteFile or WriteConsole function, or echoed by the ReadFile or ReadConsole function. This function affects text written after the function call.

可解释为“设置那些通过函数被写入控制台屏幕缓冲区或者从缓冲区被读出显示的字符的属性”,事实上,DOS中对于各种字符的色彩调控都是调用该API函数,函数原型为:

BOOL WINAPI SetConsoleTextAttribute(
  _In_ HANDLE hConsoleOutput,
  _In_ WORD   wAttributes
);

第一个参数即为通过GetStdHandle获取到的设备句柄,第二个参数是属性,这种WORD类型的值也可以宏定义形式出现,游戏中具体应用如下:

void set_color(int num)
{
    HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hout, num);
}

 

SetConsoleCursorPosition

Sets the cursor position in the specified console screen buffer.

可解释为“在指定的控制台屏幕缓冲区中设置光标位置”,游戏中实现出的动画效果都是对相关坐标位置的显示进行精确控制的结果,函数原型为:

BOOL WINAPI SetConsoleCursorPosition(
  _In_ HANDLE hConsoleOutput,
  _In_ COORD  dwCursorPosition
);

在这里第二个参数比较特别,COORD这种结构指定了光标位置,作为坐标其行列值都必须在控制台屏幕缓冲区边界范围内,游戏中应用如下:

void set_location(int valuex, int valuey)
{
    HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD crd;
    crd.X = valuex * 2;
    crd.Y = valuey;
    SetConsoleCursorPosition(hout, crd);
}

 

 

 

2. 随 机 函 数

游戏中随机函数的运用很常见,如食物的随机出现,下面给出在head~tail范围内随机整数和随机浮点数的random函数写法:

// [head, tail)
double random(int head, int tail)
{ return head + (tail-head)*rand()/(RAND_MAX+1.0); }
// [head, tail]
int random(int head, int tail)
{ return head + rand()%(tail-head+1); }

The rand function returns a pseudorandom(伪随机) integer in the range 0 to RAND_MAX (32767,即0x7fff). Use the srand function to seed the pseudorandom-number generator before calling rand.

根据官方文档,可知调用rand函数生成伪随机数之前需使用srand函数给伪随机数生成器播种,而默认情况下未设随机数种子,rand()被调用时自动设置种子值为1,所以通过rand函数生成的随机数序列每次都会完全对应。为了避免这种情况,C提供了srand函数改变种子值,并采用time(NULL)或time(0)作为种子,它是从1970年1月1日到现在的秒数,这样就能使随机数种子不同,具体写法为:

srand((unsigned int)time(NULL));

另外注意下面这种情况,由于程序运行极快,设置随机数种子这个操作如果放在循环内部,不会得到一个随机数序列:

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    srand((unsigned int)time(NULL));   // right way
    for(int i=0; i<10; i++)
    {
        printf("%5d ", rand());
    }
    printf("\n");
    for (int j=0; j<10; j++)           // wrong way
    {
        srand((unsigned int)time(NULL));
        printf("%5d ", rand());
    }
    return 0;
}

 

 

 

3. 动态分配存储空间

区别malloc,calloc,realloc,下面给出函数声明:

void *malloc(size_t size);
void *calloc(size_t num, size_t size);
void *realloc(void *memblock, size_t size);

三者都在stdlib.h函数库内,空间请求成功返回系统非配地址,失败返回NULL。

char *p;
p = (char *)malloc(sizeof(char) * 20);

char *p;
p = (char *)calloc(20, sizeof(char));

char *p;
p = (char *)malloc(sizeof(char) * 20);
p = (char *)realloc(p, sizeof(char) * 40);

1.从上面具体用法可以看出,malloc用于申请一段新的地址,参数size为所需内存空间长度。

2.calloc与malloc类似,参数num为申请地址的元素个数,参数size为单个元素长度;二者的主要区别在于calloc会将所分配内存空间中的每一位都初始化为零,而malloc不会初始化内存空间,空间中保存的是随机性的垃圾数据;另外需要解释的是,理论上指针的算术运算只能在指定的数组中进行,但在实际使用中,即使C编译程序遵循这种规定,但许多C程序还是冲破了这种限制,malloc不能像calloc返回一个数组,只能返回一个对象,但它所分配的内存空间仍能供一个数组使用,对realloc函数来说同样如此,所以,除了是否初始化所分配的内存空间这一点之外,绝大多数程序员认为以下这函数调用方式没有区别。下面给出贪吃蛇游戏中两种等效写法,含义是为蛇的躯体增加一个节点snake[i],而snake[i]是指向一个长度为2的数组的首地址,因为该数组中包括横纵坐标snake[i][0]和snake[i][1]两个元素:

snake[i] = (int *)malloc(sizeof(int) * 2);
snake[i] = (int *)calloc(2, sizeof(int));

3.realloc是给一个已分配了地址的指针重新分配空间,参数size是重新申请的空间长度。

posted on 2015-07-17 19:17  huashunli  阅读(560)  评论(0编辑  收藏  举报

导航