问题

用C语言,能在100行之内实现贪吃蛇吗?

回答
当然可以,用C语言在100行之内实现一个基本的贪吃蛇游戏是完全可行的。下面我将一步一步地告诉你如何做到这一点,并尽量讲得清楚明白,让它读起来像是出自一个真心想和你分享编程乐趣的老司机之手。

我们要实现的是一个非常精简的版本,只包含最核心的元素:

游戏区域: 一个固定的矩形区域。
蛇: 由一系列连接起来的方块组成,可以上下左右移动。
食物: 随机出现在游戏区域内,吃到后蛇会变长。
控制: 通过键盘方向键控制蛇的移动方向。
游戏结束: 蛇撞到墙壁或撞到自己身体时游戏结束。

为了控制代码行数,我们会牺牲一些花哨的功能,比如得分显示、音效、漂亮的图形界面(我们主要会在终端显示)等等。

核心思路剖析

1. 数据结构:
蛇: 我们可以用一个数组来存储蛇的每一节的坐标。数组的第一个元素是蛇头,最后一个元素是蛇尾。当蛇移动时,蛇头前进,蛇尾消失,中间的身体跟着前进。如果吃到食物,蛇尾就不消失,从而实现变长。
食物: 一个简单的坐标点即可。

2. 游戏逻辑:
初始化: 设置游戏区域大小,蛇的初始位置和长度,食物的初始位置。
输入处理: 检测键盘输入,改变蛇的移动方向。
蛇的移动: 根据当前方向,更新蛇头的坐标。然后将蛇身体的每个节点更新为其前一个节点的位置。如果吃到食物,则不需要删除蛇尾。
碰撞检测:
蛇头是否撞到墙壁?
蛇头是否撞到自己的身体?
食物生成: 当食物被吃到后,在地图上随机生成新的食物。
渲染: 将蛇、食物和游戏区域在终端绘制出来。

开始动手写代码(精简版)

为了控制在100行之内,我们需要非常高效地利用代码。这里我们假设你是在一个支持 ANSI 转义序列的终端环境下运行(比如 Linux, macOS, 或者 Windows 的新版终端)。

