大一學習C語言的時候就想要用Turbo C編寫一個視頻小遊戲出來,種種原因後面擱淺了,現在藉着學習Linux系統編程的勁頭,編寫了一個終端下可以運行的貪喫蛇遊戲,其中此視頻遊戲用到的一些知識和操作系統運行時候的一些簡單功能有點類似,引用《Unix/Linux 編程實踐教程》(Bruce Molay著)裏面所介紹的視頻遊戲一般的編寫以及同操作系統的關係的原文如下:
一、視頻遊戲如何做
(1)空間:遊戲必須在計算機屏幕的特定位置畫影像。程序如何控制視頻顯示?
(2)時間:影像以不同的速度在屏幕上移動。以一個特定的時間間隔改變位置。程序是如何獲知時間並且在特定的時間安排事情的發生?
(3)中斷:程序在屏幕上平滑移動的物體,用戶可以在任何時刻產生輸入。程序是如何響應中斷的?
(4)同時做幾件事:遊戲必須在保持幾個物體移動的同時還要響應中斷。程序是如何同時做多件事情而不被弄得暈頭轉向的?
二、操作系統面臨着類似的問題
操作系統同樣要面對這4個問題。內核將程序載入內存空間並維護每個程序在內存中所處的位置。在內核的調度下,程序以時間片間隔的方式運行,同時,內核也在特定的時刻運行特定的內部任務。內核必須在很短的時間內響應用戶和外設在任何時刻的輸入。同時做幾件事需要一些技巧。內核是如何保證數據的有序和規整的?
上面的都是那本書上說的,個人覺得講的很好,看完這本後再看那本Linux聖經《Unix環境高級編程》或許更好些。迴歸正題吧,主要介紹一下設計一個終端下的貪喫蛇遊戲所實現的功能以及所需要的幾個條件和一些函數。
本貪喫蛇實現的功能是通過喫食物來增長自己的長度,可以利用按鍵 'f' 實現加速和 's' 鍵實現減速, 'q' 鍵退出,方向鍵控制方向,蛇要是碰到自己的身體或者碰到牆或者喫到一定數量,那麼遊戲就結束。功能還是挺簡單的吧,下面就介紹下各個步驟的設計:
1.首先要使用終端圖形庫curses.h文件,由於不是C標準庫,一般電腦不會自帶,需要自行下載安裝,ubuntu下可以這麼下載 sudo apt-get install libncurses5-dev 已經替換成ncurses.h 即 new curses.h的意思,完全兼容curses。介紹下此遊戲需要用到的常見的幾個curses函數。
基本curse函數 initscr() 初始化curses庫和tty endwin() 關閉curses並重置tty refresh() 刷新屏幕顯示 mvaddch(y,x,c) 在座標(y,x)處顯示字符c mvaddstr(y,x,str) 在座標(y,x)處顯示字符串str cbreak() 開啓輸入立即響應 noecho() 輸入不回顯到屏幕
curs_set(0) 使光標不可見 attrset() 開啓圖形顯示模式 keypad(stdscr, true) 開啓小鍵盤方向鍵輸入捕捉支持
更詳細的可以 man ncurses 或者參見http://bbs.chinaunix.net/viewthread.php?tid=909369
2.介紹完ncurses圖形庫,接下來進行屏幕繪圖,我初始化屏幕效果圖見下圖所示:先是外圍邊框,然後是蛇“@”和食物“*”。
廢話不多說,上代碼吧。
首先是頭文件 snake.h的代碼:由於在純文本模式下編程以及本人英語水平有限,可能有的註釋比較彆扭。
/* Game: snake version: 1.0 date:2011/08/22
* Author: Dream Fly
* filename: snake.h
*/
#define SNAKE_SYMBOL '@' /* snake body and food symbol */
#define FOOD_SYMBOL '*'
#define MAX_NODE 30 /* maximum snake nodes */
#define DFL_SPEED 50 /* snake default speed */
#define TOP_ROW 5 /* top_row */
#define BOT_ROW LINES - 1
#define LEFT_EDGE 0
#define RIGHT_EDGE COLS - 1
typedef struct node /* Snake_node structure */
{
int x_pos;
int y_pos;
struct node *prev;
struct node *next;
} Snake_Node;
struct position /* food position structure */
{
int x_pos;
int y_pos;
} ;
void Init_Disp(); /* init and display the interface */
void Food_Disp(); /* display the food position */
void Wrap_Up(); /* turn off the curses */
void Key_Ctrl(); /* using keyboard to control snake */
int set_ticker(int n_msecs);/* ticker */
void DLL_Snake_Create(); /* create double linked list*/
void DLL_Snake_Insert(int x, int y); /* insert node */
void DLL_Snake_Delete_Node(); /* delete a node */
void DLL_Snake_Delete(); /* delete all the linked list */
void Snake_Move(); /* control the snake move and judge */
void gameover(int n); /* different n means different state */
接下來是初始化界面圖形的子函數:
/* Function: Init_Disp()
* Usage: init and display the interface
* Return: none
*/
void Init_Disp()
{
char wall = ' ';
int i, j;
initscr();
cbreak(); /* put termial to CBREAK mode */
noecho();
curs_set(0); /* set cursor invisible */
/* display some message about title and wall */
attrset(A_NORMAL); /* set NORMAL first */
attron(A_REVERSE); /* turn on REVERSE to display the wall */
for(i = 0; i < LINES; i++)
{
mvaddch(i, LEFT_EDGE, wall);
mvaddch(i, RIGHT_EDGE, wall);
}
for(j = 0; j < COLS; j++)
{
mvaddch(0, j, '=');
mvaddch(TOP_ROW, j, wall);
mvaddch(BOT_ROW, j, wall);
}
attroff(A_REVERSE); /* turn off REVERSE */
mvaddstr(1, 2, "Game: snake version: 1.0 date: 2011/08/22");
mvaddstr(2, 2, "Author: Dream Fly Blog: blog.csdn.net/jjzhoujun2010");
mvaddstr(3, 2, "Usage: Press 'f' to speed up, 's' to speed down,'q' to quit.");
mvaddstr(4, 2, " Nagivation key controls snake moving.");
refresh();
}
/* Function: Food_Disp()
* Usage: display food position
* Return: none
*/
void Food_Disp()
{
srand(time(0));
food.x_pos = rand() % (COLS - 2) + 1;
food.y_pos = rand() % (LINES - TOP_ROW - 2) + TOP_ROW + 1;
mvaddch(food.y_pos, food.x_pos, FOOD_SYMBOL);/* display the food */
refresh();
}
/* Function: DLL_Snake_Create()
* Usage: create double linked list, and display the snake first node
* Return: none
*/
void DLL_Snake_Create()
{
Snake_Node *temp = (Snake_Node *)malloc(sizeof(Snake_Node));
head = (Snake_Node *)malloc(sizeof(Snake_Node));
tail = (Snake_Node *)malloc(sizeof(Snake_Node));
if(temp == NULL || head == NULL || tail == NULL)
perror("malloc");
temp->x_pos = 5;
temp->y_pos = 10;
head->prev =NULL;
tail->next = NULL;
head->next = temp;
temp->next = tail;
tail->prev = temp;
temp->prev = head;
mvaddch(temp->y_pos, temp->x_pos, SNAKE_SYMBOL);
refresh();
}
3.接下來就是蛇的移動問題,這個是核心部分以及最困難的設計部分了,我採用的是蛇用雙向鏈表的結構來構造出來,分別有一個head 和tail指針,用來添加和刪除元素。這裏若要實現移動的話(未碰到食物前),就是在鏈表的頭部(head的下一個)插入一個新元素,記錄下此時的座標,用mvaddch(y,x,c)函數添加蛇的圖形'@',與此同時,在鏈表尾部(tail的前一個)刪除一個節點,同時這裏的座標用mvaddch(y,x, ' ')添加了' '空白字符,實現刪除效果,最後加上refresh(). 這樣就可以看到蛇在“移動”了。當然,要是碰到食物的話,尾部節點處就不用刪除,達到增長長度的效果。
那麼,接下來的問題是:如何觸發蛇的移動呢?如何實現均勻移動以及通過按鍵 ‘f’ 或 's' 改變運動速度呢?這裏我採用的是信號計時中斷調用的函數 signal(SIGALRM, Snake_Move) 和 間隔計數器 來實現,通過產生相同間隔的時間片段來不斷地調用Snake_Move()函數來執行相應的功能。加減速的功能是通過設定其他變量ttm, ttg來實現再此基本計數器上面再次分頻的效果來加減速,ttm, ttg 越大,減速越明顯,反之則相反效果。 具體的間隔計數器的函數設計見下:(參考了以上所提書本上的函數)
/* Function: set_ticker(number_of_milliseconds)
* Usage: arrange for interval timer to issue SIGALRM's at regular intervals
* Return: -1 on error, 0 for ok
* arg in milliseconds, converted into whole seconds and microseconds
* note: set_ticker(0) turns off ticker
*/
int set_ticker(int n_msecs)
{
struct itimerval new_timeset;
long n_sec, n_usecs;
n_sec = n_msecs / 1000; /* int second part */
n_usecs = (n_msecs % 1000) * 1000L; /* microsecond part */
new_timeset.it_interval.tv_sec = n_sec; /* set reload */
new_timeset.it_interval.tv_usec = n_usecs;
new_timeset.it_value.tv_sec = n_sec; /* set new ticker value */
new_timeset.it_value.tv_usec = n_usecs;
return setitimer(ITIMER_REAL, &new_timeset, NULL);
}
蛇的移動的函數代碼如下:
void Snake_Move()
{
static int length = 1; /* length of snake */
int Length_Flag = 0; /* default snake's length no change */
int moved = 0;
signal(SIGALRM, SIG_IGN);
/* judge if the snake crash the wall */
if((head->next->x_pos == RIGHT_EDGE-1 && x_dir == 1)
|| (head->next->x_pos == LEFT_EDGE+1 && x_dir == -1)
|| (head->next->y_pos == TOP_ROW+1 && y_dir == -1)
|| (head->next->y_pos == BOT_ROW-1 && y_dir == 1))
{
gameover(1);
}
/* judge if the snake crash itself */
if(mvinch(head->next->y_pos + y_dir, head->next->x_pos + x_dir) == '@')
gameover(2);
if(ttm > 0 && ttg-- == 1)
{
/* snake moves */
DLL_Snake_Insert(head->next->x_pos + x_dir, head->next->y_pos + y_dir);
ttg = ttm; /* reset */
moved = 1; /* snake can move */
}
if(moved)
{
/* snake eat the food */
if(head->next->x_pos == food.x_pos && head->next->y_pos == food.y_pos)
{
Length_Flag = 1;
length++;
/* Mission Complete */
if(length >= MAX_NODE)
gameover(0);
/* reset display the food randomly */
Food_Disp();
}
if(Length_Flag == 0)
{
/* delete the tail->prev node */
mvaddch(tail->prev->y_pos, tail->prev->x_pos, ' ');
DLL_Snake_Delete_Node();
}
mvaddch(head->next->y_pos, head->next->x_pos, SNAKE_SYMBOL);
refresh();
}
signal(SIGALRM, Snake_Move);
}
主要函數的實現就是這些,以下貼上完整的源代碼供大家參考,或者去這裏下載:http://download.csdn.net/source/3540117
/* Game: snake version: 1.0 date:2011/08/22
* Author: Dream Fly
* filename: snake.h
*/
#define SNAKE_SYMBOL '@' /* snake body and food symbol */
#define FOOD_SYMBOL '*'
#define MAX_NODE 30 /* maximum snake nodes */
#define DFL_SPEED 50 /* snake default speed */
#define TOP_ROW 5 /* top_row */
#define BOT_ROW LINES - 1
#define LEFT_EDGE 0
#define RIGHT_EDGE COLS - 1
typedef struct node /* Snake_node structure */
{
int x_pos;
int y_pos;
struct node *prev;
struct node *next;
} Snake_Node;
struct position /* food position structure */
{
int x_pos;
int y_pos;
} ;
void Init_Disp(); /* init and display the interface */
void Food_Disp(); /* display the food position */
void Wrap_Up(); /* turn off the curses */
void Key_Ctrl(); /* using keyboard to control snake */
int set_ticker(int n_msecs);/* ticker */
void DLL_Snake_Create(); /* create double linked list*/
void DLL_Snake_Insert(int x, int y); /* insert node */
void DLL_Snake_Delete_Node(); /* delete a node */
void DLL_Snake_Delete(); /* delete all the linked list */
void Snake_Move(); /* control the snake move and judge */
void gameover(int n); /* different n means different state */
/* Filename: snake.c version:1.0 date: 2011/08/22
* Author: Dream Fly blog: blog.csdn.net/jjzhoujun2010
* Usage: 'f' means speed up, 's' means speed down, 'q' means quit;
* Navigation key controls the snake moving.
* Compile: gcc snake.c -lncurses -o snake
*/
#include<stdio.h>
#include<stdlib.h>
#include<ncurses.h>
#include<sys/time.h>
#include<signal.h>
#include"snake.h"
struct position food; /* food position */
Snake_Node *head, *tail; /* double linked list's head and tail */
int x_dir = 1, y_dir = 0; /* init dirction of the snake moving */
int ttm = 5, ttg = 5; /* two timers defined to control speed */
void main(void)
{
Init_Disp(); /* init and display the interface */
Food_Disp(); /* display food */
DLL_Snake_Create(); /* create double linked list and display snake*/
signal(SIGALRM, Snake_Move);
set_ticker(DFL_SPEED);
Key_Ctrl(); /* using keyboard to control snake */
Wrap_Up(); /* turn off the curses */
}
/* Function: Init_Disp()
* Usage: init and display the interface
* Return: none
*/
void Init_Disp()
{
char wall = ' ';
int i, j;
initscr();
cbreak(); /* put termial to CBREAK mode */
noecho();
curs_set(0); /* set cursor invisible */
/* display some message about title and wall */
attrset(A_NORMAL); /* set NORMAL first */
attron(A_REVERSE); /* turn on REVERSE to display the wall */
for(i = 0; i < LINES; i++)
{
mvaddch(i, LEFT_EDGE, wall);
mvaddch(i, RIGHT_EDGE, wall);
}
for(j = 0; j < COLS; j++)
{
mvaddch(0, j, '=');
mvaddch(TOP_ROW, j, wall);
mvaddch(BOT_ROW, j, wall);
}
attroff(A_REVERSE); /* turn off REVERSE */
mvaddstr(1, 2, "Game: snake version: 1.0 date: 2011/08/22");
mvaddstr(2, 2, "Author: Dream Fly Blog: blog.csdn.net/jjzhoujun2010");
mvaddstr(3, 2, "Usage: Press 'f' to speed up, 's' to speed down,'q' to quit.");
mvaddstr(4, 2, " Nagivation key controls snake moving.");
refresh();
}
/* Function: Food_Disp()
* Usage: display food position
* Return: none
*/
void Food_Disp()
{
srand(time(0));
food.x_pos = rand() % (COLS - 2) + 1;
food.y_pos = rand() % (LINES - TOP_ROW - 2) + TOP_ROW + 1;
mvaddch(food.y_pos, food.x_pos, FOOD_SYMBOL);/* display the food */
refresh();
}
/* Function: DLL_Snake_Create()
* Usage: create double linked list, and display the snake first node
* Return: none
*/
void DLL_Snake_Create()
{
Snake_Node *temp = (Snake_Node *)malloc(sizeof(Snake_Node));
head = (Snake_Node *)malloc(sizeof(Snake_Node));
tail = (Snake_Node *)malloc(sizeof(Snake_Node));
if(temp == NULL || head == NULL || tail == NULL)
perror("malloc");
temp->x_pos = 5;
temp->y_pos = 10;
head->prev =NULL;
tail->next = NULL;
head->next = temp;
temp->next = tail;
tail->prev = temp;
temp->prev = head;
mvaddch(temp->y_pos, temp->x_pos, SNAKE_SYMBOL);
refresh();
}
/* Function: Snake_Move()
* Usage: use Navigation key to control snake moving, and judge
* if the snake touch the food.
* Return:
*/
void Snake_Move()
{
static int length = 1; /* length of snake */
int Length_Flag = 0; /* default snake's length no change */
int moved = 0;
signal(SIGALRM, SIG_IGN);
/* judge if the snake crash the wall */
if((head->next->x_pos == RIGHT_EDGE-1 && x_dir == 1)
|| (head->next->x_pos == LEFT_EDGE+1 && x_dir == -1)
|| (head->next->y_pos == TOP_ROW+1 && y_dir == -1)
|| (head->next->y_pos == BOT_ROW-1 && y_dir == 1))
{
gameover(1);
}
/* judge if the snake crash itself */
if(mvinch(head->next->y_pos + y_dir, head->next->x_pos + x_dir) == '@')
gameover(2);
if(ttm > 0 && ttg-- == 1)
{
/* snake moves */
DLL_Snake_Insert(head->next->x_pos + x_dir, head->next->y_pos + y_dir);
ttg = ttm; /* reset */
moved = 1; /* snake can move */
}
if(moved)
{
/* snake eat the food */
if(head->next->x_pos == food.x_pos && head->next->y_pos == food.y_pos)
{
Length_Flag = 1;
length++;
/* Mission Complete */
if(length >= MAX_NODE)
gameover(0);
/* reset display the food randomly */
Food_Disp();
}
if(Length_Flag == 0)
{
/* delete the tail->prev node */
mvaddch(tail->prev->y_pos, tail->prev->x_pos, ' ');
DLL_Snake_Delete_Node();
}
mvaddch(head->next->y_pos, head->next->x_pos, SNAKE_SYMBOL);
refresh();
}
signal(SIGALRM, Snake_Move);
}
/* Function: set_ticker(number_of_milliseconds)
* Usage: arrange for interval timer to issue SIGALRM's at regular intervals
* Return: -1 on error, 0 for ok
* arg in milliseconds, converted into whole seconds and microseconds
* note: set_ticker(0) turns off ticker
*/
int set_ticker(int n_msecs)
{
struct itimerval new_timeset;
long n_sec, n_usecs;
n_sec = n_msecs / 1000; /* int second part */
n_usecs = (n_msecs % 1000) * 1000L; /* microsecond part */
new_timeset.it_interval.tv_sec = n_sec; /* set reload */
new_timeset.it_interval.tv_usec = n_usecs;
new_timeset.it_value.tv_sec = n_sec; /* set new ticker value */
new_timeset.it_value.tv_usec = n_usecs;
return setitimer(ITIMER_REAL, &new_timeset, NULL);
}
/* Function: Wrap_Up()
* Usage: turn off the curses
* Return: none
*/
void Wrap_Up()
{
set_ticker(0); /* turn off the timer */
getchar();
endwin();
exit(0);
}
/* Function: Key_Ctrl()
* Usage: using keyboard to control snake action; 'f' means speed up,
* 's' means speed down, 'q' means quit, navigation key control direction.
* Return: none
*/
void Key_Ctrl()
{
int c;
keypad(stdscr, true); /* use little keyboard Navigation Key */
while(c = getch(), c != 'q')
{
if(c == 'f')
{
if(ttm == 1)
continue;
ttm--;
}
else if(c == 's')
{
if(ttm == 8)
continue;
ttm++;
}
if(c == KEY_LEFT)
{
if(tail->prev->prev->prev != NULL && x_dir == 1 && y_dir == 0)
continue; /* it can't turn reverse when snake have length */
x_dir = -1;
y_dir = 0;
}
else if(c == KEY_RIGHT)
{
if(tail->prev->prev->prev != NULL && x_dir == -1 && y_dir == 0)
continue;
x_dir = 1;
y_dir = 0;
}
else if(c == KEY_UP)
{
if(tail->prev->prev->prev != NULL && x_dir == 0 && y_dir == 1)
continue;
x_dir = 0;
y_dir = -1;
}
else if(c == KEY_DOWN)
{
if(tail->prev->prev->prev != NULL && x_dir == 0 && y_dir == -1)
continue;
x_dir = 0;
y_dir = 1;
}
}
}
/* Function: DLL_Snake_Insert(int x, int y)
* Usage: Insert node in the snake.
* Return: none
*/
void DLL_Snake_Insert(int x, int y)
{
Snake_Node *temp = (Snake_Node *)malloc(sizeof(Snake_Node));
if(temp == NULL)
perror("malloc");
temp->x_pos = x;
temp->y_pos = y;
temp->prev = head->next->prev;
head->next->prev = temp;
temp->next = head->next;
head->next = temp;
}
/* Function: gameover(int n)
* Usage: gameover(0) means Mission Completes; gameover(1) means crashing
* the wall; gameover(2) means crash itself.
* Return: none
*/
void gameover(int n)
{
switch(n)
{
case 0:
mvaddstr(LINES / 2, COLS / 3 - 4, "Mission Completes,press any key to exit.\n");
break;
case 1:
mvaddstr(LINES/2, COLS/3 - 4, "Game Over, crash the wall,press any key to exit.\n");
break;
case 2:
mvaddstr(LINES/2, COLS/3 - 4, "Game Over, crash yourself,press any key to exit.\n");
break;
default:
break;
}
refresh();
/* delete the whole double linked list */
DLL_Snake_Delete();
Wrap_Up();
}
/* Function: DLL_Snake_Delete_Node()
* Usage: delete a tail node, not the whole linked list
* Return: none
*/
void DLL_Snake_Delete_Node()
{
Snake_Node *temp;
temp = tail->prev;
tail->prev = tail->prev->prev;
temp->prev->next = tail;
free(temp);
}
/* Function: DLL_Snake_Delete()
* Usage: delete the whole double linked list
* Return: none
*/
void DLL_Snake_Delete()
{
while(head->next != tail)
DLL_Snake_Delete_Node();
head->next = tail->prev = NULL;
free(head);
free(tail);
}
通過本程序可以加強自己對信號間隔計數器的理解,以及終端圖形編程的理解和了解設計的此類遊戲的一般思路,也實現了我大一學習C語言所想要實現的想法。本人第一次在CSDN上面把自己的一些編程想法寫的比較詳細,不足之處還請指出,共同討論。
原創文章,歡迎轉載,轉載請註明:https://ifish.site/linux_program_snake.html
CSDN: blog.csdn.net/jjzhoujun2010
作者:Dream Fly
參考資料:《Unix/Linux編程實踐教程》 (美)Bruce Molay 著 楊宗源 黃海濤 譯 清華大學出版社
http://note.sdo.com/my#!note/preview/xJ29Q~jAYzOpnM01Y0004K curses庫的使用
http://blog.sina.com.cn/s/blog_4c3b26e10100sd7b.html 貪喫蛇雙鏈表模型