贪吃蛇

2016-02-291.9万
AI 快速预览详细 收起
这是一个基于Arduino Romeo、XY摇杆和MAX7219 LED矩阵模块的学生自制贪吃蛇游戏项目。游戏支持穿墙功能,亮度可调,最高分记录。通过这个项目,孩子们可以在实际操作中学习编程基础,并体验到成就感。适合STEM教育场景,激发孩子的创造力和逻辑思维。
注:以下是学生做的贪吃蛇,所有都是学生自己研究的,什么都不要问我

功能:
菜单里有三个选项,开始游戏,亮度和最高分。
游戏规则不多说,就是需要提醒一下是可以从墙的一边穿到另一边的。
亮度可调0-15,默认是3,重启不会保存。
最高分同样重启不会保存。
材料:
Arduino Romeo,XY摇杆,MAX7219 LED矩阵模块
接线:
MAX7219:
DIN – 6
CS – 5
CLK – 4
XY摇杆:
X – A0
Y – A1
Z – A2
  1. // 矩阵引脚(数字)
  2. #define CLK  4
  3. #define CS   5
  4. #define DIN  6
  5. // XY摇杆引脚(模拟)
  6. #define X    0
  7. #define Y    1
  8. #define SW   2
  9. #define LEFT  0
  10. #define RIGHT 1
  11. #define UP    2
  12. #define DOWN  3
  13. #define BTN   4
  14. #define PRESS   0
  15. #define RELEASE 1
  16. #define MAXLEN 64
  17. #define MAIN    0
  18. #define GAME    1
  19. #define SCORE   2
  20. #define SETTING 3
  21. #define TOP    4
  22. #define START     0
  23. #define INTENSITY 1
  24. #define HIGHSCORE 2
  25. typedef struct{
  26.   byte x, y;
  27. }position;
  28. // (类似)循环队列操作
  29. void in(position p); // 插入新位置
  30. position out(); // 弹出最末尾的位置
  31. position get(); // 获取第一个位置
  32. position get2(); // 获取第二个位置
  33. byte length(); // 获取当前长度
  34. // 数字,字母位图
  35. const short numbers[10][5] = {
  36.   {7, 5, 5, 5, 7},
  37.   {2, 2, 2, 2, 2},
  38.   {7, 4, 7, 1, 7},   
  39.   {7, 4, 7, 4, 7},
  40.   {5, 5, 7, 4, 4},
  41.   {7, 1, 7, 4, 7},
  42.   {7, 1, 7, 5, 7},
  43.   {7, 4, 4, 4, 4},
  44.   {7, 5, 7, 5, 7},
  45.   {7, 5, 7, 4, 7}
  46. }, chars[26][8] = {
  47.   {4, 6, 9, 9, 9, 15, 9, 9},
  48.   {4, 7, 9, 9, 7, 9, 9, 7},
  49.   {4, 6, 9, 1, 1, 1, 9, 6},
  50.   {4, 7, 9, 9, 9, 9, 9, 7},
  51.   {4, 15, 1, 1, 7, 1, 1, 15},
  52.   {4, 15, 1, 1, 7, 1, 1, 1},
  53.   {4, 6, 9, 1, 13, 9, 9, 6},
  54.   {4, 9, 9, 9, 15, 9, 9, 9},
  55.   {3, 7, 2, 2, 2, 2, 2, 7},
  56.   {4, 8, 8, 8, 8, 9, 9, 6},
  57.   {4, 9, 9, 5, 3, 5, 9, 9},
  58.   {4, 1, 1, 1, 1, 1, 1, 15},
  59.   {4, 9, 15, 15, 9, 9, 9, 9},
  60.   {4, 9, 11, 11, 13, 13, 9, 9},
  61.   {4, 6, 9, 9, 9, 9, 9, 6},
  62.   {4, 7, 9, 9, 7, 1, 1, 1},
  63.   {4, 6, 9, 9, 9, 11, 13, 14},
  64.   {4, 7, 9, 9, 7, 3, 5, 9},
  65.   {4, 6, 9, 1, 6, 8, 9, 6},
  66.   {5, 31, 4, 4, 4, 4, 4, 4},
  67.   {4, 9, 9, 9, 9, 9, 9, 6},
  68.   {4, 9, 9, 9, 9, 6, 6, 6},
  69.   {4, 9, 9, 9, 9, 15, 15, 9},
  70.   {4, 9, 9, 6, 6, 6, 9, 9},
  71.   {5, 17, 17, 10, 4, 4, 4, 4},
  72.   {4, 15, 8, 4, 6, 2, 1, 15}
  73. };
  74. byte disp[8][8], tail, head, direction, buttons = 0, changing, timing[3], rsw = 0, flowleng, part = MAIN, status = START, intensity, highscore = 0;
  75. short valx, valy, flowx;
  76. unsigned long delays[3], times[3], rswt;
  77. position snake[MAXLEN], food;
  78. char* flows;
  79. void in(position p){
  80.   head = (head + 1) % MAXLEN;
  81.   snake[head] = p;
  82. }
  83. position out(){
  84.   position rtn = snake[tail];
  85.   tail = (tail + 1) % MAXLEN;
  86.   return rtn;
  87. }
  88. position get(){
  89.   return snake[head];
  90. }
  91. position get2(){
  92.   if (head) return snake[head - 1];
  93.   return snake[MAXLEN];
  94. }
  95. byte length(){
  96.   if (head >= tail) return head - tail + 1;
  97.   return MAXLEN + head - tail + 1;
  98. }
  99. // 输出数字
  100. void printNum(byte num, short x, short y){
  101.   for(int i = 0; i < 5; i++){
  102.     for(int j = 0; j < 3; j++){
  103.       if (numbers[num][i] & (1 << j)) disp[i + y][j + x] = !0;
  104.       else disp[i + y][j + x] = 0;
  105.     }
  106.   }
  107. }
  108. // 获取字母对应序号
  109. byte getIndex(char c){
  110.   if ((c >= 'a') && (c <= 'z')) return c - 97; // 'a' = 97
  111.   if ((c >= 'A') && (c <= 'Z')) return c - 65; // 'A' = 65
  112.   if (c >= 26) return -1;
  113.   return c;
  114. }
  115. // 输出字母
  116. byte printChar(char c, short x, short y){
  117.   if ((c >= '0') && (c <= '9')){
  118.     printNum(c - 48, x, y); // '0' = 48
  119.     return 3;
  120.   }
  121.   c = getIndex(c);
  122.   if (c == -1) return 0;
  123.   for(int i = 0; i < 7; i++){
  124.     if (i + y > 7) break;
  125.     if (i + y < 0) continue;
  126.     for(int j = 0; j < chars[c][0]; j++){
  127.       if (j + x > 7) break;
  128.       if (j + x < 0) continue;
  129.       if (chars[c][i + 1] & (1 << j)) disp[i + y][j + x] = !0;
  130.       else disp[i + y][j + x] = 0;
  131.     }
  132.   }
  133.   return chars[c][0];
  134. }
  135. // 打印字符串
  136. void printString(char* s, short x, short y){
  137.   for(int i = 0; i < strlen(s); i++){
  138.     x += printChar(s[i], x, y + ((s[i] >= '0') && (s[i] <= '9'))) + 1;
  139.     if (x > 7) return;
  140.   }
  141. }
  142. // 流动显示字符串
  143. void flowString(char* s){
  144.   flowleng = strlen(s);
  145.   for(int i = 0; i < strlen(s); i++){
  146.     char c = getIndex(s[i]);
  147.     if (c != -1) flowleng += chars[c][0];
  148.   }
  149.   flowx = 0;
  150.   flows = s;
  151.   timerContinue(2);
  152. }
  153. // 停止显示流动字符串
  154. void stopFlowing(){
  155.   timerPause(2);
  156. }
  157. // 生成食物
  158. void newFood(){
  159.   byte num = random(64 - length()); // 能生成的位置只有 64 - length() 个
  160.   if ((food.x != get().x) || (food.y != get().y)){ // 初始化时可能排除上一次食物的位置
  161.     disp[food.y][food.x] = 0;
  162.     update(food.y);
  163.   }
  164.   for(int i = 0; i < 8; i++)
  165.     for(int j = 0; j < 8; j++)
  166.       if (!disp[j][i]){ // 身体不能生成食物
  167.         if (num) num--;
  168.         else{
  169.           food.x = i;
  170.           food.y = j;
  171.           disp[food.y][food.x] = 1;
  172.           update(food.y);
  173.           return;
  174.         }
  175.       }
  176. }
  177. // 向矩阵模块输出一个字节的数据
  178. void writeByte(byte data){
  179.   for(int i = 0; i < 8; i++){
  180.     digitalWrite(DIN, (data << i) & 0x80);
  181.     digitalWrite(CLK, LOW);
  182.     digitalWrite(CLK, HIGH);
  183.   }
  184. }
  185. // 修改矩阵模块指定寄存器的值
  186. void writeReg(byte addr, byte data){
  187.   digitalWrite(CS, LOW);
  188.   writeByte(addr);
  189.   writeByte(data);
  190.   digitalWrite(CS, HIGH);
  191. }
  192. // 刷新指定行
  193. void update(byte row){
  194.   byte t = 0;
  195.   for(int i = 0; i < 8; i++) if (disp[row][i]) t |= 128 >> i;
  196.   writeReg(row + 1, t);
  197. }
  198. // 刷新全屏幕
  199. void update(){
  200.   for(int i = 0; i < 8; i++){
  201.     update(i);
  202.   }
  203. }
  204. // 清屏
  205. void clrscr(){
  206.   for(int i = 0; i < 8; i++) for(int j = 0; j < 8; j++) disp[i][j] = 0;
  207. }
  208. // 重绘
  209. void repaint(){
  210.   clrscr();
  211.   for(int i = tail; i != head; i = (i + 1) % MAXLEN) disp[snake[i].x][snake[i].y] = 1;
  212. }
  213. // 初始化地图
  214. void initMap(){
  215.   clrscr();
  216.   for(int i = 0; i < 4; i++){
  217.     disp[3][i + 2] = 1;
  218.     snake[i].x = i + 2;
  219.     snake[i].y = 3;
  220.   }
  221.   tail = 0;
  222.   head = 3;
  223.   direction = 1 << RIGHT;
  224.   newFood();
  225.   update();
  226.   timerContinue(0);
  227.   timerContinue(1);
  228. }
  229. // 获取前进方向
  230. byte getDirection(){
  231.   byte d1 = 255, d2 = 255;
  232.   for(int i = 0; i < 4; i++) if (direction & (1 << i)){
  233.     if (d1 == 255) d1 = i;
  234.     else {
  235.       d2 = i;
  236.       break;
  237.     }
  238.   }
  239.   if (d2 == 255) return d1;
  240.   changing = !changing; // 如果摇杆掰到角落上就两个轮流来(ノへ ̄、)
  241.   if (changing) return d2;
  242.   return d1;
  243. }
  244. // 注册计时器
  245. void timerRegister(byte id, unsigned long time){
  246.   delays[id] = time;
  247.   times[id] = millis();
  248.   timing[id] = 0;
  249. }
  250. // 计时器暂停
  251. void timerPause(byte id){
  252.   timing[id] = 0;
  253. }
  254. // 计时器继续
  255. void timerContinue(byte id){
  256.   times[id] = millis();
  257.   timing[id] = !0;
  258. }
  259. // 返回计时器状态
  260. bool timerTiming(byte id){
  261.   return timing[id];
  262. }
  263. // 计时器事件
  264. void timerAction(byte id){
  265.   if (id == 0){ // 蛇该动了
  266.     byte drtn = getDirection();
  267.     position np = get();
  268.     // 穿墙判定不解释
  269.     if (drtn == LEFT){
  270.       if (np.x) np.x--;
  271.       else np.x = 7;
  272.     } else if (drtn == RIGHT){
  273.       if (np.x == 7) np.x = 0;
  274.       else np.x++;
  275.     } else if (drtn == UP){
  276.       if (np.y) np.y--;
  277.       else np.y = 7;
  278.     } else if (drtn == DOWN){
  279.       if (np.y == 7) np.y = 0;
  280.       else np.y++;
  281.     }
  282.     if ((np.x == food.x) && (np.y == food.y)){ // 吃到东西了
  283.       disp[np.y][np.x] = 1;
  284.       update(np.y);
  285.       in(np);
  286.       newFood();
  287.     } else {
  288.       position last = out();
  289.       disp[last.y][last.x] = 0;
  290.       if (disp[np.y][np.x]){ // 咬到自己了
  291.         byte score = length() - 3;
  292.         timerPause(0);
  293.         timerPause(1);
  294.         part = SCORE;
  295.         if (score > highscore) highscore = score;
  296.         clrscr();
  297.         printNum(score / 10, 0, 1);
  298.         printNum(score % 10, 4, 1);
  299.         update();
  300.       }else{ // 没咬到就挪一下
  301.         in(np);
  302.         disp[np.y][np.x] = !0;
  303.         update(np.y);
  304.         update(last.y);
  305.       }
  306.     }
  307.   } else if (id == 1){ // 食物闪一下
  308.     disp[food.y][food.x] = !disp[food.y][food.x];
  309.     update(food.y);
  310.   } else if (id == 2){ // 字符串挪一下
  311.     clrscr();
  312.     printString(flows, -flowx, 0);
  313.     update();
  314.     flowx++;
  315.     if (flowx == flowleng) flowx = -8;
  316.   }
  317. }
  318. // 按钮按下事件
  319. void buttonPressed(byte id){
  320.   if (part == GAME){
  321.     if (id == BTN){
  322.     } else { // 游戏中变方向,同时还要防止头转180°这种奇葩情况。。
  323.       position s1 = get(), s2 = get2();
  324.       if ((id == LEFT) && ((get().x - get2().x == 1) || (get2().x - get().x == 7))) return;
  325.       if ((id == RIGHT) && ((get2().x - get().x == 1) || (get().x - get2().x == 7))) return;
  326.       if ((id == UP) && ((get().y - get2().y == 1) || (get2().y - get().y == 7))) return;
  327.       if ((id == DOWN) && ((get2().y - get().y == 1) || (get().y - get2().y == 7))) return;
  328.       direction = buttons;
  329.       if (buttons & !(1 << id)) changing = 0;
  330.     }
  331.   }
  332. }
  333. // 按钮释放事件
  334. void buttonReleased(byte id){
  335.   if (part == MAIN){
  336.     if (id == BTN){ // 主菜单选择了什么东西
  337.       stopFlowing();
  338.       if (status == START){
  339.         part = GAME;
  340.         initMap();
  341.       } else if (status == INTENSITY){
  342.         part = SETTING;
  343.         clrscr();
  344.         printNum(intensity / 10, 0, 1);
  345.         printNum(intensity % 10, 4, 1);
  346.         update();
  347.       } else if (status == HIGHSCORE){
  348.         part = TOP;
  349.         clrscr();
  350.         printNum(highscore / 10, 0, 1);
  351.         printNum(highscore % 10, 4, 1);
  352.         update();
  353.       }
  354.     } else if ((id == LEFT) || (id == UP)){ // 换选项,下同  其实这最好用数组,但是只有三个就没写。。
  355.       if (status == START){
  356.         status = HIGHSCORE;
  357.         flowString("highscore");
  358.       } else if (status == INTENSITY){
  359.         status = START;
  360.         flowString("start");
  361.       } else if (status == HIGHSCORE){
  362.         status = INTENSITY;
  363.         flowString("intensity");
  364.       }
  365.     } else {
  366.       if (status == START){
  367.         status = INTENSITY;
  368.         flowString("intensity");
  369.       } else if (status == INTENSITY){
  370.         status = HIGHSCORE;
  371.         flowString("highscore");
  372.       } else if (status == HIGHSCORE){
  373.         status = START;
  374.         flowString("start");
  375.       }
  376.     }
  377.   } else if (part == GAME){
  378.     if (id == BTN){ // 暂停
  379.       if (timerTiming(0)) timerPause(0);
  380.       else timerContinue(0);
  381.     } else {
  382.       if (direction & !(1 << id)) direction &= !(1 << id);
  383.     }
  384.   } else if (part == SCORE){
  385.     if (id == BTN){ // 回主菜单
  386.       part = MAIN;
  387.       flowString("start");
  388.     }
  389.   } else if (part == SETTING){
  390.     if (id == BTN){ // 还是回主菜单
  391.       part = MAIN;
  392.       flowString("intensity");
  393.     } else{ // 改亮度
  394.       if ((id == LEFT) || (id == UP)){
  395.         if (intensity != 0) intensity--;
  396.       } else {
  397.         if (intensity != 15) intensity++;
  398.       }
  399.       writeReg(0xA, intensity);
  400.       printNum(intensity / 10, 0, 1);
  401.       printNum(intensity % 10, 4, 1);
  402.       update();
  403.     }
  404.   } else if (part == TOP){
  405.     if (id == BTN){ // 仍然是回主菜单
  406.       part = MAIN;
  407.       flowString("highscore");
  408.     }
  409.   }
  410. }
  411. void setup() {
  412.   pinMode(CLK, OUTPUT);
  413.   pinMode(CS, OUTPUT);
  414.   pinMode(DIN, OUTPUT);
  415.   intensity = 3;
  416.   writeReg(0x9, 0); // Decode Mode
  417.   writeReg(0xA, intensity); // Intensity
  418.   writeReg(0xB, 7); // Scan Limit
  419.   writeReg(0xC, 1); // Shutdown
  420.   writeReg(0xF, 0); // Display Test
  421.   update();
  422.   timerRegister(0, 1000); // move timer
  423.   timerRegister(1, 200);  // food twinkle
  424.   timerRegister(2, 100);  // flow string
  425.   flowString("start");
  426. }
  427. void loop() {
  428.   // 循环检测计时器
  429.   for(int i = 0; i < 3; i++) if ((timing[i]) && (millis() - times[i] >= delays[i])){
  430.     timerAction(i);
  431.     times[i] += delays[i];
  432.   }
  433.   // 循环检测摇杆
  434.   if (analogRead(X) == 1023){
  435.     if (!(buttons & (1 << RIGHT))){
  436.       buttons |= 1 << RIGHT;
  437.       buttonPressed(RIGHT);
  438.     }
  439.   } else if (analogRead(X) == 0){
  440.     if (!(buttons & (1 << LEFT))){
  441.       buttons |= 1 << LEFT;
  442.       buttonPressed(LEFT);
  443.     }
  444.   } else if (buttons & (1 << RIGHT)){
  445.     if (analogRead(X) < 923){
  446.       buttons &= !(1 << RIGHT);
  447.       buttonReleased(RIGHT);
  448.     }
  449.   } else if (buttons & (1 << LEFT)){
  450.     if (analogRead(X) >= 100){
  451.       buttons &= !(1 << LEFT);
  452.       buttonReleased(LEFT);
  453.     }
  454.   }
  455.   if (analogRead(Y) == 1023){
  456.     if (!(buttons & (1 << UP))){
  457.       buttons |= 1 << UP;
  458.       buttonPressed(UP);
  459.     }
  460.   } else if (analogRead(Y) == 0){
  461.     if (!(buttons & (1 << DOWN))){
  462.       buttons |= 1 << DOWN;
  463.       buttonPressed(DOWN);
  464.     }
  465.   } else if (buttons & (1 << UP)){
  466.     if (analogRead(Y) < 923){
  467.       buttons &= !(1 << UP);
  468.       buttonReleased(UP);
  469.     }
  470.   } else if (buttons & (1 << DOWN)){
  471.     if (analogRead(Y) >= 100){
  472.       buttons &= !(1 << DOWN);
  473.       buttonReleased(DOWN);
  474.     }
  475.   }
  476.   // 循环检测按钮
  477.   if (buttons & (1 << BTN)){
  478.     if (analogRead(SW)){
  479.       if (rsw){
  480.         if (micros() - rswt > 1000){ // 防止按钮按下时出现有时认为没有按下的时候,认为用户连续按下了多次
  481.           buttons &= !(1 << BTN);
  482.           rsw = 0;
  483.           buttonReleased(BTN);
  484.         }
  485.       } else {
  486.         rsw = 1;
  487.         rswt = micros();
  488.       }
  489.     } else rsw = 0;
  490.   } else if (!analogRead(SW)){
  491.     buttons |= 1 << BTN;
  492.     buttonPressed(BTN);
  493.   }
  494. }
复制代码

效果图:
贪吃蛇图1

贪吃蛇图2

创作许可协议

本项目采用 None(不开放任何权利,保留所有权利) 进行许可。

评论(9)
20060606
20060606
好创意,赞一个
Mickey
Mickey
用Arduino UNO + 输入扩展板V2.0 + LCD12864 显示屏扩展板做的话,效果会更好。
丄帝De咗臂
丄帝De咗臂
作者
回复Mickey
谢谢指导
孙毅
孙毅
棒!
iooops
iooops
棒!
iooops
iooops
啊!
孙毅
孙毅
张禄老师是大学老师?!
好腻害的学生!
丄帝De咗臂
丄帝De咗臂
作者
回复孙毅
高中老师,学生比我厉害
源代码
源代码
你的学生好腻害
dsweiliang
dsweiliang
Romeo BLE多合一控制器好用么,我也想入一块
丄帝De咗臂
丄帝De咗臂
作者
回复dsweiliang
果断好用
qq815076543322
qq815076543322
超牛逼的 学生
- 没有更多了 -