代码示例:PMAN – 持久内存版本的吃豆人 (Pac-Man) 游戏

文件:下载
许可:3 条款 BSD 许可
面向...优化 
操作系统:Linux* 内核版本 4.3 或更高版本
硬件:模拟:如何使用动态随机存取内存 (DRAM) 模拟持久内存
软件:
(编程语言、工具、IDE 和框架)
C++ 编译器和持久内存开发工具包 (PMDK)
前提条件:熟悉 C++

概述

User interface for a Pac-Man type gamePMAN 是一款吃豆人游戏,通过使用事务、持久内存指针和持久内存池展示持久内存的优势。PMAN 的对象存储在持久内存中,允许安全地终止游戏,然后恢复到相同的状态。例如,在断电等糟糕情形下,用户可以恢复游戏。在本例中,我们将介绍代码,讨论持久内存概念的实施并向您展示如何运行 PMAN 游戏,回顾一下 PMAN 成为持久游戏的原因。 

 

持久内存

本文假定您对持久内存 (PMEM) 概念有基本的了解,并且熟悉持久性内存开发工具包 (PMDK) 的功能。如果您不具备这些知识,请访问英特尔® 开发人员专区 持久内存编程 网站,在那里您可以找到入门所需的信息。继续阅读,了解如何使用持久内存开发 PMAN,这是持久内存版本的吃豆人游戏。

PMAN 游戏设计

我们来看看 PMAN 代码的结构。总共有 7 个类:Point、Bomb、Player、Alien、Intro、Board_State 和 State。每个类都包含自己的函数,但我们将看到的主要类包括:

  • State: 这个类会跟踪游戏的状态。它包含恢复/重置游戏、处理移动和碰撞以及打印“开始”和“游戏结束”消息的功能。
  • Board_State:这个类会跟踪游戏板上的对象和字段的大小。它包含跟踪元素及其位置的功能,无论玩家是否在游戏中死亡以及炸弹是否爆炸(炸弹可放置在游戏板上的任何位置,并为您提供放置它的行中的所有可用点)。
  • 玩家:这个类负责在地图上移动玩家。它包含函数进程,它根据用户输入更改玩家元素的方向。

结构和 UML* 图

设计将实现持久化的对象的结构和类型对于使用 libpmemobj ++ 创建 PMEM 感知代码至关重要。创建持久数据结构时,首先需要确定根对象。在这种情况下,状态类是根,这意味着它对于到达程序中创建的所有其他对象是唯一且必不可少的。我们稍后将详细讨论状态类以及它成为根的原因。对于 PMAN 代码示例,我们使用以下持久数据结构。

persistent data structure map

游戏执行

在 PMAN.cpp 的主要功能中,您可以看到 PMAN 的初始化逻辑是如何工作的。它的第一件任务是检查参数的数量及其顺序是否正确。以下代码段显示了调用游戏所需的正确格式:它需要游戏文件名,如果您有默认地图以外的其他内容,则可以选择输入地图文件:

./pman <game session file> [map file]

在接下来的几行中,它会检查先前指定的游戏会话文件是否已存在。此时会有以下两种情况之一:输入的文件存在并且游戏使用其中的数据结构运行(通过映射文件的第一个内存),或者文件不存在并且游戏创建新文件(也使用新的新数据结构初始化新游戏)。

下面的代码片段显示了这种情况。在这种情况下,池检查存储在状态中的 LAYOUT_NAME,并检查它是否与输入的游戏文件匹配。

if (pool<examples::state>::check(name, LAYOUT_NAME) == 1)
	pop = pool<examples::state>::open(name, LAYOUT_NAME);
else
	pop = pool<examples::state>::create(
		name, LAYOUT_NAME, PMEMOBJ_MIN_POOL * 2);

现在我们已经知道游戏如何开始执行,接下来我们回顾一下我们之前发现的三个主类。在这三个类中的每个类中,我们将讨论使每个类中的项目持久化的原因。

状态类

正如我们之前讨论的,State 类是根对象,同时显示了程序的统一建模语言* (UML*) 图。根对象会连接持久内存池中的所有其他对象。这样,如果池的内存范围位置发生更改,您仍然可以从根目录访问所有其他对象。在 PMAN(以及使用 libpmemobj++ 的其他持久内存代码示例)中,池对象指针 (POP) 是用于连接内存池的对象。我们可以使用 POP 获取指向根对象的指针,它在State 中为:

persistent_ptr<examples::state> r = pop.get_root();

如果您想要复习或想要阅读有关池和根对象的更多信息,请查看 introduction to libpmemobj pmem.io 中的教程。

State 类中的第一个函数是 state::init(),用于初始化游戏。在这个函数中我们遇到了第一个事务。

事务是一种保护数据结构免遭可能由于中断(如断电等)导致的损坏的方法。事务通过在中断发生后回滚更改来实现这项功能。事务有三种类型:闭包、手动和自动。这些事务有其微妙的差异;例如,闭包事务自动提交或中止,而手动事务最终需要手动提交。我们在整个 PMAN 代码示例中看到了手动事务。如欲了解关于事务及其实施方式的更多详细信息,请参见 pmem.io 上的libpmemobj 的 C++ 绑定(第 6 部分) – 事务。

state::init() 内的事务是手动的,这意味着它最终必须提交 (Transaction::commit())。这一事务正在保护初始化游戏点和方向的过程。在本例中,intro_p 是游戏中使用的对象列表。事务可确保游戏不会以半初始化的方式结束。

