目录
前言
一、游戏截图和全部代码
1.游戏截图
2.源代码
头文件代码
cpp文件代码
二、easyX库安装
三、宏定义、变量的说明
1.方块像素
2.游戏地图区域
3.预生成方块区域
4.玩家数据结构
5.所有方块数据库
6.当前控制方块数据结构
四、主函数tetrisrun()
五、代码和所有函数说明
1. tetrisInit()
2.tetrisDraw()
3.tetrisNewBlock() 函数
4.tetrisMoveUp() 函数
5.tetrisMoveDown() 函数
6.tetrisMoveLeft() 函数
7.tetrisMoveRight() 函数
8.tetrisLoadBlock() 函数
9.tetrisIsOver() 函数
10.tetrisQuit() 函数
11.tetrisKeyHandle() 函数
12.tetrisReset() 函数
13.tetrisRemove() 函数
总结
前言
使用easyX库,基于c/c++,实现俄罗斯方块小游戏。
作者使用的是VS2010版本,VS2019版本也可用。
一、游戏截图和全部代码
1.游戏截图
2.源代码
可直接拷贝,并运行俄罗斯方块小游戏
头文件代码
#pragma once
#define GAMEMAP1 200 // 预生成方块区域
#define GAMEMAP2 10
#define GAMEMAP3 500
#define GAMEMAP4 610
#define GAMEMAP5 40 // 游戏地图区域
#define GAMEMAP6 50
#define GAMEMAP7 140
#define GAMEMAP8 130
#define GAMEGUIDE1 40 // 按键说明
#define GAMEGUIDE2 300
#define GAMEGUIDE3 150
#define GAMEGUIDE4 440
#define GAMELEVEL1 30 // 游戏等级区域
#define GAMELEVEL2 160
#define GAMERANK1 30 // 游戏rank分数
#define GAMERANK2 190
#define TETRISWIDTH 15 // 游戏地图宽
#define TETRISHEIGHT 30 // 游戏地图高
#define BLOCKTYPE 8 // 方块种类数
#define BLOCKSTYLE 4 // 每种方块的样式数
#define BLOCKSPACE 20 // 方块占用空间 5 x 4
#define SIDELENGTH 20 // 方块边长 20 像素
#define ESC 27
#define CLEARKEY(); { while(_kbhit()) { _getch(); } } // 清除按键输入缓存
enum BLOCKCOLER
{
BLOCKNONE,
BLOCKBLUE,
BLOCKGREEN,
BLOCKCYAN,
BLOCKRED,
BLOCKMAGENTA,
BLOCKBROWN,
BLOCKYELLOW,
};
typedef struct _GAME_TETRIS
{
int ttime; // 计时
int ctime; // 计时
int level; // 当前游戏等级
int rank; // 当前游戏分数
int key; // 玩家按键值
int blockType; // 当前方块类型
int blockStyle; // 当前方块朝向
bool moveFlag; // 移动标识,标识为1时,移动当前方块
bool newBlockFlag; // 载入新方块标识,当标识为真时,将预生成的随机方块载入地图
bool gameOverFlag; // 游戏结束标识
}GAME_TETRIS;
typedef struct _GAME_PREBLOCK
{
int tetrisPreBlock[BLOCKSPACE]; // 预生成的方块数组
int blockType,blockStyle; // 预生成方块的类型和朝向
}GAME_PREBLOCK;
typedef struct _GAME_MOVE_BLOCK
{
int blockSite[4][2]; // 4个方块格坐标
int blockColor; // 方块颜色
}GAME_MOVE_BLOCK;
void tetrisrun(void);
void tetrisInit(void);
void tetrisDraw(void);
void tetrisNewBlock(void);
void tetrisMoveUp(void);
void tetrisMoveDown(void);
void tetrisMoveLeft(void);
void tetrisMoveRight(void);
void tetrisLoadBlock(void);
void tetrisIsOver(void);
void tetrisQuit(void);
void tetrisKeyHandle(void);
void tetrisReset(void);
void tetrisRemove(void);
cpp文件代码
#include "stdafx.h"
#include "tetrisnew.h"
#include<graphics.h>
#include<conio.h>
#include<stdio.h>
#include<time.h>
// 所有方块数组库
const int block[BLOCKTYPE][BLOCKSTYLE][BLOCKSPACE] =
{
// 总共有8种方块,每种方块有4种样式
// 'I'形方块
{
{
BLOCKNONE, BLOCKBLUE, BLOCKBLUE, BLOCKBLUE, BLOCKBLUE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE
},
{
BLOCKBLUE, BLOCKBLUE, BLOCKBLUE, BLOCKBLUE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE
}
},
// 'Z'形方块
{
{
BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKGREEN, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKGREEN, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// 反'Z'形方块
{
{
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// 'T'形方块
{
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// 'T'形方块 X 2 增加T形方块出现概率
{
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// '田'形方块
{
{
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// 'L'形方块
{
{
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKBROWN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKBROWN, BLOCKBROWN,
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKBROWN, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKBROWN, BLOCKBROWN, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// 反'L'形方块
{
{
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKYELLOW,BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKYELLOW,BLOCKYELLOW,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKYELLOW,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKYELLOW,BLOCKYELLOW,BLOCKYELLOW,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
}
};
// 游戏实时画面
int tetrisMap[TETRISHEIGHT][TETRISWIDTH] =
{
};
GAME_TETRIS player; // 玩家数据
GAME_PREBLOCK preBlock; // 预生成方块数据
GAME_MOVE_BLOCK moveBlock; // 当前控制方块数据
int main(void)
{
tetrisrun();
return 0;
}
void tetrisrun(void)
{
tetrisInit();
while(1)
{
player.ctime = GetTickCount();
if( (player.ctime - 500 + 30*player.level) > player.ttime )
{
player.moveFlag = TRUE;
player.ttime = GetTickCount();
}
if(player.moveFlag)
{
player.moveFlag = FALSE;
tetrisMoveDown();
tetrisDraw();
}
if(_kbhit())
{
player.key = _getch();
tetrisKeyHandle();
}
if(player.newBlockFlag)
{
player.newBlockFlag = FALSE;
tetrisLoadBlock();
tetrisRemove();
tetrisNewBlock();
tetrisIsOver();
}
if(player.gameOverFlag)
{
player.gameOverFlag = FALSE;
break;
}
}
}
void tetrisInit(void)
{
int i,j,k;
HWND window = initgraph(510, 620);
SetWindowText(window, "俄罗斯方块 - by耒阳阿杰");
//SetWindowPos(window,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
rectangle(GAMEMAP1 - 2,GAMEMAP2 - 2,GAMEMAP3 + 2,GAMEMAP4 + 2);
rectangle(GAMEMAP5 - 2,GAMEMAP6 - 2,GAMEMAP7 + 2,GAMEMAP8 + 2);
rectangle(GAMEGUIDE1 - 5,GAMEGUIDE2 - 5,GAMEGUIDE3 + 5,GAMEGUIDE4 + 5);
outtextxy(GAMEMAP5 + 10 ,GAMEMAP6 - 26,"下个方块");
settextcolor(GREEN);
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 ,"W :改变形状");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 25 ,"A :方块左移");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 50 ,"S :方块右移");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 75 ,"D :方块下移");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 100 ,"R :重新开始");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 125 ,"ESC:退出游戏");
memset ( &player, 0, sizeof ( GAME_TETRIS ) );
memset ( &preBlock, 0, sizeof ( GAME_PREBLOCK ) );
memset(tetrisMap,0,sizeof(tetrisMap));
player.ttime = GetTickCount();
srand((unsigned)time(NULL));
i = (rand() + 1) % BLOCKTYPE;
j = rand() % BLOCKSTYLE;
for(k = 0; k < BLOCKSPACE;k++)
{
preBlock.tetrisPreBlock[k] = block[i][j][k];
}
preBlock.blockType = i;
preBlock.blockStyle = j;
tetrisNewBlock();
}
void tetrisDraw(void)
{
int i,j;
int x,y;
char ch[20];
settextstyle(20,12,"宋体");
settextcolor(CYAN);
if(player.level < 10)
{
sprintf_s(ch, "%s%d","LEVEL:", player.level);
}
else
{
sprintf_s(ch, "%s","LEVEL:MAX");
}
outtextxy(GAMELEVEL1,GAMELEVEL2,ch);
settextcolor(LIGHTBLUE);
sprintf_s(ch, "%s%d","RANK:", player.rank);
outtextxy(GAMERANK1,GAMERANK2,ch);
setfillcolor(BLACK);
solidrectangle(GAMEMAP5,GAMEMAP6,GAMEMAP7,GAMEMAP8);
for(i = 0; i < BLOCKSPACE; i++)
{
switch(preBlock.tetrisPreBlock[i])
{
case BLOCKNONE:
continue;
break;
case BLOCKBLUE:
setfillcolor(BLUE);
break;
case BLOCKGREEN:
setfillcolor(GREEN);
break;
case BLOCKCYAN:
setfillcolor(CYAN);
break;
case BLOCKRED:
setfillcolor(RED);
break;
case BLOCKMAGENTA:
setfillcolor(MAGENTA);
break;
case BLOCKBROWN:
setfillcolor(BROWN);
break;
case BLOCKYELLOW:
setfillcolor(YELLOW);
break;
}
fillrectangle(GAMEMAP5 + (i%5) * SIDELENGTH ,GAMEMAP6 + (i/5) * SIDELENGTH ,GAMEMAP5 + ((i%5)+1)*SIDELENGTH ,GAMEMAP6 + ((i/5)+1) * SIDELENGTH );
}
setfillcolor(BLACK);
solidrectangle(GAMEMAP1,GAMEMAP2,GAMEMAP3,GAMEMAP4);
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
switch(moveBlock.blockColor)
{
case BLOCKNONE:
continue;
break;
case BLOCKBLUE:
setfillcolor(BLUE);
break;
case BLOCKGREEN:
setfillcolor(GREEN);
break;
case BLOCKCYAN:
setfillcolor(CYAN);
break;
case BLOCKRED:
setfillcolor(RED);
break;
case BLOCKMAGENTA:
setfillcolor(MAGENTA);
break;
case BLOCKBROWN:
setfillcolor(BROWN);
break;
case BLOCKYELLOW:
setfillcolor(YELLOW);
break;
}
fillrectangle(GAMEMAP1 + y * SIDELENGTH ,GAMEMAP2 + x * SIDELENGTH ,GAMEMAP1 + (y+1)*SIDELENGTH ,GAMEMAP2 + (x+1) * SIDELENGTH );
}
for(i = 0; i < TETRISHEIGHT; i++)
{
for(j = 0; j < TETRISWIDTH; j++)
{
switch(tetrisMap[i][j])
{
case BLOCKNONE:
continue;
break;
case BLOCKBLUE:
setfillcolor(BLUE);
break;
case BLOCKGREEN:
setfillcolor(GREEN);
break;
case BLOCKCYAN:
setfillcolor(CYAN);
break;
case BLOCKRED:
setfillcolor(RED);
break;
case BLOCKMAGENTA:
setfillcolor(MAGENTA);
break;
case BLOCKBROWN:
setfillcolor(BROWN);
break;
case BLOCKYELLOW:
setfillcolor(YELLOW);
break;
}
fillrectangle(GAMEMAP1 + j * SIDELENGTH ,GAMEMAP2 + i * SIDELENGTH ,GAMEMAP1 + (j+1)*SIDELENGTH ,GAMEMAP2 + (i+1) * SIDELENGTH );
}
}
}
void tetrisNewBlock(void)
{
int i,j,k;
k = 0;
for(i = 0; i < BLOCKSPACE; i++)
{
if(preBlock.tetrisPreBlock[i])
{
moveBlock.blockColor = preBlock.tetrisPreBlock[i];
moveBlock.blockSite[k][0] = i/5;
moveBlock.blockSite[k][1] = 5 + i%5;
k++;
}
}
player.blockType = preBlock.blockType;
player.blockStyle = preBlock.blockStyle;
srand((unsigned)time(NULL));
i = rand() % BLOCKTYPE;
j = rand() % BLOCKSTYLE;
for(k = 0; k < BLOCKSPACE;k++)
{
preBlock.tetrisPreBlock[k] = block[i][j][k];
}
preBlock.blockType = i;
preBlock.blockStyle = j;
}
void tetrisMoveUp(void)
{
int i,k;
int bStyle;
int ux,uy; // 改变方块朝向前后,方块格的位移矢量
int blocksite2[4][2]; // 记录改变朝向前,4个方块的方块库坐标
int blocksite3[4][2]; // 记录改变朝向后,4个方块的方块库坐标
int blocksite4[4][2]; // 记录改变朝向后,4个方块的游戏地图坐标
k = 0;
for(i = 0; i < BLOCKSPACE; i++)
{
if(k > 3)
{
break;
}
if(block[player.blockType][player.blockStyle][i])
{
blocksite2[k][0] = i / 5;
blocksite2[k][1] = i % 5;
k++;
}
}
k = 0;
bStyle = (player.blockStyle + 1)%BLOCKSTYLE;
for(i = 0; i < BLOCKSPACE; i++)
{
if(k > 3)
{
break;
}
if(block[player.blockType][bStyle][i])
{
blocksite3[k][0] = i / 5;
blocksite3[k][1] = i % 5;
k++;
}
}
for(i = 0; i < 4; i++)
{
ux = blocksite3[i][0] - blocksite2[i][0];
uy = blocksite3[i][1] - blocksite2[i][1];
blocksite4[i][0] = moveBlock.blockSite[i][0] + ux;
blocksite4[i][1] = moveBlock.blockSite[i][1] + uy;
if( (blocksite4[i][0] < 0)
|| (blocksite4[i][1] < 0)
|| (blocksite4[i][0] > TETRISHEIGHT - 1)
|| (blocksite4[i][1] > TETRISWIDTH - 1)
|| ( tetrisMap[blocksite4[i][0]][blocksite4[i][1]] ) )
{
return;
}
}
for(i = 0;i < 4; i++)
{
moveBlock.blockSite[i][0] = blocksite4[i][0];
moveBlock.blockSite[i][1] = blocksite4[i][1];
}
player.blockStyle = bStyle;
}
void tetrisMoveDown(void)
{
int i;
int x,y;
for(i = 0; i < 4; i++)
{
if(moveBlock.blockSite[i][0] > TETRISHEIGHT - 2)
{
player.newBlockFlag = TRUE;
return;
}
}
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0] + 1;
y = moveBlock.blockSite[i][1];
if(tetrisMap[x][y])
{
player.newBlockFlag = TRUE;
return;
}
}
for(i = 0; i < 4; i++)
{
moveBlock.blockSite[i][0]++;
}
}
void tetrisMoveLeft(void)
{
int i;
int x,y;
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
if( (y - 1) < 0
|| tetrisMap[x][y - 1])
{
return;
}
}
for(i = 0; i < 4; i++)
{
moveBlock.blockSite[i][1]--;
}
}
void tetrisMoveRight(void)
{
int i;
int x,y;
//k = 0;
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
if( ((y + 1) >= TETRISWIDTH)
|| (tetrisMap[x][y + 1]) )
{
return;
}
}
for(i = 0; i < 4; i++)
{
moveBlock.blockSite[i][1]++;
}
}
void tetrisLoadBlock(void)
{
int i;
int x,y;
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
if(!tetrisMap[x][y])
{
tetrisMap[x][y] = moveBlock.blockColor;
}
}
}
void tetrisIsOver(void)
{
int i;
int x,y;
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
if(tetrisMap[x][y])
{
player.gameOverFlag = TRUE;
tetrisDraw();
tetrisQuit();
return;
}
}
}
void tetrisQuit(void)
{
int key,flag;
flag = player.gameOverFlag;
key = MessageBox(NULL,"是否退出游戏?","提示",MB_YESNO| MB_SYSTEMMODAL);
switch(key)
{
case IDYES:
player.gameOverFlag = TRUE;
break;
case IDNO:
player.gameOverFlag = FALSE;
break;
default:
break;
}
while(flag
&& !player.gameOverFlag)
{
key = MessageBox(NULL,"检测到游戏已无法继续,\n请选择\"是\": 退出游戏;\n或者选择\"否\": 重启游戏;","提示",MB_YESNO| MB_SYSTEMMODAL);
switch(key)
{
case IDYES:
player.gameOverFlag = TRUE;
break;
case IDNO:
tetrisInit();
player.gameOverFlag = FALSE;
return;
default:
break;
}
}
CLEARKEY();
}
void tetrisKeyHandle(void)
{
switch(player.key)
{
case 'w':
case 'W':
tetrisMoveUp();
break;
case 'a':
case 'A':
tetrisMoveLeft();
break;
case 's':
case 'S':
player.moveFlag = TRUE;
player.ttime = GetTickCount();
return;
case 'd':
case 'D':
tetrisMoveRight();
break;
case ESC:
tetrisQuit();
break;
case 'r':
case 'R':
tetrisReset();
break;
default:
break;
}
tetrisDraw();
}
void tetrisReset(void)
{
int key;
key = MessageBox(NULL,"是否重新开始游戏?","提示",MB_YESNO| MB_SYSTEMMODAL);
CLEARKEY();
switch(key)
{
case IDYES:
tetrisInit();
break;
case IDNO:
break;
default:
break;
}
}
void tetrisRemove(void)
{
int i,j,m,n;
int flag,bnum;
bnum = 0;
for(i = TETRISHEIGHT - 1; i >= 0; i-- )
{
flag = 0;
for(j = 0; j < TETRISWIDTH; j++)
{
if(!tetrisMap[i][j])
{
flag = 1;
break;
}
}
if(flag)
{
continue;
}
bnum++;
for(j = 0; j < TETRISWIDTH; j++)
{
tetrisMap[i][j] = BLOCKNONE;
}
for(m = i; m > 0; m--)
{
for(n = 0; n < TETRISWIDTH; n++)
{
tetrisMap[m][n] = tetrisMap[m - 1][n];
}
}
i++;
}
if(!bnum)
{
return;
}
switch(bnum)
{
case 0:
break;
case 1:
player.rank += 10;
break;
case 2:
player.rank += 20;
break;
case 3:
player.rank += 40;
break;
case 4:
player.rank += 80;
break;
default:
break;
}
player.level = player.rank/100;
if(player.level > 10)
{
player.level = 10;
}
}
二、easyX库安装
EasyX Graphics Library for C++
进入以上链接,下载并安装easyX库
三、宏定义、变量的说明
1.方块像素
方块为正方形,大小为20X20像素
#define SIDELENGTH 20
2.游戏地图区域
游戏地图为矩形,大小为600X300像素,四个角坐标为下面的宏定义。
#define GAMEMAP1 200
#define GAMEMAP2 10
#define GAMEMAP3 500
#define GAMEMAP4 610
使用rectangle函数,填入四个角坐标,画出游戏地图边界
rectangle(GAMEMAP1 - 2,GAMEMAP2 - 2,GAMEMAP3 + 2,GAMEMAP4 + 2);
把地图按20X20像素分割,就是30X15个方块,这样就把地图转换为坐标的形式了,选中一个方块格,再进行涂色,就是一个相应的方块了。
3.预生成方块区域
预生成方块区域为100X80像素的矩形,四个角坐标如下宏定义
#define GAMEMAP5 40 // 预生成方块区域
#define GAMEMAP6 50
#define GAMEMAP7 140
#define GAMEMAP8 130
使用rectangle函数,填入四个角坐标,画出游戏地图边界
rectangle(GAMEMAP5 - 2,GAMEMAP6 - 2,GAMEMAP7 + 2,GAMEMAP8 + 2);
把区域按照20X20像素分割,就是4X5个方块,这样就可以转换为坐标的形式;
根据随机得到的方块种类,换算成四个坐标,再选择颜色进行填充,就可以得到预生成的方块了。
4.玩家数据结构
typedef struct _GAME_TETRIS
{
int ttime; // 计时
int ctime; // 计时
int level; // 当前游戏等级
int rank; // 当前游戏分数
int key; // 玩家按键值
int blockType; // 当前方块类型
int blockStyle; // 当前方块朝向
bool moveFlag; // 移动标识,标识为1时,移动当前方块
bool newBlockFlag; // 载入新方块标识,当标识为真时,将预生成的随机方块载入地图
bool gameOverFlag; // 游戏结束标识
}GAME_TETRIS;
GAME_TETRIS player; // 玩家数据
player会保存玩家在游戏中的数据,ttime和ctime是用来定时的,每隔(500ms - 等级*30毫秒),会将moveflag置1,方块下降一行;
level和rank是玩家当前获得分数,已经根据分数得到的level;
key是玩家按下的键盘输入值;
blockType和blockStyle是玩家当前控制方块的方块类型和方块朝向,用来判定方块是否可以左、右、下移、变换形态;
moveflag,移动标识,标识为1时,当前方块会下移一行;
bewBlockFlag,新方块标识,标识为1时,会执行以下函数:当前方块载入游戏地图、消除满行的方块、生成新的方块、判断游戏是否结束;
gameOverFlag,游戏结束标识,标识为1时,会让玩家选择结束游戏,或重新开始游戏
5.所有方块数据库
数据库是一个三维数组
总共有8种类型的方块,每个类型的方块颜色都不一样;每个方块有4个样式(朝向);每种方块的每种朝向都是一个5X4的方块矩形。
生成新方块时,就会从方块数据库中获取方块数据。
#define BLOCKTYPE 8 // 方块种类数
#define BLOCKSTYLE 4 // 每种方块的样式数
#define BLOCKSPACE 20 // 方块占用空间 5 x 4
const int block[BLOCKTYPE][BLOCKSTYLE][BLOCKSPACE] =
{
// 总共有8种方块,每种方块有4种样式
// 'I'形方块
{
{
BLOCKNONE, BLOCKBLUE, BLOCKBLUE, BLOCKBLUE, BLOCKBLUE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE
},
{
BLOCKBLUE, BLOCKBLUE, BLOCKBLUE, BLOCKBLUE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBLUE, BLOCKNONE, BLOCKNONE
}
},
// 'Z'形方块
{
{
BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKGREEN, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKGREEN, BLOCKGREEN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKGREEN, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// 反'Z'形方块
{
{
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKCYAN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// 'T'形方块
{
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// 'T'形方块 X 2 增加T形方块出现概率
{
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKRED, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKRED, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKRED, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// '田'形方块
{
{
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKMAGENTA,BLOCKMAGENTA,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// 'L'形方块
{
{
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKBROWN, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKBROWN, BLOCKBROWN,
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKBROWN, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKBROWN, BLOCKBROWN, BLOCKBROWN, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
},
// 反'L'形方块
{
{
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKYELLOW,BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKYELLOW,BLOCKYELLOW,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKYELLOW,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
},
{
BLOCKNONE, BLOCKYELLOW,BLOCKYELLOW,BLOCKYELLOW,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKYELLOW,BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE,
BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE, BLOCKNONE
}
}
};
6.当前控制方块数据结构
typedef struct _GAME_MOVE_BLOCK
{
int blockSite[4][2]; // 4个方块格坐标
int blockColor; // 方块颜色
}GAME_MOVE_BLOCK;
GAME_MOVE_BLOCK moveBlock; // 当前控制方块数据
数据结构由两部分组成,一个是保存了四个方块坐标的二维数组;一个是当前控制方块的颜色;
有了坐标和颜色,就可以在游戏地图中,准确的画出当前控制方块。
四、主函数tetrisrun()
- 1.函数说明
主函数由一个初始化函数,和一个死循环组成;初始化游戏数据后,会在死循环while(1)中不停的执行游戏数据处理,直到玩家选择退出游戏。
通过主函数,也能看出来整个代码的运行逻辑,了解编程思路。
void tetrisrun(void)
{
tetrisInit();
while(1)
{
// (每500毫秒 - 游戏等级*30毫秒)运行一次,方块下移
player.ctime = GetTickCount();
if( (player.ctime - 500 + 30*player.level) > player.ttime )
{
player.moveFlag = TRUE;
player.ttime = GetTickCount();
}
// 如果移动标识为1,则当前方块下移
if(player.moveFlag)
{
player.moveFlag = FALSE;
tetrisMoveDown();
tetrisDraw();
}
// 用户按键处理
if(_kbhit())
{
player.key = _getch();
tetrisKeyHandle();
}
// 将预生成的随机方块载入游戏地图
if(player.newBlockFlag)
{
player.newBlockFlag = FALSE;
// 将当前方块载入游戏地图
tetrisLoadBlock();
// 消除满行的方块
tetrisRemove();
// 生成新的方块
tetrisNewBlock();
// 判断游戏是否结束
tetrisIsOver();
}
// 游戏结束处理
if(player.gameOverFlag)
{
player.gameOverFlag = FALSE;
break;
}
}
}
- 2.计时部分
// (每500毫秒 - 游戏等级*30毫秒)运行一次,方块下移
player.ctime = GetTickCount();
if( (player.ctime - 500 + 30*player.level) > player.ttime )
{
player.moveFlag = TRUE;
player.ttime = GetTickCount();
}
第一部分,计时部分:通过记录两个时间变量的时间差,来得到一个周期执行的标识
1.初始化时,先给变量player.ttime赋值,获得当时的时间数据;
2.每次主程序循环时,都会给另一个变量player.ctime赋值,获得最新的时间数据;
3.以默认的500ms周期为例,如果最新的时间player.ctime减去500ms,还是大于当时的时间player.ttime,说明离第一次给player.ttime赋值,已经过去了500ms,满足一个周期;
4.此时,给周期执行标识 player.moveFlag置1;表示当前方块需要下移了,将会在后面的函数处理,并将标识置0,等待下一次周期来临;
5.再给player.ttime赋值,获得当时的时间数据,开始执行下一次500ms的周期计算。
- 3.方块移动
// 如果移动标识为1,则当前方块下移
if(player.moveFlag)
{
player.moveFlag = FALSE; tetrisMoveDown();
tetrisDraw();
}
第二部分,方块移动:每过一个周期,方块移动标识player.moveFlag置1,执行这部分函数
1.先将移动标识player.moveFlag置0,方便下一次周期的执行;
2.执行方块下移函数tetrisMoveDown(),对方块进行下移处理;如果方块到达游戏底部,或者下方已经存在方块,就会将生成新方块标识player.newBlockFlag置1,进行后面的处理;
3.执行绘制地图函数tetrisDraw(),画出方块下移后的画面。
- 4.按键处理
// 用户按键处理
if(_kbhit())
{
player.key = _getch();
tetrisKeyHandle();
}
第三部分,按键处理:对用户的按键进行处理,控制方块的移动、游戏的进程
1._kbhit()函数,每当检测到用户按下按键时,会返回TRUE,此时if条件为真,执行if里面的代码;
2.通过_getch()函数,将用户按键值赋给变量player.key;
3.执行按键处理函数tetrisKeyHandle(),对不同的按键进行不同的处理
- 5.新方块处理
// 将预生成的随机方块载入游戏地图
if(player.newBlockFlag)
{
player.newBlockFlag = FALSE;
// 将当前方块载入游戏地图
tetrisLoadBlock();
// 消除满行的方块
tetrisRemove();
// 生成新的方块
tetrisNewBlock();
// 判断游戏是否结束
tetrisIsOver();
}
第四部分,新方块处理:每当当前方块到达游戏底部,或者当前方块下一行已存在方块时,会将新方块标识player.newBlockFlag置1,执行if里面的代码。
1.先把新方块标识player.newBlockFlag清0,防止重复执行;
2.当前方块已不能下移,执行tetrisLoadBlock()函数,把当前方块固定到游戏地图,不再移动;
3.固定方块后,执行tetrisRemove()函数,检测并消除满行的方块,并通过消除的行数,增加RANK分,改变level等级;
4.消除满行后,执行tetrisNewBlock()函数,将预生成的方块载入游戏,成为当前控制方块,并生成新的随机方块,载入到预生成方块地图;
5.生成新方块后,执行 tetrisIsOver()函数,判断当前控制方块是否和游戏地图有重叠,如果重叠,则游戏结束,将结束标识player.gameOverFlag置1,并让用户选择是结束游戏还是重新开始。
- 6.游戏结束
// 游戏结束处理
if(player.gameOverFlag)
{
player.gameOverFlag = FALSE;
break;
}
第五部分,游戏结束处理:如果用户决定结束游戏, 则退出while(1)死循环,退出程序。
五、代码和所有函数说明
上面已经解释过主函数tetrisrun(),接下来会详细解释每一个函数
1. tetrisInit()
初始化函数,程序会先运行初始化函数,然后再进入while(1)死循环,运行俄罗斯方块游戏;
void tetrisInit(void)
{
int i,j,k;
HWND window = initgraph(510, 620); // 初始化窗口大小
SetWindowText(window, "俄罗斯方块 - by耒阳阿杰"); // 设置当前窗口的标题
//SetWindowPos(window,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE); // 置顶
rectangle(GAMEMAP1 - 2,GAMEMAP2 - 2,GAMEMAP3 + 2,GAMEMAP4 + 2); // 绘制游戏地图范围
rectangle(GAMEMAP5 - 2,GAMEMAP6 - 2,GAMEMAP7 + 2,GAMEMAP8 + 2); // 绘制预生成方块方位
rectangle(GAMEGUIDE1 - 5,GAMEGUIDE2 - 5,GAMEGUIDE3 + 5,GAMEGUIDE4 + 5); // 绘制按键说明
outtextxy(GAMEMAP5 + 10 ,GAMEMAP6 - 26,"下个方块");
settextcolor(GREEN);
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 ,"W :改变形状");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 25 ,"A :方块左移");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 50 ,"S :方块右移");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 75 ,"D :方块下移");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 100 ,"R :重新开始");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 125 ,"ESC:退出游戏");
// 初始化游戏数据
memset ( &player, 0, sizeof ( GAME_TETRIS ) );
memset ( &preBlock, 0, sizeof ( GAME_PREBLOCK ) );
memset(tetrisMap,0,sizeof(tetrisMap));
player.ttime = GetTickCount();
// 获取随机数,生成第一个随机方块
srand((unsigned)time(NULL)); // 获取随机数,生成新的随机方块
i = (rand() + 1) % BLOCKTYPE;
j = rand() % BLOCKSTYLE;
for(k = 0; k < BLOCKSPACE;k++)
{
preBlock.tetrisPreBlock[k] = block[i][j][k];
}
preBlock.blockType = i;
preBlock.blockStyle = j;
// 把第一个随机方块加载进游戏,并生成下一个随机方块
tetrisNewBlock();
}
HWND window = initgraph(510, 620); // 初始化窗口大小
SetWindowText(window, "俄罗斯方块 - by耒阳阿杰"); // 设置当前窗口的标题
//SetWindowPos(window,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE); // 置顶
1.初始化窗口,定义窗口大小,设置窗口标题,屏蔽的SetWindowPos()函数是用于给游戏置顶的,置顶之后,总是会显示在WINDOWS的最顶层,类似于微信的置顶;
rectangle(GAMEMAP1 - 2,GAMEMAP2 - 2,GAMEMAP3 + 2,GAMEMAP4 + 2); // 绘制游戏地图范围
rectangle(GAMEMAP5 - 2,GAMEMAP6 - 2,GAMEMAP7 + 2,GAMEMAP8 + 2); // 绘制预生成方块方位
rectangle(GAMEGUIDE1 - 5,GAMEGUIDE2 - 5,GAMEGUIDE3 + 5,GAMEGUIDE4 + 5); // 绘制按键说明 outtextxy(GAMEMAP5 + 10 ,GAMEMAP6 - 26,"下个方块");
settextcolor(GREEN);
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 ,"W :改变形状");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 25 ,"A :方块左移");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 50 ,"S :方块右移");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 75 ,"D :方块下移");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 100 ,"R :重新开始");
outtextxy(GAMEGUIDE1 ,GAMEGUIDE2 + 125 ,"ESC:退出游戏");
2.rectangle()函数是根据你输入的四个坐标,绘制一个矩形,这里绘制了游戏地图、预生成方块地图、按键说明框;
outtextxy(x,y,string)函数是根据你输入的起点坐标,还有后面的字符串,在相应位置打印字符串内容;
settextcolor(GREEN)函数是设置打印字符串的颜色;
// 初始化游戏数据
memset ( &player, 0, sizeof ( GAME_TETRIS ) );
memset ( &preBlock, 0, sizeof ( GAME_PREBLOCK ) );
memset(tetrisMap,0,sizeof(tetrisMap));
player.ttime = GetTickCount();
3. memset()函数是重置结构或者数据的数据,将他们置0;
然后给player.ttime赋值当时的时间,方便后面用来判定500ms周期。
// 获取随机数,生成第一个随机方块
srand((unsigned)time(NULL)); // 获取随机数,生成新的随机方块
i = (rand() + 1) % BLOCKTYPE;
j = rand() % BLOCKSTYLE;
for(k = 0; k < BLOCKSPACE;k++)
{
preBlock.tetrisPreBlock[k] = block[i][j][k];
}
preBlock.blockType = i;
preBlock.blockStyle = j;
// 把第一个随机方块加载进游戏,并生成下一个随机方块
tetrisNewBlock();
4.关于随机数生成,分为伪随机和真随机,这个建议读者们自己去查阅相关资料;
通过生成的随机数,再分别取方块类型和形状(朝向)的余数,就能得到一个随机方块的数据了;
得到第一个随机方块后,再执行 tetrisNewBlock()函数,把第一个随机方块载入游戏地图,再生成新的随机方块,载入预生成方块地图。
2.tetrisDraw()
绘制函数,根据当前游戏数据,绘制整个游戏画面。
void tetrisDraw(void)
{
int i,j;
int x,y;
char ch[20];
// 更新RANK分数和LEVEL等级
settextstyle(20,12,"宋体"); // 设置字体和大小
settextcolor(CYAN); // 设置字体颜色
if(player.level < 10)
{
sprintf_s(ch, "%s%d","LEVEL:", player.level);
}
else
{ // 等级为10时,显示最大max
sprintf_s(ch, "%s","LEVEL:MAX");
}
outtextxy(GAMELEVEL1,GAMELEVEL2,ch);
settextcolor(LIGHTBLUE);
sprintf_s(ch, "%s%d","RANK:", player.rank);
outtextxy(GAMERANK1,GAMERANK2,ch);
// 绘制预生成方块的背景板
setfillcolor(BLACK);
solidrectangle(GAMEMAP5,GAMEMAP6,GAMEMAP7,GAMEMAP8);
// 绘制预生成的方块
for(i = 0; i < BLOCKSPACE; i++)
{
switch(preBlock.tetrisPreBlock[i])
{
case BLOCKNONE: // 空
continue;
break;
case BLOCKBLUE: // 蓝色
setfillcolor(BLUE);
break;
case BLOCKGREEN: // 绿色
setfillcolor(GREEN);
break;
case BLOCKCYAN: // 青色
setfillcolor(CYAN);
break;
case BLOCKRED: // 红色
setfillcolor(RED);
break;
case BLOCKMAGENTA: // 深红色
setfillcolor(MAGENTA);
break;
case BLOCKBROWN: // 灰色
setfillcolor(BROWN);
break;
case BLOCKYELLOW:
setfillcolor(YELLOW);
break;
}
// 填充相应区域
fillrectangle(GAMEMAP5 + (i%5) * SIDELENGTH ,GAMEMAP6 + (i/5) * SIDELENGTH ,GAMEMAP5 + ((i%5)+1)*SIDELENGTH ,GAMEMAP6 + ((i/5)+1) * SIDELENGTH );
}
// 绘制游戏地图背景板
setfillcolor(BLACK);
solidrectangle(GAMEMAP1,GAMEMAP2,GAMEMAP3,GAMEMAP4);
// 绘制当前控制方块的4个方块格
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
switch(moveBlock.blockColor)
{
case BLOCKNONE:
continue;
break;
case BLOCKBLUE:
setfillcolor(BLUE);
break;
case BLOCKGREEN:
setfillcolor(GREEN);
break;
case BLOCKCYAN:
setfillcolor(CYAN);
break;
case BLOCKRED:
setfillcolor(RED);
break;
case BLOCKMAGENTA:
setfillcolor(MAGENTA);
break;
case BLOCKBROWN:
setfillcolor(BROWN);
break;
case BLOCKYELLOW:
setfillcolor(YELLOW);
break;
}
// 填充相应区域
fillrectangle(GAMEMAP1 + y * SIDELENGTH ,GAMEMAP2 + x * SIDELENGTH ,GAMEMAP1 + (y+1)*SIDELENGTH ,GAMEMAP2 + (x+1) * SIDELENGTH );
}
// 绘制游戏实时画面
for(i = 0; i < TETRISHEIGHT; i++)
{
for(j = 0; j < TETRISWIDTH; j++)
{
switch(tetrisMap[i][j])
{
case BLOCKNONE:
continue;
break;
case BLOCKBLUE:
setfillcolor(BLUE);
break;
case BLOCKGREEN:
setfillcolor(GREEN);
break;
case BLOCKCYAN:
setfillcolor(CYAN);
break;
case BLOCKRED:
setfillcolor(RED);
break;
case BLOCKMAGENTA:
setfillcolor(MAGENTA);
break;
case BLOCKBROWN:
setfillcolor(BROWN);
break;
case BLOCKYELLOW:
setfillcolor(YELLOW);
break;
}
// 填充相应区域
fillrectangle(GAMEMAP1 + j * SIDELENGTH ,GAMEMAP2 + i * SIDELENGTH ,GAMEMAP1 + (j+1)*SIDELENGTH ,GAMEMAP2 + (i+1) * SIDELENGTH );
}
}
}
// 更新RANK分数和LEVEL等级
settextstyle(20,12,"宋体"); // 设置字体和大小
settextcolor(CYAN); // 设置字体颜色
if(player.level < 10)
{
sprintf_s(ch, "%s%d","LEVEL:", player.level);
}
else
{ // 等级为10时,显示最大max
sprintf_s(ch, "%s","LEVEL:MAX");
}
outtextxy(GAMELEVEL1,GAMELEVEL2,ch);
settextcolor(LIGHTBLUE);
sprintf_s(ch, "%s%d","RANK:", player.rank);
outtextxy(GAMERANK1,GAMERANK2,ch);
1.设置输出字符的大小和字体,再设置颜色;
等级在10级以下时,照常显示;10级及以上时,显示MAX;
// 绘制预生成方块的背景板
setfillcolor(BLACK);
solidrectangle(GAMEMAP5,GAMEMAP6,GAMEMAP7,GAMEMAP8);
2. setfillcolor(BLACK)设置填充颜色;solidrectangle(GAMEMAP5,GAMEMAP6,GAMEMAP7,GAMEMAP8) 根据四个坐标,使用设置好的填充颜色填充矩形区域,实际就是绘制一个带白色边框的黑色矩形。
// 绘制预生成的方块
for(i = 0; i < BLOCKSPACE; i++)
{
switch(preBlock.tetrisPreBlock[i])
{
case BLOCKNONE: // 空
continue;
break;
case BLOCKBLUE: // 蓝色
setfillcolor(BLUE);
break;
case BLOCKGREEN: // 绿色
setfillcolor(GREEN);
break;
case BLOCKCYAN: // 青色
setfillcolor(CYAN);
break;
case BLOCKRED: // 红色
setfillcolor(RED);
break;
case BLOCKMAGENTA: // 深红色
setfillcolor(MAGENTA);
break;
case BLOCKBROWN: // 灰色
setfillcolor(BROWN);
break;
case BLOCKYELLOW:
setfillcolor(YELLOW);
break;
}
// 填充相应区域
fillrectangle(GAMEMAP5 + (i%5) * SIDELENGTH ,GAMEMAP6 + (i/5) * SIDELENGTH ,GAMEMAP5 + ((i%5)+1)*SIDELENGTH ,GAMEMAP6 + ((i/5)+1) * SIDELENGTH );
}
3.绘制好预生成方块区域后,preBlock.tetrisPreBlock[i]是一个包含20个成员的数组,里面有16个空成员,不用管;还有4个成员就是要绘制的方块。用switch选择相应的填充颜色setfillcolor(),然后再执行 fillrectangle()函数,在相应坐标填充方块;
最后的填充函数,可能会有点复杂,这里解释一下:
预生成方块区域是一个100X80像素大小的矩形,每个方块都是20X20像素大小,所以矩形按20X20像素切割,得到一个5X4的坐标轴,包含20个方块,刚好和所有方块数据库里的方块对应。
如下图所示,只需要把数据库里20个数据中,包含方块的四个数据选出来填入,就能得到一个完整的方块了。
现在,我想要绘制一个 '----' 型方块,只需要填充2,3,4,5这4个坐标,就能得到方块;
那么它在preBlock.tetrisPreBlock[]数组中对应的数据就是
{0,1,1,1,1, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0};得到的最终图像就是如下所示
当preBlock.tetrisPreBlock[i]为1时,就会执行fillrectangle()函数,根据i的值,获得四个坐标值,填充这个方块;
第一个方块:i = 1, fillrectangle(GAMEMAP5 + 1 * 20,GAMEMAP6,GAMEMAP5 + 2*20,GAMEMAP6 + 1 * 20);
GAMEMAP5和GAMEMAP6就是预生成方块区域的坐上角坐标,将它视为起点坐标,忽略掉,
上面的fillrectangle() 就相当于fillrectangle( 20,0, 40,20),绘制出来就是如下图
第二个方块:i = 2,fillrectangle(GAMEMAP5 + 2 * 20,GAMEMAP6 ,GAMEMAP5 + 3*20,GAMEMAP6 + 1 * 20);
忽略起点坐标,得到fillrectangle( 40,0,60,20),绘制出来如下图
依次画出第三个方块,第四个方块,就能得到最终的图像了
// 绘制游戏地图背景板
setfillcolor(BLACK);
solidrectangle(GAMEMAP1,GAMEMAP2,GAMEMAP3,GAMEMAP4);
4.和2类似,绘制一个白色边框的纯黑矩形
// 绘制当前控制方块的4个方块格
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
switch(moveBlock.blockColor)
{
case BLOCKNONE:
continue;
break;
case BLOCKBLUE:
setfillcolor(BLUE);
break;
case BLOCKGREEN:
setfillcolor(GREEN);
break;
case BLOCKCYAN:
setfillcolor(CYAN);
break;
case BLOCKRED:
setfillcolor(RED);
break;
case BLOCKMAGENTA:
setfillcolor(MAGENTA);
break;
case BLOCKBROWN:
setfillcolor(BROWN);
break;
case BLOCKYELLOW:
setfillcolor(YELLOW);
break;
}
// 填充相应区域
fillrectangle(GAMEMAP1 + y * SIDELENGTH ,GAMEMAP2 + x * SIDELENGTH ,GAMEMAP1 + (y+1)*SIDELENGTH ,GAMEMAP2 + (x+1) * SIDELENGTH );
}
5.和3类似,变量moveBlock.blockSite[]保存了当前方块的四个方块格坐标,根据方块颜色moveBlock.blockColor,通过switch选择相应的颜色,然后fillrectangle填充,得到当前方块。
// 绘制游戏实时画面
for(i = 0; i < TETRISHEIGHT; i++)
{
for(j = 0; j < TETRISWIDTH; j++)
{
switch(tetrisMap[i][j])
{
case BLOCKNONE:
continue;
break;
case BLOCKBLUE:
setfillcolor(BLUE);
break;
case BLOCKGREEN:
setfillcolor(GREEN);
break;
case BLOCKCYAN:
setfillcolor(CYAN);
break;
case BLOCKRED:
setfillcolor(RED);
break;
case BLOCKMAGENTA:
setfillcolor(MAGENTA);
break;
case BLOCKBROWN:
setfillcolor(BROWN);
break;
case BLOCKYELLOW:
setfillcolor(YELLOW);
break;
}
// 填充相应区域
fillrectangle(GAMEMAP1 + j * SIDELENGTH ,GAMEMAP2 + i * SIDELENGTH ,GAMEMAP1 + (j+1)*SIDELENGTH ,GAMEMAP2 + (i+1) * SIDELENGTH );
}
}
6.和3、5类似,tetrisMap[]数组保存了游戏地图中已经固定下来的方块和颜色,通过switch选择颜色,然后使用fillrectangle()函数,绘制所有固定方块。
3.tetrisNewBlock() 函数
当新方块标识player.newBlockFlag为1时,会执行本函数,把预生成方块加载进游戏,成为当前控制方块;之后再获取随机数,生成新的预生成方块。
void tetrisNewBlock(void)
{
int i,j,k;
// 把上一个随机方块加载进当前方块数组
k = 0;
for(i = 0; i < BLOCKSPACE; i++)
{
if(preBlock.tetrisPreBlock[i])
{
moveBlock.blockColor = preBlock.tetrisPreBlock[i]; // 记录方块颜色
moveBlock.blockSite[k][0] = i/5; // 记录方块格的X坐标
moveBlock.blockSite[k][1] = 5 + i%5; // 记录方块格的Y坐标
k++;
}
}
player.blockType = preBlock.blockType; // 记录方块的类型
player.blockStyle = preBlock.blockStyle; // 记录方块的形状(朝向)
// 获取随机数,生成新的随机方块
srand((unsigned)time(NULL));
i = rand() % BLOCKTYPE;
j = rand() % BLOCKSTYLE;
for(k = 0; k < BLOCKSPACE;k++)
{
preBlock.tetrisPreBlock[k] = block[i][j][k];
}
preBlock.blockType = i;
preBlock.blockStyle = j;
}
// 把上一个随机方块加载进当前方块数组
k = 0;
for(i = 0; i < BLOCKSPACE; i++)
{
if(preBlock.tetrisPreBlock[i])
{
moveBlock.blockColor = preBlock.tetrisPreBlock[i]; // 记录方块颜色
moveBlock.blockSite[k][0] = i/5; // 记录方块格的X坐标
moveBlock.blockSite[k][1] = 5 + i%5; // 记录方块格的Y坐标
k++;
}
}
player.blockType = preBlock.blockType; // 记录方块的类型
player.blockStyle = preBlock.blockStyle; // 记录方块的形状(朝向)
1.preBlock.tetrisPreBlock[i]数组有16个成员为0,还有4个非0成员(方块格),检测到非0成员(方块格)时,就会记录方块颜色、坐标、类型、形状(朝向),保存到当前方块变量moveBlock。
// 获取随机数,生成新的随机方块
srand((unsigned)time(NULL));
i = rand() % BLOCKTYPE;
j = rand() % BLOCKSTYLE;
for(k = 0; k < BLOCKSPACE;k++)
{
preBlock.tetrisPreBlock[k] = block[i][j][k];
}
preBlock.blockType = i;
preBlock.blockStyle = j;
2.生成随机数,然后得到方块类型和形状(朝向),再把方块数据库中对应的20个成员值复制到preBlock.tetrisPreBlock数组中,就成为了一个新的随机方块。
4.tetrisMoveUp() 函数
当玩家按下W键后,会执行此函数,如果当前方块可改变形状(朝向),则改变形状(朝向)。
void tetrisMoveUp(void)
{
int i,k;
int bStyle;
int ux,uy; // 改变方块朝向前后,方块格的位移矢量
int blocksite2[4][2]; // 记录改变朝向前,4个方块的方块库坐标
int blocksite3[4][2]; // 记录改变朝向后,4个方块的方块库坐标
int blocksite4[4][2]; // 记录改变朝向后,4个方块的游戏地图坐标
// 找到4个方块格的方块库坐标
k = 0;
for(i = 0; i < BLOCKSPACE; i++)
{
// 得到4个方块坐标后,退出循环
if(k > 3)
{
break;
}
if(block[player.blockType][player.blockStyle][i])
{
blocksite2[k][0] = i / 5;
blocksite2[k][1] = i % 5;
k++;
}
}
// 找到改变朝向后,4个方块格的方块库坐标
k = 0;
bStyle = (player.blockStyle + 1)%BLOCKSTYLE; // 获取改变朝向后的方块形状
for(i = 0; i < BLOCKSPACE; i++)
{
// 得到4个方块坐标后,退出循环
if(k > 3)
{
break;
}
if(block[player.blockType][bStyle][i])
{
blocksite3[k][0] = i / 5;
blocksite3[k][1] = i % 5;
k++;
}
}
// 根据改变前后方块坐标的位移矢量,得到改变朝向后的当前控制方块的新坐标
for(i = 0; i < 4; i++)
{
ux = blocksite3[i][0] - blocksite2[i][0]; // 获取方格的X位移矢量
uy = blocksite3[i][1] - blocksite2[i][1]; // 获取方格的Y位移矢量
blocksite4[i][0] = moveBlock.blockSite[i][0] + ux; // 得到改变朝向后的,当前控制方块格,位于游戏地图的新X坐标
blocksite4[i][1] = moveBlock.blockSite[i][1] + uy; // 得到改变朝向后的,当前控制方块格,位于游戏地图的新Y坐标
if( (blocksite4[i][0] < 0) // 判断新坐标是否超出游戏地图,超出则终止改变方块朝向
|| (blocksite4[i][1] < 0)
|| (blocksite4[i][0] > TETRISHEIGHT - 1)
|| (blocksite4[i][1] > TETRISWIDTH - 1)
|| ( tetrisMap[blocksite4[i][0]][blocksite4[i][1]] ) ) // 如果新坐标在游戏地图中已存在方块格,则终止改变方块朝向
{
return;
}
}
// 改变方块朝向没有问题,则更新新方块的坐标
for(i = 0;i < 4; i++)
{
moveBlock.blockSite[i][0] = blocksite4[i][0];
moveBlock.blockSite[i][1] = blocksite4[i][1];
}
player.blockStyle = bStyle; // 更新方块格形状
}
// 找到4个方块格的方块库坐标
k = 0;
for(i = 0; i < BLOCKSPACE; i++)
{
// 得到4个方块坐标后,退出循环
if(k > 3)
{
break;
}
if(block[player.blockType][player.blockStyle][i])
{
blocksite2[k][0] = i / 5;
blocksite2[k][1] = i % 5;
k++;
}
}
1.根据当前控制方块的形状(朝向),在方块数据库中找到对应的坐标,并放入数组blocksite2中。
// 找到改变朝向后,4个方块格的方块库坐标
k = 0;
bStyle = (player.blockStyle + 1)%BLOCKSTYLE; // 获取改变朝向后的方块形状
for(i = 0; i < BLOCKSPACE; i++)
{
// 得到4个方块坐标后,退出循环
if(k > 3)
{
break;
}
if(block[player.blockType][bStyle][i])
{
blocksite3[k][0] = i / 5;
blocksite3[k][1] = i % 5;
k++;
}
}
2. player.blockStyle是当前方块的形状(朝向),只要把这个变量加1,再取余方块形状数,就可以得到新的方块形状(朝向)bStyle;根据方块类型,还有新的朝向bStyle,就可以从方块数据库中得到新方块的坐标,把新方块的坐标放入blocksite3[]数组。
// 根据改变前后方块坐标的位移矢量,得到改变朝向后的当前控制方块的新坐标
for(i = 0; i < 4; i++)
{
ux = blocksite3[i][0] - blocksite2[i][0]; // 获取方格的X位移矢量
uy = blocksite3[i][1] - blocksite2[i][1]; // 获取方格的Y位移矢量 blocksite4[i][0] = moveBlock.blockSite[i][0] + ux; // 得到改变朝向后的,当前控制方块格,位于游戏地图的新X坐标
blocksite4[i][1] = moveBlock.blockSite[i][1] + uy; // 得到改变朝向后的,当前控制方块格,位于游戏地图的新Y坐标
if( (blocksite4[i][0] < 0) // 判断新坐标是否超出游戏地图,超出则终止改变方块朝向
|| (blocksite4[i][1] < 0)
|| (blocksite4[i][0] > TETRISHEIGHT - 1)
|| (blocksite4[i][1] > TETRISWIDTH - 1)
|| ( tetrisMap[blocksite4[i][0]][blocksite4[i][1]] ) ) // 如果新坐标在游戏地图中已存在方块格,则终止改变方块朝向
{
return;
}
}
3.既然通过1、2步骤,得到了方块改变形状前后,位于方块数据库中的坐标,那么就可以通过把新旧坐标相减,得到旧坐标的位移矢量ux,uy;
通过位移矢量ux,uy,还有当前方块的四个方块格坐标moveBlock.blockSite,就可以得到改变形状(朝向)后,当前方块的新坐标,并把新坐标放入blocksite4[][]数组中;
有了新坐标blocksite4,就可以判断当前方块是否可以改变形状(朝向),首先判断新坐标是否超过游戏地图范围,再判断新坐标位置,是否已经存在方块;只要有一个不符合,那么本次操作无效,退出函数。
// 改变方块朝向没有问题,则更新新方块的坐标
for(i = 0;i < 4; i++)
{
moveBlock.blockSite[i][0] = blocksite4[i][0];
moveBlock.blockSite[i][1] = blocksite4[i][1];
}
player.blockStyle = bStyle; // 更新方块格形状
4.如果方块可以改变形状(朝向),那么就把方块的新坐标blocksite4,赋值给当前方块坐标moveBlock.blockSite;再把新的方块形状(朝向)bStyle,赋值给当前方块形状player.blockStyle
5.tetrisMoveDown() 函数
当方块下移标识player.moveFlag为1时,会执行本函数,控制当前方块下移。
player.moveFlag为1有两种条件
1:每个周期(默认500ms)会赋值为1。
2:用户按下S键,会赋值为1。
void tetrisMoveDown(void)
{
int i;
int x,y;
// 判断当前方块是否到达地图底部
for(i = 0; i < 4; i++)
{
if(moveBlock.blockSite[i][0] > TETRISHEIGHT - 2)
{
// 如果到达地图底部,把生成新方块标识置1
player.newBlockFlag = TRUE;
return;
}
}
// 判断当前方块能否下移,是否会和游戏地图已存在的方块产生冲突
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0] + 1; // 获取方块下移后的新X坐标
y = moveBlock.blockSite[i][1]; // 获取方块下移后的新Y坐标
// 如果新坐标在游戏地图中,已经存在方块格,则方块停止移动
if(tetrisMap[x][y])
{
player.newBlockFlag = TRUE;
return;
}
}
// 当前方块下移
for(i = 0; i < 4; i++)
{
moveBlock.blockSite[i][0]++;
}
}
// 判断当前方块是否到达地图底部
for(i = 0; i < 4; i++)
{
if(moveBlock.blockSite[i][0] > TETRISHEIGHT - 2)
{
// 如果到达地图底部,把生成新方块标识置1
player.newBlockFlag = TRUE;
return;
}
}
1.每次方块下移时,都会先判断方块是否到达游戏地图底部,如果到达底部,会给新方块标识player.newBlockFlag赋1,然后退出本函数,当前方块不再移动。
// 判断当前方块能否下移,是否会和游戏地图已存在的方块产生冲突
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0] + 1; // 获取方块下移后的新X坐标
y = moveBlock.blockSite[i][1]; // 获取方块下移后的新Y坐标
// 如果新坐标在游戏地图中,已经存在方块格,则方块停止移动
if(tetrisMap[x][y])
{
player.newBlockFlag = TRUE;
return;
}
}
2.方块下移会改变moveBlock.blockSite[][]数组的X坐标,加一;
判断游戏地图中新坐标位置是否已存在方块,如果存在方块,会给新方块标识player.newBlockFlag赋1,然后退出本函数,当前方块不再移动。
// 当前方块下移
for(i = 0; i < 4; i++)
{
moveBlock.blockSite[i][0]++;
}
3.方块可以下移,则将当前方块的4个方块格的X坐标都加一。
6.tetrisMoveLeft() 函数
每当用户按下A键,会执行本函数,控制当前方块左移。
void tetrisMoveLeft(void)
{
int i;
int x,y;
// 先判断是否能左移
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
// 将Y坐标减一,再判断是否超出地图范围或引起冲突
if( (y - 1) < 0 // 1.超出地图范围
|| tetrisMap[x][y - 1]) // 2.方块将要移动的地方已存在方块格
{
return; // 超出地图范围或冲突,无法左移
}
}
// 左移
for(i = 0; i < 4; i++)
{
moveBlock.blockSite[i][1]--;
}
}
// 先判断是否能左移
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
// 将Y坐标减一,再判断是否超出地图范围或引起冲突
if( (y - 1) < 0 // 1.超出地图范围
|| tetrisMap[x][y - 1]) // 2.方块将要移动的地方已存在方块格
{
return; // 超出地图范围或冲突,无法左移
}
}
1.方块左移,就是把方块的Y坐标减一,然后判断新的坐标是否超过游戏地图范围、新的坐标是否已经存在方块;只要有一个条件不满足,就会退出本函数,无法左移。
// 左移
for(i = 0; i < 4; i++)
{
moveBlock.blockSite[i][1]--;
}
2.可以左移,则将当前方块的四个方块格的Y坐标都减一。
7.tetrisMoveRight() 函数
每当用户按下D键,会执行本函数,控制当前方块右移。
和左移函数基本相同,就是Y坐标变为加1,因此不再过多说明。
void tetrisMoveRight(void)
{
int i;
int x,y;
//k = 0;
// 先判断是否能右移
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
// 将Y坐标加1,再判断是否超出地图范围或冲突
if( ((y + 1) >= TETRISWIDTH) // 1.超出地图范围
|| (tetrisMap[x][y + 1]) ) // 2.方块将要移动的地方已存在方块格
{
return; // 超出地图范围或冲突,无法左移
}
}
// 右移
for(i = 0; i < 4; i++)
{
moveBlock.blockSite[i][1]++;
}
}
8.tetrisLoadBlock() 函数
每当新方块标识player.newBlockFlag为1时,执行本函数,将当前控制方块固定在游戏地图。
void tetrisLoadBlock(void)
{
int i;
int x,y;
// 将当前方块载入游戏地图
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
// 只有游戏地图坐标不为空,才会改变游戏地图
if(!tetrisMap[x][y])
{
tetrisMap[x][y] = moveBlock.blockColor;
}
}
}
9.tetrisIsOver() 函数
每当新方块标识player.newBlockFlag为1时,执行本函数,判断游戏是否结束
void tetrisIsOver(void)
{
int i;
int x,y;
// 将新的移动方块与游戏地图比较,如果有冲突格,则游戏结束
for(i = 0; i < 4; i++)
{
x = moveBlock.blockSite[i][0];
y = moveBlock.blockSite[i][1];
// 如果移动方块和游戏地图在同一坐标都存在方块,则游戏结束
if(tetrisMap[x][y])
{
player.gameOverFlag = TRUE;
tetrisDraw();
tetrisQuit();
return;
}
}
}
将预生成方块载入游戏地图时,和游戏地图比较;预生成方块和游戏地图已存在的方块重叠,则预生成方块无法载入游戏地图,游戏无法继续,判断游戏结束。
将游戏结束标识player.gameOverFlag赋1,接着执行tetrisDraw()函数,绘制重叠方块;再执行tetrisQuit()函数,由用户选择是退出游戏还是重新开始游戏。
10.tetrisQuit() 函数
本函数执行有两种方式,一:游戏结束,玩家已无法再继续游戏。
二:玩家按下了ESC键,想要主动退出游戏。
void tetrisQuit(void)
{
int key,flag;
flag = player.gameOverFlag;
// 让用户选择是否退出
key = MessageBox(NULL,"是否退出游戏?","提示",MB_YESNO| MB_SYSTEMMODAL);
switch(key)
{
case IDYES:
player.gameOverFlag = TRUE;
break;
case IDNO:
player.gameOverFlag = FALSE;
break;
default:
break;
}
// 如果游戏已无法继续,且用户不退出游戏,则强制用户选择结束游戏,或重新开始游戏
while(flag
&& !player.gameOverFlag)
{
key = MessageBox(NULL,"检测到游戏已无法继续,\n请选择\"是\": 退出游戏;\n或者选择\"否\": 重启游戏;","提示",MB_YESNO| MB_SYSTEMMODAL);
switch(key)
{
case IDYES:
player.gameOverFlag = TRUE;
break;
case IDNO:
tetrisInit();
player.gameOverFlag = FALSE;
return;
default:
break;
}
}
CLEARKEY();
}
1.flag = player.gameOverFlag;
// 让用户选择是否退出
key = MessageBox(NULL,"是否退出游戏?","提示",MB_YESNO| MB_SYSTEMMODAL);
switch(key)
{
case IDYES:
player.gameOverFlag = TRUE;
break;
case IDNO:
player.gameOverFlag = FALSE;
break;
default:
break;
}
1.flag标识用于区分是游戏结束,还是玩家主动结束游戏,不同方式有不同处理。
生成一个消息弹窗,MB_SYSTEMMODAL是强制弹窗置顶,功能类似于微信置顶功能。
key是用于得到用户选择“是”还是“否”,如果玩家选择退出游戏, 将游戏结束标识player.gameOverFlag置1;反之置0。
// 如果游戏已无法继续,且用户不退出游戏,则强制用户选择结束游戏,或重新开始游戏
while(flag
&& !player.gameOverFlag)
{
key = MessageBox(NULL,"检测到游戏已无法继续,\n请选择\"是\": 退出游戏;\n或者选择\"否\": 重启游戏;","提示",MB_YESNO| MB_SYSTEMMODAL);
switch(key)
{
case IDYES:
player.gameOverFlag = TRUE;
break;
case IDNO:
tetrisInit();
player.gameOverFlag = FALSE;
return;
default:
break;
}
}
CLEARKEY();
2.如果是用户主动结束游戏,那么flag为0,不会进入循环;
如果是游戏已无法继续进行,那么flag为1,会进入循环。
如果进入循环,会生成一个消息弹窗,提示用户是退出游戏,还是重新开始游戏并获取用户选择结果。
通过key获取用户选择, 如果用户选择“是”,那么再次赋值player.gameOverFlag = TRUE,接着退出此函数。
如果用户选择“否”,那么会执行初始化函数tetrisInit(),并赋值player.gameOverFlag = FALSE;
重新开始游戏。
CLEARKEY();是一个宏定义,用于清除按键输入缓冲区。
11.tetrisKeyHandle() 函数
用户按键处理函数,每次用户按下相应按键后,都会执行本函数。
其中的W键改变方块形状(朝向)、A键左移、S键下移、D键右移、ESC退出键都已经在上面的函数说明了,这里就不再复述。
void tetrisKeyHandle(void)
{
switch(player.key)
{
case 'w':
case 'W':
// W键,改变当前方块的朝向
tetrisMoveUp();
break;
case 'a':
case 'A':
// A键,当前方块想左移动
tetrisMoveLeft();
break;
case 's':
case 'S':
// S键,当前方块向下移动
player.moveFlag = TRUE;
player.ttime = GetTickCount();
return;
case 'd':
case 'D':
// D键,当前方块向右移动
tetrisMoveRight();
break;
case ESC:
// 退出键,退出游戏
tetrisQuit();
break;
case 'r':
case 'R':
// R键,重置游戏进度
tetrisReset();
break;
default:
break;
}
// 按键之后,刷新地图
tetrisDraw();
}
1.就剩一个R键重新开始游戏了;
按下R键后,会执行tetrisReset()函数,进行游戏重置,函数详情会在下一个函数说明。
12.tetrisReset() 函数
重置游戏函数,玩家按下R键后,可以选择是否重新开始游戏。
void tetrisReset(void)
{
int key;
key = MessageBox(NULL, "是否重新开始游戏?", "提示", MB_YESNO | MB_SYSTEMMODAL);
CLEARKEY();
switch (key)
{
case IDYES:
tetrisInit();
break;
case IDNO:
break;
default:
break;
}
}
生成一个置顶弹窗,用变量key获取用户点击的按钮(“是”“否”);
如果用户点击的是“是”,则执行tetrisInit()函数,初始化所有游戏数据,重新开始游戏。
如果用户点击的是“否”,则取消重置游戏,本次按键无效。
13.tetrisRemove() 函数
最后一个函数,消除满行函数,每次生成新方块标识player.newBlockFlag置1时,会执行本函数。
void tetrisRemove(void)
{
int i, j, m, n;
int flag, bnum;
bnum = 0;
// 从最下面一行开始,检测并消除满行方块
for (i = TETRISHEIGHT - 1; i >= 0; i--)
{
flag = 0;
for (j = 0; j < TETRISWIDTH; j++)
{
// 只要检测到空格,就不再检测本行
if (!tetrisMap[i][j])
{
flag = 1;
break;
}
}
if (flag)
{
continue;
}
// 满行,消除行数加1
bnum++;
// 检测到满行,消除当前行
for (j = 0; j < TETRISWIDTH; j++)
{
tetrisMap[i][j] = BLOCKNONE;
}
// 消除行上面的所有方块下移一行
for (m = i; m > 0; m--)
{
for (n = 0; n < TETRISWIDTH; n++)
{
tetrisMap[m][n] = tetrisMap[m - 1][n];
}
}
i++; // 由于消除行上面的方块下移了,所以再从当前行检测
}
// 消除行数为0,退出
if (!bnum)
{
return;
}
// 根据消除的行数,增加RANK分数
switch (bnum)
{
case 0:
break;
case 1:
player.rank += 10;
break;
case 2:
player.rank += 20;
break;
case 3:
player.rank += 40;
break;
case 4:
player.rank += 80;
break;
default:
break;
}
// 根据rank分数换算成level
player.level = player.rank / 100;
if (player.level > 10)
{
player.level = 10;
}
}
// 从最下面一行开始,检测并消除满行方块
for (i = TETRISHEIGHT - 1; i >= 0; i--)
{
flag = 0;
for (j = 0; j < TETRISWIDTH; j++)
{
// 只要检测到空格,就不再检测本行
if (!tetrisMap[i][j])
{
flag = 1;
break;
}
}
if (flag)
{
continue;
}
// 满行,消除行数加1
bnum++;
// 检测到满行,消除当前行
for (j = 0; j < TETRISWIDTH; j++)
{
tetrisMap[i][j] = BLOCKNONE;
}
// 消除行上面的所有方块下移一行
for (m = i; m > 0; m--)
{
for (n = 0; n < TETRISWIDTH; n++)
{
tetrisMap[m][n] = tetrisMap[m - 1][n];
}
}
i++; // 由于消除行上面的方块下移了,所以再从当前行检测
}
1.从游戏地图最下面一行开始检测,依次检测到最上面一行;只要检测到一个空白格,则放弃检测本行,检测上一行。
如果当前行是满行,则消除行数bnum加一;然后把当前行清零;清零后,把当前行上面的所有方块全部下移一行;i++是因为所有方块都下移一行了,需要再次从当前行开始检测。
// 消除行数为0,退出
if (!bnum)
{
return;
}
// 根据消除的行数,增加RANK分数
switch (bnum)
{
case 0:
break;
case 1:
player.rank += 10;
break;
case 2:
player.rank += 20;
break;
case 3:
player.rank += 40;
break;
case 4:
player.rank += 80;
break;
default:
break;
}
// 根据rank分数换算成level
player.level = player.rank / 100;
if (player.level > 10)
{
player.level = 10;
}
2.如果没有检测到可消除行(bnum为0),则退出本函数。
根据消除行数bnum,增加player.rank分数:
1行加10分;2行加20分;3行加40分;4行加80。
根据player.rank分数,换算成player.level等级:
1000分以下,等级是分数/100;1000分以上,等级固定为10(MAX)。
总结
至此,代码部分已经结束,从编写俄罗斯方块到结束,代码有经历过各种优化,bug也基本都修正了,可以说是一个比较完善的版本(基本没有BUG)。有任何疑问都可以在评论里提问,谢谢观看。