C 游戏snake源代码


C 游戏snake源代码


正文

snake.c:

#include <stdio.h>
#include <stdlib.h>
#include <curses.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#define NUM 60

struct direct                //用来表示方向的
{
    int cx;
    int cy;
};

typedef struct node            //链表的结点
{
    int cx;
    int cy;
    struct node *back;
    struct node *next;
} node;

void initGame();            //初始化游戏
int setTicker(int);            //设置计时器
void show();                //显示整个画面
void showInformation();        //显示游戏信息(前两行)
void showSnake();            //显示蛇的身体
void getOrder();            //从键盘中获取命令
void over(int i);            //完成游戏结束后的提示信息

void creatLink();                //(带头尾结点)双向链表以及它的操作
void insertNode(int x, int y);   
void deleteNode();
void deleteLink();

int ch;                                //输入的命令
int hour, minute, second;            //时分秒
int length, tTime, level;            //(蛇的)长度,计时器,(游戏)等级
struct direct dir, food;            //蛇的前进方向,食物的位置
node *head, *tail;                    //链表的头尾结点

int main()
{
    /**
     * curses.h宏 定义了 nitscr() , endwin()
     */
    initscr();

    // 初始化
    initGame();

    /**
     * signal.h宏 定义了 signal()
     * SIGALRM 14 A 由alarm(2)发出的信号 
     * 调用 show()
     */
    signal(SIGALRM, show);

    // 获取键盘命令
    getOrder();

    endwin();

    return 0;
}

void initGame()
{
    cbreak();                    // 把终端的CBREAK模式打开
    noecho();                    // 关闭回显
    curs_set(0);                // 把光标置为不可见
    keypad(stdscr, true);        // 使用用户终端的键盘上的小键盘

    srand(time(0));                // 设置随机数种子

    // 初始化各项数据
    hour = minute = second = tTime = 0;
    length = 1;
    dir.cx = 1;
    dir.cy = 0;
    ch = 'A';
    food.cx = rand() % COLS;            // COLS 命令行列宽
    food.cy = rand() % (LINES-2) + 2;   // LINES 命令行行高

    creatLink();    // 创建一个双向链表

    setTicker(20);  // 设置计时器
}

/* 设置计时器(这个函数是书本上的例子,有改动) 
 * n_msecs 设置间隔时长 s
 * 1s后启动定时器
 */
int setTicker(int n_msecs)
{
    /**
     *   time.h 宏:
     *   struct itimerval {
     *       struct timeval it_interval;  // next value  间隔时长
     *       struct timeval it_value;     // current value  运行时间
     *   };
     *   struct timeval {
     *       long tv_sec; // seconds  秒
     *       lonng tv_usec // microseconds  微秒
     *   };
     */ 
    struct itimerval new_timeset;

    // n_sec 秒  ,  n_usecs 微秒 
    long    n_sec, n_usecs;

    // 间隔
    n_sec = n_msecs / 1000 ;
    n_usecs = ( n_msecs % 1000 ) * 1000L ;  // L长整型
    new_timeset.it_interval.tv_sec  = n_sec;       
    new_timeset.it_interval.tv_usec = n_usecs;  

    // 运行时间
    n_msecs = 1;
    n_sec = n_msecs / 1000 ;
    n_usecs = ( n_msecs % 1000 ) * 1000L ;
    new_timeset.it_value.tv_sec     = n_sec  ;     
    new_timeset.it_value.tv_usec    = n_usecs ;    

    // 设置定时器
    // int setitimer(int which, const struct itimerval *value,struct itimerval *ovalue);
    // setitimer()将value指向的结构体设为计时器的当前值,假设ovalue不是NULL,将返回计时器原有值。
    return setitimer(ITIMER_REAL, &new_timeset, NULL);
}

void showInformation()
{
    tTime++;
    if (tTime >= 1000000)                
        tTime = 0;
    if (1 != tTime % 50)
        return;

    move(0, 3);   // curses库定义: move(y,x): 将游标移动至 x,y 的位置.

    // 显示时间
    printw("time: %d:%d:%d %c", hour, minute, second);

    second++;
    if (second > NUM) {
        second = 0;
        minute++;
    }
    if (minute > NUM) {
        minute = 0;
        hour++;
    }

    //显示长度,等级
    move(1, 0);  

    int i;
    for (i=0; i<COLS; i++)
        addstr("-");

    move(0, COLS/2 - 5);
    printw("length: %d", length);

    move(0, COLS-10);

    level = length / 3 + 1;
    printw("level: %d", level);
}

