“出去” - 一个简单的棋盘游戏
一个简单的棋盘游戏
引言

“移出我”是一款棋盘游戏,目标是将中间最大的洋红色块从顶部中间移到底部中间。你需要移动其他块来为大块腾出空间。游戏有 3 个级别:初学者、中级和专家。你可以从菜单中更改级别。
这款软件的奇妙之处在于它只有 2 个主要类:Block
和 Board
。代码也非常简单。因此,我认为它非常适合初学者学习面向对象编程。或者,希望能爱上游戏开发。:) 另一个有趣之处在于 UI 设计。我看到很多棋盘游戏,用户要么需要拖放,要么需要几个步骤才能移动一个项目。这是因为要让一个项目移动,用户需要告诉它移动以及移动到哪里。这款游戏使用一个箭头来显示块将移动的方向,该方向随鼠标移动而变化。由于用户已经有了方向,他们只需要单击一下鼠标即可。
在继续阅读之前,我建议您玩一下游戏并享受乐趣。您可能想将级别设置为初学者以熟悉用户界面。
背景
这是一款拥有千年历史的中国游戏。它讲述了一场伟大的战斗。战败者竭尽全力逃跑,这几乎是不可能的。但他做到了。2x2 的正方形,即洋红色块,代表那个试图逃跑的人。如果您玩过这款游戏,您就会明白为什么它“几乎不可能”。
Using the Code
Block
类继承自 Button
类,因此可以单击。它的样式已更改为平面风格,使其看起来更像一个块。由于我们将通过代码将块添加到棋盘,因此我们使用 ToolboxItem(false)
属性来修饰该类,以防止它出现在 Visual Studio 工具箱窗口中。
[ToolboxItem(false)]
public class Block : Button {
...
}
Block
类负责记住其大小和位置。当鼠标悬停在它上面时,会显示一个箭头。箭头会随着鼠标的移动而改变方向。我们将在 UI 设计部分更详细地讨论这一点。
棋盘被划分为 4 x 5 个方格,如下所示:

Board
类负责记住方格的状态,这些方格可以是已占用或空闲的。它也负责移动块。要移动块,Board
类会检查 Block
是否可以移动。Board
知道这一点,因为它拥有每个方格的记录。通过询问 Block
类,它知道 Block
的位置和大小,以及移动方向,从而可以决定 Block
是否可以移动。如果可以,Block
会更新其位置,Board
会更新其状态。以下是移动块向左的代码。这是 Board
类中的一个方法:
void MoveLeft(Block block) {
int oldX = block.X;
if (oldX == 0) return;
int newX = oldX - 1;
//Check whether we can move the block
for (int i = block.Y; i < block.Y + block.YSpan; ++i) {
if (_occupied[newX, i]) return;
}
//Move it
block.MoveBlock(newX, block.Y);
//Change board status
for (int i = block.Y; i < block.Y + block.YSpan; ++i) {
_occupied[newX + block.XSpan, i] = false;
_occupied[newX, i] = true;
}
//Add step
_steps++;
}
前两行检查块是否已经在最左边。如果是,它就不能再向左移动,程序将返回。“newX
”是块移动后的 x 位置。要理解后面的代码,我们需要弄清楚 Block
类的属性。我们举个例子。游戏开始时,大块位于 (1, 0)。在这种情况下,block.X = 1
、block.Y=0
、block.YSpan=2
(表示块跨越的行数)和 block.XSpan=2
(表示块跨越的列数)。
在第一个 for
循环中,我们检查块左侧的方格是否都为空。如果任何方格被占用,则块无法移动,程序将立即返回。对于大块的例子,如果我们想将它向左移动,我们需要检查 (0,0) 和 (1,0) 的状态。
第二个 for
循环改变棋盘状态。如果大块从 (1,0) 移动到 (0,0),我们需要将 (0,0) 和 (1,0) 的状态更改为已占用,将 (2, 0) 和 (2,1) 更改为空闲。
UI 设计
在此程序中,我们通过使用箭头显示方向和单击触发移动来移动块。为了实现这一点,我们将块分为 4 个区域,如下所示:

当鼠标移动到左侧区域时,块的图像会更改为左箭头。其他区域都有相应的图像,并且行为方式相同。
要获得每个区域的功能需要一些几何知识。基本上,你需要知道两条线的函数。其中一条线经过点 (0,0) 和 (w, h),所以直线是 y=(h/w)*x。另一条线经过 (w, 0) 和 (0, h),所以直线是 y=h-(h/w)*x。
代码如下,它在 Block
类中,用于 MouseMove
事件:
void Block_MouseMove(object sender, MouseEventArgs e) {
int x = e.X;
int y = e.Y;
int h = this.Height;
int w = this.Width;
double k = (double)h / (double)w;
int newIndex = -2;
int kx = (int)(k * x + 0.5);
if (y < h - kx) {
if (y < kx) {
newIndex = Direction_Up;
} else if (y > kx) {
newIndex = Direction_Left;
}
} else if (y > h - kx) {
if (y < kx) {
newIndex = Direction_Right;
} else if (y > kx) {
newIndex = Direction_Down;
}
}
if (newIndex >= 0 && _direction != newIndex) {
this.Image = Button_Images[newIndex];
_direction = newIndex;
}
}
}
Button_Images
是一个 static
的 4 个图像数组。图像来自项目资源。
下一步
可以通过执行以下操作来改进此程序:
- 保存/加载程序
- 游戏求解器
保存和加载可以通过将每个块的位置保存到 XML 文件中来完成。用户移动的总步数也应保存。棋盘状态不需要保存,因为加载时可以重新建立棋盘状态。
游戏求解器是可能的,因为在每个游戏状态下,可用的移动次数都不多。如果我发现人们喜欢这款游戏,我会写一个游戏求解器。
历史
- 版本 1.0 于 2010 年 3 月 14 日发布
- 于 2010 年 3 月 21 日添加了更多文档