用C写了一个贪吃蛇

qq_38347379 2017-04-19 10:06:48
前几天闲着无聊想着能不能用C语言写个贪吃蛇,在这想法下查了一些博客和帖子有了思路后,由于是第一次写,写了3天多才写完。
先贴源码:

下面是gb.h头文件内容
void HideCursor()//隐藏光标
{
CONSOLE_CURSOR_INFO cursor_info={1,0};// 后边的0代表光标不可见
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cursor_info);
}
void GotoXy(int x,int y)//光标移动
{
HANDLE hout; //定义句柄变量hout
COORD coord; //定义结构体coord
coord.X=x;
coord.Y=y;
hout=GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出(屏幕)句柄
SetConsoleCursorPosition(hout,coord); //移动光标
}



下面为源代码:
#include "stdio.h"
#include "stdlib.h"
#include "windows.h"
#include "gb.h"
#include "conio.h"
#include "time.h"
#define up 72
#define down 80
#define left 75
#define right 77 //方向键对应ASCII码
#define si 19
#define sj 39 //游戏区域大小
#define foodnum 10 //食物的数量
int live=1,score=0; //蛇的状态,1为活,0为死
typedef struct node//蛇结点
{
int x;
int y;
int fx; //运动方向
struct node *next;
}snake;
typedef struct f//食物
{
int x;
int y;
}food;
void cshs(snake *she)//蛇初始化
{
she->x=10;
she->y=10;
she->fx=up;
she->next=NULL;
}
void fz(int s[si][sj],snake *she)//为游戏区域、蛇赋值
{

int i,j;
for(i=0;i<si;i++)
{
for(j=0;j<sj;j++)
{
if(s[i][j]!=2)
{
if(i==0||i==si-1||j==0||j==sj-1||(j==she->x&&i==she->y))
s[i][j]=1;
else
s[i][j]=0;
}
else continue;
}
}
while(she!=NULL)
{
s[she->y][she->x]=1;
she=she->next;
}
}
void sc(int s[si][sj],int t)//输出整个区域
{
int i,j;
GotoXy(0,0); //将光标移动至坐标(0,0)
for(i=0;i<si;i++)
{
for(j=0;j<sj;j++)
{
if(s[i][j]==0)printf(" ");
if(s[i][j]==1)printf("0");
if(s[i][j]==2)printf("+");
}
printf("\n");
}
printf("000000000000000\n");
printf("0 分数: %d\t\t 0\n",score);
if(t==1)printf("0 难度: 慢\t\t 0\n");
if(t!=1&&t!=3)printf("0 难度: \t\t 0\n");
if(t==3)printf("0 难度: 极快\t\t 0\n");
printf("0 提示:按除方向键的键暂停! 0\n");
printf("00000000000000000000");
HideCursor();
}
void csfood(int s[si][sj],food *f1)
{
while(1)
{
f1->y=rand()%(si-3)+1;
f1->x=rand()%(sj-3)+1; //产生随机坐标
if(s[f1->y][f1->x]==0) //让食物产生在空白地方
break;
else continue;
}
s[f1->y][f1->x]=2;
}
snake *move(snake *she,int s[si][sj]) //蛇的移动
{
char ch1,ch2; //接受按键
food *f1;
snake *t=NULL,*newh,*p;
f1=(food *)malloc(sizeof(food *));
newh=(snake *)malloc(sizeof(snake *));
//使蛇无法后退
ch1=getch();
ch2=getch(); //ch2即第二次返回的才是方向键的ASCII值
if(((she->fx==up)&&(ch2==up||ch2==left||ch2==right)))goto a;
if(((she->fx==down)&&(ch2==down||ch2==left||ch2==right)))goto a;
if(((she->fx==left)&&(ch2==up||ch2==left||ch2==down)))goto a;
if(((she->fx==right)&&(ch2==up||ch2==down||ch2==right)))goto a;
else return she;
a:;
newh->x=she->x;
newh->y=she->y;
newh->fx=she->fx;
newh->next=she; //新头接到旧头前面
she=newh;
switch(ch2)
{
case up: if(she->y>1&&(she->fx!=down))
{ she->fx=up;she->y--;}break;
case down: if(she->y<(si-2)&&(she->fx!=up))
{ she->fx=down;she->y++;}break;
case left:if(she->x>1&&(she->fx!=right))
{ she->fx=left;she->x--;}break;
case right: if(she->x<(sj-2)&&(she->fx!=left))
{ she->fx=right;she->x++;}break;
}
if(s[she->y][she->x]==2)
{
csfood(s,f1);
score++;
}
else if(s[she->y][she->x]==1)
{
live=0;
}
else
{
p=she; //删除最后一个结点
while(p->next->next!=NULL)
p=p->next;
t=p->next;
p->next=NULL;
//free(t);
}
return she;
}
snake *zidong(snake *she,int s[si][sj])//使蛇每时每刻都在移动
{
snake *newh,*p,*t=NULL;
food *f1;
newh=(snake *)malloc(sizeof(snake *));
f1=(food *)malloc(sizeof(food *));
newh->x=she->x;
newh->y=she->y;
newh->fx=she->fx;
newh->next=she; //新头接到旧头前面
she=newh;
switch(she->fx)
{
case up:
she->fx=up;she->y--;break;
case down:
she->fx=down;she->y++;break;
case left:
she->fx=left;she->x--;break;
case right:
she->fx=right;she->x++;break;
}
if(s[she->y][she->x]==2)
{
csfood(s,f1);
score++;
}
else if(s[she->y][she->x]==1)
{
live=0;
}
else
{
p=she; //删除最后一个结点
while(p->next->next!=NULL)
p=p->next;
t=p->next;
p->next=NULL;
//free(t);
}
return she;
}
void main()
{
snake *she; //蛇
food *f1; //食物
int s[si][sj],i,j,k,gtime,t; //k为游戏菜单选择变量
begin:;
system("cls");
printf("请选择难度:1----慢 2----正常 3----极快 ");
fflush(stdin);
scanf("%d",&t);
switch(t)
{
case 1:gtime=1000;break;
case 2:gtime=100;break;
case 3:gtime=0;break;
default:gtime=100;break;
}
live=1;
srand(time(NULL)); //随机随机数种子
for(i=0;i<si;i++)
for(j=0;j<sj;j++)
s[i][j]=0; //初始化
she=(snake *)malloc(sizeof(snake *));
f1=(food *)malloc(sizeof(food *));
cshs(she); //初始化蛇的位置
fz(s,she);
for(i=0;i<foodnum;i++) //为游戏区域、蛇赋值
csfood(s,f1);
sc(s,t);
while(1)
{
while(1)//使蛇每时每刻都在移动
{
if(kbhit())
break;
else
Sleep(gtime);
she=zidong(she,s);
fz(s,she);
sc(s,t);
if(live==0)goto gameover;
}
she=move(she,s);
fz(s,she);
sc(s,t);
if(live==0)break;
}
gameover:;
GotoXy(sj/2,si/2);
printf("游戏结束!");
GotoXy(sj/2,si/2+1);
printf("请选择:1---重新开始 2---退出游戏");
while(1)
{
k=getch();
if(k==49||k==50)
{
switch(k)
{
case 49:goto begin;
case 50:goto end;
}
}
}
end:;
}


