项目地址:https://gitee.com/daycen/stm32-tetris/tree/master

使用Keil uVision5打开即可

一、概述

​ 本文介绍了一个基于STM32的俄罗斯方块游戏实现例子

​ 整体方案的硬件部分由一个最小系统、按键开关模块以及2.2寸TFTLCD屏幕组成,软件部分设计由绘图、逻辑、整合控制三大部分组成,由一个二维绘图函数绘制出游戏画面,并由碰撞判断、状态储存等机制实现游戏的正常运行。

需求:

开发一款基于STM32F103的游戏机,能够游玩经典游戏《TETRIS》

(1) 显示:通过屏幕显示游戏UI等信息供用户进行游玩;

(2) 控制:用户可通过独立按键进行操控;

(3) 用户进行游戏时会有分数记录、难度等级等提示;

(4) 游戏过程中可暂停游戏;

指标:

(1) 能够通过屏幕正常显示游戏信息;

(2) 对按键的操作能及时响应;

(3) 游戏结束时要显示玩家得分;

(4) 游戏过程可任意暂停;

总体方案:

五向按键模块

显示屏模块

STM32最小系统

二、软件与算法介绍

绘图工具(显示部分)

1、画圆函数&画点(线)函数

为实时显示图像,我们需要一个可以在任意的指定坐标画点的函数,我们称之为画点函数Gui_DrawLine()和画圆函数Gui_Circle()在这里,我们使用屏幕供应商提供的画点/园函数,它们基于Bresenham算法构建而成。本质上,Bresenham是一种让计算机实现高效的画线的一种算法。

下面我们用一张图来举例说明该算法的基本思想:

假设该线段位于第一象限内且斜率大于0小于1,设起点为(x1,y1),终点为(x2,y2).根据对称性,可推导至全象限内的线段。

第一步,画起点(x1,y1);

第二步,准备画下个点。x坐标增1,判断如果达到终点,则完成。否则,由图中可知,下个要画的点要么为当前点的右邻接点(B),要么是当前点的右上邻接点(U);

判断:(以跟直线上点M的纵坐标距离为依据选择下一个点的位置)

(1) 如果线段ax+by+c=0与x=x1+1的交点的y坐标大于M点的y坐标的话,下个点为U(x1+1,y1+1);

(2) 否则,下个点为B(x1+1,y1);

简单来说,就是判断U、B跟直线ax+by+c=0与直线x=x1+1的交点M之间的距离远近(通过两点间距离公式),选取近的一个作为下一点并画出,以此类推直到画出整条直线。

第三步,画点(U或者B);

第四步,跳回第2步;

结束

细化的代码实现方式在此不做过多讨论,网络上已经有很多种较为成熟的代码实现方式,下面给出我们使用的供应商提供的具体实现代码:

Gui_DrawLine函数

void Gui_DrawLine(u16 x0, u16 y0,u16 x1, u16 y1,u16 Color)  

{

int dx,       // difference in x's

  dy,       // difference in y's

  dx2,      // dx,dy * 2

  dy2, 

  x_inc,     // amount in pixel space to move during drawing

  y_inc,     // amount in pixel space to move during drawing

  error,     // the discriminant i.e. error i.e. decision variable

  index;     // used for looping    

 Lcd_SetXY(x0,y0);

 dx = x1-x0;//计算x距离

 dy = y1-y0;//计算y距离

 if (dx>=0)

 {

​      x_inc = 1;

 }

 else

 {

​      x_inc = -1;

​      dx  = -dx; 

 } 

 if (dy>=0)

 {

​      y_inc = 1;

 } 

 else

 {

​      y_inc = -1;

​      dy  = -dy; 

 } 

 dx2 = dx << 1;

 dy2 = dy << 1;

 if (dx > dy)//x距离大于y距离,那么每个x轴上只有一个点,每个y轴上有若干个点

 {//且线的点数等于x距离,以x轴递增画点

​      // initialize error term

​      error = dy2 - dx; 

​      // draw the line

​      for (index=0; index <= dx; index++)//要画的点数不会超过x距离

​      {

​           //画点

​           Gui_DrawPoint(x0,y0,Color);

​           

​           // test if error has overflowed

​           if (error >= 0) //是否需要增加y坐标值

​           {

​                error-=dx2;

​                // move to next line

​                y0+=y_inc;//增加y坐标值

​           } // end if error overflowed

​           // adjust the error term

​           error+=dy2;

​           // move to the next pixel

​           x0+=x_inc;//x坐标值每次画点后都递增1

​      } // end for

 } // end if |slope| <= 1

 else//y轴大于x轴,则每个y轴上只有一个点,x轴若干个点

 {//以y轴为递增画点

​      // initialize error term

​      error = dx2 - dy; 

​      // draw the line

​      for (index=0; index <= dy; index++)

​      {

​           // set the pixel

​           Gui_DrawPoint(x0,y0,Color);

​           // test if error overflowed

​           if (error >= 0)

​           {

​                error-=dy2;

​                // move to next line

​                x0+=x_inc;

​           } // end if error overflowed

​           // adjust the error term

​           error+=dx2;

​           // move to the next pixel

​           y0+=y_inc;

​      } // end for

 } // end else |slope| > 1

}

