C语言代码链表实现贪吃蛇游戏

本文实例为大家分享了C语言链表实现贪吃蛇游戏的具体代码,供大家参考,具体内容如下

贪吃蛇设计思路:

屏幕坐标:

拓展功能:

1.F1,F2控制加速减速  空格暂停游戏  Esc退出

2.加速每个食物得分更高

先打印出游戏界面,还有初始化蛇,蛇的节点用字符串★表示,游戏背景用■表示,因为这些字符串占两个字节的宽度,所以每次x,y坐标的对应关系是x=y*2。在相应位置打印出蛇,初始化蛇为五个节点

初始化蛇头的移动方向为右,根据按键来确定蛇的移动状态,要是吃到食物,就让食物为蛇头,然后随机生成食物。

重点模块:

蛇移动的实现:

定义一个结构体,里面放节点坐标,和next指针。

蛇每次移动是通过用户按键方向来确定下一个蛇头节点的x,y坐标,新建一个节点赋给下一个坐标,在这个坐标打出蛇的图标,找到尾节点,将尾节点打印成背景图标,再将节点释放,这样蛇就动了一下,以此重复,蛇就可以了动态移动。

食物的实现:

定义一个食物节点,也是一个类似于蛇的结构体指针,通过随机数生成坐标,注意不能在墙上和蛇身上。

用户按键的检测:可以使用wasd的字符来确定方向,但这里我们还需要使用空格,ESC键,上下左右键,所以直接一点,通过GetAsyncKeyState( )函数检测用户输入的按键,需要引用头文件conio.h,_getch()函数用来检测当前是否有按键输入,有的话返回非0的数。

蛇的变速:还是通过GetAsyncKeyState( )函数检测F1,F2,键来确定蛇移动一次Sleep()函数中的毫秒数,这样就实现了蛇的加速减速。而且蛇每移动一次,不必使用system(“cls")来清屏,通过在循环中snakemove()执行一次,Sleep()函数执行一次,就实现了动态移动。

实现效果:

完整代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#include<windows.h>
#include<stdlib.h>
#include<conio.h> //接收键盘输入输出

#define U 1
#define D 2
#define L 3
#define R 4  //蛇的状态,U:上 ;D:下;L:左 R:右

/*******定 义 全 局 变 量 *******/
typedef struct snake //蛇身的一个节点
{
 int x;
 int y;
 struct snake *next;
}snake;
int score=0,add=10; //总得分与每次吃食物得分
int status,sleeptime=200; //蛇前进状态,每次运行的时间间隔
snake *head, *food; //蛇头指针,食物指针
snake *q;  //遍历蛇的时候用到的指针
int endgamestatus=0; //游戏结束的情况,1:撞到墙;2:咬到自己;3:主动退出游戏。
HANDLE hOut; //控制台句柄

/*******函 数 声 明 *******/
void gotoxy(int x,int y); //设置光标位置
int color(int c);   //更改文字颜色
void welcometogame();  //开始界面
void createMap();   //绘制地图
void scoreandtips(); //游戏界面右侧的得分和小提示
void initsnake();   //初始化蛇身,画蛇身
void createfood();   //创建并随机出现食物
int biteself();    //判断是否咬到了自己
void cantcrosswall();  //设置蛇撞墙的情况
void speedup(); //加速
void speeddown(); //减速
void snakemove();   //控制蛇前进方向
void keyboardControl();  //控制键盘按键
void Lostdraw();   //游戏结束界面
void endgame();    //游戏结束
void choose(); //游戏失败之后的选择
void explation();   //游戏说明

/**
 * 设置光标位置
 */
void gotoxy(int x,int y)
{
 COORD c;
 c.X=x;
 c.Y=y;
 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),c);
}

/**
 * 文字颜色函数
 */
int color(int c)
{
 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);  //更改文字颜色
 return 0;
}

 //开始界面

void welcometogame()
{
 int n;
 gotoxy(43,10);
 color(11);
 printf("贪 吃 蛇 大 作 战");
 color(12);
 gotoxy(25, 22);
 printf("1.开始游戏");
 gotoxy(45, 22);
 printf("2.游戏说明");
 gotoxy(65, 22);
 printf("3.退出游戏");
 gotoxy(40,27);
 color(3);
 printf("请选择 1 2 3:");
 color(14);
 scanf("%d", &n);  //输入选项
 switch (n)
 {
  case 1:
  system("cls");
 createMap();  //创建地图
  initsnake();  //初始化蛇身
  createfood();  //创建食物
  keyboardControl(); //按键控制
   break;
  case 2:
   explation();  //游戏说明函数
   break;
  case 3:
   exit(0);  //退出游戏
   break;
 default:
 color(12);
 gotoxy(40,28);
 printf("请输入1—3之间的数!");
 _getch(); //输入任意键
 system("cls"); //清屏
 welcometogame();
 }
}

 //创建地图

void createMap()
{
 int i,j;
 for(i=0;i<58;i+=2) //打印上下边框
 {
  gotoxy(i,0);
 color(5);
  printf("□");
  gotoxy(i,26);
  printf("□");
 }
 for(i=1;i<26;i++) //打印左右边框
 {
  gotoxy(0,i);
  printf("□");
  gotoxy(56,i);
  printf("□");
 }
 for(i = 2;i<56;i+=2) //打印中间网格
 {
 for(j = 1;j<26;j++)
 {
 gotoxy(i,j);
 color(3);
 printf("■");
 }
 }
}

 // 游戏界面右侧的得分和小提示

void scoreandtips()
{

 gotoxy(64,8);
 color(14);
 printf("得分:%d ",score);

 gotoxy(64,14);
 printf("每个食物得分:%d分",add);
 gotoxy(64,16);
 printf("不能穿墙,不能咬到自己");
 gotoxy(64,18);
 printf("用↑ ↓ ← →分别控制蛇的移动");
 gotoxy(64,20);
 printf("F1 为加速,F2 为减速");
 gotoxy(64,22);
 printf("space:暂停游戏");
 gotoxy(64,24);
 printf("ESC :退出游戏");
}

 //初始化蛇身,画蛇身

void initsnake()
{
 snake *tail;
 int i;
 tail=(snake*)malloc(sizeof(snake));//从蛇尾开始,头插法,以x,y设定开始的位置
 tail->x=24;   //蛇的初始位置(24,5)
 tail->y=5;
 tail->next=NULL;
 for(i=1;i<=4;i++)  //设置蛇身,长度为5
 {
  head=(snake*)malloc(sizeof(snake)); //初始化蛇头
  head->next=tail;  //蛇头的下一位为蛇尾
  head->x=24+2*i;   //设置蛇头位置
  head->y=5;
  tail=head;    //蛇头变成蛇尾,然后重复循环
 }
 while(tail!=NULL) //从头到尾,输出蛇身
 {
  gotoxy(tail->x,tail->y);
 color(14);
  printf("★");  //输出蛇身,蛇身使用★组成
  tail=tail->next; //蛇头输出完毕,输出蛇头的下一位,一直输出到蛇尾
 }
}

 /**
 * 随机出现食物
 */
void createfood()
{
 snake *food_1;
 srand((unsigned)time(NULL));   //初始化随机数
 food_1=(snake*)malloc(sizeof(snake)); //初始化food_1
 while((food_1->x%2)!=0)  //保证其为偶数,使得食物能与蛇头对其,然后食物会出现在网格线上
 {
  food_1->x=rand()%52+2;    //食物随机出现
 }
 food_1->y=rand()%24+1;
 q=head;
 while(q->next==NULL)
 {
  if(q->x==food_1->x && q->y==food_1->y) //判断蛇身是否与食物重合
  {
   free(food_1);    //如果蛇身和食物重合,那么释放食物指针
   createfood();    //重新创建食物
  }
  q=q->next;
 }
 gotoxy(food_1->x,food_1->y);
 food=food_1;
 color(12);
 printf("●");   //输出食物
}

 /**
 * 判断是否咬到了自己
 */
int biteself()
{
 snake *self;   //定义self为蛇身上的一个节点
 self=head->next;  //self是蛇头之外的蛇身上的节点
 while(self!=NULL)
 {
  if(self->x==head->x && self->y==head->y) //如果self和蛇身上的节点重合
  {
   return 1;  //返回1
  }
  self=self->next;
 }
 return 0;
}

 /**
 * 设置蛇撞墙的情况
 */
