暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

在单片机写了个俄罗斯方块游戏(附源码详解)

嵌入式情报局 2021-12-26
862
俄罗斯方块这个小游戏大家都玩过吧,今天我就带大家用C语言实现一款自己的俄罗斯方块小游戏(只要大家 实现一个在屏幕任意位置画矩形的函数就可以移植了)。


硬件配置:树莓派PCIO单片机
屏幕:240*240 SPI接口的显示屏


俄罗斯方块之数据结构
#define ELSFK_HIGHT  23
#define ELSFK_WIDTH  16
static uint8_t datas[ELSFK_HIGHT][ELSFK_WIDTH]={0};

  • 主要数据结构就是一个二维数组datas

  • 它是记录在屏幕的什么地方显示小方块的(一个小方块的像素点为10*10)。

下面我们详细讲一下他是怎么记录小方块的坐标的。

啥,坐标?

对,就是一个小方块在屏幕中显示的位置,

简单点讲就是用(x,y)来表示的。

我们规定屏幕的坐标是这样的,如图:



我们要在坐标(0,0)处显示一个点,就在对应的数组中置1就可以

比如:datas[0][0] = 1;就表示在屏幕的(0,0)处显示了一个方块



有了坐标,就知道了在哪显示方块了。

注意:最后一个点是datas[22][15] = 1;

[22]在前,[15]在后,大家要小心,

也就是先写第几行,再写第几列,

数组的下标都是从零开始的,

#define ELSFK_HIGHT  23
#define ELSFK_WIDTH  16

我们定义了行数为23行,也就是数组下标0~22,
列数为16,也就是0~15.
那我们就开始编写方块绘制函数:
void draw(){
for(uint8_t i = 0; i < ELSFK_HIGHT; i++){||行
    diamond_region.tLocation.iY = 10 + i * 10;
for(uint8_t j = 0; j < ELSFK_WIDTH; j++){||列
if(datas[i][j]){||有方块就显示
        diamond_region.tLocation.iX = 10 + 10 * j;
        ||绘制一个小方块
        arm_2d_rgb16_fill_colour(ptTile, &diamond_region, GLCD_COLOR_BLACK);  
      }            
    }  
  }
}

  • 注意diamond_region.tLocation.iY = 10 + i * 10;这里是计算屏幕显示的绝对位置,我们一个小方块是10个像素点。


接着介绍我们的数据结构,接下来还是一个数组。

static uint16_t block[7][4] = {
  {0x0e40,0x2620,0x0270,0x0464,},//T
  {0x0446,0x0e80,0x6220,0x0170,},//L
  {0x0226,0x08e0,0x6440,0x0710,},//J
  {0x0c60,0x2640,0x0630,0x0264,},//Z
  {0x06c0,0x4620,0x0360,0x0462,},//S
  {0x0660,0x0660,0x0660,0x0660,},//O
  {0x4444,0x0f00,0x2222,0x00f0,},//I        
};

  • 这个数组定义了俄罗斯方块的形状

  • 一共分成 7 形状,有的形状有 4种状态,

  • 不管是多少种状态,一个方块需要2字节来存储,也就是16bit来保存一个方块的信息。

如图:


那他又是怎么转换成2个字节的数据的呢?
这就需要2进制转换成16进制


十进制十六进制二进制
0
0
0000
1
1
0001
22
0010
3
3
0011
4
4
0100
5
5
0101
6
6
0110
7
7
0111
8
8
1000
9
9
1001
10
a
1010
11
b
1011
12
c
1100
13
d
1101
14
e
1110
15
f
1111

有了这个表就方便转换了,举个例子:



  • 这就是第一个  T  字图形转换成十六进制为 0x0e40的过程,简单吧。


俄罗斯方块之函数


首先讲一下整个俄罗斯方块的结构:
run(){
  while(1){
    ||绘制方块函数
    draw();
    ||获取按键值
    key = elsfk_menu_get_key();
    ||生成一个新的方块图形
    new_diamond();
    ||图形下降     
    diamond_down(key){
      ||按下左键左移
      diamond_down_left();
      ||按下右键右移 
      diamond_down_right();
      ||按下上键改变形状
      diamond_down_up();
      ||没有按键按下就下降
      diamond_down_down();
    }
    ||消层函数
    elimination_layer();
    ||延时函数
    elsfk_delay(1000);
  }
}

  • 大家移植的时候需要自己实现绘制方块函数draw();

    和elsfk_menu_get_key();获取按键值函数

  • 获取项目源码可以在公众号回复“游戏”。


