From 33196352f76a9bda4828fabcc234b7280bfb54f6 Mon Sep 17 00:00:00 2001 From: daleclack Date: Thu, 29 Feb 2024 22:42:21 +0800 Subject: [PATCH] Update MineSweeper app --- Gtk4_Reset/CMakeLists.txt | 3 +- Gtk4_Reset/src/mine_app/InputBox.cpp | 152 ++++++++++++++++++++ Gtk4_Reset/src/mine_app/InputBox.h | 13 ++ Gtk4_Reset/src/mine_app/MineSweeper.cpp | 77 +++++++++- Gtk4_Reset/src/mine_app/ScoresItem.cpp | 47 ++++++ Gtk4_Reset/src/mine_app/ScoresItem.h | 15 ++ Gtk4_Reset/src/mine_app/ScoresWin.cpp | 181 ++++++++++++++++++++++++ Gtk4_Reset/src/mine_app/ScoresWin.h | 9 ++ Gtk4_Reset/src/mine_app/jsonfile.h | 7 + 9 files changed, 496 insertions(+), 8 deletions(-) create mode 100644 Gtk4_Reset/src/mine_app/InputBox.cpp create mode 100644 Gtk4_Reset/src/mine_app/InputBox.h create mode 100644 Gtk4_Reset/src/mine_app/ScoresItem.cpp create mode 100644 Gtk4_Reset/src/mine_app/ScoresItem.h create mode 100644 Gtk4_Reset/src/mine_app/ScoresWin.cpp create mode 100644 Gtk4_Reset/src/mine_app/ScoresWin.h create mode 100644 Gtk4_Reset/src/mine_app/jsonfile.h diff --git a/Gtk4_Reset/CMakeLists.txt b/Gtk4_Reset/CMakeLists.txt index 3f1b361..09a69cd 100644 --- a/Gtk4_Reset/CMakeLists.txt +++ b/Gtk4_Reset/CMakeLists.txt @@ -30,7 +30,8 @@ set(SOURCES src/core/main.cpp src/core/MainWin.cpp src/core/MyStack.cpp src/calc_app/CalcApp.cpp src/run_app/RunApp.cpp src/draw_app/DrawApp.cpp src/game24_app/Game24.cpp src/game24_app/Game24App.cpp src/text_app/TextEditor.cpp src/text_app/MyInfoBar.cpp src/image_app/ImageApp.cpp src/image_app/MyImage.cpp - src/mine_app/MineSweeper.cpp src/mine_app/MineCell.cpp) + src/mine_app/MineSweeper.cpp src/mine_app/MineCell.cpp src/mine_app/InputBox.cpp + src/mine_app/ScoresItem.cpp src/mine_app/ScoresWin.cpp) #Compile resources with GCR_CMake diff --git a/Gtk4_Reset/src/mine_app/InputBox.cpp b/Gtk4_Reset/src/mine_app/InputBox.cpp new file mode 100644 index 0000000..09b0245 --- /dev/null +++ b/Gtk4_Reset/src/mine_app/InputBox.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include "InputBox.h" +#include "ScoresWin.h" +#include "jsonfile.h" + +struct _InputBox +{ + GtkWindow parent_instance; + GtkWidget *main_box, *btn_box; + GtkWidget *entry_name; + GtkWidget *scores_check; + GtkWidget *btn_ok, *btn_cancel; + int game_time; + + // Scores Window + ScoresWin *scores_win; +}; + +G_DEFINE_TYPE(InputBox, input_box, GTK_TYPE_WINDOW) + +static std::vector names; +static std::vector times; +static json data; + +static void btnok_clicked(GtkButton *btn, InputBox *self) +{ + // Save score to json file + // Open a file to save json data + std::fstream outfile; + outfile.open("scores.json", std::ios_base::out); + if (outfile.is_open()) + { + // Insert data to json + const char *c_name = gtk_editable_get_text(GTK_EDITABLE(self->entry_name)); + std::string name = std::string(c_name); + names.push_back(name); + times.push_back(self->game_time); + data["name"] = names; + data["time"] = times; + + // Output data + outfile << data; + } + outfile.close(); + + // Show Scores window + if (gtk_check_button_get_active(GTK_CHECK_BUTTON(self->scores_check))) + { + scores_win_update_and_show(self->scores_win); + } + gtk_window_close(GTK_WINDOW(self)); +} + +static gboolean input_box_closed(GtkWidget *win, InputBox *self) +{ + // Hide the window + gtk_widget_set_visible(win, FALSE); + return TRUE; +} + +static void input_box_init(InputBox *self) +{ + // Initalize window + gtk_window_set_default_size(GTK_WINDOW(self), 300, 150); + gtk_window_set_icon_name(GTK_WINDOW(self), "mines_app"); + + // Create widgets + self->main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + self->btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + self->entry_name = gtk_entry_new(); + self->scores_check = gtk_check_button_new_with_label("Show scores window"); + self->btn_ok = gtk_button_new_with_label("OK"); + self->btn_cancel = gtk_button_new_with_label("Cancel"); + + // Create Scores Window + GtkWindow *window = gtk_window_get_transient_for(GTK_WINDOW(self)); + self->scores_win = scores_win_new(window); + + // Link signals + g_signal_connect(self->btn_ok, "clicked", G_CALLBACK(btnok_clicked), self); + g_signal_connect(self, "close-request", G_CALLBACK(input_box_closed), self); + g_signal_connect_swapped(self->btn_cancel, "clicked", G_CALLBACK(gtk_window_close), self); + + // Pack widgets + gtk_box_append(GTK_BOX(self->main_box), self->entry_name); + gtk_box_append(GTK_BOX(self->main_box), self->scores_check); + gtk_box_append(GTK_BOX(self->btn_box), self->btn_ok); + gtk_box_append(GTK_BOX(self->btn_box), self->btn_cancel); + gtk_box_append(GTK_BOX(self->main_box), self->btn_box); + gtk_widget_set_margin_bottom(self->main_box, 10); + gtk_widget_set_margin_end(self->main_box, 10); + gtk_widget_set_margin_start(self->main_box, 10); + gtk_widget_set_margin_top(self->main_box, 10); + gtk_widget_set_halign(self->btn_box, GTK_ALIGN_END); + // gtk_widget_set_halign(self->main_box, GTK_ALIGN_CENTER); + gtk_widget_set_valign(self->main_box, GTK_ALIGN_CENTER); + gtk_window_set_child(GTK_WINDOW(self), self->main_box); +} + +static void input_box_class_init(InputBoxClass *klass) +{ +} + +InputBox *input_box_new(GtkWindow *parent) +{ + return Input_Box(g_object_new(input_box_get_type(), + "transient-for", parent, NULL)); +} + +void input_box_present(InputBox *self) +{ + // Show the input box + gtk_window_present(GTK_WINDOW(self)); +} + +void input_box_set_game_time(InputBox *self, int time) +{ + // Try to open json file + std::fstream jsonfile; + jsonfile.open("scores.json", std::ios_base::in); + + // If json file opened, read the data + if (jsonfile.is_open()) + { + data = json::parse(jsonfile); + std::vector names1 = data["name"]; + std::vector times1 = data["time"]; + names = names1; + times = times1; + } + else + { + // Otherwist, create json data + data = json::parse(R"( + { + "name":[" "], + "time":[0] + } + )"); + } + jsonfile.close(); + + // Set game time for input box + self->game_time = time; +} + +void input_box_show_scores(InputBox *self) +{ + scores_win_update_and_show(self->scores_win); +} diff --git a/Gtk4_Reset/src/mine_app/InputBox.h b/Gtk4_Reset/src/mine_app/InputBox.h new file mode 100644 index 0000000..99367ca --- /dev/null +++ b/Gtk4_Reset/src/mine_app/InputBox.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +G_DECLARE_FINAL_TYPE(InputBox, input_box, Input, Box, GtkWindow) + +InputBox *input_box_new(GtkWindow *parent); + +void input_box_set_game_time(InputBox *self, int time); + +void input_box_present(InputBox *self); + +void input_box_show_scores(InputBox *self); diff --git a/Gtk4_Reset/src/mine_app/MineSweeper.cpp b/Gtk4_Reset/src/mine_app/MineSweeper.cpp index 1537122..abcb47e 100644 --- a/Gtk4_Reset/src/mine_app/MineSweeper.cpp +++ b/Gtk4_Reset/src/mine_app/MineSweeper.cpp @@ -1,5 +1,6 @@ #include "MineSweeper.h" #include "MineCell.h" +#include "InputBox.h" #include #include @@ -16,6 +17,10 @@ struct _MineSweeper { GtkApplicationWindow parent_instance; + // Header widgets + GtkWidget *header, *menu_btn; + GtkBuilder *menu_builder; + // Child widgets GtkWidget *main_box, *btn_box; GtkWidget *mine_grid; @@ -28,6 +33,9 @@ struct _MineSweeper gboolean started; int mines_clear, mine_count; GameStatus game_status; + + // InputBox for game win + InputBox *input_box; }; G_DEFINE_TYPE(MineSweeper, mine_sweeper, GTK_TYPE_APPLICATION_WINDOW) @@ -170,9 +178,9 @@ static void mine_sweeper_check_mines(MineSweeper *self, int pos_x, int pos_y) self->game_status = GameStatus::Winned; self->started = FALSE; - // // Save the time of game - // input_dialog->set_game_time(timer_count); - // input_dialog->show(); + // Save the time of game + input_box_set_game_time(self->input_box, self->time_count); + input_box_present(self->input_box); } } @@ -244,9 +252,13 @@ static gboolean time_func(gpointer data) { MineSweeper *mine_app = MINE_SWEEPER(data); char tmp[50]; - (mine_app->time_count)++; - sprintf(tmp, "Time:%d", mine_app->time_count); - gtk_label_set_label(GTK_LABEL(mine_app->time_label), tmp); + // Update time when game is running + if (mine_app->game_status == GameStatus::Running) + { + (mine_app->time_count)++; + sprintf(tmp, "Time:%d", mine_app->time_count); + gtk_label_set_label(GTK_LABEL(mine_app->time_label), tmp); + } return mine_app->started; } @@ -284,16 +296,67 @@ static void btnshow_clicked(GtkButton *btn, MineSweeper *self) } } +// Signal Handler for menus +static void newgame_activated(GSimpleAction *action, GVariant *parmeter, gpointer data) +{ + btnstart_clicked(NULL, MINE_SWEEPER(data)); +} + +static void scores_activated(GSimpleAction *action, GVariant *parmeter, gpointer data) +{ + MineSweeper *app = MINE_SWEEPER(data); + input_box_show_scores(app->input_box); +} + +static void showmines_activated(GSimpleAction *action, GVariant *parmeter, gpointer data) +{ + btnshow_clicked(NULL, MINE_SWEEPER(data)); +} + +static void quit_activated(GSimpleAction *action, GVariant *parmeter, gpointer data) +{ + gtk_window_destroy(GTK_WINDOW(data)); +} + static void mine_sweeper_init(MineSweeper *self) { // Initalize window gtk_window_set_title(GTK_WINDOW(self), "MineSweeper"); + gtk_window_set_icon_name(GTK_WINDOW(self), "mine_app"); + self->header = gtk_header_bar_new(); + gtk_window_set_titlebar(GTK_WINDOW(self), self->header); + + // Add action for menu + GActionEntry entries[] = + { + {"new_game", newgame_activated, NULL, NULL, NULL}, + {"scores", scores_activated, NULL, NULL, NULL}, + {"show_mines", showmines_activated, NULL, NULL, NULL}, + {"quit", quit_activated, NULL, NULL, NULL}}; + g_action_map_add_action_entries(G_ACTION_MAP(self), entries, + G_N_ELEMENTS(entries), self); + + // Create Menu and button + self->menu_btn = gtk_menu_button_new(); + gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(self->menu_btn), "open-menu"); + gtk_header_bar_pack_end(GTK_HEADER_BAR(self->header), self->menu_btn); + + // Create Menu + self->menu_builder = gtk_builder_new_from_resource("/org/gtk/daleclack/mine_menu.xml"); + GMenuModel *model = G_MENU_MODEL(gtk_builder_get_object(self->menu_builder, "mine_menu")); + gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(self->menu_btn), model); + GtkPopover *popover = gtk_menu_button_get_popover(GTK_MENU_BUTTON(self->menu_btn)); + gtk_popover_set_has_arrow(popover, FALSE); + gtk_widget_set_halign(GTK_WIDGET(popover), GTK_ALIGN_END); + + // Create Input Box + self->input_box = input_box_new(GTK_WINDOW(self)); // Create widgets self->main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); self->mine_grid = gtk_grid_new(); self->time_label = gtk_label_new(""); - self->status_label = gtk_label_new("Game not started"); + self->status_label = gtk_label_new(""); self->btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); self->btn_start = gtk_button_new_with_label("Start/Reset"); self->btn_show = gtk_button_new_with_label("Show All"); diff --git a/Gtk4_Reset/src/mine_app/ScoresItem.cpp b/Gtk4_Reset/src/mine_app/ScoresItem.cpp new file mode 100644 index 0000000..d82402f --- /dev/null +++ b/Gtk4_Reset/src/mine_app/ScoresItem.cpp @@ -0,0 +1,47 @@ +#include "ScoresItem.h" + +struct _ScoresItem +{ + GObject parent_instance; + char name[NAME_MAX]; + int time; +}; + +G_DEFINE_TYPE(ScoresItem, scores_item, G_TYPE_OBJECT) + +static void scores_item_init(ScoresItem *self) +{ +} + +static void scores_item_class_init(ScoresItemClass *klass) +{ +} + +ScoresItem *scores_item_new(const char *win_name, int win_time) +{ + ScoresItem *item = Scores_Item(g_object_new(scores_item_get_type(), NULL)); + strncpy(item->name, win_name, NAME_MAX); + item->time = win_time; + return item; +} + + +const char *scores_item_get_name(ScoresItem *item) +{ + return item->name; +} + +void scores_item_set_name(ScoresItem *item, const char *win_name) +{ + strncpy(item->name, win_name, NAME_MAX); +} + +int scores_item_get_time(ScoresItem *item) +{ + return item->time; +} + +void scores_item_set_time(ScoresItem *item, int win_time) +{ + item->time = win_time; +} diff --git a/Gtk4_Reset/src/mine_app/ScoresItem.h b/Gtk4_Reset/src/mine_app/ScoresItem.h new file mode 100644 index 0000000..1a01aff --- /dev/null +++ b/Gtk4_Reset/src/mine_app/ScoresItem.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +G_DECLARE_FINAL_TYPE(ScoresItem, scores_item, Scores, Item, GObject) + +ScoresItem *scores_item_new(const char *win_name, int win_time); + +const char *scores_item_get_name(ScoresItem *item); + +void scores_item_set_name(ScoresItem *item, const char *win_name); + +int scores_item_get_time(ScoresItem *item); + +void scores_item_set_time(ScoresItem *item, int win_time); diff --git a/Gtk4_Reset/src/mine_app/ScoresWin.cpp b/Gtk4_Reset/src/mine_app/ScoresWin.cpp new file mode 100644 index 0000000..87b604d --- /dev/null +++ b/Gtk4_Reset/src/mine_app/ScoresWin.cpp @@ -0,0 +1,181 @@ +#include "jsonfile.h" +#include "ScoresWin.h" +#include "ScoresItem.h" +#include + +struct _ScoresWin +{ + GtkWindow parent; + // Child widgets + GtkWidget *scrolled_win; + GtkWidget *main_box; + GtkWidget *btn_close; + + // List widgets + GtkWidget *list_view; + GtkSingleSelection *selection; + GtkListItemFactory *factory_name, *factory_time; + GtkColumnViewColumn *column_name, *column_time; + GListStore *store; +}; + +G_DEFINE_TYPE(ScoresWin, scores_win, GTK_TYPE_WINDOW) + +static gboolean scores_win_closed(GtkWidget *window, ScoresWin *self) +{ + gtk_widget_set_visible(window, FALSE); + return TRUE; +} + +static void btnclose_clicked(GtkButton *btn, GtkWindow *self) +{ + gtk_window_close(self); +} + +static void name_factory_setup(GtkSignalListItemFactory *factory, + GtkListItem *item) +{ + GtkWidget *label = gtk_label_new(""); + gtk_list_item_set_child(item, label); +} + +static void name_factory_bind(GtkListItemFactory *factory, + GtkListItem *item) +{ + // Get child + GtkWidget *label; + label = gtk_list_item_get_child(item); + + // Get Item + ScoresItem *item1 = Scores_Item(gtk_list_item_get_item(item)); + gtk_label_set_label(GTK_LABEL(label), + scores_item_get_name(item1)); +} + +static void time_factory_setup(GtkSignalListItemFactory *factory, + GtkListItem *item) +{ + GtkWidget *label = gtk_label_new(""); + gtk_list_item_set_child(item, label); +} + +static void time_factory_bind(GtkListItemFactory *factory, + GtkListItem *item) +{ + // Get child + GtkWidget *label; + label = gtk_list_item_get_child(item); + + // Get Item + ScoresItem *item1 = Scores_Item(gtk_list_item_get_item(item)); + char *time_str = g_strdup_printf("%d", scores_item_get_time(item1)); + gtk_label_set_label(GTK_LABEL(label), time_str); +} + +static gint sort_func(gpointer a, gpointer b, gpointer user_data) +{ + ScoresItem *item_a = Scores_Item(a); + ScoresItem *item_b = Scores_Item(b); + int time_a = scores_item_get_time(item_a); + int time_b = scores_item_get_time(item_b); + + // Return result + if (time_a > time_b) + { + return 1; + } + + if (time_a == time_b) + { + return 0; + } + + if (time_a < time_b) + { + return -1; + } + return 0; +} + +static void scores_win_init(ScoresWin *self) +{ + // Initalize window + gtk_window_set_title(GTK_WINDOW(self), "Scores"); + gtk_window_set_icon_name(GTK_WINDOW(self), "mines_app"); + + // Create widgets + self->main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + self->scrolled_win = gtk_scrolled_window_new(); + self->btn_close = gtk_button_new_with_label("Close"); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self->scrolled_win), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_set_size_request(self->scrolled_win, 300, 400); + gtk_widget_set_halign(self->btn_close, GTK_ALIGN_CENTER); + + // Link signals + g_signal_connect(self->btn_close, "clicked", G_CALLBACK(btnclose_clicked), self); + g_signal_connect(self, "close-request", G_CALLBACK(scores_win_closed), self); + + // Create store + self->store = g_list_store_new(scores_item_get_type()); + self->selection = gtk_single_selection_new(G_LIST_MODEL(self->store)); + + // Create Column View + self->list_view = gtk_column_view_new(GTK_SELECTION_MODEL(self->selection)); + + // Create factory for name + self->factory_name = gtk_signal_list_item_factory_new(); + g_signal_connect(self->factory_name, "bind", G_CALLBACK(name_factory_bind), NULL); + g_signal_connect(self->factory_name, "setup", G_CALLBACK(name_factory_setup), NULL); + self->column_name = gtk_column_view_column_new("Name", self->factory_name); + gtk_column_view_append_column(GTK_COLUMN_VIEW(self->list_view), self->column_name); + + // Create factory for time + self->factory_time = gtk_signal_list_item_factory_new(); + g_signal_connect(self->factory_time, "bind", G_CALLBACK(time_factory_bind), NULL); + g_signal_connect(self->factory_time, "setup", G_CALLBACK(time_factory_setup), NULL); + self->column_time = gtk_column_view_column_new("Time", self->factory_time); + gtk_column_view_append_column(GTK_COLUMN_VIEW(self->list_view), self->column_time); + + // Pack widgets + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_win), self->list_view); + gtk_box_append(GTK_BOX(self->main_box), self->scrolled_win); + gtk_box_append(GTK_BOX(self->main_box), self->btn_close); + gtk_window_set_child(GTK_WINDOW(self), self->main_box); +} + +static void scores_win_class_init(ScoresWinClass *klass) +{ +} + +ScoresWin *scores_win_new(GtkWindow *parent) +{ + return Scores_Win(g_object_new(scores_win_get_type(), + "transient-for", parent, NULL)); +} + +void scores_win_update_and_show(ScoresWin *self) +{ + std::fstream infile; + infile.open("scores.json", std::ios_base::in); + + if (infile.is_open()) + { + // Read data from json file + json data = json::parse(infile); + std::vector name_vec = data["name"]; + std::vector time_vec = data["time"]; + + // Clear the store + g_list_store_remove_all(self->store); + + // Append data to the store + for (int i = 0; i < name_vec.size(); i++) + { + g_list_store_append(self->store, + scores_item_new(name_vec[i].c_str(), time_vec[i])); + } + g_list_store_sort(self->store, (GCompareDataFunc)sort_func, NULL); + } + gtk_window_present(GTK_WINDOW(self)); +} diff --git a/Gtk4_Reset/src/mine_app/ScoresWin.h b/Gtk4_Reset/src/mine_app/ScoresWin.h new file mode 100644 index 0000000..44d43af --- /dev/null +++ b/Gtk4_Reset/src/mine_app/ScoresWin.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +G_DECLARE_FINAL_TYPE(ScoresWin, scores_win, Scores, Win, GtkWindow) + +ScoresWin *scores_win_new(GtkWindow *parent); + +void scores_win_update_and_show(ScoresWin *self); diff --git a/Gtk4_Reset/src/mine_app/jsonfile.h b/Gtk4_Reset/src/mine_app/jsonfile.h new file mode 100644 index 0000000..6fbdabb --- /dev/null +++ b/Gtk4_Reset/src/mine_app/jsonfile.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../json_nlohmann/json.hpp" +#include +#include + +using json = nlohmann::json;