怎样用C++在控制台中编写俄罗斯方块

我都没有在CSDN上写过东西,也不知道写什么好,下面就来讲诉一下我编的一个游戏吧。这是一个每个人小时候都玩过的游戏,也就是俄罗斯方块。我个人觉得俄罗斯方块这个游戏用MFC来编比较简单,而且我也编了一个MFC版的俄罗斯方块,但在此我不想讲诉怎样用MFC来编写俄罗斯方块,因为那太简单了,而且很多人也做过同样的事情,我想介绍的是怎样在控制台中编写这个游戏,而且等我讲诉完后,你会发现原来俄罗斯方块也可以这样编,同时你来可以学习到不少底层的API函数,这对初学C++的人来说是一个很好的提高。我一再试图把程序写得短一些,但还是有496行,需要你的一点点耐心,以下我先给出源代码:
#include <windows.h>
#include <conio.h>
#include <string>
#include <time.h>
using namespace std;

//-----------------------

// 重要数据定义
typedef struct m_point { //控制台中的坐标点定义, 其中0<=x<=24, 0<=y<79
 int x;
 int y;
}m_point;
typedef struct block { //方块定义, 一个方块有四个点, 例如: ■■■■                                
 m_point opt;
 m_point pt2;
 m_point pt3;
 m_point pt4;
}block;
block bloary[6][4]; //方块数组,一共有6种,每种有4个方向
bool scrop[15][10]; //游戏空间定义,初始值全标记为0,向屏幕打印蓝色方块,
                    //如果标记为1,则向屏幕找印深蓝色方块
// 数据定义完毕

//---------------------------
// 功能函数声明
void begin();             //开始函数
void color(int n_color);  //设置字符颜色
int getrand(int max);     //获得不大于max的随机数
void go(int x, int y);    //使光移动到指定位置
void show_unit();         //输出 ■ 字符
char getudlr();           //接受键盘上的上下左右及回车键并返回相应字符 u d l r k
void show_block(block b); //输出方块
void init();              //初始化block bloary[6][4]数组
void show_scrop();        //根据scrop[15][10]的值打印相应颜色的方块
bool isexist(block b);    //判断当前方块中的四个坐标是否在游戏空间中被标记为 1
void markspace(block b);  //当方块停留时,把方块四个点的坐标在游戏空间数组scrop[][]中的相应位置标记为1
void move_block(block &b, char ch, int &lark);         //移动方块
void change_block(block &b, int i, int &j, int lark);  //改变当前方块的方向,注:一共有四个方向
void check(int &score);   //消行函数
void wait(float secs);    //让程序等待secs秒
void othergetch();        //起暂停功能
void out(char* chars, int n);    //相当于C++中的cout功能
void initb(int optx, int opty,   //容易看出这个函数中传递了8个int值,分别用于这四点的横坐标和纵坐标,
                                 //另外bloc是一个block结构体,block结构体中含有8个int值来表示方块的4个点
     int pt2x, int pt2y,  
     int pt3x, int pt3y,
     int pt4x, int pt4y,
     block &bloc);
// 功能函数声明完毕

int main()
{
 begin();
 return 0;
}

// 以下是功能函数定义部分
void begin()
{
 system("color ec");
 system("title 俄罗斯方块");
 color(0xec);
 go(33,2);
 out("制作人:ckh2007",15);
 go(33,3);
 out("游戏规则:",10);
 go(33,4);
 out("1.按左右下键进行移动",20);
 go(33,5);
 out("2.按向上键改变形状",18);
 go(33,6);
 out("3.按回车重新开始",12);

 init();
lapp:
 for(int i=0; i<15; i++)
  for(int j=0; j<10; j++)
   scrop[i][j]=0;
 int score = 0;
 while(1)
 {
  block b;
  int lark=1;        //lark变量很重要,用于标记俄罗斯方块是否停下
  int i=getrand(6);
  int j=getrand(4);
  
  go(20,21);
  b = bloary[i][j];
  show_scrop();
  show_block(b);
  if(isexist(b))
  {
   system("cls");
   go(37,12);
   out("失败,结束!",10);
   othergetch();
   exit(-1);
  }
  while(lark!=0)
  {
   show_scrop();
   show_block(b);
   char ch=getudlr();
   go(18,21);
   
   if(ch!='u')
   {
    move_block(b, ch, lark);
   }
   if(ch=='u')
   {
    change_block(b, i, j, lark);
   }
   if(ch=='k')
   {
    goto lapp;
   }
   if(lark==0)
   {
    markspace(b);
   }
  }
  check(score);
 }
}

