linux下的贪吃蛇小游戏(c实现)

主要设计思路:


  • 主要数据结构:双向循环链表
  • 蛇的移动:每次在蛇头添加2个节点,在蛇尾删除2个节点
  • 蛇的转向:通过一组方向坐标与蛇的位置坐标的运算来实现转向
  • 蛇吃食物:只在蛇头添加2个节点,而不必在蛇尾删除2个节点
  • 蛇的死亡:撞上边界或自身

*

然后给出实现中的一点说明:

  • 之所以每次操作2个节点,是因为我所绘制的游戏界面比较大,这样能使游戏效果更明显点
  • 而且为了当蛇身比较长的时候,对蛇身的折叠缠绕能够明显的区分开,而不是混成一团,我们让蛇每次移动2行或2列
  • 还有一个需要注意的点:当你给普通字符着色后,它所对应的一个整形值就不再是原来的值了,它会在原来的基础上附加上颜色属性。所以不同颜色的相同字符其实是不同的,不能一概视之。

游戏的核心逻辑:

正常情况下,程序一直处于监听键盘的状态,但一旦有信号中断产生,就转而去执行display_snake函数;执行完毕后,又处于监听键盘的状态… 直至程序运行结束(这里用到了一点简单的signal方面的知识)。这个机制应该是整个程序设计的难点了,我也是在参考了许多博客,再加上一段时间的思考之后,才想明白的。


还有一点就是,你得确保你的电脑上有curses库,绝大多数unix系统应该会自带curses库,如果没有的话,那么你得自行安装,安装方法很简单,网上也有很多,这里就不多说了。


下面便是程序的有关代码:

首先是一些声明

#define DELAY  150    /*  设置延时  */                                                                                                            
/*  蛇的活动地图的大小  */
#define ROW    (LINES - 3)
#define COL    (COLS - 24) 

typedef struct snake {     /*  蛇身节点  */
    int sx;       /*  行坐标  */
    int sy;       /*  列坐标  */
    struct snake *last;
    struct snake *next;
} Snake;

extern int dx, dy;
extern Snake *head, *tail;
extern int score;

void init(void);
void over(void);
void draw_initscr();
void draw_overscr();
void draw_map();
void display_snake(int);
void display_score();
void set_color(void); 
int set_ticker(int);  

void creat_snake(void);
void creat_food(void);
void add_snake(void);
void del_snake(void);
void lock_snake(void);
int is_eat_food(void);
int is_crash(void);
void get_keyboard(void);

主函数很简单(^_^)a

main.c

Snake *head, *tail;   /*  蛇头、蛇尾  */
int dx, dy;           /*  一组方向坐标  */
int score;            /*  得分  */

int main(void)
{
    init();
    signal(SIGALRM, display_snake);
    get_keyboard();     /*  监听键盘  */
    endwin();
    return 0;
}
proctrl.c

/*  init函数:游戏初始化  */
void init(void)
{
    initscr();   /*  初始化curses  */
    start_color();  /*  初始化颜色表  */
    set_color();    /*  设置颜色  */
    box(stdscr, ACS_VLINE, ACS_HLINE);   /*  绘制一个同物理终端大小相同的窗口  */
    noecho();    /*  关闭键入字符的回显  */
    cbreak();    /*  字符一键入,直接传递给程序  */
    curs_set(0);    /*  隐藏光标  */
    keypad(stdscr, true);    /*  开启逻辑键  */
    draw_initscr();
    draw_map();
    creat_snake();
    creat_food();
    display_score();
    refresh();   
}

/*  display_snake函数:游戏的主要控制逻辑  */
void display_snake(int signo)
{
    if (is_crash())
        over();
    if (is_eat_food()) {
        add_snake();
        creat_food();
        display_score();
    } else {
        add_snake();
        del_snake();
    }
    refresh();
}

/*  over函数:游戏结束  */
void over(void)
{
    draw_overscr();
    endwin();    /*  结束调用curses  */
    exit(0);
}

下面是与蛇的移动有关的代码,也是游戏的核心部分代码

mvsnake.c

/*  creat_snake函数:初始化蛇身  */
void creat_snake(void)
{
    assert(head = tail = malloc(sizeof(Snake)));
    head->last = head->next = NULL;
    srand(clock());   /*  以当前挂钟时间作随机种子数  */
    while ((head->sx = rand() % (ROW - 3) + 3) % 2 == 0)
        ;
    while ((head->sy = rand() % (COL - 4) + 4) % 2 != 0)
        ;
    attron(COLOR_PAIR(1));
    mvaddch(head->sx, head->sy, ' ');
    attroff(COLOR_PAIR(1));
}

