这个贪吃蛇只实现了基本功能,有一些错误检测未加入,我后边再再补充。
绘图只是自己写了几句代码简单绘图,没有使用LVGL。
按键由于板上只有2个,所以按键用串口控制,有一个上位机,打开上位机后,使用电脑键盘来控制游戏。
串口控制使用了cli,见我的上一个贴子:【平头哥RVB2601创意应用开发】@fxyc87 RVB2601-CLI(命令提示符)测试
先上效果:
上位机软件,使用小众的aardio编写的,这个工具写的单EXE程序非常小。
游戏主代码:
#include <stdlib.h>
#include "oled.h"
#include "ui.h"
#include "snake.h"
#include <aos/aos.h>
#include "aos/cli.h"
//游戏状态
EnumSnakeStatus SnakeStatus;
//蛇体长度
volatile int SnakeLength;
//蛇体数据
Point SnakeData[250] ;
//蛇身移动前尾部,要清除
Point SnakeTail;
//食物上次位置 ,要清除
Point LastFood;
//死亡位置
Point DeathPoint;
//食物位置
Point FoodPoint;
//随机数种子
uint8_t RandFood;
//游戏初始速度 ,
int SnakeSpeed=600;
//每增加一个长度,速度+30mS
const int SnakeSpeedStep=30;
//最近一次按键
EnumMoveDirection LastKey=KeyNone;
//调试模式 此模式下移动受按键控制,不自动走
int SnakeDebug=0;
//cli回调指令结构
const struct cli_command SnakeCommand={
.name="snake",
.help="Direction Operation,Command=Left/Right/Up/Down",
.function=SnakeKey
};
//初始化
void SnakeInit(){
//初始长度1,位置=屏幕中央
SnakeData[0].y=SNAKE_HEIGHT/2;
SnakeData[0].x=SNAKE_WIDTH/2;
SnakeData[1].y=SNAKE_HEIGHT/2;
SnakeData[1].x=SNAKE_WIDTH/2+1;
SnakeLength=2;
//初始cli命令
aos_cli_register_command(&SnakeCommand);
SnakeStatus=None;
DeathPoint.x=0xff;
FoodPoint.x=0xff;
SnakeTail.x=0xff;
//随机产生食物
SnakeFood();
}
//随机产生食物,产生食物不能在蛇体上,需要在空白处
void SnakeFood(){
FoodPoint.x=0xff;
//伪随机数
RandFood+=RandFood*17+3;
//随机数不能大于蛇身总长度
RandFood=RandFood%sizeof(SnakeData);
//查找空白处,查找N次以达到随机效果
printf("food:%d",RandFood);
uint8_t count=0,flag;
for(int x=0;x<SNAKE_WIDTH;x++){
for(int y=0;y<SNAKE_HEIGHT;y++){
flag=0;
for(int i=0;i<=SnakeLength;i++){
if(SnakeData[i].x==x && SnakeData[i].y==y){ //不能在蛇身上
flag=1;
break;
}
}
if(flag==0 && count==RandFood){
FoodPoint.x=x;
FoodPoint.y=y;
}
count++;
}
}
if(FoodPoint.x==0xff){
FoodPoint.x=SNAKE_WIDTH/2;
FoodPoint.y=SNAKE_HEIGHT/2;
}
}
//键盘按下键, cli回调函数
static void SnakeKey(char *outbuf, int len, int argc, char **argv){
if(argc<=1){
//cli_printf("Error:Direction not specified"); //未指定方向
strcpy(outbuf,"Error:Direction not specified"); //未指定方向
return;
}
//strcasecmp strcmp
if(strcasecmp(argv[1],"left") == 0) {
LastKey=Left;
}else if(strcasecmp(argv[1],"right") == 0) {
LastKey=Right;
}else if(strcasecmp(argv[1],"up") == 0) {
LastKey=Up;
}else if(strcasecmp(argv[1],"down") == 0) {
LastKey=Down;
}else if(strcasecmp(argv[1],"pause") == 0) {
SnakeStatus=Pause;
return;
}else if(strcasecmp(argv[1],"debug") == 0) {
SnakeDebug=1;
}else{
strcpy(outbuf,"Invalid direction");
return;
}
SnakeStatus=Run;
}
//移动,4个方向
void SnakeMove(EnumMoveDirection dir){
if(dir==KeyNone)
return;
//保存未移动前的位置
//DeathPoint
//第一个移动,后一个=前一个
int li=SnakeLength-1;
SnakeTail=SnakeData[li]; //保存最后一个,绘图时要清除
for(int i=li;i>0;i--){
SnakeData[i]=SnakeData[i-1];
}
switch(dir){
case Left:
SnakeData[0].x--;
break;
case Right:
SnakeData[0].x++;
break;
case Up:
SnakeData[0].y--;
break;
case Down:
SnakeData[0].y++;
break;
}
if(SnakeDebug)
LastKey=None;
//所有检查是否有超出屏幕的 x<0 x>width y<0 y>heigh
for(int i=0;i<SnakeLength;i++){
if(SnakeData[i].x<0 || SnakeData[i].x>=SNAKE_WIDTH ||
SnakeData[i].y<0 || SnakeData[i].y>=SNAKE_HEIGHT ){
SnakeStatus=Death;
return;
}
}
//检查与自身是否相撞
//吃子,身体变长
if(SnakeData[0].x==FoodPoint.x && SnakeData[0].y==FoodPoint.y){
//todo:检查身体达到极限,显示win画面
SnakeData[SnakeLength]=SnakeTail;
SnakeTail.x=0xff;
SnakeFood();
//检查尾部到达当前食物点,加1
SnakeLength++;
//速度加快
if(SnakeSpeed>=80)
SnakeSpeed-=SnakeSpeedStep;
}
}
//绘制当前游戏,定时调用,游戏速度*4倍速调用
void SnakeDraw(){
static int draw_count=0;
//画蛇身
//蛇身10次画1次
for(int i=0;i<SnakeLength;i++){
SnakeDrawPoint(SnakeData[i].x,SnakeData[i].y,1);
}
//清除上次蛇尾
if(SnakeTail.x!=0xff)
SnakeDrawPoint(SnakeTail.x,SnakeTail.y,0);
//画食物 闪烁
if(draw_count % 2==0){
SnakeDrawFood(FoodPoint.x,FoodPoint.y);
}else{
SnakeDrawPoint(FoodPoint.x,FoodPoint.y,0);
}
//画碰撞(闪烁)
//更新
oled_reflesh();
draw_count++;
if(draw_count>10)
draw_count=0;
}
//画食物
void SnakeDrawFood(uint8_t xx,uint8_t yy){
//4*4 去除4个角点
SnakeDrawPoint(xx,yy,1);
int x=xx*4;
int y=yy*4;
oled_draw_point(y,x,0);
oled_draw_point(y,x+3,0);
oled_draw_point(y+3,x,0);
oled_draw_point(y+3,x+3,0);
}
//画蛇的大点 4像素一个点 ,x,y,c颜色
void SnakeDrawPoint(uint8_t xx,uint8_t yy,uint8_t c){
int x=xx*4;
int y=yy*4;
int temp=y;
int x2=x+SNAKE_SIZE;
int y2=y+SNAKE_SIZE;
for(;x<x2;x++){
for(y=temp;y<y2;y++){
oled_draw_point(y,x,c);
}
}
}
//游戏循环
void SnakeLoop(){
SnakeInit();
while(1){
//触发移动
SnakeMove(LastKey);
//更新 todo:只更新变化部分
SnakeDraw();
if(SnakeDebug)
aos_msleep(100);
else
aos_msleep(SnakeSpeed);
}
}
注释都比较全,不介绍了
在main主程序调用SnakeLoop主循环即可。
int main(void)
{
board_yoc_init();
oled_init();
SnakeLoop();
return 0;
}
以下是源程序:
上位机程序EXE:
上位机程序源码Aardio创建:
import fonts.fontAwesome;
import win.ui;
import sio;
import sys.comPort;
import win.timer;
/*DSG{{*/
mainForm = win.form(text="CH2601_Direction_Operation";right=489;bottom=229)
mainForm.add(
btnOpen={cls="button";text='\uF04B 开始';left=390;top=15;right=475;bottom=65;font=LOGFONT(name='FontAwesome');z=3};
cmbCom={cls="combobox";left=15;top=40;right=375;bottom=65;edge=1;items={};mode="dropdown";z=1};
labDirection={cls="static";text='\uF0E7';left=300;top=100;right=460;bottom=175;font=LOGFONT(h=-35;name='FontAwesome');transparent=1;z=5};
labInfo={cls="static";text="等待打开通信串口";left=15;top=115;right=260;bottom=170;transparent=1;z=7};
labTime={cls="static";left=305;top=200;right=470;bottom=220;transparent=1;z=8};
static={cls="static";text='\uF044 通信串口选择';left=15;top=15;right=115;bottom=35;font=LOGFONT(name='FontAwesome');transparent=1;z=2};
static2={cls="static";text="方向控制说明:
可以通过方向键或WSAD键来控制游戏。
fxyc87 xa87@live.cn";left=15;top=175;right=270;bottom=230;transparent=1;z=4};
static4={cls="static";text="状态:";left=15;top=85;right=75;bottom=110;transparent=1;z=6}
)
/*}}*/
var coms=sys.comPort.list();
for(i,v in coms){
mainForm.cmbCom.add(v.portName + " 【" + v.friendlyName+"】");
}
mainForm.cmbCom.selIndex = sys.comPort.find("CP210x")[["index"]] or 1;
mainForm.labDirection.hide=true;
var port;
var status=0; //0默认 1打开串口未开始 2已开始
mainForm.btnOpen.oncommand = function(id,event){
if(status>0){
mainForm.btnOpen.text='\uF04B 开始';
status=0;
if(port){
port.close();
}
mainForm.labInfo.text="已关闭通信端口";
mainForm.labDirection.hide=true;
mainForm.cmbCom.disabled=false;
return;
}
var sel=string.split(mainForm.cmbCom.selText," ")[1];
port,err=sio.port(sel);
if(port){
port.setBaudRate(115200);
mainForm.labDirection.hide=false;
mainForm.btnOpen.text='\uF2D4 关闭';
mainForm.labInfo.text="可以按任意方向键开始游戏了";
mainForm.cmbCom.disabled=true;
status=1;
mainForm.btnOpen.setFocus();
}else{
mainForm.labInfo.text='通信端口打开失败:\n'+err;
}
}
var timer=win.timer(mainForm,100);
timer.enable();
timer.onTimer = function(){
if(status>0){
mainForm.btnOpen.setFocus();
}
mainForm.labTime.text=tostring(time.now());
}
mainForm.btnOpen.wndproc = function(hwnd,message,wParam,lParam){
if(message==0x0100){
if(wParam==100 || wParam==65){ //\uF060
//mainForm.labDirection.text=tostring(wParam);
port.write('snake left\r\n');
mainForm.labDirection.text='\uF060';
}elseif(wParam==102 || wParam==68){
mainForm.labDirection.text='\uF061';
port.write('snake right\r\n');
}elseif(wParam==104 || wParam==87){
port.write('snake up\r\n');
mainForm.labDirection.text='\uF062';
}elseif(wParam==98 || wParam==83||wParam==101){
port.write('snake down\r\n');
mainForm.labDirection.text='\uF063';
}else{
mainForm.labDirection.text=tostring(wParam);
}
}
}
mainForm.show();
return win.loopMessage()
不错,显示屏用起来了。