{
transaction::manual tx(pop);
if (intro_p->size() == 0) {
	for (int i = 0; i < SIZE / 4; i++) {
		intro_p->push_back(
			make_persistent<intro>(i, i, DOWN));
		intro_p->push_back(make_persistent<intro>(
			SIZE - i, i, LEFT));
		intro_p->push_back(make_persistent<intro>(
			i, SIZE - i, RIGHT));
		intro_p->push_back(make_persistent<intro>(
			SIZE - i, SIZE - i, UP));
		}
	}
	Transaction::commit();
}

Board_State 类

board_state 类从这个类的一个构造函数开始,这个构造函数用于初始化吃豆人游戏的 “board” (map) 和必要的变量。该构造函数在持久内存上分配大小为 SIZE*SIZE 的字段元素组,并向其返回持久指针对象。随后,持久指针对象将被分配到 board_state::board 字段。

board_state::board_state(const std::string &map_file) : highscore(0){
	reset_params();
	board = make_persistent<field[]>(SIZE * SIZE);
	board_tmpl = make_persistent<field[]>(SIZE * SIZE);
	for (int i = 0; i < SIZE * SIZE; ++i)
		set_board_elm(i, 0, FREE);
	set_board(map_file);
}

这一分配作为构建 board_state 的一部分完成,在 state::new_game 方法中进行。这种方法使用一种事务,这意味着如果它中止,则回滚分配并将内存返回到其原始状态。值得一提的是,如果未在事务内调用,make_persistent 将失败。如欲深入了解关于 make_persistent 函数的更多信息,请阅读 libpmemobj 的 C++ 绑定(第 5 部分) – make_persistent

通过使用调用 delete_persistent 的析构函数,则可以释放对象板和 board_tmpl 在上述构造函数和指针所指向的构造函数中分配的内存,如下所示:

board_state::~board_state(){
	delete_persistent<field[]>(board, SIZE * SIZE);
	delete_persistent<field[]>(board_tmpl, SIZE * SIZE);
}

Player 类

player 类包含 player::progress 函数,用于检查键盘的输入并使用该输入为游戏中的玩家设置正确的方向。除了设置玩家的方向之外,它还允许您放置炸弹,这些炸弹将炸毁玩家所在的整行,并为您提供该行内的可用点数。您可以查看下面的代码片段,了解该函数如何使用 switch case 根据提供的输入执行不同的操作:

player::progress(int in, bomb_vec *bombs){
	switch (in) {
			case KEY_LEFT:
			case 'j':
				dir = LEFT;
				break;
			case KEY_RIGHT:
			case 'l':
				dir = RIGHT;
				break;
			case KEY_UP:
			case 'i':
				dir = UP;
				break;
			case KEY_DOWN:
			case 'k':
				dir = DOWN;
				break;
			case KEY_SPACE:
			case 'b':
				dir = STOP;
				if ((*bombs)->size() <= MAX_BOMBS)
					(*bombs)->push_back(make_persistent<bomb>(x, y));
				break;
	}
	move();
dir = STOP; }

设置 (dir) 方向后,point 类中的 15px;">point::move 函数将通过调整位置点在该方向上移动对象。

运行游戏

现在您已经了解了 PMAN 游戏的工作方式,并掌握了使游戏持久化的特定元素,您可以构建和运行游戏了。

首先下载并构建 PMDK。包括依赖关系在内的安装帮助可以在 PMDK Readme 中找到。

执行此操作后,您可以看到 gameFile 是存储游戏会话的地方。这个位置可能是在您第一次玩游戏时创建的,或者您可以在您之前玩游戏的位置打开一个游戏文件。如果这是第一次,请为文件命名并像这样开始游戏:

$ ./pman firstGame

控制

一旦您输入该命令,PMAN 游戏就会启动,现在您可以开始玩游戏了。若要启动游戏,请按 s。若要退出,请按 q。在游戏中,玩家可以通过箭头键控制,也可以通过 j(向左)、l(向右)、i(向上)和k(向下)键控制。游戏还实施了炸弹,您可以按 空格键b 来触发。炸弹可以让您获得整行或整列的分数。但务必小心,因为炸弹也会炸死您。您可以随时按 q 退出游戏。您可以使用相同的文件名启动游戏,以便重新开始。

游戏目标

这款游戏的目标是获得最高分,方法是操控吃豆人吃掉所有点和其他物品,以赢得积分并通过关卡。

总结

PMAN 代码示例旨在向您介绍持久内存编程模型,以便了解哪些附加项可以使程序持久化,从而让您将其用于开发。我们向您介绍了持久内存池、指针和事务的示例,并提供了其他链接,以便您可以了解有关每个主题的更多信息。

我们简要回顾一下我们在 PMAN 代码示例中看到的内容。State 类是用于固定持久内存池中所有其他对象的根对象。PMAN 还在整个 State 类中使用事务来保护数据结构免遭损坏。我们引入了关于指针的 make_persistentdelete_persistent 函数,并介绍了持久指针。如果您想要了解更多信息,建议您深入了解一下 PMAN 或研究一下我们在 software.intel.com 上或 GitHub* 存储库 中的其他 代码示例 。您还可以浏览我们在 GitHub 上的其他 PMDK 示例。

Для получения подробной информации о возможностях оптимизации компилятора обратитесь к нашему Уведомлению об оптимизации.
Возможность комментирования русскоязычного контента была отключена. Узнать подробнее.