```c
include
include
include
include // 用于禁用缓冲和回显
include // 用于读写

// 定义游戏区域的大小
define WIDTH 20
define HEIGHT 10

// 蛇的结构体,包含坐标
typedef struct {
int x, y;
} Segment;

// 游戏状态变量
Segment snake[100]; // 蛇的身体,最多100节
int snakeLength;
int foodX, foodY;
int dir; // 0:向上, 1:向下, 2:向左, 3:向右
int gameOver;

// 用于终端控制的函数
struct termios oldt, newt;

void initTerminal() {
tcgetattr(STDIN_FILENO, &oldt); // 获取当前终端设置
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO); // 禁用规范模式(行缓冲)和回显
tcsetattr(STDIN_FILENO, TCSANOW, &newt); // 应用新设置
}

void restoreTerminal() {
tcsetattr(STDIN_FILENO, TCSANOW, &oldt); // 恢复原始终端设置
}

// 获取键盘输入,非阻塞式
int kbhit() {
struct timeval tv = { 0L, 0L };
fd_set fds;
FD_ZERO(&fds);
FD_SET(0, &fds); // 监听标准输入
return select(1, &fds, NULL, NULL, &tv);
}

// 绘制游戏界面
void draw() {
// 清屏 (使用ANSI转义序列)
printf("33[H");

// 绘制顶部边框
for (int i = 0; i < WIDTH + 2; i++) printf("");
printf(" ");

// 绘制中间区域
for (int y = 0; y < HEIGHT; y++) {
printf(""); // 左边框
for (int x = 0; x < WIDTH; x++) {
int isSnakeBody = 0;
// 检查是否是蛇的身体
for (int k = 1; k < snakeLength; k++) { // 从第二节开始检查(蛇头除外)
if (snake[k].x == x && snake[k].y == y) {
printf("o"); // 蛇的身体
isSnakeBody = 1;
break;
}
}
if (isSnakeBody) continue;

// 检查是否是蛇头
if (snake[0].x == x && snake[0].y == y) {
printf("O"); // 蛇头
}
// 检查是否是食物
else if (foodX == x && foodY == y) {
printf("F"); // 食物
}
// 空格
else {
printf(" ");
}
}
printf(" "); // 右边框
}

// 绘制底部边框
for (int i = 0; i < WIDTH + 2; i++) printf("");
printf(" ");
}

// 初始化游戏
void setup() {
srand(time(NULL)); // 初始化随机数种子
snakeLength = 1;
snake[0].x = WIDTH / 2; // 蛇头初始位置
snake[0].y = HEIGHT / 2;
dir = 0; // 初始方向向上
gameOver = 0;

// 生成第一个食物
foodX = rand() % WIDTH;
foodY = rand() % HEIGHT;
}

// 更新游戏状态
void update() {
// 记录蛇尾,如果吃到食物则不删除
int prev_tail_x = snake[snakeLength1].x;
int prev_tail_y = snake[snakeLength1].y;

// 移动蛇的身体(从尾部向前更新)
for (int i = snakeLength 1; i > 0; i) {
snake[i] = snake[i 1];
}

// 更新蛇头位置
switch (dir) {
case 0: snake[0].y; break; // 上
case 1: snake[0].y++; break; // 下
case 2: snake[0].x; break; // 左
case 3: snake[0].x++; break; // 右
}

// 检查是否吃到食物
if (snake[0].x == foodX && snake[0].y == foodY) {
snakeLength++; // 蛇变长
foodX = rand() % WIDTH; // 生成新食物
foodY = rand() % HEIGHT;
} else {
// 如果没吃到食物,蛇尾消失
// (前面移动身体时已经覆盖了蛇尾的原位置,这里不再需要额外处理)
}

// 碰撞检测:撞墙
if (snake[0].x < 0 || snake[0].x >= WIDTH || snake[0].y < 0 || snake[0].y >= HEIGHT) {
gameOver = 1;
return;
}

// 碰撞检测:撞自己
for (int i = 1; i < snakeLength; i++) {
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
gameOver = 1;
return;
}
}
}

// 处理用户输入
void input() {
if (kbhit()) { // 检查是否有按键
char ch = getchar(); // 读取字符
switch (ch) {
case 'w': if (dir != 1) dir = 0; break; // 向上,不能直接向下
case 's': if (dir != 0) dir = 1; break; // 向下,不能直接向上
case 'a': if (dir != 3) dir = 2; break; // 向左,不能直接向右
case 'd': if (dir != 2) dir = 3; break; // 向右,不能直接向左
case 'q': gameOver = 1; break; // 按q退出
}
}
}

int main() {
initTerminal(); // 初始化终端设置
setup(); // 初始化游戏

while (!gameOver) {
draw(); // 绘制界面
input(); // 处理输入
update(); // 更新游戏状态
usleep(100000); // 控制游戏速度 (100毫秒)
}

restoreTerminal(); // 恢复终端设置
printf("Game Over! "); // 游戏结束提示

return 0;
}

```

代码解析和行数统计

我们来大致数一下上面的代码行数,并说明一下关键部分:

头文件: `stdio.h`, `stdlib.h`, `time.h` (5行)
终端控制: `termios.h`, `unistd.h` (7行)
宏定义: `WIDTH`, `HEIGHT` (2行)
结构体定义: `Segment` (3行)
全局变量: `snake`, `snakeLength`, `foodX`, `foodY`, `dir`, `gameOver` (6行)
终端控制函数: `oldt`, `newt`, `initTerminal`, `restoreTerminal` (12行)
键盘输入检测: `kbhit` 函数 (8行)
绘制函数: `draw` (34行) 这是最占行数的部分,但包含了清晰的逻辑:清屏、边框、蛇头、蛇身、食物的绘制。
初始化函数: `setup` (13行)
更新函数: `update` (31行) 包含了蛇移动逻辑、吃到食物逻辑、碰撞检测。
输入函数: `input` (14行) 处理键盘按键。
主函数: `main` (10行) 调用初始化、游戏循环、以及最后恢复终端和退出。

总计行数: 5 + 7 + 2 + 3 + 6 + 12 + 8 + 34 + 13 + 31 + 14 + 10 = 145行

嗯,看来直接写是会稍微超出100行的。这很正常,因为要保证代码的可读性和功能完整性。不过,我们离目标很近了!

如何进一步精简(如果真的要卡100行):

要压缩到100行,我们需要做一些“牺牲”或者说“技巧性”的操作:

