UEFI开发探索94 – 迷宫小游戏

请保留-> 【原文:  https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】

最近一直在写YIE002开发探索的博客,偶尔看看其他人写的和BIOS开发相关的博客。

经常看的博客有Tim Lewis、Vincent Zimmer等,总是能学到一些东西。

今天下班后,可能是因为最近公司的事情比较繁杂,精神有点不振。虽然计划了近期要写的嵌入式代码,可是怎么也提不起劲,开发工具都不想打开。

我无聊地翻阅着常看的几位作者的博客,Tim Lewis有个关于UEFI和C++的议题,稍微有点兴趣,就点进去看了。

他的代码放在了sourceforge上,地址为:https://sourceforge.net/projects/syslibforuefi/。下载了之后,我浏览了目录结构,发现几个有意思的程序。其中有些是可以在UEFI下玩的小游戏,看来程序员的想法很类似啊,我之前也开发了UEFI下的贪吃蛇游戏。

今天休息一下,拜读下顶级BIOS程序员写的UEFI程序。

1 Maze程序结构分析

源程序在文末给出了,可以试玩一下。

整个迷宫程序的主要实现目标包括:
1) 自动生成迷宫;
2) 通过方向键,控制角色通过迷宫。

实现的目标比较简单,从代码结构来看,主要包含如下函数,如图1所示。

图1 Maze程序架构图

整体的程序结构还是非常容易理解的,而且作者写了非常详细的注释,很容易看明白。基本步骤如下:

1)定义全局变量

// Global Bitmaps
UINTN               mBgWidth;           // width of background image.
UINTN               mBgHeight;          // height of background image.
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *mBgBlt;  // background image.
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *mRockBlt;// rock image.
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *mCharBlt;// character image.

EFI_GRAPHICS_OUTPUT_BLT_PIXEL *mDisplay;// copy of grid display.
UINT32              mDisplayWidth;      // width (in pixels) of grid display.
UINT32              mDisplayHeight;     // height (in pixels) of grid display.

EFI_GRAPHICS_OUTPUT_BLT_PIXEL **mGrid = NULL; // grid of boxes for the game
INT32               mGridHeight;        // number of columns in the grid.
INT32               mGridWidth;         // number of rows in the grid.

UINTN               mGenImageWidth;     // width of images that take up one box
UINTN               mGenImageHeight;    // height of images that take up one box

INT32               mCharacterX;        // character's X cell location.
INT32               mCharacterY;        // character's Y cell location.

INT32               mEntranceX;
INT32               mEntranceY;

INT32               mExitX;
INT32               mExitY;

其中,与迷宫背景、障碍物(也即石头)、角色相关的图像,都使用EFI_GRAPHICS_OUTPUT_BLT_PIXEL型指针变量mBgBlt、mRockBlt和mCharBlt保存了。

2)设置迷宫

设置迷宫是本游戏中最重要的部分,为保证可以随机生成迷宫,在main()函数开始,使用用当前时间生成了新的seed:

srand((unsigned)time(NULL));

而生成迷宫的核心函数为MazeCreate(),它调用了MazeGenerate()来随机产生迷宫。代码内容如下:

VOID MazeCreate(VOID)
{
  assert(mGridWidth % 2 == 1);
  assert(mGridHeight % 2 == 1);
  MazeGenerate(0, 1, 1, (mGridWidth - 1)/2, (mGridHeight - 1)/2, 1);
}

VOID 
MazeGenerate(
  int               Index,              // backtrack index.
  int               X,                  // grid horizontal position to investigate.
  int               Y,                  // grid vertical position to investigate.
  int               W,                  // number of unique horizontal maze positions.
  int               H,                  // number of unique vertical maze positions.
  int               Visited             // number of maze positions visited.
  )
{
  int               neighbor_valid;     // valid neighbors: 0 = none.
  int               neighbor_x[4];      // array of grid positions of possible neighbor (x).
  int               neighbor_y[4];      // array of grid positions of possible neighbor (y).
  int               step[4];            // array of valid possible neighbors.
  int               x_next;             // next selected grid position (x).
  int               y_next;             // next selected grid position (y).
  int               random;             // randomly selected neighbor to try.

  if (Visited < H * W) {
    neighbor_valid = 0; 

    // Add the left neighbor if it is completely blocked off.
    if (X - 2 > 0 && MazeIsClosed(X - 2, Y)) {
      neighbor_x[neighbor_valid] = X - 2;;
      neighbor_y[neighbor_valid] = Y;
      step[neighbor_valid] = 1;
      neighbor_valid++;
    }

    // Add the up neighbor if it is completely blocked off.
    if (Y - 2 > 0 && MazeIsClosed(X, Y - 2)) {
      neighbor_x[neighbor_valid] = X;
      neighbor_y[neighbor_valid] = Y - 2;
      step[neighbor_valid] = 2;
      neighbor_valid++;
    }

    // Add the down neighbor if it is completely blocked off.
    if (Y + 2 < H * 2 + 1 && MazeIsClosed(X, Y + 2)) {
      neighbor_x[neighbor_valid] = X;
      neighbor_y[neighbor_valid] = Y + 2;
      step[neighbor_valid] = 3; 
      neighbor_valid++;
    }
    
    // Add the right neighbor if it is completely blocked off.
    if (X + 2 < W * 2 + 1 && MazeIsClosed(X + 2, Y)) {
      neighbor_x[neighbor_valid] = X + 2; 
      neighbor_y[neighbor_valid] = Y;
      step[neighbor_valid] = 4; 
      neighbor_valid++;
    }
    
    // Count the number of neighbors that are completely surrounded by walls. 
    // If there are none, then backtrack to the previous location and try 
    // again.
    if (neighbor_valid == 0) { // backtrack 
      x_next = mBacktrackX[Index]; 
      y_next = mBacktrackY[Index]; 
      Index--;

    // There is at least one neighbor that was completely blocked off, so 
    // randomly select among them.
    } else {
      random = rand() % neighbor_valid;

      // Find the next grid location to try from among the valid neighbors.
      x_next = neighbor_x[random]; 
      y_next = neighbor_y[random]; 

      // Record the next location in the backtrack location list.
      Index++; 
      mBacktrackX[Index] = (char)x_next; 
      mBacktrackY[Index] = (char)y_next; 

      // Make sure there is an opening between the next location and the 
      // location by putting a path there.
      switch (step[random]) {
        case 1: MazeSet(x_next + 1, y_next, PATH); break;
        case 2: MazeSet(x_next, y_next + 1, PATH); break;
        case 3: MazeSet(x_next, y_next - 1, PATH); break;
        case 4: MazeSet(x_next - 1, y_next, PATH); break;
        default: assert(FALSE); break;
      }

      Visited++; 
    } 

    // Recursively generate the maze from the next location selected.
    MazeGenerate(Index, x_next, y_next, W, H, Visited); 
  } 
}

MazeGenerate()函数非常有意思,它使用递归的方式,保证生成的迷宫能有出口。而出口的位置,是由随机函数rand()来决定的。

3) 游戏控制

游戏控制通过函数RunGame()实现,它接收用户对方向键的输入,控制角色在迷宫中行走。RunGame()对觉得的操作,则由函数PlayerMove()来实现,具体内容如下:

EFI_STATUS
PlayerMove(IN DIRECTION dir)
{
  int xd;
  int yd;
  int newx;
  int newy;

  switch (dir) {
    case NORTH: xd = 0; yd = -1; break;
    case SOUTH: xd = 0; yd = 1; break;
    case EAST: xd = 1; yd = 0; break;
    case WEST: xd = -1; yd = 0; break;
  }

  newx = mCharacterX + xd;
  newy = mCharacterY + yd;
  if (newx < 0 || newx >= mGridWidth || newy < 0 || newy >= mGridHeight || !GridIsBackground(newx, newy)) {
    return EFI_UNSUPPORTED;
  }

  GridSet(newx, newy, mCharBlt);
  GridSetBackground(mCharacterX, mCharacterY);
  mCharacterX = newx;
  mCharacterY = newy;
  return EFI_SUCCESS;
}

此函数根据用户的输入,在当前位置显示角色,并消去上一位置的角色图形。而当角色的位置与迷宫障碍物相同时(也即石头组成的墙壁),则不进行任何操作。

2 编译运行

使用如下命令编译:

C:\vUDK2018\edk2>build -p RobinPkg\RobinPkg.dsc -m RobinPkg\Applications\Maze\MazeGame.inf -a IA32

将编译好的MazeGame.efi,以及项目工程中的bg.bmp、char.bmp和object.bmp拷贝到Tiancore模拟器所在的目录,启动UEFI Shell,运行MazeGame.efi,效果如图2所示。

图2 运行迷宫小游戏

Gitee地址:https://gitee.com/luobing4365/uefi-explorer
项目代码位于:/ FF RobinPkg/RobinPkg/Applications/Maze下

1,563 total views, 1 views today

发表评论

电子邮件地址不会被公开。