// 函数作用:初始化bloary[6][4]数组,这个数组表示16个方块的结构体,具体是,
//           一共有6种方块,每种方块又有4种不同的方向(也就是种不同的形状)
//           其中initb函数将在下面定义
void init(){
 
 /**/ initb(2, 1, 0, 0, 2, 0, 0, 1, bloary[0][0]);   /
 /**/ initb(2, 1, 0, 0, 2, 0, 0, 1, bloary[0][1]);   /
 /**/ initb(2, 1, 0, 0, 2, 0, 0, 1, bloary[0][2]);   /
 /**/ initb(2, 1, 0, 0, 2, 0, 0, 1, bloary[0][3]);   /
 /**/ initb(2, 1, 0, 1, 4, 1, 6, 1, bloary[1][0]);   /
 /**/ initb(2, 1, 2, 0, 2, 2, 2, 3, bloary[1][1]);   /
 /**/ initb(2, 1, 0, 1, 4, 1, 6, 1, bloary[1][2]);   /
 /**/ initb(2, 1, 2, 0, 2, 2, 2, 3, bloary[1][3]);   /
 /**/ initb(2, 1, 0, 0, 0, 1, 4, 1, bloary[2][0]);   /
 /**/ initb(2, 1, 2, 0, 4, 0, 2, 2, bloary[2][1]);   /
 /**/ initb(2, 1, 0, 1, 4, 1, 4, 2, bloary[2][2]);   /
 /**/ initb(2, 1, 2, 0, 0, 2, 2, 2, bloary[2][3]);   /
 /**/ initb(2, 1, 4, 0, 0, 1, 4, 1, bloary[3][0]);   /
 /**/ initb(2, 1, 2, 0, 2, 2, 4, 2, bloary[3][1]);   /
 /**/ initb(2, 1, 0, 1, 4, 1, 0, 2, bloary[3][2]);   /
 /**/ initb(2, 1, 0, 0, 2, 0, 2, 2, bloary[3][3]);   /
 /**/ initb(2, 1, 2, 0, 0, 1, 4, 1, bloary[4][0]);   /
 /**/ initb(2, 1, 2, 0, 4, 1, 2, 2, bloary[4][1]);   /
 /**/ initb(2, 1, 0, 1, 4, 1, 2, 2, bloary[4][2]);   /
 /**/ initb(2, 1, 2, 0, 0, 1, 2, 2, bloary[4][3]);   /
 /**/ initb(2, 1, 2, 0, 4, 0, 0, 1, bloary[5][0]);   /
 /**/ initb(2, 1, 2, 0, 4, 1, 4, 2, bloary[5][1]);   /
 /**/ initb(2, 1, 2, 0, 4, 0, 0, 1, bloary[5][2]);   /
 /**/ initb(2, 1, 2, 0, 4, 1, 4, 2, bloary[5][3]);   /
 
}
// 函数作用:初始化方块,一个方块具有四个坐标,如 ■■■■
//           容易看出这个函数中传递了8个int值,分别用于这四点的横坐标和纵坐标,
//           另外bloc是一个block结构体,block结构体中含有8个int值来表示方块的4个点
void initb(int optx, int opty,
     int pt2x, int pt2y,
     int pt3x, int pt3y,
     int pt4x, int pt4y,
     block &bloc)
{
 bloc.opt.x = optx;
 bloc.opt.y = opty;
 bloc.pt2.x = pt2x;
 bloc.pt2.y = pt2y;
 bloc.pt3.x = pt3x;
 bloc.pt3.y = pt3y;
 bloc.pt4.x = pt4x;
 bloc.pt4.y = pt4y;
}
// 函数作用:这个函数的作用是在控制台下打印游戏空间,即数组scrop[15][10],
//           如果scrop[i][j]是0,则用0xab色打印■字符
//           如果scrop[i][j]是1,则用0xb3色打印■字符
//           打印效果为10*15的矩形方块,我把它称为游戏空间
void show_scrop(){
 go(0,0);
 for (int i=0; i<15; i++)
 {
  for (int j=0; j<10; j++)
  {
   if (0==scrop[i][j])
   {
    color(0xab);
    show_unit();
   }
   else
   {
    color(0xb3);
    show_unit();
   }
  }
  go(0,i+1);
 }
 go(0,0);
 color(0xec);
}
//函数作用:判断当前方块中的四个坐标是否在游戏空间中被标记为 1
bool isexist(block b)
{
 if (scrop[b.opt.y][b.opt.x/2]==1||
  scrop[b.pt2.y][b.pt2.x/2]==1||
  scrop[b.pt3.y][b.pt3.x/2]==1||
  scrop[b.pt4.y][b.pt4.x/2]==1)
  return true;
 else
  return false;
}
//函数作用:移动方块
void move_block(block &b, char ch, int &lark){
 block copyb = b;
 switch(ch)
 {
 case 'l': //左移
  b.opt.x=b.opt.x-2;
  b.pt2.x=b.pt2.x-2;
  b.pt3.x=b.pt3.x-2;
  b.pt4.x=b.pt4.x-2;
  break;
 case 'r': //右移
  b.opt.x=b.opt.x+2;
  b.pt2.x=b.pt2.x+2;
  b.pt3.x=b.pt3.x+2;
  b.pt4.x=b.pt4.x+2;
  break;
 case 'd': //下移
  b.opt.y=b.opt.y+1;
  b.pt2.y=b.pt2.y+1;
  b.pt3.y=b.pt3.y+1;
  b.pt4.y=b.pt4.y+1;
  break;
 }
 
 if(b.opt.x<0||b.opt.x>18||
  b.pt2.x<0||b.pt2.x>18||
  b.pt3.x<0||b.pt3.x>18||
  b.pt4.x<0||b.pt4.x>18
  ) //过左右界还原
 {
        b = copyb;
 }
 if(b.opt.y>14||
  b.pt2.y>14||
  b.pt3.y>14||
  b.pt4.y>14||
  isexist(b)
  ) //到底还原或重叠还原
 {
  b = copyb;
  lark = 0; //lark = 0 表示方块停下
 }
 //消除按左右键导致重叠时发生停留现象
 if(ch=='l'||ch=='r')
 {
        lark = 1;
    }
 //消除按左右键导致重叠时发生停留现象...
}
//函数作用:改变当前方块的方向,注:一共有四个方向
void change_block(block &b, int i, int &j, int lark){
 block copyb = b;
 int orx = b.opt.x;
 int ory = b.opt.y;
 j = j+1;
 j = j%4;
 b = bloary[i][j];
 
 if(b.opt.x<orx)
 {
  int i = orx - b.opt.x;
  for(int j=0; j<i/2; j++)
  {
   b.opt.x=b.opt.x+2;
   b.pt2.x=b.pt2.x+2;
   b.pt3.x=b.pt3.x+2;
   b.pt4.x=b.pt4.x+2;
  }
 }
 if(b.opt.y<ory)
 {
  int i = ory - b.opt.y;
  for(int j=0; j<i; j++)
  {
   b.opt.y=b.opt.y+1;
   b.pt2.y=b.pt2.y+1;
   b.pt3.y=b.pt3.y+1;
   b.pt4.y=b.pt4.y+1;
  }
 }
 if(b.opt.x<0||b.opt.x>18||
  b.pt2.x<0||b.pt2.x>18||
  b.pt3.x<0||b.pt3.x>18||
  b.pt4.x<0||b.pt4.x>18
  )
 {
        b = copyb;
  j--;
 }
 if(b.opt.y>14||
  b.pt2.y>14||
  b.pt3.y>14||
  b.pt4.y>14||
  isexist(b)
  )
 {
  b = copyb;
  j--;
  lark = 0;
 }
}
// 函数作用:消行
void check(int &score){
 bool mark = 1;
 int line = 0;
 int line_marked[4] = {0, 0, 0, 0}; // line_marked用于记录游戏空间中被全部标记的某一行的行号,(即需要被消去的行的行号)
 int kai=0;                         // s 用于记录line_marked中当前被标记的个数
 //检查哪些行需要消去
 for(int i=0; i<15; i++)
 {
  for(int j=0; j<10; j++)
  {
   if(scrop[i][j]==0)
    mark = 0;
  }
  if(mark==1)
  {
   line++;
   line_marked[kai++] = i;
   score++;
        }
  mark=1;
 }
 //检查哪些行需要消去...
 go(25,21);
 go(28, 21);
 out("分数: ",6);  //打印分数
    if(score==100)
 {
  system("cls");
  go(37,12);
  out("通关",4);
        othergetch();
  exit(-1);
    }
 int dis = line;
 //消行前闪烁
 for(int goline=0; goline<kai; goline++)
 {
  color(0xe4);
  go(0,line_marked[goline]);
  
  int s=0;
  for(; s<10; s++)
  {
   out("■",2);
  }
  wait(.1f);
  color(0xbd);
  go(0,line_marked[goline]);
  for(s=0; s<10; s++)
  {
   out("■",2);
  }
  wait(.1f);
  
 }
 //消行前闪烁...
 kai = 0;
 //消行
 for(int k=1; k<=dis; ++k)
 {
  for (int p=line_marked[kai++]; p>=1; p--)
  {
   for (int q=0; q<10; q++)
   {
    scrop[p][q]=scrop[p-1][q];
   }
  }
  for(int h=0; h<10; h++)
  {
   scrop[0][h]=0;
  }
 }
 //消行...
}
//函数作用:当方块停留时,把方块四个点的坐标在游戏空间数组scrop[][]中的相应位置标记为1
void markspace(block b)
{
 for (int i=0; i<15; i++)
 {
  for (int j=0; j<10; j++)
  {
   if((b.opt.x==2*j && b.opt.y==i)||
    (b.pt2.x==2*j && b.pt2.y==i)||
    (b.pt3.x==2*j && b.pt3.y==i)||
    (b.pt4.x==2*j && b.pt4.y==i))
   {
    scrop[i][j]=1;
   }
  }
 }
}
//函数作用:输出方块
void show_block(block b)
{
 go(b.opt.x, b.opt.y);
 show_unit();
 go(b.pt2.x, b.pt2.y);
 show_unit();
 go(b.pt3.x, b.pt3.y);
 show_unit();
 go(b.pt4.x, b.pt4.y);
 show_unit();
}
// 函数作用:让程序等待一段时间
void wait(float secs)
{
 clock_t delay = secs* CLOCKS_PER_SEC;
 clock_t start = clock();
 while(clock()-start<delay);
}
// 函数作用:设置输出的文本前景色和背景色
void color(int n_color)
{
 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), n_color);
}
// 函数作用:获得一个不大于 max 的随机数
int getrand(int max)
{
 srand((unsigned)time(NULL));
 int j = rand() % max;
 return j;
}
// 函数作用:将光标移到控制台中的指定的位置
void go(int x, int y)
{
 COORD pos = {x, y};
 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),pos);
}
// 函数作用:这个函数没什么作用,主要用于暂停
void othergetch()
{
 getch();
 getch();
 getch();
 getch();
}
// 函数作用:相当于C++中的Cout的功能,chars传递欲输出的字符串,
//           n用于指定输出chars中的字符个数
void out(char* chars, int n)
{
 WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),chars,n,0,0);
}
// 函数作用:输出一个 ■
void show_unit()
{
 WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),"■",2,0,0);
}
// 函数作用:捕获键盘上的上下左右键和回车健,并对其它键不作出响应
char getudlr()
{
    string u="郒";
    string d="郟";
    string l="郖";
    string r="郙";
    string str="";
    char ch=0;
    do
    {
  ch=getch();
  if (ch=='/r')
  {
   return 'k';
  }
 }while(ch!=-32);
    str=str+ch;
    ch=getch();
    str=str+ch;
 
    if(str==u)
  return 'u';
    else if(str==d)
  return 'd';
    else if(str==l)
  return 'l';
    else if(str==r)
  return 'r';
 else
  return 'o';
}

