其中的主要逻辑有:(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的大程序深感羡慕(自己只是写几十行的程序)。
数年后我仍然看到有不同样式的实现,但是我一直没有实现它,知道今天忽然有这个想法去做,算是弥补多年前的遗憾和心愿吧。
---------------------------------------------Q&A:----------------------------------------------Q:我编译时出现错误:fatal error C1083: Cannot open include file: 'bios.h': Nosuch file or directory,该如何解决?A:正文中的代码,是使用Borland公司的TC2.0编译的,编译结果运行在Windows的16位虚拟机上。
bi os.h是属于TC的头文件,在VC或者其他编译器中可能没有这个头文件。
因此会产生这个错误。
解决办法:(1)可以下载附件中的压缩包,是使用VC6.0进行编译的版本,编译结果是Windows程序,运行在wind ows 32系统上。
两者之间的算法完全一致,区别仅仅是绘图时采用的接口不同,TC下采用BGI(进入图形模式),VC下采用的是GDI(使用DC进行绘图)。
(2)下载TC2.0进行编译。
Q:使用TC3.0进行编译时,提示未能初始化图形模式,请使用initgraph函数。
A:这是因为TC3.0的BGI文件的路径和TC2.0有所不同。
TC2.0的BGI文件位于TC路径下。
而TC3.0的BGI文件位于BGI文件夹中。
请把initgame函数中的initgraph函数中的第三个参数设置为正确的路径。
例如:initgraph(&..,&..,"C:\\TC\\BGI");Q:编译后运行时,弹出错误对话框报告16位虚拟机遇到不可执行的指令,点击确定后退出。
A:该问题在某些环境中出现,可能是基于软件或者硬件环境造成,具体原因暂时未知。
为避免该问题,请加载附件中的压缩包VC6.0版本。
-----------------------------------------------------------------TC2.0的代码如下(VC6.0版本代码见附件):程序代码:/************************************* Desc: 俄罗斯方块游戏* By: hoodlum1980* Email: jinfd@* 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 _INNER_HELPER GetKeyCode(){int key=0;if(bioskey(1)){key=bioskey(0);}return key;}/* display text! */void _INNER_HELPER 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->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][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(block->size==2)return true;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,"");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*NBCellSiz e);line(NBBoardLeft, NBBoardTop+i*NBCellSize, NBBoardLeft+4*NBCellSize, NBBoardTop+ i*NBCellS ize);}/* draw score rect */rectangle(ScoreBoardLeft,ScoreBoardTop,ScoreBoardLeft+ScoreBoardWidth,ScoreBoardTop+ScoreBoardHei ght);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 */}elsej--;} 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();}。