C语言写的俄罗斯方块程序

  • 来源: 编程中国 作者: 若水   2008-03-27/15:31
  •     大概在最近两天之内编码完成,但此前一天开始构思。第一天晚上主要完成了方块旋转算法,第二天也就是今天加了消方块的处理算法。但是可能还有一些考虑不周的地方,比如,没有采用定时中断,而是图方便采用了和cpu频率有关的delay()函数来模拟时间间隔,这是需要改进的地方。
    其中的主要逻辑有:
    (1)由于c的随机性函数不好,所以每次游戏开始根据bios时间设置种子。
    (2)得分越高,方块下降速度越快(每200分为单位)。
    (3)每下落一个方块加1分,每消除一行加10分,两行加30分,三行加70分,四行加150分。初试分数为100分。
    游戏控制:
       up-旋转;空格-下落到底; 左右下方向键-控制方向。P-开始或暂停游戏。 ESC-退出。
    特点:
    (1)由于tc不支持中文,所以基本都是英文注释。
    (2)函数命名尽可能规范的表达其内部处理目的和过程。
    (3)代码加上注释仅有577行。(我下载过的两个俄罗斯方块代码一个在1087行,一个在993行,我的比它们代码少)。
    (4)除了消除空格时算法比较复杂,其他算法都比较简单易读。
    (5)绘图效率和局部代码效率扔有待提高。
    (6)FrameTime参数可能依据不同硬件环境进行具体设置,InitGame需要正确的TC路径。

        俄罗斯方块源于大约9年前上大一时的一个梦,我们在学习c语言时,我的同寝室友邀请我合作一起完成俄罗斯方块(课外作业性质),但是当时限于我们的水平比较菜和学习状态比较懒散,我们没有完成。大一的时候我在机房里无意发现别人留下的俄罗斯方块程序,运行,老师发现后激动的问我是我写的吗,我惭愧的摇摇头。那时看到别人做c的大程序深感羡慕(自己只是写几十行的程序)。数年后我仍然看到有不同样式的实现,但是我一直没有实现它,知道今天忽然有这个想法去做,算是弥补多年前的遗憾和心愿吧。
    -----------------------【以下是我的代码文件:】-----------------------------------------------
    /********************************/
    /* Desc:    俄罗斯方块游戏                */
    /* By:        hoodlum1980                */
    /* Email:    jinfd@126.com            */
    /* Date:    2008.03.12 22:30            */
    /********************************/
    #include <stdio.h>
    #include <bios.h>
    #include <dos.h>
    #include <graphics.h>
    #include <string.h>
    #include <stdlib.h>
    #define true         1
    #define false         0
    #define BoardWidth    12
    #define BoardHeight     23
    #define _INNER_HELPER /*inner helper method */
    /*Scan Codes Define*/
    enum KEYCODES
    {
        K_ESC                =0x011b,
        K_UP                =0x4800,        /* upward arrow */
        K_LEFT            =0x4b00,
        K_DOWN            =0x5000,
        K_RIGHT            =0x4d00,
        K_SPACE            =0x3920,
        K_P                =0x1970
    };

    /* the data structure of the block */
    typedef struct tagBlock
    {
        char c[4][4];    /* cell fill info array, 0-empty, 1-filled */
        int x;                /* block position cx [ 0,BoardWidht -1] */
        int y;                /* block position cy [-4,BoardHeight-1] */
        char color;        /* block color */
        char size;        /* block max size in width or height */
        char name;        /* block name (the block's shape) */
    } Block;

    /* game's global info */
    int FrameTime= 1300;
    int CellSize= 18;
    int BoardLeft= 30;
    int BoardTop=    30;

    /* next block grid */
    int NBBoardLeft= 300;
    int NBBoardTop=    30;
    int NBCellSize=  10;

    /* score board position */
    int ScoreBoardLeft= 300;
    int ScoreBoardTop=100;
    int ScoreBoardWidth=200;#p#分页标题#e#
    int ScoreBoardHeight=35;
    int ScoreColor=LIGHTCYAN;

    /* infor text postion */
    int InfoLeft=300;
    int InfoTop=200;
    int InfoColor=YELLOW;

    int BorderColor=DARKGRAY;
    int BkGndColor=BLACK;
    int GameRunning=true;
    int TopLine=BoardHeight-1;    /* top empty line */
    int TotalScore=100;
    char info_score[20];
    char info_help[255];
    char info_common[255];

    /* our board, Board[x][y][0]-isFilled, Board[x][y][1]-fillColor */
    unsigned char Board[BoardWidth][BoardHeight][2];
    char BufferCells[4][4];    /* used to judge if can rotate block */
    Block curBlock;        /* current moving block */
    Block nextBlock;    /* next Block to appear */

    /* function list */
    int GetKeyCode();
    int CanMove(int dx,int dy);
    int CanRotate();
    int RotateBlock(Block *block);
    int MoveBlock(Block *block,int dx,int dy);
    void DrawBlock(Block *block,int,int,int);
    void EraseBlock(Block *block,int,int,int);
    void DisplayScore();
    void DisplayInfo(char* text);
    void GenerateBlock(Block *block);
    void NextBlock();
    void InitGame();
    int PauseGame();
    void QuitGame();

    /*Get Key Code */
    int GetKeyCode()
    {
        int key=0;
        if(bioskey(1))
        {
            key=bioskey(0);
        }
        return key;
    }

    /* display text! */
    void DisplayInfo(char *text)
    {
        setcolor(BkGndColor);
        outtextxy(InfoLeft,InfoTop,info_common);
        strcpy(info_common,text);
        setcolor(InfoColor);
        outtextxy(InfoLeft,InfoTop,info_common);
    }

    /* create a new block by key number,
    * the block anchor to the top-left corner of 4*4 cells
    */
    void _INNER_HELPER GenerateBlock(Block *block)
    {
        int key=(random(13)*random(17)+random(1000)+random(3000))%7;
        block->size=3;/* because most blocks' size=3 */
        memset(block->c,0,16);
        switch(key)
        {
            case 0:
                block->name='T';
                block->color=RED;
                block->c[1][0]=1;
                block->c[1][1]=1, block->c[2][1]=1;
                block->c[1][2]=1;
                break;
            case 1:
                block->name='L';
                block->color=YELLOW;
                block->c[1][0]=1;
                block->c[1][1]=1;
                block->c[1][2]=1, block->c[2][2]=1;
                break;
            case 2:
                block->name='J';
                block->color=LIGHTGRAY;
                block->c[1][0]=1;
                block->c[1][1]=1;
                block->c[1][2]=1, block->c[0][2]=1;
                break;
            case 3:
                block->name='z';
                block->color=CYAN;
                block->c[0][0]=1, block->c[1][0]=1;
                block->c[1][1]=1, block->c[2][1]=1;
                break;
            case 4:
                block->name='5';#p#分页标题#e#
                block->color=LIGHTBLUE;
                block->c[1][0]=1, block->c[2][0]=1;
                block->c[0][1]=1, block->c[1][1]=1;
                break;
            case 5:
                block->name='o';
                block->color=BLUE;
                block->size=2;
                block->c[0][0]=1, block->c[1][0]=1;
                block->c[0][1]=1, block->c[1][1]=1;
                break;
            case 6:
                block->name='I';
                block->color=GREEN;
                block->size=4;
                block->c[1][0]=1;
                block->c[1][1]=1;
                block->c[1][2]=1;
                block->c[1][3]=1;
                break;
        }
    }

    /* get next block! */
    void NextBlock()
    {
        /* copy the nextBlock to curBlock */
        curBlock.size=nextBlock.size;
        curBlock.color=nextBlock.color;
        curBlock.x=(BoardWidth-4)/2;
        curBlock.y=-curBlock.size;
        memcpy(curBlock.c,nextBlock.c,16);
        /* generate nextBlock and show it */
        EraseBlock(&nextBlock,NBBoardLeft,NBBoardTop,NBCellSize);
        GenerateBlock(&nextBlock);
        nextBlock.x=1,nextBlock.y=0;
        DrawBlock(&nextBlock,NBBoardLeft,NBBoardTop,NBCellSize);
    }

    /* rotate the block, update the block struct data */
    int _INNER_HELPER RotateCells(char c[4][4],char blockSize)
    {
        char temp,i,j;
        switch(blockSize)
        {
            case 3:
                temp=c[0][0];
                c[0][0]=c[2][0], c[2][0]=c[2][2],    c[2][2]=c[0][2], c[0][2]=temp;
                temp=c[0][1];
                c[0][1]=c[1][0], c[1][0]=c[2][1],    c[2][1]=c[1][2], c[1][2]=temp;
                break;
            case 4:    /* only 'I' block arived here! */
                c[1][0]=1-c[1][0], c[1][2]=1-c[1][2], c[1][3]=1-c[1][3];
                c[0][1]=1-c[0][1], c[2][1]=1-c[2][1],    c[3][1]=1-c[3][1];
                break;
        }
    }

    /* judge if the block can move toward the direction */
    int CanMove(int dx,int dy)
    {
        int i,j,tempX,tempY;
        for(i=0;i<curBlock.size;i++)
        {
            for(j=0;j<curBlock.size;j++)
            {
                if(curBlock.c[i][j])
                {
                    /* cannot move leftward or rightward */
                    tempX = curBlock.x + i + dx;
                    if(tempX<0 || tempX>(BoardWidth-1))    return false; /* make sure x is valid! */#p#分页标题#e#
                    /* cannot move downward */
                    tempY = curBlock.y + j + dy;
                    if(tempY>(BoardHeight-1))    return false; /* y is only checked lower bound, maybe negative!!!! */
                    /* the cell already filled, we must check Y's upper bound before check cell ! */
                    if(tempY>=0 && Board[tempX][tempY][0]) return false;
                }
            }
        }
        return true;
    }

    /* judge if the block can rotate */
    int CanRotate()
    {
        int i,j,tempX,tempY;
        /* update buffer */
        memcpy(BufferCells, curBlock.c, 16);
        RotateCells(BufferCells,curBlock.size);
        for(i=0;i<curBlock.size;i++)
        {
            for(j=0;j<curBlock.size;j++)
            {
                if(BufferCells[i][j])
                {
                    tempX=curBlock.x+i;
                    tempY=curBlock.y+j;
                    if(tempX<0 || tempX>(BoardWidth-1))
                        return false;
                    if(tempY>(BoardHeight-1))
                        return false;
                    if(tempY>=0 && Board[tempX][tempY][0])
                        return false;
                }
            }
        }
        return true;
    }

    /* draw the block */
    void _INNER_HELPER DrawBlock(Block *block,int bdLeft,int bdTop,int cellSize)
    {
        int i,j;
        setfillstyle(SOLID_FILL,block->color);
        for(i=0;i<block->size;i++)
        {
            for(j=0;j<block->size;j++)
            {
                if(block->c[i][j] && (block->y+j)>=0)
                {
                    floodfill(
                        bdLeft+cellSize*(i+block->x)+cellSize/2,
                        bdTop+cellSize*(j+block->y)+cellSize/2,
                        BorderColor);
                }
            }
        }
    }

    /* Rotate the block, if success, return true */
    int RotateBlock(Block *block)
    {
        char temp,i,j;
        int b_success;
        if(curBlock.size==2)
            return;
        if(( b_success=CanRotate()))
        {
            EraseBlock(block,BoardLeft,BoardTop,CellSize);
            memcpy(curBlock.c,BufferCells,16);
            DrawBlock(block,BoardLeft,BoardTop,CellSize);#p#分页标题#e#
        }
        return b_success;
    }


    /* erase a block, only fill the filled cell with background color */
    void _INNER_HELPER EraseBlock(Block *block,int bdLeft,int bdTop,int cellSize)
    {
        int i,j;
        setfillstyle(SOLID_FILL,BkGndColor);
        for(i=0;i<block->size;i++)
        {
            for(j=0;j<block->size;j++)
            {
                if(block->c[i][j] && (block->y+j>=0))
                {
                    floodfill(
                        bdLeft+cellSize*(i+block->x)+cellSize/2,
                        bdTop+cellSize*(j+block->y)+cellSize/2,
                        BorderColor);
                }
            }
        }
    }

    /* move by the direction if can, donothing if cannot
    * return value: true - success, false - cannot move toward this direction
    */
    int MoveBlock(Block *block,int dx,int dy)
    {
        int b_canmove=CanMove(dx,dy);
        if(b_canmove)
        {
            EraseBlock(block,BoardLeft,BoardTop,CellSize);
            curBlock.x+=dx;
            curBlock.y+=dy;
            DrawBlock(block,BoardLeft,BoardTop,CellSize);
        }
        return b_canmove;
    }

    /* drop the block to the bottom! */
    int DropBlock(Block *block)
    {
        EraseBlock(block,BoardLeft,BoardTop,CellSize);
        while(CanMove(0,1))
        {
            curBlock.y++;
        }
        DrawBlock(block,BoardLeft,BoardTop,CellSize);
        return 0;/* return value is assign to the block's alive */
    }


    /* init the graphics mode, draw the board grid */
    void InitGame()
    {
        int i,j,gdriver=DETECT,gmode;
        struct time sysTime;
        /* draw board cells */
        memset(Board,0,BoardWidth*BoardHeight*2);
        memset(nextBlock.c,0,16);
        strcpy(info_help,"P: Pause Game. --by hoodlum1980");
        initgraph(&gdriver,&gmode,"c:\\tc\\");
        setcolor(BorderColor);
        for(i=0;i<=BoardWidth;i++)
        {
            line(BoardLeft+i*CellSize, BoardTop, BoardLeft+i*CellSize, BoardTop+ BoardHeight*CellSize);
        }
        for(i=0;i<=BoardHeight;i++)
        {
            line(BoardLeft, BoardTop+i*CellSize, BoardLeft+BoardWidth*CellSize, BoardTop+ i*CellSize);
        }
        /* draw board outer border rect */
        rectangle(BoardLeft-CellSize/4, BoardTop-CellSize/4,
            BoardLeft+BoardWidth*CellSize+CellSize/4,
            BoardTop+BoardHeight*CellSize+CellSize/4);

        /* draw next block grids */
        for(i=0;i<=4;i++)
        {
            line(NBBoardLeft+i*NBCellSize, NBBoardTop, NBBoardLeft+i*NBCellSize, NBBoardTop+4*NBCellSize);
            line(NBBoardLeft, NBBoardTop+i*NBCellSize, NBBoardLeft+4*NBCellSize, NBBoardTop+ i*NBCellSize);
        }

        /* draw score rect */
        rectangle(ScoreBoardLeft,ScoreBoardTop,ScoreBoardLeft+ScoreBoardWidth,ScoreBoardTop+ScoreBoardHeight);
        DisplayScore();

        /* set new seed! */
        gettime(&sysTime);#p#分页标题#e#
        srand(sysTime.ti_hour*3600+sysTime.ti_min*60+sysTime.ti_sec);

        GenerateBlock(&nextBlock);
        NextBlock();    /* create first block */
        setcolor(DARKGRAY);
        outtextxy(InfoLeft,InfoTop+20,"Up  -rotate  Space-drop");
        outtextxy(InfoLeft,InfoTop+35,"Left-left    Right-right");
        outtextxy(InfoLeft,InfoTop+50,"Esc -exit");
        DisplayInfo(info_help);
    }

    /* set the isFilled and fillcolor data to the board */
    void _INNER_HELPER FillBoardData()
    {
        int i,j;
        for(i=0;i<curBlock.size;i++)
        {
            for(j=0;j<curBlock.size;j++)
            {
                if(curBlock.c[i][j] && (curBlock.y+j)>=0)
                {
                    Board[curBlock.x+i][curBlock.y+j][0]=1;
                    Board[curBlock.x+i][curBlock.y+j][1]=curBlock.color;
                }
            }
        }
    }

    /* draw one line of the board */
    void _INNER_HELPER PaintBoard()
    {
        int i,j,fillcolor;
        for(j=max((TopLine-4),0);j<BoardHeight;j++)
        {
            for(i=0;i<BoardWidth;i++)
            {
                fillcolor=Board[i][j][0]? Board[i][j][1]:BkGndColor;
                setfillstyle(SOLID_FILL,fillcolor);
                floodfill(BoardLeft+i*CellSize+CellSize/2,BoardTop+j*CellSize+CellSize/2,BorderColor);
            }
        }
    }

    /* check if one line if filled full and increase the totalScore! */
    void _INNER_HELPER CheckBoard()
    {
        int i,j,k,score=10,sum=0,topy,lines=0;
        /* we find the top empty line! */
        j=topy=BoardHeight-1;
        do
        {
            sum=0;
            for(i=0;i< BoardWidth; i++)
            {
                sum+=Board[i][topy][0];
            }
            topy--;
        } while(sum>0 && topy>0);

        /* remove the full filled line (max remove lines count = 4) */
        do
        {
            sum=0;
            for(i=0;i< BoardWidth; i++)
                sum+=Board[i][j][0];

            if(sum==BoardWidth)/* we find this line is full filled, remove it! */
            {
                /* move the cells data down one line */
                for(k=j; k > topy;k--)
                {
                    for(i=0;i<BoardWidth;i++)
                    {
                        Board[i][k][0]=Board[i][k-1][0];
                        Board[i][k][1]=Board[i][k-1][1];
                    }
                }
                /*make the top line empty! */#p#分页标题#e#
                for(i=0;i<BoardWidth;i++)
                {
                    Board[i][topy][0]=0;
                    Board[i][topy][1]=0;
                }
                topy++;        /* move the topline downward one line! */
                lines++;    /* lines <=4 */
                TotalScore+=score;
                score*=2;    /* adding: 10, 30, 70, 150 */
            }
            else
                j--;
        } while(sum>0 && j>topy && lines<4);
        /* speed up the game when score is high, minimum is 400 */
        FrameTime=max(1200-100*(TotalScore/200), 400);
        TopLine=topy;/* update the top line */
        /* if no lines remove, only add 1: */
        if(lines==0)
            TotalScore++;
    }

    /* display the score */
    void _INNER_HELPER DisplayScore()
    {
        setcolor(BkGndColor);
        outtextxy(ScoreBoardLeft+5,ScoreBoardTop+5,info_score);
        setcolor(ScoreColor);
        sprintf(info_score,"Score: %d",TotalScore);
        outtextxy(ScoreBoardLeft+5,ScoreBoardTop+5,info_score);
    }

    /* we call this function when a block is inactive. */
    void UpdateBoard()
    {
        FillBoardData();
        CheckBoard();
        PaintBoard();
        DisplayScore();
    }

    /* pause the game, and timer handler stop move down the block! */
    int PauseGame()
    {
        int key=0;
        DisplayInfo("Press P to Start or Resume!");
        while(key!=K_P && key!=K_ESC)
        {
            while(!(key=GetKeyCode())){}
        }
        DisplayInfo(info_help);
        return key;
    }

    /* quit the game and do cleaning work. */
    void QuitGame()
    {
        closegraph();
    }
    /* the entry point function. */
    void main()
    {
        int i,flag=1,j,key=0,tick=0;
        InitGame();
        if(PauseGame()==K_ESC)
            goto GameOver;
        /* wait until a key pressed */
        while(key!=K_ESC)
        {
            /* wait until a key pressed */
            while(!(key=GetKeyCode()))
            {
                tick++;
                if(tick>=FrameTime)
                {
                    /* our block has dead! (can't move down), we get next block */
                    if(!MoveBlock(&curBlock,0,1))
                    {
                        UpdateBoard();
                        NextBlock();
                        if(!CanMove(0,1))
                            goto GameOver;
                    }#p#分页标题#e#
                    tick=0;
                }
                delay(100);
            }
            switch(key)
            {
                case K_LEFT:
                    MoveBlock(&curBlock,-1,0);
                    break;
                case K_RIGHT:
                    MoveBlock(&curBlock,1,0);
                    break;
                case K_DOWN:
                    MoveBlock(&curBlock,0,1);
                    break;
                case K_UP:
                    RotateBlock(&curBlock);
                    break;
                case K_SPACE:
                    DropBlock(&curBlock);
                    break;
                case K_P:
                    PauseGame();
                    break;
            }
        }
    GameOver:
        DisplayInfo("GAME OVER!  Press any key to exit!");
        getch(); /* wait the user Press any key. */
        QuitGame();
    }
    ----------------------------------【代码文件结尾】--------------------------------------------------

    评论 {{userinfo.comments}}

    {{money}}

    {{question.question}}

    A {{question.A}}
    B {{question.B}}
    C {{question.C}}
    D {{question.D}}
    提交

    驱动号 更多