大概在最近两天之内编码完成,但此前一天开始构思。第一天晚上主要完成了方块旋转算法,第二天也就是今天加了消方块的处理算法。但是可能还有一些考虑不周的地方,比如,没有采用定时中断,而是图方便采用了和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[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! */ /* 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); } 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); 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! */ 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++; } |
#p#副标题#e#
/* 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(); } |