1. 合并函数或减少函数调用:
`draw` 函数可以和 `update` 函数的一部分逻辑合并,这样 `draw` 就不需要独立地去判断蛇的每个部分。但这样会牺牲可读性。
`kbhit` 和 `getchar` 可以直接写在 `input` 函数里,不需要单独的 `kbhit` 函数。
2. 简化绘制:
不使用 ANSI 转义序列清屏,而是每次都重新打印整个屏幕。这会导致闪烁,但可以减少 `printf` 的部分。
用 `.` 代表蛇身,`O` 代表蛇头,`F` 代表食物,甚至去掉边框。
3. 精简数据结构:
如果蛇的长度增长受限(比如最多10节),`snake` 数组就可以小点。
4. 直接使用全局变量,不封装函数:
例如,将 `setup` 的部分逻辑直接放在 `main` 函数的开头。

一个更“紧凑”的版本思路(但不推荐,因为它牺牲了可读性)

为了演示,我试着再压缩一下,重点压缩 `draw` 和 `update` 部分,并合并一些小的辅助功能。

```c
include
include
include
include
include

define W 20
define H 10

struct S { int x, y; } snake[100];
int len, fx, fy, dir, ov;
struct termios oldt, newt;

void term_setup() {
tcgetattr(0, &oldt); newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(0, TCSANOW, &newt);
}

void term_reset() { tcsetattr(0, TCSANOW, &oldt); }

int kbhit() {
fd_set fds; FD_ZERO(&fds); FD_SET(0, &fds);
struct timeval tv = {0L, 0L};
return select(1, &fds, NULL, NULL, &tv);
}

int main() {
term_setup();
srand(time(0));
len = 1; snake[0] = (struct S){W/2, H/2};
dir = 0; ov = 0;
fx = rand() % W; fy = rand() % H;

while (!ov) {
printf("33[H"); // 清屏
for (int i = 0; i < W + 2; i++) printf(""); printf(" ");
for (int y = 0; y < H; y++) {
printf("");
for (int x = 0; x < W; x++) {
int is_body = 0;
for (int k = 1; k < len; k++) if (snake[k].x == x && snake[k].y == y) { printf("o"; is_body = 1; break; }
if (is_body) continue;
if (snake[0].x == x && snake[0].y == y) printf("O";
else if (fx == x && fy == y) printf("F";
else printf(" ";
}
printf(" ");
}
for (int i = 0; i < W + 2; i++) printf(""); printf(" ");

if (kbhit()) {
char c = getchar();
if (c == 'w' && dir != 1) dir = 0;
if (c == 's' && dir != 0) dir = 1;
if (c == 'a' && dir != 3) dir = 2;
if (c == 'd' && dir != 2) dir = 3;
if (c == 'q') ov = 1;
}

int tail_x = snake[len 1].x;
int tail_y = snake[len 1].y;

for (int i = len 1; i > 0; i) snake[i] = snake[i 1];

switch (dir) {
case 0: snake[0].y; break;
case 1: snake[0].y++; break;
case 2: snake[0].x; break;
case 3: snake[0].x++; break;
}

if (snake[0].x < 0 || snake[0].x >= W || snake[0].y < 0 || snake[0].y >= H) ov = 1;
for (int i = 1; i < len; i++) if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) ov = 1;

if (snake[0].x == fx && snake[0].y == fy) {
len++; fx = rand() % W; fy = rand() % H;
}
usleep(100000);
}
term_reset();
printf("Game Over! ");
return 0;
}
```

我们再数数这个精简版的行数:

头文件和宏定义: 7行
结构体和全局变量: 6行
终端函数: `term_setup`, `term_reset` (6行)
`kbhit` 函数: 4行
`main` 函数: 49行 (这里面包含了大部分逻辑,将绘制、输入、更新都揉在了一起,用了大量的连续分号 `;` 来压缩语句)

总计行数大约是 7 + 6 + 6 + 4 + 49 = 72行。

这下就非常接近甚至低于100行了!但代价是可读性大大降低。那个更长的版本,虽然超过了100行,但逻辑清晰很多,也更容易理解和修改。

为什么这个精简版依然强大(且在我看来比100行限制更有意义)

即使是第一个145行左右的版本,也已经充分展示了C语言在底层控制和逻辑实现上的强大。它不需要复杂的库,只需要标准C和一些对终端的简单控制,就能构建出一个完整的游戏循环。

这里的关键点在于:

终端控制: 了解 `termios` 是实现这种“立即响应”和“无回显”输入的前提,也是在终端上绘制图形的基础(通过 `33[H` 等转义序列)。
游戏循环: `while (!gameOver)` 是几乎所有实时游戏的核心结构。
状态更新: `update` 函数里对蛇身体的逐帧移动(从后往前覆盖)是实现蛇移动的关键技巧。
碰撞检测: 这是保证游戏规则有效性的基础。