void cantcrosswall()
{
 if(head->x==0 || head->x==56 ||head->y==0 || head->y==26) //如果蛇头碰到了墙壁
 {
  endgamestatus=1;  //返回第一种情况
  endgame();    //出现游戏结束界面
 }
}

/**
 * 加速,蛇吃到食物会自动提速,并且按F1会加速
 */
void speedup()
{
 if(sleeptime>=50)
 {
 sleeptime=sleeptime-10;
 add=add+2;

 }
}

/**
 * 加速,按F2会减速
 */
void speeddown()
{
 if(sleeptime<350)    //如果时间间隔小于350
 {
  sleeptime=sleeptime+30;  //时间间隔加上30
  add=add-2;     //每吃一次食物的得分减2

 }
}

/**
 * 控制方向 问题:为什么要设置status,而不使用前两章中接收键盘按键的方法
 */
void snakemove() //蛇前进,上U,下D,左L,右R
{
 snake * nexthead;
 cantcrosswall();
 nexthead=(snake*)malloc(sizeof(snake)); //为下一步开辟空间
 if(status==U)
 {
  nexthead->x=head->x;  //向上前进时,x坐标不动,y坐标-1
  nexthead->y=head->y-1;
  nexthead->next=head;
  head=nexthead;
  q=head;     //指针q指向蛇头
  if(nexthead->x==food->x && nexthead->y==food->y) //如果下一个有食物 下一个位置的坐标和食物的坐标相同
  {

   while(q!=NULL)
   {
    gotoxy(q->x,q->y);
 color(14);
    printf("★");  //原来食物的位置,从●换成★
    q=q->next;   //指针q指向的蛇身的下一位也执行循环里的操作

   }
   score=score+add;  //吃了一个食物,在总分上加上食物的分
 speedup();
   createfood();   //创建食物
  }
  else
  {
   while(q->next->next!=NULL) //如果没遇到食物
   {
    gotoxy(q->x,q->y);
    color(14);
    printf("★");   //蛇正常往前走,输出当前位置的蛇身
    q=q->next;    //继续输出整个蛇身
   }
   gotoxy(q->next->x,q->next->y); //经过上面的循环,q指向蛇尾,蛇尾的下一位,就是蛇走过去的位置
 color(3);
   printf("■");
   free(q->next);  //进行输出■之后,释放指向下一位的指针
   q->next=NULL;  //指针下一位指向空
  }
 }
 if(status==D)
 {
  nexthead->x=head->x;  //向下前进时,x坐标不动,y坐标+1
  nexthead->y=head->y+1;
  nexthead->next=head;
  head=nexthead;
  q=head;
  if(nexthead->x==food->x && nexthead->y==food->y) //有食物
  {

   while(q!=NULL)
   {
    gotoxy(q->x,q->y);
    color(14);
    printf("★");
    q=q->next;
   }
   score=score+add;
 speedup();
   createfood();
  }
  else        //没有食物
  {
   while(q->next->next!=NULL)
   {
    gotoxy(q->x,q->y);
    color(14);
    printf("★");
    q=q->next;
   }
   gotoxy(q->next->x,q->next->y);
 color(3);
   printf("■");
   free(q->next);
   q->next=NULL;
  }
 }
 if(status==L)
 {
  nexthead->x=head->x-2;  //向左前进时,x坐标向左移动-2,y坐标不动
  nexthead->y=head->y;
  nexthead->next=head;
  head=nexthead;
  q=head;
  if(nexthead->x==food->x && nexthead->y==food->y)//有食物
  {
   while(q!=NULL)
   {
    gotoxy(q->x,q->y);
    color(14);
    printf("★");
    q=q->next;
   }
   score=score+add;
 speedup();
   createfood();
  }
  else        //没有食物
  {
   while(q->next->next!=NULL)
   {
    gotoxy(q->x,q->y);
    color(14);
    printf("★");
    q=q->next;
   }
   gotoxy(q->next->x,q->next->y);
 color(3);
   printf("■");
   free(q->next);
   q->next=NULL;
  }
 }
 if(status==R)
 {
  nexthead->x=head->x+2;  //向右前进时,x坐标向右移动+2,y坐标不动
  nexthead->y=head->y;
  nexthead->next=head;
  head=nexthead;
  q=head;
  if(nexthead->x==food->x && nexthead->y==food->y)//有食物
  {
   while(q!=NULL)
   {
    gotoxy(q->x,q->y);
    color(14);
    printf("★");
    q=q->next;
   }
   score=score+add;
 speedup();
   createfood();
  }
  else           //没有食物
  {
   while(q->next->next!=NULL)
   {
    gotoxy(q->x,q->y);
    color(14);
    printf("★");
    q=q->next;
   }
   gotoxy(q->next->x,q->next->y);
 color(3);
   printf("■");
   free(q->next);
   q->next=NULL;
  }
 }
 if(biteself()==1)  //判断是否会咬到自己
 {
  endgamestatus=2;
  endgame();
 }
}

