Linux下C语言实现俄罗斯方块——详细版

一、思路

1、创建界面map数组边界填充值 1。
2、rand生成随机数确定下移的图形。
3、界面显示,通过移动光标输出确定界面位置。

4、图形显示,通过光标移动输出图形,四个数据块每一块对应一个小方块。

      下一个输出next为 反L3:{(0,0), (1,0), (0,1), (0,2)}。

5、图形下移,通过y变化改变位置下移。
6、下移完成,判断图形到界面边界,将图形到达位置写入map, map填充值2。
7、判断一行是否都填满,填满后忽略本行,将此行以上的拷贝下来,最上面一行不用拷贝(最上面一行如果有图形游戏已经结束了)。
8、判断结束,判断最上面那一行map中有 2 ,有则结束。  
9、每次输出完,清理输出界面system("clear"),是一帧一帧输出后形成流畅画面。
10、控制颜色输出详细内容:https://mrleef.blog.csdn.net/article/details/125542134?spm=1001.2014.3001.5506

 二、其他

      本次用的Linux版本为,ubuntu-18.04.6-desktop-amd64.ios,更新20的版本也不错,个人感觉这个版本的更好用,CentoS不太好用,软件安装也不方便。

      编辑器是VSCode和vim,VSCode是开源的,没有版权问题。

      运行是用终端运行的。编译命令:gcc game.c   -o game  运行:./game

三、代码

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>

#define TTY_PATH  "/dev/tty"                      //系统指令
#define STTY_US  "stty raw -echo -F "      //系统指令
#define STTY_DEF  "stty -raw echo -F "    //系统指令

#define WIDE 18
#define HEIGHT 20
#define INFO_AREA 6
#define SPEED 8
#define Y_NEXT 4                             // x_next, y_next下一个图形在方框的位置
#define X_NEXT WIDE - INFO_AREA / 2 - 1

int score = 0;                                       //分数
int map[HEIGHT][WIDE] = { 0 };  //界面

int x, y;                                                  // x, y 正在下降图形的位置
int moveDown_count = 0;
int shapeIndex, shapeIndex_next;  //shapeIndex:下移图形 shapeIndex_next:下一个图形

struct Point {						             //中心点的偏移量,中心点以此延伸获得各形状点的坐标
	int shape_x;
	int shape_y;
};

struct Point shapes[19][4] = {
    {{0, 0},{-1, 0},{1, 0},{2, 0}},      //横条        	通过光标移动输出图形
    {{0, 0},{0, -1},{0, 1},{0, 2}},      //竖条	        每一个图形由四个方块组成
    {{0,0},{-1,-1},{-1,0},{0,-1}},     //方块	       四组,每一组对应输出一个方块 
    {{0, 0},{0, -1},{0, -2},{1, 0}},    //正L1	  0, 1...反映光标移动情况
    {{0,0},{0,1},{1,0},{2,0}},           //正L2	
    {{0,0},{-1,0},{0,1},{0,2}},         //正L3	
    {{0,0},{0,-1},{-1,0},{-2,0}},      //正L4	
    {{0,0},{-1,0},{0,-1},{0,-2}},      //反L1	
    {{0,0},{0,-1},{1,0},{2,0}},         //反L2	
    {{0,0},{1,0},{0,1},{0,2}},           //反L3	
    {{0,0},{-1,0},{-2,0},{0,1}},        //反L4	
    {{0,0},{-1,0},{1,0},{0,-1}},        //T1	
    {{0,0},{0,1},{0,-1},{1,0}},         //T 2	
    {{0,0},{-1,0},{1,0},{0,1}},         //T 3	
    {{0,0},{-1,0},{0,-1},{0,1}},        //T 4	
    {{0,0},{1,0},{0,-1},{-1,-1}},      //正 Z 1	
    {{0,0},{1,-1},{0,1},{1,0}},         //正Z2	
    {{0,0},{1,-1},{-1,0},{0,-1}},      //反z1	
    {{0,0},{-1,-1},{-1,0},{0,1}}       //反Z2
};

void setFrame();              //构建边宽
void showMap();             //展示界面
void showPoint(int x, int y,int bright,int color);
void showShape();         //输出图形
void creatNewShape();//创建新的图形
int moveDown();             //图形下降,判断是否还可以移动
void addToMap();           //把确定位置的保存在map中
int overCheak();              //确定游戏是否结束
int get_char();                  //获取输入
void control(char str);  //控制移动
void chageShape();       //改变形状 
void moveLeft();             //左移
void moveRight();          //右移
void clearLines();           //清除已满行

