#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/times.h>
#include<sys/types.h>
#include<unistd.h>
#include<ncurses.h>

#define TBool        int
#define True        1
#define False        0
#define    SHAPE_FOOD    '@'
#define SHAPE_SNAKE    '#'
#define    GAMEWIN_YLEN    20
#define GAMEWIN_XLEN    60
#define LOGWIN_YLEN    7
#define LOGWIN_XLEN    (GAMEWIN_XLEN)
#define LOGBUF_NUM    (LOGWIN_YLEN - 2)
#define LOGBUF_LEN    (GAMEWIN_XLEN - 2)
#define MAXLEVEL    12

#define GetSnakeTail(s)    ((s)->head->front)

WINDOW *logwin;        //declare a windows for displaying message
#define INITRUNLOG()    logwin = newlogw()
#define RUNLOG(str)        runlog(logwin, str)
#define DESTROYRUNLOG()    delwin(logwin)

int g_level;    //level of player

enum TDirection
{
    DIR_UP,
    DIR_DOWN,
    DIR_LEFT,
    DIR_RIGHT
};

struct TFood
{
    int x;
    int y;
};

struct TSnakeNode
{
    int y;
    int x;
    struct TSnakeNode *front;
};

struct TSnake
{
    int length;
    struct TSnakeNode *head;
    enum TDirection dir;
};

int refreshgamew(WINDOW *win, struct TSnake *psnake);
void movesnake(struct TSnake *psnake);
int checksnake(struct TFood *pfood, struct TSnake *psnake);
void snakegrowup(struct TFood *pfood, struct TSnake *psnake);
void gameover(WINDOW *win, char *str);
struct TSnakeNode *newsnakenode(struct TSnakeNode **ppsnode, int y, int x);
struct TSnake *initsnake();
void destroysnake(struct TSnake *psnake);
void drawsnakew(WINDOW *win, struct TSnake *psnake);
void drawfoodw(WINDOW *win, struct TFood *pfood, struct TSnake *psnake);
TBool checkfood(struct TFood *pfood, struct TSnake *psnake);
WINDOW*    newgamew();
WINDOW* newlogw();
void runlog(WINDOW* win, char *str);
void cleanline(WINDOW *win, int y, int x);


int main()
{
    initscr();
    raw();
    noecho();
    keypad(stdscr, TRUE);
    curs_set(0);
    refresh();

    g_level = 1;
    INITRUNLOG();

    RUNLOG("    press 'q' or 'Q' to quit.");
    RUNLOG("    press 'w/s/a/d' or 'W/S/A/D' to move the snake.");
    RUNLOG("info:");

    WINDOW *gwin = newgamew();

    struct TSnake *psnake = initsnake();
    drawsnakew(gwin, psnake);

    while(refreshgamew(gwin, psnake) >= 0)
        ;

    getch();
    destroysnake(psnake);
    delwin(gwin);
    DESTROYRUNLOG();
    endwin();

    return 0;
}

WINDOW* newlogw()
{
    WINDOW *win = newwin(LOGWIN_YLEN, LOGWIN_XLEN, GAMEWIN_YLEN + 2, 3);
    box(win, 0, 0);
    mvwprintw(win, 0, 2, " LOG ");
    wrefresh(win);

    return win;
}

WINDOW *newgamew()
{
    WINDOW *win = newwin(GAMEWIN_YLEN, GAMEWIN_XLEN, 1, 3);
    box(win, 0, 0);
    mvwprintw(win, 0, 2, " GAME ");
    mvwprintw(win, GAMEWIN_YLEN - 1, 2, " Level: %d ", g_level);
    mvwprintw(win, GAMEWIN_YLEN - 1, 30, " Speed: %d ", (int)(g_level/3));
    wrefresh(win);
    return win;
}    

void runlog(WINDOW *win, char *str)
{
    static char logbuf[LOGBUF_NUM][LOGBUF_LEN] = {0};
    static int index = 0;
    strcpy(logbuf[index], str);

    int i = 0;

    for (; i < LOGBUF_NUM; ++i)
    {
        cleanline(win, i+1, 1);
        mvwprintw(win, i+1, 1, logbuf[(index + i) % LOGBUF_NUM]);
        wrefresh(win);
    }
    index = (index + LOGBUF_NUM - 1) % LOGBUF_NUM;
}