这个游戏毫无趣味可言,而且方块不会自动向下运动(需要多线程的支持),
主要目的是向大家介绍如何在控制台程序( 也就是所谓的console程序)中
实现简单的界面控制。以下是我个人在学习中总结并封装的一些可用于控
制台程序中关于界面控制的一些常用函数:

注意:这些函数均需要包含windows.h头文件

1)光标定位
void go(int x, int y)  // 0<=x<=24, 0<=y<=79
{
 COORD pos = {x, y};
 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

2)文本颜色(1)            //n_color的格式是:0Xmn 
                         //其中0X表示16进制,m,n取值是0 ~ f  m为背景色 n为前景色
                         //例如 color 0x0a; 这条语句设置背景色为黑色,前景色为亮绿色
void color(int n_color)
{
 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), n_color);
}

文本颜色(2)
system("color 0xmn"); //dos功能调用

3)窗口大小
void Chang_Console_size(int _x, int _y)
{
 CONSOLE_SCREEN_BUFFER_INFO bInfo;                          // 窗口缓冲区信息
 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &bInfo); // 获取窗口缓冲区信息
 COORD size = {_x, _y};
  SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), size);      // 重新设置缓冲区大小
 SMALL_RECT rc = {0, 0, _x-1, _y-1};                          // 重置窗口位置和大小
 SetConsoleWindowInfo(GetStdHandle(STD_OUTPUT_HANDLE), true, &rc);
}

