1 /*
2     Bastet - tetris clone with embedded bastard block chooser
3     (c) 2005-2009 Federico Poloni <f.polonithirtyseven@sns.it> minus 37
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (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, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "Config.hpp"
20 
21 #include <boost/program_options.hpp>
22 #include <boost/foreach.hpp>
23 #include <boost/format.hpp>
24 #include <boost/algorithm/string.hpp>
25 #include <boost/assign.hpp>
26 #include <curses.h>
27 #include <cstdlib>
28 #include <fstream>
29 
30 //DBG
31 #include <iostream>
32 
33 using namespace std;
34 using namespace boost;
35 namespace po=boost::program_options;
36 
37 namespace Bastet{
38   const size_t HowManyHighScores=10;
39   const std::string RcFileName="/.bastetrc";
40   const std::string LocalHighScoresFileName="/.bastetscores";
41   const std::string GlobalHighScoresFileName="/var/games/bastet.scores2";
42 
Qualifies(int score)43   bool HighScores::Qualifies(int score){
44     stable_sort(begin(),end());
45     return begin()->Score < score;
46   }
47 
InsertHighScore(int score,const std::string & scorer)48   int HighScores::InsertHighScore(int score, const std::string &scorer){
49     if(!Qualifies(score)) return -1;
50     HighScore hs={score,scorer};
51     insert(begin(),hs); //we insert at the beginning to resolve ties
52     stable_sort(begin(),end()); //the dumbest way to do it
53     erase(begin());
54     iterator it=find(begin(),end(),hs); //the dumbest way to find the position
55     return end()-it+1;
56   }
57 
58   Config config; //singleton instance
59 
GetConfigFileName() const60   std::string Config::GetConfigFileName() const{
61     return string(getenv("HOME"))+RcFileName;
62   }
63 
64   class CannotOpenFile{};
65 
GetHighScoresFileName() const66   std::string Config::GetHighScoresFileName() const{
67     static std::string result; //gets cached
68     if(!result.empty()) return result;
69 
70     //tries the global one first and sees if it's writable
71     fstream ofs(GlobalHighScoresFileName.c_str());
72     if(!ofs.fail()){
73       result=GlobalHighScoresFileName;
74     }
75     ofs.close();
76 
77     //falls back to the user-specific file
78     string s=string(getenv("HOME"))+LocalHighScoresFileName;
79     if(result.empty()){
80       cerr<<boost::str(boost::format("bastet: using a user-specific high scores file: %1%\nas the global high scores file %2% is not writable\n") % s % GlobalHighScoresFileName);
81       fstream ofs2(s.c_str());
82       if(!ofs2.fail()){
83 
84 	result=s;
85       }
86       ofs2.close();
87     }
88 
89     //tries to create the local high scores
90     if(result.empty()){
91       ofstream ofs3(s.c_str());
92       if(!ofs3.fail()){
93 	cerr<<boost::str(boost::format("bastet: creating a new user-specific high scores file %1%\n") % s);
94 	result=s;
95       }
96       ofs3.close();
97     }
98     if(result.empty())
99       throw(CannotOpenFile());
100     else return result;
101   }
102 
Config()103   Config::Config(){
104     po::options_description keyMappingOpts("Key mappings");
105     keyMappingOpts.add_options()
106       ("Down",po::value<int>()->default_value(KEY_DOWN),"Down key")
107       ("Left",po::value<int>()->default_value(KEY_LEFT),"Left key")
108       ("Right",po::value<int>()->default_value(KEY_RIGHT),"Right key")
109       ("RotateCW",po::value<int>()->default_value(' '),"Clockwise turn key")
110       ("RotateCCW",po::value<int>()->default_value(KEY_UP),"Counterclockwise turn key")
111       ("Drop",po::value<int>()->default_value(KEY_ENTER),"Drop tetromino key")
112       ("Pause",po::value<int>()->default_value('p'),"Pause key")
113       ;
114 
115     po::options_description highScoresOpts("High scores");
116     boost::format scorer("Scorer%02d%02d");
117     boost::format score("Score%02d%02d");
118     for(int difficulty=0;difficulty<num_difficulties;++difficulty)
119       for(size_t i=0;i<HowManyHighScores;i++){
120 	highScoresOpts.add_options()
121 	  (str(scorer % difficulty % i).c_str(),po::value<string>()->default_value("No one played yet"),"Name of high scorer")
122 	  (str(score % difficulty % i).c_str(),po::value<int>()->default_value(0),"High score (points)")
123 	  ;
124       }
125 
126     boost::program_options::variables_map _options;
127     boost::program_options::variables_map _highScores;
128 
129     ifstream ifs(GetConfigFileName().c_str());
130     po::store(po::parse_config_file(ifs,keyMappingOpts),_options);
131 
132     _keys.Down=_options["Down"].as<int>();
133     _keys.Left=_options["Left"].as<int>();
134     _keys.Right=_options["Right"].as<int>();
135     _keys.RotateCW=_options["RotateCW"].as<int>();
136     _keys.RotateCCW=_options["RotateCCW"].as<int>();
137     _keys.Drop=_options["Drop"].as<int>();
138     _keys.Pause=_options["Pause"].as<int>();
139 
140     string s=GetHighScoresFileName();
141     ifstream ifs2(s.c_str());
142     po::store(po::parse_config_file(ifs2,highScoresOpts),_highScores);
143 
144     for(int difficulty=0;difficulty<num_difficulties;difficulty++){
145       for(size_t i=0;i<HowManyHighScores;++i)
146 	_hs[difficulty].push_back((HighScore){_highScores[str(score % difficulty % i)].as<int>(),
147 	      _highScores[str(scorer % difficulty % i)].as<string>()});
148     stable_sort(_hs[difficulty].begin(),_hs[difficulty].end()); //should not be needed but...
149     }
150   }
151 
GetKeys()152   Keys *Config::GetKeys(){
153     return &_keys;
154   }
155 
GetHighScores(int difficulty)156   HighScores *Config::GetHighScores(int difficulty){
157     assert(difficulty>=0 && difficulty<num_difficulties);
158     return &(_hs[difficulty]);
159   }
160 
~Config()161   Config::~Config(){
162     /**
163        The config and highscore files are written down at each game, even if it is not needed, but who cares for now
164      */
165     boost::program_options::variables_map _options;
166 
167     _options.insert(make_pair("Down",po::variable_value(_keys.Down,false)));
168     _options.insert(make_pair("Left",po::variable_value(_keys.Left,false)));
169     _options.insert(make_pair("Right",po::variable_value(_keys.Right,false)));
170     _options.insert(make_pair("RotateCW",po::variable_value(_keys.RotateCW,false)));
171     _options.insert(make_pair("RotateCCW",po::variable_value(_keys.RotateCCW,false)));
172     _options.insert(make_pair("Drop",po::variable_value(_keys.Drop,false)));
173     _options.insert(make_pair("Pause",po::variable_value(_keys.Pause,false)));
174 
175     ofstream ofs(GetConfigFileName().c_str());
176     ofs<<"# Automatically regenerated by the program at each run, edit at your own risk\n";
177     BOOST_FOREACH(const po::variables_map::value_type &pair, _options){
178       ofs<<pair.first<<" = "<<pair.second.as<int>()<<"\n";
179     }
180 
181     ofstream ofs2(GetHighScoresFileName().c_str());
182     ofs2<<"# Do not edit this file, Bastet sees you\n";
183 
184     boost::format scorer("Scorer%02d%02d");
185     boost::format score("Score%02d%02d");
186 
187     for(int difficulty=0;difficulty<num_difficulties;++difficulty){
188       int i=0;
189       BOOST_FOREACH(const HighScore &hs, _hs[difficulty]){
190 	ofs2<<str(scorer % difficulty % i) << " = \"" << hs.Scorer << "\"\n";
191 	ofs2<<str(score % difficulty % i) << " = " << hs.Score <<"\n";
192 	i++;
193       }
194     }
195   }
196 }
197