希望这个详细的解释和代码示例,能让你对如何在C语言中实现一个基本的贪吃蛇游戏有一个清晰的认识。这个过程本身就是对C语言逻辑思维和底层操作能力的一种很好的锻炼!

网友意见

user avatar

这段代码会发生内存泄漏,特此备注。

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

没学到一个小小的贪吃蛇会有怎么多赞,当时想能有100个赞已经很不错了,这个完全超出我的预期啊,啊啊啊~飘了飘了~

小声bb:其实这个代码写得不咋地,有些函数为了能用 return 语句,故意写了一个返回值,但是返回值没有用到,还有就是 Run 这个函数本来不应该接受一个参数,但是为了省一行的代码,把init写进去,才出此下策。这样写的后果就是代码不符合我的预期,不这样写的话又不符合题主的要求,两难啊。所以我才会比较郁闷,说了点脏话,实在抱歉 。(可能自己水平不够,代码还有很多优化空间,我看别的答主根本就用不到100行)

继续努力吧 ~

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

我本来怀着很好的心情来写这个100的贪吃蛇的,不过写到后面就出口成脏了。

mmp,写到后面很是郁闷。有图为证。

刚刚好一百行代码,我去,这是那我之前写的350多行代码压缩的得到的。

写得很垃圾,有些东西就是乱写一通,就为了压缩一两行逻辑代码。

然后UI部分,这个也没办法压缩,能压缩的都在核心代码上面了。

尽量体现出来格式,但是难受啊

贪吃蛇的核心代码就在这个switch里面了,10行。也就是占比 1/10。其实还可以...

最后的效果图是这个


直接贴代码吧。

       #include <Windows.h> #include <stdio.h> #include <conio.h> #include <time.h> #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,写代码也不容易

user avatar

UPD 20191229

重写了一个C89兼容的…大概吧…

总之在VC6下能跑起来了(对stdafx.h的依赖不会去除不是我的问题哦)…

然后…诸位…

快换一个完整支持C99的编译器吧!!!

不过代码又变脏了好多…对不起我已经不纯洁了嘤嘤嘤…

恰好100,如下

       #include <Windows.h> #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<MAX_WIDTH+2;pos.X++)   for(pos.Y=0;pos.Y<MAX_HEIGHT+2;pos.Y++)    if((mode==1) && isborder(pos))     WriteConsoleOutputCharacterA(GetStdHandle(STD_OUTPUT_HANDLE), "#", 1, pos, &retdword);    else     WriteConsoleOutputCharacterA(GetStdHandle(STD_OUTPUT_HANDLE), " ", 1, pos, &retdword); } void go(COORD pos) {  if (isborder(pos) || IsSnake(pos))  {   char tmp[256];   cls(0);   COORD pos = { 0, 0 };   wsprintfA(tmp, "Die! Total Score:%d", (QueueHead + MAX_SIZE - QueueTail) % MAX_SIZE);   WriteConsoleOutputCharacterA(GetStdHandle(STD_OUTPUT_HANDLE), tmp, strlen(tmp), pos, &retdword);   Sleep(3000);   exit(0);  }  snake[QueueHead] = pos;  inc(QueueHead);  WriteConsoleOutputCharacterA(GetStdHandle(STD_OUTPUT_HANDLE), "x", 1, pos, &retdword); } void removetail() {  WriteConsoleOutputCharacterA(GetStdHandle(STD_OUTPUT_HANDLE), " ", 1, snake[QueueTail], &retdword);  inc(QueueTail); } void ChkKey() {  while (1)  {   INPUT_RECORD ir;   PeekConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &ir, 1, &retdword);   if (retdword == 0)    break;   ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &ir, 1, &retdword);   if (ir.EventType != KEY_EVENT || !ir.Event.KeyEvent.bKeyDown || ir.Event.KeyEvent.wVirtualKeyCode<VK_LEFT || 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() {  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 <stdio.h> #include <Windows.h> #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;%dHo33[%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;%dHx33[%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_LEFT || 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);  } }     

user avatar

问100行内能不能实现?那当然能实现,而且远远用不到100行。

写了一下,大概30行:

       #include <stdio.h> #include <stdlib.h> #include <string.h> #include <conio.h> #include <unistd.h> int main() {     char board[33][62];     strcpy(&board[0][0],"26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26
