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:
parent
29ef0d2bcc
commit
1ff52e478b
@ -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";
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user