[C++] Scoreboard, Timer, and Graphics

denmax

You can change this now in User CP.
Reaction score
155
Currently, we've been told to create a project where we're supposed to create a game. I'm having trouble with knowing how to work my way with introducing the scoreboard, timer, and graphics, mainly because our professors haven't taught us how to do so. (we've only reached functions - with arrays as our next subject - and we're about a week or two away from the deadline).

Coding for the game in question is not hard, but we are required to implement the those three features in the game.

First off, the scoreboard needs to be available at multiple sessions, so I'm assuming this has something to do with reading and writing on another file outside the program. First off, I do not know how manipulate files outside the program. Second, I do not know how to sort it, or if there is such a command that would easily do so for me.

The timer is simple, it counts down from x minutes (e.g. 3) to zero. I'm assuming there's a library for that, but again, it is not taught to us.

Last but definitely not the least is implementing graphics. This is probably the most difficult and the hardest to explain. The game is a simple archer game. A man hurls an arrow to a target. The game asks for an input. If the input is spot on, then the arrow travels dead center. If it's too high, it goes upward, too low, and it goes downward (et cetera). It doesn't have to be fancy, just simple enough that it shows the player something.

I've done my research, but all of them are too vague (telling me to go to a tutorial site, etc.), or I can't really analyze them well mainly because of lack of knowledge to C++ programming in itself.

What I've found, and I'm not even sure, is that I have to create a structure for the variable used on the scoreboard, and to use the library "ctimer" for the timer. I'm still doing more research, but I'm hoping to be more interactive when it comes to learning so that I can be sure that all my questions may be answered.

Thank you in advance.
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
You are right, you do need to read & write files for the scoreboard; look up fstream.

As for your graphics; are you using SDL, SFML or OpenGL?
If you're free to choose, go with SFML; seeing as its interface is C++ and you'll be more familiar with it. All three libraries support a timer that you can use for your game.

Assuming your scoreboard only stores the names and scores..
(Been a long, long, long time since I last used C++.. I'm sorry, s3rius >< )

The following should work fine..
The main.cpp shows how one would use the class Scoreboard.
(I did this because I hadn't touched C++ in ages and wanted to try my hand at it =x)

Code:
//Scoreboard.h
#include <string>
#include <utility>
#include <vector>
 
class Scoreboard {
    public:
        struct Entry {
            char name[256];
            int  score;
 
            void setName (std::string str);
        };
        typedef std::vector<Entry> Entries;
        typedef std::pair<Entries::iterator, int> EntryLocation;
        Scoreboard ();
       
        bool load (char const* data_file);
        bool save (char const* data_file) const;
 
        int  addEntry (Entry const& entry);
        Entry getEntry (int index) const;
       
        void prune (int max_amt);
        int  size  () const;
    private:
        //Not copy-constructable
        Scoreboard (Scoreboard const&);
 
        Entries entries;
        static int const SizeOfEntry = sizeof(Entry);
 
        EntryLocation entryWorseThan (Entry const& entry);
};

Code:
//Scoreboard.cpp
#include "Scoreboard.h"
#include <fstream>
#include <algorithm>
using namespace std;
 
Scoreboard::Scoreboard () {}
 
bool Scoreboard::load (char const* data_file) {
    ifstream f;
    f.open(data_file, ios::binary);
    if (!f.is_open()) { return false; }
 
    Entry entry_buffer;
    f.read(reinterpret_cast<char*>(&entry_buffer), SizeOfEntry);
    entries.clear();
 
    while (!f.eof()) {
        entries.push_back(entry_buffer);
        f.read(reinterpret_cast<char*>(&entry_buffer), SizeOfEntry);
    }
    f.close();
    return true;
}
 
bool Scoreboard::save (char const* data_file) const {
    ofstream f;
    f.open(data_file, ios::binary | ios::out);
    if (!f.is_open()) { return false; }
 
    f.write(reinterpret_cast<char const*>(&entries[0]), SizeOfEntry * entries.size());
    f.close();
    return true;
}
 
int Scoreboard::addEntry (Entry const& entry) {
    auto loc = entryWorseThan(entry);
    entries.insert(loc.first, entry);
    return loc.second;
}
 
Scoreboard::Entry Scoreboard::getEntry (int index) const {
    return entries[index];
}
 
void Scoreboard::prune (int max_amt) {
    entries.resize(max_amt);
}
 
int Scoreboard::size () const { return entries.size(); }
 
Scoreboard::EntryLocation Scoreboard::entryWorseThan (Entry const& entry) {
    int i = 0;
    for (auto it=entries.begin(); it!=entries.end(); ++it, ++i) {
        if (it->score <= entry.score) {
            return EntryLocation(it, i);
        }
    }
    return EntryLocation(entries.end(), i);
}
 
void Scoreboard::Entry::setName (std::string str) {
    memcpy(&name[0], &str.c_str()[0], sizeof(char) * (str.length()+1));
}

Code:
//main.cpp
#include "Scoreboard.h"
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
 
string getInput () {
    static char input_char[256];
    cin.getline(&input_char[0], 256, '\n');
    return string(input_char);
}
int str2int (string str) {
    istringstream ss(str);
    int result = 0;
    ss >> result;
    return result;
}
int main () {
    Scoreboard s;
    string input;
   
    while (input != "exit") {
        cout << "Command: ";
        input = getInput();
 
        if (input == "load") {
            cout << "Data File: ";
            input = getInput();
            s.load(input.c_str());
        } else if (input == "save") {
            cout << "Data File: ";
            input = getInput();
            s.save(input.c_str());
        } else if (input == "add") {
            Scoreboard::Entry entry;
            cout << "Name: ";
            entry.setName(getInput());
            cout << "Score: ";
            entry.score = str2int(getInput());
            int pos = s.addEntry(entry);
            cout << "Entry was added at position #" << pos << "\n";
        } else if (input == "scores") {
            for (int i=0; i<s.size(); ++i) {
                Scoreboard::Entry e = s.getEntry(i);
                cout << e.name << "\t-\t" << e.score << endl;
            }
        } else if (input == "prune") {
            int max = str2int(getInput());
            s.prune(max);
        } else if (input == "size") {
            cout << "Size = " << s.size() << endl;
        }
    }
    return 0;
}
 

denmax

You can change this now in User CP.
Reaction score
155
Is scoreboard supposed to be a variable in the main.cpp? And s as it's name? Or am I reading this wrong?

And how many entries does this scoreboard limit itself to? I can't really test it right now since I'm in school, but I would like to know more about it.

As for graphics, we are free to choose which ones we desire. I'll research more on what you call SFML.
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
Yes, s is the name of a Scoreboard object inside main().

The scoreboard is limited to (usually) 4,294,967,295 entries. That should be enough :)
That's because he's using a vector. A vector is an array which automatically resized itself when it needs to. And it can do so up to a very high limit of 4-something billion/milliards.
Code:
typedef std::vector<Entry> Entries; //He defines a vector typdef called Entries
...
Entries entries; //And uses it here
SFML is also very useful because it's pretty easy to set up and the easiest one to work with. SDL or pure OpenGL require you to learn even more.

Last but not least Camel's example is already quite elaborate C++ code. He's using a lot of things you don't have to know about to make a scoreboard yourself (typedef's, static keyword, vector iterators, std:: pair, const correctness (at least sometimes), sizeof).
So don't worry too much if you're not understanding the code to the last letter.

