俄罗斯方块
一、设置控制台
1、更改屏幕的背景色,字体颜色
printf("\33[%dm", i);
30 30黑,31红,32绿,33黄,34蓝,35紫,36深绿,37白
40 40黑,41红,42绿,43黄,44蓝,45紫,46深绿,47白
2、任意指定屏幕的输出坐标
printf("\33[x;yH"); 将输出坐标定位在第x行,第y列
printf("\33[%d;%dH", x,y); 将输出坐标定位在第x行,第y列
二、菜单
1、边框
游戏区宽48,高31;辅助区宽27,高31
游戏区起始坐标(2,3);辅助区起始坐标(2,53)(18,53)
2、开始
用空格和[]描出一个fight单词
3、结束
用空格和[]描出一个game over单词
三、绘制方块
1、一个小方格
由于一个字符在屏幕上占的空间是一个长方形,所以用[]来表示一个方格
2、俄罗斯方块
总共有7个类型的方块,每个方块有4种摆放的方式(可以旋转)
一个方块最多占用4个小方格
用4x4的矩阵来表示一个俄罗斯方块,矩阵中为1的地方,绘制[]
{0, 0, 0, 0}
{0, 0, 1, 0} -----> []
{0, 0, 1, 1} [][]
{0, 0, 1, 0} []
3、用一个三位数组表示所有的俄罗斯方块
int shape[i][j][k]
i表示方块的类型,j表示方块摆放的方式,k将4x4矩阵变为一维数组
将上面的二维数组变成一维的{0,0,0,0, 0,0,1,0, 0,0,1,0, 0,0,1,0}
4、起始坐标
绘制方块从4x4矩阵的左下角开始,制定其坐标为row、col
俄罗斯方块中小方格的坐标rrow = row + j/4-3 ccol = ccol + (i%4)*2
四、 自动下落
1、内核定时器
setitimer(int which, const struct itimerval *val, struct itimerval *oldval)
参数which:
ITIMER_REAL 用系统实时的时间计算,时间减1,减到0发送SIGALRM信号
ITIMER_VIRTUAL 当前进程用户态计数, 计数完成发送SIGVTALRM信号
ITIMER_PROF 用户态和内核态同时计数,计数完成发送SIGPROF信号
参数val:
struct itimerval{
struct timeval it_interval; //定时时间
struct timeval it_value; //定时器开始执行的时间
}
struct timeval{
long second; //秒
long usec; //微妙
}
参数oldval:保存旧的状态
struct itimerval val = {
{0,500000}, //0.5秒的定时时间
{1,0} //1s后开启定时器
};
setitimer{ITIMER_REAL, &val, NULL};
2、信号捕捉
sigaction(int signo, struct sigaction *act, struct sigaction *oldact);
参数signo: 要捕捉的信号
参数act:为信号设置动作
参数oldact: 保存旧的状态,NULL就是不保存
struct sigaction{
void (*sa_handler)(int); //信号执行的函数
sigset_t mask; //信号执行过程中要屏蔽的其他信号
}
struct sigaction act;
act.sa_handler = self_down;
sigaction(SIGALRM, &act, NULL);
五、 响应键盘
1、设置终端,键盘可以输入,但是不能在屏幕上显示输入的字符,否则影响游戏画面。
struct termios old, new;
tcgetattr(0, &old); //保存旧的状态
tcgetattr(0, &new); //获取旧的状态
new.c_lflag = new.c_lflag & ~(ICANON|ECHO); //设置屏幕不显示输入的字符
new.c_cc[VTIME] = 0;
new.c_cc[VMIN] = 1;
tcsetattr(0, TCSANOW, &new); //修改终端的属性,立即生效
2、响应键盘输入
3、设置标志位
4、边界判断
1)、下落判断
2)、左移判断
3)、右移判断
4)、旋转判断
六、消行
1、判断是否需要消行,获取需要消的行号
2、记录以前的状态,设值新的状态
3、重新画图
一、设置控制台
1、更改屏幕的背景色,字体颜色
printf("\33[%dm", i);
30 30黑,31红,32绿,33黄,34蓝,35紫,36深绿,37白
40 40黑,41红,42绿,43黄,44蓝,45紫,46深绿,47白
2、任意指定屏幕的输出坐标
printf("\33[x;yH"); 将输出坐标定位在第x行,第y列
printf("\33[%d;%dH", x,y); 将输出坐标定位在第x行,第y列
二、菜单
1、边框
游戏区宽48,高31;辅助区宽27,高31
游戏区起始坐标(2,3);辅助区起始坐标(2,53)(18,53)
2、开始
用空格和[]描出一个fight单词
3、结束
用空格和[]描出一个game over单词
三、绘制方块
1、一个小方格
由于一个字符在屏幕上占的空间是一个长方形,所以用[]来表示一个方格
2、俄罗斯方块
总共有7个类型的方块,每个方块有4种摆放的方式(可以旋转)
一个方块最多占用4个小方格
用4x4的矩阵来表示一个俄罗斯方块,矩阵中为1的地方,绘制[]
{0, 0, 0, 0}
{0, 0, 1, 0} -----> []
{0, 0, 1, 1} [][]
{0, 0, 1, 0} []
3、用一个三位数组表示所有的俄罗斯方块
int shape[i][j][k]
i表示方块的类型,j表示方块摆放的方式,k将4x4矩阵变为一维数组
将上面的二维数组变成一维的{0,0,0,0, 0,0,1,0, 0,0,1,0, 0,0,1,0}
4、起始坐标
绘制方块从4x4矩阵的左下角开始,制定其坐标为row、col
俄罗斯方块中小方格的坐标rrow = row + j/4-3 ccol = ccol + (i%4)*2
四、 自动下落
1、内核定时器
setitimer(int which, const struct itimerval *val, struct itimerval *oldval)
参数which:
ITIMER_REAL 用系统实时的时间计算,时间减1,减到0发送SIGALRM信号
ITIMER_VIRTUAL 当前进程用户态计数, 计数完成发送SIGVTALRM信号
ITIMER_PROF 用户态和内核态同时计数,计数完成发送SIGPROF信号
参数val:
struct itimerval{
struct timeval it_interval; //定时时间
struct timeval it_value; //定时器开始执行的时间
}
struct timeval{
long second; //秒
long usec; //微妙
}
参数oldval:保存旧的状态
struct itimerval val = {
{0,500000}, //0.5秒的定时时间
{1,0} //1s后开启定时器
};
setitimer{ITIMER_REAL, &val, NULL};
2、信号捕捉
sigaction(int signo, struct sigaction *act, struct sigaction *oldact);
参数signo: 要捕捉的信号
参数act:为信号设置动作
参数oldact: 保存旧的状态,NULL就是不保存
struct sigaction{
void (*sa_handler)(int); //信号执行的函数
sigset_t mask; //信号执行过程中要屏蔽的其他信号
}
struct sigaction act;
act.sa_handler = self_down;
sigaction(SIGALRM, &act, NULL);
五、 响应键盘
1、设置终端,键盘可以输入,但是不能在屏幕上显示输入的字符,否则影响游戏画面。
struct termios old, new;
tcgetattr(0, &old); //保存旧的状态
tcgetattr(0, &new); //获取旧的状态
new.c_lflag = new.c_lflag & ~(ICANON|ECHO); //设置屏幕不显示输入的字符
new.c_cc[VTIME] = 0;
new.c_cc[VMIN] = 1;
tcsetattr(0, TCSANOW, &new); //修改终端的属性,立即生效
2、响应键盘输入
点击(此处)折叠或打开
- while(1)
- {
- c = getchar();
- switch(c)
- {
- case 'a':
- left(); //左移,列-2,因为一个小方格占2列
- break;
- case 'd':
- right(); //右移,列+2
- break;
- case 'w':
- change(); //逆时针旋转
- break;
- case 's':
- self_down(1); //自由下落 row+1
- break;
- default:
- break;
- }
- }
3、设置标志位
点击(此处)折叠或打开
- 方块降落到最后,需要用以个二维数组来标志,数组中为1的地方证明有方格
- int flag[30][24];
- void set_flag()
- {
- int i;
- int row, col;
-
- for(i=0; i16; i++)
- {
- if(shape[box.sh][box.num][i] == 1)
- {
- row = box.row + i/4 -3 -2;
- col = (box.col + (i%4)*2 + 1)/2-2;
- flag[row][col] = 1;
- }
- }
- }
4、边界判断
1)、下落判断
点击(此处)折叠或打开
- //判断能有下落,当对应方格的下一行位置有东西,那么就不能下落来了
- //比如现在方格在8行9列,那么如果9行9列有东西(判断标志位),那么
- //方格就不能在下落了
- int is_down()
- {
- int err = 1;
- int i;
-
- if(box.row >= 31) //第一次超过31行就不能下落,那是最低的边界
- {
- err = 0;
- return err;
- }
-
- for(i = 0; i 16; i++)
- {
- if(shape[box.sh][box.num][i] == 1)
- {
- if(flag[box.row + i/4 -3 -2 + 1][(box.col+i%4*2+1)/2-2] == 1)
- {
- err = 0;
- return err;
- }
- }
- }
-
- return err;
- }
点击(此处)折叠或打开
- //首先要获取大方块中的最左边的列
- int get_left()
- {
- int col[4];
- int i, j, temp;
-
- for(i=0, j=0; i16; i++)
- {
- if(shape[box.sh][box.num][i] == 1) //获取方块中小方格占的列
- {
- col[j] = box.col + (i%4)*2;
- j++;
- }
- }
-
- for(i=1; i4; i++)
- {
- if(col[0] > col[i]) //求出列中最小的,也就是最左边的列
- {
- temp = col[0];
- col[0] = col[i];
- col[i] = temp;
- }
- }
-
- return col[0];
- }
-
- int is_left() //判断能否左移
- {
- int err = 1;
- int i;
-
- int col = get_left();
- if(col == 3) //最左边的列是第3列,移动到第3列就不能左移了
- return 0;
-
-
- //如果左边有方格,也不能左移,通过标志位来判断
- for(i = 0; i 16; i++)
- {
- if(shape[box.sh][box.num][i] == 1)
- {
- if(flag[box.row + i/4 -3 -2][(box.col+i%4*2+1)/2-2-1] == 1)
- {
- err = 0;
- return err;
- }
- }
- }
-
- return err;
- }
点击(此处)折叠或打开
- //首先要获取大方块中的最右边的列
- int get_right()
- {
- int col[4];
- int i, j, temp;
-
- for(i=0, j=0; i16; i++)
- {
- if(shape[box.sh][box.num][i] == 1) //获取方块中小方格占的列
- {
- col[j] = box.col + (i%4)*2;
- j++;
- }
- }
-
- for(i=1; i4; i++)
- {
- if(col[0] col[i]) //求出列中最大的,也就是最右边的列
- {
- temp = col[0];
- col[0] = col[i];
- col[i] = temp;
- }
- }
-
- return col[0];
- }
-
- int is_right() //判断能否右移
- {
- int err = 1;
- int i;
-
- int col = get_right();
- if(col == 3) //最右边的列是第49列,移动到第49列就不能右移了
- return 0;
-
-
- //如果右边有方格,也不能右移,通过标志位来判断
- for(i = 0; i 16; i++)
- {
- if(shape[box.sh][box.num][i] == 1)
- {
- if(flag[box.row + i/4 -3 -2][(box.col+i%4*2+1)/2-2+1] == 1)
- {
- err = 0;
- return err;
- }
- }
- }
-
- return err;
- }
点击(此处)折叠或打开
- 旋转依赖4x4大方格,在4x4大方格内有东西到地方都不能旋转
- int is_change()
- {
- int err=1;
- int i;
-
- for(i=0; i16; i++)
- {
- if(flag[box.row+i/4-3-2+1][(box.col+1+i%4*2)/2-2] == 1)
- {
- err = 0;
- return err;
- }
- if(box.col3)
- {
- err = 0;
- return err;
- }
- }
-
- return err;
- }
六、消行
1、判断是否需要消行,获取需要消的行号
点击(此处)折叠或打开
- 在flag数组中,如果有一行全部为1,那么就需要消行了
- int is_clean()
- {
- int err=0, fg=1;
- int num=0;
-
- int i,j;
- for(i=0; i30; i++)
- {
- fg = 1; //假设这一行能消去
- for(j=0; j24; j++)
- {
- if(flag[i][j]==0) //如果这一行中有一个0,那么就不能消去
- {
- fg = 0;
- break;
- }
- }
- if(fg == 1)
- {
- clean_row[num] = i; //如果能消行,那么需要记录行号
- num++;
- err = 1;
- }
- }
-
- return err;
- }
点击(此处)折叠或打开
- void swap()
- {
- int i,j,k; //old_flag记录以前的状态
- for(i=0; i30; i++)
- for(j=0; j24; j++)
- old_flag[i][j] = flag[i][j];
-
-
- for(k=0; k4; k++) //更新flag,消行后,上面的行要掉下来
- for(i=0; iclean_row[k]; i++)
- for(j=0; j24; j++)
- {
- flag[i+1][j] = old_flag[i][j];
- }
- }
点击(此处)折叠或打开
- void redraw()
- {
- int i,j;
-
- //将以前的图形全部擦掉
- for(i=0; i30; i++)
- {
- for(j=0; j24; j++)
- {
- if(old_flag[i][j]==1)
- single_box(i+2, (j+1)*2+1, 0);
- }
- }
-
- //绘制新的图形
- for(i=0; i30; i++)
- {
- for(j=0; j24; j++)
- {
- if(flag[i][j]==1)
- single_box(i+2, (j+1)*2+1, 1);
- }
- }
- }