65.9K
CodeProject 正在变化。 阅读更多。
Home

保龄球记分牌

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (10投票s)

2012年2月6日

CPOL

6分钟阅读

viewsIcon

47767

downloadIcon

1641

用于记录和显示十柱保龄球得分的命令行应用程序。

引言

十瓶保龄球的规则很容易理解,但在代码实现上可能会有些棘手。Tomas Brennan 的一个示例可以在这里找到,其中包含一个 C# 单人 GUI,用于输入得分和计算结果。我采用的方法是实现一个记分板,包括在命令提示符中显示的相应布局和计算。它还以非常不同的方式存储内部数据,从而支持一套不同的功能。

背景

作为一项技术测试,我被要求创建一个十瓶保龄球比赛的记分板,验证所有输入并包含与比赛相关的所有规则。本文提供的示例涵盖了在 Windows 命令提示符或 Mac OS X 终端中以文本形式打印记分板。当然,这也可以扩展到在 GUI 中显示值。

Using the Code

底层系统(即不包括 InterfaceManager 的实现)被设计为仅依赖 STL 类,因此是完全跨平台的。虽然没有创建多种游戏形式,但游戏逻辑已从主程序中分离出来(使用两个类:ProgramBowlingGame),以便此类更改将是微不足道的。为了简单起见并节省时间,InterfaceManager 分别在两个独立的部署中编写了两次,而不是创建一个抽象接口来确定输出方法(或者更糟的是,将接口包装在大型预处理器指令块中)。

开始:初始化

初始化很简单;设置初始默认值并请求玩家数量。技术规范没有提供最大玩家数量,所以我将其设置为六。为了从用户那里获取值,所有操作都通过 InterfaceManager(外部称为 Interface)进行。这包括一个简单的 std::cinstd::string 值,然后将其解析为适当的类型。要从用户那里获取一到六之间的玩家数量,将使用以下代码

int playerCount = 0;
do
{
    Interface.drawSplashScreen(*this);
    playerCount = Interface.getIntegerValue(" Please enter the number of players (1-6): ");
}
while (playerCount < 1 || playerCount > 6);

这仅仅是绘制欢迎屏幕(在这些控制台版本中包含一些 ASCII 艺术),打印要求输入整数的消息,然后等待输入值。我不相信 std::cin 可以直接将输入值放入除 string 以外的任何内容,因此我使用了我的 GameUtil 类中的 stringToInteger 函数。

一旦程序获取了介于一到六之间的数字,它将继续询问每个新玩家的名字。这方面的验证很简单;任何长度在三到十六个字符之间且尚未被已有玩家使用的名字。

325728/PlayerNames.jpg

额外的输入验证很容易实现(例如,只允许字母,首字母大写,后续字母小写)。当所有玩家设置完毕后,游戏的初始化就完成了——它从第一局、第一帧开始,程序开始游戏的更新周期。

轮流:更新循环

下一行确定轮到哪个玩家

_turn = (_turn + 1) % _players.size(); 

当轮到玩家 1 时,它开始下一轮(或者游戏术语中的帧)。

if (_turn == 0)
    _frame++;

与其处理每次投球的更新周期,不如让一个给定的周期代表玩家的整个回合。这意味着玩家将继续投球,直到他们的回合在此帧中结束。玩家通过调用他们的 bowlBall 函数来推进他们的回合,直到它返回 true,表示回合结束。

投出一球是一个简单的过程,使用以下代码

do
{
    scoreThisBowl = Interface.getIntegerValue(" How many pins were hit on this bowl? ");
}
while (!isValidScore(scoreThisBowl, currentFrame)); 

注意:这已从 Windows 版本中缩减,因为控制台类在非 Windows 系统中无效。

isValidScore 函数仅确保输入了一个逻辑数字。大多数情况下,这是一个介于 0 和 10 之间的值。否则,如果不是最后一帧并且已经投出了非全中的第一球,它将是介于 0 和(10 - 第一球得分)之间的值。

输入有效得分后,玩家会用新得分“更新”他之前的所有得分。这可以通过不更新超过最后两个得分来稍微优化,但这几乎不是系统性能瓶颈。update 调用只是在适用时将新得分作为奖励加到旧得分上。这在首次记录得分时就已确定;如果是全中(Strike),它会将接下来的两个得分记录为奖励。如果是补中(Spare),它会将下一个得分记录为奖励。当所有奖励都已应用于一个得分时,它将不再为自己添加奖励。

显示记分板:界面

除了 Windows 版本中的一些实用程序外,InterfaceManager 类包含三个主要函数:drawSplashScreendrawScoreTabledrawGameOverScreen。欢迎屏幕仅显示一些保龄球 ASCII 艺术和玩家姓名列表。

记分表是这个程序中最具变化的部分,在 Windows 和 Mac OS X 版本之间需要最多的修改。这是因为 Mac 终端不支持与不同字体颜色相关的实用功能或手动定位窗口周围的光标。因此,它需要逐行生成记分表,而不是先打印记分表然后适当填充单个得分。在 Windows 中,这包括一些额外的格式化,以根据轮到谁来改变文本颜色,用黄色突出当前玩家的姓名和得分。最后,得分本身根据规则进行格式化;全中(Strike)显示为“X”,补中(Spare)显示为“/”,零得分显示为“-”。

游戏结束屏幕仅显示记分表和 ASCII 奖杯,宣布获胜者和他们取得的得分。最高得分的并列情况会如实显示,团队的总得分也会显示在底部。

325728/Winner.jpg

结束游戏:重玩和关闭

当一局游戏打完十帧并且宣布了获胜者后,程序会提示用户是否要再玩一局。如果想,游戏会重置所有玩家数据并重新运行初始化。否则,游戏将关闭并退出程序。此程序中没有动态分配的内存,因此所有内容在关闭时都会自动清理。

关注点

我学会了如何编写跨平台代码,并且不依赖于那些可能不存在于每台机器上的晦涩、过时或实验性的库。然而,在此项目之前,我从未尝试在 Mac OS X 终端中打印程序;我曾假设,因为它们都使用 std::cout 打印文本,所以我可以非常轻松地重新部署我的程序。

我没有考虑到终端的不同功能,结果重写了相当大的界面部分。这主要涉及移除改变字体颜色和重新定位光标;所有内容都改为逐行打印,导致类似的输出,但需要更多的注意和计算。

历史

  • 2012 年 2 月 6 日:初始发布

在 Windows 7 x64 和 Mac OS X Snow Leopard 上部署并测试了 1.0 版本

© . All rights reserved.