I suggest you first get familiar with the following things (listed in order of importance):
C++ classes - if you haven't covered this already. It's more or less a prerequisite for working with SFML and writing good code.
std::string - a string class that makes your life a lot easier.
std::vector and here - an array that makes you life a lot easier.
std::fstream - used to read to / write from files in a somewhat easy way.
SFML (as you said already)

This is most of the knowledge you need to be able to do the job. Afterwards Camel's example should make a lot more sense too.

All of these things are thoroughly documented across the internet, so explaining them here again would be kinda like reinventing the wheel. But some of those tutorials are a bit overzealous.
So if you have any problems with one of these things, or others, feel free to ask :)

Been a long, long, long time since I last used C++.. I'm sorry, s3rius ><
I am disappoint, son.
 

denmax

You can change this now in User CP.
Reaction score
155
How can I make it that the leaderboard only outputs the first ten scores (not inputs) in ascending order, rather than the 4 billion ones? Rather, does the scoreboard in the code automatically sort this thing out?

And I'm not sure if I'm wrong or not, but I'm guessing you don't need to make more files of the same copy for multiple scoreboards in a single game (I'm making scoreboards appear per level, rather than making it appear at the end of the whole game), but rather just implement multiple variables of type Scoreboard?

I swear to the norse gods, in all my experience of coding and creating triggers in WC3 (not JASS though, so that might explain it), this is harder by tenfold.
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
Umm..
How can I make it that the leaderboard only outputs the first ten scores (not inputs) ..., rather than the 4 billion ones?
To limit the amount of entries to 10, just call the prune() method.

in ascending order
Internally, it sorts by descending order; the first position (index zero) has the highest score.
You could just access, call getEntry(), the entries backwards to get it in ascending order.

And I'm not sure if I'm wrong or not, but I'm guessing you don't need to make more files of the same copy for multiple scoreboards in a single game (I'm making scoreboards appear per level, rather than making it appear at the end of the whole game), but rather just implement multiple variables of type Scoreboard?
In my implementation, you can have as many scoreboards as you want or only one.
For example, say you have five levels. You can store your scores in five separate files and access them all with one instance of Scoreboard.

Scoreboard s;
s.load("level_one_scores");
//Do stuff with level_one_scores
s.save("level_one_scores");
s.load("level_five_scores");
//Do stuff with level_five_scores
s.save("level_five_scores");

However, you really should try to implement the scoreboard yourself =x
I just did it as an exercise..
Just think of the functionality your scoreboard requires.
01) Add scores
02) Sort scores in ascending order
03) Save scores to disk
04) Read scores from disk
05) Limit to 10 entries
06) Get score entries' info.

Then work your way from there.
 

denmax

You can change this now in User CP.
Reaction score
155
While doing research for easier ways of conducting a leaderboard (to something I can understand more clearly - I think my teacher will suspect how I know all this code in only 2 months [even less] of this), I found one that is quite understandable for me.

Obviously this will be changed drastically (scores will not be a user input, date will auto-detect with the current time on the PC clock - if possible), but this looked easier to understand for me. The only problem with this code is sorting it based on the score. This code doesn't sort anything at all.

If all else fails, I'll have to resort to camelCase's code, but I want it to be simple for me and my group to understand, rather than simply just copy+pasting something out of nowhere. I'm not saying camelCase's code is bad, just that it looks total gibberish when I read it compared to this. It's my first term in C++, and computers aren't even part of my program (mech. eng.)

EDIT: wait, gotta edit the code w/ spaces.

JASS:
JASS:
<s>#include&lt;iostream&gt;</s>
<s>#include&lt;fstream&gt;</s>
<s>#include&lt;string&gt;</s>
<s>#include&lt;cstdlib&gt;</s>
<s>using namespace std;</s>
 
<s>struct player {</s>
<s>string name;</s>
<s>int score, dD, dM, dY;</s>
<s>};</s>
 
