大概在最近兩(liang) 天之內(nei) 編碼完成,但此前一天開始構思。第一天晚上主要完成了方塊旋轉算法,第二天也就是今天加了消方塊的處理算法。但是可能還有一些考慮不周的地方,比如,沒有采用定時中斷,而是圖方便采用了和cpu頻率有關(guan) 的delay()函數來模擬時間間隔,這是需要改進的地方。
其中的主要邏輯有:
(1)由於(yu) c的隨機性函數不好,所以每次遊戲開始根據bioses時間設置種子。
(2)得分越高,方塊下降速度越快(每200分為(wei) 單位)。
(3)每下落一個(ge) 方塊加1分,每消除一行加10分,兩(liang) 行加30分,三行加70分,四行加150分。初試分數為(wei) 100分。
遊戲控製:
up-旋轉;空格-下落到底; 左右下方向鍵-控製方向。P-開始或暫停遊戲。 ESC-退出。
特點:
(1)由於(yu) tc不支持中文,所以基本都是英文注釋。
(2)函數命名盡可能規範的表達其內(nei) 部處理目的和過程。
(3)代碼加上注釋僅(jin) 有577行。(我下載過的兩(liang) 個(ge) 俄羅斯方塊代碼一個(ge) 在1087行,一個(ge) 在993行,我的比它們(men) 代碼少)。
(4)除了消除空格時算法比較複雜,其他算法都比較簡單易讀。
(5)繪圖效率和局部代碼效率扔有待提高。
(6)FrameTime參數可能依據不同硬件環境進行具體(ti) 設置,InitGame需要正確的TC路徑。
俄羅斯方塊源於(yu) 大約9年前上大一時的一個(ge) 夢,我們(men) 在學習(xi) c語言時,我的同寢室友邀請我合作一起完成俄羅斯方塊(課外作業(ye) 性質),但是當時限於(yu) 我們(men) 的水平比較菜和學習(xi) 狀態比較懶散,我們(men) 沒有完成。大一的時候我在機房裏無意發現別人留下的俄羅斯方塊程序,運行,老師發現後激動的問我是我寫(xie) 的嗎,我慚愧的搖搖頭。那時看到別人做c的大程序深感羨慕(自己隻是寫(xie) 幾十行的程序)。數年後我仍然看到有不同樣式的實現,但是我一直沒有實現它,知道今天忽然有這個(ge) 想法去做,算是彌補多年前的遺憾和心願吧。
//-----------------------【以下是我的代碼文件:】--------------------- /********************************/ /* Desc: 俄羅斯方塊遊戲 */ /* By: hoodlum1980 */ /* Email: jinfd@126.com */ /* Date: 2008.03.12 22:30 */ /********************************/ #include#include #include #include #include #include #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(bioseskey(1)) { key=bioseskey(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 (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 (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 size;i++) { for(j=0;j 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 size;i++) { for(j=0;j 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 =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 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 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(); } //----------------------------------【代碼文件結尾】--------------------------------------