Sokoban Pro






4.65/5 (15投票s)
2004年12月24日
5分钟阅读

114249

3139
Sokoban Pro 是一款经典的 Sokoban 益智游戏的现代版本。
引言
Sokoban Pro 是一款经典的 Sokoban 益智游戏的现代版本。游戏规则极其简单,但游戏极具挑战性和令人上瘾。游戏规则是将所有箱子推到正确的位置。你只能推箱子,不能拉。您可以通过按 U 来撤销最后一步。
游戏的新版本现已在 Sokoban Pro 网站 上发布。由于所有最重要的功能(如读取/写入 XML、移动和绘图)都在 CodeProject 的源代码中,因此我决定保留第一个原始版本。您可以从 Sokoban Pro 网站下载最新版本。
最新版本(v1.0b)相比 CodeProject 上的版本包含以下改进:
- “官方”测试版发布
- 添加了菜单
- 我发现窗口大小随关卡变化很烦人,所以现在它已固定大小
- 您可以跳转到不同的关卡,但只能跳转到您之前完成过的关卡
- 1 步撤销功能
- 启动屏幕和图标
- 修复了许多错误
- 可能还有其他一些东西
游戏开始时,您可以创建一个新玩家或选择一个现有玩家。由于 Sokoban Pro 会保存您的进度,因此您还可以选择是否继续之前的游戏。创建玩家后,您可以选择关卡集。关卡集包含任意数量的关卡。Sokoban Pro 附带了原始 BoxWorld 游戏的前 40 个关卡。关卡集存储在 XML 文件中,这意味着您可以从互联网上不同的 Sokoban 网站下载关卡集。您也可以创建自己的关卡。当您将关卡集放入 levels 目录时,Sokoban Pro 会自动识别它们。
您的存档游戏也是一个 XML 文件。它保存最后一个玩的关卡集和最后一个玩的关卡,以便您可以从上次玩游戏的地方继续。它还保存您已完成的关卡以及得分(移动次数和推动次数)。如果您重试某个关卡并且表现更好,您的得分将得到更新。
基本上,游戏包含以下类:
LevelSet
- 包含关卡集的所有信息(作者信息、关卡数量等)。它还将关卡从关卡集 XML 加载到内存中。Level
- 代表关卡集中的一个关卡。这里发生的最重要的事情是它会跟踪您的所有移动。当玩家移动或推动箱子时,它会更新关卡中的项目。它会更新相应的图形。它实现了撤销功能,最后,它会在屏幕上绘制关卡。PlayerData
- 跟踪所有玩家信息。基本上,它反映了您的存档游戏。Board
(Form
) - 主窗体处理所有玩家输入并初始化所有对象。Players
(Form
) - 允许您创建新玩家或选择现有玩家。Levels
(Form
) - 允许您选择要玩的关卡集。
该应用程序使用 XML 文件的读写。以这个方法为例 - SaveLevel()
- 它会在玩家完成关卡后保存玩家数据。
public void SaveLevel(Level level)
{
XmlDocument doc = new XmlDocument();
doc.Load(filename);
XmlNode lastFinishedLvl = doc.SelectSingleNode("//lastFinishedLevel");
lastFinishedLvl.InnerText = level.LevelNr.ToString();
XmlNode setName = doc.SelectSingleNode("/savegame/levelSets/" +
"levelSet[@title = \"" + level.LevelSetName + "\"]");
XmlNode nodeLevel = setName.SelectSingleNode("level[@levelNr = " +
level.LevelNr + "]");
if (nodeLevel == null)
{
XmlElement nodeNewLevel = doc.CreateElement("level");
XmlAttribute xa = doc.CreateAttribute("levelNr");
xa.Value = level.LevelNr.ToString();
nodeNewLevel.Attributes.Append(xa);
XmlElement moves = doc.CreateElement("moves");
moves.InnerText = level.Moves.ToString();
XmlElement pushes = doc.CreateElement("pushes");
pushes.InnerText = level.Pushes.ToString();
nodeNewLevel.AppendChild(moves);
nodeNewLevel.AppendChild(pushes);
setName.AppendChild(nodeNewLevel);
}
else
{
XmlElement moves = nodeLevel["moves"];
XmlElement pushes = nodeLevel["pushes"];
int nrOfMoves = int.Parse(moves.InnerText);
int nrOfPushes = int.Parse(pushes.InnerText);
if (level.Pushes < nrOfPushes)
{
pushes.InnerText = level.Pushes.ToString();
moves.InnerText = level.Moves.ToString();
}
else if (level.Pushes == nrOfPushes && level.Moves < nrOfMoves)
moves.InnerText = level.Moves.ToString();
}
doc.Save(filename);
}
以下是发生的情况:
我们使用 XPath 来选择 XML 文件中的节点。这样,我们就不必逐行读取 XML 直到找到要添加新元素或更新现有元素的正确元素。这行代码
XmlNode setName = doc.SelectSingleNode("/savegame/levelSets/" +
"levelSet[@title = \"" + level.LevelSetName + "\"]");
选择我们当前正在玩的关卡的关卡集节点。(请记住,Sokoban Pro 支持多个关卡集,因此 XML 中可能有一个以上的关卡集)。然后,这行代码
XmlNode nodeLevel = setName.SelectSingleNode("level[@levelNr = "
+ level.LevelNr + "]");
选择当前关卡集中的关卡编号。
如果我们找不到当前的关卡节点,则意味着我们之前没有完成该关卡,我们会添加一个新的关卡节点。如果我们找到了关卡节点,则意味着我们之前玩过该关卡,我们会检查我们的当前得分是否更好,如果是,则更新得分。
游戏大量使用 XML 文件的读写,正如您所见,使用 XPath 是一个非常强大的工具。
我想展示的另一件事是如何加载关卡。正如我所说,我们将关卡存储在 XML 中。关卡由不同的项目组成;一堵墙、一个地板、一个箱子等。加载关卡时,我们读取 XML 中包含关卡数据的行。关卡中的每个项目都由一个 ASCII 字符表示。读取关卡数据时,我们将项目存储在二维数组中。
当我们想绘制关卡时,我们会读取数组,然后可以像这样绘制关卡:
// Draw the level
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
Image image = GetLevelImage(levelMap[i, j], sokoDirection);
g.DrawImage(image, ITEM_SIZE + i * ITEM_SIZE, ITEM_SIZE
+ j * ITEM_SIZE, ITEM_SIZE, ITEM_SIZE);
// Set Sokoban's position
if (levelMap[i, j] == ItemType.Sokoban
|| levelMap[i, j] == ItemType.SokobanOnGoal)
{
sokoPosX = i;
sokoPosY = j;
}
}
}
我们逐行、逐字符地读取数组。根据我们遇到的项目,我们将获得 GetLevelImage
方法返回的图像,该方法会检查我们有什么项目并返回相应的图像。最后,我们绘制图像,给定项目的位置以及宽度和高度。尺寸存储在 ITEM_SIZE
变量中。如果我们想以更小的尺寸绘制关卡(可能适用于分辨率较低的显示器),我们可以减小 ITEM_SIZE
的值(默认值为 30)。
另一个有趣的事情是检查 Sokoban 是否可以移动。只有当他面前的项目是空地,或者是一个后面有空地的箱子时,他才能移动。当他移动时,我们将 Sokoban 的新位置以及箱子和 Sokoban 留下的空地存储在三个独立的 Item
对象中,我们使用这些对象来只重绘关卡的这三个项目,而不是重绘整个关卡。撤销最后一次推动也是如此。
尽情享用!