/*  creat_food函数:初始化食物  */
void creat_food(void)
{
    int i, j;

    srand(clock());
    while ((i = rand() % (ROW - 3) + 3) % 2 == 0)
        ;
    while ((j = rand() % (COL - 4) + 4) % 2 != 0)
        ;
    move(i, j);
    if (inch() == 288)    /*  食物不能覆盖蛇身  */
        creat_food();
    attron(COLOR_PAIR(2));
    addch(' ');
    attroff(COLOR_PAIR(2));
}

/*  add_snake函数:在蛇头增加2个节点  */
void add_snake(void)
{
    Snake *p, *q;

    assert(p = malloc(sizeof(Snake)));
    assert(q = malloc(sizeof(Snake)));
    p->last = head;
    head->next = p;
    p->next = q;
    q->last = p;
    q->next = NULL;
    attron(COLOR_PAIR(1));
    p->sx = head->sx + dx;
    p->sy = head->sy + dy;
    mvaddch(p->sx, p->sy, ' ');
    q->sx = p->sx + dx;
    q->sy = p->sy + dy;
    mvaddch(q->sx, q->sy, ' ');
    attroff(COLOR_PAIR(1));
    head = q;
}

/*  del_snake函数:在蛇尾删除2个节点  */
void del_snake(void)
{
    Snake *tmp;

    mvaddch(tail->sx, tail->sy, ' ');
    mvaddch(tail->next->sx, tail->next->sy, ' ');
    tmp = tail->next->next;
    free(tail->next);
    free(tail);
    tail = tmp;
    tail->last = NULL;
}

/*  is_eat_food函数:判断是否吃到食物  */
int is_eat_food(void)
{
    if (inch() == 544)
        return 1;
    return 0;
}

/*  is_crash函数:判断是否撞到障碍物  */
int is_crash(void)
{
    move(head->sx + 2 * dx, head->sy + 2 * dy);
    if (head->sx <= 2 || head->sx >= ROW + 1|| 
        head->sy <= 3 || head->sy >= COL + 1)   /*  撞到边界  */
        return 1; 
    if (inch() == 288)   /*  撞到自身  */
        return 1;
    return 0;
}

/*  get_keyboard函数:监听键盘  */
void get_keyboard(void)
{
    int c;

    while ((c = getch()) != 'q') {   /*  按q键可以退出游戏  */
        switch(c) {
        case KEY_UP:
            dx = -1; dy = 0;
            break;
        case KEY_DOWN:
            dx = 1; dy = 0;
            break;
        case KEY_LEFT:
            dx = 0; dy = -1;
            break;
        case KEY_RIGHT:
            dx = 0; dy = 1;
            break;
        default:
            break;
        }
        lock_snake();
        set_ticker(DELAY);
    }
}

/*  lock_snake函数:防止蛇身反向移动  */
void lock_snake(void)
{
    static int lx, ly;

    if (dx && dx + lx == 0)
        dx = lx;
    if (dy && dy + ly == 0)
        dy = ly;
    lx = dx;
    ly = dy;
}

