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