贪吃蛇课程设计.doc
目录一.绪论21.1 开发背景21.2 开发平台2二功能描述5三基本原理5四系统总体设计64.1 相关初始化64.2 任务设计74.2.1 任务设计要求74.2.2 流程图8五.硬件设计10六.软件设计116.1 移动功能的实现116.2 判断吃否吃到豆子136.3画豆子166.4 声音的播放176.5 数码管的点亮196.6 判断游戏进程206.7 加速,计分,计时功能226.8 监听键盘23七.系统测试247.1 界面247.2 运行结果与不足之处26八.小结27九.参考文献28部分源代码29一.绪论1.1 开发背景 贪吃蛇是一款常见的小游戏,简单有趣,深受人们的喜爱,本项目作为学习软件文档写作和简单游戏编程而提出。希望通过贪吃蛇游戏软件设计开发,了解软件文档的相关标准和编写原则,训练并掌握软件各类文档写作的技巧,同时提高嵌入式设计的能力。1.2 开发平台1.知识储备 嵌入式实时操作系统µC/OS-II简介µC/OS-II是一个抢占式实时多任务内核。它是用ANSI的C语言编写的,包含一小部分汇编语言代码,使之可以提供给不同架构的微处理器使用。至今,从8位到64位,µC/OS-II已经在40多种不同架构的微处理器上使用。使用µC/OS的领域包括:照相机行业、航空业、医疗器械、网络设备、自动提款机以及工业机器人等。 µC/OS-II全部以源代码的方式提供,大约有5500行。CPU相关的部分使用的是针对Intel80x86微处理器的代码。µC/OS-II可以很容易地移植到不同架构的嵌入式微处理器上。µC/OS-II的特点: 源代码 可移植 可固化 可裁减 可抢占性 支持多任务 可确定性 任务栈 系统服务 中断管理 稳定性2.ARM简介采用RISC架构的ARM微处理器一般具有如下特点:l 体积小、低功耗、低成本、高性能;l 支持Thumb(16位)/ARM(32位)双指令集,能很好的兼容8位/16位器件;l 大量使用寄存器,指令执行速度更快;l 大多数数据操作都在寄存器中完成;l 寻址方式灵活简单,执行效率高;l 指令长度固定;l ARM处理器共有37个寄存器,被分为若干个组(BANK),这些寄存器包括:l 31个通用寄存器,包括程序计数器(PC指针),均为32位的寄存器。l 6个状态寄存器,用以标识CPU的工作状态及程序的运行状态,均为32位,目前只使用了其中的一部分。l ARM处理器又有7种不同的处理器模式,在每一种处理器模式下均有一组相应的寄存器与之对应。即在任意一种处理器模式下,可访问的寄存器包括15个通用寄存器(R0R14)、一至二个状态寄存器和程序计数器。在所有的寄存器中,有些是在7种处理器模式下共用的同一个物理寄存器,而有些寄存器则是在不同的处理器模式下有不同的物理寄存器。l ARM微处理器的在较新的体系结构中支持两种指令集:ARM指令集和Thumb指令集。其中,ARM指令为32位的长度,Thumb指令为16位长度。Thumb指令集为ARM指令集的功能子集,但与等价的ARM代码相比较,可节省3040以上的存储空间。二功能描述基本功能:开始蛇向右方移动。按键盘上定义的上下左右键,蛇改变游动的方向,可以上下左右游动。蛇不能碰到图中的灰色栅栏,如碰到游戏结束。若蛇碰到图中一粒豆子,则豆子被蛇吃掉,图中的豆子消失,蛇身变长。最终所有的豆子都被吃掉,游戏结束。增强要求:(1) 必须改进游戏的界面,增加显示相关的统计信息。左边的显示区保持不变,右边动态显示积分和总时间统计信息,其中积分栏目显示当前已经吃下的豆子数目,总时间显示本局游戏从开始到现在经过的时间。(2) 优化主程序,注意CPU和内存的使用效率。(3) 考虑一个合理的得分算法,得分值应该取决于吃下去的豆子和游戏持续的时间。(4) 得分可以在发光二极管上显示出来。三基本原理游戏开始后进入游戏界面.首先初始化蛇的坐标,食物的坐标.线程基本流程:判断是否是暂停阶段,是否有有退出按键,游戏是否有结束,如果都没有就执行, 如果游戏结束了就重新游戏或者退出。开启键盘功能,实现通过方向键来控制蛇的移动方向; 开启数码管功能,实现蛇吃到食物后能够更新和显示分数; 利用变量,实现吃了6个食物后,游戏能够结束,并能通过增加蛇的移动速度来增加游戏的难度。贪吃蛇游戏设计最主要在蛇移动的控制,在设计中用数组来存放蛇身的坐标,用数组boolbean003、boolbean011、boolbean101、boolbean111存放豆子,变量mscore记录得分,并利用数码管显示技术实时显示分数,考虑一个合理的得分算法,得分值应该取决于吃下去的豆子和游戏持续的时间。四系统总体设计4.1 相关初始化1堆栈大小,任务优先级定义OS_STK Main_StackSTACKSIZE*8=0, ; /Main_Test_Task堆栈void Main_Task(void *Id); /Main_Test_Task#define Main_Task_Prio 12OS_STK Led_Flash_StackSTACKSIZE= 0, ; /LED闪烁任务堆栈void Led_Flash_Task(void *Id); /LED闪烁任务#define Led_Flash_Prio 60OS_STK Transmit_Task_StackSTACKSIZE*8=0, ; /Transmit_Task堆栈void Transmit_Task(void *Id); /Transmit_Task#define Transmit_Task_Prio 202.任务的创建OSTaskCreate(Main_Task,(void *)0, (OS_STK *)&Main_StackSTACKSIZE*8-1, Main_Task_Prio);OSTaskCreate(Led_Flash_Task,(void*)0,(OS_STK*)&Led_Flash_StackSTACKSIZE-1, Led_Flash_Prio );OSTaskCreate(Transmit_Task,(void*)0,(OS_STK*)&Transmit_Task_StackSTACKSIZE-1, Transmit_Task_Prio);3.初始化蛇身U8 HeadPos2;U8 EndPos2; 4.初始化豆子u8 bean0032 = 2,11, 18,13, 15,20 ;u8 bean0112 = 10,27 ;u8 bean1012 = 38,3 ;u8 bean1112 = 27,38 ;BOOLEAN boolbean003 = TRUE, TRUE, TRUE;BOOLEAN boolbean011 = TRUE ;BOOLEAN boolbean101 = TRUE ;BOOLEAN boolbean111 = TRUE ;4.2 任务设计4.2.1 任务设计要求本设计需要创建三个任务,,任务一Main_Task和任务二Transmit_Task,任务三Led_Flash_Task,任务三为Led显示任务,这里不做详细解释。只对任务一跟任务二详细说明。在系统启动后,同时创建两个任务,任务一和任务二.任务一主要功能是等待键盘消 息,有键盘消息的时候判断是什么键盘,并对相应的变量重新赋值.任务二主要功能是控制 并在屏幕上显示蛇的移动,并完成对分数和其他相关参数的记录和显示.任务 一为主任务,在创建任务的时候,赋给它的优先级别比任务二高,所以任务一优先运行,任 务二处于就绪状态, 因为任务一主要是等待键盘消息, 在无键盘消息的时候, 任务一被挂起, 这时候任务二进入运行状态.4.2.2 流程图1.程序流程图(不包括任务三)图1程序流程图(不包括任务三)2.任务一(主任务)流程图图2任务一(主任务)流程图3.任务二流程图图3任务二流程图五.硬件设计由于本次课程设计在平台上做开发,所以几乎不会涉及到具体的硬件概要设计、详细设计、制作。但对于所用到的驱动,应有所了解。硬件不同,平台就不同,程序的效果也就不同。六.软件设计6.1 移动功能的实现移动功能的实现:通过ChangePointCount记录改变次数,ChangePointPos102记录每次的坐标,没有移动改变的时候,用绘图函数画原先的图,有移动的时候,先画头部,再利用循环画中间,最后画尾部。实现代码:oldendpos0 = EndPos0;/记录蛇的位置 oldendpos1 = EndPos1;/判断snake头部的变化switch (HeadDirect) /根据所按键蛇头向上向下向左向右移动,由于使用switch语句,横纵坐标只能有一个发生变化,且一次只能向一个方向,所以蛇头向上向下向左向右移动。case 1:HeadPos0 = HeadPos0 + m;/蛇头横坐标移动mbreak;case 2:HeadPos0 = HeadPos0 - m;break;case 3:HeadPos1 = HeadPos1 - m; /蛇头纵坐标移动mbreak;case 4:HeadPos1 = HeadPos1 + m;break;/判断snake尾部的变化if (ChangePointCount > 0) if ( ( abs(ChangePointPosChangePointCount-10-EndPos0)+abs(ChangePointPosChangePointCount-11-EndPos1) = 0) DelChangePoint(); switch(EndDirect)case 1:EndPos0 = EndPos0 + m; /蛇尾横坐标移动mbreak;case 2:EndPos0 = EndPos0 - m;break;case 3:EndPos1 = EndPos1 - m; /蛇尾纵坐标移动mbreak;case 4:EndPos1 = EndPos1 + m;break;/绘制snake/ClearScreen();/Draw3DRect2(pdc, psnakeRect, RGB(0, 0, 0), RGB(0, 0, 0);FillRect2(pdc, pbarRect1, GRAPH_MODE_NORMAL, RGB(0, 255, 255);FillRect2(pdc, pbarRect2, GRAPH_MODE_NORMAL, RGB(0, 255, 255);if (ChangePointCount=0)draw_rect(HeadPos0,HeadPos1, EndPos0, EndPos1);else /不断调用可以动态显示移动过程draw_rect(HeadPos0,HeadPos1,ChangePointPos00, ChangePointPos01);for(i=1; i<ChangePointCount; i+) draw_rect(ChangePointPosi-10,ChangePointPosi-11, ChangePointPosi0,ChangePointPosi1; draw_rect(ChangePointPosi-10,ChangePointPosi-11, EndPos0,EndPos1); OSTimeDly(200); 6.2 判断吃否吃到豆子判断蛇是否吃到食物的方法比较简单,只要判断蛇头的 X,Y 坐标是否同时和食物的坐 标的 X,Y 一样.吃到食物后改变响应的游戏参数,这时候要让变 量addcount 加 1,要注意根据此时蛇的运动方向来确定新蛇头的坐标.另外,要注意,蛇吃完食物后,要把变量boolbean00i, bean010, bean100, bean110 赋值 FALSE, 食物产生子程序能够判断食物已经被蛇"吃到"了,要重新产生食物.代码:u8 bean0032 = 2,11, 18,13, 15,20 ;u8 bean0112 = 10,27 ;u8 bean1012 = 38,3 ;u8 bean1112 = 27,38 ;if (HeadPos0 <= 20)if (HeadPos1 <=20)for (i=0;i<3;i+) if (boolbean00i = TRUE) if ( (bean00i0 = HeadPos0) && (bean00i1 = HeadPos1) ) catch = TRUE; boolbean00i = FALSE; addcount = 2; number+; score(number);mscore=number*2-n*0.01+1;scoresco(mscore); key=mscore;/得到按键值 Delay(1); ZLG7289_ENABLE();/使zlg7289占有同步串口WriteSDIO(ZLG7289_CMD_DATA0|0);/数码管以方式0译码,第一个数码管亮WriteSDIO(key%10);/显示个位Delay(1);/延时if(key>9)/键值大于9显示十位WriteSDIO(ZLG7289_CMD_DATA0|1);/发送十位数据WriteSDIO(unsigned char)(key/10);Delay(1);WriteSDIO(ZLG7289_CMD_HIDE);/使一、二两位数码管显示WriteSDIO(3);else/键值小于10不显示十位WriteSDIO(ZLG7289_CMD_HIDE);/使个位数码管显示WriteSDIO(1);ZLG7289_DISABLE();/zlg7289放弃同步串口控制权 / shine(); return; elseif (boolbean010 = TRUE)if ( (bean0100 = HeadPos0) && (bean0101 = HeadPos1) ) catch = TRUE; boolbean010 = FALSE; addcount = 2; number+; score(number); elseif (HeadPos1 <=20)if (boolbean100 = TRUE) if ( (bean1000 = HeadPos0) && (bean1001 = HeadPos1) ) catch = TRUE; boolbean100 = FALSE; addcount = 2; number+; score(number); elseif (boolbean110 = TRUE) if ( (bean1100 = HeadPos0) && (bean1101 = HeadPos1) ) catch = TRUE; boolbean110 = FALSE; addcount = 2; number+; score(number);6.3画豆子画豆子时,通过判断豆子有没有被吃掉决定要不要画,如果已经被蛇吃掉,就不画,没有吃掉就画,通过数组的值是TRUE还是FALSE来判断。数组的值为TRUE则画圆表示豆子,数组的值为FALSE则不画。FillRect2(pdc, psnakeRect, GRAPH_MODE_NORMAL, RGB(100, 255, 0);/清除主窗口的显示for(i=0;i<3;i+)if (boolbean00i = TRUE)Circle(pdc,20+5*bean00i0-2, 20+5*bean00i1-2, 2);if (boolbean010 = TRUE)Circle(pdc,20+5*bean0100-2, 20+5*bean0101-2, 2);if (boolbean100 = TRUE)Circle(pdc,20+5*bean1000-2, 20+5*bean1001-2, 2);if (boolbean110 = TRUE)Circle(pdc,20+5*bean1100-2, 20+5*bean1101-2, 2);6.4 声音的播放void Song()INT8U err;U32 nbyte;FILE *pfile;char filename="1.wav"/声音的源文件rIISCON=0;/disable;rIISMOD=IISMOD_TX|IISMOD_16BIT|IISMOD_32FS|IISMOD_MCLK_384FS;rIISFCON=IISFCON_TXDMA|IISFCON_TXFIFO;rIISPSR=0x11;rIISCON=IISCON_PRESCALE|IISCON_ENABLE;Init_UDA1341();pfile=OpenOSFile(filename, FILEMODE_READ);if(!pfile)return ;ReadOSFile(pfile, (U8*) buffer, 0x16*2);/读取头文件信息nbyte=ReadOSFile(pfile, (U8*) buffer, sizeof(buffer);/读取波形数据for(;)/* BDMA0 Initialize */for SourcerBDISRC0=(1<<30)+(1<<28)+(int)buffer;/Half word,inc,Buf/for desrBDIDES0=(1<<30)+(3<<28)+(int)0x1d18010);/M2IO,fix,IISFIF/Size/iis,reserve,done_int,not auto-reload/start,DMA enable,COUNTrBDICNT0=(1<<30)+(1<<26)+(3<<22)+(0<<21)+(0<<20)+(sizeof(buffer)&(0x3);rBDICNT0 |= (1<<20);/开启/Enable DMArBDCON0 = 0x0<<2;/Tx DMArIISCON|=IISCON_TXDMA;6.5 数码管的点亮 key=mscore;/得到按键值 Delay(1); ZLG7289_ENABLE();/使zlg7289占有同步串口WriteSDIO(ZLG7289_CMD_DATA0|0);/数码管以方式0译码,第一个数码管亮WriteSDIO(key%10);/显示个位Delay(1);/延时if(key>9)/键值大于9显示十位WriteSDIO(ZLG7289_CMD_DATA0|1);/发送十位数据WriteSDIO(unsigned char)(key/10);Delay(1);WriteSDIO(ZLG7289_CMD_HIDE);/使一、二两位数码管显示WriteSDIO(3);else/键值小于10不显示十位WriteSDIO(ZLG7289_CMD_HIDE);/使个位数码管显示WriteSDIO(1);ZLG7289_DISABLE();/zlg7289放弃同步串口控制权6.6 判断游戏进程判断蛇是否撞到边框的方法,只要比较蛇头的 X,Y 坐标是不是同时和边框的 X,Y 坐标一样,一样的话,即表明蛇撞到边框,游戏结束int i = 0;/if( (HeadPos0>xmax)|(HeadPos0 < xmin )|(HeadPos1 > ymax )|(HeadPos1 < ymin ) )/gameover = TRUE; /return; /_asm cmp HeadPos0, xmax bgt gameend cmp HeadPos0, xmin blt gameend cmp HeadPos1, ymax bgt gameend cmp HeadPos1, ymin bge gamecontinue gameend: mov gameover,#1 gamecontinue: time(n); n+; if (gameover = TRUE) return;if (IsInRect(pbarRect1_5, HeadPos0, HeadPos1) )gameover = TRUE;return;if (IsInRect(pbarRect2_5, HeadPos0, HeadPos1) )gameover = TRUE;return;while(1) if (pause = TRUE) TextOut(pdc,100,115,Game_Paused_Caption_16,TRUE, FONTSIZE_SMALL);/在屏幕上显示pause的提示 Song();OSTimeDly(200);continue;if (gameover = TRUE) TextOut(pdc,100,115,Game_Over_Caption_16,TRUE, FONTSIZE_SMALL); /在屏幕上显示gameover的提示Song();OSTimeDly(200);continue;if (gamepass = TRUE) TextOut(pdc,100,115,Game_Pass_Caption_16,TRUE,FONTSIZE_SMALL); /在屏幕上显示gamepass的提示Song();OSTimeDly(200);continue;6.7 加速,计分,计时功能加速:使用变量m,m一开始的值为1,按键给m赋不同的值 case 1:HeadPos0 = HeadPos0 + m;break;case 2:HeadPos0 = HeadPos0 - m;break;case 3:HeadPos1 = HeadPos1 - m;break;case 4:HeadPos1 = HeadPos1 + m;break;计分:在吃到豆子后,用式子mscore=number*2-n*0.01+1计算分数。void scoresco(int sco) U16 sco_1610; Int2Unicode(sco,sco_16);/ TextOut(pdc,280,115+60,sco_16,TRUE,FONTSIZE_SMALL);计时:根据系统时间计时void time(int tim) U16 tim_1610;Int2Unicode(tim,tim_16); TextOut(pdc,270,115+40,tim_16,TRUE,FONTSIZE_SMALL);6.8 监听键盘监听键盘动作: 任务通过等待消息而处于挂起状态,当任务接到消息以后,则处于就绪状态,然后开始判断所接受到的这个消息是不是需要处理,如果是执行相应的处理函数,最后,删除所接收到的消息,继续挂起等待下一条消息while(1)pMsg = WaitMessage(0);switch (pMsg->Message)case OSM_KEY:onKey(pMsg->WParam, pMsg->LParam);DeleteMessage(pMsg);OSTimeDly(200);七.系统测试7.1 界面1开机界面2中间界面3.游戏进行界面7.2 运行结果与不足之处运行结果:本程序能够完成设计目的的要求:开始蛇向右方移动。按键盘上定义的上下左右键,蛇改变游动的方向,可以上下左右游动。蛇不能碰到图中的灰色栅栏,如碰到游戏结束。若蛇碰到图中一粒豆子,则豆子被蛇吃掉,图中的豆子消失,蛇身变长。最终所有的豆子都被吃掉,游戏结束。1.必须改进游戏的界面,增加显示相关的统计信息。左边的显示区保持不变,右边动态显示积分和总时间统计信息,其中积分栏目显示当前已经吃下的豆子数目,总时间显示本局游戏从开始到现在经过的时间。2.优化主程序,注意CPU和内存的使用效率。3.考虑一个合理的得分算法,得分值应该取决于吃下去的豆子和游戏持续的时间。4.得分可以在发光二极管上显示出来。不足之处:方向按键按下后,要有一定的延迟蛇才能对按键作出反应,这个延迟不是很明显, 但是刚开始游戏的时候, 总感觉不能刚好控制蛇吃到食物, 需要稍微提早一些时间按下按键 才能让蛇延目标方向移动. 这个不足的原理是因为每次按键按下,要等到下一次循环,蛇才能响应方向改变,而蛇 9 控制的程序中每次循环中用到了比较多的循环语句,判断,跳转,每次程序循环所需要的时 间比较长,所以感觉按键有一点延迟,但是这个延迟非常的小,并不明显. 解决方法:要优化蛇控制程序,简化程序过程.八.小结本次课程设计,从一开始的无从下手,到了解一些基本知识,理清头绪,到最后终于完成,我收获很大。在编程时我碰到了很多的困难,在这个时候就需要我多与别人交流.三人行必有我师,也许在一次和别人不经意的谈话中, 就可以迸出灵感的火花. 在编程的过程中我也看到了有良好的编程风格是十分重要的,至少在时间效率上就体现了这一点.养成良好的习惯,代码的缩进编排,变量的命名规则要始终保持一致,这些都是提高我们编程的注意点. 还有在变成中最能体现简单的原则. 所以我要尽量思考讨论简单的程序, 这样简单的方法更容易被人理解,更容易实现,也更容易维护.遇到问题时要优先考虑最简单的方案,只有简单方案不能满足要求时再考虑复杂的方案.通过这次课程设计,不仅掌握了知识,更锻炼我的处理问题的能力,让我在处理问题时,能沉着应对。其实很多问题,看起来很复杂,可是只要你用心做,就能找到解决方案。这次课程设计,我想感谢很多人,要谢谢周老师,周老师大中午的陪着我们在机房调试程序,当我们遇到困难时你不厌其烦的教我们,让我很感动,也要谢谢同学,在我需要帮助的时候,你们利用宝贵的时间帮我。谢谢大家的帮助,在今后的学习中我会更加努力。九.参考文献1 嵌入式系统设计与实例开发基于ARM微处理器与uC/OS-II实时操作系统(第2版) 王田苗 清华大学出版社 2003 年10月2 ARM嵌入式系统基础教程(第2版) 周立功 、王祖麟、陈明计 、严寒亮 、张斌 北京航空航天大学出版社 2008年9月部分源代码int Main(int argc, char *argv)ARMTargetInit(); / do target (uHAL based ARM system) initialisation /OSInit(); / needed by uC/OS-II /uHALr_ResetMMU();LCD_Init(); /初始化LCD模块LCD_printf("LCD initialization is OKn");LCD_printf("240 x 128 Text Moden"); initOSGUI();LoadFont();LoadConfigSys(); / create the tasks in uC/OS and assign increasing / / priorities to them so that Task3 at the end of / / the pipeline has the highest priority. /LCD_printf("Create task on uCOS-II.n");OSTaskCreate(Main_Task, (void *)0, (OS_STK *)&Main_StackSTACKSIZE*8-1, Main_Task_Prio);OSTaskCreate(Led_Flash_Task, (void *)0, (OS_STK *)&Led_Flash_StackSTACKSIZE-1, Led_Flash_Prio );