免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5996 | 回复: 4
打印 上一主题 下一主题

[应用] linux系统下的俄罗斯方块 [复制链接]

论坛徽章:
1
15-16赛季CBA联赛之福建
日期:2018-12-10 14:43:45
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2015-06-08 17:58 |只看该作者 |倒序浏览
本帖最后由 一生有你llx 于 2015-06-13 19:56 编辑

/***************************************************
PROJECT: 俄罗斯方块
AUTHOR:  WJ
DATE:      2015-6-10

QQ:         78080458
GROUP:   150633458
***************************************************/

俄罗斯方块
一、设置控制台
        1、更改屏幕的背景色,字体颜色
                printf("\33[%dm", i);
                30<= i <=37  设置字体颜色
                30黑,31红,32绿,33黄,34蓝,35紫,36深绿,37白
                40<= i <=47  设置背景颜色
                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[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; i<16; i++)
                        {
                                if(shape[box.sh][box.num] == 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] == 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;
                        }

                2)、左移判断
                        //首先要获取大方块中的最左边的列
                        int get_left()
                        {
                                int col[4];
                                int i, j, temp;

                                for(i=0, j=0; i<16; i++)
                                {
                                        if(shape[box.sh][box.num] == 1)        //获取方块中小方格占的列
                                        {       
                                                col[j] = box.col + (i%4)*2;
                                                j++;
                                        }
                                }

                                for(i=1; i<4; i++)
                                {
                                        if(col[0] > col)                //求出列中最小的,也就是最左边的列
                                        {       
                                                temp = col[0];
                                                col[0] = col;
                                                col = 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] == 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;
                        }

                3)、右移判断
                        //首先要获取大方块中的最右边的列
                        int get_right()
                        {
                                int col[4];
                                int i, j, temp;

                                for(i=0, j=0; i<16; i++)
                                {
                                        if(shape[box.sh][box.num] == 1)        //获取方块中小方格占的列
                                        {       
                                                col[j] = box.col + (i%4)*2;
                                                j++;
                                        }
                                }

                                for(i=1; i<4; i++)
                                {
                                        if(col[0] < col)                //求出列中最大的,也就是最右边的列
                                        {       
                                                temp = col[0];
                                                col[0] = col;
                                                col = 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] == 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;
                        }
                4)、旋转判断
                        旋转依赖4x4大方格,在4x4大方格内有东西到地方都不能旋转
                        int is_change()
                        {
                                int err=1;
                                int i;

                                for(i=0; i<16; 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.col<3)
                                        {
                                                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; i<30; i++)
                        {
                                fg = 1;                                        //假设这一行能消去
                                for(j=0; j<24; j++)
                                {
                                        if(flag[j]==0)        //如果这一行中有一个0,那么就不能消去
                                        {
                                                fg = 0;       
                                                break;
                                        }
                                }
                                if(fg == 1)
                                {
                                        clean_row[num] = i;        //如果能消行,那么需要记录行号
                                        num++;
                                        err = 1;
                                }
                        }               

                        return err;
                }
        2、记录以前的状态,设值新的状态
       
                void swap()
                {
                        int i,j,k;                                        //old_flag记录以前的状态
                        for(i=0; i<30; i++)
                                for(j=0; j<24; j++)
                                        old_flag[j] = flag[j];


                        for(k=0; k<4; k++)                        //更新flag,消行后,上面的行要掉下来
                                for(i=0; i<clean_row[k]; i++)
                                        for(j=0; j<24; j++)
                                        {
                                                flag[i+1][j] = old_flag[j];
                                        }
                }
        3、重新画图
                void redraw()
                {
                        int i,j;

                        //将以前的图形全部擦掉
                        for(i=0; i<30; i++)               
                        {
                                for(j=0; j<24; j++)
                                {
                                        if(old_flag[j]==1)
                                                single_box(i+2, (j+1)*2+1, 0);
                                }
                        }

                        //绘制新的图形
                        for(i=0; i<30; i++)
                        {
                                for(j=0; j<24; j++)
                                {
                                        if(flag[j]==1)
                                                single_box(i+2, (j+1)*2+1, 1);
                                }
                        }
                }

七、右侧指示下一个
        1、一开始就产生两个图形,box、next_box
                使用box下落,next_box显示在右侧
                box.sh = 1;
                box.num = 1;       
                box.row = 1;
                box.col = 13;

                big_box(box, 1);

                next();
                display_next( 1);

               
                void next()
                {
                        next_box.sh = rand_num(7);
                        next_box.num = rand_num(4);
                        next_box.row = 1;
                        next_box.col = 13;
                }

                void display_next(int mode)
                {
                        struct box_info nbox;
                        memcpy(&nbox, &next_box, sizeof(struct box_info));
                        nbox.row = 10;
                        nbox.col = 60;

                        big_box(nbox, mode);
                }
        2、当下落结束的时候,需要使用next_box下落,然后接着产生另一个next_box
       
                if(new_flag == 1)
                {
                        new_flag = 0;

                        display_next(0);        //将右侧的提示去掉
                        memcpy(&box, &next_box, sizeof(struct box_info));//将next_box拷贝到box
                        next();                                        //产生新的next_box
                        display_next( 1);                //在右侧提示next_box
                       
                }

