1 #include "menu.h"
2 #include "config.h"
3 #include "constants.h"
4 #include "gfxinterface.h"
5 #include "highscore.h"
6 #include "input.h"
7 #include "instance.h"
8 #include "pix.h"
9 #include "score.h"
10 #include "stars.h"
11 #include "time_util.h"
12 #include "xpms.h"
13 #include <cstring>
14 #include <iomanip>
15 #include <stdexcept>
16 #include <sstream>
17
18
19 StartMenu * StartMenu::singleton_ (0);
20
21
Instance()22 StartMenu & StartMenu::Instance() {
23 if (!singleton_) {
24 singleton_ = new StartMenu;
25 InstancesManager::Instance().Push(DestroyInstance);
26 }
27 return *singleton_;
28 }
29
30
31 const char *const menu_lines[] = {
32 "XGalaga++ v0.9 (2017-12-10)",
33 "�2003-2017 Marc Mongenet, graphics from XGalaga 2.0.34",
34 "This game is free software, covered by the GPL.",
35 "",
36 "Welcome player %p!",
37 "",
38 "KEY ACTION",
39 "s start game",
40 "q quit",
41 "h toggle high scores / menu",
42 "p pause",
43 "1,2,3,4,5 set window size",
44 "+ increase details",
45 "- decrease details",
46 "r refresh rate %r1->%r2 Hz",
47 "<space bar> fire",
48 "<left/right arrow> move left/right",
49 "<up/down arrow> scroll high scores up/down",
50 "",
51 "BONUS",
52 "",
53 "extra shield every 1000 points"
54 };
55
56
StartMenu()57 StartMenu::StartMenu()
58 : font_(X11::Inst().LoadQueryFont("*-fixed-bold-r-*-*-14-*-*-*-*-*-iso8859-1"))
59 , text_width_ (0)
60 , wait_scores_ (5000)
61 , print_scores_ (false)
62 , scroll_scores_ (0)
63 , can_scroll_ (true)
64 , last_window_size_(0, 0)
65 , rGb_ (0)
66 , rGb_add_ (1024)
67 {
68 if (!font_) {
69 font_ = X11::Inst().LoadQueryFont("*-fixed-*");
70 if (!font_) throw std::runtime_error ("X font error");
71 }
72 for (size_t i (0); i < sizeof menu_lines / sizeof menu_lines[0]; ++i) {
73 text_width_ = std::max(text_width_, XTextWidth(font_, menu_lines[i], std::strlen(menu_lines[i])));
74 }
75 }
76
77
~StartMenu()78 StartMenu::~StartMenu()
79 {
80 X11::Inst().FreeFont(font_);
81 }
82
83
Display()84 bool StartMenu::Display()
85 {
86 Input input;
87 bool updated (false);
88 double frame_time (0);
89
90 for (;;) {
91
92 input.Update();
93
94 if (input.Quit()) return false;
95
96 if (input.Start()) {
97 print_scores_ = true;
98 wait_scores_ = 5000;
99 return true;
100 }
101
102 Config::Instance().AddDetailsLevel(input.Details());
103
104 if (input.IncRefreshRate()) {
105 Config::Instance().SetNextRefreshRate();
106 X11::Inst().ClearWindow();
107 }
108
109 SetStandardWindowSize(input.WindowSize());
110 X11::Inst().GetWindowAttributes();
111
112 if (--wait_scores_ < 0 || input.HighScores()) {
113 print_scores_ = !print_scores_;
114 wait_scores_ = 1000;
115 X11::Inst().ClearWindow();
116 }
117
118 StarsFields::Instance().Scroll();
119 Score::Instance().Refresh();
120
121 if (last_window_size_ != Coord(X11::Inst().WindowWidth(), X11::Inst().WindowHeight())) {
122 last_window_size_ = Coord(X11::Inst().WindowWidth(), X11::Inst().WindowHeight());
123 scroll_scores_ = 0;
124 }
125
126 if (print_scores_) {
127 try {
128 if (!updated) {
129 HighScores::Instance().Update();
130 updated = true;
131 }
132 if (input.VMove()) {
133 if (can_scroll_ || input.VMove() < 0) scroll_scores_ += input.VMove();
134 if (scroll_scores_ < 0) scroll_scores_ = 0;
135 wait_scores_ = 1000;
136 }
137 PrintScores(NextColor());
138 } catch (const std::exception & e) {
139 X11::Inst().SetForeground(X11::Inst().GetWhite());
140 X11::Inst().SetClipMask(None);
141 X11::Inst().SetFont(font_->fid);
142 X11::Inst().DrawString(Coord(1, last_window_size_.y / 2), e.what());
143 }
144 }
145 else PrintHelp(NextColor());
146
147 X11::Inst().Sync(False);
148
149 frame_time = SleepTimeInterval(frame_time, 1.0 / Config::Instance().RefreshRate());
150 }
151 }
152
153
NextColor()154 XColor StartMenu::NextColor()
155 {
156 XColor color;
157 color.red = 48*1024;
158 color.blue = 65535;
159 rGb_ += rGb_add_;
160 if (rGb_ == 0) {
161 rGb_add_ = -rGb_add_;
162 rGb_ += rGb_add_;
163 }
164 color.green = rGb_;
165 return color;
166 }
167
168
SetFontContext(XColor & color) const169 void StartMenu::SetFontContext(XColor & color) const
170 {
171 X11::Inst().AllocColorAlways(&color);
172 X11::Inst().SetForeground(color.pixel);
173 X11::Inst().SetClipMask(None);
174 X11::Inst().SetFont(font_->fid);
175 }
176
177
PrintHelp(XColor color)178 void StartMenu::PrintHelp(XColor color)
179 {
180 Coord pos (std::max(0, (last_window_size_.x - text_width_) / 2), 20);
181 SetFontContext(color);
182
183 // Draw help text
184 for (size_t i (0); i < sizeof menu_lines / sizeof menu_lines[0]; ++i) {
185 std::string line (menu_lines[i]);
186
187 while (line.find("%p") != std::string::npos) {
188 line.replace(line.find("%p"), 2, Config::Instance().GetPlayerName());
189 }
190
191 std::ostringstream refresh_rate;
192 refresh_rate << Config::Instance().RefreshRate();
193 while (line.find("%r1") != std::string::npos) {
194 line.replace(line.find("%r1"), 3, refresh_rate.str().c_str());
195 }
196
197 std::ostringstream next_refresh_rate;
198 next_refresh_rate << Config::Instance().NextRefreshRate();
199 while (line.find("%r2") != std::string::npos) {
200 line.replace(line.find("%r2"), 3, next_refresh_rate.str().c_str());
201 }
202
203 X11::Inst().DrawString(pos, line);
204
205 pos.y += LineHeight();
206 if (line.empty()) {
207 color.red -= 8192;
208 X11::Inst().AllocColorAlways(&color);
209 X11::Inst().SetForeground(color.pixel);
210 }
211 }
212 // Draw bonus pixs with help text
213 static const char *const *const bonuses[] =
214 { bonus_speed_xpm, bonus_fire_xpm, bonus_shield_xpm, bonus_multi_xpm };
215 static const char *const bonus_text[] =
216 { "extra speed", "extra fire", "extra shield", "multi fire" };
217 for (size_t i (0); i < sizeof bonuses / sizeof bonuses[0]; ++i) {
218 const Pix *const bonus (PixKeeper::Instance().Get(bonuses[i]));
219 bonus->Draw(pos + Coord(bonus->Width() / 2, -bonus->Height() / 2));
220 X11::Inst().SetClipMask(None);
221 X11::Inst().DrawString(pos + Coord(2 * bonus->Width(), 0), bonus_text[i]);
222 pos.y += std::max(bonus->Height(), LineHeight());
223 }
224 }
225
226
PrintScores(XColor color)227 void StartMenu::PrintScores(XColor color)
228 {
229 Coord pos (std::max(0, (last_window_size_.x - text_width_) / 2), 20);
230 SetFontContext(color);
231 // Draw high scores title
232 std::ostringstream ost;
233 ost <<"High scores for "
234 <<last_window_size_.x
235 <<'�'
236 <<last_window_size_.y
237 <<'@'
238 <<Config::Instance().RefreshRate()
239 <<" window";
240 X11::Inst().DrawString(pos, ost.str().c_str());
241 pos.y += LineHeight();
242 ost.str(std::string(ost.str().size(), '�'));
243 X11::Inst().DrawString(pos, ost.str().c_str());
244 pos.y += LineHeight();
245
246 // get high scores for current window size
247 const std::multiset<HighScore> *const high_scores (HighScores::Instance().Get(last_window_size_, Config::Instance().RefreshRate()));
248 if (!high_scores) {
249 score_lines_.clear();
250 ost.str("None.");
251 X11::Inst().DrawString(pos, ost.str().c_str());
252 return;
253 }
254 // Display high scores
255 // The first line is always displayed
256 // The best player is always displayed (on last line if necessary).
257 int rank (0);
258 size_t line (0);
259 int last_score (-1);
260 const std::string player_name (Config::Instance().GetPlayerName());
261 bool look_for_player (false);
262 bool player_displayed (false);
263 for (std::multiset<HighScore>::const_reverse_iterator it (high_scores->rbegin());
264 (can_scroll_ = it != high_scores->rend()) &&
265 (look_for_player || pos.y < last_window_size_.y - LineHeight());
266 ++it) {
267 ++rank;
268 if ((rank > scroll_scores_ && !look_for_player) ||
269 (look_for_player && it->Name() == player_name)) {
270 // Construct this line
271 ost.str("");
272 if (it->Value() != last_score) {
273 last_score = it->Value();
274 ost << std::setfill(' ') << std::setw(2) << rank << ". ";
275 } else {
276 ost << " ";
277 }
278 char score_date[20];
279 const time_t score_date_tm = it->Date();
280
281 strftime(score_date, sizeof score_date, "%x", localtime(&score_date_tm));
282 // Null score date means no date recorded -> deplace date by spaces.
283 if (it->Date() == 0) {
284 for (int i = 0; score_date[i] != '\0'; ++i) {
285 score_date[i] = ' ';
286 }
287 }
288 ost << std::setfill(' ') << std::setw(6) << last_score
289 << " " << score_date << ' ' << it->Name();
290 // Undraw old line if is different
291 const std::string score_line (ost.str());
292 if (line == score_lines_.size()) score_lines_.push_back(score_line);
293 else if (score_lines_[line] != score_line) {
294 X11::Inst().SetForeground(X11::Inst().GetBlack());
295 X11::Inst().DrawString(pos, score_lines_[line].c_str());
296 score_lines_[line] = score_line;
297 }
298 // text color with special color for current player
299 if (it->Name() == player_name) {
300 player_displayed = true;
301 if (color.green) color.green += (65535 - color.green) / 2;
302 }
303 if (color.red >= 1024) color.red -= 1024;
304 X11::Inst().AllocColorAlways(&color);
305 X11::Inst().SetForeground(color.pixel);
306 if (it->Name() == player_name) {
307 if (color.green) color.green -= 65535 - color.green;
308 }
309 X11::Inst().DrawString(pos, score_line.c_str());
310 ++line;
311 pos.y += LineHeight();
312 look_for_player = !player_displayed && pos.y >= last_window_size_.y - 2 * LineHeight();
313 }
314 }
315 }
316