现在我们就开始随机生成一个形状,并准备下落。

void elsfk_init()
  ||随机生成一个数   
  diamond_next = (rand_() + rand())%28;
  flag.new_diamond_flag = 0;
}

  • diamond_next这个变量保存下一个形状的下标,因为只有28种形状,所以要对28取模,保证下标不越界。

  • flag.new_diamond_flag这个变量为0,表示生成一个新的形状,为1表示形状正在下落,不需要生成新的形状了。

||绘制一个新形状
void new_diamond(){
  uint16_t diamond ;
  uint16_t wei;
  if(0 == flag.new_diamond_flag){
    ||绘制完成,标记置为1
    flag.new_diamond_flag = 1;
    diamond_current = diamond_next;
    ||从block数组中取形状
    diamond = block[diamond_current/4][diamond_current%4];
    ||在屏幕中间显示
    diamond_X = 6;
    ||绘制4 行4列的形状图形
    for(uint8_t i = 0; i < 4; i++){
      for(uint8_t j = 0; j < 4; j++){
        wei = (3-i)*4 + 3-j;
        ||置1 为显示小方块
        datas[i][j + diamond_X] = (diamond>>wei) & 0x01;
      }
    }
    ||为下降函数准备的数据
    for(uint8_t i = 0; i < 4; i++){
      diamond_down_line[i] = 0;
      for(uint8_t j = 0; j < 4; j++){
        if(datas[j][i + diamond_X]){
            diamond_down_line[i] = j + 1;
        }
      }
    }
    donw_line = diamond_down_line[0] ;
    for(uint8_t i = 1; i < 4; i++){
        if(diamond_down_line[i] > donw_line){
            donw_line = diamond_down_line[i] ;
        }
    }
    diamond_Y = donw_line;      
      //====right======
    for(uint8_t i = 0; i < 4; i++){
      diamond_down_line_right[i] = 0;
      for(uint8_t j = 0; j < 4; j++){
        if(datas[i][j+ diamond_X]){
          diamond_down_line_right[i] = j + 1;
        }
      }
    }
    diamond_right = diamond_down_line_right[0];
    for(uint8_t i = 1; i < 4; i++){
      if(diamond_down_line_right[i] > diamond_right){
        diamond_right = diamond_down_line_right[i] ;
      }
    }
    //====lift======
    for(uint8_t i = 0; i < 4; i++){
      diamond_down_line_lift[i] = 0;
      for(uint8_t j = 0; j < 4; j++){
        if(datas[i][j+ diamond_X]){
          diamond_down_line_lift[i] = j + 1;
          break;
        }
      }
    }
    for(uint8_t i = 1; i < 4; i++){
      if(diamond_down_line_lift[i]){
        diamond_lift = diamond_down_line_lift[i];
        break;
      }
    }
    for(uint8_t i = 1; i < 4; i++){
      if(diamond_down_line_lift[i] < diamond_lift){
        if(diamond_down_line_lift[i]){
          diamond_lift = diamond_down_line_lift[i] ;
        }
      }
    }
  }
  ||准备下一个形状数据
  diamond_next =  (rand_() + rand())%28;
}

  • donw_line表示下降到第几行,下降时要用到。

  • diamond_X表示在第几列,左右移动的时候要用到。

  • 37~74行是处理左右移动用到的变量,原理和下降的相同。

  • 21~36行是处理下降要用到的变量,原理如下图所示


 

