4

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

 2 years ago
source link: http://yiiyee.cn/blog/2021/08/19/uefi%e5%bc%80%e5%8f%91%e6%8e%a2%e7%b4%a294-%e8%bf%b7%e5%ae%ab%e5%b0%8f%e6%b8%b8%e6%88%8f/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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-exolorer
项目代码位于:/ FF RobinPkg/RobinPkg/Applications/Maze下

26 total views, 1 views today


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK