C++控制台吃豆子游戏实现解析

沿途有光
沿途有光
发布时间:2026-10-13| 5人浏览过
原创

本文将深入解析如何利用C++语言和Windows控制台API开发一款经典的“吃豆子”(Pac-Man风格)小游戏。该程序无需依赖大型图形库,仅通过字符输出和控制台光标定位即可实现流畅的游戏体验。内容涵盖核心数据结构设计、随机地图生成、碰撞检测算法以及键盘事件处理机制,适合希望了解底层游戏循环实现的开发者参考。

项目运行效果与环境

游戏在标准Windows命令行窗口中运行,采用字符矩阵表示地图元素:黑色方块(■)代表不可逾越的墙壁,数字或特定符号代表可食用的豆子,而角色(豆人)则通过动态清除与重绘实现移动。按下 ESC 键可随时退出游戏。

程序通过 mode con cols=50 lines=21 命令调整控制台大小以适配游戏区域,确保显示比例协调。

核心模块与数据结构设计

整个游戏由主逻辑入口 main.cpp 和核心功能库 lib.h/cpp 组成。为了实现简洁性,代码使用了结构体来封装关键实体:

  • POS (坐标): 记录元素的 X/Y 整数坐标。
  • BODY (角色): 包含当前坐标 pos 和运动方向 Direction。
  • WALL (墙体): 存储所有墙壁的坐标数组及总长度。
  • FOOD (食物): 存储当前屏幕上剩余豆子的位置、数量及隐藏状态。
  • HALL (可行通道): 预处理好的非墙壁区域坐标列表,用于限制豆子随机生成的范围。

第1步:编译与初始化环境

在开始编码前,请确保你的 C++ 编译器(如 MSVC 或 MinGW)支持 Windows API。以下头文件是必要的:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>

初始化阶段 (Init 函数) 负责获取控制台句柄、隐藏默认光标,并根据预定义的二维数组 short wall[17][24] 构建关卡。其中 0 表示墙壁,-1 表示通道。

第2步:绘制地图与生成食物

游戏启动时,MakeWall 函数遍历墙数组,将对应坐标的字符替换为 “■”。同时,系统会在走廊数组中随机选取 7 个有效位置生成豆子,并通过 Print 函数将其渲染到控制台。

第3步:响应按键与控制移动

这是游戏循环的核心。Move 函数每 200 毫秒执行一次,通过 GetKey() 调用 Windows API GetAsyncKeyState 监听上下左右键及 ESC 键。

  • 方向改变: 若检测到按键,更新角色的 Direction 属性。
  • 移动判定: 根据当前方向计算新坐标。
  • 碰撞检测: IsKnock 函数检查新坐标是否与墙壁重合。若相撞,保持原位置不变;若与食物重合,则扣除一个豆子并增加分数。
  • 画面刷新: 先在上一个位置打印空格 " " 进行擦除,再在新位置打印角色符号。

完整源代码实现

以下是可直接编译运行的核心代码逻辑。主循环不断调用移动和渲染函数,直到玩家退出或完成当前关卡。

1. 主程序 (main.cpp)

#include "lib.h"
#pragma once
extern int level;
int main()
{
 FOOD food;
 WALL wall;
 BODY CurPos;
 HALL hall;
 int iExit = 0;
 while(1)
 {
  if(iExit)
   break;
  Init(&food,&wall,&CurPos,&hall);
  MakeWall(&wall);
  while(1)
  {
   Sleep(200);
   int res = Move(&food,&wall,&CurPos);
   if(1 == res) //重新开局
    break;
   else if(-1 == res) //用户退出
   {
    iExit = 1;
    break;
   }
   ShowScore(&CurPos,&food);
  }
 }
 return 0;
}

2. 头文件定义 (lib.h)

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
//标识用户按下的方向键
#define KEY_UP    WM_USER + 1
#define KEY_DOWN   WM_USER + 2
#define KEY_LEFT   WM_USER + 3
#define KEY_RIGHT  WM_USER + 4
//标识用户当前运动方向
#define DIRECTION_UP  WM_USER + 5
#define DIRECTION_DOWN  WM_USER + 6
#define DIRECTION_LEFT  WM_USER + 7
#define DIRECTION_RIGHT WM_USER + 8
//标识要打印的元素类型
#define PRINT_WALL   WM_USER + 9
#define PRINT_FOOD   WM_USER + 10
#define PRINT_BODY   WM_USER + 11
#define PRINT_CLEAN  WM_USER + 12
#define KNOCK_WALL   WM_USER + 13
#define KNOCK_FOOD   WM_USER + 14
struct POS
{
 int x;
 int y;
};
struct WALL
{
 POS pos[9999];
 int len;
};
struct FOOD
{
 POS pos[8];
 int len;
 int IsHidden;
};
struct BODY
{
 POS pos;
 int Direction;
};
struct HALL{
 POS pos[200];
 int len;
};
void Init(FOOD *pFood,WALL *pWall,BODY *pBody,HALL *pHall);
//进行一些初始化操作
void Print(POS *pos,int TYPE,int ix = 0);
//完成打印功能
void MakeWall(WALL *pWall);
//生成墙
void MakeFood(HALL *pHall,FOOD *pFood);
//生成食物
int Move(FOOD *pFood,WALL *pWall,BODY *pBody);
//用户移动
void ShowScore(BODY *pBody,FOOD *pFood);
//显示等级 和剩余豆子数
int IsOver(POS pos1,POS pos2,int TYPE);
//判断2点是否重合
int IsKnock(WALL *pWall,BODY *pBody,FOOD *pFood,int TYPE);
//判断是否撞墙、吃到豆子
int GetKey();
//得到用户按键

3. 逻辑实现 (lib.cpp)

#include "lib.h"
#pragma once
HANDLE hMain_Out;
HANDLE hMain_In;
CONSOLE_CURSOR_INFO info;
int iBeans = 0;
int level = 1;
//地图定义 0为墙 -1为通道
short wall[17][24] = { ... }; //此处省略具体地图数据,详见原文lib.cpp部分

//判断是否碰撞
int IsKnock(WALL *pWall,BODY *pBody,FOOD *pFood,int TYPE)
{
 if(KNOCK_WALL == TYPE)
 {
  int i;
  for(i = 0 ; i < pWall->len ; ++i)
  {
   if(IsOver(pBody->pos,pWall->pos[i],KNOCK_WALL))
    return 1; 
  }
 }
 else if(KNOCK_FOOD == TYPE)
 {
  int i;
  for(i = 1 ; i <= pFood->len ; ++i)
  {
   if(IsOver(pFood->pos[i],pBody->pos,KNOCK_WALL))
    return i; 
  }
 }
 return 0;
}

//用户移动逻辑
int Move(FOOD *pFood,WALL *pWall,BODY *pBody)
{
 BODY prePos = *pBody;
 int iKey = GetKey();
 if(-1 == iKey) //用户退出
  return -1;
 if(iKey)
 {
  pBody->Direction = iKey + 4;
  iKey = 0;
 }
 if(0 == iKey)
 {
  if(DIRECTION_UP == pBody->Direction)
   --pBody->pos.y;
  else if(DIRECTION_DOWN == pBody->Direction)
   ++pBody->pos.y;
  else if(DIRECTION_LEFT == pBody->Direction)
   --pBody->pos.x;
  else if(DIRECTION_RIGHT == pBody->Direction)
   ++pBody->pos.x;
 }
 
 if(IsKnock(pWall,pBody,pFood,KNOCK_WALL))
 {
  *pBody = prePos;
  return 0;
 }
 int ix = IsKnock(pWall,pBody,pFood,KNOCK_FOOD);
 if(ix)
 {
  ++iBeans;
  int i;
  for(i = ix ; i <= (pFood->len - 1) ; ++i)
   pFood->pos[i] = pFood->pos[i + 1];
  --(pFood->len);
  if(0 == pFood->len)
  {
   ++level;
   return 1;
  }
 }
 Print(&prePos.pos,PRINT_CLEAN); 
 Print(&pBody->pos,PRINT_BODY);
 return 0;
}

//坐标重合判断
int IsOver(POS pos1,POS pos2,int TYPE)
{
 if(KNOCK_WALL == TYPE)
  if((pos1.x == pos2.x && pos1.y == pos2.y) || (pos2.x + 1 == pos1.x && pos2.y == pos1.y))
   return 1;
 return 0;
}