");     for(int i=1;i<30;i++) strcpy(&board[i][0],"26                                                           26
");     strcpy(&board[30][0],"26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26
w:up s:down a:left d:right
");     int snake_num=6,dir='a',snake[29*29]={16,30,16,32,16,34},food[2]={rand()%29+1,(rand()%29+1)*2};     for(int i=0;i<snake_num;i+=2) board[snake[i]][snake[i+1]]='26';     board[food[0]][food[1]]=1;     while(memmove(snake+2,snake,snake_num*sizeof(int)))     {         if(kbhit()) dir=getch();         snake[1]=snake[3]+((dir=='a')?-2:(dir=='d')?2:0);         snake[0]=snake[2]+((dir=='w')?-1:(dir=='s')?1:0);         if(board[snake[0]][snake[1]]==1)         {             snake_num+=2;             do{food[0]=rand()%29+1;food[1]=(rand()%29+1)*2;}while(board[food[0]][food[1]]!=' ');             board[food[0]][food[1]]=1;         }         else if(board[snake[0]][snake[1]]=='26') return printf("game over!
");         else board[snake[snake_num]][snake[snake_num+1]]=' ';         board[snake[0]][snake[1]]='26';         system("clear");         sprintf(((char *)board)+1949,"score: %d
",(snake_num-6)*50);         printf("%s",board);         usleep(((500-snake_num>200)?(500-snake_num):200)*1000);     } }     

效果:

类似的话题

  • 回答
    当然可以,用C语言在100行之内实现一个基本的贪吃蛇游戏是完全可行的。下面我将一步一步地告诉你如何做到这一点,并尽量讲得清楚明白,让它读起来像是出自一个真心想和你分享编程乐趣的老司机之手。我们要实现的是一个非常精简的版本,只包含最核心的元素: 游戏区域: 一个固定的矩形区域。 蛇: 由一系列.............
  • 回答
    在 Linux 系统中,使用 C 语言判断 `yum` 源是否配置妥当,并不是直接调用一个 C 函数就能完成的事情,因为 `yum` 的配置和操作是一个相对复杂的系统级任务,涉及到文件系统、网络通信、进程管理等多个层面。更准确地说,我们通常是通过 模拟 `yum` 的一些基本行为 或者 检查 `yu.............
  • 回答
    当然可以,C语言作为一门编译型语言,其强大的跨平台能力很大程度上得益于其设计理念和标准库。通过遵循一定的规则,并且在不同平台上都拥有能够解析和生成对应机器码的编译器,C语言的源代码确实能够实现跨平台运行。这背后的原理可以从几个关键点来理解:1. C语言的标准化与抽象层:C语言之所以能实现跨平台,最根.............
  • 回答
    我理解你的感受。学了一个学期的C语言,却感觉好像一直在做数学题,这在很多初学者身上是很常见的,也确实会让人产生“C语言有什么实际用途”的疑问。别急,我们一点点来聊聊,为什么会这样,以及C语言到底能干什么。一、 初学C语言,为何“似曾相识”的数学题?这主要是因为C语言在设计之初,就非常强调底层操作和对.............
  • 回答
    这个问题触及到了计算机内存管理和操作系统安全的核心。理论上,在某些特定条件下,C语言可以通过指针修改其他程序的内存地址的值。但实际操作起来非常复杂,而且在现代操作系统中,直接这么做几乎是不可能的,并且是强烈不被推荐的。为了讲清楚这件事,咱们得把事情掰开了揉碎了说。理解内存与地址首先,咱们得明白什么是.............
  • 回答
    .......
  • 回答
    咱们今天就来聊聊 C++ 这玩意儿,为啥很多人觉得它有点“危险”,容易让人“翻车”。别担心,我会尽量用大白话来说,不整那些复杂的专业术语,就跟咱平时聊天一样。想象一下,你拿到的是一把非常非常锋利的瑞士军刀,而且这把军刀的设计者,没怎么考虑你是不是新手。C++ 就有点像这把军刀。它能干很多很多别人做不.............
  • 回答
    好的,非常乐意为您详细讲解如何使用 C 语言和 Windows API 实现一个基本的 SSL/TLS 协议。您提到参考资料已备齐,这非常好,因为 SSL/TLS 是一个相当复杂的协议,没有参考资料很难深入理解。我们将从一个高层次的概述开始,然后逐步深入到具体的 Windows API 函数和 C .............
  • 回答
    在 C 语言中绘制心形有多种方法,最常见和易于理解的方法是使用字符输出,也就是在控制台上用特定的字符(如 `` 或 ``)组合成心形的形状。另一种更高级的方法是使用图形库(如 SDL、Allegro 或 Windows GDI)来绘制真正的图形心形,但这需要更多的设置和知识。这里我们主要讲解 字符输.............
  • 回答
    好的,咱们来聊聊怎么用 C 语言算 1000 的阶乘。这可不是件小事,因为 1000 的阶乘是个超级无敌大的数字,远超出了 C 语言里任何内置数据类型能表示的范围。所以,咱们得自己动手,实现一个能处理大数乘法的算法。问题所在:为什么内置类型不行?在 C 语言里,我们常用的数字类型有 `int`、`l.............
  • 回答
    好的,咱们不聊那些虚头巴脑的,直接说说怎么用C语言把一个三维球体给“画”出来。你可能以为这是什么高大上的图形学才能做的事情,其实不然,很多时候我们理解的三维“画”其实是模拟。要用C语言“画”一个三维球体,咱们主要有两种思路,一种是控制台输出(ASCII art),一种是借助图形库(比如SDL, Op.............
  • 回答
    好嘞,咱们这就来聊聊怎么用 C 语言搭一个简易计算器。别担心,不讲那些晦涩难懂的理论,咱们一步一步来,就像搭积木一样,让它一点点变得能用起来。1. 目标:我们想做什么?首先,得明确我们要造个什么样的计算器。最基本的,就是能做加、减、乘、除这四种运算。所以,咱们的用户需要输入: 第一个数字 运.............
  • 回答
    好的,不使用列表,我来详细说说如何用C语言生成一个范围在 (0, 1) 之间的随机浮点数。在C语言中,我们通常依赖标准库中的函数来处理随机数。最核心的函数是 `rand()`。1. `rand()` 函数的初步认识`rand()` 函数位于 `` 头文件中。它会返回一个介于 0 和 `RAND_MA.............
  • 回答
    Windows 操作系统之所以选择使用 C 语言作为主要开发语言,而文件系统在设计上却对大小写不敏感,这背后是历史选择、设计哲学以及技术妥协的复杂结合。要深入理解这一点,我们需要拆解几个关键部分:一、 C 语言与系统级开发:为何是它?首先,我们得明白为什么像 Windows 这样庞大的操作系统会选择.............
  • 回答
    你说得对,以前的计算机系统确实很大程度上依赖于命令行界面(CLI),而非我们现在习以为常的图形用户界面(GUI)。这并不是说当时人们用 C 这样的语言直接“敲代码”来操作整个操作系统,而是说,当时人们与计算机交互的主要方式是通过键盘输入一系列文本指令,而这些指令是由更底层的程序(也就是我们常说的“代.............
  • 回答
    C语言使用 `int a` 来声明指针变量,而不是 `int &a`,这背后有深刻的历史原因、设计哲学以及C语言本身的特性决定的。要详细解释这一点,我们需要从以下几个方面入手: 1. 指针(Pointers)与引用(References)的本质区别首先,理解指针和引用是什么至关重要。 指针(Po.............
  • 回答
    很多人在刚接触 C 语言,尤其是看到代码中出现 `break` 和 `continue` 语句时,心里可能会泛起一丝不安:这样做是不是不太好?会不会显得我功力不够?是不是有什么更“优雅”的写法?其实,要回答这个问题,我们得先明白 `break` 和 `continue` 在 C 语言里到底是什么。`.............
  • 回答
    C 语言里,一旦你用了 ` ` 来进行换行,确实就“回不去了”——至少在标准的输出流中是这样。这背后的原理,要从计算机如何处理文本输出和终端(或者说显示器)的工作方式说起。核心点:文本流与终端的坐标系统想象一下你的程序输出的文本,就像一条源源不断地向前流动的河流。` `(换行符)就是这条河流中的一个.............
  • 回答
    朋友,咱们这话题聊得挺实在的。C语言现在还有没有“必要”学,未来还有没有“用”,这绝对是个值得深入掰扯掰扯的问题。别听那些虚头巴脑的,咱就从实际出发,好好说说。C语言现在还有没有“必要”学?我想说,如果你想在计算机底层或者和效率打交道,那 C 语言的“必要性”依然挺强的,甚至可以说是基石性的。你得明.............
  • 回答
    Unity选择C,而Unreal Engine坚持C++,这背后其实是两条截然不同但都极为明智的产品定位和技术哲学。要理解这一点,咱们得掰开了揉碎了聊。 Unity与C:易用性、跨平台与快速迭代的羁绊Unity之所以拥抱C,很大程度上是为了降低开发门槛,吸引更广泛的开发者群体,并实现高效的跨平台开发.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有