<s>player playerList[20];</s>
<s>int playerNumber=0;</s>
 
<s>void addPlayer(player user[], int size, int const track);</s>
<s>void showScores(player user[], int size, int const track);</s>
 
<s>string getString(string prompt) {</s>
<s>string s;</s>
<s>cout &lt;&lt; prompt;</s>
<s>getline(cin, s);</s>
<s>return s;</s>
<s>}</s>
 
<s>int menu(int &amp;menuChoice)</s>
<s>{</s>
<s>do {</s>
<s>cout &lt;&lt; endl &lt;&lt; &quot;#--- HIGH-SCORE MENU ---#&quot; &lt;&lt; endl;</s>
<s>cout &lt;&lt; endl &lt;&lt; &quot;1) Add a High-Score entry&quot;;</s>
<s>cout &lt;&lt; endl &lt;&lt; &quot;2) Display the High Score table&quot;;</s>
<s>cout &lt;&lt; endl &lt;&lt; &quot;3) Delete current High Scores&quot;;</s>
<s>cout &lt;&lt; endl &lt;&lt; &quot;4) Quit the program&quot; &lt;&lt; endl;</s>
<s>cout &lt;&lt; endl &lt;&lt; &quot;Please enter your choice (1-5): &quot;;</s>
<s>cin &gt;&gt; menuChoice;</s>
<s>cout &lt;&lt; endl;</s>
<s>if( menuChoice &gt; 4 || menuChoice &lt; 1) {</s>
<s>cout &lt;&lt; &quot;Please select an existing option!&quot; &lt;&lt; endl;</s>
<s>}</s>
<s>} while( menuChoice &gt; 4 || menuChoice &lt; 1);</s>
<s>return menuChoice;</s>
<s>}</s>
 
<s>int main()</s>
<s>{</s>
<s>int menuChoice;</s>
<s>int static track = 0;</s>
<s>player user[50];</s>
<s>do {</s>
<s>menu(menuChoice);</s>
<s>switch(menuChoice)</s>
<s>{</s>
<s>case 1:</s>
<s>addPlayer(user, 20, track);</s>
<s>track++;</s>
<s>break;</s>
<s>case 2:</s>
<s>showScores(user, 20, track);</s>
<s>break;</s>
<s>case 3:</s>
<s>ofstream SaveFile(&quot;playerInfo.txt&quot;, ios::trunc);</s>
<s>SaveFile.close();</s>
<s>cout &lt;&lt; &quot;High Scores successfully deleted&quot; &lt;&lt; endl;</s>
<s>break;</s>
<s>}</s>
<s>} while(menuChoice != 4);</s>
<s>}</s>
 
