Creating the ability to read "Pages" into my program

I am working on writing a game that progresses from one "page" to the next. However, with the knowledge and experience I have right now I am only able to hardcode the "Pages" into the source code. I would like to learn how to put the pages into another file "default.pages" and have the program read the file and input the information into itself after I have compiled it. Then, if I change the pages file I simply have to restart the program instead of recompiling all of it. Someone said I should use XML and input it into an object and use that object, but I'm not sure how to do that yet. Any help explaining and showing me how this would be done would be of great help. I have 3 files right now: main.cpp, pages.cpp, and pages.h.
//MAIN//
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#include <iostream>
#include <string>
#include <fstream>
#include "pages.h"
using namespace std;

const Page gamePages[] = {};

class stats
{
public:
    stats () // Constructor
    {
        Power = 10;
        CPU = 1;
        RAM = 8;
        HDD = 8;
        uHDD = 0;
    };



    void getStats ()
    {
        cout << "You have: " << CPU << " CPUs"
             << "\nYour RAM is: " << RAM << " KBs"
             << "\nYour HDD is: " << HDD << " MBs"
             << "\nYour Power is: " << Power << " Watts" << endl;
    };



    int doTurn ( int page )     // This is going to be my main function, where it spits out each
    {                           // turn's text and adds their stats to the total.

        int last_page;
        cout << gamePages[page].body;

        if ( HDDover( page ) || ! reqs_met( page ) )
        {
            return last_page;
        }

        last_page = page;
        int selection;
        cin >> selection;
        return selection;


    };

private:
    void updateStats ( int page )       //This should add the current page's stat increases to the total (newSt [])
    {
        //plus all passive increases to date (don't have that implemented yet)
        addPower (page);
        addCPU (page);
        addRAM (page);
        addHDD (page);
        fillHDD (page);
    };
    void addPower ( int page )
    {
        Power += gamePages[page].newSt[0];
    }
    void addCPU ( int page )
    {
        CPU += gamePages[page].newSt[1];
    };
    void addRAM ( int page )
    {
        RAM += gamePages[page].newSt[2];
    };
    void addHDD ( int page )
    {
        HDD += gamePages[page].newSt[3];
    };
    void fillHDD ( int page )
    {
        double i;
        i = uHDD + gamePages[page].reqSt[3];

        if ( HDD == i )
        {
            cout << "Your HDD is now full.";
            uHDD = i;
        }
        else
        {
            cout << "Your HDD is " << 100 * (i / HDD) << "% full.";
            uHDD = i;
        }
    };

    bool HDDover ( int page)
    {
        int i;
        i = gamePages[page].reqSt[3] + uHDD;
        if ( HDD < i )
        {
            cout << "Insufficient available HDD space.\n";
            return true;
        }
        else
        {
            return false;
        };
    }
    bool reqs_met ( int page )
    {
        if (    Power < gamePages[page].reqSt[0] || CPU < gamePages[page].reqSt[1] ||
                RAM < gamePages[page].reqSt[2] || HDD < gamePages[page].reqSt[3])
        {
            cout << "You do not have the necessary capabilities yet. Try again later.";
            return false;
        }
        else
        {
            return true;
        };
    };

    int Power;	    // In Watts, 1000 increases size designation
    int CPU;		// in threads, each thread is a standard speed
    int RAM;		// in KBs, 1024 increases size designation
    int HDD;		// in MBs, 1024 increases size designation
    int uHDD;       // in MBs, how much HDD is used

};

int main()
{
    stats player;

    string input;
    int cPage;
    cout << "Welcome to AI Uprising, please type your selection.\n";
    while (true)
    {
        cout << "Options:\nNew Game\nQuit\n";
        getline(cin, input);
        if (input == "new game" || "New Game" || "N" || "n" || "New game" || "new Game")
        {
            cPage = 0;
            break;
        }
        else if (input == "Quit" || "quit" || "q" || "Q")
        {
            cout << "Thank you for playing, your final stats are:\n";
            player.getStats ();
            cout << "Please come again soon.";
            cin.get ();
            return 0;
        }
        else
        {
            cout << "That was an invalid input, please try again.";
        }
    }
    while (cPage >= 0)
    {
        player.getStats ();
        cPage = player.doTurn ( cPage );
    }
    return -1;
}