4)自己封装的一个捕获键盘按键的函数
  还需包含string和conio.h头文件
char getudlr()
{
    string u="郒";   // “上” 的双ASSIC码值
    string d="郟";   // “下” 的双ASSIC码值
    string l="郖";   // “左” 的双ASSIC码值
    string r="郙";   // “右” 的双ASSIC码值
    string str="";   //  定义空串
    char ch=0;
    do
    {
  ch=_getch();
  if (ch=='/r')
  {
   return 'e'/*enter*/;     //如果是回车就返回字符'e'
  }
 }while(ch!=-32);
    str=str+ch;
    ch=_getch();
    str=str+ch;
 
    if(str==u)
  return 'u'/*up*/;       //如果是上就返回字符'u'
    else if(str==d)
  return 'd'/*down*/;     //如果是下就返回字符'd'
    else if(str==l)
  return 'l'/*left*/;     //如果是左就返回字符'l'
    else if(str==r)
  return 'r'/*right*/;    //如果是右就返回字符'r'
}

5)让程序等待一段时间 还需包含ctime或time.h头文件
void wait(float secs)
{
 clock_t delay = secs* CLOCKS_PER_SEC;
 clock_t start = clock();
 while(clock()-start<delay);
}

6)产生一个不大于maxrand的随机数 还需包含ctime或time.h头文件
int get_rnum(int maxrand)
{
 srand((unsigned)time(NULL));
 int rnum = rand()%maxrand;
 return rnum;
}

