发新话题
打印

俄罗斯方块程序

俄罗斯方块程序

大概在最近两天之内编码完成,但此前一天开始构思。第一天晚上主要完成了方块旋转算法,第二天也就是今天加了消方块的处理算法。但是可能还有一些考虑不周的地方,比如,没有采用定时中断,而是图方便采用了和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;
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';
            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[j])
            {
                /* cannot move leftward or rightward */
                tempX = curBlock.x + i + dx;
                if(tempX<0 || tempX>(BoardWidth-1))    return false; /* make sure x is valid! */
                /* 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[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[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);
    }
    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[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);
    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[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[j][0]? Board[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[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[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[k][0]=Board[k-1][0];
                    Board[k][1]=Board[k-1][1];
                }
            }
            /*make the top line empty! */
            for(i=0;i<BoardWidth;i++)
            {
                Board[topy][0]=0;
                Board[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;
                }
                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();
}
----------------------------------【代码文件结尾】-------------------------------------------------- 文
乐乎设计,乐乎生活~

TOP

C#的俄罗斯方块

http://www.cnblogs.com/zhouyinhui/archive/2006/05/05/392037.html
俄罗斯方块”游戏设计
          电子科技大学软件学院03级02班 周银辉
                                 转载请注明出处


说明:
这是一次尝试,一个比较成功的设计,其精彩的算法与漂亮的程序结构足以让人兴奋了。
这有别于常规的俄罗斯方块算法,如果你需要常规的实现方法,可以通过这个e_mail:yinhui_zhou@yahoo.com.cn索取。


平台说明:
开发平台    ms.net 2005
开发语言    C# 2.0


特殊名词说明:
3.1,象素坐标(Pixel Coordinate):以显示区域所在控件的Client Rectangle的左上角为坐标原点,一个象素点为单位1的坐标

3.2,网格坐标(Gird Coordinate):如果我们将显示区域分成m×n 的网格,那么其中某一个网格所在(列,行)组成的坐标,我们称之为网格坐标,在程序中网格坐标相关项以Gird或g或G开头

3.3,块(Block):一个小格子(比如游戏中的方块图形是由小格子组成的)称为一个块,将由块来组成游戏图形

3.4,形状(Shape):游戏中的由四个小格子组成的图形称为形状

3.5,基础点(Base Point):用于指示目前形状所在位置的一个(列,行)坐标点,以网格坐标为坐标。后面将详细说明其来历。

3.6,动态方块(Dynamic Block):用于构成运动的形状的Block,共4个

设计思想:
4.1,屏幕的组成:
     0 ⒈⒉⒊⒋⒌⒍⒎⒏⒐
  0  □□□□□□□□□□
  1  □□□□□□□□□□
  2  □□□□□□□□□□  
--------------                     
  3  □□□□□□□□□□
  4  □□□□□□□□□□
  5  □□□□□□□□□□
  6  □□□□□□□□□□
  7  □□□□□□□□□□
  8  □□□□□□□□□□
  9  □□□□□□□□□□
10  □□□□□□□□□□
11  □□□□□□□□□□
12  □□□□□□□□□□
13  □□□□□□□□□□
14  □□□□□□□□□□
15  □□□□□□□□□□
16  □□□□□□□□□□
17  □□□□□□□□□□
18  □□□□□□□□□□
19  □□□□□□□□□□
20  □□□□□□□□□□
21  □□□□□□□□□□
22  □□□□□□□□□□
--------------
23  ■■■■■■■■■■
屏幕由23行10列的网格组成;其中0~2行:初始的形状将在这里形成然后下落,这三行用户不可见;3~22行:游戏显示区域;23行,其标记已到屏幕底部。

4.2,形状的组成:
  每一种形状都是由四个方块组成,比如■■■■由四个方块横向排列而成

4.3,形状的统一:
  ■■■■等共19种形状(旋转前后的形状归为不同的形状),虽然在玩游戏时我们会去将各种不同的形状命不同的命(比如“条子”,“方块”等),但在设计游戏是它们却是统一的,它们都是“形状”。这一点是游戏成功的基础。
     为了使各种不同的形状达到统一的设计,我设计了如下解决方案:将形状始终放在4×4的格子中,以该4×4格子的第一个格子为“基础点”,只要给出组成形状的四个块相对于该基础点的相对坐标,那么在基础点网格坐标的基础上就能求出各块的网格坐标。
★□□□   ★为基础点,形状各块的相对坐标是相对于这个基础点的
□□□□
□□□□
□□□□
那么■■■■在其中就如图:其四个方块相对于基础点的网格坐标就为(0,2)(1,2)(2,2)(3,2)
□□□□
□□□□
■■■■  02122232
□□□□  
假设基础点的网格坐标是(gX, gY),那么此形状的坐标就为(gX+0,gY+2), (gX+1,gY+2), (gX+3,gY+2), (gX+3,gY+2)
我们将用一个int[8]记录下这四个相对坐标值(呵呵,用byte[8]就可以了哈)
同理:
□□□□
□□□□
■■□□
■■□□  02120313
这样,我们只要知道某个形状的相对坐标值数组,就可以轻松地求出它的各方块的排列方式,也就是其形状(样子)

4.4,移动与旋转的统一
   从上面我们可以看出形状的移动可以这样来实现: 移动基础点的网格坐标,然后组成形状的四个方块按照其与基础点坐标的相对值而改变网格坐标,则表现为移动。
   旋转与移动的原理一样:设旋转前的形状为A,旋转后的形状为B,组成形状A的四个方块按照B(而不是按照A)的相对于基础点坐标的相对值而改变网格坐标,则表现为旋转。
比如,
□□□□
□□□□
■■■■  02122232
□□□□  
移动: 设其基础点网格坐标为(gX,gY),其各方块当前坐标(gX+0,gY+2), (gX+1,gY+2), (gX+3,gY+2), (gX+3,gY+2)。如果其向左移动一格,那么它的基础了坐标gX-=1; gY=gY; 其各方块移动后坐标 (gX+0,gY+2), (gX+1,gY+2), (gX+3,gY+2), (gX+3,gY+2)。
旋转:设其基础点网格坐标为(gX,gY),其各方块当前坐标(gX+0,gY+2), (gX+1,gY+2), (gX+3,gY+2), (gX+3,gY+2)。如果其旋转一次,旋转后的形状如图
□■□□
□■□□  10111213
□■□□
□■□□
那么其旋转后的各方块坐标 (gX+1,gY+0), (gX+1,gY+1), (gX+1,gY+2), (gX+1,gY+3)

如果我们将各形状编号,比如■■■■编号0,其旋转90度以后的形状为编号1
那么0旋转目标为1,1的旋转目标为0
所以所有形状便得到了统一,如图:

           形状编号_相对坐标_旋转后的形状编号

(由于排版的问题,一下小方格都应该是左对齐的)
□□□□
□□□□
■■■■  0_02122232_1
□□□□
  
□■□□
□■□□  1_10111213_0
□■□□
□■□□

□□□□
■□□□
■□□□  2_00010212_3
■■□□  

□□□□
□□□□
■■■□  3_02122203_4
■□□□

□□□□
■■□□  
□■□□  4_01111213_5
□■□□

□□□□
□□■□
■■■□  5_21021222_2
□□□□

□□□□
□□□□
■■□□
■■□□  6_02120313_6

□□□□
□■□□
■■□□  7_11021203_8
■□□□   

□□□□
■■□□
□■■□  8_01111222_7
□□□□

□□□□
□■□□
□■□□  9_11120313_10
■■□□  

□□□□
■□□□
■■■□  10_01021222_11
□□□□

□□□□
□■■□
□■□□  11_11211213_12
□■□□

□□□□
□□□□
■■■□  12_02122223_9
□□■□

□□□□
■□□□
■■□□  13_01021213_14
□■□□

□□□□
□■■□  14_11210212_13
■■□□
□□□□

□□□□
□■□□
■■■□  15_11021222_16
□□□□

□□□□
□■□□
□■■□  16_11122213_17
□■□□

□□□□
□□□□
■■■□  17_02122213_18
□■□□                                                                                                                                                      

□□□□
■□□□
■■□□  18_11021213_15  
□■□□


数据结构
5.1,网格坐标:并没有提供这样一个类或结构,但提供了网格坐标与象素坐标的转换公式:
   pCoordinate = gCoordinate*(widthOfGird+distanceOfGird);
   象素坐标值 = 网格坐标值×(网格边长+网格间距)

5.2,网格点(GirdPoint):一个结构,它表示一个以网格坐标为坐标的点
public struct GirdPoint
{
  public int X;
  public int Y;
        //…各种构造函数
}

5.3,块(Block):继承于System.Windows.Forms.Label,用它来生成组成形状的块对象
public class Block: System.Windows.Forms.Label
{
  private GirdPoint gLocation;// 网格坐标
  //获取或设置 网格坐标值(若设置,那么其象素坐标会自动变化为相应值)
         public GirdPoint GLocation
     {
   get
   {
    return this.gLocation;
   }
   set
   {
    this.gLocation = value;
                   //其中Globals.ToPcoordinate()是一个将网格坐标值转换成象素坐标值的函数
    this.Location = new Point( Globals.ToPCoordinate(this.gLocation.X),
     Globals.ToPCoordinate(this.gLocation.Y));
   }
  }
        //……(各种构造函数)
}
对块位置的操作只需要指定其网格坐标,其象素坐标会自动变化为相应值

5.4,形状(Shape):一个结构,由它保存游戏中的形状的相关信息
public struct Shape
{
   //获取或设置该形状的索引值
   public int Index;
   //获取或设置一个数组,其为该形状的相对于基础点的相对网格坐标组
   public int[] DCoordinates;
  // 获取或设置该形状旋转后的新形状的索引
   public int EddiedIndex;
   public Shape(int index, int[] dCoordinates, int eddiedIndex)
   {
       this.Index = index;
       this.DCoordinates = dCoordinates;
      this.EddiedIndex = eddiedIndex;
}

5.5,背景方块数组(BackBlocks):一个Block[,],其维持着23行10列的Block对象,这些对象将作为屏幕的背景,当下落的形状下落到底部时,形状所在位置的背景方块将被点亮,这表现为下落的形状被固定住了

5.6,网格数组(GirdArray):一个int[,],其维持着整个屏幕的背景方块的状态,当该数组中的某个元素值由0变为1时,表示在屏幕中的该位置被某个方块占据,当由1变为0是,表示在屏幕中的该位置被释放而未被占据。被占据表现为屏幕上该位置已经有方块了。

5.7,动态方块数组(DynamicBlocksArray):一个Block[4],其维持着四个Block对象,游戏中的任何运动的形状都是由这四个Block对象变化位置而得到的

5.8,预览网格数组(ViewGirdArray):一个int[,],于GirdArray一样,只不过它维持着屏幕旁边的用于预览下一个即将出项的形状的小屏幕

5.9,预览方块数组(ViewDynamicBlocksArray):一个Block[4],其维持着四个Block对象,游戏中屏幕普遍的小屏幕显示了下一个即将出现的形状,而这个预览形状便是由这四个Block对象变化位置而得到的

5.10,Shapes:一个Shape[19],它维持着游戏中的19个形状
    public static readonly Shape[] Shapes = new Shape[19]
   {
    new Shape(0,new int[8]{0,2,1,2,2,2,3,2},1),
    new Shape(1,new int[8]{1,0,1,1,1,2,1,3},0),
    new Shape(2,new int[8]{0,0,0,1,0,2,1,2},3),
    new Shape(3,new int[8]{0,2,1,2,2,2,0,3},4),
    new Shape(4,new int[8]{0,1,1,1,1,2,1,3},5),
    new Shape(5,new int[8]{2,1,0,2,1,2,2,2},2),
    new Shape(6,new int[8]{0,2,1,2,0,3,1,3},6),
    new Shape(7,new int[8]{1,1,0,2,1,2,0,3},8),
    new Shape(8,new int[8]{0,1,1,1,1,2,2,2},7),
    new Shape(9,new int[8]{1,1,1,2,0,3,1,3},10),
    new Shape(10,new int[8]{0,1,0,2,1,2,2,2},11),
    new Shape(11,new int[8]{1,1,2,1,1,2,1,3},12),
    new Shape(12,new int[8]{0,2,1,2,2,2,2,3},9),
    new Shape(13,new int[8]{0,1,0,2,1,2,1,3},14),
    new Shape(14,new int[8]{1,1,2,1,0,2,1,2},13),
    new Shape(15,new int[8]{1,1,0,2,1,2,2,2},16),
    new Shape(16,new int[8]{1,1,1,2,2,2,1,3},17),
    new Shape(17,new int[8]{0,2,1,2,2,2,1,3},18),
           new Shape(18,new int[8]{1,1,0,2,1,2,1,3},15),
              }


功能的实现:
6.1,形状的生成:随机生成形状编号,并根据该编号生成相应的形状
   设形状编号为indexOfShape,组成形状的四个动态方块已存在DynamicBlockArray中,基础点为BasePoint,所有形状已存在Shapes中
void AssembleShape(int indexOfShape)
{
//重新安排四个动态块的位置以形成新形状
   DynamicBlocksArray[0].GLocation = new GirdPoint(
                  Shapes[indexOfShape].DCoordinates[0] + BasePoint.X,
               Shapes[indexOfShape].DCoordinates[1]+ BasePoint.Y);

   DynamicBlocksArray[1].GLocation = new GirdPoint(
    Shapes[indexOfShape].DCoordinates[2] + asePoint.X,
               Shapes[indexOfShape].DCoordinates[3]+ BasePoint.Y);

   DynamicBlocksArray[2].GLocation = new GirdPoint(
              Shapes[indexOfShape].DCoordinates[4] + BasePoint.X,
              Shapes[indexOfShape].DCoordinates[5] + BasePoint.Y);

   DynamicBlocksArray[3].GLocation = new GirdPoint(
       Shapes[indexOfShape].DCoordinates[6] + BasePoint.X,
       Shapes[indexOfShape].DCoordinates[7] + BasePoint.Y);
}

6.2,形状的移动,先试探能否向指定方向移动如果能,那么移动,否则不移动。试探,由于左右下三个方向移动的本质是一样的,所以它们可以由统一的函数来实现。移动,则是通过移动基础点位置,然后根据基础点位置重新安排一下四个动态方块的位置来实现,即,移动基础点,然后调用一次上面的AssembleShape函数。
     试探:设当前形状编号为indexOfShape,arg参数指定要试探的方向 ’L’,’R’,’D’ 分别为左,右,下;基础点为BascPoint,所有形状已存再Shapes中,CountOfTier为屏幕网格的列数。取得其试探位置的网格坐标,如果该位置已经超出屏幕或该位置已经被其他方块占据,则试探的位置不可达,如果组成形状的四个方块中有一个方块的试探位置不可达,那么试探函数返回false,说明不可向指定方向移动
private static bool canMove(int indexOfShape, char arg)
{
try
{
     GirdPoint tempBasePoint;

     switch(arg)
  {
   case 'L':
       tempBasePoint = new GirdPoint(BasePoint.X-1,BasePoint.Y);
      break;
   case 'R':
       tempBasePoint = new GirdPoint(BasePoint.X+1, BasePoint.Y);
      break;
   case 'D':
    tempBasePoint = new GirdPoint(BasePoint.X, BasePoint.Y+1);
    break;
           case 'E'://不移动,用于判断能否旋转,
                       //与判断移动不同的是传入的indexOfShape,
                       //判断旋转用的是旋转后的形状的索引,而移动用的是当前的
                    tempBasePoint = BasePoint;
                  break;
   default:
    MessageBox.Show("错误的参数"+arg+"\n应该为'L'或'R'或'D'");
    return false;
  }
   
  int gX0 = Shapes[indexOfShape].DCoordinates[0]+tempBasePoint.X;
  int gY0 = Shapes[indexOfShape].DCoordinates[1]+tempBasePoint.Y;
  int i =  GirdArray[gY0,gX0];
            
  int gX1 = Shapes[indexOfShape].DCoordinates[2]+tempBasePoint.X;
  int gY1 = Shapes[indexOfShape].DCoordinates[3]+tempBasePoint.Y;
  int j = GirdArray[gY1,gX1];

  int gX2 = Shapes[indexOfShape].DCoordinates[4]+tempBasePoint.X;
  int gY2 = Shapes[indexOfShape].DCoordinates[5]+tempBasePoint.Y;
  int m = GirdArray[gY2,gX2];

  int gX3 = Shapes[indexOfShape].DCoordinates[6]+tempBasePoint.X;
  int gY3 = Shapes[indexOfShape].DCoordinates[7]+tempBasePoint.Y;
  int n = GirdArray[gY3,gX3];

  //i,j,m,n为其即将到达的新位置的网格值,若为1,说明该网格已被占据  
  if(gX0<0 || gX0>= CountOfTier ||  i == 1 ||
       gX1<0 || gX1>= CountOfTier || j == 1 ||
   gX2<0 || gX2>=  CountOfTier || m == 1 ||
   gX3<0 || gX3>= CountOfTier || n == 1)
  {
   return false;
  }
  
    }
    catch
    {
     return false;
    }

    return true;
}
     移动:移动基础点,并重新组成形状,比如左移,
void ToLeft(int indexOfShape)
{
    if(canMove(indexOfShape,’L’)
    {
  BasePoint = new GirdPoint(BasePoint.X-1, BasePoint.Y);
  AssembleShape(indexOfShape);
    }
}

6.3,自动下落,设一个计时器timer,其每隔一定时间调用一次ToDown函数
void ToDown(int indexOfShape)
{
    if(canMove(indexOfShape,’D’)
    {
  BasePoint = new GirdPoint(BasePoint.X, BasePoint.Y+1);
  AssembleShape(indexOfShape);
    }
    else
    {
        //其他,比如固定下落的形状,判断得分,判断游戏是否已经结束等等
    }
}

6.4,下落形状的固定,当试探到不能下落时,就固定住当前形状。其实组成形状的四个块并没有被固定住,而是当它们当前位置对应的四个背景方块被点亮后,它们离开了当前位置而到屏幕的顶部(0~2行,不可见)组合新形状去了。对应的四个背景方块被点亮表现为形状被固定
void FixShape()
{
for(int i=0; i<4; i++)
{
        //将该位置的背景方块点亮(变为指定的颜色,假设为ColorOfFixedBlock)
       BackBlocks[DynamicBlocksArray.GLocation.Y,  
                 DynamicBlocksArray.GLocation.X].BackColor = ColorOfFixedBlock;
        //将对应的网格值设置为1,表示此网格已经被占据
       GirdArray[DynamicBlocksArray.GLocation.Y, DynamicBlocksArray.GLocation.X] = 1
     }
    //其他,比如判断是否应该消行加分等
    //将基础点移动到顶部,并产生新形状等等
}

6.5,旋转,与移动的原理一样。先试探能否旋转,如果能的话,取得旋转后的形状的索引,并按照此索引调用函数AssembleShape()。
    试探能否旋转:很巧妙地运用了试探能否移动的CanMove函数。与移动不同的是,试探能否旋转传入的是旋转后的形状的索引
bool CanEddy(indexOfShape)
    {
       int eddiedIndex = AllShapes.Shapes[indexOfShape].EddiedIndex;//旋转后的新形状的索引
       return canMove(eddiedIndex,’E’);
    }
   旋转:很巧妙地运用了移动时的AssembleShape()
   void Eddy(indexOfShape)
   {
       if(CanEddy(indexOfShape)
       {
          int eddiedIndex = AllShapes.Shapes[indexOfShape].EddiedIndex;//旋转后的新形状的索引
          AssembleShape(eddiedIndex);
       }
   }
这是激动人心的,程序巧妙地将左移、右移、下移、旋转高度地统一了起来,它们的实质都是调用CanMove()函数进行判断,然后再调用AssembelShape()函数移动四个动态方块的位置,而表现出各种效果。多么棒的设计啊(呵呵,自我欣赏了)

6.6,消行,当形状被固定后需要判断是否有满行,如果有,则消去已满的行
    扫描是否存在满行:没有必要扫描整个屏幕,而只需要扫描当前被固定的形状的基础点一下的四行(但不能超过第22行,因为第23行始终是满的)。对于某一行而言,如果该行的GirdArray对应值均为1,说明该行已被全部占据(或者说,如果该行的GirdArray对应值至少有一个为1,说明该行没有被全部占据),那么应该消去该行
    void ScanFullLines(int lineStart,int lineEnd)
    {
          int countOfFullLine = 0;//记录此次消除的行数,以便加分

       for(int i= lineStart; i<=lineEnd; i++)
       {
   bool isFull = true;//指示是否已被填满
   for(int j=0; j<CountOfTier; j++)//countOfTier:屏幕列数
   {
    if(GirdArray[i,j]==0)
    {
     isFull = false;
    }
   }

   if(isFull)
   {
    countOfFullLine++;
    DelLine(i);//消去第i行
          }
   
   switch(countOfFullLine)
              {
                 //根据所消行数加分
              }
       消去指定行:将该行上的背景方块熄灭(与点亮相反,变为另一种颜色,设为ColorOfBackBlock),并将该行以上的所有方块都下移一格。设背景方块数组为BackBlocks,网格数组为GirdArray
void DelLine(int indexOfLine)
{
//熄灭该行上的背景块,表现为移出了该行上的块
for(int i=0; i<CountOfTier; i++)
{
         //熄灭对应的背景方块
  BackBlocks[indexOfLine,i].BackColor = ColorOfBackBlock;
  //释放被占据的网格
  GirdArray[indexOfLine,i] = 0;
     }
//该行以上的所有行整体下落
for(int i=indexOfLine-1;i>=3; i--)
{
  for(int j=0; j<CountOfTier; j++)
  {
   if(GirdArray[i,j] == 1)
   {
    //此块熄灭,其正下方的块点亮,表现为下落了一格
    GirdArray[i,j] = 0;
    BackBlocks[i,j].BackColor =.ColorOfBackBlock;
    GirdArray[i+1,j] = 1;
    BackBlocks[i+1,j].BackColor = ColorOfFixedBlock;
   }
    }
    }  
}

6.7 游戏存档,当用户欲保存当前游戏以供以后继续时,使用这个功能。需要保存的数据有:目前的速度级别,目前的分数,目前的已屏幕上已被固定的各形状。存档文件仅需要一百多个字节就可以了,是这样做的:第一行写入游戏速度级别,第二行写入游戏分数,从第三行开始写入游戏中网格数组(GirdArray),网格数组中为1的对应的背景方块数组的方块即是被固定的(点亮的)。
bool SaveGame(string fileName)
{
try
{
          StreamWriter sWriter =     
              new StreamWriter(fileName,false,System.Text.Encoding.Default, 100);

  //写入速度级别
  sWriter.WriteLine(SpeedLevel.ToString());

  //写入分数
  sWriter.WriteLine(Score.ToString());

  //写入背景网格状态
  for(int i=0; i<CountOfRow; i++)
  {
   for(int j=0; j< CountOfTier; j++)
   {
    sWriter.Write(GirdArray[i,j].ToString());
   }
  }

  sWriter.Close();
}
catch(Exception ex)
{
  MessageBox.Show("未能保存!\n原因是:\n"+ex.Message);
  return false;
}

    return true;
}
比如我的一个游戏存档如下:
1
1500
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000010000000001100000
0001101000110110100111011111111101111111111111111111

6.8 加载游戏存档,将游戏存档读出,并将游戏中的各变量更改为存档中的值就可以了。
       bool LoadGame(string fileName)
       {
   try
   {
    StreamReader sReader =
     new StreamReader(fileName,System.Text.Encoding.Default,false,100);

     //加载游戏速度
    string strSpeedLevel = sReader.ReadLine();
    Level = int.Parse(strSpeedLevel));

    //加载游戏分数
    string strScore = sReader.ReadLine();
    Score = int.Parse(strScore);
   
    //加载游戏网格状态
    char[] buffer = new char[1];
    for(int i=0; i<CountOfRow; i++)
    {
     for(int j=0; j<CountOfTier; j++)
     {
      sReader.Read(buffer,0,1);
      GirdArray[i,j] = int.Parse(buffer[0].ToString());
                           //如果对应值为1,则点亮对应的背景网格
      if(GirdArray[i,j]==1)
      {
       BackBlocks[i,j].BackColor = ColorOfFixedBlock;
      }
      else
      {
       BackBlocks[i,j].BackColor = ColorOfBackBlock;
      }
     }
    }

    sReader.Close();
   }
   catch(Exception ex)
   {
    MessageBox.Show("加载游戏失败\n原因是:\n"+ex.Message);
    return false;
   }

   return true;
  }
乐乎设计,乐乎生活~

TOP

发新话题