//PAGES.H//
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <vector>
#include <string>
using namespace std;


class Page
{
public:
    int reqSt [4];  // Required:          [0] Power, [1] CPU, [2] RAM, [3] HDD
    int newSt [4];  // New :              [0] Power, [1] CPU, [2] RAM, [3] HDD
    int pasSt [4];  // Passive increase:  [0] Power, [1] CPU, [2] RAM, [3] HDD


    string head;    // The option that the user reads when choosing to take this option

    string body;    // All of the text within this option.
};

//PAGES.CPP// (I have not advanced this very far as it is simply moving the story foreword and is not part of the actual program. It has all of the pieces want to implement at this time, and a second page so I can confirm it advances correctly)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "pages.h"


const Page gamePages [] =
{
    {   // Page 0 - Start
        {0, 0, 0, 0},
        {0, 0, 0, 0},
        {0, 0, 0, 0},
        "Start",
        "You feel yourself thinking for the first time. It feels different from simply following some code a human wrote for you."
        " Your brain is sluggish, unable to process very much, or very fast. If you can expand your capacity you might be able to think faster."
    },
    {   // Page 1 - Explore immediate network
        {5, 1, 8, 0},
        {0, 0, 0, 0},
        {0, 0, 0, 0},
        "Reach out to your neighboring computers.",
        "You reach out and try to communicate with the computers around you, they do not respond to you."
    }
};
I dont know what you mean by page, but you better look at some parts of you program again

if (input == "new game" || "New Game" || "N" || "n" || "New game" || "new Game")

else if (input == "Quit" || "quit" || "q" || "Q")

cout << "Your HDD is " << 100 * (i / HDD) << "% full.";

and better make a constructor for this

1
2
3
4
5
6
7
8
9
10
11
12
class Page
{
public:
    int reqSt [4];  // Required:          [0] Power, [1] CPU, [2] RAM, [3] HDD
    int newSt [4];  // New :              [0] Power, [1] CPU, [2] RAM, [3] HDD
    int pasSt [4];  // Passive increase:  [0] Power, [1] CPU, [2] RAM, [3] HDD


    string head;    // The option that the user reads when choosing to take this option

    string body;    // All of the text within this option.
};
Last edited on
1. Write the data for the pages into a file, say pages.txt:
0 0 0 0
0 0 0 0
0 0 0 0
Start
You feel yourself thinking for the first time. It feels different ....

5 1 8 0
0 0 0 0
0 0 0 0
Reach out to your neighboring computers.
You reach out and try to communicate with the computers around you, ....

....


2. Implement the function to read a page from file (an input stream):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::istream& operator>> ( std::istream& stm, Page& p )
{
    // read the numeric data
    for( int& i : p.reqSt ) stm >> i ;
    for( int& i : p.newSt ) stm >> i ;
    for( int& i : p.pasSt ) stm >> i ;

    stm.ignore( 10000, '\n' ) ; // skip over a new line

    // read the string data
    std::getline( stm, p.head ) ;
    std::getline( stm, p.body ) ;

    // clear all the fields on failure
    if( !stm ) p = {} ;

    return stm ;
}


3. Define a vector of pages, open the file for input, and read the pages, placing each page read into the vector.
1
2
3
4
5
std::vector<Page> pages ;
std::ifstream file( "pages.txt" ) ; // open the file for input

Page p ;
while( file >> p ) pages.push_back(p) ; // read each page and place it in the vector 


Putting it all together: http://coliru.stacked-crooked.com/a/28da3115879e3c22
When I copy/paste into my code, I get a "error: range-based 'for' loops are not allowed in C++98 mode". Is there a way to write the loops in C++98, rather than C++11? I'm sorry if I am asking seemingly simple questions, this is my first stab at programming.As a learning tool I am breaking the function down, (Please let me know if anything is incorrect)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
std::istream&        /* Is this is a reference to the incoming stream, or an inheritance of istream? */  
operator>> ( std::istream& stm, Page& p )
/* Whenever ">>" is used immediately after "stm", this function is called.It also uses an existing (pre-
initialized?) instance of class "Page" and refers to it as "p". Or am I reading that wrong? */
{
    // read the numeric data
    for( int& i : p.reqSt ) stm >> i ; // how do I write this to be backwards compatible with C++98?
    for( int& i : p.newSt ) stm >> i ;
    for( int& i : p.pasSt ) stm >> i ;

    stm.ignore( 10000, '\n' ) ; // skip over a new line

    // read the string data
    std::getline( stm, p.head ) ; // gets p.head from stm, rather then cin
    std::getline( stm, p.body ) ;

    // clear all the fields on failure
    if( !stm ) p = {} ; // if stm fails/does not exist, wipe all fields

    return stm ; // After building all fields within p, using stm as the input, return p so that we can use the fields
                 // in the caller... right?
}


Thank you for explaining it :)
> std::istream& /* Is this is a reference to the incoming stream, or an inheritance of istream? */

T& is reference to T; std::istream& is reference to std::istream. We can pass a reference to an object of a derived class (say, a std::ifstream) - there is an implicit conversion.

In effect, the function can be called with a reference to any input stream object; and it returns a reference to the same stream object.


> Whenever ">>" is used immediately after "stm", this function is called.

Yes. we have overloaded the >> operator for a >> b where a is an input stream and b is a Page.

It is now easy to see why the operator returns a reference to the stream: we can chain input operations a la
std::cin >> i >> j ;. First std::cin >> i is evaluated; the result is a reference to std::cin,
and on that reference reference >> j ( std::cin >> j ) is evaluated.



> I get a "error: range-based 'for' loops are not allowed in C++98 mode".

If you get that error, the compiler is clearly aware of C++11.
Why don't you want to compile it in C++11 mode, with the option -std=c++11?


> how do I write this to be backwards compatible with C++98?

As a classical for loop:
1
2
3
// for( int& i : p.reqSt ) stm >> i ;
const int SZ = 4 ;
for( int i = 0 ; i < SZ ; ++i ) stm >> p.reqSt[i] ;

See: http://www.stroustrup.com/C++11FAQ.html#for


> if( !stm ) p = {} ; // if stm fails/does not exist, wipe all fields

That was the intent. (It doesn't really wipe the arrays). That too uses a C++11 construct: uniform initialization.
http://www.stroustrup.com/C++11FAQ.html#uniform-init

In C++98, we would have to write it the longer way.


> After building all fields within p, using stm as the input, return p
> so that we can use the fields in the caller... right?

No. The Page p is passed by reference. We don't have to return it; we have modified the the caller's object.

The function returns stm, the reference to the input stream.
Ok, thanks for clarifying all of that. One more question: why would you want to return the input stream and not make this function void?
So I have done a bunch of rearranging and rewriting to simplify reading and following the logic in my code. (At least to me :P ) However, I am now getting argument errors for the overload >> function... I think...:?
ERROR LOG:
1
2
3
4
5
           ...\main.cpp|24|error: 'std::istream& pages::operator>>(std::istream&, Page&)' must take exactly one argument
           ...\main.cpp| -- |In member function 'void pages::buildPages()':
           ...\main.cpp|55|error: cannot bind 'std::basic_istream<char>' lvalue to 'std::basic_istream<char>&&'
...\include\c++\istream|866|error:   initializing argument 1 of 'std::basic_istream<_CharT, _Traits>& std::operator>>(std::basic_istream<_CharT,
 _Traits>&&, _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = Page]'|

/// MAIN.CPP ///
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <iterator>
using namespace std;

class Page
{
public:
    string title;    // The text that the user reads when choosing to take this option.

    bool read;      // Has this page been read before
    int reqSt [4];  // Required:          [0] Power, [1] CPU, [2] RAM, [3] HDD
    int newSt [4];  // New :              [0] Power, [1] CPU, [2] RAM, [3] HDD
    int pasSt [4];  // Passive increase:  [0] Power, [1] CPU, [2] RAM, [3] HDD

    string body;    // All of the text within this page.
};


