贪吃蛇身设计成了一个循环队列,可以减少最多的移动,贪吃蛇本身保存在一个预分配的数组中,用两个指针表示当前队列头和队列尾。贪吃蛇最少会有一个节点,所以队列头和队列尾不会重叠。
当指针超过最后一个预分配结点时,会绕回开头,这个操作代码很简单。
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
效果如下:
推荐阅读: