Arduino提高篇26—贪吃蛇游戏

在这里插入图片描述

之前文章Processing笔记05—贪吃蛇小游戏分享过如何使用Processing来实现贪吃蛇游戏,很多小伙伴问可不可以来个Arduino版本的,那么本篇就通过Arduino,配合摇杆按键和OLED显示屏来实现贪吃蛇游戏。

1. 实验材料

  • Uno R3开发板
  • 配套USB数据线
  • 面包板及配套连接线
  • 双轴按键摇杆模块
  • OLED显示屏

2. 实验步骤

1. 根据原理图搭建电路图。

OLED屏的VCC、GND分别连接开发板的3.3V、GND,OLED屏的SDA和SCL分别连接开发板的A4和A5。双轴按键摇杆模块的VCC、GND分别连接开发板的5V、GND,模块的X轴输出、Y轴输出分别连接开发板的模拟引脚A0、A1。

实验原理图如下图所示:

实验原理图

实物连接图如下图所示:

实物连接图

2. 由于代码部分较长,这里截取部分展示。完整代码可在文末获取。

...
void snake_move(void)
{
  switch (snake_dir) {
    case RIGHT:
      snake_head_x += 4;
      break;
    case UP:
      snake_head_y -= 4;
      break;
    case LEFT:
      snake_head_x -= 4;
      break;
    case DOWN:
      snake_head_y += 4;
      break;
  }

  if ((snake_head_x == food_x) && (snake_head_y == food_y))
  {
    food_eaten = true; //可重新生成食物
    snake_len++;
    score++;
    level = score / 5 + 1;
    snake_speed -= level;
  }

  for (i = snake_len - 1; i > 0; i--)
  {
    x[i] = x[i - 1];
    y[i] = y[i - 1];
  }
  x[0] = snake_head_x;
  y[0] = snake_head_y;

  check_snake_die();
}
...

3. 连接开发板,设置好对应端口号和开发板类型,进行程序下载。

程序下载

3. 程序分析

1. 蛇身的绘制

贪吃蛇中最主要的部分就是绘制蛇身,实验中使用数组来保存蛇身每个方块的坐标值,使用drawBitmap()函数来绘制一个4X4的矩形表示蛇身方块。

void draw_snake(int x, int y)
{
  oled.drawBitmap(x, y, block, 4, 4, 1);
}

每当蛇头位置发生变化,从蛇尾往前遍历整个坐标数组,将每个方块位置往前移动,蛇头坐标为新位置的坐标,然后重新绘制整个蛇身。

2. 食物的产生

在这里使用了随机函数产生规定区域内的食物坐标。首先在setup()中初始化随机种子:

randomSeed(analogRead(3));//初始化随机种子

然后就可以使用random()生成坐标数据,这里除了需要指定区域外,还需要考虑排除掉生成在蛇身中的食物。

void draw_food(void)
{
  int food_out = 0; //判断食物是否在蛇体内

  if (food_eaten)
  {
    while (food_out == 0)
    {
      food_out = 1;

      food_x = (uint8_t)(random(4, 100) / 4) * 4;
      food_y = (uint8_t)(random(4, 60) / 4) * 4;

      for (int i = snake_len - 1; i > 0; i--) //遍历整个蛇身方块,若食物在蛇身内则重新生成
      {
        if (food_x == x[i] && food_y == y[i])
        {
          food_out = 0;
        }
      }
    }
  }

  food_eaten = false;
}

3. 方向控制

本篇使用的是摇杆模块,通过采集到的模拟量来识别出上下左右的不同操作,然后对应改变蛇头的坐标,当重新对蛇身进行绘制的时候,整个蛇就进行了一次移动。

4. 判断游戏结束

当蛇头坐标超过显示区域,即蛇撞墙,或蛇头位置坐标与蛇身其他方块坐标相同,即自己吃了自己,都会导致游戏结束。

void check_snake_die(void)
{
  //撞墙
  if (snake_head_x < 4 || snake_head_x > 96 || snake_head_y < 1 || snake_head_y > 56)
  {
    game_over = true;
  }

  //自己吃自己
  if (snake_len > 4)
  {
    for (int i = 1; i < snake_len; i++)
    {
      if (snake_head_x == x[i] && snake_head_y == y[i])
      {
        game_over = true;
      }
    }
  }
}

5. 问题及改进

由于按键的判断和屏幕的刷新都放在了loop()中,游戏的速度通过改变delay()函数即改变延时时间来实现,所以当游戏速度过慢时,按键操作就变得不灵敏。可以试着使用定时器来刷新屏幕,通过改变定时时间来改变游戏速度。

另外在撞墙的条件判断中,边界值需要实际调试,没有移动到边界或者移动超出边界都不应结束游戏。

大结局

陆陆续续分享了很多Arduino的教程,包括基础版33篇和提高版26篇,在这个过程中也帮助到了一些小伙伴,其实笔者玩Arduino都是业余兴趣,教程中的很多东西也是通过网络不断学习汇总的。

由于教程配套了实物套件,不得不与套件中的器件进行配套,其实这并不是件容易的事情,需要从一开始就要考虑后面的好几篇内容,完全不能随心所欲的分享。终于,Arduino系列教程到此就结束了,后面分享Arduino会多一些创客DIY之类的。

兴趣是最好的老师,希望小伙伴们除了应对课程设计之类的,也能喜欢上Arduino。愿你所有的坚持,都来自心中的那份热爱。


关注公众号「TonyCode」,后台回复“提高”,获取文中代码。

个人博客

回复「1024」获取1000G学习资料