//蛇的表示是用一个带头尾结点的双向链表来表示的,
//蛇的每一次前进,都是在链表的头部增加一个节点,在尾部删除一个节点
//如果蛇吃了一个食物,那就不用删除节点了
void showSnake()
{
    if(1 != tTime % (30-level)) {
        return;
    }

    //判断蛇的长度有没有改变
    bool lenChange = false;

    //显示食物
    move(food.cy, food.cx);

    printw("@");

    //如果蛇碰到墙,则游戏结束
    if((COLS-1==head->next->cx && 1==dir.cx)
        || (0==head->next->cx && -1==dir.cx)
        || (LINES-1==head->next->cy && 1==dir.cy)
        || (2==head->next->cy && -1==dir.cy))
    {
        over(1);
        return;
    }

    //如果蛇头砬到自己的身体,则游戏结束
    if('*' == mvinch(head->next->cy+dir.cy, head->next->cx+dir.cx) )
    {
        over(2);
        return;
    }
    
    insertNode(head->next->cx+dir.cx, head->next->cy+dir.cy);
    //蛇吃了一个“食物”
    if(head->next->cx==food.cx && head->next->cy==food.cy)
    {
        lenChange = true;
        length++;
        //恭喜你,通关了
        if(length >= 50)
        {
            over(3);
            return;
        }
        //重新设置食物的位置
        food.cx = rand() % COLS;
        food.cy = rand() % (LINES-2) + 2;
    }
    if(!lenChange)
    {
        move(tail->back->cy, tail->back->cx);
        printw(" ");
        deleteNode();
    }
    move(head->next->cy, head->next->cx);
    printw("*");
}

void show()
{
    // SIGALRM 14 A 由alarm(2)发出的信号 
    signal(SIGALRM, show);        // 设置中断信号

    showInformation();

    showSnake();

    refresh();                    // 刷新真实屏幕
    /* refresh() 为 curses 最常呼叫的一个函式. curses 为了使萤幕输出入达最佳化, 当您呼叫萤幕输出函式企图改变萤幕上的画面时, 
    curses 并不会立刻对萤幕做改变, 而是等到refresh() 呼叫後, 才将刚才所做的变动一次完成. 其馀的资料将维持不变. 以尽可能送最少的字元至萤幕上. 
    减少萤幕重绘的时间.如果是 initscr() 後第一次呼叫 refresh(), curses 将做清除萤幕的工作.  */ 
}

// 获取键盘命令
void getOrder()
{
    //建立一个死循环,来读取来自键盘的命令
    while(1)
    {
        ch = getch();
        if(KEY_LEFT == ch)
        {
            dir.cx = -1;
            dir.cy = 0;
        }
        else if(KEY_UP == ch)
        {
            dir.cx = 0;
            dir.cy = -1;
        }
        else if(KEY_RIGHT == ch)
        {
            dir.cx = 1;
            dir.cy = 0;
        }
        else if(KEY_DOWN == ch)
        {
            dir.cx = 0;
            dir.cy = 1;
        }
        setTicker(20);
    }
}

// 结束命令
void over(int i)
{
    // 显示结束原因
    move(0, 0);   // 定位光标到开头

    int j;
    for(j=0; j<COLS; j++) {
        addstr(" ");  // curses库 addstr(str): 显示一串字串.
    }

    move(0, 2);
    if(1 == i)
        addstr("Crash the wall. Game over");
    else if(2 == i)
        addstr("Crash itself. Game over");
    else if(3 == i)
        addstr("Mission Complete");

    setTicker(0);                //关闭计时器

    deleteLink();                //释放链表的空间
}

//创建一个双向链表
void creatLink()
{
    // 临时链表
    // void *malloc(int num);  在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
    node *temp = (node *)malloc( sizeof(node) );  

    head = (node *)malloc( sizeof(node) );  // 链表头

    tail = (node *)malloc( sizeof(node) );  // 链表尾

    temp->cx = 5;
    temp->cy = 10;

    head->back = NULL;
    head->next = temp;

    tail->next = NULL;

    temp->next = tail;

    tail->back = temp;

    temp->back = head;
}

//在链表的头部(非头结点)插入一个结点
void insertNode(int x, int y)
{
    node *temp = (node *)malloc( sizeof(node) );
    temp->cx = x;
    temp->cy = y;
    temp->next = head->next;
    head->next = temp;
    temp->back = head;
    temp->next->back = temp;
}

//删除链表的(非尾结点的)最后一个结点
void deleteNode()
{
    node *temp = tail->back;
    node *bTemp = temp->back;
    bTemp->next = tail;
    tail->back = bTemp;
    temp->next = temp->back = NULL;
    free(temp);
    temp = NULL;
}

// 删除整个链表
void deleteLink()
{
    while (head->next != tail) 
    {
        deleteNode();
    }

    head->next = tail->back = NULL;

    free(head);  /* 使用 free() 函数释放内存 */
    free(tail);
}

编译:

gcc snake.c -lcurses -o snake

要加上 -lcurses,不然报错。 显式的加入库,即用-l<库名>,因为有库并不等于在编译的时候就能链接上,你还得告诉编译器去哪里找,-l<库名>这句其实就相当于一个路径/**/**/库名。






参考资料

100个C语言经典小程序和C语言编写的小游戏,带注释和解析 http://c.biancheng.net/cpp/u/yuanma/

linux下curses库介绍 https://blog.csdn.net/csdelete/article/details/52552008

Linux curses库使用 https://www.cnblogs.com/mengfanrong/p/4072214.html

Linux struct itimerval使用方法 http://www.manongjc.com/article/91653.html

C语言中的signal函数 https://www.cnblogs.com/jhcelue/p/7256859.html


返回