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(" &nbsp;%1%2").arg(scoresign[i]).arg(result[i]);
201     }
202     if(result[ResultUnknown])
203     {
204         score += QString(" &nbsp;*%1").arg(result[ResultUnknown]);
205     }
206     if(count - result[ResultUnknown])
207         score += QString(" &nbsp;(%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 += " &nbsp;";
225     score += format.arg(ref, "1/2-1/2").arg(scoresign[Draw]).arg(result[Draw]);
226     score += " &nbsp;";
227     score += format.arg(ref, (!mode ? "1-0":"0-1")).arg(scoresign[BlackWin]).arg(result[BlackWin]);
228     if(result[ResultUnknown])
229     {
230         score += " &nbsp;";
231         score += format.arg(ref, "\\*").arg(scoresign[ResultUnknown]).arg(result[ResultUnknown]);
232     }
233     if(count - result[ResultUnknown])
234         score += QString(" &nbsp;(%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'>&#8681;</a>&nbsp;<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'>&#8679;</a>&nbsp;<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