如題。100行包括預處理命令,聲明之類的。排除很多語句塞在一行的情況。


中午試了一下寫個極簡的(除了核心玩法什麼都沒有),不壓縮大概 32 行。使用跨平台的 Ncurses 庫做控制台的輸入輸出。

代碼

#include &
#include & // usleep()
#include & // rand()
#include & // time()
#define W 40
#define H 24
int m[W * H], q[W * H], p = H / 2 * W + (W / 2), a, h = 0, t = 0, d = 1, i;
int main(void) {
initscr(); noecho(); keypad(stdscr, 1); nodelay(stdscr, 1); curs_set(0);
srand(time(NULL));
for (i = 0; i &< W * H; i++) m[i] = !(i / W % (H - 1) i % W % (W - 1)); m[q[t = (t + 1) % (W * H)] = p] = 1; do { a = rand() % (W * H); } while (m[a]); while ((i = getch()) != 27) { if (i == KEY_UP d != W) d = -W; else if (i == KEY_DOWN d != -W) d = W; else if (i == KEY_LEFT d != 1) d = -1; else if (i == KEY_RIGHT d != -1) d = 1; if (m[p += d]) break; m[q[t = (t + 1) % (W * H)] = p] = 1; if (p == a) do { a = rand() % (W * H); } while (m[a]); else m[q[h = (h + 1) % (W * H)]] = 0; for (i = 0; i &< W * H; i++) mvaddstr(i / W, (i % W) * 2, m[i] ? "[]" : " "); mvaddstr(a / W, (a % W) * 2, "()"); refresh(); usleep(100000); } while (getch() == ERR); endwin(); }

編譯與運行結果

gcc -lncurses snake.c ./a.out

代碼解釋

  • W, H:地圖的寬度和高度
  • m:地圖數組,0 表示無障礙物,1 表是障礙物(包括蛇及牆)
  • q:循環隊列,存儲蛇的坐標
  • p:當前位置,以 y * H + w 形式存儲坐標
  • a:蘋果位置
  • h, t:隊列的頭和尾(有點混淆的是,蛇尾是隊列的頭)
  • d:當前的步進(delta)

把其遊戲循環寫成偽代碼的話:

當沒有按 ESC 離開時
  檢測按鍵改變方向
  使用方向移動位置
  如新位置碰撞障礙物,跳出
  在地圖中的新位置設為障礙物
  在隊列中壓入新位置
  如吃到蘋果,計算新蘋果位置;否則,彈出隊列,並清空該位置的障礙物
  渲染地圖及蘋果


更新2020/2/14:壓縮

似乎很多人都想知道壓縮後大概是多少代碼,中午試了一下,把非必要的代碼去除後,以一行80個字元限制來說,共 6 行:

main(){int W=40,H=24,S=W*H,m[S],q[S],p=H/2*W+W/2,a,h=0,t=1,d=1,i=S;initscr();
noecho();keypad(stdscr,1);nodelay(stdscr,1);curs_set(0);srand(time(0));while(i--
)m[i]=!(i/W%(H-1)i%W%(W-1));m[q[t]=p]=1;r:while(m[a=rand()%S]);for(;(i=getch()
)-27;refresh(),usleep(100000)){d=!(i-=258)d+W?W:!--id-W?-W:!--id-1?-1:!--i
d+1?1:d;if(m[p+=d])break;m[q[t=++t%S]=p]=1;if(p-a)m[q[h=++h%S]]=0;else goto r;
for(i=S;i--;)mvaddstr(i/W,i%W*2,m[i]?"[]":" ");mvaddstr(a/W,a%W*2 ,"()");}}

這樣編譯:

clang -Wno-everything -include curses.h -lncurses a.c

統計一下這 6 行里,變數定義大概花了 1 行,輸出 2 行,輸入 1 行,餘下的 2 行是遊戲邏輯。而按字元來算是472個。


這段代碼會發生內存泄漏,特此備註。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

