1. 俄罗斯方块原理
1.1 延时函数和清屏函数
Linux系统函数
include <stdlib.h>
usleep(50000); //50毫秒 1/20 秒
system(“clear”);//system 函数调用系统命令
实现刷帧的效果:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i;
for(i = 0; i < 10;i++)
{
printf("%d\n",i);
usleep(1000*1000);
system("clear");
}
return 0;
}
1.2 清空缓存
输入缓存:stdin
输出缓存:stdout
fflush(stdout); //清缓存
1.3 非阻塞输入
scanf getchar gets 阻塞性输入,不输入程序就一直等待
不输入程序也不等待,叫非阻塞输入。
getChar :非阻塞输入
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define TTY_PATH "/dev/tty"
#define STTY_US "stty raw -echo -F"
#define STTY_DEF "stty -raw echo -F"
//非阻塞输入:
int getChar()
{
fd_set rfds; //用于表示文件描述符集合
struct timeval tv; //用于设置超时时间
int ch = 0;
//函数将文件描述符集合rfds清空,并将标准输入文件描述符0添加到集合中
FD_ZERO(&rfds);
FD_SET(0,&rfds);
//设置超时时间tv的秒数为0,微秒数为10
/*
这意味着在调用select函数时,
如果在10微秒内有可读取的输入,select函数将返回大于0的值
*/
tv.tv_sec = 0;
tv.tv_usec = 10;
/*
函数调用select函数,传递参数1(最大文件描述符值加1)
文件描述符集合rfds作为读取集合,NULL作为写入集合和错误集合,以及超时时间tv。
如果select函数返回的值大于0,表示在超时时间内有可读取的输入。
在这种情况下,函数调用getchar函数来获取一个字符,并将其赋值给变量ch
*/
if(select(1,&rfds,NULL,NULL,&tv) > 0)
{
ch = getchar();
}
return ch;
}
int main()
{
//屏蔽掉终端的正常输入
system(STTY_US TTY_PATH);// "stty raw -echo -F""/dev/tty"
while(1)
{
printf("hehe\n"); //屏幕会一直输出hehe
char c = getChar();//非阻塞输入
printf("%c\n", c); //可以输入一个字符,打印到终端,终端继续输出hehe
//由于屏蔽了终端的正常输入,ctrl + c已经不能结果程序了
//我们需要自己判断输入ctrl + c之后的功能,不是必须使用ctrl + c,任意ASCII表中的字符都可以
if(c == 3) // 输入ctrl + c 获得到的ascii值是3
{
//让终端恢复正常
system(STTY_DEF TTY_PATH);//"stty -raw echo -F""/dev/tty"
break;
}
usleep(1000*1000);
}
return 0;
}
1.4 在屏幕上移动一个方块
左上角是 0 0 点
X轴向右递增,Y轴向下递增
void drawPoint(int x,int y) //根据x,y值输出一个方块
{
printf(“\033[%d;%dH”,y+1,x*2+1);
printf(“\033[1;36m■ \033[0m”);
}
移动方块的本质是移动一对坐标 x,y(通过asdw)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define TTY_PATH "/dev/tty"
#define STTY_US "stty raw -echo -F"
#define STTY_DEF "stty -raw echo -F"
int getChar();//非阻塞输入
void drawPoint(int x,int y);//在x y位置画一个方块
int x = 5,y = 5; //图形坐标 全局变量
int main()
{
//屏蔽掉终端的正常输入
system(STTY_US TTY_PATH);// "stty raw -echo -F""/dev/tty"
while(1)
{
int ctrl = getChar();//输入
if(ctrl == 3)
{
//让终端恢复正常
system(STTY_DEF TTY_PATH);//"stty -raw echo -F""/dev/tty"
break;
}
switch(ctrl)
{
case 'a':
x--;
break;
case 's':
y++;
break;
case 'd':
x++;
break;
case 'w':
y--;
break;
}
drawPoint(x,y);
fflush(stdout);//清缓存,可以马上看到结果
//刷帧
usleep(1000*1000);
system("clear");
}
return 0;
}
int getChar()
{
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;
}
void drawPoint(int x,int y)
{
printf("\033[%d;%dH",y+1,x*2+1);
printf("\033[1;36m■ \033[0m");
}
1.5 在屏幕输出 L 方块
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define TTY_PATH "/dev/tty"
#define STTY_US "stty raw -echo -F"
#define STTY_DEF "stty -raw echo -F"
int getChar();//非阻塞输入
void drawPoint(int x,int y);//在x y位置画一个方块
int x = 5,y = 5; //图形坐标 全局变量
struct Point
{
int x;
int y;
};
// 数组中的数据是图形中各个点的偏移量
struct Point shapi[4] = {{0.0},{0,-1},{0,-2},{1,0}};
int main()
{
//屏蔽掉终端的正常输入
system(STTY_US TTY_PATH);// "stty raw -echo -F""/dev/tty"
while(1)
{
int ctrl = getChar();//输入
if(ctrl == 3)
{
//让终端恢复正常
system(STTY_DEF TTY_PATH);//"stty -raw echo -F""/dev/tty"
break;
}
switch(ctrl)
{
case 'a':
x--;
break;
case 's':
y++;
break;
case 'd':
x++;
break;
case 'w':
y--;
break;
}
int j;
for(j = 0; j < 4;j++)
{
drawPoint(x + shapi[j].x, y + shapi[j].y);
}
fflush(stdout);//清缓存,可以马上看到结果
//刷帧
usleep(1000*1000);
system("clear");
}
return 0;
}
int getChar()
{
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;
}
void drawPoint(int x,int y)
{
printf("\033[%d;%dH",y+1,x*2+1);
printf("\033[1;36m■ \033[0m");
}
1.6 自动变形
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define TTY_PATH "/dev/tty"
#define STTY_US "stty raw -echo -F"
#define STTY_DEF "stty -raw echo -F"
int getChar();//非阻塞输入
void drawPoint(int x,int y);//在x y位置画一个方块
int x = 5,y = 5; //图形坐标 全局变量
struct Point
{
int x;
int y;
};
// 数组中的数据是图形中各个点的偏移量,二维数组的第一个角标是用来选择图形的
struct Point shapi[4][4] = {
{{0.0},{0,-1},{0,-2},{1,0}},
{{0.0},{0,1},{1,0},{2,0}},
{{0.0},{-1,0},{0,1},{0,2}},
{{0.0},{0,-1},{-1,0},{-2,0}}
};
int main()
{
//屏蔽掉终端的正常输入
system(STTY_US TTY_PATH);// "stty raw -echo -F""/dev/tty"
while(1)
{
int ctrl = getChar();//输入
if(ctrl == 3)
{
//让终端恢复正常
system(STTY_DEF TTY_PATH);//"stty -raw echo -F""/dev/tty"
break;
}
switch(ctrl)
{
case 'a':
x--;
break;
case 's':
y++;
break;
case 'd':
x++;
break;
case 'w':
y--;
break;
}
int i,j;
system("clear");
for(i = 0; i < 4;i++) //改变i值实现变形
{
for(j = 0; j < 4;j++)
{
drawPoint(x + shapi[i][j].x, y + shapi[i][j].y);
}
fflush(stdout);//清缓存,可以马上看到结果
//刷帧
usleep(1000*1000);
system("clear");
}
}
return 0;
}
int getChar()
{
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;
}
void drawPoint(int x,int y)
{
printf("\033[%d;%dH",y+1,x*2+1);
printf("\033[1;36m■ \033[0m");
}
1.7 消行
1.7.1 一维数组
移动一维数组,判断是否为0,是0向前移动,将所有0都放到前面。
#include <stdio.h>
int main()
{
int a[10] = {5,2,0,3,0,0,1,0,6,0};
int i;
int count = 0;
for(i = 9;i >= 0; i--)
{
if(a[i] == 0)
{
count++;
}
else if(count > 0)
{
a[i+count] = a[i];
a[i] = 0;
}
}
for(i = 0; i < 10;i++)
{
printf("%d ",a[i]);
}
printf("\n");
return 0;
}
1.7.2 二维数组
移动二维数组,将全为1的,去掉,其余的往下放
#include <stdio.h>
int isFull(int* arr, int len); //判断数组里面的数值是否全部为1
void moveline(int* dst,int* src, int len);//将src数组赋值到dst数组里面
void clearline(int* src,int len); //将数组值全部变为0
int main()
{
int a[10][3] = {
{1,0,1},
{1,0,0},
{1,1,1},
{1,0,1},
{1,1,1},
{1,1,1},
{0,1,0},
{1,1,1},
{0,1,1},
{1,1,1}
};
int i;
int count = 0;
for(i = 9;i >= 0; i--)
{
//a[i]是int类型的一维数组,判断数组值是否全部为1
if(isFull(a[i],3))
{
count++;
clearline(a[i],3); //将数组值全部为1的数组,置0
}
else if(count > 0)
{
moveline(a[i+count],a[i],3); //将数组值不全部为1的,往后移动
}
}
int j;
//遍历打印二维数组
for(i = 0; i < 10;i++)
{
for(j = 0; j < 3;j++)
{
printf("%d ",a[i][j]);
}
printf("\n");
}
printf("\n");
return 0;
}
int isFull(int* arr, int len)
{
int i;
for(i = 0;i < len;i++)
{
if(arr[i] != 1)
{
return 0;
}
}
return 1;
}
void moveline(int* dst,int* src, int len)
{
int i;
for(i = 0; i < len; i++)
{
dst[i] = src[i];
src[i] = 0;
}
}
void clearline(int* src,int len)
{
int i;
for(i = 0; i < len; i++)
{
src[i] = 0;
}
}
根据二维数组的值画图
#include <stdio.h>
#include <stdlib.h>
int getChar();//非阻塞输入
int isFull(int* arr, int len); //判断数组里面的数值是否全部为1
void moveline(int* dst,int* src, int len);//将src数组赋值到dst数组里面
void clearline(int* src,int len); //将数组值全部变为0
void show();
void drawPoint(int x,int y);//在x y位置画一个方块
int a[10][3] = {
{1,0,1},
{1,0,0},
{1,1,1},
{1,0,1},
{1,1,1},
{1,1,1},
{0,1,0},
{1,1,1},
{0,1,1},
{1,1,1}
};
int main()
{
system("clear");
show();
int i;
int count = 0;
for(i = 9;i >= 0; i--)
{
//a[i]是int类型的一维数组,判断数组值是否全部为1
if(isFull(a[i],3))
{
count++;
clearline(a[i],3); //将数组值全部为1的数组,置0
}
else if(count > 0)
{
moveline(a[i+count],a[i],3); //将数组值不全部为1的,往后移动
}
}
getChar();//按任意键显示结果
system("clear");
show();
printf("\n");
return 0;
}
int isFull(int* arr, int len)
{
int i;
for(i = 0;i < len;i++)
{
if(arr[i] != 1)
{
return 0;
}
}
return 1;
}
void moveline(int* dst,int* src, int len)
{
int i;
for(i = 0; i < len; i++)
{
dst[i] = src[i];
src[i] = 0;
}
}
void clearline(int* src,int len)
{
int i;
for(i = 0; i < len; i++)
{
src[i] = 0;
}
}
void show()
{
int i,j;
for(i = 0; i < 10;i++)
{
for(j = 0; j < 3;j++)
{
if(a[i][j] == 1)
{
drawPoint(j,i);
}
}
}
}
void drawPoint(int x,int y)
{
printf("\033[%d;%dH",y+1,x*2+1);
printf("\033[1;36m■ \033[0m");
}
int getChar()
{
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;
}
2. 俄罗斯方块的实现
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#define TTY_PATH "/dev/tty"
#define STTY_US "stty raw -echo -F"
#define STTY_DEF "stty -raw echo -F"
#define WIDTH 20
#define HEIGHT 20
#define SPEED 8
int getChar();//非阻塞输入
void drawPoint(int x,int y);//在x y位置画一个方块
int x, y;//图形的坐标
int currentShap;//表示当前图形的角标,它是shaps数组的第一个角标
//表示屏幕的二维数组,元素值1表示有图形,0表示没有图形,2表示边框
int screen[HEIGHT][WIDTH] = {0};
void initScreen(); //初始化屏幕二维数组
void draw(); //画图形
void createShap();//创建图形
int userCtrl(); //用户控制
void changeShap();//变形
int collideChange();
//碰撞
int collideLeft();
int collideRight();
int collideDown();
void moveDown();//向下移动
void clearShap();//消行
struct Point
{
int x;
int y;
};
struct Point shaps[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,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}},//T2
{{0,0},{-1,0},{1,0},{0,1}},//T3
{{0,0},{-1,0},{0,-1},{0,1}},//T4
{{0,0},{1,0},{0,-1},{-1,-1}},//正Z1
{{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
};
int main()
{
srand(time(0));//设置随机值
//屏蔽掉终端的正常输入
system(STTY_US TTY_PATH);// "stty raw -echo -F""/dev/tty"
initScreen();//初始化屏幕二维数组
createShap();//创建图形
int moveDownCD = 0;
//游戏的主逻辑循环
while(1)
{
draw(); //画图形
//返回值0,执行break结束程序
if(!userCtrl()) //用户控制 !userCtrl() 等价于 userCtrl() == 0
{
break;
}
if(++moveDownCD == 10)//向下移动的CD计时,循环每执行10次移动一次
{
moveDown();
moveDownCD = 0;
}
//刷帧
usleep(50*1000);
}
return 0;
}
//将屏幕二维数组的边界的元素置2
void initScreen()
{
/*
screen[0][] 上 screen[HEIGHT-1][] 下
screen[][0] 左 screen[][WIDTH-1] 右
*/
int x, y;
//上下
for(x = 0; x < WIDTH;x++)
{
screen[0][x] = 2;
screen[HEIGHT-1][x] = 2;
}
//左右
for(y = 0; y < HEIGHT;y++)
{
screen[y][0] = 2;
screen[y][WIDTH-1] = 2;
}
}
void createShap()
{
x = WIDTH/2;
y = 2;
currentShap = rand()%19; //在0-18之间的随机数
}
void draw()
{
system("clear"); //清空之前的图像
/*
画图形分为两部分,一部分是我们可以控制移动的图形,叫动态图形;
另一部分是边界和已经落下的图,是我们不能控制的图形
*/
//动态图形
int i;
for(i = 0;i < 4;i++)
{
/*
shaps[currentShap]运算得到shaps数组中的一个一维数组,这个一维数组表示一个图形的4个点
当前的循环就是在遍历 shaps[currentShap]数组的4个点
*/
int lx = shaps[currentShap][i].x + x;
int ly = shaps[currentShap][i].y + y;
drawPoint(lx,ly);
}
//边界和已经落下的图
//画屏幕二维数组
int x, y;
//遍历二维数组,判断每个元素的值,只要不是0就需要画方块
for(y = 0; y < HEIGHT;y++)
{
for(x = 0;x < WIDTH;x++)
{
if(screen[y][x] != 0)
{
drawPoint(x,y);
}
}
}
}
//用户的控制,返回1 表示继续 0表示结束
int userCtrl()
{
int ctrl = getChar();
switch (ctrl)
{
case 'a': //左
if(collideLeft()) //不写花括号,遇到第一个;就结束
x--;
break;
case 'd': //右
if(collideRight())
x++;
break;
case 's': //加速
if(collideDown())
y++;
break;
case 'w': //变形
changeShap();
break;
case 3:
//让终端恢复正常
system(STTY_DEF TTY_PATH);//"stty -raw echo -F""/dev/tty"
return 0;
}
return 1;
}
void changeShap()
{
int tshap = currentShap;
switch (tshap)
{
case 1: //竖条
tshap = 0;
break;
case 2: //方块
break;
case 6: //正 L 4
tshap = 3;
break;
case 10: //反 L 4
tshap = 7;
break;
case 14: //T4
tshap = 11;
break;
case 16: //正 Z 2
tshap = 15;
break;
case 18: //反 Z 2
tshap = 17;
break;
default:
tshap++;
}
if(collideChange(tshap))
currentShap = tshap; //改变currentShap的值完成变形
}
//碰撞不允许变形
int collideChange(int tshap)
{
int i;
for(i = 0;i < 4; i++)
{
int lx = shaps[currentShap][i].x + x; //将要变形的位置的x值
int ly = shaps[currentShap][i].y + y;//将要变形的位置的y值
//判断screen二维数组的ly lx的位置有没有图形或者边界
if(screen[ly][lx] != 0)
{
return 0; //发生碰撞
}
}
return 1;//没有发生碰撞
}
//左移碰撞 返回0发生碰撞 返回1没有碰撞
int collideLeft()
{
//计算当前图形的4个点的位置,然后将每个点的x值-1,能算出假设移动之后4个点的位置
//然后遍历假设移动后的4个点,用这4个点的坐标去screen数组中找到对应的元素,只有元素的值不为0就说明发生了碰撞。
int i;
for(i = 0;i < 4; i++)
{
int lx = shaps[currentShap][i].x + x - 1; //将要移动的位置的x值
int ly = shaps[currentShap][i].y + y;
//判断screen二维数组的ly lx的位置有没有图形或者边界
if(screen[ly][lx] != 0)
{
return 0; //发生碰撞
}
}
return 1;//没有发生碰撞
}
//右移碰撞
int collideRight()
{
int i;
for(i = 0;i < 4; i++)
{
int lx = shaps[currentShap][i].x + x + 1; //将要移动的位置的x值
int ly = shaps[currentShap][i].y + y;
//判断screen二维数组的ly lx的位置有没有图形或者边界
if(screen[ly][lx] != 0)
{
return 0; //发生碰撞
}
}
return 1;//没有发生碰撞
}
//下移碰撞
int collideDown()
{
int i;
for(i = 0;i < 4; i++)
{
int lx = shaps[currentShap][i].x + x; //将要移动的位置的x值
int ly = shaps[currentShap][i].y + y + 1;
//判断screen二维数组的ly lx的位置有没有图形或者边界
if(screen[ly][lx] != 0)
{
return 0; //发生碰撞
}
}
return 1;//没有发生碰撞
}
//将动态图形添加到screen数组中
void addToScreen()
{
int i;
for(i = 0;i < 4;i++)
{
int lx = shaps[currentShap][i].x+x;
int ly = shaps[currentShap][i].y+y;
screen[ly][lx] = 1;
}
}
//清空line行
void clearLine(int line)
{
int x;
for(x = 1;x < WIDTH-1;x++)
{
screen[line][x] = 0;
}
}
//使用src行复制到dst行
void moveLine(int dst, int src)
{
int x;
for(x = 1;x < WIDTH-1;x++)
{
screen[dst][x] = screen[src][x];
screen[src][x] = 0;
}
}
//判断line行是否全是1
int isFull(int line)
{
int x;
for(x = 1;x < WIDTH-1;x++)
{
if(screen[line][x] == 0)
{
return 0;
}
}
return 1;
}
//消行
void clearShap()
{
int y;
int count = 0;
for(y = HEIGHT-2;y > 0;y--)
{
if(isFull(y))//y行全是1
{
count++;
//清空y行
clearLine(y);
}
else if(count > 0)
{
//将y行复制到y+count行
moveLine(y+count, y);
}
}
}
//方块自动下降
void moveDown()
{
if(collideDown())
y++;
else
{
addToScreen();//将动态图形添加到screen数组
clearShap();//消行
createShap();//生成新图形
}
}
void drawPoint(int x,int y)
{
printf("\033[%d;%dH",y+1,x*2+1);
printf("\033[1;36m■ \033[0m");
}
int getChar()
{
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;
}