1 /***************************************************************************
2 * (C) 2007-2009 Michal Rudolf <mrudolf@kdewebdev.org> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 ***************************************************************************/
9 #include <algorithm>
10 #include <QCoreApplication>
11 #include <QHash>
12
13 #include "ecopositions.h"
14 #include "database.h"
15 #include "playerinfo.h"
16 #include "tags.h"
17
18 #if defined(_MSC_VER) && defined(_DEBUG)
19 #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
20 #define new DEBUG_NEW
21 #endif // _MSC_VER
22
sortEcoFrequencyLt(const EcoFrequencyItem & left,const EcoFrequencyItem & right)23 static bool sortEcoFrequencyLt(const EcoFrequencyItem& left, const EcoFrequencyItem& right)
24 {
25 if(left.second.count == right.second.count)
26 {
27 return (left.first < right.first);
28 }
29 return left.second.count > right.second.count;
30 }
31
PlayerInfo()32 PlayerInfo::PlayerInfo()
33 {
34 m_database = nullptr;
35 reset();
36 }
37
~PlayerInfo()38 PlayerInfo::~PlayerInfo()
39 {
40 }
41
PlayerInfo(Database * db,const QString & player)42 PlayerInfo::PlayerInfo(Database* db, const QString & player)
43 {
44 setDatabase(db);
45 setName(player);
46 reset();
47 update();
48 }
49
name() const50 QString PlayerInfo::name() const
51 {
52 return m_name;
53 }
54
setDatabase(Database * db)55 void PlayerInfo::setDatabase(Database* db)
56 {
57 m_database = db;
58 }
59
setName(const QString & player)60 void PlayerInfo::setName(const QString& player)
61 {
62 m_name = player;
63 update();
64 }
65
toResult(const QString & res) const66 int PlayerInfo::toResult(const QString& res) const
67 {
68 if(res.startsWith("1/2"))
69 {
70 return Draw;
71 }
72 else if(res.startsWith('1'))
73 {
74 return WhiteWin;
75 }
76 else if(res.startsWith('0'))
77 {
78 return BlackWin;
79 }
80 else
81 {
82 return ResultUnknown;
83 }
84 }
85
update()86 void PlayerInfo::update()
87 {
88 QHash<QString, EcoFrequencyInfo> openings[2];
89 QHash<QString, int> openingsX[2];
90 const IndexX* index = m_database->index();
91
92 // Determine matching tag values
93 ValueIndex player = index->getValueIndex(m_name);
94
95 // Clean previous statistics
96 reset();
97
98 for(int i = 0; i < (int)m_database->count(); ++i)
99 {
100 Color c;
101 if(index->valueIndexFromTag(TagNameWhite, i) == player)
102 {
103 c = White;
104 }
105 else if(index->valueIndexFromTag(TagNameBlack, i) == player)
106 {
107 c = Black;
108 }
109 else
110 {
111 continue;
112 }
113 int res = toResult(index->tagValue(TagNameResult, i));
114 m_result[c][res]++;
115 m_count[c]++;
116 int elo = index->tagValue(c == White ? TagNameWhiteElo : TagNameBlackElo, i).toInt();
117 if(elo)
118 {
119 m_rating[0] = qMin(elo, m_rating[0]);
120 m_rating[1] = qMax(elo, m_rating[1]);
121 }
122 PartialDate date(index->tagValue(TagNameDate, i));
123 if(date.year() > 1000)
124 {
125 m_date[0] = qMin(date, m_date[0]);
126 m_date[1] = qMax(date, m_date[1]);
127 }
128 QString eco = index->tagValue(TagNameECO, i).left(3);
129 if(eco.length() == 3)
130 {
131 openings[c][eco].count++;
132 openings[c][eco].result[res]++;
133 }
134 QString ecoX = index->tagValue(TagNameECO, i).left(4);
135 if(ecoX.length() >= 3)
136 {
137 openingsX[c][ecoX]++;
138 }
139 }
140
141 for(int i = 0; i < 2; ++i)
142 {
143 for(auto it = openings[i].cbegin(); it != openings[i].cend(); ++it)
144 {
145 m_eco[i].append(EcoFrequencyItem(it.key(), it.value()));
146 }
147 std::sort(m_eco[i].begin(), m_eco[i].end(), sortEcoFrequencyLt);
148 }
149
150 for(int i = 0; i < 2; ++i)
151 {
152 auto& counts = m_opening[i];
153 for (auto it = openingsX[i].cbegin(); it != openingsX[i].cend(); ++it)
154 {
155 const auto& eco = it.key();
156 counts.append(OpeningCountItem(eco, it.value()));
157
158 QString opening = EcoPositions::findEcoName(eco);
159 m_MapOpeningToECOCodes[i][opening].append(eco);
160 }
161 // comparison function for sorting m_opening[c]
162 auto cmp = [](const OpeningCountItem& lhs, const OpeningCountItem& rhs) {
163 // first sort by descending frequency
164 if (lhs.second != rhs.second)
165 return lhs.second > rhs.second;
166 // then sort by ECO name alphabetically
167 return lhs.first < rhs.first;
168 };
169 std::sort(counts.begin(), counts.end(), cmp);
170 }
171
172 qSwap(m_result[Black][WhiteWin], m_result[Black][BlackWin]);
173 }
174
formattedScore() const175 QString PlayerInfo::formattedScore() const
176 {
177 int total[4];
178 for(int i = 0; i < 4; ++i)
179 {
180 total[i] = m_result[White][i] + m_result[Black][i];
181 }
182 int count = m_count[White] + m_count[Black];
183 QString ref1 = QString("<a href='result-white:%1#").arg(m_name);
184 QString ref2 = QString("<a href='result-black:%1#").arg(m_name);
185 return QCoreApplication::translate("PlayerInfo", "Total: %1<br>White: %2<br>Black: %3<br>")
186 .arg(formattedScore(total, count), formattedScore(m_result[White], m_count[White], ref1, true), formattedScore(m_result[Black], m_count[Black], ref2, false));
187 }
188
formattedScore(const int result[4],int count) const189 QString PlayerInfo::formattedScore(const int result[4], int count) const
190 {
191 if(!count)
192 {
193 return QCoreApplication::translate("PlayerInfo", "<i>no games</i>");
194 }
195 QString score = "<b>";
196 QChar scoresign[4] = {'*', '+', '=', '-'};
197 score += QString("%1%2").arg(scoresign[WhiteWin]).arg(result[WhiteWin]);
198 for(int i = Draw; i <= BlackWin; ++i)
199 {
200 score += QString(" %1%2").arg(scoresign[i]).arg(result[i]);
201 }
202 if(result[ResultUnknown])
203 {
204 score += QString(" *%1").arg(result[ResultUnknown]);
205 }
206 if(count - result[ResultUnknown])
207 score += QString(" (%1%)").
208 arg((100.0 * result[WhiteWin] + 50.0 * result[Draw]) / (count - result[ResultUnknown]), 1, 'f', 1);
209 score += "</b>";
210 return score;
211 }
212
formattedScore(const int result[4],int count,QString ref,bool mode) const213 QString PlayerInfo::formattedScore(const int result[4], int count, QString ref, bool mode) const
214 {
215 if(!count)
216 {
217 return QCoreApplication::translate("PlayerInfo", "<i>no games</i>");
218 }
219 QString score = "<b>";
220 QChar scoresign[4] = {'*', '+', '=', '-'};
221 QString format = "%1%2'>%3%4</a>";
222 QStringList modes;
223 score += format.arg(ref, (mode ? "1-0":"0-1")).arg(scoresign[WhiteWin]).arg(result[WhiteWin]);
224 score += " ";
225 score += format.arg(ref, "1/2-1/2").arg(scoresign[Draw]).arg(result[Draw]);
226 score += " ";
227 score += format.arg(ref, (!mode ? "1-0":"0-1")).arg(scoresign[BlackWin]).arg(result[BlackWin]);
228 if(result[ResultUnknown])
229 {
230 score += " ";
231 score += format.arg(ref, "\\*").arg(scoresign[ResultUnknown]).arg(result[ResultUnknown]);
232 }
233 if(count - result[ResultUnknown])
234 score += QString(" (%1%)").
235 arg((100.0 * result[WhiteWin] + 50.0 * result[Draw]) / (count - result[ResultUnknown]), 1, 'f', 1);
236 score += "</b>";
237 return score;
238 }
239
reset()240 void PlayerInfo::reset()
241 {
242 for(int c = White; c <= Black; ++c)
243 {
244 for(int r = 0; r < 4; ++r)
245 {
246 m_result[c][r] = 0;
247 }
248 m_count[c] = 0;
249 m_eco[c].clear();
250 m_opening[c].clear();
251 m_MapOpeningToECOCodes[c].clear();
252 }
253 m_rating[0] = 99999;
254 m_rating[1] = 0;
255 m_date[0] = PDMaxDate;
256 m_date[1] = PDMinDate;
257 }
258
formattedGameCount() const259 QString PlayerInfo::formattedGameCount() const
260 {
261 return QCoreApplication::translate("PlayerInfo", "Games in database <i>%1</i>: <b>%2</b><br>")
262 .arg(m_database->name()).arg(m_count[White] + m_count[Black]);
263 }
264
formattedRating() const265 QString PlayerInfo::formattedRating() const
266 {
267 if(!m_rating[1])
268 {
269 return QString();
270 }
271 else if(m_rating[0] == m_rating[1])
272 {
273 return QCoreApplication::translate("PlayerInfo", "Rating: <b>%1</b><br>").arg(m_rating[0]);
274 }
275 else
276 return QCoreApplication::translate("PlayerInfo", "Rating: <b>%1-%2</b><br>")
277 .arg(m_rating[0]).arg(m_rating[1]);
278 }
279
formattedRange() const280 QString PlayerInfo::formattedRange() const
281 {
282 if(m_date[0].year() == 9999) // No date
283 {
284 return QCoreApplication::translate("PlayerInfo", "Date: <b>????.??.\?\?<b><br>");
285 }
286 else if(m_date[0].year() < 1000)
287 {
288 return QString();
289 }
290 else
291 {
292 return QCoreApplication::translate("PlayerInfo", "Date: <b>%1</b><br>").arg(m_date[0].range(m_date[1]));
293 }
294 }
295
listOfOpenings() const296 QString PlayerInfo::listOfOpenings() const
297 {
298 QStringList openingsList;
299 openingsList.append(QString("<a name='ListWhite'></a><p><a href='#ListBlack'>⇩</a> <a href='player:%1#%2'>%3:</a></p>").arg(m_name, TagNameWhite, tr("White Openings")));
300 openingsList.append(QString("<a name='ListBlack'></a><p><a href='#ListWhite'>⇧</a> <a href='player:%1#%2'>%3:</a></p>").arg(m_name, TagNameBlack, tr("Black Openings")));
301 openingsList.append("");
302
303 for(int i = 0; i < 2; ++i)
304 {
305 QStringList l;
306 for(OpeningCountList::const_iterator it = m_opening[i].constBegin(); it != m_opening[i].constEnd(); ++it)
307 {
308 QString opening = (*it).first;
309 QString codes = m_MapOpeningToECOCodes[i].value(opening).join("|");
310 if (((*it).second)==1) break; // leave out things played only once
311 if (((*it).second)*25<m_count[i]) break; // leave out things played rarely
312 l.append(QString("<a href='eco-%1:%2'>%3</a> (%4)")
313 .arg((i == 0 ? "white" : "black"), codes, opening)
314 .arg((*it).second));
315 }
316 openingsList[i] += l.join(", ");
317 }
318
319 for(int i = 0; i < 2; ++i)
320 {
321 openingsList[i].append("<ul>");
322 for(EcoFrequency::const_iterator it = m_eco[i].constBegin(); it != m_eco[i].constEnd(); ++it)
323 {
324 QString score;
325 int count = (*it).second.count;
326 int resultUnknown = (*it).second.result[ResultUnknown];
327 int cResult = (*it).second.result[i == 0 ? WhiteWin : BlackWin];
328 int dResult = (*it).second.result[Draw];
329 if(count - resultUnknown)
330 {
331 score = QString(" (%1%)").arg((100.0 * cResult + 50.0 * dResult) / (count - resultUnknown), 1, 'f', 1);
332 }
333
334 openingsList[i] += QString("<li><a href='eco-%1:%2'>%3</a>: %4%5")
335 .arg((i == 0 ? "white" : "black"), (*it).first, (*it).first)
336 .arg(count)
337 .arg(score);
338 }
339 }
340
341 QString s = openingsList.join("</ul>");
342 return s;
343 }
344