/**
 * 控制键盘按键
 */
void keyboardControl()
{
 status=R;  //初始蛇向右移动
 while(1)
 {
 scoreandtips();
  if(GetAsyncKeyState(VK_UP) && status!=D)   //GetAsyncKeyState函数用来判断函数调用时指定虚拟键的状态
  {
   status=U;   //如果蛇不是向下前进的时候,按上键,执行向上前进操作
  }
  else if(GetAsyncKeyState(VK_DOWN) && status!=U)  //如果蛇不是向上前进的时候,按下键,执行向下前进操作
  {
   status=D;
  }
  else if(GetAsyncKeyState(VK_LEFT)&& status!=R)  //如果蛇不是向右前进的时候,按左键,执行向左前进
  {
   status=L;
  }
  else if(GetAsyncKeyState(VK_RIGHT)&& status!=L)  //如果蛇不是向左前进的时候,按右键,执行向右前进
  {
   status=R;
  }
  if(GetAsyncKeyState(VK_SPACE)) //按暂停键,执行pause暂停函数
  {
   while(1)
 {
 Sleep(300); //sleep()函数,头文件#include <unistd.h> 另进程暂停,知道达到里面设定的参数的时间。
 if(GetAsyncKeyState(VK_SPACE))  //按空格键暂停
 {
  break;
 }

 }
  }
  else if(GetAsyncKeyState(VK_ESCAPE))
  {
   endgamestatus=3; //按esc键,直接到结束界面
   break;
  }
  else if(GetAsyncKeyState(VK_F1)) //按F1键,加速
  {
   speedup();
  }
  else if(GetAsyncKeyState(VK_F2)) //按F2键,减速
  {
   speeddown();

  }
  Sleep(sleeptime);
  snakemove();
 }
}

/*
* 游戏说明
*/
void explation()
{
 //int i,j = 1;
 system("cls");
 // color(13);
 // gotoxy(44,3);
 // printf("游戏说明");
 // color(2);
 // for (i = 6; i <= 22; i++) //输出上下边框===
 //{
 // for (j = 20; j <= 75; j++) //输出左右边框||
 // {
 // gotoxy(j, i);
 // if (i == 6 || i == 22) printf("=");
 // else if (j == 20 || j == 75) printf("||");
 // }
 //}
 color(3);
 gotoxy(30,8);
 printf("1. 不能穿墙,不能咬到自己");
 color(10);
 gotoxy(30,11);
 printf("2. 用↑.↓.←.→分别控制蛇的移动");
 color(14);
 gotoxy(30,14);
 printf("3. F1 为加速,F2 为减速");
 color(11);
 gotoxy(30,17);
 printf("4. 按空格键暂停游戏,再按空格键继续");
 color(4);
 gotoxy(30,20);
 printf("5. ESC :退出游戏.space:暂停游戏");
 _getch();    //按任意键返回主界面
 system("cls");
 welcometogame();
}

/**
 * 结束游戏
 */
void endgame()
{
 system("cls");
 if(endgamestatus==1)
 {

 //Lostdraw();
 gotoxy(43,9);
  color(12);
 printf("GAME OVER !");
 }
 else if(endgamestatus==2)
 {

  //Lostdraw();
  gotoxy(43,9);
  color(12);
  printf("GAME OVER !");
 }
 else if(endgamestatus==3)
 {
 //Lostdraw();
 gotoxy(40,9);
  color(12);
  printf("已结束游戏。");
 }
 gotoxy(43,12);
 color(13);
 printf("你的得分是 %d",score);
 choose();
}