2.4 贪吃蛇实验/*---------------------------------------------------------------- 320x240彩屏液晶驱动程序 ----------------------------------------------------------------*/ #include"9325tp.h" #include"reg52.h" /*---------------------------------------------------------------- 全局变量 ----------------------------------------------------------------*/ #define WINDOW_XADDR_START 0x0050 // Horizontal Start Address Set #define WINDOW_XADDR_END 0x0051 // Horizontal End Address Set #define WINDOW_YADDR_START 0x0052 // Vertical Start Address Set #define WINDOW_YADDR_END 0x0053 // Vertical End Address Set #define GRAM_XADDR 0x0020 // GRAM Horizontal Address Set #define GRAM_YADDR 0x0021 // GRAM Vertical Address Set #define GRAMWR 0x0022 // memory write #define DataPort P0 //数据口使用DataPort /*---------------------------------------------------------------- 定义硬件端口 ----------------------------------------------------------------*/ sbit CS=P2^2; //片选 sbit RES=P2^1; //复位 sbit RS=P2^4; //数据/命令选择 sbit RW=P2^5; /*---------------------------------------------------------------- 清屏函数 输入参数:bColor 清屏所使用的背景色 ----------------------------------------------------------------*/ void CLR_Screen(unsigned int bColor) { unsigned int i,j; LCD_SetPos(0,240,0,320);//320x240 for (i=0;i<320;i++) { for (j=0;j<240;j++) Write_Data_U16(bColor); } } /*---------------------------------------------------------------- 显示英文字符 输入参数:x 横坐标 y 纵坐标 c 需要显示的字符 fColor 字符颜色 bColor 字符背景颜色 ----------------------------------------------------------------*/ #include "8X16.h" void LCD_PutChar8x16(unsigned short x, unsigned short y, char c, unsigned int fColor, unsigned int bColor) { unsigned int i,j; LCD_SetPos(x,x+8-1,y,y+16-1); for(i=0; i<16;i++) { unsigned char m=Font8x16[c*16+i]; for(j=0;j<8;j++) { if((m&0x80)==0x80) { Write_Data_U16(fColor); } else { Write_Data_U16(bColor); } m<<=1; } } } /*---------------------------------------------------------------- 显示英文字符 输入参数:x 横坐标 y 纵坐标 c 需要显示的字符 fColor 字符颜色 bColor 字符背景颜色 ----------------------------------------------------------------*/ void LCD_PutChar(unsigned short x, unsigned short y, char c, unsigned int fColor, unsigned int bColor) { LCD_PutChar8x16( x, y, c, fColor, bColor ); } /*---------------------------------------------------------------- 显示汉字 输入参数:x 横坐标 y 纵坐标 c 需要显示的汉字码 fColor 字符颜色 bColor 字符背景颜色 ----------------------------------------------------------------*/ #include "GB1616.h" //16*16汉字字模 void PutGB1616(unsigned short x, unsigned short y, unsigned char c[2], unsigned int fColor,unsigned int bColor){ unsigned int i,j,k; LCD_SetPos(x, x+16-1,y, y+16-1); for (k=0;k<64;k++) { //64标示自建汉字库中的个数,循环查询内码 if ((codeGB_16[k].Index[0]==c[0])&&(codeGB_16[k].Index[1]==c[1])){ for(i=0;i<32;i++) { unsigned short m=codeGB_16[k].Msk[i]; for(j=0;j<8;j++) { if((m&0x80)==0x80) { Write_Data_U16(fColor); } else { Write_Data_U16(bColor); } m<<=1; } } } } } /*---------------------------------------------------------------- 显示字符串 可以中英文同时显示 输入参数:x 横坐标 y 纵坐标 *s 需要显示的字符串 fColor 字符颜色 bColor 字符背景颜色 ----------------------------------------------------------------*/ void LCD_PutString(unsigned short x, unsigned short y, unsigned char *s, unsigned int fColor, unsigned int bColor) { unsigned char l=0; while(*s) { if( *s < 0x80) { LCD_PutChar(x+l*8,y,*s,fColor,bColor); s++;l++; } else { PutGB1616(x+l*8,y,(unsigned char*)s,fColor,bColor); s+=2;l+=2; } } } /*---------------------------------------------------------------- 显示RGB颜色 输入参数:x0,y0 起始坐标 x1,y1 结束坐标 Color 背景颜色 ----------------------------------------------------------------*/ /*void Show_RGB (unsigned int x0,unsigned int x1,unsigned int y0,unsigned int y1,unsigned int Color) { unsigned int i,j; LCD_SetPos(x0,x1,y0,y1); for (i=y0;i<=y1;i++) { for (j=x0;j<=x1;j++) Write_Data_U16(Color); } } */ /*---------------------------------------------------------------- 显示图片 图片必须是320x240,否则不能正确识别 ----------------------------------------------------------------*/ /*void show_photo(void) { unsigned char j; unsigned int i; unsigned long s=0; LCD_SetPos(0,240,0,320);//320x240 for (i=0;i<75;i++) { for (j=0;j<240;j++) Write_Data(0xff,0xff); } for (i=0;i<170;i++) { for (j=0;j<55;j++) Write_Data(0xff,0xff); for (j=0;j<130;j++) Write_Data(pic[s++],pic[s++]); for (j=0;j<55;j++) Write_Data(0xff,0xff); } for (i=0;i<75;i++) { for (j=0;j<240;j++) Write_Data(0xff,0xff); } } */ /*---------------------------------------------------------------- 写命令、写数据 输入参数:x 需要输入的命令 16位 y 需要输入的数据 16位 ----------------------------------------------------------------*/ void Write_Cmd_Data (unsigned char x,unsigned int y) { unsigned char m,n; m=y>>8; n=y; Write_Cmd(0x00,x); Write_Data(m,n); } /*---------------------------------------------------------------- 写16位数据 ----------------------------------------------------------------*/ void Write_Data_U16(unsigned int y) { unsigned char m,n; m=y>>8; n=y; Write_Data(m,n); } /*---------------------------------------------------------------- 写命令 ----------------------------------------------------------------*/ void Write_Cmd(unsigned char DH,unsigned char DL) { CS=0; RS=0; DataPort=DH; RW=0; RW=1; DataPort=DL; RW=0; RW=1; CS=1; } /*---------------------------------------------------------------- 写数据 双8位 ----------------------------------------------------------------*/ void Write_Data(unsigned char DH,unsigned char DL) { CS=0; RS=1; DataPort=DH; RW=0; RW=1; DataPort=DL; RW=0; RW=1; CS=1; } /*---------------------------------------------------------------- 延时函数 ----------------------------------------------------------------*/ void delayms(unsigned int count) { int i,j; for(i=0;i<count;i++) { for(j=0;j<260;j++); } } /*---------------------------------------------------------------- 液晶初始化 ----------------------------------------------------------------*/ void ILI9325_Initial(void) { CS=1; delayms(5); RES=0; delayms(5); RES=1; delayms(50); Write_Cmd_Data(0x0001,0x0100); Write_Cmd_Data(0x0002,0x0700); Write_Cmd_Data(0x0003,0x1030); Write_Cmd_Data(0x0004,0x0000); Write_Cmd_Data(0x0008,0x0207); Write_Cmd_Data(0x0009,0x0000); Write_Cmd_Data(0x000A,0x0000); Write_Cmd_Data(0x000C,0x0000); Write_Cmd_Data(0x000D,0x0000); Write_Cmd_Data(0x000F,0x0000); //power on sequence VGHVGL Write_Cmd_Data(0x0010,0x0000); Write_Cmd_Data(0x0011,0x0007); Write_Cmd_Data(0x0012,0x0000); Write_Cmd_Data(0x0013,0x0000); //vgh Write_Cmd_Data(0x0010,0x1290); Write_Cmd_Data(0x0011,0x0227); //delayms(100); //vregiout Write_Cmd_Data(0x0012,0x001d); //0x001b //delayms(100); //vom amplitude Write_Cmd_Data(0x0013,0x1500); //delayms(100); //vom H Write_Cmd_Data(0x0029,0x0018); Write_Cmd_Data(0x002B,0x000D); //gamma Write_Cmd_Data(0x0030,0x0004); Write_Cmd_Data(0x0031,0x0307); Write_Cmd_Data(0x0032,0x0002);// 0006 Write_Cmd_Data(0x0035,0x0206); Write_Cmd_Data(0x0036,0x0408); Write_Cmd_Data(0x0037,0x0507); Write_Cmd_Data(0x0038,0x0204);//0200 Write_Cmd_Data(0x0039,0x0707); Write_Cmd_Data(0x003C,0x0405);// 0504 Write_Cmd_Data(0x003D,0x0F02); //ram Write_Cmd_Data(0x0050,0x0000); Write_Cmd_Data(0x0051,0x00EF); Write_Cmd_Data(0x0052,0x0000); Write_Cmd_Data(0x0053,0x013F); Write_Cmd_Data(0x0060,0xA700); Write_Cmd_Data(0x0061,0x0001); Write_Cmd_Data(0x006A,0x0000); // Write_Cmd_Data(0x0080,0x0000); Write_Cmd_Data(0x0081,0x0000); Write_Cmd_Data(0x0082,0x0000); Write_Cmd_Data(0x0083,0x0000); Write_Cmd_Data(0x0084,0x0000); Write_Cmd_Data(0x0085,0x0000); // Write_Cmd_Data(0x0090,0x0010); Write_Cmd_Data(0x0092,0x0600); Write_Cmd_Data(0x0093,0x0003); Write_Cmd_Data(0x0095,0x0110); Write_Cmd_Data(0x0097,0x0000); Write_Cmd_Data(0x0098,0x0000); Write_Cmd_Data(0x0007,0x0133); // Write_Cmd_Data(0x0022);// } /*---------------------------------------------------------------- 画点 输入参数:x,y 需要画点坐标 color 点的颜色 ----------------------------------------------------------------*/ void Put_pixel(uchar x,uchar y,unsigned int color) { LCD_SetPos(x,x,y,y); Write_Data_U16(color); } /*---------------------------------------------------------------- 设置坐标 ----------------------------------------------------------------*/ static void LCD_SetPos(unsigned int x0,unsigned int x1,unsigned int y0,unsigned int y1) { Write_Cmd_Data(WINDOW_XADDR_START,x0); Write_Cmd_Data(WINDOW_XADDR_END,x1); Write_Cmd_Data(WINDOW_YADDR_START,y0); Write_Cmd_Data(WINDOW_YADDR_END,y1); Write_Cmd_Data(GRAM_XADDR,x0); Write_Cmd_Data(GRAM_YADDR,y0); Write_Cmd (0x00,0x22);//LCD_WriteCMD(GRAMWR); } /*---------------------------------------------------------------- 在屏幕上画线 输入参数: 起始坐标X0,Y0,终止坐标X1,Y1 color 线颜色 ----------------------------------------------------------------*/ void Line( uchar X0, uchar Y0, uchar X1, uchar Y1, unsigned int color) { int dx = X1 - X0; int dy = Y1 - Y0; int P = 2 * dy - dx; int dobDy = 2 * dy; int dobD = 2 * (dy - dx); int PointX = 0,PointY = 0; int incx = 0,incy = 0; int distance = 0,xerr = 0,yerr = 0; unsigned int i = 0; if(dx == 0) //k=1斜率为1 { PointX = X0; if(Y0 < Y1) { PointY = Y0; } else { PointY = Y1; } for(i = 0;i <= ((Y0<Y1) ? (Y1-Y0) : (Y0-Y1));i++) { Put_pixel(PointX,PointY,color); PointY++; } return; } if(dy == 0) //k=0斜率为0 { PointY = Y0; if(X0 < X1) { PointX = X0; } else { PointX = X1; } for(i = 0;i <= ((X0<X1) ? (X1-X0) : (X0-X1));i++) { Put_pixel(PointX,PointY,color); PointX++; } return; } if(dx > 0) incx = 1; else if(dx == 0) incx = 0; else incx = -1; if(dy > 0) incy = 1; else if(dy == 0) incy = 0; else incy = -1; dx = ((X0>X1) ? (X0-X1) : (X1-X0)); dy = ((Y0>Y1) ? (Y0-Y1) : (Y1-Y0)); if(dx>dy) distance=dx; else distance=dy; PointX = X0; PointY = Y0; for(i=0;i<=distance+1;i++) { Put_pixel(PointX,PointY,color); xerr+=dx; yerr+=dy; if(xerr>distance) { xerr-=distance; PointX+=incx; } if(yerr>distance) { yerr-=distance; PointY+=incy; } } } /*--------------------------------------------------------------------------- 绘制矩形框 输入参数:矩形的起始位置left,top 矩形的结束位置right,bottom 矩形框的颜色color -----------------------------------------------------------------------------*/ void Rectangle( uchar left, uchar top, uchar right, uchar bottom, unsigned int color) { Line(left,top,right,top,color); Line(left,top,left,bottom,color); Line(right,top,right,bottom,color); Line(left,bottom,right,bottom,color); } /*--------------------------------------------------------------------------- 绘制平面矩形 输入参数:矩形的起始位置left,top 矩形的结束位置right,bottom 矩形框的颜色color -----------------------------------------------------------------------------*/ void Bar( uchar left, uchar top, uchar right, uchar bottom, unsigned int color) { uchar i = 0,k = 0; for(k = top;k < bottom;k++) { for(i = left+1;i <= right;i++) { LCD_SetPos(i,i,k,k); Write_Data_U16(color); } } } /*--------------------------------------------------------------------------- 向LCD发送一个0--255的数值 -----------------------------------------------------------------------------*/ void LCDShow_uCharNumber( uchar x, uchar y, uchar uCharNumber, unsigned int forecolor, unsigned int bkcolor) { LCD_PutChar(x,y,uCharNumber/100+'0',forecolor,bkcolor); LCD_PutChar(x+8,y,uCharNumber/10+'0',forecolor,bkcolor); LCD_PutChar(x+16,y,uCharNumber+'0',forecolor,bkcolor); }
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值