//将窗口win的坐标(x,y)清空
void cleanline(WINDOW *win, int y, int x)
{
    char EMPTYLINE[LOGBUF_LEN] = {0}; //LOGBUF_LEN = 57
    
    //置空数组 0-56个位置
    memset(EMPTYLINE, ' ', LOGBUF_LEN-1);
    
    //将光标移动到窗口win(y,x)然后打印字符串EMPTYLINE
    mvwprintw(win, y, x, EMPTYLINE);
    
    //在指定窗口上显示内容
    wrefresh(win);    
}
int refreshgamew(WINDOW *win, struct TSnake *psnake)
{
    static TBool ffood = False;
    struct TFood pfood;

    if (!ffood)
    {
        drawfoodw(win, &pfood, psnake);
        ffood = True;
    }

    int key = -1;

    fd_set set;
    FD_ZERO(&set);
    FD_SET(0, &set);

    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = (6 - (int)(g_level/3)) * 100 * 1000;

    if (select(1, &set, NULL, NULL, &timeout) < 0)
    {
        return -1;
    }

    if (FD_ISSET(0, &set))
    {
        while ((key = getch()) == -1);
        switch(key)
        {
            case 'w':
            case 'W':
                (psnake->dir == DIR_DOWN) ? : (psnake->dir = DIR_UP);
                break;
            case 's':
            case 'S':
                (psnake->dir == DIR_UP) ? : (psnake->dir = DIR_DOWN);
                break;

            case 'a':
            case 'A':
                (psnake->dir == DIR_RIGHT) ? : (psnake->dir = DIR_LEFT);
                break;
                
            case 'd':
            case 'D':
                (psnake->dir == DIR_LEFT) ? : (psnake->dir = DIR_RIGHT);
                break;

            case 'q':
            case 'Q':
                RUNLOG("Quit Game!");
                gameover(win, "Quit Game!");
                return -1;

            default:
                break;
        }
    }

    movesnake(psnake);
    drawsnakew(win, psnake);
    switch(checksnake(&pfood, psnake))
    {
        case 0:
            break;
            
        case 1:
            ffood = False;
            if (++g_level > MAXLEVEL)
            {
                RUNLOG("Win!!!");
                gameover(win, "Win!!!");
                return -1;
            }

            mvwprintw(win, GAMEWIN_YLEN-1, 2, " Level: %d ", g_level);
            mvwprintw(win, GAMEWIN_YLEN-1, 30, " Speed: %d ", (int)(g_level/3));
            wrefresh(win);
            RUNLOG("Level UP!");
            snakegrowup(&pfood, psnake);
            break;

        default:
            RUNLOG("Game over!");
            gameover(win, "Game over!");
            return -1;
    }
    return 1;
}

/**
 * stuct TSnake是一个倒置的首尾相连的链表,head->front指向snake的尾部
 * 如: [a]<-[b]<-[c]<-[d]    a为head
 *      |              ^     snake移动的时候,只用head指向d,
 *      `--------------'     并且修改d的(y,x)为snake头移动到的位置.
 */
void movesnake(struct TSnake *psnake)
{
    int hy = psnake->head->y;
    int hx = psnake->head->x;

    psnake->head = GetSnakeTail(psnake);
    switch(psnake->dir)
    {
        case DIR_UP:
            psnake->head->y = hy - 1;
            psnake->head->x = hx;
            break;

        case DIR_DOWN:
            psnake->head->y = hy + 1;
            psnake->head->x = hx;
            break;

        case DIR_LEFT:
            psnake->head->y = hy;
            psnake->head->x = hx - 1;
            break;

        case DIR_RIGHT:
            psnake->head->y = hy;
            psnake->head->x = hx + 1;
            break;

        default:
            break;
    }
}

int checksnake(struct TFood *pfood, struct TSnake *psnake)
{
    int hy = psnake->head->y;
    int hx = psnake->head->x;
    if (hy <= 0 || hy >= GAMEWIN_YLEN || hx <= 0 || hx >= GAMEWIN_XLEN)
    {
        return -1;
    }

    struct TSnakeNode *pnode = GetSnakeTail(psnake);
    int i = 0;
    for (; i < psnake->length - 1; ++i, pnode = pnode->front)
    {
        if (hy == pnode->y && hx == pnode->x)
        {
            return -1;
        }

    }
    if (hy == pfood->y && hx == pfood->x)
    {
        return 1;
    }
    return 0;
}

