Simple game database problems - c++

NOTE: I am new to C++ and may do things that are bad practice and if you see that please tell me so I can fix that and please don't be mean. I have only started coding 1-2 months ago. And I am still learning. Please be open to the fact I may not know everything.
This is a console text-based game. It works great! Although, I am creating a feature in it to allow the user to drag and drop any amount of other databases on it to allow database transfers. Although this works fine the problem is that I have a little process it will do to try and make sure none of the info in the databases is the same by placing a number to them,
Example there will be 2 profiles 1 in each file. They are both named main. Then the user drags the second database onto the game and it loads that database into the original one. But now becuase there are 2 SIMILAR profile names it won't be able to differentiate which is which. So then it goes through a little function which scans the database and places a number in front of the copies. Starting at 5 and working its way up. Although this would seem to work and not be that hard to actually do I have hit a problem and I do not know what is wrong. I do know however it is something with how it scans for duplicates. Please help.
I have tried for like a whole day trying different methods or re-writing the code. Google has not revealed a lot to me.
I am using the following libraries in my code. (Some might not be used in the example but tbh I don't remember which is directly used in THIS function).
#include <iostream>
#include <iterator>
#include <cstring>
#include <cmath>
#include <cassert>
#include <string>
#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <utility>
#include <vector>
#include <array>
#include <functional>
#include <fstream>
Here is the scanning function to make sure there are no duplicate profiles.
Let me explain what happens.
I make a ton of variables which are used in the database. As you can see inside of the database it has a certain order.
Using file stream I access the database. (I have a function which will combine all the databases the user dragged in and the current profiles and data which works just fine).
The pattern in the database looks something like this.
profile_name user_name 100 3 0 0 0 0 knight 1 100 0 0
profile name health etc
If you look at the variables you will see the technical order.
void scanCopy()
{
std::string profile{ "John's_Profile" };
std::string name{ "John_Doe" };
int health{ 0 };
int damage{ 0 };
int gold{ 0 };
int exp{ 0 };
int level{ 0 };
int score{ 0 };
std::string CLASS{ "null" };
int dungeon{ 0 };
int maxHealth{ 0 };
int lives{ 0 };
int kills{ 0 };
std::ifstream in("data/database.txt");
std::vector <std::string> profiles;
int sizeOfVector{ 0 };
while (in >> profile >> name >> health >> damage >> gold >> exp >> level >> score >> CLASS >> dungeon >> maxHealth >> lives >> kills)
{
profiles.resize(sizeOfVector += 1);
profiles.at(sizeOfVector - 1) = { profile };
std::cout << profiles.at(sizeOfVector - 1) << "\n\n";
}
in.close();
for (int loop{ 0 }; loop < sizeOfVector; ++loop)
{
int compare{ loop };
for (int index{ loop }; index < sizeOfVector; ++index)
{
if (compare == index)//meaning they are like at profiles(1)and (1)
continue;
if (profiles.at(compare) == profiles.at(index))
{
std::ofstream out("data/~database.txt", std::ios::app);
in.open("data/database.txt");
int nameIndex{ 5 };
while (in >> profile >> name >> health >> damage >> gold >> exp >> level >> score >> CLASS >> dungeon >> maxHealth >> lives >> kills)
{
if (profile == profiles.at(index))
{
out << profile << nameIndex << " " << name << " " << health << " " << damage << " " << gold << " " << exp << " " << level << " " << score << " " << CLASS << " " << dungeon << " " << maxHealth << " " << lives << " " << kills << " " << std::endl; //Notice at the start profile is put into the database with an extra variable nameIndex to make its name now unique.
++nameIndex;
}
else
{
out << profile << " " << name << " " << health << " " << damage << " " << gold << " " << exp << " " << level << " " << score << " " << CLASS << " " << dungeon << " " << maxHealth << " " << lives << " " << kills << " " << std::endl;
}
}
}
}
}
in.close();
remove("data/database.txt");
in.open("data/~database.txt");
std::ofstream out("data/database.txt", std::ios::app);
//A buffer to copy everything inside a file to a string.
std::string upData((std::istreambuf_iterator<char>(in)),
(std::istreambuf_iterator<char>()));
/////
if (out)
out << upData; //putting everything in the tmp file to the file named database.txt
out.close();
in.close();
remove("data/~database.txt");
in.close();
out.close();
}
The problem is that it does not do its job. It will put numbers by anything. Besides that, it will also seem to overflow or something. What it does is after you already dragged something in, it pretends to work. Then any more input from dragging it does not get scanned. Thing is that everything is copied from the files the user drags from the database to a tmp file. Then the database is deleted and the temp file is renamed to database.txt. The problem is that this whole scan function seems to not be working right and I don't see the problem in it. Does anyone know a good way to do something like this or what the problem is? Thanks!

We really do not need the backstory that this is a game, that users can do XYZ and so on. Please construct a minimal example, as in minimal reproducible example. Often by constructing those, you yourself discover the problem. – Fureeish
Thank you Fureeish. I have found the problem. I was sending the function too many times which wiped the file or it did not scan it all the way. It is hard to explain the real thing I did because it was easy but I can't explain it well.
ALL IN ALL. I examined and found the bug, I was sending it to this function I posted up there too many times. or too little times.

Related

Saving and sorting game information into an external file

I have this Blackjack card game (simple text-only console application) that I have been working on and currently I am trying to improve the way it stores the statistics in an external .txt file. I have been learning and messing around with fstream stuff and I got it to write the statistics into the external file: How the statistics are saved in a .txt file.
Now I would like to improve the system so that it sorts the players' entries according to the win ratio. I basically need to read a value from a specific spot in each existing entry (win ratio), compare them, and insert the new entry to the correct spot according to the value. Each entry has the same format as shown in the picture. Then after the .txt file has been sorted, I just need to basically read it and display it in the console.
I am pretty inexperienced with using fstream stuff and manipulating files in general so I would really appreciate some direction with what is the most efficient way to do this. I have thought of several ways of reading and comparing the win ratios but I can't think of a good way to separate each player's entry and to insert the new entry into the corresponding spot.
Here is how the current code of the saving system works, in case it is useful:
void saveGame() {
string playerName = {};
getline(cin, playerName);
fstream savefile;
savefile.open("Blackjack_Statistics.txt", fstream::app);
if (savefile.is_open()) {
savefile << "\n ---~~=[ " << playerName;
if (playerName[playerName.size() - 1] == 's') {
savefile << "' ";
} else {
savefile << "'s ";
}
savefile << "Statistics ]=~~---\n\n\t[Hands won]: " << countWins << "\n\t[Hands lost]: " << countLosses << "\n\t[Hands pushed]: " << countPushes << "\n\t[Hand win ratio]: ";
if (countLosses > 0) {
savefile << (countWins / countLosses);
} else {
savefile << (countWins / 1);
}
savefile << "\n\n\t[Blackjacks]: " << countBlackjacks << "\n\t[Dealer blackjacks]: " << countDealerBlackjacks << "\n\t[Double downs won]: " << countDouble << "\n\t[Longest win streak]: " << countStreakLongest << "\n\n";
} else {
cout << "Saving failed!";
}
savefile.close();
return;
}
Thanks! :)

