C语言实现俄罗斯方块

思路

我设计时,思路历程:

  1. 方块如何旋转
  2. 光标定位
  3. 方块的移动
  4. 方块非法动作取消
  5. 方块生命消亡检查
  6. 消除一行

方块旋转

 一开始想到的笨方法,就是将方块整体融入一个九宫格矩阵中,旋转即使行列进行数据调换,这就需要创建一个3*3的数组以及一个定位坐标作为成员变量的结构体数组,由于太笨,放弃。
 观察到旋转变换与角度有关,于是将方块某一点,视为极点,进行坐标运算,旋转即是θ-π/2,计算结果很简单:
	逆时针:x=y ,y=-x;
	顺时针:x=-y,y=x;
然后再转换为控制台上坐标系上的坐标,即加上极点对应的横纵坐标即可。
==注意:算的时候,要对应上数轴的正方向==

光标定位

本人也没过多理解,如何定位,直接拿来用了。大致就是获取控制台的标准输出句柄,然后传入一个坐标结构体参数给它。 代码片.

//将光标设置跳转到具体坐标
void gotoxy(int x, int y)
{
	COORD coord;
	coord.X = x;
	coord.Y = y;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

方块的移动

前面的实现了,这个方块移动就不难了,就是消除前一个足迹,然后打印新的。然后要注意的是一个方块符"■",在很坐标上占两个字宽,所以坐标的偏移上要*2

/*
	方块动作执行
	参数: flag  int 类型
			ANTICLOCKWISE :逆时针旋转
					CLOCKWISE  :顺时针旋转
						    MOVE_L :左移
							MOVE_R	 : 右移
							MOVE_U:上移
							MOVE_D  : 下移  
*/
void action(int flag) {
	//逆时针旋转  
	if (flag == ANTICLOCKWISE) {
		for (int i = 0; i < 4; i++) {
			if (i == 2)continue;//轴心,可有可无
			int x = tetris[2].x + tetris[i].y - tetris[2].y;
			int y = tetris[2].x + tetris[2].y - tetris[i].x;
			tetris[i].x = x;
			tetris[i].y = y;
		}
	}
	//顺时针旋转
	else if (flag == CLOCKWISE) {
		for (int i = 0; i < 4; i++) {
			if (i == 2)continue;//轴心,可有可无
			int x = tetris[2].x + tetris[2].y - tetris[i].y;
			int y = tetris[i].x + tetris[2].y - tetris[2].x;
			tetris[i].x = x;
			tetris[i].y = y;
		}
	}
	//向左移动	
	else if (flag == MOVE_L) {
		for (int i = 0; i < 4; i++) {
			tetris[i].x += MOVE_L;
		}
	}
	//向右移动
	else if (flag == MOVE_R) {
		for (int i = 0; i < 4; i++) {
			tetris[i].x += MOVE_R;
		}
	}
	else if (flag == MOVE_U) {
		//向上移动
		for (int i = 0; i < 4; i++) {
			tetris[i].y += -1;
		}
	}
	else if (flag == MOVE_D) {
		//向下移动
		for (int i = 0; i < 4; i++) {
			tetris[i].y += 1;
		}
	}
	
}

方块非法动作取消

方块的非法动作行为如:超出活动边界以及碰触到“前人遗骸”。实现是先让动作执行,再检测方块是否越界,是的话,逆做动作还原。

//行为异常进行反转、取消前动作
	if (true ==sign) {
		switch (temp) {
		case UP:
			action(-1 * CLOCKWISE);
			break;
		case LEFT:
			action(-1 * MOVE_L);
			break;
		case RIGHT:
			action(-1 * MOVE_R);
			break;
		default:
			action(-1 * MOVE_D);
			break;
		}
	}

有点不想写了

方块生命消亡检查

生命检查,就是通过先判断上一个方块动作是否为下移,在这个前提下,判断方块坐标是否触及地图的非活动区域(即已经有方块存在了)

//死亡行为检查
		if (map[tetris[i].y][tetris[i].x] == 1) {
			sign = true;
			if (temp != UP &&  temp != LEFT &&  temp != LEFT &&  temp != RIGHT)
				status = DEAD;
			break;
		}

消除一行

消除一行需要检查是否存在满足消除条件,需要对全地图遍历,同时消除后,还需对地图更正,填充消除的一行,集体下移。

/*
	检查是否可以消行
*/
void updateMap()
{
	int record[HEIGTH] = {0}; //记录满行 行下标
	int top = 0,count;				// top 记录栈顶,最小为0,0代表没有记录      **  count 计数
	for (int i = HEIGTH - 2; i > 0; i--) {
		count = 0;
		for (int j = 1; j < WIDTH - 1; j++) {
			if (map[i][j] == 1) {
				count++;
			}
			else break;
		}
		if (count == WIDTH - 2) record[top++] = i;
	}
	score = score + top * 10;  //计算分值
	//top大于零,则取出栈顶元素  ---被消除的行下标
	while (top > 0) {
		for (int i = record[top-1]; i > 1; i--) {
			for (int j = 0; j < WIDTH - 1; j++) {
				map[i][j] = map[i - 1][j];
			}
		}
		system("cls"); //完全清屏
		top--;
	}	
}

完整代码

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>//windows编程头文件
#include <time.h>
#include <conio.h>//控制台输入输出头文件

#define   CLOCKWISE  2				//顺时针旋转
#define   ANTICLOCKWISE -2		//逆时针旋转
#define   MOVE_L -1						//	左移
#define   MOVE_R  1						//	右移
#define   MOVE_U -3					//上移
#define   MOVE_D  3					//下移
#define	DEAD 0							//方块死亡
#define   LIVE 1								//方块存活
#define   UP 72								//向上键   ** 旋转方块
#define   DOWN 80						//快速到底
#define   LEFT 75							//向左移动
#define   RIGHT 77						//向右移动
#define   WIDTH 20						//地图长度
#define   HEIGTH 30						//地图宽度
#define   NORMAL_SPEED 200	//正常速度
#define   GAME_OVER 0				//游戏结束
#define   PLAY_GAME  1				//游戏开始
void action(int flag);
void gotoxy(int x, int y);
void draw_block();
void get_direction();
void play();
void update(int dir);
void draw_map();
void init();
void createBlock();
bool check();
void clean();
void updateMap();
struct  
{
	int x;
	int y;
}tetris[4] = { { 1,WIDTH/2 },{ 1,WIDTH / 2 },{ 1,WIDTH / 2 },{1,WIDTH / 2 } };
unsigned char direction=0;
int map[HEIGTH][WIDTH]{0};
int speed= NORMAL_SPEED;
bool status = DEAD;
int score = 0;
bool gameStatus = PLAY_GAME;

int main() {
	//获取标准输入句柄
	HANDLE fd = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cinfo;
	cinfo.bVisible = 0;  //将光标设置为不可见
	cinfo.dwSize = 1;
	SetConsoleCursorInfo(fd, &cinfo);
	//初始化
	init();
	while (1)
	{
		play();
		if (GAME_OVER == gameStatus) {
			system("cls");
			gotoxy(WIDTH / 2, HEIGTH / 2);
			printf("游戏结束,您的得分:%d", score);
			gotoxy(WIDTH / 2, HEIGTH / 2+1);
			printf("按回车结束");
			getchar();
			break;
		}
	}
	return 0;
}

/**
*控制台按键所代表的数字
*“↑”:72
*“↓”:80
*“←”:75
*“→”:77
*/

//将光标设置跳转到具体坐标
void gotoxy(int x, int y)
{
	COORD coord;
	coord.X = x;
	coord.Y = y;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

/*
	绘制方块
*/
void draw_block()
{
	for (int i = 0; i < 4; i++) {
		gotoxy(tetris[i].x*2, tetris[i].y);
		printf("■");
	}
	gotoxy(0, 0); //将光标移到远处,防止输入其他字符影响
}
/*
	获取输入的方向
	上:旋转
	下:快速到底
	左:左移
	右:右移
*/
void get_direction()
{
	if (_kbhit())//如果用户按下了键盘中的某个键
	{
		//清空缓冲区
		rewind(stdin);
		//getch()读取方向键的时候,要读取两次,第一次调用返回0或者224,第二次调用返回的才是实际值
		direction = _getch();//第一次调用返回的不是实际值
		if (direction != 0 || direction != 224 || direction ==EOF) return;   //非方向键按下会产生干扰
		direction = _getch();//第二次调用返回实际值
		rewind(stdin);
	}
}
/*
	开始游戏
*/
void play()
{
	clean();  //清除方块足迹
	get_direction();  //获取用户选择 
	if (!check()) {		//检查方块状态行为  
		createBlock();
		status = LIVE;
		draw_map();
	}
	draw_map();
	draw_block();
	Sleep(speed);
}
/*
	更新数据
*/
void update(int dir)
{
	switch (dir) {
	case UP:
		action(CLOCKWISE);
			break;
	case LEFT:
		action(MOVE_L);
		break;
	case RIGHT:
		action(MOVE_R);
		break;
	case DOWN:
		speed = 0;
	default:										//	默认下移
		action(MOVE_D);
		break;
	}
	direction = 0;
}
/*
	绘制地图
*/
void draw_map()
{
	
	for (int i = 0; i < HEIGTH; i++) {
		for (int j = 0; j < WIDTH; j++) {
			if (map[i][j] == 1) {
				gotoxy(j*2, i);
				printf("■");
			}
		}
	}
}

/*
	初始化
	设置边界
	创建方块、地图数据模型  设置状态:live
*/
void init()
{
	for (int j = 0; j < WIDTH; j++) {
		map[0][j] = 1;
		map[HEIGTH - 1][j] = 1;
	}
	for (int i = 0; i < HEIGTH; i++) {
		map[i][0] = 1;
		map[i][WIDTH - 1] = 1;
	}
	createBlock();
	status = LIVE;
}

/*
	创建方块
	随机数生成:0-3 之间的数
	赋值四种类型之一的数据模型
	方向初始化为:0
	速度初始化为:正常速度 NORMAL_SPEED 200
*/
void createBlock()
{
	srand((unsigned int)time(NULL));
	int type = rand() % 4;
	switch (type)
	{
	case 0:
		tetris[0] = { WIDTH / 2 - 1 ,1};
		tetris[1] = { WIDTH / 2 - 1 ,2};
		tetris[2] = { WIDTH / 2       ,2};
		tetris[3] = { WIDTH / 2+1  ,2 };
		break;
	case 1:
		tetris[0] = { WIDTH / 2       ,1 };
		tetris[1] = { WIDTH / 2 +1 ,1 };
		tetris[2] = { WIDTH / 2       ,2 };
		tetris[3] = { WIDTH / 2 +1 ,2 };
		break;
	case 2:
		tetris[0] = { WIDTH / 2 - 1  , 2 };
		tetris[1] = { WIDTH / 2		  , 1 };
		tetris[2] = { WIDTH / 2       , 2 };
		tetris[3] = { WIDTH / 2 +1 , 2 };
		break;
	case 3:
		tetris[0] = { WIDTH / 2 - 2   , 2 };
		tetris[1] = { WIDTH / 2 - 1  , 2 };
		tetris[2] = { WIDTH / 2        , 2 };
		tetris[3] = { WIDTH / 2 + 1 , 2 };
		break;
	default:
		break;
	}
	direction = 0;
	speed = NORMAL_SPEED;
}
/*
	检查方块行为、检查游戏状态
	参数:无
	返回:  true  LIVE   方块存活;	
				  false  DEAD 方块死亡;
*/
bool check()
{
	int temp = direction;
	update(temp);
	bool sign=false;//行为取消标识
	for (int i = 0; i < 4; i++) {
		//碰壁行为检查
		if (tetris[i].y == 0 || tetris[i].x == 0 || tetris[i].x ==WIDTH-1) {
			sign = true;
			break;
		}
		//死亡行为检查
		if (map[tetris[i].y][tetris[i].x] == 1) {
			sign = true;
			if (temp != UP &&  temp != LEFT  &&  temp != RIGHT)
				status = DEAD;
			break;
		}
	}
	//行为异常进行反转、取消前动作
	if (true ==sign) {
		switch (temp) {
		case UP:
			action(-1 * CLOCKWISE);
			break;
		case LEFT:
			action(-1 * MOVE_L);
			break;
		case RIGHT:
			action(-1 * MOVE_R);
			break;
		default:
			action(-1 * MOVE_D);
			break;
		}
	}
	//方块死亡,将数据加入地图数据模型
	if (DEAD == status) {
		for (int i = 0; i < 4; i++) {
			map[tetris[i].y][tetris[i].x] = 1;
		}
	}
	updateMap(); //更新地图,检查是否存在消行
	//检查是否游戏结束
	for (int i = 1; i < WIDTH - 1; i++) {
		if (map[1][i] == 1) {
			gameStatus = GAME_OVER;
			break;
		}
	}
	return status;
}
/*
	消除方块足迹
*/
void clean()
{
	for (int i = 0; i < 4; i++) {
		gotoxy(tetris[i].x * 2, tetris[i].y);
		printf("  ");
	}
}
/*
	检查是否可以消行
*/
void updateMap()
{
	int record[HEIGTH] = {0}; //记录满行 行下标
	int top = 0,count;				// top 记录栈顶,最小为0,0代表没有记录      **  count 计数
	for (int i = HEIGTH - 2; i > 0; i--) {
		count = 0;
		for (int j = 1; j < WIDTH - 1; j++) {
			if (map[i][j] == 1) {
				count++;
			}
			else break;
		}
		if (count == WIDTH - 2) record[top++] = i;
	}
	score = score + top * 10;  //计算分值
	//top大于零,则取出栈顶元素  ---被消除的行下标
	while (top > 0) {
		for (int i = record[top-1]; i > 1; i--) {
			for (int j = 0; j < WIDTH - 1; j++) {
				map[i][j] = map[i - 1][j];
			}
		}
		system("cls"); //完全清屏
		top--;
	}	
}
/*
	方块动作执行
	参数: flag  int 类型
			ANTICLOCKWISE :逆时针旋转
					CLOCKWISE  :顺时针旋转
						    MOVE_L :左移
							MOVE_R	 : 右移
							MOVE_U:上移
							MOVE_D  : 下移  
*/
void action(int flag) {
	//逆时针旋转  
	if (flag == ANTICLOCKWISE) {
		for (int i = 0; i < 4; i++) {
			if (i == 2)continue;//轴心,可有可无
			int x = tetris[2].x + tetris[i].y - tetris[2].y;
			int y = tetris[2].x + tetris[2].y - tetris[i].x;
			tetris[i].x = x;
			tetris[i].y = y;
		}
	}
	//顺时针旋转
	else if (flag == CLOCKWISE) {
		for (int i = 0; i < 4; i++) {
			if (i == 2)continue;//轴心,可有可无
			int x = tetris[2].x + tetris[2].y - tetris[i].y;
			int y = tetris[i].x + tetris[2].y - tetris[2].x;
			tetris[i].x = x;
			tetris[i].y = y;
		}
	}
	//向左移动	
	else if (flag == MOVE_L) {
		for (int i = 0; i < 4; i++) {
			tetris[i].x += MOVE_L;
		}
	}
	//向右移动
	else if (flag == MOVE_R) {
		for (int i = 0; i < 4; i++) {
			tetris[i].x += MOVE_R;
		}
	}
	else if (flag == MOVE_U) {
		//向上移动
		for (int i = 0; i < 4; i++) {
			tetris[i].y += -1;
		}
	}
	else if (flag == MOVE_D) {
		//向下移动
		for (int i = 0; i < 4; i++) {
			tetris[i].y += 1;
		}
	}
	
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值