Modernize the json savegame example

Task-number: QTBUG-60625
Change-Id: I8d5bf860478ee2566b9f96854fc6491f088a28fa
Reviewed-by: Topi Reiniö <topi.reinio@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Mårten Nordheim 2017-08-22 09:52:55 +02:00
parent 29ef0d2bcc
commit 1ff52e478b
8 changed files with 173 additions and 80 deletions

View File

@ -50,12 +50,17 @@
#include "character.h"
#include <QMetaEnum>
#include <QTextStream>
Character::Character() :
mLevel(0),
mClassType(Warrior) {
}
Character::Character(const QString &name, int level, Character::ClassType classType) :
Character::Character(const QString &name,
int level,
Character::ClassType classType) :
mName(name),
mLevel(level),
mClassType(classType)
@ -95,9 +100,14 @@ void Character::setClassType(Character::ClassType classType)
//! [0]
void Character::read(const QJsonObject &json)
{
mName = json["name"].toString();
mLevel = json["level"].toDouble();
mClassType = ClassType(qRound(json["classType"].toDouble()));
if (json.contains("name") && json["name"].isString())
mName = json["name"].toString();
if (json.contains("level") && json["level"].isDouble())
mLevel = json["level"].toInt();
if (json.contains("classType") && json["classType"].isDouble())
mClassType = ClassType(json["classType"].toInt());
}
//! [0]
@ -109,3 +119,13 @@ void Character::write(QJsonObject &json) const
json["classType"] = mClassType;
}
//! [1]
void Character::print(int indentation) const
{
const QString indent(indentation * 2, ' ');
QTextStream(stdout) << indent << "Name:\t" << mName << "\n";
QTextStream(stdout) << indent << "Level:\t" << mLevel << "\n";
QString className = QMetaEnum::fromType<ClassType>().valueToKey(mClassType);
QTextStream(stdout) << indent << "Class:\t" << className << "\n";
}

View File

