瀏覽代碼

Snake: Use a statusbar to display the current and high score

The food bitmaps would sometimes be placed underneath the score text,
which was a bit hard to see. Use a statusbar like we do in other games
like Solitaire.

Note the default height change of the Snake window is to make the inner
game widget fit exactly 20x20 cells.
Timothy Flynn 2 年之前
父節點
當前提交
661c02b914
共有 4 個文件被更改,包括 36 次插入33 次删除
  1. 9 27
      Userland/Games/Snake/Game.cpp
  2. 2 5
      Userland/Games/Snake/Game.h
  3. 5 0
      Userland/Games/Snake/Snake.gml
  4. 20 1
      Userland/Games/Snake/main.cpp

+ 9 - 27
Userland/Games/Snake/Game.cpp

@@ -74,8 +74,7 @@ Game::Game()
 {
     set_font(Gfx::FontDatabase::default_fixed_width_font().bold_variant());
     reset();
-    m_high_score = Config::read_i32("Snake"sv, "Snake"sv, "HighScore"sv, 0);
-    m_high_score_text = DeprecatedString::formatted("Best: {}", m_high_score);
+
     m_snake_base_color = Color::from_argb(Config::read_u32("Snake"sv, "Snake"sv, "BaseColor"sv, m_snake_base_color.value()));
 }
 
@@ -96,9 +95,12 @@ void Game::reset()
     m_tail.clear_with_capacity();
     m_length = 2;
     m_score = 0;
-    m_score_text = "Score: 0";
     m_is_new_high_score = false;
     m_velocity_queue.clear();
+
+    if (on_score_update)
+        on_score_update(m_score);
+
     pause();
     start();
     spawn_fruit();
@@ -137,18 +139,6 @@ void Game::spawn_fruit()
     m_fruit_type = get_random_uniform(m_food_bitmaps.size());
 }
 
-Gfx::IntRect Game::score_rect() const
-{
-    int score_width = font().width(m_score_text);
-    return { frame_inner_rect().width() - score_width - 2, frame_inner_rect().height() - font().glyph_height() - 2, score_width, font().glyph_height() };
-}
-
-Gfx::IntRect Game::high_score_rect() const
-{
-    int high_score_width = font().width(m_high_score_text);
-    return { frame_thickness() + 2, frame_inner_rect().height() - font().glyph_height() - 2, high_score_width, font().glyph_height() };
-}
-
 void Game::timer_event(Core::TimerEvent&)
 {
     Vector<Coordinate> dirty_cells;
@@ -191,15 +181,10 @@ void Game::timer_event(Core::TimerEvent&)
     if (m_head == m_fruit) {
         ++m_length;
         ++m_score;
-        m_score_text = DeprecatedString::formatted("Score: {}", m_score);
-        if (m_score > m_high_score) {
-            m_is_new_high_score = true;
-            m_high_score = m_score;
-            m_high_score_text = DeprecatedString::formatted("Best: {}", m_high_score);
-            update(high_score_rect());
-            Config::write_i32("Snake"sv, "Snake"sv, "HighScore"sv, m_high_score);
-        }
-        update(score_rect());
+
+        if (on_score_update)
+            m_is_new_high_score = on_score_update(m_score);
+
         dirty_cells.append(m_fruit);
         spawn_fruit();
         dirty_cells.append(m_fruit);
@@ -279,9 +264,6 @@ void Game::paint_event(GUI::PaintEvent& event)
     }
 
     painter.draw_scaled_bitmap(cell_rect(m_fruit), m_food_bitmaps[m_fruit_type], m_food_bitmaps[m_fruit_type].rect());
-
-    painter.draw_text(high_score_rect(), m_high_score_text, Gfx::TextAlignment::TopLeft, Color::from_rgb(0xfafae0));
-    painter.draw_text(score_rect(), m_score_text, Gfx::TextAlignment::TopLeft, Color::White);
 }
 
 void Game::game_over()

+ 2 - 5
Userland/Games/Snake/Game.h

@@ -25,6 +25,8 @@ public:
 
     void set_snake_base_color(Color color);
 
+    Function<bool(u32)> on_score_update;
+
 private:
     Game();
 
@@ -53,8 +55,6 @@ private:
     void queue_velocity(int v, int h);
     Velocity const& last_velocity() const;
     Gfx::IntRect cell_rect(Coordinate const&) const;
-    Gfx::IntRect score_rect() const;
-    Gfx::IntRect high_score_rect() const;
 
     int m_rows { 20 };
     int m_columns { 20 };
@@ -72,9 +72,6 @@ private:
 
     size_t m_length { 0 };
     unsigned m_score { 0 };
-    DeprecatedString m_score_text;
-    unsigned m_high_score { 0 };
-    DeprecatedString m_high_score_text;
     bool m_is_new_high_score { false };
 
     NonnullRefPtrVector<Gfx::Bitmap> m_food_bitmaps;

+ 5 - 0
Userland/Games/Snake/Snake.gml

@@ -6,4 +6,9 @@
         name: "game"
         fill_with_background_color: true
     }
+
+    @GUI::Statusbar {
+        name: "statusbar"
+        segment_count: 2
+    }
 }

+ 20 - 1
Userland/Games/Snake/main.cpp

@@ -18,6 +18,7 @@
 #include <LibGUI/Icon.h>
 #include <LibGUI/Menu.h>
 #include <LibGUI/Menubar.h>
+#include <LibGUI/Statusbar.h>
 #include <LibGUI/Window.h>
 #include <LibMain/Main.h>
 #include <stdio.h>
@@ -45,7 +46,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
 
     window->set_double_buffering_enabled(false);
     window->set_title("Snake");
-    window->resize(324, 344);
+    window->resize(324, 345);
 
     auto widget = TRY(window->try_set_main_widget<GUI::Widget>());
     widget->load_from_gml(snake_gml);
@@ -53,6 +54,24 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     auto& game = *widget->find_descendant_of_type_named<Snake::Game>("game");
     game.set_focus(true);
 
+    auto high_score = Config::read_u32("Snake"sv, "Snake"sv, "HighScore"sv, 0);
+
+    auto& statusbar = *widget->find_descendant_of_type_named<GUI::Statusbar>("statusbar"sv);
+    statusbar.set_text(0, "Score: 0"sv);
+    statusbar.set_text(1, DeprecatedString::formatted("High Score: {}", high_score));
+
+    game.on_score_update = [&](auto score) {
+        statusbar.set_text(0, DeprecatedString::formatted("Score: {}", score));
+        if (score <= high_score)
+            return false;
+
+        statusbar.set_text(1, DeprecatedString::formatted("High Score: {}", score));
+        Config::write_u32("Snake"sv, "Snake"sv, "HighScore"sv, score);
+
+        high_score = score;
+        return true;
+    };
+
     auto game_menu = TRY(window->try_add_menu("&Game"));
 
     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"sv)), [&](auto&) {