<s>void addPlayer(player user[], int size, int const track)</s>
<s>{</s>
<s>player p;</s>
<s>cout &lt;&lt; &quot;Enter your nickname: &quot;;</s>
<s>cin &gt;&gt; user[track].name;</s>
<s>cout &lt;&lt; &quot;Enter your score: &quot;;</s>
<s>cin &gt;&gt; user[track].score;</s>
 
<s>do {</s>
<s>cout &lt;&lt; &quot;Enter date (dd mm yy): &quot;;</s>
<s>cin &gt;&gt; user[track].dD &gt;&gt; user[track].dM &gt;&gt; user[track].dY;</s>
<s>if(user[track].dD &lt; 1) {</s>
<s>cout &lt;&lt; &quot;Please select a correct date (day is invalid)!&quot; &lt;&lt; endl;</s>
<s>}</s>
<s>if(user[track].dM &gt; 12 || user[track].dM &lt; 1) {</s>
<s>cout &lt;&lt; &quot;Please select a correct date (month is invalid)!&quot; &lt;&lt; endl;</s>
<s>}</s>
<s>if(user[track].dY &gt; 2100 || user[track].dY &lt; 1900) {</s>
<s>cout &lt;&lt; &quot;Please select a correct date (year is unrealistic)!&quot; &lt;&lt; endl;</s>
<s>}</s>
<s>if(user[track].dM == 1) { if(user[track].dD &gt; 31) { cout &lt;&lt; &quot;Only 31 days in January!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 2) { if(user[track].dD &gt; 29) { cout &lt;&lt; &quot;Only 29 days in February!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 3) { if(user[track].dD &gt; 31) { cout &lt;&lt; &quot;Only 31 days in March!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 4) { if(user[track].dD &gt; 30) { cout &lt;&lt; &quot;Only 30 days in April!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 5) { if(user[track].dD &gt; 31) { cout &lt;&lt; &quot;Only 31 days in May!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 6) { if(user[track].dD &gt; 30) { cout &lt;&lt; &quot;Only 30 days in June!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 7) { if(user[track].dD &gt; 31) { cout &lt;&lt; &quot;Only 31 days in July!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 8) { if(user[track].dD &gt; 31) { cout &lt;&lt; &quot;Only 31 days in August!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 9) { if(user[track].dD &gt; 30) { cout &lt;&lt; &quot;Only 30 days in September!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 10) { if(user[track].dD &gt; 31) { cout &lt;&lt; &quot;Only 31 days in October!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 11) { if(user[track].dD &gt; 30) { cout &lt;&lt; &quot;Only 30 days in November!&quot; &lt;&lt; endl;} }</s>
<s>if(user[track].dM == 12) { if(user[track].dD &gt; 31) { cout &lt;&lt; &quot;Only 31 days in December!&quot; &lt;&lt; endl;} }</s>
<s>} while(user[track].dM == 1, 3, 5, 7, 8, 10, 12 &amp;&amp; user[track].dD &gt; 31 ||</s>
<s>user[track].dM == 2 &amp;&amp; user[track].dD &gt; 29 ||</s>
<s>user[track].dM == 4, 6, 9, 11 &amp;&amp; user[track].dD &gt; 30 ||</s>
<s>user[track].dD &lt; 1 ||</s>
<s>user[track].dM &gt; 12 ||</s>
<s>user[track].dM &lt; 1 ||</s>
<s>user[track].dY &gt; 2100 ||</s>
<s>user[track].dY &lt; 1900);</s>
<s>cout &lt;&lt; endl &lt;&lt; &quot;Thanks for your entry!&quot; &lt;&lt; endl;</s>
<s>ofstream playerInfo(&quot;playerInfo.txt&quot;, ios::app);</s>
<s>playerInfo &lt;&lt; &quot;#---------------&#039;&quot; &lt;&lt; &quot;#&quot; &lt;&lt; &quot;&#039;--------------#&quot; &lt;&lt; endl</s>
<s>&lt;&lt; &quot;#--------Nickname: &quot; &lt;&lt; user[track].name &lt;&lt; endl</s>
<s>&lt;&lt; &quot;#--------Score Achieved: &quot; &lt;&lt; user[track].score &lt;&lt; endl</s>
<s>&lt;&lt; &quot;#--------Date Achieved: &quot; &lt;&lt; user[track].dD &lt;&lt; &quot;/&quot; &lt;&lt; user[track].dM &lt;&lt; &quot;/&quot; &lt;&lt; user[track].dY &lt;&lt; endl</s>
<s>&lt;&lt; &quot;#--------------------------------#&quot; &lt;&lt; endl;</s>
<s>playerInfo.close();</s>
<s>}</s>
 
<s>void showScores(player user[], int size, int const track)</s>
<s>{</s>
<s>int count;</s>
<s>string line;</s>
<s>cout &lt;&lt; endl</s>
<s>&lt;&lt; &quot;#--------------------------------#&quot; &lt;&lt; endl</s>
<s>&lt;&lt; &quot;#-----******HIGHSCORES******-----#&quot; &lt;&lt; endl</s>
<s>&lt;&lt; &quot;#--------------------------------#&quot; &lt;&lt; endl;</s>
<s>ifstream playerInfo(&quot;playerInfo.txt&quot;);</s>
<s>if (playerInfo.is_open())</s>
<s>{</s>
<s>while (! playerInfo.eof() )</s>
<s>{</s>
<s>getline (playerInfo,line);</s>
<s>cout &lt;&lt; line &lt;&lt; endl;</s>
<s>}</s>
<s>}</s>
<s>else cout &lt;&lt; &quot;Unable to open file&quot;;</s>
<s>playerInfo.close();</s>
<s>return;</s>
<s>}</s>



EDIT2:

After a long, hard, while, I'm starting to understand Camel's code a bit more. I might just stop with what I discovered right now and just go on with Camel's.. A bit more questions.

Do I need to create a new file for the scoreboard.h and .cpp, or can I just place it in the main.cpp and make scoreboard.h a single class and scoreboard.cpp a function?

As for the prune(), do I set a value to max_amt, or should I just outright change it to a constant (since it'll be a fixed value anyway)? If I have to set a value on max_amt, where will I be setting(?) the value of the variable? In the library/class or the function?
 

camelCase

The Case of the Mysterious Camel.
Reaction score
362
Eh..
Write yer' own!

Do I need to create a new file for the scoreboard.h and .cpp, or can I just place it in the main.cpp and make scoreboard.h a single class and scoreboard.cpp a function?
Not sure what you're asking here but..
You need two files here to use it:
01) Scoreboard.h
02) Scoreboard.cpp

main.cpp was just to show you how to use it; you don't actually want the code from main.cpp
Copy and paste the code to their respective files and save.

As for the prune(), do I set a value to max_amt, or should I just outright change it to a constant (since it'll be a fixed value anyway)? If I have to set a value on max_amt, where will I be setting(?) the value of the variable? In the library/class or the function?
I made it so that you have to pass a value to the prune() method to make it generic; different people might want a different amount of entries for their scoreboards.

But, hey, the good thing about code is that you can change it to your liking as long as you have the source. (And you do.)

You can change it to a constant if you so wish or even change the prune method's declaration to this:
void prune (int max_amt=10);

Or do this:
static const int DefaultPruneSize = 10;
void prune (int max_amt=DefaultPruneSize);
I'm not sure if this will work, haven't touched C++ in a while.. But it should work, the variable's value is known at compile time..

But, really, try to write yer' own score board!
You learn more this way =/
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
It's very hard to simply explain what you have to do, nor does it really help to give you some code you don't understand.

So let's try to walk through the basic features of a scoreboard and construct a simply implementation step-by-step.

Say we want to show players like this:
1: s3rius 9001
2: denmax 15
3: random 9
...

So each entry has: it's position, the player's name and the point value.
We will store each entry in an array, so the entry's position inside the array will take care of the first requirement.
So we only need to store the name and points:

Code:
struct Entry{
    std::string name;
    int points;
}

Now let's start small. We just want to create an array of Entries which we will later make into our scoreboard:

Code:
struct Entry{
    std::string name;
    int points;
};
 
int main(){
    //We construct 3 different entries with our data.
    //The curly brackets ({}) are a way of initializing the struct.
    //It's just a convenient way of writing:
    //Entry a;
    //a.name = "s3rius";
    //a.points = 9001;
 
    Entry a = { "s3rius", 9001 };
    Entry b = { "denmax", 15 };
    Entry c = { "random", 9 };
 
    //Now we'll need an array to store them in.
    //In C++ we can almost always use a vector.
 
    //This creates a vector filled with Entry objects.
    std::vector<Entry> scoreboard;
 
    //Now we add our Entries to it
    scoreboard.push_back(a);
    scoreboard.push_back(b);
    scoreboard.push_back(c);
 
    return 0;
}

This is a basic start.
The vector now contains 3 elements which can be accessed using the subscript operator:
Code:
//Gets the topmost entry in our vector
Entry first = scoreboard[0];

The vector's data is:

scoreboard[0] => "s3rius", 9001
scoreboard[1] => "denmax", 15
scoreboard[2] => "random", 9

Keep in mind they're only in descending order because we added them in such order. The vector itself doesn't sort it's elements.

Now we want to display the scoreboard content via std::cout.

Code:
int main(){
...
    std::vector<Entry> scoreboard;
...
 
    //We make a for-loop from 0 to (number of elements in scoreboard)
    for(int i=0; i < scoreboard.size(); ++i){
        std::cout << (i+1) << ":\t" << scoreboard[i].player << "\t" << scoreboard[i].points << "\n";
    }
 
    return 0;
}

That's all.
scoreboard.size() will return the number of elements in the vector. Since we added 3 Entries it'll return 3.
So the loop will run 3 times (from 0 to 2) and for each time we output the position (i+1), the player name and his points.

The string "\t" means tabulator. So it's basically like pressing TAB in your notepad. It'll just add some whitespaces to the text.
"\n" means newline. So it'll be the same as pressing Return.

So now we should have the desired output:
1: s3rius 9001
2: denmax 15
3: random 9

Next we will put our vector and functionality into a class, for better organization:

Code:
struct Entry{
    std::string name;
    int points;
};
 
class Scoreboard{
 
public:
    //We make a class-function that takes a player name and points and add it as an Entry to our scoreboard vector.
    void AddPlayer(std::string playerName, int points){
        Entry e = {playerName, points}; //Same code as before
        m_scoreboard.push_back(e);
    }
 
    void Print(){
        for(int i=0; i < m_scoreboard.size(); ++i){
            std::cout << (i+1) << ":\t" << m_scoreboard[i].player << "\t" << m_scoreboard[i].points << "\n";
        }
    }
 
private:
//I prefix all class variables with m_ but that's just my habit.
    std::vector<Entry> m_scoreboard;
};
 
int main(){
    Scoreboard sb;
 
    //Again, we add all players to the scoreboard.
    sb.AddPlayer("s3rius", 9001);
    sb.AddPlayer("denmax", 15);
    sb.AddPlayer("random", 9);
 
    //And print it:
    sb.Print();
 
    return 0;
}

You can put the Entry and Scoreboard classes into a "Scoreboard.h" file and include that one in main.cpp if you want to.
That's how you organize classes in larger programs (like Camel said). But for a small program it's not really necessary.

Next we want some more functionality.
How about removing a player again?
The code for that is very small, but a bit tricky:

Code:
for(std::vector<Entry>::iterator i = scoreboard.begin(); i != scoreboard.end(); ++i){
    if( i->name == thePlayerWeWantToRemove ){
        scoreboard.erase(i);
        break;
    }
}

Vectors, and all the other containers in the standard library (maps, lists, arrays, etc) use something called iterator.
Think of an iterator like a bookmark in book.
Your book is the vector, each page is one of the vector's elements.
You can put a bookmark in front of any page so you can remember it next time you open the book.

The iterator is similar. It's a small object that points at an element of the vector. Everytime you "increase" the iterator (++i) it will point to the next element.

Because of C++'s template library the iterator name is very complicated:
std::vector<Entry>::iterator
Basically only means it's an iterator that belongs to a vector which contains elements of type Entry.

So if we have our old code from before:
Code:
int main(){
    Entry a = { "s3rius", 9001 };
    Entry b = { "denmax", 15 };
    Entry c = { "random", 9 };
 
    std::vector<Entry> scoreboard;
 
    scoreboard.push_back(a);
    scoreboard.push_back(b);
    scoreboard.push_back(c);
 
    //We can now create an iterator:
    //scoreboard.begin() makes the iterator point to the first element in the vector.
    std::vector<Entry>::iterator i = scoreboard.begin();
    //Right now i points at Entry a.
 
    ++i;
    //After this increment i will now point at the next element in the vector, which is b.
 
    //We can access the element an iterator points to by using the arrow-operator.
    //This prints out the name-variable of Entry b.
    std::cout <<  i->name;
 
    //So the above line is equivalent to:
    std::cout << scoreboard[1].name;
 
    return 0;
}

Ok, so why do you need to know about iterators? It's just another way of.. well.. iterating over a vector. We've done that before in the Print() function.
Well, there are good reasons why the classes like vector use iterators but I'll not indulge into that now.

But we wanted to remove an element from the vector. That was our intention. That's not so easy, however. Let's say we want to remove the first element:

scoreboard[0] => "s3rius", 9001
scoreboard[1] => "denmax", 15 <== this one will be taken out
scoreboard[2] => "random", 9

If we simply remove the data we would end up with:

scoreboard[0] => "s3rius", 9001
scoreboard[1] => (nothing)
scoreboard[2] => "random", 9

So we would not end up deleting the slot. We'd just remove the data inside. Instead we want to shrink the vector to size 2 and get rid of the empty slot inbetween.

That's why we're using the erase-function that comes with the vector. It does all the work for us. But erase() only works with iterators :(

So it would look like:

Code:
    //We create an iterator pointing to the first element.
    std::vector<Entry>::iterator i = scoreboard.begin();
 
    //Then we increase the iterator by 1, so it points to the second element - the one we want to delete
    ++i;
 
    //Now we call erase:
    scoreboard.erase(i);
    //Afterwards we've remove the second element. scoreboard now only contains 2 elements.


Now the above code starts to make sense:
Code:
for(std::vector<Entry>::iterator i = scoreboard.begin(); i != scoreboard.end(); ++i){
    if( i->name == thePlayerWeWantToRemove ){
        scoreboard.erase(i);
        break;
    }
}

We create an iterator i and initialize it to begin(). The we loop over it (by increasing with via ++i) until it becomes end() (which denotes the end of the vector).
For each loop iteration we check if the name of the element i is pointing to (i->name) is equal to the name of the player we want to remove. If yes, we call erase and break the loop (break simply stops the loop. No reason to continue looping after we've found our player).

Ok, so we turn that into a class function for our Scoreboard class now:

Code:
struct Entry{
    std::string name;
    int points;
};
 
class Scoreboard{
 
public:
    void AddPlayer(std::string playerName, int points){
        Entry e = {playerName, points};
        m_scoreboard.push_back(e);
    }
 
    void Print(){
        for(int i=0; i < m_scoreboard.size(); ++i){
            std::cout << (i+1) << ":\t" << m_scoreboard[i].player << "\t" << m_scoreboard[i].points << "\n";
        }
    }
 
    //This one is new. Just copy pasted the code (with correct variable names)
    void RemovePlayer(std::string nameWeWantToRemove){
        for(std::vector<Entry>::iterator i = m_scoreboard.begin(); i != m_scoreboard.end(); ++i){
            if( i->name == nameWeWantToRemove ){
                m_scoreboard.erase(i);
                break;
            }
        }
    }
 
private:
    std::vector<Entry> m_scoreboard;
};
 
int main(){
    Scoreboard sb;
 
    //We add all players to the scoreboard.
    sb.AddPlayer("s3rius", 9001);
    sb.AddPlayer("denmax", 15);
    sb.AddPlayer("random", 9);
 
    //And print it:
    sb.Print();
 
    //Now we remove a player for highscore cheating :(
    sb.RemovePlayer("s3rius");
 
    //And print the scoreboard again. It should now show only 2 players.
    sb.Print();
 
    return 0;
}

That's it for today.
The Scoreboard class is far from finished, but try to understand what's going on here.
If you did you can try to add some features to it:

For example you would need a function to change a player's score. You've got 2 examples of looping over all vector elements. You can use any of them and modify the code so you can change a certain player's score.

You can also try to add a sort-function that sorts all players depending on their score. That's a lot more complicated. The easiest way would probably be to use the std::sort function. It's explained here:
http://www.cplusplus.com/reference/algorithm/sort/
Don't worry if you don't manage to get it working without help. It's not that easy.

I haven't tested any of this code so there might be syntactic/semantic errors I haven't caught. If something doesn't work as expected then you can leave a message.
 

denmax

You can change this now in User CP.
Reaction score
155
s3rius, I love you so much.

But there were some errors. I fixed some of them (you used m.scoreboard.player rather than name, as stated in the struct, and some others) but there's this one annoying one

JASS:
public:
    void AddPlayer(std::string playerName, int points){
        Entry e = {playerName, points}; //error is here
        m_scoreboard.push_back(e);
    }


The error code states: "error C2552: 'e' : non-aggregates cannot be initialized with initializer list"

Because of this small thing, I can't really test my "sort" function, so I'm going to ask if it's going to actually work.

I recognized in the private this code:

JASS:
std::vector&lt;Entry&gt; m_scoreboard;


If my research was correct, this basically meant that m_scoreboard is stored inside a vector, which allows me to use vector commands (if that's the right word to use) such as vector.size() and others.

I had an idea that uses two variables x and y. I thought of making a loop that would repeat itself vector().size times. Within this loop is a nested loop that repeats itself one less than the size() (e.g. if I labeled the nested ctr as y, it would be y = size() - 1. Under the nested loop, it uses an if statement, and checks if vector[y].score is greater than the score of vector[y+1].

If the statement returns true, I made a new temporary "Entry" variable, and is set to the value of vector[y+1]. With that value saved on a separate variable, I can now set vector[y] to its higher position, which is vector[y+1]. Now vector[y] is equal to [y+1], but since we have the temporary variable save the previous [y+1], we can equate [y] to the temp variable.

With that in mind, I added a new public and called it Sort(), and hope that using scoreboard.Sort() would do the work for me.

JASS:
        void Sort() {
                 for(int x=0; x&lt;m_scoreboard.size(); x++)
                 {
                        for(int y=0; y&lt;m_scoreboard.size()-1; y++)
                        {
                                if(m_scoreboard[y].score &gt; m_scoreboard[y+1].score)
                                {
                                        Entry temporary = m_scoreboard[y+1];
                                        m_scoreboard[y+1] = m_scoreboard[y];
                                        m_scoreboard[y] = temporary;
                                }
                        }
                }
        }


This is basically the code I just made out from understanding things. I'm not sure if it's the fastest or the most reliable or if it actually works at all. I'm also unsure of how to erase the 11th entry (maybe check to see if x is greater than 11, then use vector[x].erase)



What confused me the most is how this code would be persistent. Is the data going to exist even after I exit the program? If that were the case, would that mean that I could just edit the Print() public with ifstream and ofstream stuffs? Or do I have to do some more stuff to make the data stored persistent over multiple sessions?
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
The error code states: "error C2552: 'e' : non-aggregates cannot be initialized with initializer list"

Because of this small thing, I can't really test my "sort" function, so I'm going to ask if it's going to actually work.

It could be that your compiler doesn't allow code written in the new C++11 standard. The old C++03 standard has some tighter restrictions on aggregates so it doesn't allow the { } everywhere.

Two ways of solving this.
One would be to simply initialize the Entry like this instead:
Code:
Entry e;
e.name = playerName;
e.points = points;

The other one would be to add a Constructor to Entry:
Code:
struct Entry{
    std::string name;
    int points;

    Entry(std::string inName, int inPoints){
        name = inName;
        points = inPoints;
    }
};

Then you can initialize Entrys like this:
Code:
Entry e = Entry(playerName, points);

Pick whichever you prefer :)


std::vector<Entry> m_scoreboard;

If my research was correct, this basically meant that m_scoreboard is stored inside a vector, which allows me to use vector commands (if that's the right word to use) such as vector.size() and others.
Not quite. m_scoreboard isn't stored inside a vector, m_scoreboard IS the vector.

A vector isn't what you might have heard of in school. In this case a vector is a container.
Think of a vector as a cabinet (or whatever it's called).
The cabinet has a lot of drawers. In each of these drawers there is room for a single Entry object.
If you call push_back() then you tell your cabinet that you want to put another Entry into an empty drawer.
Unlike real-life cabinets, vectors have the magic ability to become larger once you run out of empty drawers.

std::vector<Entry> m_scoreboard;

This line says: We want a cabinet (vector) which can store a lot of Entry-objects (<Entry>) inside it. m_scoreboard is the name of the cabinet.


This is basically the code I just made out from understanding things. I'm not sure if it's the fastest or the most reliable or if it actually works at all. I'm also unsure of how to erase the 11th entry (maybe check to see if x is greater than 11, then use vector[x].erase)
This looks good. You've basically implemented a sorting algorithm called Bubble Sort.

It's a very slow sorting algorithm (still faster than my favorite sorting algorithm: Bogo Sort!), but for only ~10 players it's just fine.
But what do you mean by the 11th entry? Since you're only looping to size()-1 it shouldn't write an additional Entry into the vector. (if it does then the program will most likely crash).
If you worry about the Entry temporary then worry no more. It will be automatically cleaned up when the function finishes. It's only a local variable, after all.

What confused me the most is how this code would be persistent. Is the data going to exist even after I exit the program? If that were the case, would that mean that I could just edit the Print() public with ifstream and ofstream stuffs?

The data isn't persistent yet. The vector and all it's content is lost when you close the program.
So yes, you will have to do the storage manually. The best idea would be to write new class functions Save() and Load(). Save() can be a modified version of Print() that writes the scoreboard into a file on your hard drive (via ofstream). Load() would load the data again and write it back into the vector.

You can try if you manage to get ifstream and ofstream working yourself. If you can't I'll write you up some help.

File input/output can be tricky at times, so here a few hints:

1) If you get the Save() function to work you can manually check the txt file and see what you've actually written.

2) If your player name has whitespaces in it you'll have a lot more trouble loading the data correctly. So try without whitespaces at first.

3) If you try to read a variable and ifstream cannot parse it correctly, it'll go into error mode. At that point it won't read anything anymore.
For example you try to read an integer value from your file (your points) but ifstream accidently reads a player name. It cannot convert the name into an integer. So it'll go into error mode.
So if your program hangs or goes into infinite loop: this might be it.
 

denmax

You can change this now in User CP.
Reaction score
155
Yes, since our school is using Visual Studio 6.0 (I don't see why they still do - they have copies of 2013 versions of AutoCAD but they fail to download an updated Visual Studio :/). It's also probably a big reason why camel's initial code also returns a lot of errors.

And about save and load

JASS:
void Save() {
    ofstream HighScore(&quot;highscore.txt&quot;);
    for (int x=0; x&lt;m_scoreboard.size(); x++)
    {
        HighScore &lt;&lt; (x+1) &lt;&lt; &quot;:\t&quot; &lt;&lt; m_scoreboard[x].player &lt;&lt; &quot;\t&quot; &lt;&lt; m_scoreboard[x].points &lt;&lt; &quot;\n&quot;;
    }
    HighScore.close();
}


JASS:
void Load() {
    ifstream HighScore(&quot;highscore.txt&quot;);
    if (HighScore.is_open())
    {
        while (! HighScore.eof())
        {
            getline (HighScore,line);
            cout &lt;&lt; line;
        }
    }
    else
        cout &lt;&lt; &quot;Output when it can&#039;t open file&quot;;
    HighScore.close();
}


My problem is how I would store the contents of my txt. file into the vector again when it loads. I'm not even sure on how to convert the score in the txt file (which I assume to be a string) to an integer again.

I've looked into commands such as seek.g() so that I can use get() onto a specific number of character blocks, but I wouldn't be able to know how many character blocks there actually are in the first place, so I can't really set a value beforehand. I could use a delimiter, but what character would that be? A whitespace? \t? Basically, I'm at a stop when it comes to setting the contents of the txt file into the vector because I really have no clue what some of the members of ifstream and ofstream actually do.
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
Visual Studio 6.0?? That shit is 14 years old!


..


ifstream also implements the counterpart to <<.

You store data in the file with:
Code:
Highscore << m_scoreboard.player;

And you can retrieve it again with:
Code:
Highscore >> m_scoreboard.player;

When you use << to write data into the file it'll be turned into string format:
Code:
stream << 3.14;
stream << true;
stream << "hi there";

will write the following into the ifstream file:
"3.14 true hi there"

Everytime you call >> to read data it'll take one word from the text and try to interpret it.

Code:
float f;
stream >> f; //Reads the first word "3.14" from the file and converts it to a float
 
std::string s
stream >> s; //Reads the next word "true" from the file and keeps it as a string.

The only problem is with the string "hi there".
As I said before, storing strings which contain whitespaces (or tabs) is more complicated.
If you want to retrieve "hi there" and store it back into a variable inside your program you can't just use stream >> s again. Because that would only read one word (=> "hi") and not both.
So if it's not a problem you should stay with single-word player names.

If you want multi-word names then do the following:
Before you store the string inside the file, count the number of words of the player name. Write that number in front of your string.
So a player with name "Sir Doctor Blargh" with 100 points would be stored as
1: 3 Sir Doctor Blargh 100
(position) (number of words) (player name) (points)

When you load it again you extract the number of words and read this many words from the file and just append them alltogether.
 

denmax

You can change this now in User CP.
Reaction score
155
Ahah! Figured out how getline works and now I've practically made a working test. My problem now is to add in the scoreboard.

Apparently, an error occurs when I want to convert the score from the .txt (I'm assuming its string) into an integer using 'atoi'.

Error is:
Code:
cannot convert parameter 1 from 'class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >' to 'const char *'
        No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

JASS:
void Print(){
    if (m_scoreboard.size() &lt;= 10) {
        for(int i=0; i &lt; m_scoreboard.size(); ++i){
            std::cout &lt;&lt; (i+1) &lt;&lt; &quot;:\t&quot; &lt;&lt; m_scoreboard.name &lt;&lt; &quot;\t&quot; &lt;&lt; m_scoreboard.score &lt;&lt; &quot;\n&quot;;
        }
    } else
        for(int i=0; i &lt; 10; ++i){
            std::cout &lt;&lt; (i+1) &lt;&lt; &quot;:\t&quot; &lt;&lt; m_scoreboard.name &lt;&lt; &quot;\t&quot; &lt;&lt; m_scoreboard.score &lt;&lt; &quot;\n&quot;;
        }
}
 
void Save() {
    ofstream HighScore(&quot;FINALscoreboard.txt&quot;);
    for(int i=0; i &lt; m_scoreboard.size(); ++i) {
        HighScore &lt;&lt; m_scoreboard.name &lt;&lt; &quot;*&quot; &lt;&lt; m_scoreboard.score &lt;&lt; &quot;*&quot;;
    }
    HighScore.close();
}
 
void Load() {
    string line;
    Entry e;
    ifstream HighScore(&quot;FINALscoreboard.txt&quot;);
    while (! HighScore.eof() ) {
        getline (HighScore, line, &#039;*&#039;);
        e.name = line;
        getline (HighScore, line, &#039;*&#039;);
        e.score = atoi(line);
        m_scoreboard.push_back(e);
    }
    HighScore.close();
}




EDIT:
Oh wow I didn't see your post! Sorry!

But since you're here, can I ask. Would the code I just made work with your scoreboard?

Also, I'm assuming I need to do this to clean the .txt file before I store new ones.

JASS:
ofstream File(&quot;FINALscoreboard.txt&quot;, ios::trune);
File.close();
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
Yea it should work. I'm not using getline much so I'm not 100% confident, but trying won't hurt.

However instead of:
Code:
if (m_scoreboard.size() <= 10) {
for(int i=0; i < m_scoreboard.size(); ++i){
std::cout << (i+1) << ":\t" << m_scoreboard[i].name << "\t" << m_scoreboard[i].score << "\n";
}
} else
for(int i=0; i < 10; ++i){
std::cout << (i+1) << ":\t" << m_scoreboard[i].name << "\t" << m_scoreboard[i].score << "\n";
}
    }

You should write:
Code:
int size = m_scoreboard.size();
if( size > 10){
    size = 10;
}
 
for(int i=0; i<size; ++i){
    std::cout ....
}

So you don't have to write the for-block twice.

About
Code:
e.score = atoi(line);

atoi is an old function so it's not used to working with strings. Instead it wants a char* (a c-string) passed.Fortunately std::strings have a function to convert them to c-strings

Code:
e.score = atoi( line.c_str() );

And yes, you should clear the text file before you write to it again. You can do it in one go:
Code:
std::ofstream File("somefile", ios::trunc);
... write the new stuff into your file...
File.close();
 

denmax

You can change this now in User CP.
Reaction score
155
Thanks a whole lot! It works like a charm now. It must be hard work for you to teach a guy who knew nothing about classes, vectors, struct, and so much more, and taught it to him in less than a week's span. I'm very thankful!

My last question is this.

I want it so that when the program calls save(x), x would be the name of the file it would save to (for the purpose of multiple levels). To make that possible, what would x be? A string? Or what? Would ifstreamFile("x"); do the job?
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
It's always a nice memory refresher for me too :)

Yes, you should make x a std::string and pass it as <if/of>stream File(x).
When you enclose something in quotes like "x" it'll be taken literally. So even if you have a variable named x, "x" will still only be an "x".
 

denmax

You can change this now in User CP.
Reaction score
155
Well, doing that gives this error to both ifstream and ofstream. Maybe it has something to do with this non-updated, 20 century year old program?

Code:
'__thiscall std::basic_ofstream<char,struct std::char_traits<char> >::std::basic_ofstream<char,struct std::char_traits<char> >(const char *,int)' : cannot convert parameter 1 from 'class std::
basic_string<char,struct std::char_traits<char>,class std::allocator<char> >' to 'const char *'

EDIT:
Wait a minute, this looks familiar..

Will I be using c_str() again? If so, then where?
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
So it looks something like this now?

Code:
void Save(std::string x){
    std::ofstream ofs(x);
    ... use it ...
}
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top