Randomize distinct cout statements (like getting 4 possible answers for a quiz)

This code is outputting 1 correct answer - which is always the one associated with 'random_number', so the first cout statement is always true. But who wants this kind of a quiz?
srand((int)time(0));
int random_number = rand() % max_event_number;
std::cout <<"\n" << final_years[random_number] << std::endl;
std::cout << "\n" << final_years[1 + random_number] << std::endl;
std::cout << "\n" << final_years[2 + random_number] << std::endl;
std::cout << "\n" << final_years[3 + random_number] << std::endl;
std::cout << "\n" << "Please type the correct year : " << std::endl;
Yes..., I can generate some random answers from the entire array, but they won't necessarily include the correct answer.
I don't want to change the way the correct answer is generated by the first 'random_number', because it takes only one line of code to check if the answer is true or not...
If only I could shuffle every time those 4 cout statement...
How would you do it?
Maybe not the best solution, but it works:
#include <iostream>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <vector>
std::vector<std::string> possible_answers = { final_years[random_number], final_years[1 + random_number], final_years[2 + random_number], final_years[3 + random_number] };
// I initialized a vector like an array with all the 4 answers, including the correct one (since C++11)
std::vector<std::string>::iterator it;
it = possible_answers.begin();
std::random_shuffle(possible_answers.begin(), possible_answers.end()); //shuffled its contents
for (it = possible_answers.begin(); it < possible_answers.end(); it++) //some tinkering to output vector's content
std::cout <<"\n" << *it;
Now the randomly generated numbers are displayed in a random order on the screen, but the correct answer is always the first generated number.