//生成墙体
void MakeWall(WALL *pWall)
{
 int x,y;
 int ix = 0;
 for(x = 0 ; x <= 16 ; ++x)
 {
  for(y = 0 ; y <= 23 ; ++y)
  {
   if(0 == wall[x][y])
   {
    pWall->pos[ix].x = 2 * y;
    pWall->pos[ix].y = x;
    Print(&pWall->pos[ix++],PRINT_WALL);
   }
  }
 }
 pWall->len = ix;
}

//初始化函数
void Init(FOOD *pFood,WALL *pWall,BODY *pBody,HALL *pHall)
{
 hMain_Out = GetStdHandle(STD_OUTPUT_HANDLE);
 hMain_In = GetStdHandle(STD_INPUT_HANDLE);
 GetConsoleCursorInfo(hMain_Out,&info);
 info.bVisible = FALSE;
 SetConsoleCursorInfo(hMain_Out,&info);
 
 pFood->len = 0;
 pWall->len = 0;
 pHall->len = 0;
 system("mode con cols=50 lines=21");
 
 int x,y;
 int ix = 0;
 for(x = 0 ; x < 17 ; ++x)
 {
  for(y = 0 ; y < 24 ; ++y)
  {
   if(wall[x][y])
   {
    pHall->pos[ix].x = 2 * y;
    pHall->pos[ix++].y = x;
   }
  }
 }
 pHall->len = ix;
 pBody->pos.x = 2;
 pBody->pos.y = 1;
 pBody->Direction = DIRECTION_DOWN;
 printf("%d %d\n",pHall->pos[0].x,pHall->pos[0].y);
 int i;
 MakeFood(pHall,pFood);
 for(i = 1 ; i <= 7 ; ++i)
 {
  Print(&pFood->pos[i],PRINT_FOOD,i);
 }
}

//获取按键
int GetKey()
{
 if(GetAsyncKeyState(VK_UP))
  return KEY_UP;
 if(GetAsyncKeyState(VK_DOWN))
  return KEY_DOWN;
 if(GetAsyncKeyState(VK_LEFT))
  return KEY_LEFT;
 if(GetAsyncKeyState(VK_RIGHT))
  return KEY_RIGHT;
 if(GetAsyncKeyState(VK_ESCAPE))
  return -1;
 return 0;
}

//渲染功能
void Print(POS *p,int TYPE,int ix)
{
 COORD pos;
 pos.X = p->x;
 pos.Y = p->y;
 SetConsoleCursorPosition(hMain_Out,pos);
 if(PRINT_WALL == TYPE)
  printf("■");
 else if(PRINT_FOOD == TYPE)
  printf("%d",ix);
 else if(PRINT_BODY == TYPE)
  printf("\1");
 else if(PRINT_CLEAN == TYPE)
  printf(" ");
}

//显示分数
void ShowScore(BODY *pBody,FOOD *pFood)
{
 COORD pos;
 pos.X = 0;
 pos.Y = 18;
 SetConsoleCursorPosition(hMain_Out,pos);
 printf("\tBeans left : %d | pos : x=%d,y=%d\n",pFood->len,pBody->pos.x,pBody->pos.y);
 printf("\t\tLevel : %d",level);
}

//生成食物
void MakeFood(HALL *pHall,FOOD *pFood)
{
 srand((unsigned)time(NULL));
 int tot;
 for(tot = 1 ; tot <= 7 ; ++tot)
 {
  int ixFood = rand() * pHall->len / RAND_MAX;
  pFood->pos[tot].x = pHall->pos[ixFood].x;
  pFood->pos[tot].y = pHall->pos[ixFood].y;
 }
 pFood->len = 7;
}

常见问题与调试建议

1. 字符重叠导致画面闪烁: 上述代码通过 Sleep(200) 控制帧率,若觉得卡顿可适当调低。但需注意频繁调用 printf 可能导致性能瓶颈,优化方案可使用 WriteConsoleOutput 批量写入缓冲区。

2. 光标未隐藏: 在 Windows 环境下,务必在初始化时设置 info.bVisible = FALSE,否则移动时的光标跳变会严重干扰视觉体验。

3. 跨平台兼容性: 本实现强依赖于 Windows.h 和特定的控制台命令。若需移植至 Linux/macOS,需改用 curses 库或 ANSI 转义序列来处理光标移动和键盘输入。

以上就是基于C++实现控制台界面吃豆子游戏的详细内容,更多关于C++控制台游戏开发的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。