八、判断游戏是否结束       
        //判断是否结束游戏
        int is_gameover()
        {
                int err = 0;
                int i,j;

                        for(j=0; j<24; j++)
                        {
                                //flag第0行有1就是结束游戏
                                if(flag[0][j] == 1)
                                {
                                        err = 1;
                                        break;
                                }
                        }

                return err;
        }

        //产生下一个box的时候判断是否游戏结束
        void next()
        {
                if(is_gameover()==1)
                {
                        flag_over = 1;
                        return;
                }
                next_box.sh = rand_num(7);
                next_box.num = rand_num(4);
                next_box.row = 1;
                next_box.col = 13;
        }

九、修改菜单,完善游戏
        增加游戏的灵活性,进入游戏后,按1是开始,按2是选关,按3是退出。
        选关的时候+是增加难度,-是减少难度,1是开始,3是退出



els.tar.gz (34.55 KB, 下载次数: 133)

评分

参与人数 1可用积分 +6 收起 理由
amarant + 6 赞一个!

查看全部评分

求职 : 机器学习
论坛徽章:
79
2015年亚洲杯纪念徽章
日期:2015-05-06 19:18:572015七夕节徽章
日期:2015-08-21 11:06:172015亚冠之阿尔纳斯尔
日期:2015-09-07 09:30:232015亚冠之萨济拖拉机
日期:2015-10-21 08:26:3915-16赛季CBA联赛之浙江
日期:2015-12-30 09:59:1815-16赛季CBA联赛之浙江
日期:2016-01-10 12:35:21技术图书徽章
日期:2016-01-15 11:07:2015-16赛季CBA联赛之新疆
日期:2016-02-24 13:46:0215-16赛季CBA联赛之吉林
日期:2016-06-26 01:07:172015-2016NBA季后赛纪念章
日期:2016-06-28 17:44:45黑曼巴
日期:2016-06-28 17:44:4515-16赛季CBA联赛之浙江
日期:2017-07-18 13:41:54
2 [报告]
发表于 2015-06-08 20:11 |只看该作者
\33[这些符号表示什么意思?
是转义吗?
相关的定义在哪里能够查到呢?回复 1# 一生有你llx


   

论坛徽章:
1
15-16赛季CBA联赛之福建
日期:2018-12-10 14:43:45
3 [报告]
发表于 2015-06-09 07:58 |只看该作者
在Linux终端下调试程序时,有时需要输出大量信息。若能控制字体的颜色和显示方式,可使输出信息对比鲜明,便于调试时观察数据。

     终端的字符颜色由转义序列(Escape Sequence)控制,是文本模式下的系统显示功能,与具体语言无关。

     转义序列以控制字符'ESC'开头。该字符的ASCII码十进制表示为27,十六进制表示为0x1B,八进制表示为033。多数转义序列超过两个字符,故通常以'ESC'和左括号'['开头。该起始序列称为控制序列引导符(CSI,Control Sequence Intro),通常由'\033['或'\e['代替。

     通过转义序列设置终端显示属性时,可采用以下格式:

     \033[ Param {aram;...}m 或 \e[ Param {aram;...}m

     其中,'\033['或'\e['引导转义序列,'m'表示设置属性并结束转义序列。Param为属性值,{...}表示可选(多个参数之间用分号隔开,与顺序无关)。

     注意,转义序列可被控制字符'CAN'(Cancel )和'SUB'(Substitute)中断。

     转义序列相关的常用参数如下(通过man console_codes命令可查看更多的参数描述):

     显示:0(默认)、1(粗体/高亮)、22(非粗体)、4(单条下划线)、24(无下划线)、5(闪烁)、25(无闪烁)、7(反显、翻转前景色和背景色)、27(无反显)

     颜色:0(黑)、1(红)、2(绿)、 3(黄)、4(蓝)、5(洋红)、6(青)、7(白)

     前景色为30+颜色值,如31表示前景色为红色;背景色为40+颜色值,如41表示背景色为红色。

     调色效果如下图所示:



     因此,通过转义序列设置终端显示属性时,常见格式为:

     \033[显示方式;前景色;背景色m输出字符串\033[0m 或 \e[显示方式;前景色;背景色m输出字符串\033[0m

     此外,还有一些ANSI控制码,如:nA (光标上移n行 )、nB(光标下移n行 )、nC(光标右移n行 )、nD (光标左移n行 )、2J(清屏)、K(清除从光标到行尾的内容)、s(保存光标位置)、u(恢复光标位置)、?25l(隐藏光标)、?25l(显示光标)。     其中 ,'\033[0m'用于恢复默认的终端输出属性,否则会影响后续的输出。

论坛徽章:
1
2015年亚洲杯之朝鲜
日期:2015-04-24 14:51:09
4 [报告]
发表于 2015-06-12 08:56 |只看该作者
                                            没图啊

论坛徽章:
0
5 [报告]
发表于 2015-06-14 15:26 |只看该作者
为啥不做个界面的
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP