【问题标题】:C++ Text file Reading and Parsing for text-based adventure game基于文本的冒险游戏的 C++ 文本文件读取和解析
【发布时间】:2015-02-02 10:35:42
【问题描述】:

在编程方面我有点菜鸟,但作为一个小项目,我决定尝试制作一个非常简单的基于文本的冒险游戏,只是为了一些乐趣和练习。我不知道这些类型的游戏通常是如何制作的,但我决定制作一个包含所有实际文本的文本文件,而不是在代码中全部输入,所以这就是我的“gamelocationdata.txt”文件目前的样子。

[castleStart]
{
=castleStart=
You find yourself in a dark room inside a castle.
The walls are made of mossy stone and the entire room has a very eerie atmosphere.

There is a green bottle on the floor.
There are 2 exits, east and south.
Both exits are large doorways with grand arches.
You can see a large room through the south exit, but the east exit looks very dark and
somewhat frightening.

What will you do?
#"look arch" castleStartLookArch
#"look wall" castleStartLookWall
#"look bottle" castleStartLookBottle itemcond: hasBottle "0"
#"take bottle" castleStartLookBottle itemcond: hasBottle "0"
#"pick bottle" castleStartLookBottle itemcond: hasBottle "0"
#"go south" castleHall
#"go east" castleDark loccond: hasBeenCastleDark "0"
#"wait" castleStartWait
}

[castleStartLookArch]
{
=castleStart=
The arches above the doors look very fancy.
You can't quite figure out why this tiny room deserves to be decorated as such.
#(castleStart)
}

[castleStartLookWall]
{
=castleStart=
The wall is made of stone masonry in an old-fashioned way. 
Some stones are covered in moss and there are cobwebs in the corners.
#(castleStart)
}

[castleStartWait]
{
=castleStart=
You sit still and admire the wall for a while.
Nothing happens.
#(castleStart)
}

[castleStartLookBottle]
{
=castleStart=
You pick the bottle up. It is covered in cobwebs.
The bottle is green and the label reads "1337". It contains a purple liquid.

Do you want to put the bottle in your backpack?
#"yes" castleStartTakeBottle
#"no" castleStartNoTakeBottle
}

[castleStartTakeBottle]
{
=castleStart=
You take the bottle and put it in your backpack.
+item: Bottle1337
+itemcond: hasBottle "1"
#(castleStart)
}

[castleStartNoTakeBottle]
{
=castleStart=
You put the bottle back down again.
#(castleStart)
}

[useBottle1337]
{
=curLocation=
You open the bottle and drink its contents.
It tastes very sweet.

You suddenly feel slightly stronger and more powerful.
+strength: 5
+remove_item: Bottle1337
#(castleStart)
}

[castleHall]
{
=castleHall=
You walk though the southern doorway and enter the grand hall of the castle.
It seems like the entire castle is just as old and worn out as the walls in that room,
though still very nicely decorated.
There are only a few candles on the walls, and they are somehow lit despite
the castle seeming very empty. There is not not a person to be seen.

You can go back north or proceed south through the hall.
#(castleStart)
}

[castleDark]
{
=castleStart=
You slowly tread into the dark room to the east, looking
around you as your surroundings get darker and darker.
Suddenly, you hear a noise. It sounds like the growling of an angry dog!
Horrified, you hastily turn around and run back.
+loccond: hasBeenCastleDark "1"
#(castleStart)
}

我意识到我咬的东西可能比我能咀嚼的要多,但我编的格式应该是这样工作的:

示例:[castleStart] 是“位置”的名称,而大括号 后面的大括号封装了与此有关的所有内容 位置。

示例:=castleStart= 是播放器在何时打印的位置 他们问他们目前在哪里。

之后的内容是播放器在屏幕上打印的内容 “进入”那个位置。

位置文字之后,有一堆选项都开始 带有“#”。

示例:#"wait" castleStartWait 如果玩家键入“wait”,他们将 被带到名为 [castleStartWait] 的“位置”。

示例:#"look bottle" castleStartLookBottle itemcond: hasBottle "0" 如果玩家输入“看瓶”,他们将被带到该位置 命名为 [castleStartLookBottle] 只要满足“item 要求”他们还没有瓶子。

示例:#"go east" castleDark loccond: hasBeenCastleDark "0" 如果 玩家输入“向东”,他们将被带到指定的位置 [castleDark] 只要满足他们的“位置要求” 还没去过。

示例:#(castleStart) 这将使用与 [castleStart] 中列出的选项相同的选项。

示例:+strength: 5 这应该会在玩家进入该位置并打印一些硬编码消息,例如“您已获得 5 个力量点!”时为玩家的“力量”统计数据增加 5 点。 p>

现在,问题来了:如何编写读取和解析特定位置的数据并将它们存储在特定 std::strings 中的函数?

例如,如果我这样做

readAndParseLocationData( castleStart );

它应该在文本文件中查找 [castleStart],然后读取等号 (=castleStart=) 之间的内容并将其存储在“std::string printLoc”中,然后读取文本并存储在“std ::string locText" 等等。

这是我目前的所有代码:

//main.cpp
#include <iostream>
#include <string>
#include "ClearScreen.h"
#include "LocationData.h"

int main()
{
    ClearScreen();
    std::cout << "I am a banana!\n\n"; // this is just a test

    readAndParseLocationData( "castleHall" );
    printLocationData( "castleStart" ); // this is supposed to be used for debugging the location data by printing it.

    return 0;
}

--

//LocationData.cpp
#include "LocationData.h"
#include <fstream>
#include <iostream>
#include <string>

void readAndParseLocationData( std::string location )
{
    location.insert( 0,"[" );
    location.append( "]" );
    std::ifstream locfile( "gamelocationdata.txt" );
    if( locfile.is_open() )
    {
        std::string line;
        bool foundFile = false;
        for( unsigned int curLine = 0; getline( locfile,line ); curLine++ )
        {
            if( line.find( location ) != std::string::npos )
            {
                std::cout << "found: " << location << ", line: " << curLine << "\n";
                foundFile = true;
            }
        }
        if( !foundFile )
        {
            std::cout << "\nERROR: Location " << location << " not found in data file!\n";
        }
        locfile.close();
    }
    else
    {
        std::cout << "\nERROR: Unable to open location data file!\n";
    }
}
void printLocationData( std::string location )
{
    //TODO: Implement
}

我所做的一切(通过广泛的谷歌搜索)是​​查找位置名称并将其打印到控制台上的哪一行。

我在 Windows 7 上使用 Visual Studio Express 2013。

我也很想知道是否有任何方法可以改进我的代码或一般格式!

【问题讨论】:

    标签: c++ windows file parsing file-io


    【解决方案1】:

    似乎您想要做的是在遇到您希望访问的位置时及时解析文件。如果您希望您的游戏变得太大而无法解析一次并存储在内存中,这是一个好主意,但对于像这样的小例子,这可能是不必要的。

    但是,如果您希望将其用于学习目的,则需要考虑一些事项。首先,您当前的想法将涉及每次要查看某个位置时重新解析文件。更好的想法是为位置数据设计某种表示形式并实现某种缓存。一种可能的方法(仅考虑名称和位置文本)是:

    class Location
    {
    private:
        std::string name;
        std::string displayName;
        std::string locationText;
    }
    
    std::unordered_map<std::string, Location> cache;
    
    Location& readAndParseLocationData( std::string location )
    {
        //if we have already parsed this location
        if (cache.count(location))
        {
            return cache[location];
        }
        else
        {
            Location parsed_location{};
            //do the parsing
            cache[location] = parsed_location;
        }
    }
    

    为了真正进行解析,我会写一个简单的recursive descent parser。对于您的情况,它看起来像这样(在伪代码中):

    Until we have found the location:
        Look for a location
        If this location matches:
            Read the location into a string
            Look for a opening brace
            Read the location display name into a string
            Read the rest into a string up until a closing brace
    

    【讨论】:

    • 谢谢,非常有趣。我对课程不是很好(同样,我有点菜鸟),但如果我理解正确,这将在解析后简单地存储位置数据以供将来参考?
    • 是的,这意味着您下次想要该数据时不需要再次解析文件,因为打开和读取文件很昂贵。
    • 由于不同的位置包含不同数量的选项和要存储的其他信息,我将如何创建一个动态类,其中包含该特定位置所需的正确数量的字符串?我不应该声明每个可能的字符串都存储在类中,对吗?然后我只能有预设数量的选项。
    • 使用std::vector&lt;std::string&gt;等数据结构来存储不同数量的字符串。
    • 好的,我在实际解析数据时仍有一些问题。我一直在搜索 C++ 中的递归下降解析器示例,但我没有找到任何好的示例(因为它们都非常具体)。我对它们的工作原理有一个模糊的概念,但如果你能提供一个非伪代码示例来说明如何在我的情况下做到这一点,那就太好了!
    【解决方案2】:

    对于碰巧偶然发现这个 2 年前的问题的未来读者,这是我最终用来解析位置数据的最终代码。

    (它很旧而且无论如何都不漂亮,但请注意,我的主要问题的解决方案是学习 std::getline 和 std::string 的各种操作方法 - 主要是 find() 和 substr()。)

    struct Location final
    {
        std::string displayName;
        std::string optionsName;
        std::string locationText;
        std::string defaultOption;
        std::string shop;
        std::vector<int> battleLevel;
        std::vector<int> battleChanceLevel;
        std::vector<int> battleChancePercentage;
        std::vector<std::string> battleEnemy;
        std::vector<std::string> battleChanceEnemy;
        std::vector<std::string> addItems;
        std::vector<std::string> addWeapons;
        std::vector<std::string> addConds;
        std::vector<std::string> addStats;
        std::vector<std::string> removeItems;
        std::vector<std::string> removeWeapons;
        std::vector<std::string> removeConds;
        std::vector<std::string> removeStats;
        std::unordered_set<std::string> options;
        std::unordered_map<std::string, std::string> resultLoc;
        std::unordered_map<std::string, std::string> conds;
    };
    
    std::unordered_map<std::string, Location> locationCache;
    std::unordered_map<std::string, std::string> locationNewFileCache;
    
    Location& loadLocationData( const std::string& location, const std::string& filename, const bool overRideData, const bool dataFile )
    {
        if( ::locationCache.count( location ) && overRideData == false ) // If we already parsed this location...
            return ::locationCache[ location ]; // Return cached data.
        else
        {
            auto filePath = std::string( "game/data/" );
            if( !dataFile )
                filePath.append( "locations/" );
            std::ifstream locFile( filePath + filename );
            if( locFile.is_open() )
            {
                bool foundLoc = false;
                for( std::string line; std::getline(locFile, line); )
                {
                    Location parsedLocation;
                    if( line.find( "[" + location + "]" ) != std::string::npos )
                    {
                        foundLoc = true;
    
                        // Parse optionsname/next filename.
                        std::string optsName; std::getline( locFile, optsName );
                        if( optsName.find( ".txt" ) != std::string::npos )
                            ::locationNewFileCache[ location ] = optsName;
                        else
                            parsedLocation.optionsName = optsName;
    
                        // Parse display name.
                        std::string dispName; std::getline( locFile, dispName );
                        parsedLocation.displayName = dispName;
    
                        // Parse location text.
                        std::string locText;
                        for( std::string scanLine; ( (scanLine.empty()) ? true : scanLine.back() != '}' ) && std::getline( locFile, scanLine ); )
                        {
                            if( !scanLine.empty() && scanLine.front() == '{' )
                                locText = scanLine;
                            else
                                locText.append( "\n" + scanLine );
                        }
                        if( locText.size() >= 2 )
                        {
                            locText.erase( locText.begin() ); // Remove { at beginning.
                            locText.pop_back(); // Remove } at end.
                        }
                        parsedLocation.locationText = locText;
    
                        // Parse rest.
                        bool endReached = false;
                        for( std::string scanLine; !endReached && std::getline( locFile, scanLine ); )
                        {
                            if( !scanLine.empty() )
                            {
                                switch( scanLine.front() )
                                {
                                case '*': endReached = true; break; // END
                                case '#': // OPTION / DEFAULT OPTION
                                    if( scanLine.at( 1 ) == '"' ) // OPTION
                                    {
                                        scanLine.erase( 0, 2 );
                                        auto optionName = scanLine.substr( 0, scanLine.find( '\"' ) );
                                        parsedLocation.options.insert( optionName );
                                        scanLine.erase( 0, scanLine.find( '\"' ) + 2 );
    
                                        auto optionResultLoc = scanLine.substr( 0, scanLine.find( ';' ) );
                                        parsedLocation.resultLoc[ optionName ] = optionResultLoc;
                                        scanLine.erase( 0, scanLine.find( ';' ) + 1 );
    
                                        if( !scanLine.empty() ) // if the option has conditions...
                                        {
                                            auto condType = scanLine.substr( 0, scanLine.find( ':' ) );
                                            scanLine.erase( 0, scanLine.find( ':' ) + 2 );
                                            if( condType == "loccond" || condType == "itemcond" || condType == "statcond" )
                                                parsedLocation.conds[ optionName ] = scanLine;
                                            else
                                                logError( "Invalid location data syntax in " + filename + "!", "Invalid condition" );
                                        }
                                    }
                                    else if( scanLine.at( 1 ) == '(' ) // DEFAULT OPTION
                                    {
                                        scanLine.erase( 0, 2 );
                                        parsedLocation.defaultOption = scanLine.substr( 0, scanLine.find( ')' ) );
                                    }
                                    else
                                        logError( "Invalid location data syntax in " + filename + "!", "Neither option nor default option specified" );
                                    break;
                                case '+': // ACTION (ITEM / STAT / CONDITION)
                                    scanLine.erase( scanLine.begin() );
                                    auto action = scanLine.substr( 0, scanLine.find( ':' ) );
                                    scanLine.erase( 0, scanLine.find( ':' ) + 2 );
                                    auto value = scanLine;
    
                                    if( action == "item" )
                                        parsedLocation.addItems.push_back( value );
                                    else if( action == "remove_item" )
                                        parsedLocation.removeItems.push_back( value );
                                    else if( action == "weapon" )
                                        parsedLocation.addWeapons.push_back( value );
                                    else if( action == "remove_weapon" )
                                        parsedLocation.removeWeapons.push_back( value );
                                    else if( action == "itemcond" || action == "loccond" )
                                        parsedLocation.addConds.push_back( value );
                                    else if( action == "battle" )
                                    {
                                        auto enemyName = value.substr( 0, value.find( ' ' ) );
                                        value.erase( 0, value.find( ' ' ) + 1 );
                                        auto nEnemies = std::stoi( value.substr( 0, value.find( ',' ) ) );
                                        value.erase( 0, value.find( ',' ) + 1 );
                                        auto level = std::stoi( value );
                                        for( auto i = 0; i < nEnemies; ++i )
                                        {
                                            parsedLocation.battleEnemy.push_back( enemyName );
                                            parsedLocation.battleLevel.push_back( level );
                                        }
                                    }
                                    else if( action == "battlechance" )
                                    {
                                        auto enemyName = value.substr( 0, value.find( ' ' ) );
                                        value.erase( 0, value.find( ' ' ) + 1 );
                                        auto chance = std::stoi( value.substr( 0, value.find( ',' ) ) );
                                        value.erase( 0, value.find( ',' ) + 1 );
                                        auto level = std::stoi( value );
    
                                        parsedLocation.battleChanceEnemy.push_back( enemyName );
                                        parsedLocation.battleChancePercentage.push_back( chance );
                                        parsedLocation.battleChanceLevel.push_back( level );
                                    }
                                    else if( action == "shop" )
                                        parsedLocation.shop = value;
                                    else
                                        parsedLocation.addStats.push_back( action + " " + value ); // Assume it's a stat.
                                    break;
                                }
                            }
                        }
                        ::locationCache[ location ] = parsedLocation;
                        return ::locationCache[ location ];
                    }
                }
            }
            else
                logError( "Unable to open location data file " + filename + "!" );
        }
        static Location dummyLocation;
        return dummyLocation;
    }
    

    【讨论】:

      猜你喜欢
      • 2013-07-08
      • 2016-08-25
      • 2018-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-17
      相关资源
      最近更新 更多