学完了C语言基础后想写个经典游戏俄罗斯方块Tetris练练手吗?今天它来啦!代码量少,逻辑清晰,简单易懂,山顶洞人表示一眼就能学会! (结尾附完整代码)
1.按键 W :方块变形
2.按键 S :方块加速下落
3.按键 A :方块向左移动
4.按键 D :方块向右移动
5.按键:ctrl + C :结束运行
6.游戏区右侧信息区可显示下一个方块的形状,以及当前得分
7.快动手去拓展更多功能吧!!
首先来了解一下什么是非阻塞型输入,非阻塞型输入跟操作系统有关。linux下C语言常用输入函数 scanf 、getchar 和 gets 通常都是阻塞式的,如果用户没有输入,程序就一直停在那儿。输入内容后,还需要用户点回车,同时,用户输入的信息,会在屏幕上回显出来。写俄罗斯方块的时候,我们不期望这种阻塞发生,我们希望按键后程序立刻响应,而不需要输入回车和屏幕回显。这不是我们编写程序的重点,只需了解即可。来看非阻塞型输入实验完整代码:(可直接复制尝试)
此段程序仅供做理解与测试用,ctrl+c结束运行:
#include
#include
#define TTY_PATH "/dev/tty"
#define STTY_US "stty raw -echo -F "
#define STTY_DEF "stty -raw echo -F "
int get_char();
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;
}
int main()
{
int ch = 0;
system(STTY_US TTY_PATH); //开启非阻塞输入
while(1)
{
ch = get_char();
if (ch==3) //ctrl+c
{
system(STTY_DEF TTY_PATH); //关闭非阻塞输入
return 0;
}
printf("key = %d(%c)\n\r", ch, ch);
usleep(1000*1000); //微秒数
}
}
Linux控制台特殊效果 linux下printf的特殊用法
void drawPoint(int x, int y,int mode,int color)
{
printf("\033[%d;%dH", y+1, x*2+1);//x,y参数为方块的位置
printf("\033[%d;%dm■ \033[0m",mode,color);//打印一个方块
}
(1) 通用格式控制:mode | (2) 前景色:color |
---|---|
0 重置所有属性 | 30 黑色 |
1 高亮/加粗 | 31 红色 |
2 暗淡 | 33 黄色 |
4 下划线 | 34 蓝色 |
5 闪烁 | 35 品红 |
7 反转 | 36 青色 |
8 隐藏 | 37 白色 |
#include
#include
#include
#define TTY_PATH "/dev/tty"
#define STTY_US "stty raw -echo -F "
#define STTY_DEF "stty -raw echo -F "
#define SCREEN_WIDTH 18
#define SCREEN_HEIGHT 20
#define INFO_AREA 6
#define SPEED 8
int map[SCREEN_HEIGHT][SCREEN_WIDTH] = {0};//定义二维数组作为平面直角坐标系,向右x增向下y增
struct Point_Delta //中心点的偏移量,中心点以此延伸获得各形状点的坐标
{
int delta_x;
int delta_y;
};
struct Point_Delta shapes[7][4] = //7种基本方块形状
{
{{0,0},{-1,0},{1,0},{0,1}},//T
{{0,0},{0,-1},{0,1},{1,1}},//L
{{0,0},{0,-1},{0,1},{-1,1}},//反L
{{0,0},{0,-1},{-1,-1},{1,0}},//Z
{{0,0},{-1,0},{0,-1},{1,-1}},//反Z
{{0,0},{1,0},{0,1},{1,1}},//田
{{0,0},{0,-1},{0,1},{0,2}},//|
};
int x,y,x_next,y_next,shapeIndex,shapeIndex_temp,shapeIndex_next;
int score,moveDown_count;
void showPoint(int x, int y,int bright,int color);
void showMap();
void setFrame();
void creatNewShape();
void showShape();
int moveDown();
void addToStatic();
void clearLines();
int isFullLine(int line);
void copyLine(int dst,int src);
void supplyLines(int line);
void userControl(char input);
void moveRight();
void moveLeft();
int overCheak();
void userControl(char input);
void moveRight();
void moveLeft();
int get_char();
int main()
{
system(STTY_US TTY_PATH);//开启非阻塞输入
setFrame();//初始化边框
srand(time(0));//随机种子
shapeIndex = rand()%7;//初始化一个形状的编号
shapeIndex_next = rand()%7;//初始化下一个形状的编号
creatNewShape();//创建新图形,重置定位点
while(1)
{
system("clear");//清屏
showShape();//打印当前的图形以及信息区下一个形状
if(moveDown())//控制下降函数,触底判断为真
{
addToStatic();//把触底的图形添加至静态部分
clearLines();//如果有一行满了则消除并加相应的分数
shapeIndex = shapeIndex_next;//下一个图形的编号为信息区所显示的
creatNewShape();
shapeIndex_next = shapeIndex_temp;//新随机的下一个图形编号
}
showMap();//打印整个游戏界面
if(overCheak())//检测方块触顶的函数,触顶判断为真,跳出循环
break;
fflush(stdout);//清理缓存,让终端立刻显示
usleep(1000*40);//系统等待的微秒数,控制帧率
char input = get_char();//获取按键输入
userControl(input);//控制方块的左右移动,变形,加速下降
if(input == 3)
{
system(STTY_DEF TTY_PATH);//关闭非阻塞输入
return 0;
}
}
return 0;
}
void setFrame()//创建边框
{
int i,j;
for(j=0;j<SCREEN_WIDTH;j++)
{
map[0][j] = 1;//第1行 给坐标上点赋值是为了显示特定的点,判断碰撞以及区分开不同意义的点集
map[SCREEN_HEIGHT-1][j] = 1;//最后一行
}
for(i=1;i<SCREEN_HEIGHT-1;i++)
{
map[i][0] = 1;//第一列
map[i][SCREEN_WIDTH-INFO_AREA] = 1;//信息区
map[i][SCREEN_WIDTH-1] = 1;//最后一列
}
}
void showPoint(int x, int y,int mode,int color)
{
printf("\033[%d;%dH", y+1, x*2+1);
printf("\033[%d;%dm■ \033[0m",mode,color);//打印一个方块
}
void showMap()//打印整个界面
{
int i,j;
for(i=0;i<SCREEN_HEIGHT;i++)
{
for(j=0;j<SCREEN_WIDTH;j++)
{
if(map[i][j] == 1)
showPoint(j,i,1,30);//行标i,列表j与坐标系横纵坐标的值是相反的
if(map[i][j] == 2)
showPoint(j,i,1,33);
if(j==SCREEN_WIDTH-INFO_AREA)
{
if(i==1)
printf("Next:");
if(i==7)
printf("Score:");
if(i==8)
printf(" %3d",score);
}
}
}
}
void creatNewShape()
{
x = (SCREEN_WIDTH-INFO_AREA)/2;//新形状的定位点,游戏区的中间
y = 2;//避免越界
shapeIndex_temp = rand()%7;
x_next = SCREEN_WIDTH-INFO_AREA/2;//信息区下一个形状的定位点
y_next = 4;
}
void showShape()
{
int i;
for(i=0;i<4;i++)
showPoint(x+shapes[shapeIndex][i].delta_x,//x,y分别加上4个点相应的偏移量
y+shapes[shapeIndex][i].delta_y,1,31);
for(i=0;i<4;i++)
{
showPoint(x_next+shapes[shapeIndex_next][i].delta_x,
y_next+shapes[shapeIndex_next][i].delta_y,1,31);
}
}
int moveDown()
{
int i;
if(moveDown_count<SPEED)//函数被调用SPEED次才会执行一次,用来控制下降速度
{
moveDown_count++;
return 0;
}
moveDown_count = 0;
for(i=0;i<4;i++)
{
int dx = x+shapes[shapeIndex][i].delta_x;
int dy = y+shapes[shapeIndex][i].delta_y+1;//判断map[x][y]的下方是否已经有方块
if(map[dy][dx] == 1||map[dy][dx] ==2)//如果有,不可移动
return 1;
}
y++;
return 0;
}
void moveRight()
{
int i,rx,ry;//移动之前检测合法性,先判断移动后的点是否与边界或静态部分碰撞
for(i=0;i<4;i++)
{
rx = x+shapes[shapeIndex][i].delta_x+1;
ry = y+shapes[shapeIndex][i].delta_y;
if(map[ry][rx]==1||map[ry][rx]==2)//不能移动
return;
}
x++;//合法,则移动
}
void moveLeft()
{
int i,lx,ly;
for(i=0;i<4;i++)
{
lx = x+shapes[shapeIndex][i].delta_x-1;
ly = y+shapes[shapeIndex][i].delta_y;
if(map[ly][lx]==1||map[ly][lx]==2)//不能移动
return;
}
x--;//合法,则移动
}
void chageShape()
{
int i,newDelta_x[4],newDelta_y[4];
if(shapeIndex==5)//田形状不需要旋转
return;
for(i=0;i<4;i++)
{
newDelta_x[i] = shapes[shapeIndex][i].delta_y;
newDelta_y[i] = -shapes[shapeIndex][i].delta_x;
if(map[y+newDelta_y[i]][x+newDelta_x[i]] == 1//在旋转之前要做合法性判断,旋转后是否会发生碰撞
||map[y+newDelta_y[i]][x+newDelta_x[i]] == 2)
return;
}
for(i=0;i<4;i++)//合法,则赋值完成旋转
{
shapes[shapeIndex][i].delta_x = newDelta_x[i];
shapes[shapeIndex][i].delta_y = newDelta_y[i];
}
}
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;
}
void userControl(char input)
{
switch(input)
{
case 'w':
chageShape();
break;
case 'a':
moveLeft();
break;
case 's':
moveDown_count+=SPEED;//加速下降
break;
case 'd':
moveRight();
break;
}
}
每次方块触底后:
1.把它添加到静态部分,把所有已经落定的方块成为静态区域(坐标上的点赋值为2)
2.判断是否有满行,有则消除(包含满行判断,行覆盖,行添补)
3.创建一个新的图形继续下落
void addToStatic()//添加到静态区域,把所有已经落定的方块称为静态区域
{
int i;
for(i = 0;i<4;i++)
map[y+shapes[shapeIndex][i].delta_y][x+shapes[shapeIndex][i].delta_x] = 2;//静态的点值为2
}
void clearLines()//消除已经满的行
{
int i,count = 0;
for(i=SCREEN_HEIGHT-2;i>0;i--)//从最后一行往上遍历
{
if(isFullLine(i))
{
count++;//此处算法可参考下方程序段
continue;
}
copyLine(i+count,i);//上面覆盖到下面
}
for(i=1;i<count;i++)
supplyLines(i);
score += count;//计分
}
int isFullLine(int line)//判断此行是否满
{
int i;
for(i=1;i<SCREEN_WIDTH-INFO_AREA-1;i++)//只算游戏区域
{
if(map[line][i]!=2)//此行没有满
return 0;
}
return 1; //此行全部为2
}
void copyLine(int dst,int src)//目标行,源行
{
int i;
for(i=1;i<SCREEN_WIDTH-INFO_AREA-1;i++)
map[dst][i] = map[src][i];
}
void supplyLines(int line)//给i行补0
{
int i;
for(i=1;i<SCREEN_WIDTH-INFO_AREA-1;i++)
map[line][i] = 0;
}
消行算法的原理可参考下方程序,不要与游戏程序混淆:
#include
int main()
{
int a[6] = {0,2,3,1,4,1};//消除值为1的元素并在前面补0
int i,count = 0;
for(i=6-1;i>=0;i--)
{
if(a[i]==1)
{
count++;
continue;
}
a[i+count] = a[i];
}
for(i=0;i<count;i++)
a[i] = 0;
for(i=0;i<6;i++)
printf("%d ",a[i]);//运行程序输出结果为0 0 0 2 3 4
printf("\n");
return 0;
}
int overCheak()//检测方块是否触顶
{
int i,m,n;
for(i=1;i<SCREEN_WIDTH-1;i++)
{
if(map[1][i]==2)
{
system(STTY_DEF TTY_PATH);//关闭非阻塞输入
return 1;
}
}
return 0;
}
1.笔者因水平限制如有误导大家的地方非常抱歉哈
2.欢迎下方留言讨论
3.来“华清远见” http://www.hqyj.com/学习更多编程知识
#include
#include
#include
#define TTY_PATH "/dev/tty"
#define STTY_US "stty raw -echo -F "
#define STTY_DEF "stty -raw echo -F "
#define SCREEN_WIDTH 18
#define SCREEN_HEIGHT 20
#define INFO_AREA 6
#define SPEED 8
int map[SCREEN_HEIGHT][SCREEN_WIDTH] = {0};//定义二维数组作为平面直角坐标系,向右x增向下y增
struct Point_Delta //中心点的偏移量,中心点以此延伸获得各形状点的坐标
{
int delta_x;
int delta_y;
};
struct Point_Delta shapes[7][4] =
{
{{0,0},{-1,0},{1,0},{0,1}},//T
{{0,0},{0,-1},{0,1},{1,1}},//L
{{0,0},{0,-1},{0,1},{-1,1}},//反L
{{0,0},{0,-1},{-1,-1},{1,0}},//Z
{{0,0},{-1,0},{0,-1},{1,-1}},//反Z
{{0,0},{1,0},{0,1},{1,1}},//田
{{0,0},{0,-1},{0,1},{0,2}},//|
};
int x,y,x_next,y_next,shapeIndex,shapeIndex_temp,shapeIndex_next;
int score,moveDown_count;
void showPoint(int x, int y,int bright,int color);
void showMap();
void setFrame();
void creatNewShape();
void showShape();
int moveDown();
void addToStatic();
void clearLines();
int isFullLine(int line);
void copyLine(int dst,int src);
void supplyLines(int line);
void userControl(char input);
void moveRight();
void moveLeft();
int overCheak();
void userControl(char input);
void moveRight();
void moveLeft();
int get_char();
int main()
{
system(STTY_US TTY_PATH);//开启非阻塞输入
setFrame();
srand(time(0));
shapeIndex = rand()%7;
shapeIndex_next = rand()%7;
creatNewShape();
while(1)
{
system("clear");
showShape();
if(moveDown())
{
addToStatic();
clearLines();
shapeIndex = shapeIndex_next;
creatNewShape();
shapeIndex_next = shapeIndex_temp;
}
showMap();
if(overCheak())
break;
fflush(stdout);//清理缓存,让终端立刻显示
usleep(1000*40);//微秒数
char input = get_char();
userControl(input);
if(input == 3)
{
system(STTY_DEF TTY_PATH);//关闭非阻塞输入
return 0;
}
}
return 0;
}
void setFrame()//创建边框
{
int i,j;
for(j=0;j<SCREEN_WIDTH;j++)
{
map[0][j] = 1;//第1行 给坐标上点赋值是为了显示特定的点,判断碰撞以及区分开不同意义的点集
map[SCREEN_HEIGHT-1][j] = 1;//最后一行
}
for(i=1;i<SCREEN_HEIGHT-1;i++)
{
map[i][0] = 1;//第一列
map[i][SCREEN_WIDTH-INFO_AREA] = 1;//信息区
map[i][SCREEN_WIDTH-1] = 1;//最后一列
}
}
void showPoint(int x, int y,int bright,int color)
{
printf("\033[%d;%dH", y+1, x*2+1);
printf("\033[%d;%dm■ \033[0m",bright,color);//打印一个方块
}
void showMap()
{
int i,j;
for(i=0;i<SCREEN_HEIGHT;i++)
{
for(j=0;j<SCREEN_WIDTH;j++)
{
if(map[i][j] == 1)
showPoint(j,i,1,30);//行标i,列表j与坐标系横纵坐标的值是相反的
if(map[i][j] == 2)
showPoint(j,i,1,33);
if(j==SCREEN_WIDTH-INFO_AREA)
{
if(i==1)
printf("Next:");
if(i==7)
printf("Score:");
if(i==8)
printf(" %3d",score);
}
}
}
}
void creatNewShape()
{
x = (SCREEN_WIDTH-INFO_AREA)/2;//新形状的定位点,游戏区的中间
y = 2;//避免越界
shapeIndex_temp = rand()%7;
x_next = SCREEN_WIDTH-INFO_AREA/2;
y_next = 4;
}
void showShape()
{
int i;
for(i=0;i<4;i++)
showPoint(x+shapes[shapeIndex][i].delta_x,
y+shapes[shapeIndex][i].delta_y,1,31);
for(i=0;i<4;i++)
{
showPoint(x_next+shapes[shapeIndex_next][i].delta_x,
y_next+shapes[shapeIndex_next][i].delta_y,1,31);
}
}
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].delta_x;
int dy = y+shapes[shapeIndex][i].delta_y+1;//原来位置的map[x][y]已经是1
if(map[dy][dx] == 1||map[dy][dx] ==2)
return 1;//不可移动
}
y++;
return 0;
}
void moveRight()
{
int i,rx,ry;
for(i=0;i<4;i++)
{
rx = x+shapes[shapeIndex][i].delta_x+1;
ry = y+shapes[shapeIndex][i].delta_y;
if(map[ry][rx]==1||map[ry][rx]==2)//不能移动
return;
}
x++;
}
void moveLeft()
{
int i,lx,ly;
for(i=0;i<4;i++)
{
lx = x+shapes[shapeIndex][i].delta_x-1;
ly = y+shapes[shapeIndex][i].delta_y;
if(map[ly][lx]==1||map[ly][lx]==2)//不能移动
return;
}
x--;
}
void chageShape()
{
int i,newDelta_x[4],newDelta_y[4];
if(shapeIndex==5)
return;
for(i=0;i<4;i++)
{
newDelta_x[i] = shapes[shapeIndex][i].delta_y;
newDelta_y[i] = -shapes[shapeIndex][i].delta_x;
if(map[y+newDelta_y[i]][x+newDelta_x[i]] == 1
||map[y+newDelta_y[i]][x+newDelta_x[i]] == 2)
return;
}
for(i=0;i<4;i++)
{
shapes[shapeIndex][i].delta_x = newDelta_x[i];
shapes[shapeIndex][i].delta_y = newDelta_y[i];
}
}
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;
}
void userControl(char input)
{
switch(input)
{
case 'w':
chageShape();
break;
case 'a':
moveLeft();
break;
case 's':
moveDown_count+=SPEED;
break;
case 'd':
moveRight();
break;
}
}
void addToStatic()//添加到静态区域,把所有已经落定的方块称为静态区域
{
int i;
for(i = 0;i<4;i++)
map[y+shapes[shapeIndex][i].delta_y][x+shapes[shapeIndex][i].delta_x] = 2;//静态的点值为2
}
void clearLines()//消除已经满的行
{
int i,count = 0;
for(i=SCREEN_HEIGHT-2;i>0;i--)//从最后一行往上遍历
{
if(isFullLine(i))
{
count++;
continue;
}
copyLine(i+count,i);//上面覆盖到下面
}
for(i=1;i<count;i++)
supplyLines(i);
score += count;
}
int isFullLine(int line)
{
int i;
for(i=1;i<SCREEN_WIDTH-INFO_AREA-1;i++)//只算游戏区域
{
if(map[line][i]!=2)//此行没有满
return 0;
}
return 1; //此行全部为2
}
void copyLine(int dst,int src)//目标行,源行
{
int i;
for(i=1;i<SCREEN_WIDTH-INFO_AREA-1;i++)
map[dst][i] = map[src][i];
}
void supplyLines(int line)//给i行补0
{
int i;
for(i=1;i<SCREEN_WIDTH-INFO_AREA-1;i++)
map[line][i] = 0;
}
int overCheak()//检测是否游戏结束
{
int i,m,n;
for(i=1;i<SCREEN_WIDTH-1;i++)
{
if(map[1][i]==2)
{
system(STTY_DEF TTY_PATH);//关闭非阻塞输入
return 1;
}
}
return 0;
}