由于不能发黑方块图案,可自行将图案0改为黑方块,1改为圆黑块。

主要思路就是利用二维数组去开辟一个游戏区域,然后利用单链表的性质来实现蛇的特征,每次移动就是单链表的添头删尾,然后利用GotoXy()函数代替system("cls")的刷新,使得游戏不会不断闪屏,而食物则采用伪随机数进行随机生成,但在最后蛇的自动移动问题,查了挺多帖子都说要用多线程,可惜我并未接触多线程概念,本想着放弃,却突然发现有人说可以用kbhit()和Sleep()函数实现,于是查了一下kbhit()函数用法,最后终于完整地做了出来。

问题:1、在单链表删除最后一个结点时用free()函数会出错(使用了malloc()分配后再free()也没用)。
2、将此代码生成的可执行文件发给朋友玩,朋友告知卡问题,可在本机上玩却很流畅。
以上两个问题可有大神解答一二。
...全文
637 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
qq_38347379 2017-04-30
  • 打赏
  • 举报
回复
引用 10楼赵4老师 的回复:
仅供参考:
#include <conio.h>
#include <windows.h>

void ConPrintAt(int x, int y, char *CharBuffer, int len)
{
   DWORD count;
   COORD coord = {x, y};
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
   SetConsoleCursorPosition(hStdOut, coord);
   WriteConsole(hStdOut, CharBuffer, len, &count, NULL);
}
void HideTheCursor()
{
   CONSOLE_CURSOR_INFO cciCursor;
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

   if(GetConsoleCursorInfo(hStdOut, &cciCursor))
   {
      cciCursor.bVisible = FALSE;
      SetConsoleCursorInfo(hStdOut, &cciCursor);
   }
}