int main()
{
    system(STTY_US TTY_PATH);  //输入阻塞
    setFrame();                                      //初始化map
    showMap();                                     //展示界面
    srand(time(0));
    shapeIndex = rand() % 19;
    creatNewShape();                        //创建下一个图形
    
    while(1) {
        system("clear");                        //清除界面显示内容
        showShape();                             //展示方块
        showMap();                                 //展示界面
        if (moveDown()) {                     //判断移动
            addToMap();                           //不可移动后,写入到map
            if (overCheak()) {                   //检查是否游戏结束
                break;
            }
            clearLines();                            //判断一行是否满,并清理
            shapeIndex = shapeIndex_next;
            creatNewShape();                //创建下一个图形
        }
        char str = get_char();              //得到输入指令
        if (str == 3) {                                 //是否为ctrl+C
            break;
        }
        control(str);                                 //控制移动
        usleep(1000*50);                       //阻碍运行速度
    }
    system(STTY_DEF TTY_PATH); //输入阻塞结束
    printf("\033[21;0H");
}

/**
 * @brief Set the Frame object 
 */
void setFrame()
{
    int i;
    for(i = 0; i < WIDE; i++) {              //界面两条横线
        map[0][i] = 1;
        map[HEIGHT - 1][i] = 1;
    }
    for(i = 0; i < HEIGHT; i++) {        //界面三条竖线
        map[i][0] = 1;
        map[i][WIDE - INFO_AREA - 1] = 1;
        map[i] [WIDE - 1] = 1;
    }
}

/**
 * @brief 展示界面 
 */
void showMap()
{
    int i, j;
	for(i = 0; i < HEIGHT; i++) {
		for(j = 0;j < WIDE; j++) {
			if ( map[i][j] == 1 || map[i][j] == 2) {
				showPoint(j, i, 1, 32 + map[i][j]);   //行标i,列表j与坐标系横纵坐标的值是相反的
			}
		}
	} 
	printf("\033[2;27H");          //   \033[X;XH  光标移动格式
	printf("\033[31mNext:"); 

	printf("\033[11;27H"); 
	printf("\033[32mScore:");   

	printf("\033[12;26H"); 
	printf(" \033[32m%3d", score);   
	fflush(stdout);  //清理缓存,使画面动作流畅
}

/**
 * @brief 输出一个方块
 * 
 * @param x1 光标横轴位置
 * @param y1 光标纵轴位置
 * @param bright 颜色亮度
 * @param color   颜色种类
 */
void showPoint(int x1, int y1, int bright, int color)
{
    printf("\033[%d;%dH", y1 + 1, x1 * 2 + 1);                   //光标位置
    printf("\033[%d;%dm■ \033[0m", bright, color);  //颜色深浅,颜色种类
}

/**
 * @brief 创建下一个图形和重置光标位置
 */
void creatNewShape()
{ 
    y = 2;
    x = (WIDE - INFO_AREA) / 2;
    shapeIndex_next = rand() % 19;
}


/**
 * @brief  显示正在下降的图形和显示下一个图形
 */
void showShape()
{
    int i;
    for(i = 0; i < 4; i++) {     //显示正在下降的图形
        showPoint(x + shapes[shapeIndex][i].shape_x, y + shapes[shapeIndex][i].shape_y, 1, 31);
    }
    for(i = 0; i < 4; i++) {     //显示下一个图形
        showPoint(X_NEXT + shapes[shapeIndex_next][i].shape_x, Y_NEXT + shapes[shapeIndex_next][i].shape_y, 1, 31);
    }
}

/**
 * @brief 图形下降和控制移速
 * 
 * @return int 1为已经处地,0还可移动
 */
int moveDown()
{
    int i;
    if (moveDown_count < SPEED) {   //控制移动速度
		moveDown_count++;                    //移动准确性
		return 0;                                               //也就是两次进入函数图形下移一格
	}
	moveDown_count = 0;
	for (i = 0; i < 4; i++) {
		int dx = x + shapes[shapeIndex][i].shape_x;
		int dy = y + shapes[shapeIndex][i].shape_y + 1;//原来位置的map[x][y]已经是1
		
		if (map[dy][dx] == 1 || map[dy][dx] == 2) {
			return 1;                     //不可移动
		}
	}
    y++;
    return 0;
}

/**
 * @brief  把不能移动的方块保存到map中
 */
