main.cpp 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /*
  2. * Copyright (c) 2021, Jamie Mansfield <jmansfield@cadixdev.org>
  3. * Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "Game.h"
  8. #include <Games/Spider/SpiderGML.h>
  9. #include <LibConfig/Client.h>
  10. #include <LibCore/System.h>
  11. #include <LibCore/Timer.h>
  12. #include <LibGUI/Action.h>
  13. #include <LibGUI/ActionGroup.h>
  14. #include <LibGUI/Application.h>
  15. #include <LibGUI/Icon.h>
  16. #include <LibGUI/Menu.h>
  17. #include <LibGUI/Menubar.h>
  18. #include <LibGUI/MessageBox.h>
  19. #include <LibGUI/Statusbar.h>
  20. #include <LibGUI/Window.h>
  21. #include <LibMain/Main.h>
  22. #include <stdio.h>
  23. enum class StatisticDisplay : u8 {
  24. HighScore,
  25. BestTime,
  26. __Count
  27. };
  28. static String format_seconds(uint64_t seconds_elapsed)
  29. {
  30. uint64_t hours = seconds_elapsed / 3600;
  31. uint64_t minutes = (seconds_elapsed / 60) % 60;
  32. uint64_t seconds = seconds_elapsed % 60;
  33. return String::formatted("{:02}:{:02}:{:02}", hours, minutes, seconds);
  34. }
  35. ErrorOr<int> serenity_main(Main::Arguments arguments)
  36. {
  37. TRY(Core::System::pledge("stdio recvfd sendfd rpath unix"));
  38. auto app = TRY(GUI::Application::try_create(arguments));
  39. auto app_icon = TRY(GUI::Icon::try_create_default_icon("app-spider"));
  40. Config::pledge_domain("Spider");
  41. TRY(Core::System::pledge("stdio recvfd sendfd rpath"));
  42. TRY(Core::System::unveil("/res", "r"));
  43. TRY(Core::System::unveil(nullptr, nullptr));
  44. auto window = TRY(GUI::Window::try_create());
  45. window->set_title("Spider");
  46. auto mode = static_cast<Spider::Mode>(Config::read_i32("Spider", "Settings", "Mode", static_cast<int>(Spider::Mode::SingleSuit)));
  47. auto update_mode = [&](Spider::Mode new_mode) {
  48. mode = new_mode;
  49. Config::write_i32("Spider", "Settings", "Mode", static_cast<int>(mode));
  50. };
  51. auto mode_id = [&]() {
  52. switch (mode) {
  53. case Spider::Mode::SingleSuit:
  54. return "SingleSuit";
  55. case Spider::Mode::TwoSuit:
  56. return "TwoSuit";
  57. default:
  58. VERIFY_NOT_REACHED();
  59. }
  60. };
  61. auto statistic_display = static_cast<StatisticDisplay>(Config::read_i32("Spider", "Settings", "StatisticDisplay", static_cast<int>(StatisticDisplay::HighScore)));
  62. auto update_statistic_display = [&](StatisticDisplay new_statistic_display) {
  63. statistic_display = new_statistic_display;
  64. Config::write_i32("Spider", "Settings", "StatisticDisplay", static_cast<int>(statistic_display));
  65. };
  66. auto high_score = [&]() {
  67. return static_cast<u32>(Config::read_i32("Spider", "HighScores", mode_id(), 0));
  68. };
  69. auto update_high_score = [&](u32 new_high_score) {
  70. Config::write_i32("Spider", "HighScores", mode_id(), static_cast<int>(new_high_score));
  71. };
  72. auto best_time = [&]() {
  73. return static_cast<u32>(Config::read_i32("Spider", "BestTimes", mode_id(), 0));
  74. };
  75. auto update_best_time = [&](u32 new_best_time) {
  76. Config::write_i32("Spider", "BestTimes", mode_id(), static_cast<int>(new_best_time));
  77. };
  78. auto total_wins = [&]() {
  79. return static_cast<u32>(Config::read_i32("Spider", "TotalWins", mode_id(), 0));
  80. };
  81. auto increment_total_wins = [&]() {
  82. Config::write_i32("Spider", "TotalWins", mode_id(), static_cast<int>(total_wins() + 1));
  83. };
  84. auto total_losses = [&]() {
  85. return static_cast<u32>(Config::read_i32("Spider", "TotalLosses", mode_id(), 0));
  86. };
  87. auto increment_total_losses = [&]() {
  88. Config::write_i32("Spider", "TotalLosses", mode_id(), static_cast<int>(total_losses() + 1));
  89. };
  90. if (mode >= Spider::Mode::__Count)
  91. update_mode(Spider::Mode::SingleSuit);
  92. if (statistic_display >= StatisticDisplay::__Count)
  93. update_statistic_display(StatisticDisplay::HighScore);
  94. auto widget = TRY(window->try_set_main_widget<GUI::Widget>());
  95. widget->load_from_gml(spider_gml);
  96. auto& game = *widget->find_descendant_of_type_named<Spider::Game>("game");
  97. game.set_focus(true);
  98. auto& statusbar = *widget->find_descendant_of_type_named<GUI::Statusbar>("statusbar");
  99. auto reset_statistic_status = [&]() {
  100. switch (statistic_display) {
  101. case StatisticDisplay::HighScore:
  102. statusbar.set_text(1, String::formatted("High Score: {}", high_score()));
  103. break;
  104. case StatisticDisplay::BestTime:
  105. statusbar.set_text(1, String::formatted("Best Time: {}", format_seconds(best_time())));
  106. break;
  107. default:
  108. VERIFY_NOT_REACHED();
  109. }
  110. };
  111. statusbar.set_text(0, "Score: 0");
  112. reset_statistic_status();
  113. statusbar.set_text(2, "Time: 00:00:00");
  114. app->on_action_enter = [&](GUI::Action& action) {
  115. auto text = action.status_tip();
  116. if (text.is_empty())
  117. text = Gfx::parse_ampersand_string(action.text());
  118. statusbar.set_override_text(move(text));
  119. };
  120. app->on_action_leave = [&](GUI::Action&) {
  121. statusbar.set_override_text({});
  122. };
  123. game.on_score_update = [&](uint32_t score) {
  124. statusbar.set_text(0, String::formatted("Score: {}", score));
  125. };
  126. uint64_t seconds_elapsed = 0;
  127. auto timer = Core::Timer::create_repeating(1000, [&]() {
  128. ++seconds_elapsed;
  129. statusbar.set_text(2, String::formatted("Time: {}", format_seconds(seconds_elapsed)));
  130. });
  131. game.on_game_start = [&]() {
  132. seconds_elapsed = 0;
  133. timer->start();
  134. statusbar.set_text(2, "Time: 00:00:00");
  135. };
  136. game.on_game_end = [&](Spider::GameOverReason reason, uint32_t score) {
  137. auto game_was_in_progress = timer->is_active();
  138. if (game_was_in_progress) {
  139. timer->stop();
  140. if (reason != Spider::GameOverReason::Victory)
  141. increment_total_losses();
  142. }
  143. if (reason == Spider::GameOverReason::Victory) {
  144. increment_total_wins();
  145. if (score > high_score()) {
  146. update_high_score(score);
  147. }
  148. auto current_best_time = best_time();
  149. if (seconds_elapsed < current_best_time || current_best_time == 0) {
  150. update_best_time(seconds_elapsed);
  151. }
  152. reset_statistic_status();
  153. }
  154. statusbar.set_text(2, "Timer starts after your first move");
  155. };
  156. window->on_close_request = [&]() {
  157. auto game_in_progress = timer->is_active();
  158. if (game_in_progress) {
  159. auto result = GUI::MessageBox::show(window,
  160. "A game is still in progress, are you sure you would like to quit? Doing so will count as a loss.",
  161. "Game in progress",
  162. GUI::MessageBox::Type::Warning,
  163. GUI::MessageBox::InputType::YesNo);
  164. if (result == GUI::MessageBox::ExecResult::Yes)
  165. return GUI::Window::CloseRequestDecision::Close;
  166. else
  167. return GUI::Window::CloseRequestDecision::StayOpen;
  168. }
  169. return GUI::Window::CloseRequestDecision::Close;
  170. };
  171. window->on_close = [&]() {
  172. game.on_game_end(Spider::GameOverReason::Quit, 0);
  173. };
  174. GUI::ActionGroup suit_actions;
  175. suit_actions.set_exclusive(true);
  176. auto single_suit_action = GUI::Action::create_checkable("&Single Suit", [&](auto&) {
  177. update_mode(Spider::Mode::SingleSuit);
  178. reset_statistic_status();
  179. game.setup(mode);
  180. });
  181. single_suit_action->set_checked(mode == Spider::Mode::SingleSuit);
  182. suit_actions.add_action(single_suit_action);
  183. auto two_suit_action = GUI::Action::create_checkable("&Two Suit", [&](auto&) {
  184. update_mode(Spider::Mode::TwoSuit);
  185. reset_statistic_status();
  186. game.setup(mode);
  187. });
  188. two_suit_action->set_checked(mode == Spider::Mode::TwoSuit);
  189. suit_actions.add_action(two_suit_action);
  190. auto game_menu = TRY(window->try_add_menu("&Game"));
  191. TRY(game_menu->try_add_action(GUI::Action::create("&New Game", { Mod_None, Key_F2 }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/reload.png")), [&](auto&) {
  192. game.setup(mode);
  193. })));
  194. TRY(game_menu->try_add_separator());
  195. TRY(game_menu->try_add_action(single_suit_action));
  196. TRY(game_menu->try_add_action(two_suit_action));
  197. TRY(game_menu->try_add_separator());
  198. TRY(game_menu->try_add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); })));
  199. auto view_menu = TRY(window->try_add_menu("&View"));
  200. GUI::ActionGroup statistic_display_actions;
  201. statistic_display_actions.set_exclusive(true);
  202. auto high_score_action = GUI::Action::create_checkable("&High Score", [&](auto&) {
  203. update_statistic_display(StatisticDisplay::HighScore);
  204. reset_statistic_status();
  205. });
  206. high_score_action->set_checked(statistic_display == StatisticDisplay::HighScore);
  207. statistic_display_actions.add_action(high_score_action);
  208. auto best_time_actions = GUI::Action::create_checkable("&Best Time", [&](auto&) {
  209. update_statistic_display(StatisticDisplay::BestTime);
  210. reset_statistic_status();
  211. });
  212. best_time_actions->set_checked(statistic_display == StatisticDisplay::BestTime);
  213. statistic_display_actions.add_action(best_time_actions);
  214. TRY(view_menu->try_add_action(high_score_action));
  215. TRY(view_menu->try_add_action(best_time_actions));
  216. auto help_menu = TRY(window->try_add_menu("&Help"));
  217. help_menu->add_action(GUI::CommonActions::make_about_action("Spider", app_icon, window));
  218. window->set_resizable(false);
  219. window->resize(Spider::Game::width, Spider::Game::height + statusbar.max_height());
  220. window->set_icon(app_icon.bitmap_for_size(16));
  221. window->show();
  222. game.setup(mode);
  223. return app->exec();
  224. }