void ShowTheCursor()
{
   CONSOLE_CURSOR_INFO cciCursor;
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

   if(GetConsoleCursorInfo(hStdOut, &cciCursor))
   {
      cciCursor.bVisible = TRUE;
      SetConsoleCursorInfo(hStdOut, &cciCursor);
   }
}
void GetWH(int *w,int *h) {
    HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    if (GetConsoleScreenBufferInfo(hStdOut, &csbi)) {
        *w=csbi.srWindow.Right;
        *h=csbi.srWindow.Bottom;
    } else {
        *w=80;
        *h=25;
    }
}
void ClearConsole()
{
   //Get the handle to the current output buffer...
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
   //This is used to reset the carat/cursor to the top left.
   COORD coord = {0, 0};
   //A return value... indicating how many chars were written
   //   not used but we need to capture this since it will be
   //   written anyway (passing NULL causes an access violation).
   DWORD count;
   //This is a structure containing all of the console info
   // it is used here to find the size of the console.
   CONSOLE_SCREEN_BUFFER_INFO csbi;
   //Here we will set the current color
   if(GetConsoleScreenBufferInfo(hStdOut, &csbi))
   {
      //This fills the buffer with a given character (in this case 32=space).
      FillConsoleOutputCharacter(hStdOut, (TCHAR) 32, csbi.dwSize.X * csbi.dwSize.Y, coord, &count);
      FillConsoleOutputAttribute(hStdOut, csbi.wAttributes, csbi.dwSize.X * csbi.dwSize.Y, coord, &count);
      //This will set our cursor position for the next print statement.
      SetConsoleCursorPosition(hStdOut, coord);
   }
}