Gui_Circle函数

void Gui_Circle(u16 X,u16 Y,u16 R,u16 fc) 

{ 

  unsigned short a,b; 

  int c; 

  a=0; 

  b=R; 

  c=3-2*R; 

  while (a<b) 

  { 

​    Gui_DrawPoint(X+a,Y+b,fc);   //    7 

​    Gui_DrawPoint(X-a,Y+b,fc);   //    6 

​    Gui_DrawPoint(X+a,Y-b,fc);   //    2 

​    Gui_DrawPoint(X-a,Y-b,fc);   //    3 

​    Gui_DrawPoint(X+b,Y+a,fc);   //    8 

​    Gui_DrawPoint(X-b,Y+a,fc);   //    5 

​    Gui_DrawPoint(X+b,Y-a,fc);   //    1 

​    Gui_DrawPoint(X-b,Y-a,fc);   //    4 

​    if(c<0) c=c+4*a+6; 

​    else 

​    { 

​      c=c+4*(a-b)+10; 

​      b-=1; 

​    } 

​    a+=1; 

  } 

  if (a==b) 

  { 

​    Gui_DrawPoint(X+a,Y+b,fc); 

​    Gui_DrawPoint(X+a,Y+b,fc); 

​    Gui_DrawPoint(X+a,Y-b,fc); 

​    Gui_DrawPoint(X-a,Y-b,fc); 

​    Gui_DrawPoint(X+b,Y+a,fc); 

​    Gui_DrawPoint(X-b,Y+a,fc); 

​    Gui_DrawPoint(X+b,Y-a,fc); 

​    Gui_DrawPoint(X-b,Y-a,fc); 

  } 

}

2、方块绘制相关函数

根据俄罗斯方块的游戏规则,每个方块由4个小块构成,一共有19种样式如下图

我们设置一个俄罗斯方块中的一小块大小为10*10,由此可由遍历的方法得到绘制一小块方块的Draw_realbox()函数如下:

同样的方法我们需要一个删除方块函数用于方块的消除,即将10*10区域画上白色即可。删除方块函数如下:

有了以上两个函数,我们只需要在规定的坐标处调用四次小方块绘制或删除函数

即可得到或消除一块完整的俄罗斯方块。而方块有19种,故使用switch语句进行选择需要何种方块。图形绘制函数如下(部分):

同理需要一个图形删除函数

3、游戏引擎(逻辑部分)

状态储存机制

我们使用了一个大小为23*16的二位数组来记录方块的位置,便于后续进行碰撞判断、方块消除等操作

数组类似于一个显示屏,里面为1的地方表示有小方块存在,我们只需要改变数组中的0、1即可实现对一个俄罗斯方块的保存,为此需要一个绘制和删除函数,逻辑与在LCD绘制方块类似,由Draw_a_zhuangtai()Del_a_zhuangtai()实现

碰撞判断