沒學到一個小小的貪吃蛇會有怎麼多贊,當時想能有100個贊已經很不錯了,這個完全超出我的預期啊,啊啊啊~飄了飄了~

小聲bb:其實這個代碼寫得不咋地,有些函數為了能用 return 語句,故意寫了一個返回值,但是返回值沒有用到,還有就是 Run 這個函數本來不應該接受一個參數,但是為了省一行的代碼,把init寫進去,才出此下策。這樣寫的後果就是代碼不符合我的預期,不這樣寫的話又不符合題主的要求,兩難啊。所以我才會比較鬱悶,說了點髒話,實在抱歉 。(可能自己水平不夠,代碼還有很多優化空間,我看別的答主根本就用不到100行)

繼續努力吧 ~

~~~~~~~~~~~~原答案~~~~~~~~~~~

我本來懷著很好的心情來寫這個100的貪吃蛇的,不過寫到後面就出口成髒了。

mmp,寫到後面很是鬱悶。有圖為證。

忍不住說髒話了

剛剛好一百行代碼,我去,這是那我之前寫的350多行代碼壓縮的得到的。

寫得很垃圾,有些東西就是亂寫一通,就為了壓縮一兩行邏輯代碼。

然後UI部分,這個也沒辦法壓縮,能壓縮的都在核心代碼上面了。

盡量體現出來格式,但是難受啊

貪吃蛇的核心代碼就在這個switch裡面了,10行。也就是佔比 1/10。其實還可以...

最後的效果圖是這個

按空格開始和暫停

直接貼代碼吧。