/**
 * 边框下面的分支选项
 */
void choose()
{
 int n;
 gotoxy(25,23);
 color(12);
 printf("Continue ------ 1");
 gotoxy(52,23);
 printf("Exit ------ 2");
 gotoxy(45,25);
 color(11);
 printf("选择: ");
 scanf("%d", &n);
 switch (n)
 {
 case 1:
 system("cls");   //清屏
 score=0;    //分数归零
 sleeptime=200; //设定初始速度
 add = 10; //使add设定为初值,吃一个食物得分10,然后累加
 welcometogame();
 break;
 case 2:
 exit(0);    //退出游戏
 break;
 default:
 gotoxy(35,27);
 color(12);
 printf(" 输入有误 重新输入 !");
 system("pause >nul");
 endgame();
 choose();
 break;
 }

}
/**
 * 失败界面
 */
void Lostdraw()
{
 system("cls");
 int i;
 gotoxy(45,2);
 color(6);
 printf("\\\\\\|///");
 gotoxy(43,3);
 printf("\\\\");
 gotoxy(47,3);
 color(15);
 printf(".-.-");
 gotoxy(54,3);
 color(6);
 printf("//");

 gotoxy(44,4);
 color(14);
 printf("(");

 gotoxy(47,4);
 color(15);
 printf(".@.@");

 gotoxy(54,4);
 color(14);
 printf(")");

 gotoxy(17,5);
 color(11);
 printf("+------------------------");

 gotoxy(35,5);
 color(14);
 printf("oOOo");

 gotoxy(39,5);
 color(11);
 printf("----------");

 gotoxy(48,5);
 color(14);
 printf("(_)");

 gotoxy(51,5);
 color(11);
 printf("----------");

 gotoxy(61,5);
 color(14);
 printf("oOOo");

 gotoxy(65,5);
 color(11);
 printf("-----------------+");

 for(i = 6;i<=19;i++)  //竖边框
 {
 gotoxy(17,i);
 printf("|");
 gotoxy(82,i);
 printf("|");
 }

 gotoxy(17,20);
 printf("+---------------------------------");

 gotoxy(52,20);
 color(14);
 printf("☆☆☆〃");

 gotoxy(60,20);
 color(11);
 printf("----------------------+");

}

