貪吃蛇身設計成了一個循環隊列,可以減少最多的移動,貪吃蛇本身保存在一個預分配的數組中,用兩個指針表示當前隊列頭和隊列尾。貪吃蛇最少會有一個節點,所以隊列頭和隊列尾不會重疊。
當指針超過最後一個預分配結點時,會繞回開頭,這個操作代碼很簡單。
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
效果如下:
推薦閱讀: