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 "character.h"
#include <QMetaEnum>
#include <QTextStream>
Character::Character() : Character::Character() :
mLevel(0), mLevel(0),
mClassType(Warrior) { mClassType(Warrior) {
} }
Character::Character(const QString &name, int level, Character::ClassType classType) : Character::Character(const QString &name,
int level,
Character::ClassType classType) :
mName(name), mName(name),
mLevel(level), mLevel(level),
mClassType(classType) mClassType(classType)
@ -95,9 +100,14 @@ void Character::setClassType(Character::ClassType classType)
//! [0] //! [0]
void Character::read(const QJsonObject &json) void Character::read(const QJsonObject &json)
{ {
if (json.contains("name") && json["name"].isString())
mName = json["name"].toString(); mName = json["name"].toString();
mLevel = json["level"].toDouble();
mClassType = ClassType(qRound(json["classType"].toDouble())); if (json.contains("level") && json["level"].isDouble())
mLevel = json["level"].toInt();
if (json.contains("classType") && json["classType"].isDouble())
mClassType = ClassType(json["classType"].toInt());
} }
//! [0] //! [0]
@ -109,3 +119,13 @@ void Character::write(QJsonObject &json) const
json["classType"] = mClassType; json["classType"] = mClassType;
} }
//! [1] //! [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 #define CHARACTER_H
#include <QJsonObject> #include <QJsonObject>
#include <QObject>
#include <QString> #include <QString>
//! [0] //! [0]
class Character class Character
{ {
Q_GADGET;
public: public:
enum ClassType { enum ClassType {
Warrior, Mage, Archer Warrior, Mage, Archer
}; };
Q_ENUM(ClassType)
Character(); Character();
Character(const QString &name, int level, ClassType classType); Character(const QString &name, int level, ClassType classType);
@ -76,6 +80,8 @@ public:
void read(const QJsonObject &json); void read(const QJsonObject &json);
void write(QJsonObject &json) const; void write(QJsonObject &json) const;
void print(int indentation = 0) const;
private: private:
QString mName; QString mName;
int mLevel; int mLevel;

View File

@ -61,8 +61,8 @@
QJsonObject argument. You can use either \l QJsonObject::operator[]() or QJsonObject argument. You can use either \l QJsonObject::operator[]() or
QJsonObject::value() to access values within the JSON object; both are QJsonObject::value() to access values within the JSON object; both are
const functions and return QJsonValue::Undefined if the key is invalid. We 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 check if the keys are valid before attempting to read them with
QJsonObject::contains(), but we assume that they are. QJsonObject::contains().
\snippet json/savegame/character.cpp 1 \snippet json/savegame/character.cpp 1
@ -77,7 +77,7 @@
\snippet json/savegame/level.h 0 \snippet json/savegame/level.h 0
We want to have several levels in our game, each with several NPCs, so we 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. write() functions.
\snippet json/savegame/level.cpp 0 \snippet json/savegame/level.cpp 0
@ -86,7 +86,7 @@
case, we construct a QJsonArray from the value associated with the key case, we construct a QJsonArray from the value associated with the key
\c "npcs". Then, for each QJsonValue element in the array, we call \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 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 \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 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 \snippet json/savegame/game.cpp 1
The first thing we do in the read() function is tell the player to read 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 itself. We then clear the level array so that calling loadGame() on the
Game object twice doesn't result in old levels hanging around. 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 \snippet json/savegame/game.cpp 2
@ -159,20 +159,23 @@
Since we're only interested in demonstrating \e serialization of a game with Since we're only interested in demonstrating \e serialization of a game with
JSON, our game is not actually playable. Therefore, we only need JSON, our game is not actually playable. Therefore, we only need
QCoreApplication and have no event loop. We create our game and assume that QCoreApplication and have no event loop. On application start-up we parse
the player had a great time and made lots of progress, altering the internal the command-line arguments to decide how to start the game. For the first
state of our Character, Level and Game objects. 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 \snippet json/savegame/main.cpp 1
When the player has finished, we save their game. For demonstration When the player has finished, we save their game. For demonstration
purposes, we serialize to both JSON and binary. You can examine the contents purposes, we can serialize to either JSON or binary. You can examine the
of the files in the same directory as the executable, although the binary contents of the files in the same directory as the executable (or re-run
save file will contain some garbage characters (which is normal). the example, making sure to also specify the "load" option), 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.
That concludes our example. As you can see, serialization with Qt's JSON That concludes our example. As you can see, serialization with Qt's JSON
classes is very simple and convenient. The advantages of using QJsonDocument classes is very simple and convenient. The advantages of using QJsonDocument

View File

@ -53,41 +53,54 @@
#include <QFile> #include <QFile>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QRandomGenerator>
#include <QTextStream>
Game::Game() Character Game::player() const
{
}
const Character &Game::player() const
{ {
return mPlayer; return mPlayer;
} }
const QList<Level> &Game::levels() const { QVector<Level> Game::levels() const
{
return mLevels; return mLevels;
} }
//! [0] //! [0]
void Game::newGame() { void Game::newGame()
{
mPlayer = Character(); mPlayer = Character();
mPlayer.setName(QStringLiteral("Hero")); mPlayer.setName(QStringLiteral("Hero"));
mPlayer.setClassType(Character::Archer); mPlayer.setClassType(Character::Archer);
mPlayer.setLevel(15); mPlayer.setLevel(QRandomGenerator::bounded(15, 21));
mLevels.clear(); mLevels.clear();
mLevels.reserve(2);
Level village; Level village(QStringLiteral("Village"));
QList<Character> villageNpcs; QVector<Character> villageNpcs;
villageNpcs.append(Character(QStringLiteral("Barry the Blacksmith"), 10, Character::Warrior)); villageNpcs.reserve(2);
villageNpcs.append(Character(QStringLiteral("Terry the Trader"), 10, Character::Warrior)); 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); village.setNpcs(villageNpcs);
mLevels.append(village); mLevels.append(village);
Level dungeon; Level dungeon(QStringLiteral("Dungeon"));
QList<Character> dungeonNpcs; QVector<Character> dungeonNpcs;
dungeonNpcs.append(Character(QStringLiteral("Eric the Evil"), 20, Character::Mage)); dungeonNpcs.reserve(3);
dungeonNpcs.append(Character(QStringLiteral("Eric's Sidekick #1"), 5, Character::Warrior)); dungeonNpcs.append(Character(QStringLiteral("Eric the Evil"),
dungeonNpcs.append(Character(QStringLiteral("Eric's Sidekick #2"), 5, Character::Warrior)); 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); dungeon.setNpcs(dungeonNpcs);
mLevels.append(dungeon); mLevels.append(dungeon);
} }
@ -113,6 +126,10 @@ bool Game::loadGame(Game::SaveFormat saveFormat)
read(loadDoc.object()); read(loadDoc.object());
QTextStream(stdout) << "Loaded save for "
<< loadDoc["player"]["name"].toString()
<< " using "
<< (saveFormat != Json ? "binary " : "") << "JSON...\n";
return true; return true;
} }
//! [3] //! [3]
@ -143,10 +160,13 @@ bool Game::saveGame(Game::SaveFormat saveFormat) const
//! [1] //! [1]
void Game::read(const QJsonObject &json) void Game::read(const QJsonObject &json)
{ {
if (json.contains("player") && json["player"].isObject())
mPlayer.read(json["player"].toObject()); mPlayer.read(json["player"].toObject());
mLevels.clear(); if (json.contains("levels") && json["levels"].isArray()) {
QJsonArray levelArray = json["levels"].toArray(); QJsonArray levelArray = json["levels"].toArray();
mLevels.clear();
mLevels.reserve(levelArray.size());
for (int levelIndex = 0; levelIndex < levelArray.size(); ++levelIndex) { for (int levelIndex = 0; levelIndex < levelArray.size(); ++levelIndex) {
QJsonObject levelObject = levelArray[levelIndex].toObject(); QJsonObject levelObject = levelArray[levelIndex].toObject();
Level level; Level level;
@ -154,6 +174,7 @@ void Game::read(const QJsonObject &json)
mLevels.append(level); mLevels.append(level);
} }
} }
}
//! [1] //! [1]
//! [2] //! [2]
@ -172,3 +193,14 @@ void Game::write(QJsonObject &json) const
json["levels"] = levelArray; json["levels"] = levelArray;
} }
//! [2] //! [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 #define GAME_H
#include <QJsonObject> #include <QJsonObject>
#include <QList> #include <QVector>
#include "character.h" #include "character.h"
#include "level.h" #include "level.h"
@ -61,14 +61,12 @@
class Game class Game
{ {
public: public:
Game();
enum SaveFormat { enum SaveFormat {
Json, Binary Json, Binary
}; };
const Character &player() const; Character player() const;
const QList<Level> &levels() const; QVector<Level> levels() const;
void newGame(); void newGame();
bool loadGame(SaveFormat saveFormat); bool loadGame(SaveFormat saveFormat);
@ -76,9 +74,11 @@ public:
void read(const QJsonObject &json); void read(const QJsonObject &json);
void write(QJsonObject &json) const; void write(QJsonObject &json) const;
void print(int indentation = 0) const;
private: private:
Character mPlayer; Character mPlayer;
QList<Level> mLevels; QVector<Level> mLevels;
}; };
//! [0] //! [0]

View File

@ -51,16 +51,23 @@
#include "level.h" #include "level.h"
#include <QJsonArray> #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; return mNpcs;
} }
void Level::setNpcs(const QList<Character> &npcs) void Level::setNpcs(const QVector<Character> &npcs)
{ {
mNpcs = npcs; mNpcs = npcs;
} }
@ -68,8 +75,13 @@ void Level::setNpcs(const QList<Character> &npcs)
//! [0] //! [0]
void Level::read(const QJsonObject &json) void Level::read(const QJsonObject &json)
{ {
mNpcs.clear(); if (json.contains("name") && json["name"].isString())
mName = json["name"].toString();
if (json.contains("npcs") && json["npcs"].isArray()) {
QJsonArray npcArray = json["npcs"].toArray(); QJsonArray npcArray = json["npcs"].toArray();
mNpcs.clear();
mNpcs.reserve(npcArray.size());
for (int npcIndex = 0; npcIndex < npcArray.size(); ++npcIndex) { for (int npcIndex = 0; npcIndex < npcArray.size(); ++npcIndex) {
QJsonObject npcObject = npcArray[npcIndex].toObject(); QJsonObject npcObject = npcArray[npcIndex].toObject();
Character npc; Character npc;
@ -77,11 +89,13 @@ void Level::read(const QJsonObject &json)
mNpcs.append(npc); mNpcs.append(npc);
} }
} }
}
//! [0] //! [0]
//! [1] //! [1]
void Level::write(QJsonObject &json) const void Level::write(QJsonObject &json) const
{ {
json["name"] = mName;
QJsonArray npcArray; QJsonArray npcArray;
foreach (const Character npc, mNpcs) { foreach (const Character npc, mNpcs) {
QJsonObject npcObject; QJsonObject npcObject;
@ -91,3 +105,13 @@ void Level::write(QJsonObject &json) const
json["npcs"] = npcArray; json["npcs"] = npcArray;
} }
//! [1] //! [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 #define LEVEL_H
#include <QJsonObject> #include <QJsonObject>
#include <QList> #include <QVector>
#include "character.h" #include "character.h"
@ -60,15 +60,21 @@
class Level class Level
{ {
public: public:
Level(); Level() = default;
Level(const QString &name);
const QList<Character> &npcs() const; QString name() const;
void setNpcs(const QList<Character> &npcs);
QVector<Character> npcs() const;
void setNpcs(const QVector<Character> &npcs);
void read(const QJsonObject &json); void read(const QJsonObject &json);
void write(QJsonObject &json) const; void write(QJsonObject &json) const;
void print(int indentation = 0) const;
private: private:
QList<Character> mNpcs; QString mName;
QVector<Character> mNpcs;
}; };
//! [0] //! [0]

View File

@ -49,30 +49,32 @@
****************************************************************************/ ****************************************************************************/
#include <QCoreApplication> #include <QCoreApplication>
#include <QTextStream>
#include "game.h" #include "game.h"
//! [0] //! [0]
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QCoreApplication app(argc, 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 game;
if (newGame)
game.newGame(); game.newGame();
else if (!game.loadGame(json ? Game::Json : Game::Binary))
return 1;
// Game is played; changes are made... // Game is played; changes are made...
//! [0] //! [0]
//! [1] //! [1]
if (!game.saveGame(Game::Json)) QTextStream(stdout) << "Game ended in the following state:\n";
return 1; game.print();
if (!game.saveGame(json ? Game::Json : Game::Binary))
if (!game.saveGame(Game::Binary))
return 1;
Game fromJsonGame;
if (!fromJsonGame.loadGame(Game::Json))
return 1;
Game fromBinaryGame;
if (!fromBinaryGame.loadGame(Game::Binary))
return 1; return 1;
return 0; return 0;