(C++) Vector elements seem to disappear after for loop ends

I'm extremely new to C++ (even newer to OOP) and I'm doing my first project that doesn't take place within one .cpp file. I've run into a seemingly simple issue where my vector data seems to be disappearing.
Code chunk inside main.cpp's main function:
vector<Horse> HorseStable(horseAmount); // creating an vector of horse objects based on user input horseAmount
for (int i = 0; i < horseAmount; i++) // sets name for each horse and rider
{
string nameString = "";
string riderString = "";
cout << "Enter name for horse #" << (i + 1) << ": ";
cin >> nameString;
HorseStable[i].setName(nameString);
cout << "Enter name for rider of " << nameString << ": ";
cin >> riderString;
HorseStable[i].setRider(riderString);
system("cls");
}
HorseStable[0].printName(); // a test to see if the horse name stayed inside the vector (it did not)
Entire Horse.h file:
#pragma once
#include <iostream>
#include <string>
#include <stdlib.h>
#include <time.h>
class Horse
{
private:
std::string name;
std::string rider;
public:
// these three ints were supposed to be private, but I couldn't access
// maxRunningDistPerSecond as a displayHorse() function parameter from main
// maybe figuring out my first issue will help with this, as I was attempting
// HorseStable[0].displayHorse(maxRunningDistPerSecond)
int maxRunningDistPerSecond;
int distanceTraveled;
int racesWon;
Horse() // default constructor
{
std::string name = " ";
std::string rider = " ";
int maxRunningDistPerSecond = 100;
int distanceTraveled = 0;
int racesWon = 0;
};
int runASecond(int, int);
int sendToGate(int);
void displayHorse(int);
std::string setName(std::string); // sets the horse name based on user input from main.cpp variable
std::string printName(); // simply prints the horse name, I don't believe my issue is here
std::string setRider(std::string);
std::string printRider();
};
Code chunk inside Horse.cpp:
std::string Horse::setName(std::string nameString) // takes user input for horse name
{
Horse::name = nameString;
return std::string(nameString);
}
std::string Horse::printName() // prints the horse's name
{
return std::string(name);
}
setName() and getName() work perfectly within my for loop inside main.cpp, but all data seems to disappear when I attempt them after the loop ends. I've looked for hours for solutions, but had to revert to this stable build after nothing worked. I'm not very good with pointers and passing by reference, but these seem to be the only things that will work. Is it possible that I was using pointers wrong? Should I be creating a vector of Horse pointers, rather than a vector of actual Horse objects?
My other issue:
If you've noticed my public members that are supposed to be private in Horse.h, I cannot access them when private as parameters from functions called in main. This makes some sense, as my function call in main looked like this:
HorseStable[0].displayHorse(distanceTraveled)
I'm not sure how I could refer to each element of the vector within the Horse class, which seems like the only way distanceTraveled would be reachable as private. My professor wants the variables in question to be private, which makes this an issue. The user defines the amount of Horse objects, which means I can't just declare a few named Horses and simply displayHorse(distanceTraveled) them.
Function declaration from Horse.cpp:
void Horse::displayHorse(int distanceTraveled) // attempts to show a graphic of the race progress
{
if (distanceTraveled >= 50)
{
std::cout << "|-> |" << " " << name << ", ridden by " << rider;
}
else if (distanceTraveled >= 100)
{
std::cout << "|--> |" << " " << name << ", ridden by " << rider;
}
else if (distanceTraveled >= 150)
{
std::cout << "|---> |" << " " << name << ", ridden by " << rider;
} // this goes on up to 1000, but this is all that's necessary for posting
I apologize if my formatting isn't up to par, but this assignment has really been stressing me out. I've been understanding all the new material, but it always seems like pointers and referencing are the things that render my assignments unusable.

Structured output

I recently started programming in c++ and I've bumped into a small problem. If I want my output to be structured (let's say that every line starts with a name and then a number) in a way that the names are written normally to the screen (every first letter of every name starts at the beginning of each new line) and I want the numbers that follow to be lined up in a column, how would I do this? I want the programs output to look like this:
Gary 0
LongName 0
VerylongName 0
I want my program to print something in the way above, but with different lengths of names (and the '0' in this case, lined up in a column).
Try the following: if you know the maximum length of all the names you intend to print (e.g. 20), then use the C++ i/o manipulators to set the width of the output (and left-justification). This will force the output to take up max characters.
Code snippet:
#include <iostream>
#include <iomanip>
...
// for each entry
std::cout << std::setw(20) << std::left << "Gary" << 10 << "\n";
...
std::cout << std::flush;
Here's some more information...
I'm shooting in the dark here since you haven't really included much information... HOWEVER one way you can do this is to make sure that you create the columns with padding around the name - and not worry about the numbers. Formatted output is one case where C has an advantage over C++ (IMHO). In C++ you can also do this with something like this:
cout << setw(15) << name << number << "\n";
Bonus points if you figure out ahead of time the maximum length of the name you have and add, say, 4 to it.
Not in the C++ standard library, but still worth mentioning: boost::format. It will let you write printf-like format strings while still being type-safe.
Example:
#include <boost/format.hpp>
#include <iostream>
#include <string>
struct PersonData
{
std::string name;
int age;
};
PersonData persons[] =
{
{"Gary", 1},
{"Whitney", 12},
{"Josephine ", 101}
};
int main(void)
{
for (auto person : persons)
{
std::cout << boost::format("%-20s %5i") % person.name % person.age << std::endl;
}
return 0;
}
Outputs:
Gary 1
Whitney 12
Josephine 101
struct X
{
const char *s;
int num;
} tab[] = {
{"Gary",1},
{"LongName",23},
{"VeryLongName",456}
};
int main(void)
{
for (int i = 0; i < sizeof(tab) / sizeof(struct X); i++ )
{
// C like - example width 20chars
//printf( "%-20s %5i\n", tab[i].s, tab[i].num );
// C++ like
std::cout << std::setw(20) << std::left << tab[i].s << std::setw(5) << std::right << tab[i].num << std::endl;
}
getchar();
return 0;
}

