zoukankan      html  css  js  c++  java
  • 第一个C语言项目开发------俄罗斯方块的设计与实现

    这是上大学敲得第一个完整的项目,使用的开发工具是codeblock,是用纯C语言编写的。效果如下

    现在把完整的整合出来

    主体部分代码

    #include "Tetris.h"

    void gotoxyWithFullWidth(short x,short y)
    {
    static COORD pos;
    pos.X=x*2;
    pos.Y=y;
    SetConsoleCursorPosition(Output,pos);
    }

    //显示提示信息
    void printPrompting()
    {
    SetConsoleTextAttribute(Output,0x0B);
    //gotoxyWithFullWidth(0,1);
    //printf("请输入等级 1-1000:dj=");
    gotoxyWithFullWidth(26,8);
    printf(" 下一方块 下下一方块");
    gotoxyWithFullWidth(26,10);
    printf("■控制");
    gotoxyWithFullWidth(27,12);
    printf("□向左移动:← A 4");
    gotoxyWithFullWidth(27,13);
    printf("□向右移动:→ D 6");
    gotoxyWithFullWidth(27,14);
    printf("□向下移动:↓ S 2");
    gotoxyWithFullWidth(27,15);
    printf("□顺时针转:↑ W 8");
    gotoxyWithFullWidth(27,16);
    printf("□逆时针转:0");
    gotoxyWithFullWidth(27,17);
    printf("□直接落地:空格");
    gotoxyWithFullWidth(27,18);
    printf("□暂停游戏:回车");
    gotoxyWithFullWidth(26,22);
    printf("■作者 :       ");

    //gotoxyWithFullWidth(28,22);
    // printf("★★版权所有,仿冒必究★★");

    }
    //==========================================================================================
    //显示游戏池边界()
    void printPoolBorder()
    {
    int x,y;
    SetConsoleTextAttribute(Output,0xF0);//背景色高亮白色
    for(y=4;y<26;++y)
    {
    gotoxyWithFullWidth(10,y-3);
    SetConsoleTextAttribute(Output,0xBB);
    printf("%2s","◆");
    gotoxyWithFullWidth(23,y-3);
    SetConsoleTextAttribute(Output,0xBB);
    printf("%2s","◆");


    }
    for(x=10;x<=23;x++)
    {
    gotoxyWithFullWidth(10,22);
    SetConsoleTextAttribute(Output,0xBB);
    printf("%2s","◆");
    }
    for (y=0;y<26;++y)
    {
    gotoxyWithFullWidth(0,y);
    SetConsoleTextAttribute(Output,0xCE);
    printf ("%2s","●");
    gotoxyWithFullWidth(39,y);
    SetConsoleTextAttribute(Output,0xCE);
    printf ("%2s","●");
    }
    for (x=0;x<=39;++x)
    {
    gotoxyWithFullWidth(x,1);
    SetConsoleTextAttribute(Output,0xCE);
    printf ("%2s","●");
    gotoxyWithFullWidth(x,26);
    SetConsoleTextAttribute(Output,0xCE);
    printf ("%2s","●");
    }

    gotoxyWithFullWidth(10,y-3);
    SetConsoleTextAttribute(Output,0xBB);
    printf("%28s","");

    }
    //==================================================================================================
    //显示得分信息
    void printScore(const Manager *manager)
    {
    SetConsoleTextAttribute(Output,0X0B);
    gotoxyWithFullWidth(2,2);
    printf("■得分:%u",manager->score);
    gotoxyWithFullWidth(1,6);
    printf("■消行总数:%u",manager->erasedTotal);
    int i;
    for(i=1;i<=4;++i)
    {
    SetConsoleTextAttribute(Output,0X0C);
    gotoxyWithFullWidth(2,8+i);
    printf("□消%d:%u",i,manager->erasedCount[i-1]);
    }
    gotoxyWithFullWidth(1,15);
    printf("■方块总数:%u",manager->tetrisTotal);
    static const char * tetirsName="ITLJZSO";
    for(i=0;i<7;++i)
    {
    SetConsoleTextAttribute(Output,0X0E);
    gotoxyWithFullWidth(2,17+i);
    printf("□%c形:%u",tetirsName[i],manager->tetrisCount[i]);
    }
    }
    //===========================================================================================================
    //显示下个及下下个方块的显示边框
    void printNextBorder()
    {

    SetConsoleTextAttribute(Output,0X0F);
    gotoxyWithFullWidth(26,2);
    printf("┌────┬─────┐");
    gotoxyWithFullWidth(26,3);
    printf("│ │ │");
    gotoxyWithFullWidth(26,4);
    printf("│ │ │");
    gotoxyWithFullWidth(26,5);
    printf("│ │ │");
    gotoxyWithFullWidth(26,6);
    printf("│ │ │");
    gotoxyWithFullWidth(26,7);
    printf("└────┴─────┘");
    gotoxyWithFullWidth(1,28);

    }
    //============================================================================
    void startGame(Manager *manager,Control *control)
    {

    memset(manager,0,sizeof(Manager));//游戏相关数据结构体全部清0
    memset(control,0,sizeof(Control));
    memcpy(manager->pool,gs_uInitialTetrisPool,sizeof(unsigned int [28]));
    srand((unsigned)time(NULL));//设置随机数种子
    manager->type[1]=rand()%7;//初始化下一个方块的形状
    manager->orientation[1]=rand()%4;//初始化下一个方块的状态
    manager->type[2]=rand()%7;//初始化下下一个方块的形状
    manager->orientation[2]=rand()%4;//初始化下下一个方块的状态
    //memset(control,0,sizeof(Control));
    initTetris(manager);
    setPoolColor(manager,control);
    }
    //==============================================================================================
    void initGame(Manager *manager,Control *control)//控制台标题,光标隐藏,游戏池初始化
    {
    SetConsoleTitle("俄罗斯方块");
    CONSOLE_CURSOR_INFO cursorInfo={1,FALSE};
    Output=GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorInfo(Output,&cursorInfo);
    startGame(manager,control);
    }
    //================================================================================
    void printNextTetirs(const Manager *manager)
    {
    unsigned int tetris;
    tetris=TetrisTable[manager->type[1]][manager->orientation[1]];
    SetConsoleTextAttribute(Output,manager->type[1]| 8);
    int i;
    for (i=0;i<16;++i)
    {
    gotoxyWithFullWidth(30-(i&3),(i>>2)+3);
    ((tetris>>i)&1)? printf("■"):printf("%2s","");

    }
    tetris=TetrisTable[manager->type[2]][manager->orientation[2]];
    SetConsoleTextAttribute(Output,manager->type[2]| 8);
    for (i=0;i<16;++i)
    {
    gotoxyWithFullWidth(35-(i&3),(i>>2)+3);
    ((tetris>>i)&1)? printf("■"):printf("%2s","");
    }
    }
    //==============================================================================================
    //初始化方块
    void initTetris(Manager *manager)
    {
    unsigned int tetris;
    manager->type[0]=manager->type[1];//当前类型=下一方块类型
    manager->orientation[0]=manager->orientation[1];//当前状态=下一方块状态
    manager->type[1]=manager->type[2];//下一类型=下下一方块类型
    manager->orientation[1]=manager->orientation[2];//下一状态=下下一方块状态
    manager->type[2]=rand()%7;//下下一方块随机产生
    manager->orientation[2]=rand()%4;
    tetris=TetrisTable[manager->type[0]][manager->orientation[0]];
    manager->x=6;//定义方块位置
    manager->y=0;
    if (checkCollision(manager))
    manager->dead=true;//有碰撞,游戏结束
    else
    {
    insertTetris(manager);//没有碰撞,插入方块,
    }
    ++manager->tetrisTotal;//方块总数增加
    ++manager->tetrisCount[manager->type[0]];//相应方块数增加
    printNextBorder();
    printScore(manager);
    printNextTetirs(manager);//显示下一个,下下一个方块
    //显示得分信息
    }
    //================================================================================
    //插入方块
    void insertTetris(Manager *manager)
    {
    unsigned int tetris;
    int z;
    z=12-manager->x;
    tetris=TetrisTable[manager->type[0]][manager->orientation[0]];
    manager->pool[manager->y+0]|=((tetris>>0x0 & 0x000F)<<z);
    manager->pool[manager->y+1]|=((tetris>>0x4 & 0x000F)<<z);
    manager->pool[manager->y+2]|=((tetris>>0x8 & 0x000F)<<z);
    manager->pool[manager->y+3]|=((tetris>>0xC & 0x000F)<<z);
    }
    //===================================================================================
    //显示当前方块
    void printCurrentTetris(const Manager *manager,const Control *control)
    {
    //显示当前方块是在移动后调用的,要擦去移动前的方块,需要扩展
    //向上扩展一行manager->y 向左,向右扩张 manager->x
    int x,y;
    y=(manager->y>4) ? (manager->y-1):4;
    for(;y<26 && y<(manager->y+4);++y)
    {
    //x=manager->x;
    x=(manager->x>2) ? (manager->x-1):2;
    for(;x<14 && x<(manager->x+5);++x)
    {
    gotoxyWithFullWidth(x+9,y-3);
    if (manager->pool[y]<<x&0x8000)
    {SetConsoleTextAttribute(Output,control->color[y][x]);
    printf("■");
    }
    else
    {
    SetConsoleTextAttribute(Output,0x00);
    printf("%2s","");
    }
    }
    }
    }
    //====================================================================================
    //检测碰撞
    bool checkCollision(const Manager *manager)
    {
    unsigned int tetris;
    tetris=TetrisTable[manager->type[0]][manager->orientation[0]];
    int z;
    z=12-manager->x;
    unsigned int dest=0;
    dest|=(((manager->pool[manager->y+0]>>z)<<0x0)& 0x000F);
    dest|=(((manager->pool[manager->y+1]>>z)<<0x4)& 0x00F0);
    dest|=(((manager->pool[manager->y+2]>>z)<<0x8)& 0x0F00);
    dest|=(((manager->pool[manager->y+3]>>z)<<0xC)& 0xF000);
    return ((dest & tetris)!=0);
    }
    //===========================================================================================
    //改变游戏池中每个方块的颜色
    void setPoolColor(const Manager *manager,Control *control)
    {
    unsigned int tetris;
    tetris=TetrisTable[manager->type[0]][manager->orientation[0]];
    int i,x,y;
    for(i=0;i<16;++i)
    {
    y=(i>>2)+manager->y;//待设置的列
    if(y>26)
    {
    break;
    }
    x=manager->x+3-(i&3);
    if(tetris>>i&1)
    {
    control->color[y][x]=(manager->type[0]|8);//设置颜色
    }

    }

    }
    //======================================================================================================
    void printTetrisPool(const Manager *manager,const Control *control)
    {
    int x,y;
    for (y=4;y<26;++y)
    {
    gotoxyWithFullWidth(11,y-3);
    for(x=2;x<14;++x)
    {
    if((manager->pool[y]<<x)&0x8000)
    {SetConsoleTextAttribute(Output,control->color[y][x]);
    printf("■");}

    else
    {
    SetConsoleTextAttribute(Output,0);
    printf("%2s","");
    }

    }
    }
    }
    //=================================================================================================================
    //移除当前方块,其余保持不变
    void removeTetris(Manager *manager)
    {
    unsigned int tetris;
    tetris=TetrisTable[manager->type[0]][manager->orientation[0]];
    int z;
    int y;
    z=12-manager->x;
    manager->pool[manager->y+0]&=~(((tetris>>0x0)&0x000F)<<z);
    manager->pool[manager->y+1]&=~(((tetris>>0x4)&0x000F)<<z);
    manager->pool[manager->y+2]&=~(((tetris>>0x8)&0x000F)<<z);
    manager->pool[manager->y+3]&=~(((tetris>>0xC)&0x000F)<<z);

    }
    //===============================================================================================
    //向下移动方块
    void moveDownTetris(Manager *manager,Control *control)
    {
    int y=manager->y;//原来位置 manager->y
    removeTetris(manager);
    //manager->y=manager->y+1;//manager-> 变化
    ++manager->y;
    if(checkCollision(manager))//有碰撞 恢复原来位置 插入当前方块
    {
    manager->y=y;
    insertTetris(manager);
    //initTetris(manager);
    if(checkFrasing(manager,control))//有消行
    {
    printTetrisPool(manager,control);
    }
    }
    else//无碰撞 插入当前方块
    {
    insertTetris(manager);
    setPoolColor(manager,control);
    printCurrentTetris(manager,control);
    }
    }
    //============================================================================================
    void runGame(Manager *manager,Control *control)
    {
    clock_t clockLast,clockNow;
    clockLast=clock();
    printTetrisPool(manager,control);


    while(!manager->dead)//没挂
    {
    while(_kbhit())
    {
    keydownControl(manager,control,getch());
    }
    if(!control->pause)
    {
    clockNow=clock();
    if(clockNow-clockLast>=450)//
    {
    clockLast=clockNow;
    moveDownTetris(manager,control);
    }

    }
    }
    }

    //=================================================================================================
    void hozeMoveTetris(Manager *manager,Control *control)
    {
    int x=manager->x;//记录原来X位置
    removeTetris(manager);
    control->direction==0 ? (--manager->x):(++manager->x);//左移或右移
    if(checkCollision(manager))//检测到碰撞
    {
    manager->x=x;//恢复原来位置
    insertTetris(manager);
    //initTetris(manage);
    }
    else //没有碰撞
    {
    insertTetris(manager);
    setPoolColor(manager,control);
    printCurrentTetris(manager,control);
    }
    }
    //===================================================================
    //按键控制
    void keydownControl(Manager *manager,Control *control,int key)
    {
    //处理暂停
    if(key == 13)
    {
    control->pause=!control->pause;
    }
    if(control->pause)//暂停状态
    {
    return;
    }
    switch(key)
    {
    case 75: //左移
    case'A':
    case'a':
    case'4':
    control->direction=0;
    hozeMoveTetris(manager,control);
    break;
    case 77://右移
    case 'D':
    case'd':
    case '6':
    control->direction=1;
    hozeMoveTetris(manager,control);
    break;
    case 80: //下移
    case 'S':
    case's' :
    case '2':
    moveDownTetris(manager,control);
    break;
    case 72://顺时针转
    case 'W':
    case 'w':
    case '8':
    control->chockwise=true;
    rotateTetris(manager,control);
    break;
    case '0'://逆时针转
    control->chockwise=false;
    rotateTetris(manager,control);
    break;
    case ' '://直接落地
    dropDownTetris(manager,control);
    break;
    default:
    break;

    }
    }
    //========================================================================================
    //旋转方块
    void rotateTetris(Manager *manager,Control *control)
    {
    int ori=manager->orientation[0];//记录当前方块状态
    removeTetris(manager) ;//移走当前方块
    manager->orientation[0]=(control->chockwise)?(ori+1)&3:(ori+3)&3;//顺指针改变状态
    if(checkCollision(manager))//有碰撞
    {
    manager->orientation[0]=ori;//恢复原来状态
    insertTetris(manager);//插入当前方块
    }
    else//无碰撞 放入当前方块
    {
    insertTetris(manager);
    setPoolColor(manager,control);//设置颜色,显示当前方块
    printCurrentTetris(manager,control);
    }

    }
    //==============================================================
    //消行检测
    bool checkFrasing(Manager *manager,Control *control)
    {
    static const unsigned int score[5]={0,1,3,6,9};//定义数组存分数 消0,1,2,3,4
    int count=0;//定义变量用于存很消行数 0
    int y=manager->y+3;//从下往上检测 y=manager->y+3
    do
    {
    if(y<26 && manager->pool[y]==0xFFFFU)//游戏池底部不能消且一行的值全为1
    {
    count++;//增加消行数
    memmove(manager->pool+1,manager->pool,sizeof(unsigned int)*y);
    memmove(control->color[1],control->color[0],sizeof(int[16])*y);//游戏池整体下移一行,颜色下移一行
    gotoxyWithFullWidth(15,18);
    SetConsoleTextAttribute(Output,0xF0);
    printf ("GOOD");
    }
    else
    {
    --y;
    }
    } while(y>=manager->y); //循环最多4次
    manager->erasedTotal+=count;//消行总数增加
    manager->score+=score[count];//得分增加
    if(count>0)
    {
    ++manager->erasedCount[count-1];//相应消行数增加
    }
    initTetris(manager);//初始化方块
    setPoolColor(manager,control);//设置游戏池颜色

    return (count>0);

    }
    //=======================================================
    //直接落地
    void dropDownTetris(Manager *manager,Control *control)
    {
    int y;
    removeTetris(manager);//移走当前方块
    for(;manager->y<26;++manager->y)
    {
    if(checkCollision(manager))
    {
    break;
    }
    }
    //循环 y<26 ++manager->y
    --manager->y;//--manager->y 恢复位置
    insertTetris(manager);//插入当前方块
    setPoolColor(manager,control);//设置颜色
    checkFrasing(manager,control); //检测消行
    printTetrisPool(manager,control);//显示游戏池
    }
    //==============================================================
    //再来一次
    bool ifPlayAgain()
    {
    int Y,y ,n, N;
    int ch; //定义整形变量,用于接受键盘输入的字符
    SetConsoleTextAttribute(Output,0xF0); //设置颜色, 前景色为黑色,背景色白色
    gotoxyWithFullWidth(15,10); //设置位置15,10
    printf("Game over"); //打印游戏结束
    gotoxyWithFullWidth(13,11);//设置位置13,11
    printf("按Y重玩,按N退出");//打印按y重玩, 按n退出
    do{ //do
    ch=_getch() ; //变量=从键盘接受字符
    if(ch=='y' ||ch== 'Y')
    {
    return true;
    } //判断是yY return true
    else if(ch=='n'||ch=='N')
    {
    return false;
    } //判断是nN return false;
    }while(1);
    } //while(1)
    //=============================================================
    //游戏等级
    //void youxidengji()
    //{
    // int a;
    // SetConsoleTextAttribute(output,0xF0);
    // gotoxyWithFullWidth(16,11);
    // printf("请选择等级123");

    //}

    Tetris.h文件

    #ifndef TETRIS_H_INCLUDED
    #define TETRIS_H_INCLUDED
    #include <stdio.h>//标准输入输出头文件
    #include <string.h>//标准字符串头文件
    #include<stdlib.h>//标准库头文件
    #include <time.h>//日期和时间头文件
    #include <conio.h>//控制台输入输出头文件
    #include<windows.h>//windows控制台头文件
    #include<stdbool.h>//标准库尔函数头文件
    //定义7种的方块的4种状态
    static const unsigned int TetrisTable[7][4]=
    {
    //==================================
    //正常状态
    {0x00F0,0x2222,0x00F0,0x2222},//I形方块的4个状态
    {0x7200,0x8C80,0x004E,0x0131},//T形方块的4个状态
    {0x6440,0x08E0,0x0226,0x0710},//L形方块的4个状态
    {0x4460,0x02E0,0x0622,0x0740},//J形方块的4个状态
    {0x3600,0x8C40,0x006C,0x0231},//Z形方块的4个状态
    {0x2640,0x0C60,0x0264,0x0630},//S形方块的4个状态
    {0x0660,0x0660,0x0660,0x0660},//O形方块的4个状态
    //==================================
    //全I形
    {0x0F00,0x4444,0x00F0,0x2222},
    {0x0F00,0x4444,0x00F0,0x2222},
    {0x0F00,0x4444,0x00F0,0x2222},
    {0x0F00,0x4444,0x00F0,0x2222},
    {0x0F00,0x4444,0x00F0,0x2222},
    {0x0F00,0x4444,0x00F0,0x2222},
    {0x0F00,0x4444,0x00F0,0x2222},
    //====================================
    //全O形
    {0x0660,0x0660,0x0660,0x0660},
    {0x0660,0x0660,0x0660,0x0660},
    {0x0660,0x0660,0x0660,0x0660},
    {0x0660,0x0660,0x0660,0x0660},
    {0x0660,0x0660,0x0660,0x0660},
    {0x0660,0x0660,0x0660,0x0660},
    {0x0660,0x0660,0x0660,0x0660},
    {0x0660,0x0660,0x0660,0x0660},
    };
    //初始化游戏池
    //28行每行16位,前26行每行16位,每行前2位,最后两位均为1,其余为0,用于检测碰撞
    //最后2行全部为1,用于检测碰撞
    //当某一行为0xFFFF时,该行已满,可以消行
    static const unsigned int gs_uInitialTetrisPool[28]=
    {
    0xC003,0xC003,0xC003,0xC003,
    0xC003,0xC003,0xC003,0xC003,
    0xC003,0xC003,0xC003,0xC003,
    0xC003,0xC003,0xC003,0xC003,
    0xC003,0xC003,0xC003,0xC003,
    0xC003,0xC003,0xC003,0xC003,
    0xC003,0xC003,0xFFFF,0xFFFF,
    };
    typedef struct TetrisManager //这个结构体存储游戏相关数据
    {
    unsigned int pool[28];//记录游戏池,28行,每行16位
    int x;//记录方块的x坐标,左上角坐标
    int y;//记录方块的y坐标
    int type[3];//当前,下一个,下下以一个方块的形状
    int orientation[3];//当前,下一个,下下一个的状态
    unsigned int score;//得分
    unsigned int erasedTotal;//消行总数
    unsigned int erasedCount[4];//消1,2,3,4的数目
    unsigned int tetrisTotal;//方块总数
    unsigned int tetrisCount [7];//7种方块的数目
    bool dead;//游戏结束
    }Manager ;
    typedef struct TetrisControl//这个结构体存储控制相关数据
    {
    bool pause;//暂停
    bool chockwise;//旋转方向 顺时针位true
    int direction;//移动方向,0;向左移动;1; 向右移动
    int color[28][16];//游戏池中每个点的颜色
    }Control;
    HANDLE Output;
    void gotoxyWithFullWidth(short x,short y);
    void printPrompting();//显示提示信息
    void printPoolBorder();//显示游戏池边界
    void printScore();//显示得分信息
    void printNextBorder();//显示下一个,下下一个边框
    void startGame(Manager *manager,Control *Control);
    void initGame (Manager *manager,Control *control);
    void printNextTetirs(const Manager *manager);//显示下一个下下一个方块
    void initTetris(Manager *manager);//初始化当前方块,下一个,下下一个
    void insertTetris(Manager *manager);//插入当前方块
    void printCurrentTetris(const Manager *manager,const Control *Control);//显示当前方块
    bool checkCollision(const Manager *manager);//碰撞检测
    void setPoolColor(const Manager *manager,Control *control);//设置游戏池当前方块颜色
    void printTetrisPool(const Manager *manager,const Control *control);
    void removeTetris(Manager *manager);//移除当前方块,游戏池中有方块的清零
    void moveDownTetris(Manager *manager,Control *control);
    void runGame(Manager *manager,Control *control);//运行游戏
    void hozeMoveTetris(Manager *manager,Control *control);//水平移动方块
    void keydownControl(Manager *manager,Control *control,int key);//按键控制
    void rotateTetris(Manager *manager,Control *control);//旋转方块
    bool checkFrasing(Manager *manager,Control *control);//消行检测
    void dropDownTetris(Manager *manager,Control *control);//方块直接落地
    bool ifPlayAgain();//再来一次

    //void youxidengji();//游戏等级

    #endif // TETRIS_H_INCLUDED

    主函数

    #include <stdio.h>
    #include <stdlib.h>
    #include "Tetris.h"
    int main()
    {

    Manager manager;
    Control control;
    //printNextBorder();
    initGame(&manager,&control);
    //initTetris(&manager);
    printPoolBorder();
    printPrompting();
    do
    {
    //printNextBorder();
    //printNextTetirs(&manager);
    printPrompting();
    printPoolBorder();
    //setPoolColor(&manager,&control);
    //printScore(&manager);
    //printCurrentTetris(&manager,&control);
    //insertTetris(&manager);
    //printTetrisPool(&manager,&control);
    //moveDownTetris(&manager,&control);
    runGame(&manager,&control);
    if(ifPlayAgain())//判断重玩函数是真,假
    {
    SetConsoleTextAttribute(Output,0x07);//真值,重玩 颜色设置为0x07
    system("cls");//system("cls")清屏
    startGame(&manager,&control);//重新开始游戏 startGame
    }

    else
    {
    break;
    }//假值 break
    //

    } while(1);
    gotoxyWithFullWidth(0,0);
    CloseHandle(Output);
    return 0;
    }

  • 相关阅读:
    随手记录---transform 属性
    界面实例--图片布局在前端
    随手记录---jq如何判断当前元素是第几个元素
    PDF.Js的使用—javascript中前端显示pdf文件
    Jszip的使用和打包下载图片
    有关Canvas的一点小事—canvas和resize
    form input限制
    idea打war包正确姿势
    轻松建站神器!15个超精致的Bootstrap网站模板下载
    bootstrap教程
  • 原文地址:https://www.cnblogs.com/DennySmith/p/12111152.html
Copyright © 2011-2022 走看看