@ -52,15 +52,19 @@
#define CHARACTER_H
#include <QJsonObject>
#include <QObject>
#include <QString>
//! [0]
class Character
{
Q_GADGET;
public:
enum ClassType {
Warrior, Mage, Archer
};
Q_ENUM(ClassType)
Character();
Character(const QString &name, int level, ClassType classType);
@ -76,6 +80,8 @@ public:
void read(const QJsonObject &json);
void write(QJsonObject &json) const;
void print(int indentation = 0) const;
private:
QString mName;
int mLevel;

View File

@ -61,8 +61,8 @@
QJsonObject argument. You can use either \l QJsonObject::operator[]() or
QJsonObject::value() to access values within the JSON object; both are
const functions and return QJsonValue::Undefined if the key is invalid. We
could check if the keys are valid before attempting to read them with
QJsonObject::contains(), but we assume that they are.
check if the keys are valid before attempting to read them with
QJsonObject::contains().
\snippet json/savegame/character.cpp 1
@ -77,7 +77,7 @@
\snippet json/savegame/level.h 0
We want to have several levels in our game, each with several NPCs, so we
keep a QList of Character objects. We also provide the familiar read() and
keep a QVector of Character objects. We also provide the familiar read() and
write() functions.
\snippet json/savegame/level.cpp 0
@ -86,7 +86,7 @@
case, we construct a QJsonArray from the value associated with the key
\c "npcs". Then, for each QJsonValue element in the array, we call
toObject() to get the Character's JSON object. The Character object can then
read their JSON and be appended to our NPC list.
read their JSON and be appended to our NPC array.
\note \l{Container Classes}{Associate containers} can be written by storing
the key in each value object (if it's not already). With this approach, the
@ -120,10 +120,10 @@
\snippet json/savegame/game.cpp 1
The first thing we do in the read() function is tell the player to read
itself. We then clear the levels list so that calling loadGame() on the same
Game object twice doesn't result in old levels hanging around.
itself. We then clear the level array so that calling loadGame() on the
same Game object twice doesn't result in old levels hanging around.
We then populate the level list by reading each Level from a QJsonArray.
We then populate the level array by reading each Level from a QJsonArray.
\snippet json/savegame/game.cpp 2
@ -159,20 +159,23 @@
Since we're only interested in demonstrating \e serialization of a game with
JSON, our game is not actually playable. Therefore, we only need
QCoreApplication and have no event loop. We create our game and assume that
the player had a great time and made lots of progress, altering the internal
state of our Character, Level and Game objects.
QCoreApplication and have no event loop. On application start-up we parse
the command-line arguments to decide how to start the game. For the first
argument the options "new" (default) and "load" are available. When "new"
is specified a new game will be generated, and when "load" is specified a
previously saved game will be loaded in. For the second argument
"json" (default) and "binary" are available as options. This argument will
decide which file is saved to and/or loaded from. We then move ahead and
assume that the player had a great time and made lots of progress, altering
the internal state of our Character, Level and Game objects.
\snippet json/savegame/main.cpp 1
When the player has finished, we save their game. For demonstration
purposes, we serialize to both JSON and binary. You can examine the contents
of the files in the same directory as the executable, although the binary
save file will contain some garbage characters (which is normal).
To show that the saved files can be loaded again, we call loadGame() for
each format, returning \c 1 on failure. Assuming everything went well, we
return \c 0 to indicate success.
purposes, we can serialize to either JSON or binary. You can examine the
contents of the files in the same directory as the executable (or re-run
the example, making sure to also specify the "load" option), although the
binary save file will contain some garbage characters (which is normal).
That concludes our example. As you can see, serialization with Qt's JSON
classes is very simple and convenient. The advantages of using QJsonDocument

View File

@ -53,41 +53,54 @@
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QRandomGenerator>
#include <QTextStream>
Game::Game()
{
}
const Character &Game::player() const
Character Game::player() const
{
return mPlayer;
}
const QList<Level> &Game::levels() const {
QVector<Level> Game::levels() const
{
return mLevels;
}
//! [0]
void Game::newGame() {
void Game::newGame()
{
mPlayer = Character();
mPlayer.setName(QStringLiteral("Hero"));
mPlayer.setClassType(Character::Archer);
mPlayer.setLevel(15);
mPlayer.setLevel(QRandomGenerator::bounded(15, 21));
mLevels.clear();
mLevels.reserve(2);
Level village;
QList<Character> villageNpcs;
villageNpcs.append(Character(QStringLiteral("Barry the Blacksmith"), 10, Character::Warrior));
villageNpcs.append(Character(QStringLiteral("Terry the Trader"), 10, Character::Warrior));
Level village(QStringLiteral("Village"));
QVector<Character> villageNpcs;
villageNpcs.reserve(2);
villageNpcs.append(Character(QStringLiteral("Barry the Blacksmith"),
QRandomGenerator::bounded(8, 11),
Character::Warrior));
villageNpcs.append(Character(QStringLiteral("Terry the Trader"),
QRandomGenerator::bounded(6, 8),
Character::Warrior));
village.setNpcs(villageNpcs);
mLevels.append(village);
Level dungeon;
QList<Character> dungeonNpcs;
dungeonNpcs.append(Character(QStringLiteral("Eric the Evil"), 20, Character::Mage));
dungeonNpcs.append(Character(QStringLiteral("Eric's Sidekick #1"), 5, Character::Warrior));
dungeonNpcs.append(Character(QStringLiteral("Eric's Sidekick #2"), 5, Character::Warrior));
Level dungeon(QStringLiteral("Dungeon"));
QVector<Character> dungeonNpcs;
dungeonNpcs.reserve(3);
dungeonNpcs.append(Character(QStringLiteral("Eric the Evil"),
QRandomGenerator::bounded(18, 26),
Character::Mage));
dungeonNpcs.append(Character(QStringLiteral("Eric's Left Minion"),
QRandomGenerator::bounded(5, 7),
Character::Warrior));
dungeonNpcs.append(Character(QStringLiteral("Eric's Right Minion"),
QRandomGenerator::bounded(4, 9),
Character::Warrior));
dungeon.setNpcs(dungeonNpcs);
mLevels.append(dungeon);
}
@ -113,6 +126,10 @@ bool Game::loadGame(Game::SaveFormat saveFormat)
read(loadDoc.object());
QTextStream(stdout) << "Loaded save for "
<< loadDoc["player"]["name"].toString()
<< " using "
<< (saveFormat != Json ? "binary " : "") << "JSON...\n";
return true;
}
//! [3]
@ -143,15 +160,19 @@ bool Game::saveGame(Game::SaveFormat saveFormat) const
//! [1]
void Game::read(const QJsonObject &json)
{
mPlayer.read(json["player"].toObject());
if (json.contains("player") && json["player"].isObject())
mPlayer.read(json["player"].toObject());
mLevels.clear();
QJsonArray levelArray = json["levels"].toArray();
for (int levelIndex = 0; levelIndex < levelArray.size(); ++levelIndex) {
QJsonObject levelObject = levelArray[levelIndex].toObject();
Level level;
level.read(levelObject);
mLevels.append(level);
if (json.contains("levels") && json["levels"].isArray()) {
QJsonArray levelArray = json["levels"].toArray();
mLevels.clear();
mLevels.reserve(levelArray.size());
for (int levelIndex = 0; levelIndex < levelArray.size(); ++levelIndex) {
QJsonObject levelObject = levelArray[levelIndex].toObject();
Level level;
level.read(levelObject);
mLevels.append(level);
}
}
}
//! [1]
@ -172,3 +193,14 @@ void Game::write(QJsonObject &json) const
json["levels"] = levelArray;
}
//! [2]
void Game::print(int indentation) const
{
const QString indent(indentation * 2, ' ');
QTextStream(stdout) << indent << "Player\n";
mPlayer.print(indentation + 1);
QTextStream(stdout) << indent << "Levels\n";
for (Level level : mLevels)
level.print(indentation + 1);
}

View File

@ -52,7 +52,7 @@
#define GAME_H
#include <QJsonObject>
#include <QList>
#include <QVector>
#include "character.h"
#include "level.h"
@ -61,14 +61,12 @@
class Game
{
public:
Game();
enum SaveFormat {
Json, Binary
};
const Character &player() const;
const QList<Level> &levels() const;
Character player() const;
QVector<Level> levels() const;
void newGame();
bool loadGame(SaveFormat saveFormat);
@ -76,9 +74,11 @@ public:
void read(const QJsonObject &json);
void write(QJsonObject &json) const;
void print(int indentation = 0) const;
private:
Character mPlayer;
QList<Level> mLevels;
QVector<Level> mLevels;
};
//! [0]

View File

@ -51,16 +51,23 @@
#include "level.h"
#include <QJsonArray>
#include <QTextStream>
Level::Level() {
Level::Level(const QString &name) : mName(name)
{
}
const QList<Character> &Level::npcs() const
QString Level::name() const
{
return mName;
}
QVector<Character> Level::npcs() const
{
return mNpcs;
}
void Level::setNpcs(const QList<Character> &npcs)
void Level::setNpcs(const QVector<Character> &npcs)
{
mNpcs = npcs;
}
@ -68,13 +75,19 @@ void Level::setNpcs(const QList<Character> &npcs)
//! [0]
void Level::read(const QJsonObject &json)
{
mNpcs.clear();
QJsonArray npcArray = json["npcs"].toArray();
for (int npcIndex = 0; npcIndex < npcArray.size(); ++npcIndex) {
QJsonObject npcObject = npcArray[npcIndex].toObject();
Character npc;
npc.read(npcObject);
mNpcs.append(npc);
if (json.contains("name") && json["name"].isString())
mName = json["name"].toString();
if (json.contains("npcs") && json["npcs"].isArray()) {
QJsonArray npcArray = json["npcs"].toArray();
mNpcs.clear();
mNpcs.reserve(npcArray.size());
for (int npcIndex = 0; npcIndex < npcArray.size(); ++npcIndex) {
QJsonObject npcObject = npcArray[npcIndex].toObject();
Character npc;
npc.read(npcObject);
mNpcs.append(npc);
}
}
}
//! [0]
@ -82,6 +95,7 @@ void Level::read(const QJsonObject &json)
//! [1]
void Level::write(QJsonObject &json) const
{
json["name"] = mName;
QJsonArray npcArray;
foreach (const Character npc, mNpcs) {
QJsonObject npcObject;
@ -91,3 +105,13 @@ void Level::write(QJsonObject &json) const
json["npcs"] = npcArray;
}
//! [1]
void Level::print(int indentation) const
{
const QString indent(indentation * 2, ' ');
QTextStream(stdout) << indent << "Name:\t" << mName << "\n";
QTextStream(stdout) << indent << "NPCs:\n";
for (const Character &character : mNpcs)
character.print(2);
}

View File

@ -52,7 +52,7 @@
#define LEVEL_H
#include <QJsonObject>
#include <QList>
#include <QVector>
#include "character.h"
@ -60,15 +60,21 @@
class Level
{
public:
Level();
Level() = default;
Level(const QString &name);
const QList<Character> &npcs() const;
void setNpcs(const QList<Character> &npcs);
QString name() const;
QVector<Character> npcs() const;
void setNpcs(const QVector<Character> &npcs);
void read(const QJsonObject &json);
void write(QJsonObject &json) const;
void print(int indentation = 0) const;
private:
QList<Character> mNpcs;
QString mName;
QVector<Character> mNpcs;
};
//! [0]

View File

@ -49,30 +49,32 @@
****************************************************************************/
#include <QCoreApplication>
#include <QTextStream>
#include "game.h"
//! [0]
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = QCoreApplication::arguments();
bool newGame = true;
if (args.length() > 1)
newGame = (args[1].toLower() != QStringLiteral("load"));
bool json = true;
if (args.length() > 2)
json = (args[2].toLower() != QStringLiteral("binary"));
Game game;
game.newGame();
if (newGame)
game.newGame();
else if (!game.loadGame(json ? Game::Json : Game::Binary))
return 1;
// Game is played; changes are made...
//! [0]
//! [1]
if (!game.saveGame(Game::Json))
return 1;
if (!game.saveGame(Game::Binary))
return 1;
Game fromJsonGame;
if (!fromJsonGame.loadGame(Game::Json))
return 1;
Game fromBinaryGame;
if (!fromBinaryGame.loadGame(Game::Binary))
QTextStream(stdout) << "Game ended in the following state:\n";
game.print();
if (!game.saveGame(json ? Game::Json : Game::Binary))
return 1;
return 0;