Array of Struct

I want to just hard code these values into a table. when I try to use 2D arrays, I run into the problem of dealing with characters and integers. When I do a struct I have this so far but it doesn't divide the information up in columns, and I'm not sure how to format it that way. (I only did 3 rows to start off with, if I get them working, the rest will just be the same)
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
typedef struct table
{
std::string game;
int year;
float rating;
int num_voters;
}t;
void processTab(t*);
int main()
{
t tabl[2] = {0,0};
int i;
processTab(tabl);
for(i=0; i<2; i++)
{
std::cout << "Game: " << setw(20) << tabl[i].game;
std::cout << "\tYear: " << setw(4) << tabl[i].year;
std::cout << "\tRating: " << fixed << setprecision(2) << tabl[i].rating;
std::cout << "\tVoters: " << setw(6) << tabl[i].num_voters;
}
system("pause");
return 0;
}
void processTab(t*tab)
{
(tab[0].game, "twilight struggles");
tab[0].year = 2005;
tab[0].rating = 8.226;
tab[0].num_voters = 10690;
(tab[1].game, "Agricloa");
tab[1].year = 2007;
tab[1].rating = 8.17;
tab[1].num_voters = 23738;
(tab[2].game, "Puerto Rico");
tab[2].year = 2002;
tab[2].rating = 8.163;
tab[2].num_voters = 27433;
}
Table Data:
Game (0) Year (1) Rating (2) Num Voters (3)
Twilight Struggle 2005 8.226 10690
Agricola 2007 8.17 23738
Puerto Rico 2002 8.163 27433
Through the Ages 2006 8.153 8137
Power Grid 2004 8.02 21655
Le Havre 2008 7.972 9258
Eclipse 2011 7.968 3194
Brass 2007 7.911 5814
Dominion: Intrigue 2009 7.895 10889
Caylus 2005 7.878 13878
What I think you are looking for is <iomanip>
#include <iomanip>
std::cout << "Game: " << setw(20) << tabl[i].game;
std::cout << "\tYear: " << setw(4) << tabl[i].year;
std::cout << "\tRating: " << fixed << setprecision(3) << tabl[i].rating;
std::cout << "\tVoters: " << setw(6) << tabl[i].num_voters;
std::cout << std::end;
Notes:
setw adds padding when writing out stuff, so it will always be at least a certain width
setprecision specifies how many decimal places to display
fixed makes floating point never use scientific notation
Your game member is a letter, and you're attemptying to assign it a string. Don't use strcpy in C++, use the std::string class instead.
#include <string>
struct table
{
std::string game;
int year;
double rating;
int num_voters;
};
Avoid using namespace std;, when you get to complex code with many namespaces, those few letters are a small price to pay for avoiding confusion.
Avoid endl: it flushes buffers which is slow. If you just want a newline, use '\n'.
Also, you can use the new initialization syntax to initialize your list:
std::vector<table> tbal = {
{"twilight struggles ", 2005, 8.226, 10690},
{"Agricola ", 2007, 8.17 , 23738},
{"Puerto Rico ", 2002, 8.163, 27433}
};
Short answer: use std::vector, not a raw array.
The following is as close to your original code as I could make it, introducing a minimum number of new features for you:
#include <assert.h> // assert
#include <iostream> // std::cout, std::endl
#include <stddef.h> // ptrdiff_t
#include <string> // std::string
#include <utility> // std::begin, std::end
#include <vector> // std::vector
using namespace std;
typedef ptrdiff_t Size;
template< class Container >
Size countOf( Container& c ) { return end( c ) - begin( c ); }
struct Game
{
string game;
int year;
double rating;
int num_voters;
};
void initialize( vector<Game>& games )
{
assert( countOf( games ) == 0 );
games.resize( 3 );
games[0].game = "twilight struggles";
games[0].year = 2005;
games[0].rating = 8.226;
games[0].num_voters = 10690;
games[1].game = "Agricloa";
games[1].year = 2007;
games[1].rating = 8.17;
games[1].num_voters = 23738;
games[2].game = "Puerto Rico";
games[2].year = 2002;
games[2].rating = 8.163;
games[2].num_voters = 27433;
}
int main()
{
vector<Game> games;
initialize( games );
for( int i = 0; i < countOf( games ); ++i )
{
cout << i << endl;
cout <<"\tGame: " << games[i].game << endl;
cout<<"\tYear: " << games[i].year << endl;
cout<<"\trating: " << games[i].rating << endl;
cout<<"\tnum voters: " << games[i].num_voters << endl;
}
}
There are ways to just declare the data more directly, including brace initializers; check out your C++ textbook.
First you need to define your table (bad name for a struct, by the way) correctly. You're trying to use game to store a string, but have defined it as only a single char. You probably want to change that to a std::string instead.
Then you probably want to do your formatting in an operator<< overloaded to take a reference to table as the type. #MooingDuck has already covered the formatting itself quite well, so it's mostly a matter of how you package that:
std::ostream &operator<<(std::ostream &os, table const &t) {
os << "Game: " << setw(20) << t.game;
os << "\tYear: " << setw(4) << t.year;
os << "\tRating: " << fixed << setprecision(2) << t.rating;
return os << "\tVoters: " << setw(6) << t.num_voters;
}
Along with that, you almost certainly want to change tabl from an array to std::vector<table>:
std::vector<tabl> tabl;
Then processing the table becomes:
std::copy(tabl.begin(), tabl.end(), std::ostream_iterator<table>(std::cout, "\n"));
One other minor detail: you seem to have two entirely different/separate functions, both named processTab. You probably want to rename at least one of those. Just glancing at them, I'd probably call one initializeTable or something on that order.