描述程序对象
设计程序时,选择合理的数据类型也是重要的一环,这里我们以俄罗斯方块游戏为起点,探索数据的表达。
说到俄罗斯方块,我想你脑海中肯定已经冒出了形形色色的形状,甚至低估着:嗯,我讨厌那个N状方块,它经常让我无所适从。回到我们的讨论,现在的首要问题是这些方块在程序中该如何表示?快速的回想一遍所知的形状,你会发现最宽的是横条,它有4个小方块的宽度;最高的是竖条,它有4个小方块的高度。而L形状虽然也由4个小方块构成,但是仅占据了2列宽度。
显然,如果我们选择最宽与最高的维度,构建成一个网格,此时任意一个形状都可以容纳在其中。对于当前的方块而言,所在的小方格是可见的,其它的方格是隐藏的,如果我们用1与0来表示这两种状态,可以得到下面这样的图例。

第一眼看去,对于这样4x4的格子,我们可以使用一个二维数组来表达,将形状所在的区域置位1即可,我们先排除掉浮点类型,因为它们的相等性比较不直接。
/*
0 ■ 0 0
0 ■ ■ 0
0 0 ■ 0
0 0 0 0
*/
int shape[4][4] =
{
0, 1, 0, 0,
0, 1, 1, 0,
0, 0, 1, 0,
0, 0, 0, 0
}
二维数组意味着可能需要双层循环才能遍历出,也许你喜欢一维数组的易用性,也可能这样表达。
/*
■ ■ ■ ■
0 0 0 0
0 0 0 0
0 0 0 0
*/
int shape[16] = { 1, 1, 1, 1, 0 };
现在从内存花费角度考虑:使用int数组表达一个形状将占用64个字节,而使用char数组仅需16个字节,这一举使我们节省了75%的内存消耗!
#include <stdio.h>
int main()
{
/*
■ 0 0 0
■ 0 0 0
■ ■ 0 0
0 0 0 0
*/
char shape[16] =
{
1, 0, 0, 0,
1, 0, 0, 0,
1, 1
};
printf("\n\n");
for (char i = 0; i < 16; i++)
{
if (i % 4 == 0)
putchar('\t');
if (shape[i])
printf("■");
if ((i + 1) % 4 == 0)
putchar('\n');
}
return 0;
}

我们还可以在内存消耗方面进行优化,方法是通过bit位来存储方块的信息,这样一个形状仅需2个字节就能表达(我们将这16位每4位看作一行,就形成了4x4网格),此时对于形状L分解如下:
| BYTE-0 BYTE-1
--------|------------------------
BIN | 10001000 11000000
DEC | 136 192
HEX | 88 C0
---------------------------------
用代码可以这样表示:
char shapeL[2] = { 0x88, 0xC0 };
当你尝试遍历shapeL时,你会发现有点麻烦,因为数据被分散到两个离散量中,也许short是更加合适的选择。
short shapeL = 0x88C0;
目前我们只是站在具体形状的角度描述程序如何表达所需的对象。站在设计的角度看,方块还需要一个颜色的表达,这样程序会更美观;站在策划的角度看,还需要记录每种方块出现的次数,以便在玩家急需出现竖条时,而保证它久久不会出现。如你所见,不同的层次对所需要的表达不尽相同,现在我们则需要一个表达形状的抽象概念,这可以由结构体实现。如:
struct shape
{
short box;
COLOR color;
int count; // 已出现次数
};
示例程序并没有表达所有可能的属性,仅是附加了色彩表示。在显示一个方块前,先使用system()函数清除上次屏幕输出,然后绘图,接下来等待你按下任意键以输出下一个形状,直到循环结束。
```C
#include <stdio.h>
#include <stdlib.h>
#define MAX_SHAPES 6
// 色彩枚举
typedef enum COLOR
{
BLUE = 31,
GREEN = 175,
RED = 203,
YELLOW = 224,
PURPLE = 223,
CYAN = 178,
BROWN = 100,
} COLOR;
// 方块形状抽象
typedef struct SHAPE
{
short box;
COLOR color;
} SHAPE, *PSHAPE;
// 形状数组
SHAPE g_shapes[MAX_SHAPES] =
{
{ 0x88C0, RED },
{ 0x8c40, BROWN },
{ 0x4E00, CYAN },
{ 0xF000, YELLOW },
{ 0xCC00, GREEN },
{ 0x8888, PURPLE }
};
// 设置控制台色彩
void set_console_color(int color)
{
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, color);
}
// 绘制方块
void draw_tetris(int x, int y, PSHAPE shape)
{
// 定位起始行到y处
for (int y1 = 0; y1 < y; ++y1)
printf("\n");
// 绘制方块
for (int i = 0, mask = 1 << 15; i < 16; i++)
{
if (i % 4 == 0)
{
if (i > 1)
printf("\n");
// 定位起始列到x处
set_console_color(0);
for (int x1 = 0; x1 < x; ++x1)
printf(" ");
}
if (mask & shape->box)
{
set_console_color(shape->color);
printf("■");
}
else
{
set_console_color(0);
putchar(' ');
}
mask >>= 1;
}
}
int main()
{
for (int i = 0; i < MAX_SHAPES; i++)
{
// 调用system函数清除屏幕显示
system("cls");
// 显示当前俄罗斯方块
draw_tetris(15, 2, &g_shapes[i]);
// 按下任意键显示下一个方块
getch();
}
return 0;
}