/**
* 主函数
*/
int main()
{
 system("mode con cols=100 lines=30"); //设置控制台的宽高
 //printsnake();
 welcometogame();

 keyboardControl();
 endgame();
 return 0;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C语言手把手教你实现贪吃蛇AI(中)

    手把手教你实现贪吃蛇AI,具体内容如下 1. 目标 这一部分主要是讲解编写贪吃蛇AI所需要用到的算法基础. 2. 问题分析 贪吃蛇AI说白了就是寻找一条从蛇头到食物的一条最短路径,同时这条路径需要避开障碍物,这里仅有的障碍就是蛇身.而A star 算法就是专门针对这一个问题的.在A star 算法中需要用到排序算法,这里采用堆排序(当然其他排序也可以),如果对堆排序不熟悉的朋友,请移步到这里--堆排序,先看看堆排序的内容. 3. A*算法 A star(也称A*)搜寻算法俗称A星算法.这是一种在

  • C语言链表实现贪吃蛇游戏

    阅读学习了源代码,并做了简单的注释和修改,里面只用了链表数据结构,非常适合C语言入门者学习阅读. 程序可在VS2013下编译运行. #include<stdio.h> #include<time.h> #include<windows.h> #include<stdlib.h> #define U 1 #define D 2 #define L 3 #define R 4 //蛇的状态,U:上 :D:下:L:左 R:右 typedef struct SNAK

  • 贪吃蛇C语言代码实现(难度可选)

    本文实例为大家分享了C语言实现贪吃蛇的具体代码,供大家参考,具体内容如下 /********************************************************* ********************贪吃蛇(难度可选)******************** **************制作者:Xu Lizi 日期:2012/12/31******** ********************部分函数有借鉴************************ ****

  • C语言结构数组实现贪吃蛇小游戏

    一.设计思路 蛇身本质上就是个结构数组,数组里存储了坐标x.y的值,再通过一个循环把它打印出来,蛇的移动则是不断地刷新重新打印.所以撞墙.咬到自己只是数组x.y值的简单比较. 二.用上的知识点 结构数组Windows API函数 三.具体实现 先来实现静态页面,把地图.初始蛇身.食物搞定. 这里需要用到Windows API的知识,也就是对控制台上坐标的修改 //这段代码来自参考1 void Pos(int x, int y) { COORD pos; HANDLE hOutput; pos.X

  • C语言实现贪吃蛇游戏

    最近整理下电脑,看到了自己在上个学期打的贪吃蛇游戏的c代码,觉得真的是略微有点冗长,但是实现起来应该也算是比较好理解,于是把自己的代码搬上来,网络上写贪吃蛇的c语言的文章很多,我这篇也仅是给大家作为一个参考而已. 我的代码是在Windows下运行的,因为需要用到windows.h这个库. 然后也做了一个简单的ai模式,这在没有障碍物的情况下前期还是蛮不错的,但是到了后期蛇变长了之后就会有bug了. 好了,直接上代码吧: 1)头文件和宏定义 #include<stdio.h> #include&

  • C语言手把手教你实现贪吃蛇AI(下)

    本文实例为大家分享了C语言实现贪吃蛇AI的具体代码,供大家参考,具体内容如下 1. 目标 这一部分的目标是把之前写的贪吃蛇加入AI功能,即自动的去寻找食物并吃掉. 2. 控制策略 为了保证蛇不会走入"死地",所以蛇每前进一步都需要检查,移动到新的位置后,能否找到走到蛇尾的路径,如果可以,才可以走到新的位置:否则在当前的位置寻找走到蛇尾的路径,并按照路径向前走一步,开始循环之前的操作,如下图所示.这个策略可以工作,但是并不高效,也可以尝试其他的控制策略,比如易水寒的贪吃蛇AI 运行效果如

  • C语言贪吃蛇经典小游戏

    一.贪吃蛇小游戏简介: 用上下左右控制蛇的方向,寻找吃的东西,每吃一口就能得到一定的积分,而且蛇的身子会越吃越长,身子越长玩的难度就越大,不能碰墙,也不能咬到自己的身体,等到了一定的分数,就能过关. 二.函数框架 三.数据结构 typedef struct Snake { size_t x; //行 size_t y; //列 struct Snake* next; }Snake, *pSnake; 定义蛇的结构体,利用单链表来表示蛇,每个结点为蛇身体的一部分. 四.代码实现(vs2010  c

  • 基于C语言实现的贪吃蛇游戏完整实例代码

    本文以实例的形式讲述了基于C语言实现的贪吃蛇游戏代码,这是一个比较常见的游戏,代码备有比较详细的注释,对于读者理解有一定的帮助. 贪吃蛇完整实现代码如下: #include <graphics.h> #include <conio.h> #include <stdlib.h> #include <dos.h> #define NULL 0 #define UP 18432 #define DOWN 20480 #define LEFT 19200 #defi

  • 70行C语言代码实现贪吃蛇

    本文实例为大家分享了C语言实现贪吃蛇的具体代码,供大家参考,具体内容如下 #include <stdio.h> #include <Windows.h> #include <conio.h> #include <time.h> #define MAX_WIDE 50 #define MAX_HIGH 16 short dx = 1, dy = 0, randxy, score = 0; COORD coord; struct Snake{ short len

  • C语言手把手教你实现贪吃蛇AI(上)

    本文实例为大家分享了手把手教你实现贪吃蛇AI的具体步骤,供大家参考,具体内容如下 1. 目标 编写一个贪吃蛇AI,也就是自动绕过障碍,去寻找最优路径吃食物. 2. 问题分析 为了达到这一目的,其实很容易,总共只需要两步,第一步抓一条蛇,第二步给蛇装一个脑子.具体来说就是,首先我们需要有一条普通的贪吃蛇,也就是我们常玩儿的,手动控制去吃食物的贪吃蛇:然后给这条蛇加入AI,也就是通过算法控制,告诉蛇怎么最方便的绕开障碍去吃食物.为了讲清楚这个问题,文章将分为三部分:上,写一个贪吃蛇程序:中,算法基础

随机推荐