void addToMap()
{
    int i;
    for(i = 0; i < 4; i++) {
        map[y + shapes[shapeIndex][i].shape_y][x + shapes[shapeIndex][i].shape_x] = 2;
    }
}

/**
 * @brief 检测游戏是否结束
 * 
 * @return int 0为结束,1还可继续
 */
int overCheak()
{
    int i;
    for (i = 0; i < WIDE; i++) {
        if (map[1][i] == 2) {
            return 1;
        }
    }
    return 0;
}

/**
 * @brief Get the char object
 * 
 * @return int 返回输入的字符对应的ASCII值
 */
int get_char()	
{
    fd_set rfds;
    struct timeval tv;
    int ch = 0;
    FD_ZERO(&rfds);
   FD_SET(0, &rfds);
    tv.tv_sec = 0;
    tv.tv_usec = 10; 
    if (select(1, &rfds, NULL, NULL, &tv) > 0) {
        ch = getchar(); 
    }
    return ch;
}

/**
 * @brief 判断输入,得出下一步移动
 * 
 * @param str 输入的字符
 */
void control(char str)
{
    switch (str) {
    case 'w':                          //变形
        chageShape();
        break;
    case 'a':                           //左移
        moveLeft();
        break;
    case 's':                            //下移
        moveDown_count = moveDown_count + SPEED;
        break;
    case 'd':                            //右移
        moveRight();
        break;
    }
}

/**
 * @brief  改变形状
 * 原理:通过改变shapeIndex的值
 */
void chageShape()
{
	int ts = shapeIndex;
	switch(ts) {
	case 0:
		ts++;
		break;
	case 1:	
		ts = 0;
		break;
	case 2:
		break;
	case 3:
	case 4:
	case 5:
		ts++;
		break;
	case 6:
		ts = 3;
		break;
	case 7:
	case 8:
	case 9:
		ts++;
		break;
	case 10:
		ts = 7;
		break;
	case 11:
	case 12:
	case 13:
		ts++;
		break;
	case 14:
		ts = 11;
		break;
	case 15:
		ts++;
		break;
	case 16:
		ts = 15;
		break;
	case 17:
		ts++;
		break;
	case 18:
		ts = 17;
		break;
	}
	//ts是假设变形后的图形值
	int i; 
	//判断假设的图形是否发生碰撞
	for(i = 0;i < 4;i++) {
		int cx = x+shapes[ts][i].shape_x;
		int cy = y+shapes[ts][i].shape_y;
		if (map[cy][cx]==1 || map [cy][cx]==2) {
			return;
		}
	}
	shapeIndex = ts; //完成变形
}

/**
 * @brief  左移
 */
void moveLeft()
{
    int i;
    for (i = 0; i < 4; i++) {
        if(map[y + shapes[shapeIndex][i].shape_y][x + shapes[shapeIndex][i].shape_x - 1] == 1
        || map[y + shapes[shapeIndex][i].shape_y][x + shapes[shapeIndex][i].shape_x - 1] == 2) {
            return;
        }
    }
    x--;
}

/**
 * @brief 右移
 */
void moveRight()
{
    int i;
    for(i = 0; i < 4; i++) {
        if (map[y + shapes[shapeIndex][i].shape_y][x + shapes[shapeIndex][i].shape_x + 1] == 1
        || map[y + shapes[shapeIndex][i].shape_y][x + shapes[shapeIndex][i].shape_x + 1] == 2) {
            return;
        }
    }
    x++;
}

/**
 * @brief  判断一行是否满,并清理,拷贝上面内容
 */
void clearLines() 
{
    int i, j, a;
    for (i = HEIGHT - 2; i > 0; i--) {
        a = 1;
        for(j = 1; j < WIDE - INFO_AREA - 1;  j++) {  //确定是否一行排满
            if (map[i][j] != 2) {
                a = 0;
                break;
            } 
        }
    
        if (a == 1) {
            score++;
            int n;
            for(n = i; n > 1 ; n--) {   //把上面的拷贝下来
                for(j = 0; j < WIDE - INFO_AREA - 1;  j++) {
                    map[n][j] = map[n- 1][j];
                }
            }
            i++;              //防止有多行一起排满
        }
    }
    fflush(stdout); //清理缓存,使画面动作流畅
}

有什么问题欢迎留言私信,必回!!!

去克制自己纠正别人的欲望,收起你改造他人的执着!人教人是教不会的。

 

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值