大二上学期前两周的课程设计写(更多的是借鉴)了一个500多行的俄罗斯方块游戏,毕竟也是花了两天时间把被人的看懂,然后花了两天多的时间敲出来,又花了两天时间写了4000多字的项目报告,所有有必要在博客中写下来以留作纪念。
struct BLOCK
{
int dir[4]; // 方块的四个旋转状态
COLORREF color; // 方块的颜色
} Blocks[7] = {
{0x0F00, 0x4444, 0x0F00, 0x4444, RED}, // I
{0x0660, 0x0660, 0x0660, 0x0660, BLUE}, // 口
{0x4460, 0x02E0, 0x0622, 0x0740, MAGENTA}, // L
{0x2260, 0x0E20, 0x0644, 0x0470, YELLOW}, // 反L
{0x0C60, 0x2640, 0x0C60, 0x2640, CYAN}, // Z
{0x0360, 0x4620, 0x0360, 0x4620, GREEN}, // 反Z
{0x4E00, 0x4C40, 0x0E40, 0x4640, BROWN} // T
};
用十六进制数转化为16位的二进制数,在将16位的二进制看成4*4的矩阵,1构成的图形即方块的形状。上面的代码表示7中形状的方块,每个方块有4中旋转状态和一种颜色。
for(i = 0; i < 16; i++, b <<= 1)
{
if (b & 0x8000)
{
x = block.x + i % 4;
y = block.y - i / 4;
if (y < HIG)
DrawUnit(x, y, Blocks[block.id].color);
}
}
上面的代码将十六进制代表的4*4矩阵中1所在的位置计算出来,发送给DrawUnit函数,DrawUnit函数将所有的1画出了,就形成了完整的方块。
if (b & 0x8000) b(十六进制数)和0x8000做与运算是为了测试最高位是否为1。每一次都是取b的最高一位来判断(b & 0x8000,这是位且,屏蔽了低位)。然后再用b <<= 1; 把次高位移到最高位。
游戏截图:
源代码(在VC++6.0中可运行,需要用到easyX图形库):
#include <graphics.h>
#include <conio.h>
#include <time.h>
#include <stdio.h>
//////////////////////////////////////////////////
//定义常量、全局变量、结构体、枚举
//////////////////////////////////////////////////
#define WID 10 //宽为10个单元
#define HIG 20 //高为20个单元
#define UNIT 30 //单元方块的大小
struct BLOCK
{
int dir[4]; // 方块的四个旋转状态
COLORREF color; // 方块的颜色
} Blocks[7] = {
{0x0F00, 0x4444, 0x0F00, 0x4444, RED}, // 1
{0x0660, 0x0660, 0x0660, 0x0660, BLUE}, // 口
{0x4460, 0x02E0, 0x0622, 0x0740, MAGENTA}, // L
{0x2260, 0x0E20, 0x0644, 0x0470, YELLOW}, // 反L
{0x0C60, 0x2640, 0x0C60, 0x2640, CYAN}, // Z
{0x0360, 0x4620, 0x0360, 0x4620, GREEN}, // 反Z
{0x4E00, 0x4C40, 0x0E40, 0x4640, BROWN} // T
};
// 定义当前方块、下一个方块的信息
struct BLOCKID
{
int id; // 方块 ID
char x, y; // 方块在游戏区中的坐标
unsigned int dir:2; //方向 dir:2 表示dir这个成员只用2个bit.
}NowBlock, NextBlock;
enum CMD
{
ROTATE, LEFT, RIGHT, DOWN, SINK, QUIT
};
//时间变量
long int old_time, new_time;
//固定方块区
char FixBlock[WID][HIG] = {0};
//分数
int score;
//////////////////////////////////////////////////
//函数声明
//////////////////////////////////////////////////
void Init();
void NewGame();
void CreatBlock();
void DrawBlock(BLOCKID block);
void DrawUnit(int x, int y, COLORREF color);
void ClearBlock(BLOCKID block);
void ClearUnit(int x, int y, COLORREF color = BLACK);
CMD GetCommand();
void SendCommand(CMD cmd);
void Go_Down();
void Go_Left();
void Go_Right();
void Go_Sink();
void Go_Rotate();
bool CheckBlock(BLOCKID temp);
void GameOver();
void Quit();
//建立游戏窗口
void Init()
{
//建立窗口并设置大小
initgraph(500, 645);
setbkcolor(TRANSPARENT); // 设置图案填充的背景色为透明
setorigin(20, 20); // 设置坐标原点
// 绘制游戏区边界
rectangle(-1, -1, WID * UNIT, HIG * UNIT); //大矩形
line((WID + 1) * UNIT -1, -1,(WID + 1) * UNIT -1, HIG * UNIT); //竖直线
line((WID + 1) * UNIT -1, -1, 550, -1); //横线1
line((WID + 1) * UNIT -1, 4 * UNIT, 550, 4 * UNIT);//横线2
line((WID + 1) * UNIT -1, 8 * UNIT, 550, 8 * UNIT);//横线3
line((WID + 1) * UNIT -1, HIG * UNIT,550, HIG * UNIT);//横线4
//插入操作说明
settextstyle(22, 0, _T("黑体"));
outtextxy((WID + 2) * UNIT, 9 * UNIT, _T("操作说明"));
outtextxy((WID + 2) * UNIT, 11 * UNIT, _T("上:旋转"));
outtextxy((WID + 2) * UNIT, 12.5 * UNIT, _T("左:左移"));
outtextxy((WID + 2) * UNIT, 14 * UNIT, _T("右:右移"));
outtextxy((WID + 2) * UNIT, 15.5 * UNIT, _T("下:加速"));
outtextxy((WID + 2) * UNIT-8, 17 * UNIT, _T("空格:沉底"));
outtextxy((WID + 2) * UNIT-4, 18.5 * UNIT, _T("ESC:退出"));
settextstyle(36, 0, _T("隶书"));
outtextxy((WID + 2) * UNIT, 4.5 * UNIT, _T("得分"));
//开始新游戏
NewGame();
}
//开始新游戏
void NewGame()
{
// 清空游戏区
setfillcolor(BLACK);
solidrectangle(0, 0, WID * UNIT - 1, HIG * UNIT - 1);//实心的矩形
ZeroMemory(FixBlock, WID * HIG);
//FixBlock[WID][HIG] = 0;
//memset((FixBlock),0,(WID * HIG));
/*
ZeroMemory函数
void ZeroMemory( PVOID Destination, SIZE_T Length );
参数
Destination :指向一块准备用0来填充的内存区域的开始地址。
Length :准备用0来填充的内存区域的大小,按字节来计算。
*/
score = 0; //初始化分数
char s[10];
sprintf(s, "%d", score);
//显示分数
settextstyle(40, 0, _T("黑体"));
setfillcolor(BLACK);
setlinecolor(BLACK);
fillrectangle((WID + 1) * UNIT, 6 * UNIT, (WID + 10) * UNIT, 8 * UNIT - 1);
outtextxy((WID + 2) * UNIT + 25, 6 * UNIT, _T(s));
//初始化下一个方块
srand((unsigned int)time(NULL));
NextBlock.id = rand() % 7;
NextBlock.x = WID + 1;
NextBlock.y = HIG - 1;
NextBlock.dir = rand() % 4;
//创建新的方块
CreatBlock();
}
void CreatBlock()
{
NowBlock.x = 3;
NowBlock.y = HIG + 2;
NowBlock.id = NextBlock.id;
NowBlock.dir = NextBlock.dir;
NextBlock.id = rand() % 7;
NextBlock.dir = rand() % 4;
// 绘制新方块
DrawBlock(NowBlock);
// 绘制下一个方块
setfillcolor(BLACK);
solidrectangle((WID + 1) * UNIT, 0, (WID + 5) * UNIT - 1, 4 * UNIT - 1);
DrawBlock(NextBlock);
// 设置计时器,用于判断自动下落
old_time = GetTickCount();
}
//画方块
void DrawBlock(BLOCKID block)
{
int b = Blocks[block.id].dir[block.dir];
int x, y;
int i;
for(i = 0; i < 16; i++, b <<= 1)
{
if (b & 0x8000) //和0x8000做与运算是为了测试最高位是否为1.
{
x = block.x + i % 4;
y = block.y - i / 4;
if (y < HIG)
DrawUnit(x, y, Blocks[block.id].color);
}
}
/*
和0x8000做与运算是为了测试最高位是否为1.
b左移一位的意义在与,b这个值以二进制来看,是否还有某位为1.
把每个俄罗斯图形都看成是一个4x4的矩阵,
可以在纸上画个4x4的方块图,
从左边第一列,从上往下开始,依次开始编码,
如果某个块对应俄罗斯图为实心块,编码时为1,否则为0.
从for(int i=0; i<16; i++)可以看出,要画16次。
每一次都是取b的最高一位来判断(b & 0x8000,这是位且,屏蔽了低位)。
画完后再用b <<= 1; 把次高位移到最高位。
*/
}
void ClearBlock(BLOCKID block)
{
int b = Blocks[block.id].dir[block.dir];
int x, y;
int i;
for(i = 0; i < 16; i++, b <<= 1)
{
if (b & 0x8000) //和0x8000做与运算是为了测试最高位是否为1.
{
x = block.x + i % 4;
y = block.y - i / 4;
if (y < HIG)
ClearUnit(x, y, Blocks[block.id].color);
}
}
}
void DrawUnit(int x, int y, COLORREF color)
{
// 计算单元方块对应的屏幕坐标
int left = x * UNIT;
int top = (HIG - y - 1) * UNIT;
int right = (x + 1) * UNIT - 1;
int bottom = (HIG - y) * UNIT - 1;
// 画单元方块
setfillcolor(color);
setlinecolor(WHITE);
fillrectangle(left + 2, top + 2, right - 2, bottom - 2);
}
void ClearUnit(int x, int y, COLORREF color)
{
// 计算单元方块对应的屏幕坐标
int left = x * UNIT;
int top = (HIG - y - 1) * UNIT;
int right = (x + 1) * UNIT - 1;
int bottom = (HIG - y) * UNIT - 1;
setfillcolor(BLACK);
solidrectangle(x * UNIT, (HIG - y - 1) * UNIT, (x + 1) * UNIT - 1, (HIG - y) * UNIT - 1);
}
CMD GetCommand()
{
CMD cmd;
while(true)
{
new_time = GetTickCount();
// 不控制,默认自动下落一格
if (new_time - old_time > 500)
{
old_time = new_time;
return DOWN;
}
// 如果有按键,返回按键对应的功能
if (kbhit())
{
switch(getch())
{
case 72:
cmd = ROTATE;
return cmd;
case 80:
cmd = DOWN;
return cmd;
case 75:
cmd = LEFT;
return cmd;
case 77:
cmd = RIGHT;
return cmd;
case 27: //ESC键
cmd = QUIT;
return cmd;
case ' ':
cmd = SINK;
return cmd;
}
}
}
}
void SendCommand(CMD cmd)
{
switch(cmd)
{
case DOWN:
Go_Down();
break;
case LEFT:
Go_Left();
break;
case RIGHT:
Go_Right();
break;
case ROTATE:
Go_Rotate();
break;
case SINK:
Go_Sink();
break;
case QUIT:
// 按退出时,显示对话框咨询用户是否退出
HWND wnd = GetHWnd();
if (MessageBox(wnd, _T("您确定要退出游戏吗?"), _T("提醒"), MB_OKCANCEL | MB_ICONQUESTION) == IDOK)
Quit();
}
}
void Go_Down()
{
BLOCKID temp = NowBlock;
temp.y--;
if (CheckBlock(temp))
{
ClearBlock(NowBlock);
NowBlock.y--;
DrawBlock(NowBlock);
}
else
Go_Sink(); //不能下移时,执行沉淀操作
}
void Go_Left()
{
BLOCKID temp = NowBlock;
temp.x--;
if (CheckBlock(temp))
{
ClearBlock(NowBlock);
NowBlock.x--;
DrawBlock(NowBlock);
}
}
void Go_Right()
{
BLOCKID temp = NowBlock;
temp.x++;
if (CheckBlock(temp))
{
ClearBlock(NowBlock);
NowBlock.x++;
DrawBlock(NowBlock);
}
}
//沉淀、消行
void Go_Sink()
{
int x, y, i, n;
int m = 0; //可消的行数
ClearBlock(NowBlock);
BLOCKID temp = NowBlock;
temp.y--;
while (CheckBlock(temp))
{
NowBlock.y--;
temp.y--;
}
DrawBlock(NowBlock);
//固定方块
int b = Blocks[NowBlock.id].dir[NowBlock.dir];
for (i = 0; i < 16; i++, b = b << 1)
{
if (b & 0x8000)
{
x = NowBlock.x + i % 4;
y = NowBlock.y - i / 4;
if (y >= HIG)
{
GameOver();
return;
}
else
FixBlock[x][y] = 1;
}
}
//4位用来标记方块涉及的 4 行是否有消除行为
char remove = 0; //标记是否要消掉行
for (y = NowBlock.y; y >= 0; y--)
{
n = 0;
for (x = 0; x < WID; x++)
{
if (FixBlock[x][y] == 1)
n++;
}
if (n == WID)
{
m++;
remove = remove | (1 << (NowBlock.y - y));
setfillcolor(BLACK);
fillrectangle(0, (HIG - y - 1) * UNIT ,
WID * UNIT - 1, (HIG - y) * UNIT - 2);
}
}
if(m != 0)
{
//更新分数并显示;
if (m == 1)
score = score + 10;
else if (m == 2)
score = score + 30;
else if (m == 3 )
score = score + 50;
else
score = score + 100;
char s[10];
sprintf(s, "%d", score);
setfillcolor(BLACK);
setlinecolor(BLACK);
fillrectangle((WID + 1) * UNIT, 6 * UNIT, (WID + 10) * UNIT, 8 * UNIT - 1);
settextstyle(40, 0, _T("黑体"));
outtextxy((WID + 2) * UNIT + 15, 6 * UNIT, _T(s));
}
if (remove)
{
Sleep(200);
// 擦掉刚才标记的行
IMAGE img; //C++类
for(i = 0; i < 4; i++, remove >>= 1)
{
if (remove & 1)
{
for(y = NowBlock.y - i + 1; y < HIG; y++)
for(x = 0; x < WID; x++)
{
FixBlock[x][y - 1] = FixBlock[x][y];
FixBlock[x][y] = 0;
}
//调用getimage()保存屏幕图像
getimage(&img, 0, 0, WID * UNIT, (HIG - (NowBlock.y - i + 1)) * UNIT);
//用函数putimage()输出getimage()保存的屏幕图像。
putimage(0, UNIT, &img);
}
}
}
CreatBlock();
}
void Go_Rotate()
{
// 获取可以旋转的 x 偏移量
int dx;
BLOCKID temp = NowBlock;
temp.dir++;
if (CheckBlock(temp))
{
dx = 0;
goto rotate;
}
temp.x = NowBlock.x - 1;
if (CheckBlock(temp))
{
dx = -1;
goto rotate;
}
temp.x = NowBlock.x + 1;
if (CheckBlock(temp))
{
dx = 1;
goto rotate;
}
temp.x = NowBlock.x - 2;
if (CheckBlock(temp))
{
dx = -2;
goto rotate;
}
temp.x = NowBlock.x + 2;
if (CheckBlock(temp))
{
dx = 2;
goto rotate;
}
return;
rotate:
// 旋转
ClearBlock(NowBlock);
NowBlock.dir++;
NowBlock.x += dx;
DrawBlock(NowBlock);
}
bool CheckBlock(BLOCKID temp)
{
int b = Blocks[temp.id].dir[temp.dir];
int x, y, i;
for (i = 0; i < 16; i++, b = b << 1)
{
if (b & 0x8000)
{
x = temp.x + i % 4;
y = temp.y - i / 4;
if ((x < 0) || (x >= WID) || (y < 0))
return false;
if ((y < HIG) && (FixBlock[x][y]))
return false;
}
}
return true;
}
// 结束游戏
void GameOver()
{
HWND wnd = GetHWnd();
if (MessageBox(wnd, _T("游戏结束。\n您想重新来一局吗?"),
_T("游戏结束"), MB_YESNO | MB_ICONQUESTION) == IDYES)
NewGame();
else
Quit();
}
// 退出游戏
void Quit()
{
closegraph();
exit(0);
}
//主函数
int main(void)
{
Init();
CMD cmd;
while(true)
{
cmd = GetCommand();
SendCommand(cmd);
}
getch();
closegraph();
return 0;
}
EasyX下载地址:http://easyx.cn/
参考网站:http://codebus.easyx.cn/krissi/post/tetris