C ++文本文件阅读和解析基于文本的冒险游戏

时间:2015-02-02 10:35:43

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

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

[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 如果玩家输入“等待”,他们将   被带到名为[castleStartWait]的“位置”。

     

示例:#“look bottle”castleStartLookBottle itemcond:hasBottle“0”   如果玩家输入“外观瓶子”,他们将被带到该位置   命名为[castleStartLookBottle],只要他们符合“项目   要求“他们还没有瓶子。”

     

示例:#“go east”castleDark loccond:hasBeenCastleDark“0”如果   玩家类型“go east”,他们将被带到名为的位置   [castleDark]只要符合他们的“位置要求”即可   还没去过那里。

     

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

     

示例: + strength:5 当玩家进入该位置时,应该为玩家的“力量”属性添加5点,并打印一些硬编码消息,例如“你已经获得了5个力量点!”

现在,这是问题:如何编写读取和解析特定位置数据的函数并将它们存储在特定的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.

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

2 个答案:

答案 0 :(得分:0)

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

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

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

答案 1 :(得分:0)

对于碰巧遇到这个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;
}