[SOLVED]Saving and reading information with QTextStream
-
EDIT: I have solved my problem thanks very much.
Hello fellow Qt users I have created a game of tic-tac-toe using Qt almost everything in it is functional but recently I attempted to add save/load game feature. I used QFile and QTextStream but, it won't work the game is saved correctly but is never loaded correctly my current setup for loading and saving is as follows:
@void MainWindow::on_actionSaveGame_1_triggered()
{
QFile game1file("game1.txt");
game1file.open(QIODevice::WriteOnly|QIODevice::Text);
QTextStream game1(&game1file);
game1 << ui->z11->text() << "\n";
game1 << ui->z21->text() << "\n";
game1 << ui->z31->text() << "\n";
game1 << ui->z12->text() << "\n";
game1 << ui->z22->text() << "\n";
game1 << ui->z32->text() << "\n";
game1 << ui->z13->text() << "\n";
game1 << ui->z23->text() << "\n";
game1 << ui->z33->text() << "\n";
game1file.close();
}void MainWindow::on_actionLoadGame_1_triggered()
{
QFile game1file("game1.txt");
game1file.open(QIODevice::ReadOnly|QIODevice::Text);
QTextStream game1(&game1file);
game1.seek(1);
game1 >> tilestate;
if (tilestate == "x")
{
ui->z11->setText("x");
}
else if (tilestate == "o")
{
ui->z11->setText("o");
}
else
{
ui->z11->setText("");
}
game1.seek(2);
game1 >> tilestate;
if (tilestate == "x")
{
ui->z21->setText("x");
}
else if (tilestate == "o")
{
ui->z21->setText("o");
}
game1.seek(3);
game1 >> tilestate;
if (tilestate == "x")
{
ui->z31->setText("x");
}
else if (tilestate == "o")
{
ui->z31->setText("o");
}
else
{
ui->z31->setText("");
}
game1.seek(4);
game1 >> tilestate;
if (tilestate == "x")
{
ui->z12->setText("x");
}
else if (tilestate == "o")
{
ui->z12->setText("o");
}
else
{
ui->z12->setText("");
}
game1.seek(5);
game1 >> tilestate;
if (tilestate == "x")
{
ui->z22->setText("x");
}
else if (tilestate == "o")
{
ui->z22->setText("o");
}
else
{
ui->z22->setText("");
}
game1.seek(6);
game1 >> tilestate;
if (tilestate == "x")
{
ui->z32->setText("x");
}
else if (tilestate == "o")
{
ui->z32->setText("o");
}
else
{
ui->z32->setText("");
}
game1.seek(7);
game1 >> tilestate;
if (tilestate == "x")
{
ui->z13->setText("x");
}
else if (tilestate == "o")
{
ui->z13->setText("o");
}
else
{
ui->z13->setText("");
}
game1.seek(8);
game1 >> tilestate;
if (tilestate == "x")
{
ui->z23->setText("x");
}
else if (tilestate == "o")
{
ui->z23->setText("o");
}
else
{
ui->z23->setText("");
}
game1.seek(9);
game1 >> tilestate;
if (tilestate == "x")
{
ui->z33->setText("x");
}
else if (tilestate == "o")
{
ui->z33->setText("o");
}
else
{
ui->z33->setText("");
}
game1file.close();
}
@
Could someone please explain what the issue is? I can provide more code if needed or even a working sample.
Each Ui element prefixed with a 'z' is a tile and the numbers are co-ordinates for example z11 is x=1 and y=1 and z23 would be the top of the middle column. -
Hi, welcome to devnet
First of all... OMG... there are loops in programming languages you know. What if tic-tac-toe was 20x20 and not 3x3? Spend the weekend copy/pasting? ;)
Second - when using QTextStream don't open the file in QFile::Text mode. The text stream will take care of the newlines.
But to the point. You haven't said that but I'm guessing the possible state of the text is either "x", "o" or empty. Saving nothing (i.e. an empty string) is not not a good way to store something ;) Your usage of seek() doesn't make things better either.
Consider this example (I'll shorten it to 5 for brevity):
Save: nothing, nothing, x, o, nothing
The content of the file is now:
@
\n
\n
x\n
o\n
\n
@
Now read like you did:
@
//why 1??? you skip the first newline that way
//and QTextStream skips whitespace so:
seek(1); game1 >> tilestate; //tilestate == "x";
//why 2??? now you skip two newlines
seek(2); game1 >> tilestate; //tilestate == "x" again
//why 3??? now you skip two newlines and the x
//QTextStream skips whitespace so:
seek(3); game1 >> tilestate; //tilestate == "o";
//why 4??? now you skip two newlines, x and another newline
seek(4); game1 >> tilestate; //tilestate == "o" again
//why 5??? now you skip two newlines, x, another newline and o
seek(5); game1 >> tilestate; //tilestate == ""
@
So you entered: nothing, nothing, x, o, nothing
You got back: x, x, o, o, nothingSo a couple of things:
- use loops, this is a classic example of a usage for them
- don't open file in text mode when using QTextStream
- don't save "nothing". Use some character to signify an empty space
- c++ indices and offsets start at 0, not 1
- operator >> of QTextStream skips whitespace (e.g. newlines)
- seek() works with characters, not lines. The count also includes invisible characters like newlines
- don't seek at all, just loop and use readLine()
-
Thank you so much, that was very helpful and hilarious I couldn't even begin to imagine what your reaction would be if you saw all of the code *currently its nearly 1000 lines so yeah.... You'd probably go into a coma. Anyways thanks for the reply I've used some of your other answers to others posts as well. But, I still have one question would there be a way for me to systematically access the different tiles such as co-ordinates x=1 y=2 ui->"z" + x + y->setText(blah)? I've looked and tried but could not find anything plus, it's so unfortunate you cant't use variables in style-sheets WHY! Whatever, thank you.
-
The way I would do that would be something like this:
@
//define the grid size
const int GridSize = 3;//create the tiles and place them in a grid layout:
QList<Tile*> tiles; //I'll assume your tile class is called Tile
QGridLayout* grid = new QGridLayout();
for(int i = 0; i < GridSize; ++i)
for(int j = 0; j < GridSize; ++j)
tiles.append(new Tile());
grid->addWidget(tiles.last(), i, j);
}
}
someWidget->setLayout(grid); //someWidget might be your window//function to access specific tile by index:
Tile* tile(int index) { return tiles[index]; }//or by x,y coordinate:
Tile* tile(int x, int y) { return tiles[y * GridSize + x]; }//then you can access tile 1,2 text like this:
tile(1,2)->text();
@
I'm skipping range checking etc. for brevity.
Just remember that upper left is (0,0), and bottom right is (2,2), not (1,1) and (3,3).As for stylesheets - there are no variables, but if you name your widgets (using setObjectName) eg. "w12" then you can write a stylesheet specifically for that widget. For example:
@
QWidget#w12 { color: red }
@Also, if your tile has a name like above you can access it through a parent by name e.g.
@
someParent->findChild<QWidget*>("w12")->setText("foo");
@[EDIT]: Fixed some code. I was writing in a hurry.
-
So, if I called @game1.pos()@ it would not give the line position?
-
No, it would give character position, which might be on any line, depending how much newline characters are between. There is no correlation between neither seek() nor pos() to a line number. The only method that understands concept of a line is (badam badam!) readLine() ;)
-
I'm sorry to keep bothering you but now QTextStream won't even insert the newline character which I guess I could alter my reading function to account for that except... It just doesn't quite add up
Code:
@void MainWindow::on_actionSaveGame_1_triggered()
{
QString line;
int linenum = 0;
QFile game1file("game1.txt");
game1file.open(QIODevice::WriteOnly);
QTextStream game1(&game1file);
QString z11,z21,z31,z12,z22,z32,z13,z23,z33;
if (ui->z11->text() == "") z11 = ".";
else z11 = ui->z11->text();
if (ui->z21->text() == "") z21 = ".";
else z21 = ui->z21->text();
if (ui->z31->text() == "") z31 = ".";
else z31 = ui->z31->text();
if (ui->z12->text() == "") z12 = ".";
else z12 = ui->z12->text();
if (ui->z22->text() == "") z22 = ".";
else z22 = ui->z22->text();
if (ui->z32->text() == "") z32 = ".";
else z32 = ui->z32->text();
if (ui->z13->text() == "") z13 = ".";
else z13 = ui->z13->text();
if (ui->z23->text() == "") z23 = ".";
else z23 = ui->z23->text();
if (ui->z33->text() == "") z33 = ".";
else z33 = ui->z33->text();game1 << z11 << "\n"; game1 << z21 << "\n"; game1 << z31 << "\n"; game1 << z12 << "\n"; game1 << z22 << "\n"; game1 << z32 << "\n"; game1 << z13 << "\n"; game1 << z23 << "\n"; game1 << z33 << "\n"; /*while (linenum <= 9) { ++linenum; line = game1.readLine(); if (line == "") { game1 << ".\n"; } }*/ game1file.close();
}
void MainWindow::on_actionLoadGame_1_triggered()
{
QString line;
int linenum = -1;
char tilemark;
QFile game1file("game1.txt");
game1file.open(QIODevice::ReadOnly);
QTextStream game1(&game1file);
while (linenum <= 9)
{
line = game1.readLine();
//game1 << "\n";
linenum++;
if (line == ".") {}
else if (linenum == 1) ui->z11->setText(line);
else if (linenum == 2) ui->z21->setText(line);
else if (linenum == 3) ui->z31->setText(line);
else if (linenum == 4) ui->z12->setText(line);
else if (linenum == 5) ui->z22->setText(line);
else if (linenum == 6) ui->z32->setText(line);
else if (linenum == 7) ui->z13->setText(line);
else if (linenum == 8) ui->z23->setText(line);
else if (linenum == 9) ui->z33->setText(line);
}
game1file.close();
}
@EDIT: I have just looked up said issue on the internet and found it is apparently because I didn't include QIoDevice:text like you'd said so I should try "<< endl;" But I found that to be no different and presented the same issue.
EDIT 2: I realized I totally forgot to actually read the lines in my loop
EDIT 3: I've looked at even more posts and concluded I need QIODevice::Text when I open the QFile so now the newline characters are inserted but still... The x's,o'x, and ".'s" are not being put in the right places
EDIT 4: Tried entering some information manually, it doesn't load data correctly either
I don't understand why QTextStream is making this so much more difficult for me x.x I remember when I only wrote console programs @#include <fstream>@ for life
You know, what would be really cool is if you could store data with the Qt api and then have it handle everything for you so you can store a variable and rather than worrying about lines and everything you can just tell it to retrieve this variable or this section of data. Yeah, thay would be great.
-
Seriously... loops ;)
bq. I don’t understand why QTextStream is making this so much more difficult for me
It really doesn't. You're overthinking it ;)
@
QString makeName(int i, int j) {
return QString("z")+QString::number(i)+QString::number(j);
}void MainWindow::save() {
QFile file("file.txt");
if(file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);for(int i=1; i<4; ++i) for(int j=1; j<4; ++j) stream << (findChild<QLineEdit*>(makeName(i,j))-> text().isEmpty() ? ".\n" : "x\n"); }
}
void MainWindow::load() {
QFile file("file.txt");
if(file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);for(int i=1; i<4; ++i) for(int j=1; j<4; ++j) findChild<QLineEdit*>(makeName(i,j))-> setText(stream.readLine() == "." ? "" : "x"); }
}
@bq. You know, what would be really cool (...) Yeah, thay would be great.
You mean "QSettings":http://doc.qt.io/qt-5/qsettings.html ? ;)
@
QSettings s("file.txt", QSettings::IniFormat);
s.setValue("value1", 42);
int foo = s.value("value1").toInt();
@