接下来就是下降函数:
void diamond_down(uint8_t key){
  switch( key ){
    case DIRECTION_LEFT:
    ||按下左键左移 
      diamond_down_left();
      break;
    case DIRECTION_RIGHT:  
    ||按下右键右移      
      diamond_down_right();
      break;
    case DIRECTION_KEY_Y:
    case DIRECTION_UP:
    ||按下上或Y键改变形状
      diamond_down_up();
      break;        
    case DIRECTION_KEY_A:
    ||按下A键清屏,重新玩 
      for(uint8_t i = 0; i < ELSFK_HIGHT; i++){        
          for(uint8_t j = 0; j < ELSFK_WIDTH; j++){
              datas[i][j] = 0;
          }
      }
      flag.new_diamond_flag = 0;
      new_diamond();
      break;
    default:
      ||没有按键按下就下降
      diamond_down_down();
      break;
  }
}


方块下降函数没有按键按下就下降
void diamond_down_down(){  
  uint8_t i,j,stop_flag = 0;
  ||超出行数,不能下降,直接结束
  if(donw_line > ELSFK_HIGHT){return;}  
  for(i = 0; i < 4; i++){
  ||判断能否下降  
    if(diamond_down_line[i]){
      if(datas[donw_line-(diamond_Y - diamond_down_line[i])][diamond_X+i]){
          ||下边有方块,则停止下降
          stop_flag = 1;
      }
    }
  }
  ||方块下降
  if(0 == stop_flag){
    for(i = 0; i < diamond_Y; i++){
      for(j = 0; j < 4; j++){
        if(diamond_down_line[j]){
          if(datas[donw_line - i - 1][diamond_X+j]){
            datas[donw_line - i][diamond_X+j] = datas[donw_line - i - 1][diamond_X+j];
            datas[donw_line - i - 1][diamond_X+j] = 0;
          }                  
        }
      }
    }
    donw_line++;
    ||下降到底停止
    if(donw_line >= ELSFK_HIGHT){
        stop_flag = 1;
    }
  }
  ||如果停止下降,重新生成方块形状
  if(stop_flag == 1){
    flag.new_diamond_flag = 0;
  }    
}

  • stop_flag = 1;代表方块已将到底或者下面有小方块不能在下降了。

  • flag.new_diamond_flag = 0;代表可以重新生成方块形状。

  • 下降检测原理如下图



方块左移函数:

void diamond_down_left(){
  int8_t i,j,x,y,stop_flag = 0;    
  for(i = 0; i < 4; i++ ){
    if(diamond_down_line_lift[i]){
      x = diamond_X + diamond_down_line_lift[i]-2;
      if(x < 0){
        stop_flag = 1;
        return;
      }
      else if(datas[donw_line-diamond_Y+i][x]){
        stop_flag = 1;
      }
    }
  }   
  if(0 == stop_flag){
    for(i = 0; i < diamond_Y; i++ ){
      if(diamond_down_line_lift[i]){
        for(j = diamond_down_line_lift[i]-1; j < diamond_down_line_right[i]; j++){
          y = donw_line-diamond_Y+i;
          x = diamond_X+j;
          datas[y][x-1] = datas[y][x];
          datas[y][x] = 0;
        }
      }
    }
    diamond_X--;  
  }
}

  • 左移函数的原理和下降一样,如果左边有方块就停止左移,左边没有方块且没到最左边就左移。

方块右移函数:


void diamond_down_right(){
    uint8_t i,j,x,y,stop_flag = 0;
    if((diamond_X + diamond_right) >= (ELSFK_WIDTH)){
        return;
    }
    for(i = 0; i < 4; i++){
        if(diamond_down_line_right[i]){
            if(datas[donw_line-diamond_Y+i][diamond_X + diamond_down_line_right[i]]){
                stop_flag = 1;
            }
        }
    }
    if(0 == stop_flag){
        for(i = 0; i < diamond_Y ; i++){
            if(diamond_down_line_lift[i]){
                for(j = diamond_down_line_lift[i]-1; j < diamond_down_line_right[i]; j++){
                    y = donw_line-diamond_Y+i;
                    x = diamond_X+diamond_down_line_right[i]-j + diamond_down_line_lift[i]-2;
                    if( datas[y][x]){
                        datas[y][x+1] = datas[y][x];
                        datas[y][x] = 0;
                    }                                     
                }
            }
        }
        diamond_X++;
        if((diamond_X + diamond_right) >= ELSFK_WIDTH){
            stop_flag = 1;
        }
    }    
}

  • 原理和左移一样


方块变换函数:

