forked from miki151/keeperrl
/
highscores.cpp
183 lines (161 loc) · 5.63 KB
/
highscores.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#include "stdafx.h"
#include "highscores.h"
#include "view.h"
#include "file_sharing.h"
#include "options.h"
#include "campaign_type.h"
#include "player_role.h"
#include "parse_game.h"
const static int highscoreVersion = 2;
Highscores::Highscores(const FilePath& local, FileSharing& sharing, Options* o)
: localPath(local), fileSharing(sharing), options(o) {
localScores = fromFile(localPath);
remoteScores = fromString(fileSharing.downloadHighscores(highscoreVersion));
fileSharing.uploadHighscores(localPath);
}
void Highscores::add(Score s) {
s.version = highscoreVersion;
localScores.push_back(s);
saveToFile(localScores, localPath);
remoteScores.push_back(s);
fileSharing.uploadHighscores(localPath);
}
vector<Highscores::Score> Highscores::fromStream(istream& in) {
vector<Score> ret;
while (1) {
char buf[1000];
in.getline(buf, 1000);
if (!in)
break;
if (auto score = Score::parse(buf))
if (score->version == highscoreVersion)
ret.push_back(*score);
}
return ret;
}
void Highscores::saveToFile(const vector<Score>& scores, const FilePath& path) {
CompressedOutput out(path.getPath());
out.getArchive() << scores;
}
bool Highscores::Score::operator == (const Score& s) const {
return gameId == s.gameId && playerName == s.playerName && worldName == s.worldName && gameResult == s.gameResult
&& points == s.points && turns == s.turns;
}
vector<Highscores::Score> Highscores::fromFile(const FilePath& path) {
vector<Highscores::Score> scores;
try {
CompressedInput in(path.getPath());
in.getArchive() >> scores;
} catch (...) {}
return scores.filter([](const Score& s) { return s.version == highscoreVersion;});
}
vector<Highscores::Score> Highscores::fromString(const string& s) {
stringstream in(s);
return fromStream(in);
}
optional<Highscores::Score> Highscores::Score::parse(const string& buf) {
vector<string> p = split(buf, {','});
if (p.size() == 10)
try {
return CONSTRUCT(Score,
c.gameId = p[0];
c.playerName = p[1];
c.worldName = p[2];
c.gameResult = p[3];
c.gameWon = ::fromString<int>(p[4]);
c.points = ::fromString<int>(p[5]);
c.turns = ::fromString<int>(p[6]);
c.campaignType = EnumInfo<CampaignType>::fromStringWithException(p[7]);
c.playerRole = EnumInfo<PlayerRole>::fromStringWithException(p[8]);
c.version = ::fromString<int>(p[9]);
);
} catch (ParsingException&) {}
#ifndef RELEASE
return CONSTRUCT(Score, c.playerName = "ERROR: " + buf;);
#else
return none;
#endif
}
bool sortByShortestMostPoints(const Highscores::Score& a, const Highscores::Score& b) {
if (a.gameWon) {
if (!b.gameWon)
return true;
else
return a.turns < b.turns;
} else
if (b.gameWon)
return false;
else
return a.points > b.points || (a.points == b.points && a.gameId < b.gameId);
}
bool sortByLongest(const Highscores::Score& a, const Highscores::Score& b) {
return a.turns > b.turns;
}
enum class SortingType {
SHORTEST_MOST_POINTS,
LONGEST
};
typedef Highscores::Score Score;
typedef bool (*SortingFun)(const Score&, const Score&);
SortingFun getSortingFun(SortingType type) {
switch (type) {
case SortingType::SHORTEST_MOST_POINTS:
return sortByShortestMostPoints;
case SortingType::LONGEST:
return sortByLongest;
}
}
string getPointsColumn(const Score& score, SortingType sorting) {
switch (sorting) {
case SortingType::SHORTEST_MOST_POINTS:
return score.gameWon ? toString(score.turns) + " turns" : toString(score.points) + " points";
case SortingType::LONGEST:
return toString(score.turns) + " turns";
}
}
static HighscoreList fillScores(const string& name, optional<Score> lastElem, vector<Score> scores, SortingType sorting) {
HighscoreList list { name, {} };
sort(scores.begin(), scores.end(), getSortingFun(sorting));
for (auto& score : scores) {
bool highlight = lastElem == score;
string points = getPointsColumn(score, sorting);
if (score.gameResult.empty())
list.scores.push_back({score.playerName + " of " + score.worldName, points, highlight});
else
list.scores.push_back({score.playerName + " of " + score.worldName +
", " + score.gameResult, points, highlight});
}
return list;
}
struct PublicScorePage {
CampaignType type;
PlayerRole role;
const char* name;
SortingType sorting;
};
static vector<PublicScorePage> getPublicScores() {
return {
{CampaignType::CAMPAIGN, PlayerRole::KEEPER, "Keeper", SortingType::SHORTEST_MOST_POINTS},
{CampaignType::CAMPAIGN, PlayerRole::ADVENTURER, "Adventurer", SortingType::SHORTEST_MOST_POINTS},
{CampaignType::SINGLE_KEEPER, PlayerRole::KEEPER, "Single map", SortingType::SHORTEST_MOST_POINTS},
{CampaignType::ENDLESS, PlayerRole::KEEPER, "Endless", SortingType::LONGEST},
};
}
bool Score::isPublic() const {
for (auto& elem : getPublicScores())
if (elem.type == campaignType && elem.role == playerRole)
return true;
return false;
}
void Highscores::present(View* view, optional<Score> lastAdded) const {
if (lastAdded && !lastAdded->isPublic())
return;
vector<HighscoreList> lists;
for (auto& elem : getPublicScores())
lists.push_back(fillScores(elem.name, lastAdded, localScores.filter(
[&] (const Score& s) { return s.campaignType == elem.type && s.playerRole == elem.role;}), elem.sorting));
for (auto& elem : getPublicScores())
lists.push_back(fillScores(elem.name, lastAdded, remoteScores.filter(
[&] (const Score& s) { return s.campaignType == elem.type && s.playerRole == elem.role;}), elem.sorting));
view->presentHighscores(lists);
}