void snakegrowup(struct TFood *pfood, struct TSnake *psnake)
{
    struct TSnakeNode *pnode = (struct TSnakeNode *)malloc(sizeof(struct TSnakeNode));

    switch(psnake->dir)
    {
        case DIR_UP:
            pnode->y = psnake->head->y - 1;
            pnode->x = psnake->head->x;
            break;
            
        case DIR_DOWN:
            pnode->y = psnake->head->y + 1;
            pnode->x = psnake->head->x;
            break;
            
        case DIR_LEFT:
            pnode->y = psnake->head->y ;
            pnode->x = psnake->head->x - 1;
            break;
            
        case DIR_RIGHT:
            pnode->y = psnake->head->y;
            pnode->x = psnake->head->x + 1;
            break;

        default:
            break;
    }

    pnode->front = GetSnakeTail(psnake);
    psnake->head->front = pnode;
    psnake->head = pnode;
    ++psnake->length;
}

void gameover(WINDOW *win, char *str)
{
    mvwprintw(win, (int)(GAMEWIN_YLEN/2), (GAMEWIN_XLEN/2 - strlen(str)/2), str);
    mvwprintw(win, (int)(GAMEWIN_YLEN/2 + 1), 20, "Please any key to quit...");
    wrefresh(win);
}

struct TSnakeNode *newsnakenode(struct TSnakeNode **ppsnode, int y, int x)
{
    *ppsnode = (struct TSnakeNode *)malloc(sizeof(struct TSnakeNode));
    (*ppsnode)->y = y;
    (*ppsnode)->x = x;
    (*ppsnode)->front = NULL;

    return *ppsnode;
}

struct TSnake *initsnake()
{
    struct TSnake *psnake = (struct TSnake *)malloc(sizeof(struct TSnake));

    psnake->dir = DIR_LEFT;
    psnake->length = 4;

    newsnakenode(&newsnakenode(&newsnakenode(&newsnakenode(&psnake->head, 4, 50)->front,
                    4, 53)->front, 4, 52)->front,
                        4, 51)->front = psnake->head;

    return psnake;
}

void destroysnake(struct TSnake *psnake)
{
    struct TSnakeNode *psnode =    GetSnakeTail(psnake);
    struct TSnakeNode *ptmp = NULL;

    int i = 0;
    for (; i < psnake->length; ++i)
    {
        ptmp = psnode;
        psnode = psnode->front;
        free(ptmp);
    }

    free(psnake);
    psnake = NULL;
}

void drawsnakew(WINDOW *win, struct TSnake *psnake)
{
    static int taily = 0;
    static int tailx = 0;
    if (taily != 0 && tailx != 0)
    {
        //将光标移动到指定窗口的指定位置(taily, tailx)然后输出字符
        mvwaddch(win, taily, tailx, ' ');
    }

    struct TSnakeNode *psnode = GetSnakeTail(psnake);

    int i = 0;
    for (; i < psnake->length; ++i)
    {
        mvwaddch(win, psnode->y, psnode->x, SHAPE_SNAKE);
        psnode = psnode->front;
    }

    taily = GetSnakeTail(psnake)->y;
    tailx = GetSnakeTail(psnake)->x;

    wrefresh(win);
}


void drawfoodw(WINDOW *win, struct TFood *pfood, struct TSnake *psnake)
{
    do
    {
        pfood->y = random() % (GAMEWIN_YLEN - 2) + 1;
        pfood->x = random() % (GAMEWIN_XLEN - 2) + 1;
    }while(False == checkfood(pfood, psnake));
    
    checkfood(pfood, psnake);
    mvwaddch(win, pfood->y, pfood->x, SHAPE_FOOD);
    wrefresh(win);
}

//检查food出现的位置不能在snake上
TBool checkfood(struct TFood *pfood, struct TSnake *psnake)
{
    struct TSnakeNode *pnode = GetSnakeTail(psnake);

    int i = 0;
    for (; i < psnake->length; ++i, pnode = pnode->front)
    {
        if (pfood->y == pnode->y && pfood->x == pnode->x)
        {
            return false;
        }
    }

    return true;
}

 

两个重点:

1.整个窗口的坐标以(0,0)为原点,x轴,y轴建立坐标点,x轴正向和y轴负向作为数值增大的方向,所以坐标移动时向下y轴坐标+1,反之-1

2.蛇移动时,每次只移动一个单位距离,所以只需要将最后一个节点拿走,放到要移动的那个位置上就整体上有移动的感觉了,至于移动的位置会有函数检测是否合法。

 

注意:

程序用到了ncurses库,如果没有的话需要下载:

# 更新软件源中的所有软件列表
$ sudo apt-get update

# 安装 `curses` 库
$ sudo apt-get install libncurses5-dev

 

编译时最后要加上-lncurses