void diamond_down_up(){
    uint16_t wei,diamond;
    uint8_t x,y,stop_flag = 0;
    uint8_t diamond_Y_temp;  
    y = diamond_current/4;
    x = diamond_current%4;
    diamond = block[y][x];        
    for(uint8_t i = 0; i < 4; i++){
        for(uint8_t j = 0; j < 4; j++){
            wei = (3-i)*4 + 3-j;              
            if(datas[donw_line - diamond_Y + i][j + diamond_X] &&(!((diamond>>wei) & 0x01))){
                stop_flag = 1;
            }
        }
    }
    if((diamond_X < 0) ||(diamond_X > (ELSFK_WIDTH - 4))){
        stop_flag = 1;
    }
    if(stop_flag == 0){
        x++;
        x = x%4;
        diamond_current = y * 4 + x;
        diamond = block[y][x];
        for(uint8_t i = 0; i < 4; i++){
            for(uint8_t j = 0; j < 4; j++){
                wei = (3-i)*4 + 3-j;  
                datas[donw_line - diamond_Y + i][j + diamond_X] = (diamond>>wei) & 0x01;
            }
        }
        //===================
        ||变化完重新初始化移动需要的变量
        for(uint8_t i = 0; i < 4; i++){
            diamond_down_line[i] = 0;
            for(uint8_t j = 0; j < 4; j++){
                if(datas[donw_line - diamond_Y + j][i + diamond_X]){
                    diamond_down_line[i] = j + 1;
                }
            }
        }
        diamond_Y_temp = diamond_down_line[0] ;
        for(uint8_t i = 1; i < 4; i++){
            if(diamond_down_line[i] > diamond_Y_temp){
                diamond_Y_temp = diamond_down_line[i] ;
            }
        }
        donw_line = donw_line - (diamond_Y - diamond_Y_temp);
        diamond_Y = diamond_Y_temp;        
        //====right======        
        for(uint8_t i = 0; i < 4; i++){
            diamond_down_line_right[i] = 0;
            for(uint8_t j = 0; j < 4; j++){
                if(datas[donw_line - diamond_Y + i][j+ diamond_X]){
                    diamond_down_line_right[i] = j + 1;
                }
            }
        }
        diamond_right = diamond_down_line_right[0];
        for(uint8_t i = 1; i < 4; i++){
            if(diamond_down_line_right[i] > diamond_right){
                diamond_right = diamond_down_line_right[i] ;
            }
        }
        //====lift======
        for(uint8_t i = 0; i < 4; i++){
            diamond_down_line_lift[i] = 0;
            for(uint8_t j = 0; j < 4; j++){
                if(datas[donw_line - diamond_Y + i][j+ diamond_X]){
                    diamond_down_line_lift[i] = j + 1;
                    break;
                }
            }
        }
        for(uint8_t i = 1; i < 4; i++){
            if(diamond_down_line_lift[i]){
                diamond_lift = diamond_down_line_lift[i];
                break;
            }
        }
        for(uint8_t i = 1; i < 4; i++){
            if(diamond_down_line_lift[i] < diamond_lift){
                if(diamond_down_line_lift[i]){
                    diamond_lift = diamond_down_line_lift[i] ;
                }
            }
        }  
    }
}

  • 31行之后是处理变化完重新初始化移动需要的变量

  • 方块变换原理如下图



到这里我们就讲完了俄罗斯方块源码,这个只是我自己实现的(其中还有很多不足),项目源码可以在公众号回复“游戏”,其中还有一些小bug没有处理,希望大家一起改进。
文章来源:公众号:嵌入式小书虫
本文仅供大家学习参考与知识传播,版权归原作者所有,如有侵权,麻烦联系小哥进行删除,感谢~

小哥搜集了一些嵌入式学习资料,公众号内回复1024即可找到下载链接!

推荐好文  点击蓝色字体即可跳转

 专辑|Linux应用程序编程大全
☞ 专辑|学点网络知识
☞ 专辑|手撕C语言
☞ 专辑|手撕C++语言
☞ 专辑|经验分享
☞ 专辑|从单片机到Linux
☞ 专辑|电能控制技术
文章转载自嵌入式情报局,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论