int main() {
    unsigned short k;
    int x,y,w,h;
    char d;

    SetConsoleOutputCP(437);
    ClearConsole();
    GetWH(&w,&h);
    x=w/2;y=h/2;
    HideTheCursor();
    ConPrintAt(x,y,"O",1);
    d='o';
    while (1) {
        Sleep(50);
        if (kbhit()) {
            k=getch();
            if (27==k) break;//按Esc键退出
            if (0==k||0xe0==k) k|=getch()<<8;//非字符键
            switch (k) {
                case 0x48e0:case 0x04800://上
                    d='u';
                    if (y>0) {
                        ConPrintAt(x,y," ",1);
                        y--;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 0x50e0:case 0x05000://下
                    d='d';
                    if (y<h) {
                        ConPrintAt(x,y," ",1);
                        y++;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 0x4be0:case 0x04b00://左
                    d='l';
                    if (x>0) {
                        ConPrintAt(x,y," ",1);
                        x--;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 0x4de0:case 0x04d00://右
                    d='r';
                    if (x<w-1) {
                        ConPrintAt(x,y," ",1);
                        x++;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
            }
//          cprintf("%04x pressed.\r\n",k);
        } else {
            switch (d) {
                case 'u':
                    if (y>0) {
                        ConPrintAt(x,y," ",1);
                        y--;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 'd':
                    if (y<h) {
                        ConPrintAt(x,y," ",1);
                        y++;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 'l':
                    if (x>0) {
                        ConPrintAt(x,y," ",1);
                        x--;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 'r':
                    if (x<w-1) {
                        ConPrintAt(x,y," ",1);
                        x++;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
            }
        }
    }
    ClearConsole();
    ShowTheCursor();
    return 0;
}
这..
qq_38347379 2017-04-30
  • 打赏
  • 举报
回复
引用 9楼会飞的爱吃薄荷的猪 的回复:
很好呀,继续努力。
我完了一会,最后结束的时候是一直往上走的,但蛇的身体不是直的,而且右边界好像有问题。
把图案修改一下 黑方块和黑圆块 应该是发上来时我改图案时出错了
赵4老师 2017-04-26
  • 打赏
  • 举报
回复
仅供参考:
#include <conio.h>
#include <windows.h>

void ConPrintAt(int x, int y, char *CharBuffer, int len)
{
   DWORD count;
   COORD coord = {x, y};
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
   SetConsoleCursorPosition(hStdOut, coord);
   WriteConsole(hStdOut, CharBuffer, len, &count, NULL);
}
void HideTheCursor()
{
   CONSOLE_CURSOR_INFO cciCursor;
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

   if(GetConsoleCursorInfo(hStdOut, &cciCursor))
   {
      cciCursor.bVisible = FALSE;
      SetConsoleCursorInfo(hStdOut, &cciCursor);
   }
}

void ShowTheCursor()
{
   CONSOLE_CURSOR_INFO cciCursor;
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

   if(GetConsoleCursorInfo(hStdOut, &cciCursor))
   {
      cciCursor.bVisible = TRUE;
      SetConsoleCursorInfo(hStdOut, &cciCursor);
   }
}
void GetWH(int *w,int *h) {
    HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    if (GetConsoleScreenBufferInfo(hStdOut, &csbi)) {
        *w=csbi.srWindow.Right;
        *h=csbi.srWindow.Bottom;
    } else {
        *w=80;
        *h=25;
    }
}
void ClearConsole()
{
   //Get the handle to the current output buffer...
   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
   //This is used to reset the carat/cursor to the top left.
   COORD coord = {0, 0};
   //A return value... indicating how many chars were written
   //   not used but we need to capture this since it will be
   //   written anyway (passing NULL causes an access violation).
   DWORD count;
   //This is a structure containing all of the console info
   // it is used here to find the size of the console.
   CONSOLE_SCREEN_BUFFER_INFO csbi;
   //Here we will set the current color
   if(GetConsoleScreenBufferInfo(hStdOut, &csbi))
   {
      //This fills the buffer with a given character (in this case 32=space).
      FillConsoleOutputCharacter(hStdOut, (TCHAR) 32, csbi.dwSize.X * csbi.dwSize.Y, coord, &count);
      FillConsoleOutputAttribute(hStdOut, csbi.wAttributes, csbi.dwSize.X * csbi.dwSize.Y, coord, &count);
      //This will set our cursor position for the next print statement.
      SetConsoleCursorPosition(hStdOut, coord);
   }
}

int main() {
    unsigned short k;
    int x,y,w,h;
    char d;

    SetConsoleOutputCP(437);
    ClearConsole();
    GetWH(&w,&h);
    x=w/2;y=h/2;
    HideTheCursor();
    ConPrintAt(x,y,"O",1);
    d='o';
    while (1) {
        Sleep(50);
        if (kbhit()) {
            k=getch();
            if (27==k) break;//按Esc键退出
            if (0==k||0xe0==k) k|=getch()<<8;//非字符键
            switch (k) {
                case 0x48e0:case 0x04800://上
                    d='u';
                    if (y>0) {
                        ConPrintAt(x,y," ",1);
                        y--;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 0x50e0:case 0x05000://下
                    d='d';
                    if (y<h) {
                        ConPrintAt(x,y," ",1);
                        y++;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 0x4be0:case 0x04b00://左
                    d='l';
                    if (x>0) {
                        ConPrintAt(x,y," ",1);
                        x--;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 0x4de0:case 0x04d00://右
                    d='r';
                    if (x<w-1) {
                        ConPrintAt(x,y," ",1);
                        x++;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
            }
//          cprintf("%04x pressed.\r\n",k);
        } else {
            switch (d) {
                case 'u':
                    if (y>0) {
                        ConPrintAt(x,y," ",1);
                        y--;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 'd':
                    if (y<h) {
                        ConPrintAt(x,y," ",1);
                        y++;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 'l':
                    if (x>0) {
                        ConPrintAt(x,y," ",1);
                        x--;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
                case 'r':
                    if (x<w-1) {
                        ConPrintAt(x,y," ",1);
                        x++;
                        ConPrintAt(x,y,"O",1);
                    }
                break;
            }
        }
    }
    ClearConsole();
    ShowTheCursor();
    return 0;
}
  • 打赏
  • 举报
回复
很好呀,继续努力。
我完了一会,最后结束的时候是一直往上走的,但蛇的身体不是直的,而且右边界好像有问题。
qq_38347379 2017-04-20
  • 打赏
  • 举报
回复
引用 3楼ooolinux 的回复:
引用 2 楼 qq_38347379 的回复:
[quote=引用 1楼ooolinux 的回复:]不错不错。。
请问最后的问题有解不
代码没有细看,你是用带头结点的链表,还是头指针指向第一个结点? 我写的一个贪吃蛇,没有用动态链表,用的C++的vector(动态数组)[/quote]第二种
自信男孩 2017-04-20
  • 打赏
  • 举报
回复
she=(snake *)malloc(sizeof(snake *));
    f1=(food *)malloc(sizeof(food *));
改成这样的:
she=(snake *)malloc(sizeof(snake));
    f1=(food *)malloc(sizeof(food));
qq_38347379 2017-04-20
  • 打赏
  • 举报
回复
引用 6楼ooolinux 的回复:
参考: 《深入理解C指针》
好吧 多谢
qq_38347379 2017-04-20
  • 打赏
  • 举报
回复
引用 4楼自信男孩 的回复:
she=(snake *)malloc(sizeof(snake *));
    f1=(food *)malloc(sizeof(food *));
改成这样的:
she=(snake *)malloc(sizeof(snake));
    f1=(food *)malloc(sizeof(food));
好像没什么用
ooolinux 2017-04-20
  • 打赏
  • 举报
回复
参考: 《深入理解C指针》
ooolinux 2017-04-20
  • 打赏
  • 举报
回复
引用 2 楼 qq_38347379 的回复:
引用 1楼ooolinux 的回复:
不错不错。。
请问最后的问题有解不
代码没有细看,你是用带头结点的链表,还是头指针指向第一个结点? 我写的一个贪吃蛇,没有用动态链表,用的C++的vector(动态数组)
qq_38347379 2017-04-19
  • 打赏
  • 举报
回复
引用 1楼ooolinux 的回复:
不错不错。。
请问最后的问题有解不
ooolinux 2017-04-19
  • 打赏
  • 举报
回复
不错不错。。

69,371

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