游戏界面的绘制就是个细心活喽,大家按自己的喜好绘制就可以了,说实话,这个界面我是尝试了好多好多次,挺无聊的^o^(逃

draw.c

/*  draw_initscr函数:绘制游戏开始界面  */
void draw_initscr()
{
    int i;

    attron(COLOR_PAIR(4));
    for (i = COLS / 2 - 20; i <= COLS / 2 + 20; i += 2) {
        mvaddch(LINES / 2 - 4, i, '@');
        mvaddch(LINES / 2 + 6, i, '@');
    }
    for (i = LINES / 2 - 3; i <= LINES / 2 + 5; i++) {
        mvaddch(i, COLS / 2 - 22, '@');
        mvaddch(i, COLS / 2 + 22, '@');
    }
    mvaddstr(LINES / 2 + 8, COLS / 2 - 13, "Press any key to continue...");
    attroff(COLOR_PAIR(4));
    attron(COLOR_PAIR(5));
    mvaddstr(LINES / 2 - 2, COLS / 2 - 8, "Welcome to snake");
    mvaddstr(LINES / 2 - 1, COLS / 2 - 10, "emmmmmmmmm, however,");
    mvaddstr(LINES / 2, COLS / 2 - 20, "now that you are here, you must be greedy!");
    mvaddstr(LINES / 2 + 1, COLS / 2 - 11, "~~o~~o~~o~~o~~o~~o~~o~~");
    mvaddstr(LINES / 2 + 2, COLS / 2 - 5, "Game Rules:");
    mvaddstr(LINES / 2 + 3, COLS / 2 - 18, "1. use direciton keys to move snake");
    mvaddstr(LINES / 2 + 4, COLS / 2 - 18, "2. snake is green space, food is red");
    getch();
    attroff(COLOR_PAIR(5));
    refresh();
    clear();   /*  清屏  */
}

/*  draw_map函数:绘制游戏地图  */
void draw_map(void)
{
    int i;

    attron(COLOR_PAIR(3));
    for (i = 3; i < COLS - 1; i += 2) {
        mvaddch(2, i, ' ');
        mvaddch(LINES - 2, i, ' ');
    }
    for (i = 3; i < LINES - 1; i += 2) {
        mvaddch(i, 3, ' ');
        mvaddch(i, COL + 1, ' ');
        mvaddch(i, COLS - 3, ' ');
    }
    attroff(COLOR_PAIR(3));
    attron(COLOR_PAIR(4));
    mvaddstr(10, 132, "Your Score:");
    mvaddstr(12, 130, "^0^ smiling ^0^");
    attroff(COLOR_PAIR(4));
}

/*  draw_overscr函数:绘制结束界面  */
void draw_overscr(void)
{
    int i;

    clear();
    attron(COLOR_PAIR(4));
    for (i = COLS / 2 - 16; i <= COLS / 2 + 16; i += 2) {
        mvaddch(LINES / 2 - 4, i, '@');
        mvaddch(LINES / 2 + 3, i, '@');
    }
    for (i = LINES / 2 - 3; i <= LINES / 2 + 2; i++) {
        mvaddch(i, COLS / 2 - 18, '@');
        mvaddch(i, COLS / 2 + 18, '@');
    }
    mvaddstr(LINES / 2 + 5, COLS / 2 - 13, "Press any key to continue...");
    attroff(COLOR_PAIR(4));
    attron(COLOR_PAIR(5));
    mvaddstr(LINES / 2 - 1, COLS / 2 - 13, "I'm sorry, the game is over");
    mvaddstr(LINES / 2, COLS / 2 - 9, "But expect you back");
    attroff(COLOR_PAIR(5));
    refresh();
    getch();
}

/*  display_score函数:显示当前得分  */
void display_score(void)
{
    attron(COLOR_PAIR(5));
    move(11, 136);
    printw("%d", score++);
    attroff(COLOR_PAIR(5));
}

其中set_ticker函数是我从网上找的,具体原理不太清楚^ _ ^

set.c

/*  set_color函数:设置颜色  */
void set_color(void)
{
    init_pair(1, COLOR_GREEN, COLOR_GREEN);
    init_pair(2, COLOR_RED, COLOR_RED);
    init_pair(3, COLOR_WHITE, COLOR_WHITE);
    init_pair(4, COLOR_YELLOW, COLOR_BLACK);
    init_pair(5, COLOR_RED, COLOR_BLACK);
}

/*  set_ticker函数:设置间隔计时器(ms)  */
int set_ticker(int n_msecs)  
{  
    struct itimerval new_timeset;  
    long n_sec, n_usecs;  

    n_sec = n_msecs / 1000;      
    n_usecs = (n_msecs % 1000) * 1000L;      

    new_timeset.it_interval.tv_sec = n_sec;   /*  设置初始间隔  */  
    new_timeset.it_interval.tv_usec = n_usecs;  

    new_timeset.it_value.tv_sec = n_sec;      /*  设置重复间隔  */  
    new_timeset.it_value.tv_usec = n_usecs;  
    return setitimer(ITIMER_REAL, &new_timeset, NULL);  
}  
运行图:

最后说明一点:当你编译运行的时候需要链接至curses库哦,在编译命令后加上-lcurses就可以了

emmmm,然后呢,希望大家能多多提出建议^o^

猜你喜欢

转载自blog.csdn.net/qq_41145192/article/details/80767386