你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32F446+OLED12864跑贪吃蛇游戏

[复制链接]
不吃肉的羊 发布时间:2018-2-7 14:16
闲暇之余发现看了下OLED12864的数据手册,发现他的显示在Y轴上只有页写,也就是一次写8个点。
5 H: y$ I; d$ A9 W5 J
. [  C  e, g# _5 W4 U, K1 s* A突然想到,如果自己程序需要精准定位到某个点,那这不是会很艹蛋么?去网上搜索,基本上都是页写的代码,也就是在Y轴上的定位只能是0~7.7 s. b6 k4 I+ Y# ]$ i( z

2 o% T4 T; J  @! @( K$ Q. _, D于是花了点时间,做了个贪吃蛇的游戏,写了段程序。
" z! x) M; D; l+ Y+ S8 l. j; G
7 P( o- e: O2 V9 w" o程序硬件支持:& n4 m' j3 B* i) S" l
STM32F446R+OLED12864+按键' @- c5 u: k/ ]: F% w
& B0 P, K3 Y8 [5 b3 w9 S
软件结构:! }& ^8 j; X& p
1.DMA跑SPI刷屏
1 e) B  Z/ `) I& z% E; @9 Y2.贪吃蛇游戏算法6 e! w# C3 v$ f; v2 O  J5 f! @
3.重新定义OLED显示方式
3 f" {- E9 A4 j! ^1 T3 Y5 u7 z2 c; t  T3 U+ n, G2 J0 p$ \" H
底层驱动就不说,DMA配置,跑SPI数据。0 w  E' m* T% X& b) t
% I- ]- j& {) u- ~" b
我先做了个二位数组,存放64*128个点,初始值为0;9 ]% v4 [* `; Q1 S( N) d! A
uint8_t OLED_Display_Data[8][128]; W" r1 F6 X, w
4 Y6 L! r3 x' b+ _. V
之后开个定时器,每隔一段时间跑一次屏幕4 F* T& ~( c  S8 C6 {$ F% v
                if(HardwareParamter.TimingOLED>=Refresh_Time && !HardwareParamter.OLEDFlash_flag)) `6 b/ k5 ]3 l; K
                {
- O9 u: x6 O+ q7 a8 T/ h& K# S                        HardwareParamter.TimingOLED = 0;& W9 A! d* O( L$ e
                        Display_Process(OLED_Display_Data);        5 p& @! _, d  h% B) x8 U0 `( w) V
                }3 N$ ~8 P  e6 p5 ]6 w6 w  h5 z

/ R. J2 u# h4 b
, Z1 O  O) X0 c/ M基本想法就是,不停的跑这个数组,数组就是屏幕的每个点。
2 `0 k3 Q. M2 j6 q0 w# ^* w8 r1 I5 i% r9 h3 s4 }
贪吃蛇算法做的简单:* e& @: s! }) `8 ?: o
首先是一个结构体,存放相关参数; W; q7 y$ r3 U) `2 @- _

+ G9 i4 i8 n) @/ Ftypedef struct {& J' j( e2 Q& M- S+ f& b" r
        uint8_t food_x;      //食物横坐标
. i+ @+ n8 H* Z; q, o        uint8_t food_y;      //食物纵坐标
* W6 H0 w) X6 Q; R9 v4 M# F: H
# K, `' t+ U# x1 Y        uint8_t gameEsc;     //游戏开始暂停
  s- `3 W, o5 A0 R* X& n        uint8_t gameSpeed;   //游戏速度
7 X3 T, L* \0 k6 U: Y        uint8_t gameLevel;   //游戏难度
& [. [9 f& z8 A6 h1 L1 \# O& b       
- k8 J+ Y( ]  r- n        uint8_t x[39];
; H  O9 E* Z( a+ b* s7 j' h$ a        uint8_t y[39];
/ V& a6 z) H. d1 h6 A# J" ]8 j8 @        uint8_t node;        //蛇的节数
1 b3 |! S) O* A        uint8_t direction;   //蛇头方向) G5 `: x+ M; f6 O
}GameParamter_t;
7 A3 ?+ Z, ^+ h  P6 m4 ?9 Y  e% p
0 s2 j& a- y7 [9 i+ o# x' I9 V) u. O2 j& I' P: k
两个核心函数:移动和创建食物
0 H+ t2 h, i; T2 `食物依赖于随机数生成函数. v) M9 S0 C6 O
//    需要出现新食物
2 R7 O! E! p+ e* y0 Fuint8_t createNewFood(void)
# B' J" P+ R  J! U{; [" E1 W  ^/ P/ F; ~0 s
        uint8_t i;8 a5 l# a) _) y9 [
        uint8_t size = GameParamter.node;$ d% d4 T6 J9 ]" @0 A4 T" a( R
        uint8_t flag = TRUE;                         //    标记创建的新事物与贪吃蛇的身体冲突4 C4 \6 @9 \% G2 G
        srand(HardwareParamter.TimingVal);5 g: D* a8 ~* y+ ^9 ^, }
        GameParamter.food_x = rand() % MAP_H;
* F) t5 k" `5 H        GameParamter.food_y = rand() % MAP_L;. q9 M; C. _! M# {3 u/ l9 z$ z% V# _) S
       
2 f: n; E4 O8 x0 f* g6 [" k        //    食物的坐标必须为3的倍数才会在显示屏先被显示5 |6 [; H1 z- I& z& ^
        for (; GameParamter.food_x % DOT_H != 0; ++GameParamter.food_x) {      1 d( S2 t7 y- ]1 H; r! |" h
                        ;
+ R1 Q: r& H% K, A  O# a6 b5 l        }: Q/ u/ j3 w) C
        9 v0 s6 ^9 {. ^+ \, N
        for (; GameParamter.food_y % DOT_L != 0; ++GameParamter.food_y) {      
+ b; }6 @0 T/ i$ ]4 V$ S5 D                        ;+ j) M5 ?6 I" D5 i
        }3 J: B) {9 W) }( H
       
! {9 j* z  V0 O5 X2 U        for (i = 0; i < size; ++i) {8 p. s" D; ]. l7 Y! k* l
                        if (GameParamter.food_x == GameParamter.x[i] && GameParamter.food_y == GameParamter.y[i]) {
2 \; i* l  m) G2 _+ r( i3 c+ X/ Q  ^                                        flag = FALSE;( J# d# o' Q. ]# j1 e% u* g
                                        break;3 |$ E3 a: s& T- j7 d3 u5 T6 f1 n
                        }
2 @' a, I' O: o; ~; B2 x        }   
0 \  L! O# b  \7 z, a7 |
2 n+ Z; [& P4 z3 R1 N/ Q# z4 g  I% @8 U        return flag;6 R2 X6 }+ N& S# c7 N
}9 k0 V" G4 L( L3 ]4 ?! t( X

' ^! M: g/ G! k9 t' u
, y1 ^! r- y% Z2 \6 U/ p& N移动就是简单的数据加减
$ l3 s$ e: V( a# v! }! x- N//    贪吃蛇移动, F. l8 j9 f" l- G( {: Z7 d0 R
void move(void)% z8 _+ W& Q& ?- Y  n
{. B2 C& T, i: d1 |) Q- H, `0 A; n
        uint8_t i;) \0 I  ^% T' C8 ~" ^, M
0 w: ~+ Z* L& _  H5 [! W8 G$ u, J" I
        if(GameParamter.gameEsc)
, C/ ^& p6 s2 p8 V- s" }: E' H6 S5 y# u* z        {" _) L( t  j" F& @3 q% L
                //    将蛇从最后个节点向前一个节点移动+ ]+ U: S+ l  ]3 o$ }
                for(i = GameParamter.node - 1; i > 0; i--) {                    
- b& S$ {& B* h4 h5 N                                GameParamter.x[i] = GameParamter.x[i - 1];
( ?5 ?8 ~- V8 g: f                                GameParamter.y[i] = GameParamter.y[i - 1];
' W! {( h" d8 `7 g9 v2 Y1 \& j                }
- Q; r6 P. U0 i4 y. \1 s                3 [2 L/ b9 b5 B: S: V& s
                //    根据此时贪吃蛇的方向,设置蛇头的位置            
9 e  s9 B/ ?1 w! `/ ^                switch(GameParamter.direction)      G) H' v/ D9 N8 q# A$ T
                {                  
, }8 l' S7 j) ^; D+ y/ S! D' i' n' y                        case RIGHT:
& \( j* D* o) r9 g* F: v9 _                                        GameParamter.x[0] += DOT_H;
7 h) N' L- ^$ b  I! C5 b# M                                        break;
; e! |! N$ ~; G" n: t9 E                        case LEFT: 6 i* d9 c( A, Q$ z) e' H7 t; c
                                        GameParamter.x[0] -= DOT_H;
7 O7 x4 u/ N7 |& T                                        break;( N2 X" u5 n: `: Q' |
                        case UP:
5 D8 M' V3 u1 U2 k' X                                        GameParamter.y[0] -= DOT_L;+ }2 a4 d# |1 [' T8 ?
                                        break;
' t# K) S3 D, p- P, [! Z                        case DOWN: - G! f  a7 |, G3 R2 j* }. @
                                        GameParamter.y[0] += DOT_L;
$ ^& b: Z+ S8 f6 J3 \+ Z                                        break;
9 Z8 B6 M5 j; }9 P; C3 Y                }
# n: z: m6 ~8 o" k! `6 m5 o        }
$ y/ }% f# R; `}; F( \, w6 [. X" _2 W$ _

  ^) q: |/ d, Z/ }( t# z显示部分随时更新) S9 t4 o2 e9 h) G1 I& x
uint8_t temp;6 R/ `& X* R3 \0 B0 p! I
       
( O: N  C9 p& C- u$ I( R! `7 R        switch(State_Flag)) k! l/ S5 |( u7 F% u
        {1 ~' N9 \/ g% R. Q+ M" N6 o
                case Snake_Move:
! U& r# B; e, Q/ W! h" p* {                        //清除蛇尾部
+ }7 k2 }" p. x8 ?                        for(temp = 0;temp < GameParamter.node;temp++)
. o' l/ M4 f% }& F# H5 m2 b0 b; c                                Oled_Dot(GameParamter.x[temp],GameParamter.y[temp],0);- W* v2 H, l, G5 J% w0 d
                       