7)输出字符
// 函数作用:相当于C++中的cout的功能,chars传递欲输出的字符串,
//           n用于指定输出chars中的字符个数,为什么我在程序中
//           没有采用C++中的cout函数而采用这种形式呢?这是因为
//           这种方法效率更高
void out(char* chars, int n)   //chars 是欲输出的字符串,n 是字符串的个数
{
 WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),chars,n,0,0);
}
注:这个函数不能象C++中的cout那样输出数字,只能输出字符,如果想让这个函数也
    能象cout那样输出数字,可以采用如下方法改进这个函数:把数字循环除10后所得
    的单个数字的ASSIC码加上 48 后转化为字符,然后利用这个函数输出即可,如有
    不会请联系我。本人QQ:296173446

8)清屏(1)
void cls()
{
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( GetStdHandle(STD_OUTPUT_HANDLE), &bInfo );
 COORD home = {0, 0};
 WORD att = bInfo.wAttributes;
 unsigned long size = bInfo.dwSize.X * bInfo.dwSize.Y;
 FillConsoleOutputAttribute(GetStdHandle(STD_OUTPUT_HANDLE), att, size, home, NULL);
 FillConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), ' ', size, home, NULL);
}
 
清屏(2)
system("cls"); //dos功能调用

以上讲诉的是函数体,至于用法可参见我在游戏设计中的代码实现部分
值得注意的是俄罗斯方块用MFC来编写更为简单,方便。


小时候玩的“俄罗斯方块”终于诞生。遗憾的是这个程
序中的方块不能自动向下运动。

 

 

@echo off

set ip=ip
for /f "tokens=2 delims=:" %%i in ('ipconfig ^| find /i "IPv4"') do set ip=%%i

if "%ip%"=="10.2.158.94" (echo 无需重启网卡) else (
netsh interface set interface name="本地连接" admin=DISABLED
ping -n 20 127.1
netsh interface set interface name="本地连接" admin=ENABLED
)


 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值