1 //
2 //  SuperTuxKart - a fun racing game with go-kart
3 //  Copyright (C) 2006-2015 Joerg Henrichs
4 //
5 //  This program is free software; you can redistribute it and/or
6 //  modify it under the terms of the GNU General Public License
7 //  as published by the Free Software Foundation; either version 3
8 //  of the License, or (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 #include "race/highscores.hpp"
20 
21 #include "io/utf_writer.hpp"
22 #include "io/xml_node.hpp"
23 #include "race/race_manager.hpp"
24 #include "utils/log.hpp"
25 
26 #include <stdexcept>
27 #include <fstream>
28 
29 // -----------------------------------------------------------------------------
Highscores(const HighscoreType & highscore_type,int num_karts,const RaceManager::Difficulty & difficulty,const std::string & track_name,const int number_of_laps,const bool reverse)30 Highscores::Highscores(const HighscoreType &highscore_type,
31                        int num_karts,
32                        const RaceManager::Difficulty &difficulty,
33                        const std::string &track_name,
34                        const int number_of_laps,
35                        const bool reverse)
36 {
37     m_track           = track_name;
38     m_highscore_type  = highscore_type;
39     m_number_of_karts = num_karts;
40     m_difficulty      = difficulty;
41     m_number_of_laps  = number_of_laps;
42     m_reverse         = reverse;
43 
44     for(int i=0; i<HIGHSCORE_LEN; i++)
45     {
46         m_name[i]      = "";
47         m_kart_name[i] = "";
48         m_time[i]      = -9.9f;
49     }
50 }
51 // -----------------------------------------------------------------------------
Highscores(const XMLNode & node)52 Highscores::Highscores(const XMLNode &node)
53 {
54     m_track           = "";
55     m_highscore_type  = "HST_UNDEFINED";
56     m_number_of_karts = -1;
57     m_difficulty      = -1;
58     m_number_of_laps  = -1;
59     m_reverse         = false;
60 
61     for(int i=0; i<HIGHSCORE_LEN; i++)
62     {
63         m_name[i]      = "";
64         m_kart_name[i] = "";
65         m_time[i]      = -9.9f;
66     }
67 
68     readEntry(node);
69 }   // Highscores
70 
71 // -----------------------------------------------------------------------------
readEntry(const XMLNode & node)72 void Highscores::readEntry(const XMLNode &node)
73 {
74     node.get("track-name",     &m_track               );
75     node.get("number-karts",   &m_number_of_karts     );
76     std::string hst="HST_UNDEFINED";
77     node.get("hscore-type",    &hst                   );
78     m_highscore_type = (HighscoreType)hst;
79     node.get("difficulty",     &m_difficulty          );
80     node.get("number-of-laps", &m_number_of_laps      );
81     node.get("reverse",        &m_reverse             );
82 
83     for(unsigned int i=0; i<node.getNumNodes(); i++)
84     {
85         if (i >= HIGHSCORE_LEN)
86         {
87             Log::warn("Highscores", "Hiscore has too many entries.");
88             break;
89         }
90 
91         const XMLNode *entry = node.getNode(i);
92         entry->get("time",     &m_time[i]            );
93         entry->getAndDecode("name",     &m_name[i]            );
94         entry->get("kartname", &m_kart_name[i]       );
95 
96         // a non-empty entry needs a non-empty kart name.
97         if (!(m_time[i] <= 0.0f || m_kart_name[i].size() > 0))
98         {
99             throw std::logic_error("Invalid highscore entry : empty kart name");
100         }
101         if (!(m_time[i] <= 0.0f || m_name[i].size() > 0))
102         {
103             throw std::logic_error("Invalid highscore entry : empty kart name");
104         }
105     }
106 }   // readEntry
107 
108 // -----------------------------------------------------------------------------
109 /** Writes the highscores in this entry to the writer. It will only write
110  *  anything if there is actually a highscore recored (i.e. time >=0). Empty
111  *  entries are created e.g. when changing the number of laps in the GUI,
112  *  resulting in empty entries here.
113  *  \param writer The file stream to write the data to.
114  */
writeEntry(UTFWriter & writer)115 void Highscores::writeEntry(UTFWriter &writer)
116 {
117     // Only
118     bool one_is_set = false;
119     for(unsigned int i=0; i<HIGHSCORE_LEN; i++)
120         one_is_set |= m_time[i]>=0;
121     if(!one_is_set) return;
122 
123     writer << "  <highscore track-name    =\"" << m_track.c_str()           << "\"\n";
124     writer << "             number-karts  =\"" << m_number_of_karts         << "\"\n";
125     writer << "             difficulty    =\"" << m_difficulty              << "\"\n";
126     writer << "             hscore-type   =\"" << m_highscore_type.c_str()  << "\"\n";
127     writer << "             number-of-laps=\"" << m_number_of_laps          << "\"\n";
128     writer << "             reverse       =\"" << m_reverse                 << "\">\n";
129 
130     for(int i=0; i<HIGHSCORE_LEN; i++)
131     {
132         if (m_time[i] > 0.0f)
133         {
134             assert(m_kart_name[i].size() > 0);
135             writer << "             <entry time    =\"" << m_time[i] << L"\"\n";
136             writer << "                    name    =\"" << StringUtils::xmlEncode(m_name[i]) << L"\"\n";
137             writer << "                    kartname=\"" << m_kart_name[i]
138                    << "\"/>\n";
139         }
140     }   // for i
141     writer << "  </highscore>\n";
142 }   // writeEntry
143 
144 // -----------------------------------------------------------------------------
matches(const HighscoreType & highscore_type,int num_karts,const RaceManager::Difficulty & difficulty,const std::string & track,const int number_of_laps,const bool reverse)145 int Highscores::matches(const HighscoreType &highscore_type,
146                         int num_karts, const RaceManager::Difficulty &difficulty,
147                         const std::string &track, const int number_of_laps,
148                         const bool reverse)
149 {
150     return (m_highscore_type  == highscore_type   &&
151             m_track           == track            &&
152             m_difficulty      == difficulty       &&
153             m_number_of_laps  == number_of_laps   &&
154             m_number_of_karts == num_karts        &&
155             m_reverse         == reverse            );
156 }   // matches
157 
158 // -----------------------------------------------------------------------------
159 /** Inserts the data into the highscore list.
160  *  If the new entry is fast enough to
161  *  be in the highscore list, the new position (1-HIGHSCORE_LEN) is returned,
162  *  otherwise a 0.
163  */
addData(const std::string & kart_name,const core::stringw & name,const float time)164 int Highscores::addData(const std::string& kart_name,
165                         const core::stringw& name, const float time)
166 {
167     int position=-1;
168     for(int i=0; i<HIGHSCORE_LEN; i++)
169     {
170         // Check for unused entry. If so, just insert the new record
171         if(m_time[i]<0.0f)
172         {
173             position=i;
174             break;
175         }
176         // Check if new entry is faster than than in slot 'i', if so
177         // move times etc and insert new entry
178         if(time < m_time[i])
179         {
180             for(int j=HIGHSCORE_LEN-2;j>=i;j--)
181             {
182                 m_name[j+1]      = m_name[j];
183                 m_kart_name[j+1] = m_kart_name[j];
184                 m_time[j+1]      = m_time[j];
185             }
186             position = i;
187             break;
188         }
189     }//next score slot
190 
191     if(position>=0)
192     {
193         m_track               = RaceManager::get()->getTrackName();
194         m_number_of_karts     = RaceManager::get()->getNumNonGhostKarts();
195         m_difficulty          = RaceManager::get()->getDifficulty();
196         m_number_of_laps      = RaceManager::get()->getNumLaps();
197         m_reverse             = RaceManager::get()->getReverseTrack();
198         m_name[position]      = name;
199         m_time[position]      = time;
200         m_kart_name[position] = kart_name;
201     }
202 
203     return position+1;
204 }   // addData
205 
206 // -----------------------------------------------------------------------------
getNumberEntries() const207 int Highscores::getNumberEntries() const
208 {
209     for(int i=HIGHSCORE_LEN-1; i>=0; i--)
210     {
211         if(m_time[i]>0) return i+1;
212     }
213     return 0;
214 }   // getNumberEntries
215 
216 // -----------------------------------------------------------------------------
getEntry(int number,std::string & kart_name,core::stringw & name,float * const time) const217 void Highscores::getEntry(int number, std::string &kart_name,
218                           core::stringw &name, float *const time) const
219 {
220     if(number<0 || number>getNumberEntries())
221     {
222         Log::warn("Highscores", "Accessing undefined highscore entry:");
223         Log::warn("Highscores", "Number %d, but %d entries are defined.", number,
224                   getNumberEntries());
225         Log::warn("Highscores", "This error can be ignored, but no highscores are available.");
226         return;
227     }
228     kart_name = m_kart_name[number];
229     name      = m_name[number];
230     *time     = m_time[number];
231 
232 }   // getEntry
233 
234 // -----------------------------------------------------------------------------
235