#include &
#include &
#include &
#include &
#define PANIC(err) (fprintf(stderr,"PANIC Line %d : %s",__LINE__,err),exit(-1),1)
#define PANICIFNULL(EXP) ((EXP)==NULL PANIC("NULL"))
typedef enum { EMPTY=0, WALL, BODY, FOOD } MAP;
typedef int POSITION;
struct { int color; const char* shape; } UI[] = {
{2,""},{4,"□"},{6,""},{4,"●"}
};
struct {
int WIDTH, HEIGHT, direction, delay;
MAP* map;
POSITION* body, head, tail, len;
} C;
void initConsole(int width, int height) {
char cmd[100];
sprintf_s(cmd,100, "mode con cols=%d lines=%d title C語言貪吃蛇 By dreamer2q %s", width, height,__DATE__);
system(cmd);
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cur_info;
GetConsoleCursorInfo(handle, cur_info);
cur_info.bVisible = FALSE;
SetConsoleCursorInfo(handle, cur_info);
}
void updatePosition(POSITION pos) {
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD coord = { (pos % (C.WIDTH)) * 2 ,pos / (C.WIDTH) };
SetConsoleCursorPosition(handle, coord);
SetConsoleTextAttribute(handle, UI[C.map[pos]].color);
printf("%s", UI[C.map[pos]].shape);
}
MAP food(int t) {
POSITION pos = (rand() % ((C.WIDTH - 2) * (C.HEIGHT - 2))) + C.WIDTH + 1;
if (C.map[pos]) return food(t);
else return (C.map[pos] = FOOD) ? updatePosition(pos), BODY : BODY;
}
int init() {
C.WIDTH = C.HEIGHT = 30;
initConsole(C.WIDTH * 2, C.HEIGHT);
PANICIFNULL(C.map = (MAP*)malloc((C.WIDTH) * (C.HEIGHT) * sizeof(MAP)));
PANICIFNULL(C.body = (POSITION*)malloc(C.WIDTH * C.HEIGHT * sizeof(POSITION)));
C.head = (C.len = 3) - 1;
C.direction = (C.tail = 0) + 1;
C.delay = -150;
memset(C.map, EMPTY, (C.WIDTH) * (C.HEIGHT) * sizeof(MAP));
for (int i = 0; i &< (C.WIDTH) * (C.HEIGHT); i++) { i &< C.WIDTH (C.map[i] = C.map[C.WIDTH * (C.HEIGHT - 1) + i] = WALL); i &< C.HEIGHT (C.map[C.WIDTH * i] = C.map[C.WIDTH * i + C.WIDTH - 1] = WALL); i &< C.len (C.map[C.body[i] = C.WIDTH * C.HEIGHT / 2 + C.WIDTH / 2 - 1 + i] = BODY); updatePosition(i); } srand(time(NULL)); return food(0); } int Run(int shit) { int prv = 77; while (1) { if (_kbhit()) { int t = _getch(); if ((prv + t) == 152)continue; switch (t) { case 72:C.direction = -C.WIDTH; break; case 80:C.direction = C.WIDTH; break; case 75:C.direction = -1; break; case 77:C.direction = 1; break; case :C.delay = -C.delay; break; default:continue; } prv = t; } #define INC(p) (((p)+1)%(C.WIDTH*C.HEIGHT)) if (C.delay &> 0) Sleep(C.delay);
else continue;
switch (C.map[C.body[INC(C.head)] = C.body[C.head] + C.direction]) {
case FOOD:food(C.len = -C.len - 1);
case EMPTY:
C.map[C.body[C.head = INC(C.head)]] = BODY;
updatePosition(C.body[C.head]);
if (C.len &> 0) updatePosition((C.map[C.body[C.tail]] = EMPTY) ? BODY : C.body[C.tail]), C.tail = INC(C.tail);
else C.len = -C.len;
break;
case WALL:case BODY:
return -1;//dead
}
}
}
int main() {
while (1) {
initConsole(25, 10);
printf("
C語言貪吃蛇

1. 開始遊戲
2. 關於
q. 退出
%");
switch (_getch()) {
case q:return 0;
case 2:MessageBoxA(GetConsoleWindow(), "100行代碼?", "有病吧你?", MB_OK|MB_ICONASTERISK); continue;
case 1:Run(init());
MessageBoxA(GetConsoleWindow(), "你死了。有病去看看吧", "SHIT", MB_OK | MB_ICONERROR);
}
}
}

覺得還行就thumbup,寫代碼也不容易


更新:好多人問那個18行版本,我也給一個壓縮版本吧。22行,實在不想進一步犧牲可讀性了。除了秀這一下,並沒有什麼意義。

#include & /* setlocale, 使用特殊字元 */
#include & /* 繪圖 */
#include & /* 隨機數 */
#define DRAW(pos, c) !mvprintw(pos / W, pos % W * 2, c)
int const W = 30, H = 30, MSIZE = W * H, MoveHints[4] = {W, 2 * W + 1, 2 + W, 1};
int Board[MSIZE] = {0}, Snake[MSIZE] = {W / 2 + H * W / 2};
int main() {
/* 使用特殊字元,初始化屏幕, 隱藏游標 使用游標鍵控制, 不緩衝按鍵, 隨機種子 */
setlocale(0, ""), initscr(), curs_set(0), keypad(stdscr, TRUE), nodelay(stdscr, TRUE), srand(0);
int food = Snake[0] - 1, h = 0, t = 0, x = 0, moveDirection = 0;
for (int i = 0, j = 1; i != MSIZE; i += j, j = (i &< W || i &>= MSIZE - W) ? 1 : W - j) Board[i] = DRAW(i, "");
do {
int key = (KEY_LEFT == x) + (KEY_RIGHT == x) * 3 + (KEY_UP == x) * 4 + (KEY_DOWN == x) * 2;
if (key (key - 1 1) != (moveDirection 1)) moveDirection = key - 1;
int next = Snake[h] + MoveHints[moveDirection] - W - 1;
if (Board[next]) break; /* end game */
Board[Snake[h = (h == MSIZE - 1) ? 0 : h + 1] = next] = DRAW(next, "");
if (next != food) Board[Snake[t]] = !DRAW(Snake[t], " "), t = (t == MSIZE - 1) ? 0 : t + 1;
while (Board[food] || !DRAW(food, "◆")) food = rand() / (RAND_MAX / ((W - 1) * (H - 1))) + W + 1;
timeout(500); /* 等待按鍵的最大時間 */
} while ((x = getch()) != q);
endwin();
}

這個版本基本上與下面的解析基本一致,主要改進是從Milo Yip 的答案中抄的通過Board記錄蛇的位置,優化了is_snake函數。以及大量語句壓縮。


役情原因,在家無事,來優化一下,保證可讀性的情況下壓縮在100行以內。使用ncurses庫,完成命令行繪圖和非同步按鍵處理。window下應有pdcurses,可以相應處理。

關鍵是一些數據結構設計,如果用C++還可以少很多代碼。

這類小遊戲可以簡單分成繪圖(clear和go),輸入和邏輯處理(process)兩到三個部分,基本遊戲循環如下:

int main() {
clear();
while (process() != q) {
go();
timeout(500);
}
}

繪圖部分為了最大程度的復用,可以只使用一個函數(draw)來繪一個圖標,貪吃蛇身,蘋果,底圖邊框都可以復用。示例如下:

如何表示一個坐標位,用一個整數就可以。基本操作如下

typedef unsigned int POS;
const POS W = 30, H = 30, MSIZE = (W - 1) * (H - 1);

#define X(pos) (pos % W) /* 分量 */
#define Y(pos) (pos / W) /* 分量 */
#define XY(x, y) ((y)*W + (x))

當然也可以用結構體,但後續函數需要用指針操作,代碼會多一點。

typedef struct Pos { unsigned short x, y; } POS;

繪邊框,底圖和食物

void clear() {
for (POS i = 0; i != W * H; ++i) {
if (Y(i) == 0 || Y(i) == H - 1 || X(i) == 0 || X(i) == W - 1) DRAW(i, "");
// else DRAW(i, 「 」)
}
new_food();
}

else 部分是繪底圖,我的平台上不用,如果其它平台上需要也可以繪。這裡利用了POS是一個整數,把二維循環壓縮成了一維。

繪食物,這裡可以暫時不看is_snake這個函數。

void new_food() {
do {
food = rand() / (RAND_MAX / MSIZE) + XY(1, 1);
} while (is_snake(food));
DRAW(food, "◆");
}

這裡同樣利用了POS是一個整數,把生成食物的位置控制在邊框以內。

貪吃蛇身設計成了一個循環隊列,可以減少最多的移動,貪吃蛇本身保存在一個預分配的數組中,用兩個指針表示當前隊列頭和隊列尾。貪吃蛇最少會有一個節點,所以隊列頭和隊列尾不會重疊。

當指針超過最後一個預分配結點時,會繞回開頭,這個操作代碼很簡單。

POS snake[20] = {XY(H / 2, W / 2)};
POS *head = snake, *tail = snake,
*end = snake + sizeof(snake) / sizeof(POS);
#define INC(p) if (++(p) == end) (p) = snake

判斷食物是否在蛇身上,會先在head頭上放一個哨兵,再從tail查找到head。

bool is_snake(POS pos) {
POS *p = tail, *pflag = head;
INC(pflag);
*pflag = pos;
while (*p != pos) {
INC(p);
}
return p != pflag;
}

蛇移動邏輯如下

POS const MoveHints[4] = {XY(0, 1), XY(1, 2), XY(2, 1), XY(1, 0)};
int moveDirection = 2;

void go() {
POS next = *head + MoveHints[moveDirection] - XY(1, 1);
if (X(*head) == 0 || Y(*head) % H == 0 || is_snake(next)) {
/* end game */
exit(0);
}
INC(head);
*head = next;
DRAW(*head, "");

if (*head == food) {
new_food();
return;
}
DRAW(*tail, " ");
INC(tail);
}

蛇會保持一個移動方向(moveDirection),如果用戶不按鍵,蛇則一直向這個方向前進(MoveHints)。一直到撞到什麼東西,則遊戲結束。

蛇移動就是頭指針向前,多繪一個節點。如果這個位置有食物,那麼蛇增長一格,不需要消除自己的尾巴。如果沒有食物,則需要消除尾巴節點,尾指針也向前移動。此處理未處理預分配空間不足的情況,理論上這個空間應等於MSIZE。

按鍵處理部分,特別需注意getch在沒有按鍵時,也會馬上返回。

int process() {
int x = getch();

POS key = (KEY_LEFT == x) + (KEY_RIGHT == x) * 3 + (KEY_UP == x) * 4 +
(KEY_DOWN == x) * 2;
if (key (key - 1 1) != (moveDirection 1)) {
moveDirection = key - 1;
}
return x;
}

這裡只用到了四個方向和Q鍵,用於退出。

Key的計算:左和右安排在1和3,上和下安排在2和4,四個邏輯表達式只有一個會取值為1,所以可以計算得到方向鍵。當蛇向左走時,右鍵不能用;向上走時,下鍵不能用,反之也是。這個key數值設計保證,key 的最低位與方向(moveDirection 1)相同。最後,moveDirection的範圍是0-3,而key的範圍是1-4,所以會有減一操作。

完整代碼在 https://gitee.com/hl4/codes/y3p5lzke68fj4xhnamrq045

環境所限,只在mac下測試過。


UPD 20191229

重寫了一個C89兼容的…大概吧…

總之在VC6下能跑起來了(對stdafx.h的依賴不會去除不是我的問題哦)…

然後…諸位…

快換一個完整支持C99的編譯器吧!!!

不過代碼又變髒了好多…對不起我已經不純潔了嚶嚶嚶…

恰好100,如下

#include &
#define MAX_WIDTH (30)
#define MAX_HEIGHT (30)
#define MAX_SIZE (MAX_WIDTH*MAX_HEIGHT)
#define inc(x) (x=(x+1)%MAX_SIZE)
#define cmppos(left, right) (left.X == right.X left.Y == right.Y)
#define isborder(pos) (pos.X &>= MAX_WIDTH+1 || pos.X &<= 0 || pos.Y &>= MAX_HEIGHT+1 || pos.Y &<= 0) COORD snake[MAX_SIZE] = { {MAX_WIDTH / 2,MAX_HEIGHT / 2} }; COORD food = { MAX_WIDTH + 1, MAX_HEIGHT + 1 }, head = { MAX_WIDTH / 2, MAX_HEIGHT / 2 }; unsigned int QueueHead = 1, QueueTail = 0, movement = 2; const signed int MoveHints[4] = { 0 + 1 * 3,1 + 0 * 3,2 + 1 * 3,1 + 2 * 3 }; DWORD retdword; int IsSnake(COORD chk) { unsigned int ptr = QueueTail; while (ptr != QueueHead) { if (cmppos(snake[ptr], chk) == 1) return 1; inc(ptr); } return cmppos(snake[ptr], chk); } void genfood() { do { food.X = rand() % MAX_WIDTH + 1; food.Y = rand() % MAX_HEIGHT + 1; } while (IsSnake(food)); WriteConsoleOutputCharacterA(GetStdHandle(STD_OUTPUT_HANDLE), "o", 1, food, retdword); } void cls(int mode) { COORD pos={0,0}; for(pos.X=0;pos.X&VK_DOWN)
continue;
if ((((ir.Event.KeyEvent.wVirtualKeyCode - VK_LEFT) ^ movement) 1) == 0)
continue;
movement = ir.Event.KeyEvent.wVirtualKeyCode - VK_LEFT;
}
}
int main()
{
if (FALSE == GetStdHandle(STD_OUTPUT_HANDLE))
AllocConsole();
SMALL_RECT rect = { 0,0,MAX_WIDTH + 1,MAX_HEIGHT + 1 };
SetConsoleWindowInfo(GetStdHandle(STD_OUTPUT_HANDLE), TRUE, rect);
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),food);
srand(GetTickCount());
cls(1);
genfood();
while (1)
{
ChkKey();
head.X += (SHORT)(MoveHints[movement] % 3 - 1);
head.Y += (SHORT)(MoveHints[movement] / 3 - 1);
(cmppos(food, head)) ? genfood() : removetail();
go(head);
Sleep(500);
}
}