有了之前定义的数组,我们可以使用求和的方式(类似前导零算法)找出碰撞的方块。因为在方块没发生碰撞之前,对该数组求和的值为60(数组边界)+方块占的值(方块缓存)。当方块发生碰撞,两个方块之间的交集会使得方块占的值变化(变小),与原值比较后可得出方块是否碰撞,碰撞则返回一个值。特别的,为了判断碰撞事件,在panduan()函数中我们还需要对方块的方向进行判断,因此panduan()函数还可在需要判断方向时调用。

物体消除

由函数xiaochu()实现,每发生一次碰撞就检测一次是否满足消除条件

其中,消除函数的“消除”功能是由调用换行函数lie_move()和删行函数Del_lie()实现的

形状控制函数

即按指令绘制出规定形状的俄罗斯方块的函数,一共有两个,change()函数用于绘制LCD上的,change_Zhuangtai()函数用于改变状态数组中的。

随机数生成

在游戏中,方块需要随机生成,所以我们需要一个随机数作为方块产生的依据,但仅用rand()函数生成的是伪随机数,所以我们使用srand()函数打乱伪随机数,同时引入ADC产生的末尾数据,以达到一个较高的随机性。

如:

srand(Get_Adc(ADC_Channel_1));

what=rand()%19+1;//what代表着不同俄罗斯方块

4、整合部分

方向控制

方块可进行左、右、下的移动,因此我们需要在接到指令后再对应坐标画出完整的俄罗斯方块并将原坐标处的图形消除,只需调用之前定义的图形调用/删除函数即可。值得注意的是,在移动图像的同时,我们也需要对状态数组中的数据进行相应的移动,为此我们需要一些整合后的函数如Down()Left()Right()Del(),在此不做列举说明。

向左移动函数:

向右移动函数:

向下移动函数:

封装好上述方向移动功能函数后,我们只需调用它们即可实现相应方向的改变,调用函数如下:

再配合KEY_Scan()函数即可实现方向判断,该函数流程图如下:

分数、等级显示

关于分数、等级等函数,只需通过当前分数、等级变量选择相应数字在指定坐标绘制即可(此处仅列举display_leave()函数)

开始游戏

通过begin()函数first()函数实现。方块的实际绘制是begin()函数完成的,此外该函数定义了方块的初始刷新位置并且对方块是否触顶进行判断以提示游戏结束。first()函数以负责清空上一次数组保存的状态和基本UI的绘制,由于我们设置的边界,UI部分在游戏过程中不会受到刷新影响,故只需要绘制一次。

暂停/恢复

当触发暂停功能时,该函数在指定区域绘制暂停提示,修改标志位game到暂停状态并且清空定时器使能位实现暂停。

恢复游戏运行是由star()函数实现的,当恢复时,该函数会把标志位game改为运行状态,并重绘部分UI。(受暂停提示界面的刷新影响必须重绘,不然显示会有缺失)流程图如下:

定时器中断函数

大约10μs中断一次作为基础下落的信号,在主程序中若i大于speed则执行下落函数

主程序

在主函数中,对各项进行初始化、获取随机数,通过switch语句对获取到的相应键值进行处理。

三、实物图

