我是一个普通二本学校电气的学生。今年大三。
这个东西是我大二的时候做的玩的。
这里面有我做51单片机+12864液晶的贪吃蛇游戏机的详细资料。仿真实物我都做过,效果我自己比较满意。花的精力也比较多,代码也全都是自己一行一行的写下来。所以做完之后想把这些东西都整理一下,然后放在csdn上面,方便后面要做这些东西的人。
open精神在国内是很重要的说···0 0、
联系方式 : 湖北工业大学 08电气2班
环境: keil V4.02 protues version 7.5
我先说下我这个东西最主要的几块。
1.单片机用的是stc的,51内核,你首先要会这个···
2.用状态机实现按键。这个里面有别人写的技术帖子··
3.液晶是12864,2种控制器都做过,一个控制器是ST7920带字库的,一个控制器是KS0108不带字库的。2个的英文资料我都放在里面了。首先得弄懂液晶要怎么样和单片机连接。
要在液晶上面做游戏,推荐先看英文资料,一定要看英文,研究液晶读写时序和初始化方法,然后找别人的程序,实现液晶的基本操作,画点,写汉字什么的,读液晶···然后自己写程序实现液晶的所有基本操作。这样才能对液晶有充分的认识,才能开始在上面做游戏。有了这个基础,以后学别的液晶也会快的多。
4.菜单,游戏少不了要用到菜单,比如选择难度,选择地图什么的。我里面放了一个别人的论文,看了之后应该就会自己写了。(注意论文里面讲的是用函数指针实现菜单,但是KEIL这个环境下用51函数指针会很困难,推荐像我那样直接传菜单号)
5.贪吃蛇游戏,推荐先不要看别人的程序,自己在电脑上面下个贪吃蛇游戏,玩的时候就观察贪吃蛇游戏的特征···比如蛇在怎么动,蛇要怎么描述,是每个点都描述还是只要蛇头蛇尾···蛇转弯的时候怎么写? 对于上面的问题,可以参考别人的程序,然后就开始动手画图什么的表示出来这些过程,一个功能一个功能逐一实现。(推荐看带字库的那个程序,那个程序代码是最终优化版本。思路很清晰···)
6.液晶的动画,动画实际上就是图片一张一张的显示,我在里面放了一个把彩色照片黑白二值化的工具,可以找到自己喜欢的GIF动画,用提取工具把照片一张一张提取出来,然后黑白二值化,然后取模工具取模,然后实现···虽然过程蛮枯燥,但是效果还不过,也可以学到不少东西。
最后希望大家在看这个的时候,特别是贪吃蛇游戏实现的时候,哪一个方面有更好的办法一定通知我,因为我觉得我实现贪吃蛇游戏的方法已经很不错了。如果能有更好的,我会非常希望学习下····
仿真原理图如下(proteus仿真工程文件可到本帖附件中下载)
单片机源程序如下:
- #include <12864.h>
- #include <tcs_resource.h>
- #define num 15
- //用来描述蛇在液晶上面一个点的结构体
- struct she
- {
- unsigned char x,y,value;
- unsigned char direction;
- };
- struct loop_queue //利用循环数组实现循环队列,拐点不会超过15个。所以大小选15足以。
- {
- unsigned int front;
- unsigned int rear;
- unsigned int count;
- struct she dat[num];
- };
- //不熟悉数组实现循环队列的可以在纸上画一下操作过程
- struct she tou,wei,food; //蛇的头尾节点 和食物点
- struct loop_queue queue;//循环队列 蛇转弯则形成拐点 将拐点入队列 尾巴到达拐点则拐点出队列
- unsigned char flag_exit_game; //返回标志 为1时 从贪吃蛇游戏返回菜单
- unsigned char flag_game_over; //结束标志 为1时 显示游戏结束画面 同时使返回标志为1
- unsigned char tcs_stop;//暂停标志
- unsigned char tcs_nd; //难度标志
- unsigned char tcs_dt; //地图标志
- unsigned char tcs_key_flag; //贪吃蛇在一次移动中 只允许捕获一次按键 也就是只能产生一个拐点
- unsigned char tcs_key_state; //用于按键状态机的实现
- unsigned int tcs_df;//得分
- unsigned int tcs_highest_df;//最高得分···没有使用这个 因为不知道要怎么在运行时写单片机的ROM
-
- void tcs_game();
- void tcs_bianjie(unsigned int ); //贪吃蛇边界
- void tcs_game_initial(); //游戏初始化
- void timer0_initial(); //定时器0初始化
- void timer1_initial();
- void food_produce(); //产生食物
- unsigned char test_point_exist(unsigned char x, unsigned char y, unsigned char value);
- void queue_initial(struct loop_queue *q);
- void queue_in(struct loop_queue *q,struct she h) ;
- struct she queue_out(struct loop_queue *q) ;
- void wei_you();
- void wei_xia();
- void wei_zuo();
- void wei_shang();
- void tcs_game()
- {
- struct she check; //用与暂存蛇即将移动的点 从而进行判断
- unsigned char he;
- unsigned char hui,i;
- flag_exit_game=0;
- flag_game_over=0;
- tcs_key_state=0;
- tcs_df=0;
- tcs_stop=1;
- draw_lcd_picture(&tcs_picture[0]); //贪吃蛇游戏画面
- delay_ms(2000);
- clear_lcd();//清屏12864
- tcs_bianjie(tcs_dt); //贪吃蛇游戏边界
- tcs_game_initial();//贪吃蛇初始化 主要是设定蛇头蛇尾 并显示蛇
- timer0_initial(); //定时器0工作在方式2 8位自动装初值 只计数不中断 用来产生随机数
- timer1_initial(); //定时器1工作在方式1 16位 10ms中断一次扫描键盘
- do
- {
- food_produce();//产生一个随机数 即随即产生x y value3个值确定一个点为食物
- he=test_point_exist(food.x,food.y,food.value);//检测产生的点的地方是否已经有点存在
- }
- while(he==food.value);//如果随机数产生在蛇的身体以及边界上 则重新再产生一个
- draw_lcd_point(food.x,food.y,food.value);//显示食物 先读取这个点所在8位的情况 以免破坏现场
-
- queue_initial(&queue);//初始化循环队列 使队列空 front rear 即头尾指针为0
- check.x=0;
- check.y=0;
- check.value=0;
- check.direction=0;
- for(i=0;i<num;i++)
- queue.dat[i]=check;
- while(1)
- {
- if(flag_exit_game==1)//如果游戏过程中按下返回键 则立即结束游戏 返回菜单
- break;
- if(flag_game_over==1)
- break;
-
- ET1=1; //允许扫描按键 贪吃蛇游戏难度决定蛇2次移动之间的间隔时间 也就是检测按键的时间
- tcs_key_flag=0; //允许捕获按键 在检测按键时间内 只允许生产一个拐点 即捕获到一个有效的
- //按键以后 就置一这个变量 不允许再增加拐点
- while(tcs_stop==0);
- switch(tcs_nd)
- {
- case 0: {delay_ms(10); break;}
- case 1: {delay_ms(50); break;}
- case 2: {delay_ms(100); break;}
- }
- ET1=0;
- switch(tou.direction)//这个switch用来根据蛇头的x y value值以及当前蛇头移动的方向
- //取出蛇头要到达的下一个点 存入check中 来判断是否撞墙 吃到食物 什么的···
- {
- case 0: {
- check.x=tou.x;
- if(tou.y==63)
- check.y=0;
- else
- check.y=tou.y+1;
- check.value=tou.value;
- break ;
- }//->向右y+1 其他不变
- case 1: { //↓ 向下y不变
- check.y=tou.y;
- if(tou.value==0x80)// value==0x80 则 x+1 value=0x01
- {
- if(tou.x==7)
- check.x=0;
- else
- check.x=tou.x+1;
- check.value=0x01;
- }
- else
- {
- check.x=tou.x;
- check.value=tou.value<<1;
- }
- break ;
- }
- case 2: {
- check.x=tou.x;
- if(tou.y==0)
- check.y=63;
- else
- check.y=tou.y-1;
- check.value=tou.value;
- break ;
- }//<-向左y-1 其他不变
- case 3: { //↑ 向上y不变
- check.y=tou.y;
- if(tou.value==0x01)// value==0x01 则 x-1 value=0x80
- {
- if(tou.x==0)
- check.x=7;
- else
- check.x=tou.x-1;
- check.value=0x80;
- }
- else
- {
- check.x=tou.x;
- check.value=tou.value>>1;
- }
- break ;
- }
- }
-
- he=test_point_exist(check.x,check.y,check.value); // 检测蛇头移动的下一个点是否几经有点存在
- if(he==check.value) //如果前面的点已经存在 则可以是食物 或者是墙和蛇身
- {
- if( (check.x==food.x) && (check.y==food.y) && (check.value==food.value) )//吃到食物
- {
- tcs_df++;
- cs1=1; cs2=0;
- write_lcd_shuzi(2,32,tcs_shuzi[tcs_df/100] ); //显示分数的百位
- write_lcd_shuzi(2,40,tcs_shuzi[ (tcs_df%100)/10] ); // 十
- write_lcd_shuzi(2,48,tcs_shuzi[tcs_df%10] ); // 个
- cs1=0; cs2=1;
- food.direction=tou.direction;//食物本没有方向 但是为了下面的语句
- tou=food; //食物就成了蛇头···蛇头的方向依然不变
- hui=wei.direction; //先记录下尾巴本来的移动方向
- wei.direction=611;//这次行动尾巴不动 这样蛇身就自然加一了···611代表下面的尾巴不动
- do
- {
- food_produce();
- he=test_point_exist(food.x,food.y,food.value);
- }
- while(he==food.value);
- draw_lcd_point(food.x,food.y,food.value);//上面有说过 这个是产生并显示食物
- }
- else//撞到身体或者墙
- {
- flag_game_over=1; // 游戏结束
- }
- }
- else //什么都么有遇到 继续前进
- {
- check.direction=tou.direction;//这么做的理由同上 为了保持蛇头当前的移动方向
- tou=check;
- draw_lcd_point(tou.x,tou.y,tou.value);//画出此时的蛇头
- }
- //下面是对尾巴的操作
-
- if ( (wei.x == queue.dat[queue.front].x) && ( wei.y==queue.dat[queue.front].y ) && ( wei.value== queue.dat[queue.front].value) ) //如果尾巴到达拐点
- {
- check=queue_out(&queue);
- wei.direction=check.direction; //尾巴按照拐点指示的方向走
- }
- else //如果没有到达拐点 尾巴移动的方向不变
- {
-
- }
-
- switch( wei.direction ) //这个是尾巴的移动 以及尾巴节点数据的修改
- {
- case 0: { wei_you(); break; }
- case 1: { wei_xia(); break; }
- case 2: { wei_zuo(); break; }
- case 3: { wei_shang(); break; }
- case 611: { wei.direction=hui; break;} //这次移动 吃到实物 还原尾巴本来的移动方向
- }
- }
- TR0=0;
- TR1=0;
- ET1=0; //退出游戏时 关闭游戏过程中用到的定时器0和1 然后定时器中断也要关闭
- }
- void timer0_initial()
- {
- TMOD=(0xf0 & TMOD) | 0x01; //定时器0 8位自动装初值(TH0->TL0)定时器模式 用来产生随机数
- TH0=0x00;
- TL0=0x00;
- TR0=1;
- }
- void timer1_initial()
- {
- EA=1;
- TMOD= (0x0f & TMOD) | 0x10 ; //定时器一16位定时器模式 10ms中断一次 用来扫描键盘
- TH1=0xDC;//10ms中断一次
- TL1=0x00;
- TR1=1;
- }
- void timer1() interrupt 3
- {
- unsigned char tou_last_direction; //记录蛇头原来的移动方向
-
- TR1=0;
- TH1=0xDC;
- TL1=0x00;
- switch (tcs_key_state)
- {
- case 0: { // 按键初始态
- if ( (P2 & 0x7f) != 0x7f ) tcs_key_state = 1; // 键被按下,状态转换到键确认态
- else tcs_key_state = 0;
- break;
- }
-
- case 1: { // 按键确认态
- if ( (P2 & 0x7f) != 0x7f)
- {
- // 按键仍按下,此时确定记录一次有效按键
- tcs_key_state = 2; // 状态转换到键释放态
- if(tcs_key_flag==0)
- {
- tou_last_direction=tou.direction;
- switch(P2 & 0x7f)
- {
- case 0x6f: break; // 确定键 不进行任何操作
- case 0x5f: { flag_exit_game=1; break; }//返回键 结束游戏 返回菜单
- case 0x7e: { tou.direction=3;break; }//↑
- case 0x7b: { tou.direction=2;break; }//←
- case 0x7d: { tou.direction=1;break; }//↓
- case 0x77: { tou.direction=0;break; }//->
- case 0x3f: { if(tcs_stop==0) tcs_stop=1; else tcs_stop=0; break;}//暂停键
- }
-
- if( (tou.direction==tou_last_direction) || (tou.direction+tou_last_direction==2) || (tou.direction+tou_last_direction==4) )
- //如果本来就是按此方向移动 或者按键给的方向与蛇移动方向相反 则不做任何操作 此次按键无效
- {
- tou.direction=tou_last_direction; // 还原蛇头的移动方向
- }
- else //否则记录此头结点成为拐点
- {
- queue_in(&queue,tou);
- tcs_key_flag=1; //已经捕获到一次按键信息 这次移动拐点已经产生 关闭捕获键盘
- }
- }
- }
- else
- {
- tcs_key_state = 0; // 按键已抬起,转换到按键初始态
- break;
- }
- }
- case 2: { //等待按键弹起状态
- if ( (P2 & 0x7f) == 0x7f) tcs_key_state=0; //按键已释放,转换到按键初始态
- else tcs_key_state = 2;
- break;
- }
- }
- TR1=1;
- }
- void food_produce()
- {
- unsigned char j1,j2;
- unsigned char code a[]={0x01,0x02,0x04,0x08, 0x10,0x20,0x40,0x80,0x01,0x02,0x04,0x08, 0x10,0x20,0x40,0x80};
- unsigned char code b[]={0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7};
-
- j1 = TL0 & 0x0f; //取TL0的低四位
- food.value = a[j1]; //用0-15的数确定食物的value值
-
- j2 = (TL0 & 0xf0)>>4; //取TL0的高4位
- food.x=b[j2]; //同理确定食物的x值
-
- j2=j1+j2; // 0-30
-
- food.y=j2 +( TL0 & 0x0f ) + ( (TL0 & 0xf0)>>4 ) + 1;//3次0-15的值和一次1-16的值相加,产生1-61的数
- }
- unsigned char test_point_exist(unsigned char x, unsigned char y, unsigned char value)
- {
- unsigned char he;
- set_lcd_xy(x,y);
- he=read_lcd_dat();
- he=read_lcd_dat();
- he=he&value; //如果液晶上这个点是亮的 则he为value 若不亮则为 0
- return (he);
- }
- void tcs_game_initial()
- {
- unsigned char i;
- cs1=0; cs2=1;
- tou.x=0;
- tou.y=5;
- tou.value=0x08; //初始化蛇头
- tou.direction=0; // 初始化蛇头移动方向为向右
- wei.x=0;
- wei.y=1;
- wei.value=0x08; //初始化蛇尾
- wei.direction=0;
-
- for(i=wei.y;i<=tou.y;i++)
- draw_lcd_point(tou.x,i,tou.value);//形成蛇
- }
- void tcs_bianjie(unsigned int he)
- {
- unsigned char i;
- cs1=0; cs2=1;
- set_lcd_xy(0,0);
- for(i=0;i<=63;i++)
- write_lcd_dat(0x01);
- set_lcd_xy(7,0);
- for(i=0;i<=63;i++)
- write_lcd_dat(0x80);
-
- for(i=0;i<=7;i++)
- {
- set_lcd_xy(i,0);
- write_lcd_dat(0xff);
- set_lcd_xy(i,63);
- write_lcd_dat(0xff);
- }
- if(he==2)
- {
-
- unsigned char i,j;
- for(i=0;i<8;i++)
- {
- cs1=0;cs2=1;
- set_lcd_xy(i,0);
- for(j=0;j<64;j++)
- write_lcd_dat(tcs_dt2[i*64+j]);
- }
- }
-
- if(he==3)
- {
- unsigned char i,j;
- for(i=0;i<8;i++)
- {
- cs1=0;cs2=1;
- set_lcd_xy(i,0);
- for(j=0;j<64;j++)
- write_lcd_dat(tcs_dt3[i*64+j]);
- }
- }
- if(he==4)
- {
- unsigned char i,j;
- for(i=0;i<8;i++)
- {
- cs1=0;cs2=1;
- set_lcd_xy(i,0);
- for(j=0;j<64;j++)
- write_lcd_dat(tcs_dt4[i*64+j]);
- }
- }
- cs1=1; cs2=0;
- write_lcd_hanzi(0,0,df[0]);
- write_lcd_hanzi(0,16,df[1]);
- write_lcd_shuzi(2,32,tcs_shuzi[tcs_df/100] ); //显示分数的百位
- write_lcd_shuzi(2,40,tcs_shuzi[ (tcs_df%100)/10] ); // 十
- write_lcd_shuzi(2,48,tcs_shuzi[tcs_df%10] ); // 个
- write_lcd_hanzi(4,0,jb[0]);
- write_lcd_hanzi(4,16,jb[1]);
- switch(tcs_nd)
- {
- case 0 : { write_lcd_hanzi(6,32,tcs_bt[0]); write_lcd_hanzi(6,48,tcs_bt[1]); break;}
- case 1 : { write_lcd_hanzi(6,32,tcs_kn[0]); write_lcd_hanzi(6,48,tcs_kn[1]); break;}
- case 2 : { write_lcd_hanzi(6,32,tcs_jd[0]); write_lcd_hanzi(6,48,tcs_jd[1]); break;}
- }
- }
- void queue_initial(struct loop_queue *q)
- {
- q->front =q->rear=0;
- q->count = 0;
- }
- void queue_in(struct loop_queue *q,struct she h)
- {
- q->dat[q->rear] = h;
- q->rear = (q->rear + 1) % num;
- q->count = q->count + 1;
- }
-
- struct she queue_out(struct loop_queue *q)
- {
- struct she he;
- he = q->dat[q->front];
- q->front = (q->front + 1) % num;
- q->count = q->count - 1;
- return (he);
- }
- void wei_you()
- {
- unsigned char he;
- set_lcd_xy(wei.x,wei.y);
- he=read_lcd_dat();
- he=read_lcd_dat();
-
- set_lcd_xy(wei.x,wei.y);
- write_lcd_dat(he-wei.value); //取当前位置的值,去掉尾巴这个点,然后显示
- if(wei.y==63)
- wei.y=0;
- else
- wei.y++;
- }
- void wei_zuo()
- {
- unsigned char he;
- set_lcd_xy(wei.x,wei.y);
- he=read_lcd_dat();
- he=read_lcd_dat();
-
- set_lcd_xy(wei.x,wei.y);
- write_lcd_dat(he-wei.value); //取当前位置的值,去掉尾巴这个点,然后显示
- if(wei.y==0)
- wei.y=63;
- else
- wei.y--;
- }
- void wei_shang()
- {
- ……………………
- …………限于本文篇幅 余下代码请从51黑下载附件…………
复制代码
所有资料51hei提供下载:
51单片机 12864液晶贪吃蛇游戏机(完美版).rar
(7.79 MB, 下载次数: 1231)
|