c/c++ 实现俄罗斯方块小游戏(附全部源码)

目录


前言

使用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)。有任何疑问都可以在评论里提问,谢谢观看。

评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值