俄罗斯方块游戏
我们以控制台程序的字符界面来实现这个游戏,规定游戏区域为10×20个单元格,宽为10,高为20,在屏幕上用20行×10列个字符(如*和空格)来代表这些单元格,每次将整个游戏区域输出,然后等待用户输入一个命令,按命令的要求进行方块的移动、旋转、清行等动作,并把新的游戏区域重新输出(输出之前清除屏幕),游戏结束后给出提示,用户可重新开始或退出程序。要求用OO技术来进行游戏的设计:
1、 方块类:每个方块都要记录自己的ID、形状。形状采用4对整数坐标来表示,分别记录其4个小方块的相对位置
方块类主要操作:
构造函数:参数至少有:ID和参考点初始坐标。
填充:把自己填充到指定的游戏区域中。返回填充成功或失败的信息。
清除:把自己从指定的游戏区域中清除。
移动:在指定的游戏区域中移动,包括向左、右、下移动,以1个单元格为单位。返回是否移动成功的信息。
旋转:在指定的游戏区域中旋转一次,然后在指定的游戏区域中重新填充自己
获得占用行:返回本方块4个小方块所占用的各行
2、 游戏区域类
其主要属性:游戏区域数据,状态:游戏未开始(初态)和游戏已开始。
主要操作为:
游戏区域读写:指定游戏区域的一个单元格坐标后,返回或设置单元格的值。
绘制:清除屏幕后将游戏区域绘制出来。
命令处理:命令为一个字符串,格式自定。要求实现如下命令:
游戏开始:当接收到此命令时,将状态切换为游戏已开始。
方块左移、右移、旋转:对活动方块调用相应的操作,不论是否能移动成功都重新绘制本游戏区域,如果移动失败绘制完毕后给出文字提示信息。为增加游戏难度,可以在执行几次左右移或旋转后自动执行“方块下移”命令。
方块下移:对活动方块调用相应的操作,如果移动成功,则重新绘制本游戏区域后操作结束,如果移动失败,则说明方块已经掉到最下面,需要执行方块落地操作。
方块落地:
先执行清行操作:获得活动方块占用了哪些行的信息,然后逐行进行扫描:可以每清除一行就重新绘制一次游戏区域,获得动画效果。
执行完清行操作后,要产生下一个方块,此时要把活动方块指定为新产生的方块,而且还要检查新产生的方块是否能成功填充,如果填充失败,说明游戏结束,此时将游戏区域状态恢复为游戏未开始,抛出异常通知游戏结束(类型自定),但在本类内不处理该异常,让类的使用者处理。
3、 程序总体流程
程序运行后,生成一个游戏区域类对象,然后不停地接收用户输入的命令,并进行处理,格式自定。
游戏开始命令。此命令被转发给游戏区域处理。
退出命令。用户可直接退出程序。
方块左移、右移、旋转、下移命令:转发给游戏区域处理。注意,由于此时游戏区域对象的函数可能会抛出异常表示游戏结束,所以需要捕获这些异常。捕获到异常时说明游戏结束,此时提问用户是退出还是继续游戏,用户要求退出则退出程序,如果要求继续游戏则执行一次“ 游戏开始命令”即可。
4、 如果有兴趣可加上分数的计算功能。
请问哪位大哥可以帮着设计一下?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <assert.h>
#include <limits.h>
#ifdef _DEBUG
#define verify(f) assert(f)
#else
#define verify(f) ((void)(f))
#endif
class Console // 控制台
{
friend class Window;
public:
// title: 控制台标题
// point: 控制台宽度和高度
void Open( const char* title, COORD wl )
{
assert( wl.X>0 && wl.Y>0 );
verify( (hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE)) != INVALID_HANDLE_VALUE ); // 获得输出句柄
verify( SetConsoleTitle( title ) ); // 设置标题
CONSOLE_CURSOR_INFO cci = { 25, FALSE };
verify( SetConsoleCursorInfo( hStdOutput, &cci ) ); // 去处光标
SMALL_RECT wrt = { 0, 0, wl.X-1, wl.Y-1 };
verify( SetConsoleWindowInfo( hStdOutput, TRUE, &wrt ) ); // 设置窗体尺寸
coord = wl;
verify( SetConsoleScreenBufferSize( hStdOutput, coord ) ); // 设置缓冲尺寸
}
private:
HANDLE hStdOutput;
COORD coord;
};
class Window // 窗体是控制台的一个子部分
{
public:
// wrect: 窗体左上角坐标,右下角坐标
void Open( Console& console, SMALL_RECT wrect )
{
assert( wrect.Left < wrect.Right && wrect.Top < wrect.Bottom );
assert( wrect.Left>=0 && wrect.Right<=console.coord.X );
assert( wrect.Top>=0 && wrect.Bottom<=console.coord.Y );
pcon = &console;
wrt = wrect;
}
// x,y: 相对于窗体的坐标
// color: 字体和背景颜色
// str: 欲输出字符串,遇0或达到len数量则结束,没考虑控制字符,比如\r等
void Output( short x, short y, WORD color, const char* str, size_t len=INT_MAX )
{
assert( x>=0 && x<=wrt.Right-wrt.Left && y>=0 && y<=wrt.Bottom-wrt.Top );
COORD c = { wrt.Left+x, wrt.Top+y };
DWORD n = 0;
WORD cs[2] = { color, color };
for( const char* p=str; len!=0 && *p!=0; --len,++p,++c.X )
{
if( c.X >= wrt.Right ) // 换行
{
c.X = wrt.Left+x;
++c.Y;
assert( c.Y < wrt.Bottom );
}
if( *p > 0 ) // 单字节字符
{
verify( WriteConsoleOutputCharacter( pcon->hStdOutput, p, 1, c, &n ) && n==1 );
verify( WriteConsoleOutputAttribute( pcon->hStdOutput, cs, 1, c, &n ) && n==1 );
}
else // 双字节字符
{
assert( len>=2 && *(p+1)!=0 && (c.X+1)<wrt.Right );
verify( WriteConsoleOutputCharacter( pcon->hStdOutput, p, 2, c, &n ) && n==2 );
verify( WriteConsoleOutputAttribute( pcon->hStdOutput, cs, 2, c, &n ) && n==2 );
--len,++p,++c.X;
}
}
}
private:
Console* pcon;
SMALL_RECT wrt;
}; class RussiaDiamonds
{
public:
// x,y: 左上角坐标,右下角坐标
RussiaDiamonds( Console& console, short x, short y )
{
SMALL_RECT wrect = { x, y, x+38, y+21 };
win.Open( console, wrect );
}
// 分别是 开始,暂停,声效,变形,左移,右移,下移,落地 键的键码, 声效频率, 延续时间
// Example:
// A 键为 0x41, 用0x00000041 表示
// F1 键为 0x00,0x3B, 用0x00003800 表示
// F11 键为 0xE0,0x85, 用0x000085E0 表示
void Open( int ctrlkeys[8], char ctrlkeydecs[8][5], DWORD frequency, DWORD duration )
{
memcpy( keys, ctrlkeys, sizeof(keys) );
memcpy( decs, ctrlkeydecs, sizeof(decs) );
freq = frequency, dura =duration;
gameover = false;
pause = true;
voice = true;
score = 0, level = 0;
memset( data, 0, sizeof(data) );
srand( time(0) );
next = rand()%7;
x=4, y=-2, c=-1, z=0;
win.Output( 0, 0, COLOR_B, bg+0, 38 );
for( int i=1; i<20; ++i )
{
win.Output( 0, i, COLOR_B, bg+38*i+0, 2 );
win.Output( 2, i, COLOR_C, bg+38*i+2, 22 );
win.Output( 24, i, COLOR_B, bg+38*i+24, 14 );
}
win.Output( 0, 20, COLOR_B, bg+38*20, 38 );
for( int j=0; j<8; ++j )
win.Output( 33, j+7, COLOR_B, decs[j], 4 );
DrawNext();
}
bool IsRun() const
{
return !gameover && !pause;
}
int Level() const
{
return level;
}
bool Down( void )
{
return MessageDeal( keys[6] );
} // 游戏结束返回 false
bool MessageDeal( const int k )
{
if( gameover )
{
if( k == keys[0] ) // 重新开始
{
Open( keys, decs,freq, dura );
return true;
}
return false;
}
if( pause ) // 暂停
{
if( k == keys[0] ) // 开始
{
pause = false;
if( c == -1 )
{
c = next;
next = rand()%7;
DrawNext();
}
}
else if( k == keys[2] ) // 声效
voice = !voice;
else
return true;
VoiceBeep();
return true;
}
if( k == keys[1] ) // 暂停
pause = true;
else if( k == keys[3] ) // 变形
MoveTrans();
else if( k == keys[4] ) // 左移
MoveLeft();
else if( k == keys[5] ) // 右移
MoveRight();
else if( k == keys[6] ) // 下移
{ if( 0 == MoveDown() ) return false; }
else if( k == keys[7] ) // 落地
{ if( !MoveDownDown() ) return false; }
else if( k == keys[2] ) // 声效
voice = !voice;
else
return true;
VoiceBeep();
return true;
}
private:
void VoiceBeep( void ) // 声效
{
if( voice ) Beep( freq, dura );
}
void DrawScoreLevel( void ) // 绘制得分
{
char tmp[6];
sprintf( tmp, "%05d", score );
win.Output( 31, 19, COLOR_B, tmp, 5 );
sprintf( tmp, "%1d", level );
win.Output( 28, 19, COLOR_B, tmp, 1 );
}
void DrawNext( void ) // 绘制 "next框" 中的图形
{
for( int i=0; i<2; ++i )
for( int j=0; j<4; ++j )
win.Output( 28+j*2, 1+i, COLOR_B, bk[next][0][i][j]==0?" ":"■", 2 );
}
void DrawOver( void ) // 游戏结束
{
win.Output( 28, 1, COLOR_B, "GAME" );
win.Output( 28, 2, COLOR_B, "OVER" );
}
void Draw( WORD color )
{
for( int i=0; i<4; ++i )
{
if( y+i<0 || y+i>= 19 ) continue;
for( int j=0; j<4; ++j )
if( bk[c][z][i][j] == 1 )
win.Output( 2+x*2+j*2, 1+y+i, color, "■", 2 );
}
} bool IsFit( int x, int y, int c, int z ) // 给定的x,y,c,z是否可行
{
for( int i=0; i<4; ++i )
{
for( int j=0; j<4; ++j )
{
if( bk[c][z][i][j]==1 )
{
if( y+i < 0 ) continue;
if( y+i>=19 || x+j<0 || x+j>=11 || data[y+i][x+j]==1 ) return false;
}
}
}
return true;
}
void RemoveRow( void ) // 消行
{
const char FULLLINE[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
int linecount = 0;
for( int i=0; i<19; ++i )
{
if( 0 == memcmp( data[i], FULLLINE, 11 ) )
{
++linecount;
for( int m=0; m<11; ++m )
{
for( int n=i; n>1; --n )
{
data[n][m] = data[n-1][m];
win.Output( 2+m*2, 1+n, data[n][m]==1?COLOR_B:COLOR_C, "■", 2 );
}
data[0][m] = 0;
win.Output( 2+m*2, 1, COLOR_C, "■", 2 );
}
}
}
char data[19][11] = { 0 };
if( linecount == 0 ) return;
int _score = 0;
switch( linecount )
{
case 1: _score = 100; break;
case 2: _score = 300; break;
case 3: _score = 700; break;
case 4: _score = 1500; break;
}
score += _score;
if( score > 99999 ) score = 99999;
level = score/10000;
DrawScoreLevel();
} void MoveTrans( void ) // 逆时针翻转
{
if( IsFit( x, y, c, (z+1)%4 ) )
{
VoiceBeep();
Draw( COLOR_C );
z=(z+1)%4;
Draw( COLOR_A );
}
}
void MoveLeft( void ) // 向左移
{
if( IsFit( x-1, y, c, z ) )
{
VoiceBeep();
Draw( COLOR_C );
--x;
Draw( COLOR_A );
}
}
void MoveRight( void ) // 向右移
{
if( IsFit( x+1, y, c, z ) )
{
VoiceBeep();
Draw( COLOR_C );
++x;
Draw( COLOR_A );
}
}
int MoveDown( void ) // 向下移, 游戏结束返回0, 触底返回-1,否则返回1
{
if( IsFit( x, y+1, c, z ) )
{
VoiceBeep();
Draw( COLOR_C );
++y;
Draw( COLOR_A );
return 1;
}
if( y != -2 ) // 触底
{
Draw( COLOR_B );
for( int i=0; i<4; ++i )
{
if( y+i<0 ) continue;
for( int j=0; j<4; ++j )
if( bk[c][z][i][j] == 1 )
data[y+i][x+j] = 1;
}
RemoveRow();
x=4, y=-2, c=next, z=0;
next = rand()%7;
DrawNext();
return -1;
}
// 游戏结束
gameover = true;
DrawOver();
return 0;
}
bool MoveDownDown( void ) // 下落到底
{
int r;
for( ; r=MoveDown(),r==1; );
return r == -1;
}
private:
static const char bg[21*38+1];
static const char bk[7][4][4][4];
static const WORD COLOR_A; // 运动中的颜色
static const WORD COLOR_B; // 固定不动的颜色
static const WORD COLOR_C; // 空白处的颜色
private:
DWORD freq, dura; // 声效频率,延续时间
int keys[8]; // 8个控制键
char decs[8][5]; // 8个控制键的描述, 长度为4个字节, 不含\0
bool gameover;
bool pause; // 暂停
bool voice; // 声效开关
int score, level; // 得分 和 速度
char data[19][11];
int next;
int x, y, c, z; // x坐标,y坐标,当前方块,方向
Window win;
}; const char RussiaDiamonds::bg[21*38+1] =
"┏━━━━━━━━━━━┓┏━━━━┓"
"┃■■■■■■■■■■■┃┃ ┃"
"┃■■■■■■■■■■■┃┃ ┃"
"┃■■■■■■■■■■■┃┗━━━━┛"
"┃■■■■■■■■■■■┃ "
"┃■■■■■■■■■■■┃ 退出= ESC "
"┃■■■■■■■■■■■┃ "
"┃■■■■■■■■■■■┃ 开始= "
"┃■■■■■■■■■■■┃ 暂停= "
"┃■■■■■■■■■■■┃ 声效= "
"┃■■■■■■■■■■■┃ 变形= "
"┃■■■■■■■■■■■┃ 左移= "
"┃■■■■■■■■■■■┃ 右移= "
"┃■■■■■■■■■■■┃ 下移= "
"┃■■■■■■■■■■■┃ 落地= "
"┃■■■■■■■■■■■┃ "
"┃■■■■■■■■■■■┃ "
"┃■■■■■■■■■■■┃ 速度 得分 "
"┃■■■■■■■■■■■┃┏━━━━┓"
"┃■■■■■■■■■■■┃┃0 00000┃"
"┗━━━━━━━━━━━┛┗━━━━┛";
const char RussiaDiamonds::bk[7][4][4][4] =
{
{
{ { 0,1,1,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,0,0,0 },{ 1,1,0,0 },{ 0,1,0,0 },{ 0,0,0,0 } },
{ { 0,1,1,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,0,0,0 },{ 1,1,0,0 },{ 0,1,0,0 },{ 0,0,0,0 } }
}
,
{
{ { 1,1,0,0 },{ 0,1,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 0,1,0,0 },{ 1,1,0,0 },{ 1,0,0,0 },{ 0,0,0,0 } },
{ { 1,1,0,0 },{ 0,1,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 0,1,0,0 },{ 1,1,0,0 },{ 1,0,0,0 },{ 0,0,0,0 } }
}
,
{
{ { 1,1,1,0 },{ 1,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,0,0,0 },{ 1,0,0,0 },{ 1,1,0,0 },{ 0,0,0,0 } },
{ { 0,0,1,0 },{ 1,1,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,1,0,0 },{ 0,1,0,0 },{ 0,1,0,0 },{ 0,0,0,0 } }
}
,
{
{ { 1,1,1,0 },{ 0,0,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,1,0,0 },{ 1,0,0,0 },{ 1,0,0,0 },{ 0,0,0,0 } },
{ { 1,0,0,0 },{ 1,1,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 0,1,0,0 },{ 0,1,0,0 },{ 1,1,0,0 },{ 0,0,0,0 } }
}
,
{
{ { 1,1,0,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,1,0,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,1,0,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,1,0,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } }
}
,
{
{ { 0,1,0,0 },{ 1,1,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 0,1,0,0 },{ 1,1,0,0 },{ 0,1,0,0 },{ 0,0,0,0 } },
{ { 1,1,1,0 },{ 0,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,0,0,0 },{ 1,1,0,0 },{ 1,0,0,0 },{ 0,0,0,0 } }
}
,
{
{ { 1,1,1,1 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,0,0,0 },{ 1,0,0,0 },{ 1,0,0,0 },{ 1,0,0,0 } },
{ { 1,1,1,1 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },
{ { 1,0,0,0 },{ 1,0,0,0 },{ 1,0,0,0 },{ 1,0,0,0 } }
}
};
const WORD RussiaDiamonds::COLOR_A = FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_INTENSITY;
const WORD RussiaDiamonds::COLOR_B = FOREGROUND_GREEN;
const WORD RussiaDiamonds::COLOR_C = FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE;
void MessageDeal( RussiaDiamonds rds[], int num )
{
int* counts = new int[num];
memset( counts, 0, num*sizeof(int) );
for( ; ; )
{
if( _kbhit() )
{
int k = _getch();
if( k == 0x1B ) break; // 遇到 Esc 键则退出
if( k==0 || k==0xE0 ) // 控制字符
k = _getch()<<8 | k;
else if( k>='a' && k<='z' ) // 小写字母转化为大写
k = k - ( 'a'-'A' );
for( int n=0; n<num; ++n )
rds[n].MessageDeal( k );
}
for( int n=0; n<num; ++n )
{
if( rds[n].IsRun() && 10-rds[n].Level()==++counts[n] )
{
counts[n] = 0;
rds[n].Down();
}
}
Sleep( 55 );
}
delete[] counts;
} int main()
{
//* 一个人玩
Console csl;
COORD crect = { 38, 21 };
csl.Open( "罗刹国方块 beta", crect );
int keys[8] = { 0x00000042, 0x00000053, 0x00000056, 0x000048E0, 0x00004BE0, 0x00004DE0, 0x000050E0, 0x00000020 };
char decs[8][5] = { "B","S","V","↑","←","→","↓","空格" };
RussiaDiamonds rd(csl,0,0);
rd.Open( keys, decs, 1760, 10 );
MessageDeal( &rd, 1 );
//*/
/* 二个人玩(仅用于测试多窗体,多窗体是为了将来联网作战准备的)
// 二个人玩时有一个问题:如果两个键同时按着不放,则只有后一个起作用
Console csl;
COORD crect = { 38 + 38, 21 };
csl.Open( "罗刹国方块 alpha", crect );
int keys[2][8] = { {0x00003B00,0x00003C00,0x00003D00,0x00000057,0x00000041,0x00000044,0x00000053,0x00000058}
,{0x00000037,0x00000038,0x00000039,0x000048E0,0x00004BE0,0x00004DE0,0x000050E0,0x00000030} };
char decs[2][8][5] = { { "F1", "F2", "F3", "W", "A", "D", "S", "X" }
,{ "7", "8", "9", "↑", "←", "→", "↓", "0" } };
RussiaDiamonds rds[2] = { RussiaDiamonds(csl,0,0), RussiaDiamonds(csl,38,0) };
rds[0].Open( keys[0], decs[0], 1760, 10 );
rds[1].Open( keys[1], decs[1], 2760, 10 );
MessageDeal( rds, 2 );
//*/
return 0;
} 以上为一个俄罗斯方块代码,有哪位能对上述代码作出如下修改:
1、运行程序时,控制台窗口按照默认大小显示,游戏区域大小不变。
2、游戏中的方块区域改为20*10,并且取消声音模块。
3、方块的移动、旋转改为手动执行,即输入命令进行相应的操作,命令格式自定。
4、能不能不用图形界面,而用“*”或者“■”来直接在控制台上显示,不用一些句柄。
页:
[1]