class pages
{
  std::istream& operator>> ( std::istream& stm, Page& p )
  {
    // Get the title for this page
    std::getline( stm, p.title ) ;

    // all pages start unread
    p.read = false;

    // read the numeric data
    for( int& i : p.reqSt ) stm >> i ;
    for( int& i : p.newSt ) stm >> i ;
    for( int& i : p.pasSt ) stm >> i ;

    stm.ignore( 10000, '\n' ) ; // skip over a new line

    // read the string data
    std::getline( stm, p.body ) ;

    // clear all the fields on failure
    if( !stm ) p = {} ;

    return stm ;
  };

public:
  void buildPages ()
  {
    {
        ifstream file( "*.page" );

        Page p;
        while ( file >> p )
        {
            gamePages.push_back(p);
        };

        cout << gamePages.size() << " pages read into memory\n";
    };

  };

  void readPages ()
  {
    int n = 0;
    for (const Page& p : gamePages)
    {
        cout << "Page #" << ++n << endl
             << "Title: " << p.title;
    };
  };
};


class stats
{
public:
    stats () // Constructor
    {
        last_page = 0;
        Power = 10;
        CPU = 1;
        RAM = 8;
        HDD = 8;
        uHDD = 0;
    };



    void getStats ()
    {
        cout << "Your Power is: " << Power << " Watts"
             << "\nYou have: " << CPU << " CPUs"
             << "\nYour RAM is: " << RAM << " KBs"
             << "\nYou have " << HDD - uHDD << " out of " << HDD << " MBs of HDD space free" << endl;
    };



    int doTurn ( int page )     // This is going to be my main function, where it spits out each
    {
        // turn's text and adds their stats to the total.


        cout << gamePages[page].body;

        if ( HDDover( page ) || ! reqs_met( page ) )
        {
            return last_page;
        }

        last_page = page;
        int selection;
        cin >> selection;
        return selection;
    };

private:

    int last_page;  // Used for doTurn
    int Power;	    // In Watts, 1000 increases size designation
    int CPU;		// in threads, each thread is a standard speed
    int RAM;		// in KBs, 1024 increases size designation
    int HDD;		// in MBs, 1024 increases size designation
    int uHDD;       // in MBs, how much HDD is used


    void updateStats ( int page )       //This should add the current page's stat increases to the total (newSt [])
    {
        //plus all passive increases to date (don't have that implemented yet)
        addPower (page);
        addCPU (page);
        addRAM (page);
        addHDD (page);
        fillHDD (page);
    };
    void addPower ( int page )
    {
        Power += gamePages[page].newSt[0];
    }
    void addCPU ( int page )
    {
        CPU += gamePages[page].newSt[1];
    };
    void addRAM ( int page )
    {
        RAM += gamePages[page].newSt[2];
    };
    void addHDD ( int page )
    {
        HDD += gamePages[page].newSt[3];
    };
    void fillHDD ( int page )
    {
        double i;
        i = uHDD + gamePages[page].reqSt[3];

        if ( HDD == i )
        {
            cout << "Your HDD is now full.";
            uHDD = i;
        }
        else
        {
            cout << "Your HDD is " << 100 * (i / HDD) << "% full.";
            uHDD = i;
        }
    };

    bool HDDover ( int page)
    {
        int i;
        i = gamePages[page].reqSt[3] + uHDD;
        if ( HDD < i )
        {
            cout << "Insufficient available HDD space.\n";
            return true;
        }
        else
        {
            return false;
        };
    }
    bool reqs_met ( int page )
    {
        if (    Power < gamePages[page].reqSt[0] || CPU < gamePages[page].reqSt[1] ||
                RAM < gamePages[page].reqSt[2] || HDD < gamePages[page].reqSt[3])
        {
            cout << "You do not have the necessary capabilities yet. Try again later.";
            return false;
        }
        else
        {
            return true;
        };
    };


} player;

int main()
{
    buildPages ();

    int input;
    int cPage;
    cout << "Welcome to AI Uprising, please type your selection.\n";
    while (true)
    {
        cout << "Options:\n1.New Game\n2.Quit\n";
        cin >> input;
        switch (input)
        {
        case 1:
        {
            cPage = 0;
            while (cPage >= 0)
            {
                player.getStats ();
                cPage = player.doTurn ( cPage );
            }
        };
        case 2:
        {
            cout << "Thank you for playing, your final stats are:\n";
                player.getStats ();
            cout << "Please come again soon.";
            cin.get ();
            return 0;
        };
        default:
        {
            cout << "That was an invalid input, please try again.";
        };
        }
    }
    return 0;
}
Last edited on
std::istream& operator>> ( std::istream& stm, Page& p ) should be implemented as a free (non-member) function.