3 z8 f" O; }5 D! x- k  E+ I                        //移动蛇* p8 B: ~0 V: D. @# \; d
                        move();
, r% m) {, L2 v+ U5 P! w# l! h                        % [1 }2 r1 k0 F) b% i* m
                        //显示蛇的位置
5 v+ \+ o# n7 _! T                        for(temp = 0;temp < GameParamter.node;temp++)
6 s3 Q9 J3 w7 l1 m8 {" i. D" G                                Oled_Dot(GameParamter.x[temp],GameParamter.y[temp],1);
# ~# q! J7 G( z* l                       
/ ?% S" X( e6 s9 D2 M! c                        //吃到食物,创建新的食物. G1 l$ x( P+ R2 Q% `1 |
                        if(GameParamter.food_x == GameParamter.x[0] && GameParamter.food_y == GameParamter.y[0])
# z1 V& k5 h+ P! @                                Oled_food();" D* H7 M: H* w2 n8 N2 m8 `9 _5 w+ R
                        % G+ R! `$ f3 i% O
                        if(GameParamter.x[0] >= MAP_H || GameParamter.y[0] >= MAP_L)9 u5 P+ d. q3 R" S
            {$ u+ N6 V$ V$ T$ f
                                OLEDClear();
3 z( a3 n# v* G( B) N                                OLED_ShowString(((128 - ((strlen("GAME")*10)))>>1),2,"GAME");
7 G- U) e3 H" x0 B' r                                OLED_ShowString(((128 - ((strlen("OVER")*10)))>>1),5,"OVER");
9 O4 z. W" v* k8 Y6 Z                                State_Flag = Game_Over;8 P5 F2 [% i3 D. v1 m% h  e# R
                                HardwareParamter.OLEDFlash_flag = 1;
! F2 W8 x- Q2 D7 ?                        }
* v# {0 s+ b, G! }                break;# y9 M  Y7 W9 A. r5 c
                case Game_Over:
2 g- x+ X: {# k
& T0 f3 X5 b4 B! A* h. r7 e                break;2 H  w5 }, f3 |1 x' C' L. B
                default:
, h8 \9 S! r8 R+ a                        OLEDClear();3 v% c* D. x. t. n$ u/ {! |
                        State_Flag = Snake_Move;- p$ b/ O. X# y, O8 ?: o
                  HardwareParamter.OLEDFlash_flag = 0;
& u  ~9 S  t1 `: g. k( {                break;* P# U6 d- O6 z6 ]" a
        }
! V4 A$ [5 X- `! |4 }, w& L, H2 L( I3 s# V* f+ W# h* U6 `
8 h8 ]3 V! _2 {4 V: I
外带一个按键处理,解决方向和暂停重置问题。: A4 M" F+ e& v- C2 D, ?6 M

8 R9 r* u9 C3 L$ ~
1.png
N$TC_KA52ICSCU3@IQJ~OBQ.png
赞 收藏 1 评论7 发布时间:2018-2-7 14:16

举报

7个回答
MrJiu 回答时间:2018-2-7 15:17:28
说起贪吃蛇,以前在大学的时候,学完数据结构,然后就在dos下面写了一个极其简陋的!!!
西点钟灵毓秀 回答时间:2018-2-7 22:21:36
可以试试
hello_bug 回答时间:2018-2-8 08:14:23
有点意思。楼主有时间放个视频上来看看。
zero99 回答时间:2018-2-12 14:17:18
感谢分享,已汇总到2月技术原创  https://www.stmcu.org.cn/module/forum/thread-614799-1-1.html
gdyn2 回答时间:2018-8-21 16:22:06
程序下载链接木有?
xuetudou 回答时间:2018-10-10 16:55:48
楼主,想知道蛇头方向改变蛇身呈L或者类似矩形方波这些不规则的情况下蛇的移动部分的代码
Litthins 回答时间:2019-4-17 21:57:54
最近正在捣鼓446,等手头的事情告一段落也想试着搞一个

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版