能…

試著寫了一下…總之我的原則是盡量避免不規範表達,然後在100行內盡量保證可讀性…

不過畢竟為了壓到100行的話…寫了點比較髒的東西…

如果發現這裡有不規範的用法的話…請務必提醒我…多謝了…

#include &
#include &
#define MAX_WIDTH (30)
#define MAX_HEIGHT (30)
#define MAX_SIZE (MAX_WIDTH*MAX_HEIGHT)
typedef struct POS
{
signed int x;
signed int y;
}POS;
const signed int MoveHints[4] = { 0+1*3,1+0*3,2+1*3,1+2*3 };
POS snake[MAX_SIZE] = { {MAX_WIDTH / 2,MAX_HEIGHT / 2} }, food;
unsigned int QueueHead = 0, QueueTail = 0, movement = 2;
void inc(unsigned int* pint)
{
if (++(*pint) == MAX_SIZE)
*pint = 0;
}
#define cmppos(left, right) ((left.x == right.x left.y == right.y) ? 1 : 0)
int IsSnake(POS chk)
{
unsigned int ptr = QueueTail;
while (ptr != QueueHead)
{
if (cmppos(snake[ptr], chk) == 1)
return 1;
inc(ptr);
}
return cmppos(snake[ptr], chk);
}
void genfood()
{
do
{
food.x = rand() % MAX_WIDTH;
food.y = rand() % MAX_HEIGHT;
}while (IsSnake(food));
printf(" 33[%d;%dHo 33[%d;%dH", food.y,food.x,MAX_HEIGHT,MAX_WIDTH);
}
void cls(int mode) {
for (int i = 0; i &<= MAX_HEIGHT; i++) for (int j = 0; j &<= MAX_WIDTH; j++) if(mode==1 (i==MAX_HEIGHT||j==MAX_WIDTH)) printf(" 33[%d;%dH#", i, j); else printf(" 33[%d;%dH ", i, j); } void go(POS p) { if (p.x &>= MAX_WIDTH || p.x &<= 0 || p.y &>= MAX_HEIGHT || p.y &<= 0 || IsSnake(p)) { cls(0); printf(" 33[0;0HDIE! Score:%d ",(QueueHead+MAX_SIZE-QueueTail)%MAX_SIZE); exit(0); } inc(QueueHead); snake[QueueHead] = p; printf(" 33[%d;%dHx 33[%d;%dH", p.y, p.x,MAX_HEIGHT,MAX_WIDTH); } void removetail() { printf(" 33[%d;%dH 33[%d;%dH", snake[QueueTail].y, snake[QueueTail].x,MAX_HEIGHT,MAX_WIDTH); inc(QueueTail); } void ChkKey() { while (1) { INPUT_RECORD ir; DWORD dw; PeekConsoleInput(GetStdHandle(STD_INPUT_HANDLE), ir, 1, dw); if (dw==0) break; ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), ir, 1, dw); if (ir.EventType != KEY_EVENT || !ir.Event.KeyEvent.bKeyDown || ir.Event.KeyEvent.wVirtualKeyCode&VK_DOWN)
continue;
if ((((ir.Event.KeyEvent.wVirtualKeyCode -VK_LEFT)^movement)1)==0)
continue;
movement = ir.Event.KeyEvent.wVirtualKeyCode - VK_LEFT;
}
}
int main()
{
srand(GetTickCount());
cls(1);
genfood();
while (1)
{
ChkKey();
POS t = (POS){
.x = snake[QueueHead].x + MoveHints[movement] % 3 - 1,
.y = snake[QueueHead].y + MoveHints[movement] / 3 - 1
};
go(t);
(cmppos(food, t)) ? genfood() : removetail();
Sleep(500);
}
}