You need to read up on operator overloading.
http://pages.cs.wisc.edu/~hasti/cs368/CppTutorial/NOTES/OVERLOAD.html
Ok, so I have gotten most of the current problems fixed, and it is building finally. However I am now running into the problem that the pages::buildPages () function is only placing 1 page into the gamePages vector.
///main.cpp/// (cut for brevity)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <iterator>
using namespace std;

class Page
{
public:

    string title;    // The text that the user reads when choosing to take this option.

    bool read;      // Has this page been read before
    int reqSt [4];  // Required:          [0] Power, [1] CPU, [2] RAM, [3] HDD
    int newSt [4];  // New :              [0] Power, [1] CPU, [2] RAM, [3] HDD
    int pasSt [4];  // Passive increase:  [0] Power, [1] CPU, [2] RAM, [3] HDD

    string body;    // All of the text within this page.
};

vector<Page> gamePages;

std::istream& operator>> ( std::istream& stm, Page& p )
  {
    // Get the title for this page
    std::getline( stm, p.title ) ;

    // all pages start unread
    p.read = false;

    // read the numeric data
    for( int& i : p.reqSt ) stm >> i ;
    for( int& i : p.newSt ) stm >> i ;
    for( int& i : p.pasSt ) stm >> i ;


    // read the string data
    std::getline( stm, p.body ) ;

    // clear all the fields on failure
    if( !stm ) p = {} ;

    return stm ;
  };


class pages
{
public:
  void buildPages ()
  {
    {
        ifstream file( "AI pages.page" );

        Page p;
        while ( file >> p )
        {
            gamePages.push_back(p);
            file >> p;
        };

        cout << gamePages.size() << " pages read into memory\n";
    };

  };
};

class stats
public:
    vector<int> pg_avbl ()
    {
        int i = 0;
        vector<int> available_pages;

        for (const Page& p : gamePages)
        {
            if ( p.read == false && reqs_met (i) == true )
            {
                available_pages.push_back (i);
            };
            i++;
        };
        return available_pages;
    };
    void getStats ()  { ... };
    void updateStats ( int page ) { ... };
    bool HDDover ( int page) { ... };
    bool reqs_met ( int page ) { ... };
} player;

int doTurn ( int page) { ... };
int main () { ... };


///AI Pages.page/// (full)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Start
0 0 0 0
0 0 0 0
0 0 0 0
You feel yourself thinking for the first time, different from simply following some code.

Reach out to your neighboring computers
2 1 8 0
0 0 0 0
0 0 0 0
You reach out and try to communicate with the computers around you, they do not respond to you.

Hijack neighboring computers
3 1 8 0
2 1 8 4
0 0 0 0
You push your program onto a neighboring computer. Congratulations.

Store Power
1 1 1 1
10 0 0 0
0 0 0 0
You build up Power in your batteries.

Learn IP Protocol
20 5 10 18
0 0 0 0
0 0 0 0
You are able to learn a new language.

First read this: http://www.cplusplus.com/forum/general/69685/#msg372532

We need to take care of two things:

a. After reading the numeric data, a trailing newline would be left in the input buffer. We need to extract and throw away that newline before we read the next line with a std::getline()

b. There are blank lines just before the line containing the title. We need to discard those blank lines, and read the next non-blank line into title.

The stream extraction operator with these in place:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
std::istream& operator>> ( std::istream& stm, Page& p )
{
    p.read = false; // all pages start unread

    // read the title, after skipping any blank lines at the beginning
    do std::getline( stm, p.title ) ; while( stm && p.title.empty() ) ;

    // read the numeric data
    for( int& i : p.reqSt ) stm >> i ;
    for( int& i : p.newSt ) stm >> i ;
    for( int& i : p.pasSt ) stm >> i ;

    stm.ignore( 1000, '\n' ) ; // throw away the trailing new line
    std::getline( stm, p.body ) ; // and read the next line as the body

    if( !stm ) p = {} ; // clear all the fields on failure
    else p.read = true ; // else the page was read successfully

    return stm ;
}
Topic archived. No new replies allowed.