在命令行游戏中避免大量switch case的更好解决方案

huangapple go评论60阅读模式
英文:

Better solution to a ton of switch cases in command line game

问题

我目前正在用C++编写一个项目,将书籍《格斗幻想》转化为一个命令行游戏。对于那些不了解的人,格斗幻想是一款互动小说游戏,您从第一页开始,根据您做出的决定,翻到不同的页面。例如,在第1页,“您来到一个洞穴,翻到第30页向东走,翻到第40页向西走”。

现在,我当前的实现方式是:我将游戏的所有机制(物品、属性、战斗等)抽象成不同的文件/函数,我的主函数充当控制器。我将每一页视为一个事件,例如,如果玩家向东走,那么事件被设置为30。在主函数中有一个大型的switch case,位于while循环内,每个case调用需要运行该页面特定机制的函数,然后设置下一个事件,再次循环。

现在,这本书中有400个事件,也就是说有400个case。我的问题是,是否拥有这么多case是设计不良的?或者在这种情况下,这种非常简单/逻辑的方式导航书籍/故事是可以接受的?此外,如果这真的很糟糕,您有什么建议来改进游戏的逻辑流程?

谢谢!

英文:

I’m currently writing a project in c++
to turn the book “Fighting Fantasy” into a command line game. For those of you who don’t know, Fighting Fantasy is an interactive novel game where you start on page one and based on decisions you make, you flip to different pages. For example, on page 1 “you arrive at a cave, flip to page 30 to go east, flip to page 40 to go west.”

Now the way I currently have this implemented is: I have all the mechanics of the game (items, stats, battles, etc.) abstracted into different files/functions and my main function serves as the control. I treat every page as an event, so for example, if the player goes east, then the event is set to 30. There’s a MASSIVE switch case inside a while loop that’s inside main, and each case calls the functions required to run the specific mechanics for that page, then sets the next event before it loops again.

Now, there are 400 events in this book, which means 400 cases. My question is, is it poor design to have that many cases? Or is it okay in this situation since it’s a very simple/logical way of navigating the book/story? Furthermore, if this really poor, what suggestions would you have for improving the logic flow for this game?

Thank you!

答案1

得分: 1

以下是您提供的程序的翻译:

如果您的程序只是打印页面的文本内容,然后执行后续操作,而且这个操作要么是

1. 前往下一页,或者
2. 跳转到特定页码,或者
3. 向用户提问并根据答案执行操作,或者
4. 退出程序,

那么您可以为每个页面和每个问题的每个可能选择分配一个 `struct action`,并像这样定义 `struct action`  `struct question`:

```cpp
struct question;

struct action
{
    enum class types
    {
        GO_TO_NEXT_PAGE,
        JUMP_TO_PAGE,
        ASK_QUESTION,
        EXIT_PROGRAM
    } type;

    union
    {
        // 当 "type" 为 JUMP_TO_PAGE 时,此成员才有效
        int target;

        // 当 "type" 为 ASK_QUESTION 时,此成员才有效
        question* q;
    };
};

struct question
{
    const char* text;
    std::vector<action> actions;
};

这样,如果您有一个对象数组,每个对象表示一个页面,并且每个这样的对象都包含一个 struct action,您只需要处理这些操作所需的程序逻辑。您将能够添加游戏页面,而无需添加额外的程序逻辑。

这是一个完整的工作程序示例:

#include <iostream>
#include <vector>
#include <exception>

struct question;

struct action
{
    enum class types
    {
        GO_TO_NEXT_PAGE,
        JUMP_TO_PAGE,
        ASK_QUESTION,
        EXIT_PROGRAM
    } type;

    union
    {
        // 当 "type" 为 JUMP_TO_PAGE 时,此成员才有效
        int target;

        // 当 "type" 为 ASK_QUESTION 时,此成员才有效
        question* q;
    };
};

struct question
{
    const char* text;
    std::vector<action> actions;
};

struct page
{
    const char* text;
    action a;
};

// 此函数初始化所有游戏数据并返回指向第一页的指针
page* init_game()
{
    // 创建问题
    static question questions[] =
    {
        // 问题 0
        {
            "你想要\n"
            "1. 向西前进,还是\n"
            "2. 向东前进?\n"
            "输入选择:",
            {
                {
                    .type = action::types::GO_TO_NEXT_PAGE,
                    .target = -1
                },
                {
                    .type = action::types::JUMP_TO_PAGE,
                    .target = 2
                }
            }
        },

        // 问题 1
        {
            "你想要\n"
            "1. 与食人魔战斗,还是\n"
            "2. 逃跑?\n"
            "输入选择:",
            {
                {
                    .type = action::types::GO_TO_NEXT_PAGE,
                    .target = -1
                },
                {
                    .type = action::types::JUMP_TO_PAGE,
                    .target = 4
                }
            }
        }
    };

    // 创建页面
    static page pages[] =
    {
        // 注意:游戏打印的页面编号是从 1 开始的,而程序代码内部的页面编号是从 0 开始的。

        // 页面 0
        {
            "你在一片森林中。向西,你看到一条\n"
            "河流。向东,你看到一个食人魔。\n",
            {
                .type = action::types::ASK_QUESTION,
                .q = &questions[0]
            }
        },

        // 页面 1
        {
            "你走到了河边。在河里喝了一些水后,\n"
            "你没有找到其他有趣的东西,\n"
            "于是你返回了森林。\n",
            {
                .type = action::types::JUMP_TO_PAGE,
                .target = 0
            }
        },

        // 页面 2
        {
            "你正在被食人魔攻击。\n",
            {
                .type = action::types::ASK_QUESTION,
                .q = &questions[1]
            }
        },

        // 页面 3
        {
            "你用剑击中了食人魔并将其杀死。\n",
            {
                .type = action::types::EXIT_PROGRAM,
                .target = -1
            }
        },

        // 页面 4
        {
            "当你试图逃跑时,食人魔\n"
            "投掷斧头并将你杀死。\n",
            {
                .type = action::types::EXIT_PROGRAM,
                .target = -1
            }
        }
    };

    return &pages[0];
}

void game_logic( page* pages )
{
    int page_num = 0;

    for (;;)
    {
        // 打印页面内容
        std::cout << "页面 " << page_num + 1 << ":\n";
        std::cout << pages[page_num].text;

        // 执行页面的操作
        action* a = &pages[page_num].a;
        for (;;)
        {
            switch ( a->type )
            {
                case action::types::GO_TO_NEXT_PAGE:
                    page_num++;
                    break;
                case action::types::JUMP_TO_PAGE:
                    page_num = a->target;
                    break;
                case action::types::ASK_QUESTION:
                {
                    int answer;

                    // 打印问题
                    std::cout << a->q->text;

                    // 获取用户输入
                    std::cin >> answer;
                    if ( !std::cin )
                        throw std::runtime_error( "输入错误" );

                    // 执行下一个操作
                    a = &a->q->actions.at(answer-1);
                    continue;
                }
                case action::types::EXIT_PROGRAM:
                    return;
                default:
                    throw std::runtime_error( "内部错误" );
            }

            // 添加换行
            std::cout << '\n';

            // 完成所有操作,跳出无限循环
            break;
        }
    }
}

int main()
{
    page* first = init_game();

    try
    {
        game_logic( first );
    }
    catch ( const std::exception &e )
    {
        std::cerr << "发生异常:" << e.what() << '\n';
    }
}

这个程序的行为

英文:

If all your program does is print the text content of a page and then perform an action afterwards, and if this action is either to

  1. go to the next page, or
  2. jump to a certain page number, or
  3. ask the user a question and perform an action depending on the response, or
  4. exit the program,

then you could assign a struct action to every page and to every possible choice of a question, and define struct action and struct question like this:

struct question;
struct action
{
enum class types
{
GO_TO_NEXT_PAGE,
JUMP_TO_PAGE,
ASK_QUESTION,
EXIT_PROGRAM
} type;
union
{
//this member is only valid when &quot;type&quot; is JUMP_TO_PAGE
int target;
//this member is only valid when &quot;type&quot; is ASK_QUESTION
question *q;
};
};
struct question
{
const char* text;
std::vector&lt;action&gt; actions;
};

That way, if you have an array of objects that each represent a page and if every such object contains a struct action, you will only need the program logic necessary to process these actions. You will be able to add pages to the game without the need of adding additional program logic.

Here is an example of a full working program:

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;exception&gt;
struct question;
struct action
{
enum class types
{
GO_TO_NEXT_PAGE,
JUMP_TO_PAGE,
ASK_QUESTION,
EXIT_PROGRAM
} type;
union
{
//this member is only valid when &quot;type&quot; is JUMP_TO_PAGE
int target;
//this member is only valid when &quot;type&quot; is ASK_QUESTION
question* q;
};
};
struct question
{
const char* text;
std::vector&lt;action&gt; actions;
};
struct page
{
const char* text;
action a;
};
//this function initializes all game data and
//returns a pointer to the first page
page* init_game()
{
//create questions
static question questions[] =
{
//question 0
{
&quot;Do you want to\n&quot;
&quot;1. head west, or\n&quot;
&quot;2. head east?\n&quot;
&quot;Enter choice: &quot;,
{
{
.type = action::types::GO_TO_NEXT_PAGE,
.target = -1
},
{
.type = action::types::JUMP_TO_PAGE,
.target = 2
}
}
},
//question 1
{
&quot;Do you want to\n&quot;
&quot;1. fight the ogre, or\n&quot;
&quot;2. run away?\n&quot;
&quot;Enter choice: &quot;,
{
{
.type = action::types::GO_TO_NEXT_PAGE,
.target = -1
},
{
.type = action::types::JUMP_TO_PAGE,
.target = 4
}
}
}
};
//create pages
static page pages[] =
{
//NOTE: The page numbers printed by the game are
//      1-based, whereas the page numbers inside
//      the program&#39;s code are 0-based.
//page 0
{
&quot;You are in a forest. To the west, you see a\n&quot;
&quot;river. To the east, you see an ogre.\n&quot;,
{
.type = action::types::ASK_QUESTION,
.q = &amp;questions[0]
}
},
//page 1
{
&quot;You reach the river. After drinking some water out\n&quot;
&quot;of the river, you find nothing further of interest,\n&quot;
&quot;so you head back east to the forest.\n&quot;,
{
.type = action::types::JUMP_TO_PAGE,
.target = 0
}
},
//page 2
{
&quot;You are being attacked by an ogre.\n&quot;,
{
.type = action::types::ASK_QUESTION,
.q = &amp;questions[1]
}
},
//page 3
{
&quot;You hit the ogre with your sword and kill it.\n&quot;,
{
.type = action::types::EXIT_PROGRAM,
.target = -1
}
},
//page 4
{
&quot;While you attempt to run away, the ogre\n&quot;
&quot;throws its axe at you and it kills you.\n&quot;,
{
.type = action::types::EXIT_PROGRAM,
.target = -1
}
}
};
return &amp;pages[0];
}
void game_logic( page* pages )
{
int page_num = 0;
for (;;)
{
//print the page content
std::cout &lt;&lt; &quot;Page &quot; &lt;&lt; page_num + 1 &lt;&lt; &quot;:\n&quot;;
std::cout &lt;&lt; pages[page_num].text;
//perform action(s) for the page
action* a = &amp;pages[page_num].a;
for (;;)
{
switch ( a-&gt;type )
{
case action::types::GO_TO_NEXT_PAGE:
page_num++;
break;
case action::types::JUMP_TO_PAGE:
page_num = a-&gt;target;
break;
case action::types::ASK_QUESTION:
{
int answer;
//print the question
std::cout &lt;&lt; a-&gt;q-&gt;text;
//get response
std::cin &gt;&gt; answer;
if ( !std::cin )
throw std::runtime_error( &quot;input error&quot; );
//perform the next action                    
a = &amp;a-&gt;q-&gt;actions.at(answer-1);
continue;
}
case action::types::EXIT_PROGRAM:
return;
default:
throw std::runtime_error( &quot;internal error&quot; );
}
//add spacing
std::cout &lt;&lt; &#39;\n&#39;;
//we have completed all actions, so break out
//of infinite loop
break;
}
}
}
int main()
{
page* first = init_game();
try
{
game_logic( first );
}
catch ( const std::exception &amp;e )
{
std::cerr &lt;&lt; &quot;Exception occurred: &quot; &lt;&lt; e.what() &lt;&lt; &#39;\n&#39;;
}
}

This program has the following behavior:

Page 1:
You are in a forest. To the west, you see a
river. To the east, you see an ogre.
Do you want to
1. head west, or
2. head east?
Enter choice: 1
Page 2:
You reach the river. After drinking some water out
of the river, you find nothing further of interest,
so you head back east to the forest.
Page 1:
You are in a forest. To the west, you see a
river. To the east, you see an ogre.
Do you want to
1. head west, or
2. head east?
Enter choice: 2
Page 3:
You are being attacked by an ogre.
Do you want to
1. fight the ogre, or
2. run away?
Enter choice: 2
Page 5:
While you attempt to run away, the ogre
throws its axe at you and it kills you.

Ok, that didn't go well, so let's try a different choice:

Page 1:
You are in a forest. To the west, you see a
river. To the east, you see an ogre.
Do you want to
1. head west, or
2. head east?
Enter choice: 2
Page 3:
You are being attacked by an ogre.
Do you want to
1. fight the ogre, or
2. run away?
Enter choice: 1
Page 4:
You hit the ogre with your sword and kill it.

huangapple
  • 本文由 发表于 2023年6月6日 11:04:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76411194.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定