不想額外調用ncurses或者conio,所以代碼就冗長了一些(86行,含空行和注釋)。當然那一串常量定義可以省略掉,但那樣可讀性就更差了:

/* NOTE: Only tested on x86-64 Linux / macOS. */
#include & #include &
#include &
#include &
#include &
#include &
#include &

const int kWidth = 77;
const int kHeight = 21;
const int kMapSize = (kWidth + 2) * (kHeight + 2);
const int kHorizontalTickTimeUs = 100000;
const int kVerticalTickTimeUs = 175000; /* Vertical moves look faster. */
const int kFoodFlag = -1;
const int kEmpty = 0;
const int kWall = INT_MAX;
const char kClearScreen[] = "e[1;1He[2J";
const char kEscapeChar = 27;
const char kEscapeCharBeforeArrow = 91;
const char kKeyUpChar = 65; /* Not A! Same below. */
const char kKeyDownChar = 66;
const char kKeyLeftChar = 68;
const char kKeyRightChar = 67;

struct termios old_attr;

void restore_termios() { tcsetattr(STDIN_FILENO, TCSANOW, old_attr); }

int main(int argc, char* argv[]) {
struct termios attr;
tcgetattr(STDIN_FILENO, attr);
memcpy(old_attr, attr, sizeof(attr));
attr.c_lflag = ~(ICANON | ECHO);
attr.c_cc[VMIN] = attr.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, attr); /* Get chars without a newline. */
atexit(restore_termios); /* Restore old settings at exit. */
int map[kMapSize];
for (int i = 0; i &< kMapSize; ++i) { map[i] = i / (kWidth + 2) &< 1 || i / (kWidth + 2) &> kHeight ||
i % (kWidth + 2) &< 1 || i % (kWidth + 2) &> kWidth ? kWall : kEmpty;
}
int tick = 1, length = 1, move = 1, new_move = 1, head = kMapSize / 2 + 1;
int food = 0;
srand(time(/* timer = */ NULL));
do { food = rand() % kMapSize; } while (food == head || map[food] != kEmpty);
map[food] = kFoodFlag;
while (length &< kWidth * kHeight) { struct timeval zero_duration = { .tv_sec = 0L, .tv_usec = 0L }; fd_set fds; FD_ZERO(fds); FD_SET(/* fd = */ 0, fds); while (!select(FD_SETSIZE, fds, /* writefds = */ NULL, /* exceptfds = */ NULL, zero_duration)) { if (new_move + move != 0) { move = new_move; } if (map[head] &> tick - length) { return EXIT_FAILURE; }
if (map[head] == kFoodFlag) {
if (++length == kWidth * kHeight) { return EXIT_SUCCESS; }
do { food = rand() % kMapSize; }
while (food == head || map[food] &> tick - length);
map[food] = kFoodFlag;
}
map[head] = tick;
for (int i = 0; i &< kMapSize; ++i) { printf("%c", map[i] == kWall ? X : map[i] &> tick - length ? O :
map[i] == kFoodFlag ? + : );
printf("%s", i % (kWidth + 2) &> kWidth ? "
" : "");
}
printf(kClearScreen);
usleep(abs(move) &> 1 ? kVerticalTickTimeUs : kHorizontalTickTimeUs);
head += move;
if (++tick == kWall) { return EXIT_FAILURE; }
zero_duration.tv_sec = zero_duration.tv_usec = 0L;
FD_ZERO(fds);
FD_SET(/* fd = */ 0, fds);
}
char c; /* Ignore non-escape chars. Quit for real escapes. */
if ((c = getchar()) != kEscapeChar) { continue; }
if ((c = getchar()) != kEscapeCharBeforeArrow) { return 0; }
c = getchar();
new_move = c == kKeyUpChar ? -(kWidth + 2) :
c == kKeyDownChar ? kWidth + 2 : c == kKeyLeftChar ? -1 :
c == kKeyRightChar ? 1 : move;
}
return 0;
}

這樣寫用gcc編譯的時候,不需要鏈接額外的庫:

gcc snake.c -o snake

效果如下:


推薦閱讀:
相关文章