【补档STM32】STM32F103俄罗斯方块游戏实现的更多相关文章

  1. 经典 HTML5 & Javascript 俄罗斯方块游戏

    Blockrain.js 是一个使用 HTML5 & JavaScript 开发的经典俄罗斯方块游戏.只需要复制和粘贴一段代码就可以玩起来了.最重要的是,它是响应式的,无论你的显示屏多么宽都能 ...

  2. 从零开始---控制台用c写俄罗斯方块游戏(1)

    从零开始---控制台用c写俄罗斯方块游戏(1) 很少写博文,一来自身知识有限,二来自己知道,已经有很多这样的博文了,三就是因为懒,文笔也一般,四来刚出来工作,时间也不多 之所以写这篇博文,是因为应群里 ...

  3. 用C写的俄罗斯方块游戏 By: hoodlum1980 编程论坛

    /************************************ * Desc: 俄罗斯方块游戏 * By: hoodlum1980 * Email: jinfd@126.com * Dat ...

  4. java俄罗斯方块游戏代码

    java俄罗斯方块游戏代码: package com; import java.awt.Color; import java.awt.Graphics; import java.awt.event.K ...

  5. 教你看懂网上流传的60行JavaScript代码俄罗斯方块游戏

    早就听说网上有人仅仅用60行JavaScript代码写出了一个俄罗斯方块游戏,最近看了看,今天在这篇文章里面我把我做的分析整理一下(主要是以注释的形式). 我用C写一个功能基本齐全的俄罗斯方块的话,大 ...

  6. 俄罗斯方块游戏 --- java

    俄罗斯方块游戏 如有疑问请查看:http://zh.wikipedia.org/zh-tw/%E4%BF%84%E7%BD%97%E6%96%AF%E6%96%B9%E5%9D%97 更多疑问请参考: ...

  7. 俄罗斯方块游戏JavaScript代码

    JavaScript代码俄罗斯方块游戏 早就听说网上有人仅仅用60行JavaScript代码写出了一个俄罗斯方块游戏,最近看了看,今天在这篇文章里面我把我做的分析整理一下(主要是以注释的形式). 我用 ...

  8. 使用JS实现俄罗斯方块游戏

    简单的JS俄罗斯方块游戏源码 效果图: 代码如下,复制即可使用: <!DOCTYPE html> <html> <head> <meta charset=&q ...

  9. C++编写简单的俄罗斯方块游戏

    代码地址如下:http://www.demodashi.com/demo/14593.html C++编写简单的俄罗斯方块游戏 使用C++编写一个简单的俄罗斯方块游戏. 1 环境要求 使用C++图形库 ...

随机推荐

  1. 读取ini配置文件 及 UI对象库

    读取ini配置文件 配置项 读取API 写入API 实战:UI 对象库 读取ini配置文件 配置项 在每个 ini 配置文件中,配置数据会被分组(比如下述配置文件中的"config" ...

  2. 内网渗透-windows认证

    前言:全国HW刚结束,加强一波内网概念,去年11月红队成绩并不理想,这次必拿下好成绩.冲!!! 0x00 本地认证 本地认证基础知识 在本地登录Windows的情况下,操作系统会使用用户输入的密码作为 ...

  3. 图解Leetcode组合总和系列——回溯(剪枝优化)+动态规划

    Leetcode组合总和系列--回溯(剪枝优化)+动态规划 组合总和 I 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 ...

  4. mooc人大单元测试3

    @font-face { font-family: Wingdings } @font-face { font-family: 宋体 } @font-face { font-family: " ...

  5. Pytorch系列:(四)IO操作

    首先注意pytorch中模型保存有两种格式,pth和pkl,其中,pth是pytorch默认格式,pkl还支持pickle库,不过一般如果没有特殊需求的时候,推荐使用默认pth格式保存 pytorch ...

  6. wire shark 抓包过滤器

    http.request.method==GET vuin= 抓取QQ信息 数据链路层: 筛选mac地址为04:f9:38:ad:13:26的数据包----eth.src == 04:f9:38:ad ...

  7. 【python】Leetcode每日一题-删除排序链表中的重复元素

    [python]Leetcode每日一题-删除排序链表中的重复元素 [题目描述] 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 . 返回同 ...

  8. 【小技巧】Eclipse 中创建Maven项目后没有WEB-INF文件夹以及web.xml文件

    懒得截图了,一张图配下面步骤搞定. 1.右键项目,选择propertities后选择图中①(被遮住了): 2.先不②勾选去掉,点击Apply:然后在把②处勾选上.此时④位置会出现东东,点击蓝色超链接. ...

  9. LeetCode 26. 删除有序数组中的重复项

    双指针法 分析: 设置两个指针:p1,p2,初始p1指向数组的第一个元素,p2指向第二个元素 1)如果p1的值 == p2的值,就让p2后移一位 2)如果p1的值 != p2的值,修改p1的下一个元素 ...

  10. HelloGitHub 小程序上线了,蛋只有一个搜索功能

    作者:HelloGitHub-卤蛋 我是...蛋蛋啊,本文是我从零开发「HelloGitHub 小程序」的开发日记,不要把这个系列当作技术文章来读,你将会收获更多的乐趣.‍♂️ 我只是个 Python ...