1 /****************************************************************************
2 *   Copyright (C) 2012 by Jens Nissen jens-chessx@gmx.net                   *
3 ****************************************************************************/
4 
5 #include <QCoreApplication>
6 #include <QHash>
7 
8 #include "ecoinfo.h"
9 #include "database.h"
10 #include "tags.h"
11 
12 #if defined(_MSC_VER) && defined(_DEBUG)
13 #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
14 #define new DEBUG_NEW
15 #endif // _MSC_VER
16 
sortPlayersLt(const PlayerInfoListItem & a1,const PlayerInfoListItem & a2)17 static bool sortPlayersLt(const PlayerInfoListItem& a1, const PlayerInfoListItem& a2)
18 {
19     if(a1.second == a2.second)
20     {
21         return (a1.first < a2.first);
22     }
23     return a1.second > a2.second;
24 }
25 
EcoInfo()26 EcoInfo::EcoInfo()
27 {
28     m_database = nullptr;
29     reset();
30 }
31 
~EcoInfo()32 EcoInfo::~EcoInfo()
33 {
34 }
35 
EcoInfo(Database * db,const QString & eco)36 EcoInfo::EcoInfo(Database* db, const QString & eco)
37 {
38     setDatabase(db);
39     setCode(eco);
40     reset();
41     update();
42 }
43 
name() const44 QString EcoInfo::name() const
45 {
46     return m_code;
47 }
48 
setDatabase(Database * db)49 void EcoInfo::setDatabase(Database* db)
50 {
51     m_database = db;
52 }
53 
setCode(const QString & eco)54 void EcoInfo::setCode(const QString& eco)
55 {
56     m_code = eco;
57     update();
58 }
59 
toResult(const QString & res) const60 int EcoInfo::toResult(const QString& res) const
61 {
62     if(res.startsWith("1/2"))
63     {
64         return Draw;
65     }
66     else if(res.startsWith('1'))
67     {
68         return WhiteWin;
69     }
70     else if(res.startsWith('0'))
71     {
72         return BlackWin;
73     }
74     else
75     {
76         return ResultUnknown;
77     }
78 }
79 
toPoints(const QString & res) const80 float EcoInfo::toPoints(const QString& res) const
81 {
82     if(res.startsWith("1/2"))
83     {
84         return 0.5;
85     }
86     else if(res.startsWith('1'))
87     {
88         return 1.0;
89     }
90     else if(res.startsWith('0'))
91     {
92         return 0.0;
93     }
94     else
95     {
96         return -1.0;
97     }
98 }
99 
update()100 void EcoInfo::update()
101 {
102     QHash<QString, float> playersWhite;
103     QHash<QString, float> playersBlack;
104 
105     const IndexX* index = m_database->index();
106 
107     // Determine matching tag values
108     ValueIndex eco = index->getValueIndex(m_code);
109 
110     // Clean previous statistics
111     reset();
112 
113     for(int i = 0; i < (int) m_database->count(); ++i)
114     {
115         if(index->valueIndexFromTag(TagNameECO, i) != eco)
116         {
117             continue;
118         }
119         QString result = index->tagValue(TagNameResult, i);
120         int res = toResult(result);
121         QString whitePlayer = index->tagValue(TagNameWhite, i);
122         QString blackPlayer = index->tagValue(TagNameBlack, i);
123         // The following works as QHash initializes a default-constructed value to 0
124         float fres = toPoints(result);
125         if(fres >= 0)
126         {
127             playersWhite[whitePlayer] += fres;
128             playersBlack[blackPlayer] += (1.0 - fres);
129             m_gamesWhite[whitePlayer]++;
130             m_gamesBlack[blackPlayer]++;
131         }
132         else
133         {
134             // This looks silly, but the []operator has a side effect!
135             if(playersWhite[whitePlayer] == 0)
136             {
137                 playersWhite[whitePlayer] = 0;
138             }
139             if(playersBlack[blackPlayer] == 0)
140             {
141                 playersBlack[blackPlayer] = 0;
142             }
143         }
144 
145         m_result[res]++;
146         m_count++;
147     }
148 
149     for (auto it = playersWhite.cbegin(); it != playersWhite.cend(); ++it)
150     {
151         m_playersWhite.append(PlayerInfoListItem(it.key(), it.value()));
152     }
153     std::sort(m_playersWhite.begin(), m_playersWhite.end(), sortPlayersLt);
154 
155     for (auto it = playersBlack.cbegin(); it != playersBlack.cend(); ++it)
156     {
157         m_playersBlack.append(PlayerInfoListItem(it.key(), it.value()));
158     }
159     std::sort(m_playersBlack.begin(), m_playersBlack.end(), sortPlayersLt);
160 }
161 
162 
formattedScore(const int result[4],int count) const163 QString EcoInfo::formattedScore(const int result[4], int count) const
164 {
165     if(!count)
166     {
167         return QCoreApplication::translate("EcoInfo", "<i>no games</i>");
168     }
169     QString score = "<b>";
170     QChar scoresign[4] = {'*', '+', '=', '-'};
171     QStringList results;
172     results << "\\*" << "1-0" << "1/2-1/2" << "0-1";
173     int order[] = { WhiteWin, Draw, BlackWin, ResultUnknown };
174     QString format = QString("<a href='result:%1'> &nbsp;%2%3</a>");
175 
176     for(int j=0;j<4;j++)
177     {
178         int i = order[j];
179         score += format.arg(results[i]).arg(scoresign[i]).arg(result[i]);
180     }
181     int n = count - result[ResultUnknown];
182     if(n)
183     {
184         score += QString(" &nbsp;(%1%)").arg((100.0 * result[WhiteWin] + 50.0 * result[Draw]) / (n), 1, 'f', 1);
185     }
186     score += "</b>";
187     return score;
188 }
189 
190 
formattedScore() const191 QString EcoInfo::formattedScore() const
192 {
193     return tr("Total: %1").arg(formattedScore(m_result, m_count));
194 }
195 
reset()196 void EcoInfo::reset()
197 {
198     for(int c = White; c <= Black; ++c)
199     {
200         for(int r = 0; r < 4; ++r)
201         {
202             m_result[r] = 0;
203         }
204     }
205     m_playersWhite.clear();
206     m_playersBlack.clear();
207     m_gamesWhite.clear();
208     m_gamesBlack.clear();
209     m_count = 0;
210     m_rating[0] = 99999;
211     m_rating[1] = 0;
212 }
213 
formattedGameCount() const214 QString EcoInfo::formattedGameCount() const
215 {
216     return QCoreApplication::translate("EcoInfo", "Games in database %1: %2")
217            .arg(m_database->name()).arg(m_count);
218 }
219 
formattedRating() const220 QString EcoInfo::formattedRating() const
221 {
222     if(!m_rating[1])
223     {
224         return QString();
225     }
226     else if(m_rating[0] == m_rating[1])
227     {
228         return QCoreApplication::translate("EcoInfo", "Rating: <b>%1</b>").arg(m_rating[0]);
229     }
230     else
231         return QCoreApplication::translate("EcoInfo", "Rating: <b>%1-%2</b>")
232                .arg(m_rating[0]).arg(m_rating[1]);
233 }
234 
listOfPlayers() const235 QString EcoInfo::listOfPlayers() const
236 {
237     QString playersList;
238 
239     playersList.append(QString("<a name='ListWhite'></a><table><tr><th><a href=\"#ListBlack\">&#8681;</a>%1</th><th>%2</th></tr>").arg(tr("White Player"), tr("Score")));
240 
241     for(PlayerInfoList::const_iterator it = m_playersWhite.constBegin(); it != m_playersWhite.constEnd(); ++it)
242     {
243         playersList += QString("<tr><td><a href=\"player-white:%1\">%2</a></td><td>%3/%4</td></tr>")
244                        .arg((*it).first, (*it).first)
245                        .arg((*it).second)
246                        .arg(m_gamesWhite[(*it).first]);
247     }
248 
249     playersList = playersList.append("</table>");
250 
251     playersList.append(QString("<a name='ListBlack'></a><table><tr><th><a href=\"#ListWhite\">&#8679;</a>%1</th><th>%2</th></tr>").arg(tr("Black Player"), tr("Score")));
252 
253     for(PlayerInfoList::const_iterator it = m_playersBlack.begin(); it != m_playersBlack.end(); ++it)
254     {
255         playersList += QString("<tr><td><a href=\"player-black:%1\">%2</a></td><td>%3/%4</td></tr>")
256                        .arg((*it).first, (*it).first)
257                        .arg((*it).second)
258                        .arg(m_gamesBlack[(*it).first]);
259     }
260 
261     playersList = playersList.append("</table>");
262 
263     return playersList;
264 }
265