C语言实现贪吃蛇游戏
#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