diff options
author | Gunnar Beutner <gbeutner@serenityos.org> | 2021-05-23 23:20:36 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-05-25 21:05:35 +0200 |
commit | ac1eba2f7cfff02d4558191ff9dcbc5eb8dfb440 (patch) | |
tree | 83b274b3a75bc793e4d8689d22db2713d8940591 /Userland/Games | |
parent | fab073f33cefdb4053ce00d1daacc8b91a13f9ff (diff) | |
download | serenity-ac1eba2f7cfff02d4558191ff9dcbc5eb8dfb440.zip |
Hearts: Implement passing cards to other players
Fixes #7375.
Diffstat (limited to 'Userland/Games')
-rw-r--r-- | Userland/Games/Hearts/Game.cpp | 201 | ||||
-rw-r--r-- | Userland/Games/Hearts/Game.h | 25 | ||||
-rw-r--r-- | Userland/Games/Hearts/Player.cpp | 19 | ||||
-rw-r--r-- | Userland/Games/Hearts/Player.h | 8 |
4 files changed, 248 insertions, 5 deletions
diff --git a/Userland/Games/Hearts/Game.cpp b/Userland/Games/Hearts/Game.cpp index 7c29907db4..efc550ff03 100644 --- a/Userland/Games/Hearts/Game.cpp +++ b/Userland/Games/Hearts/Game.cpp @@ -9,6 +9,7 @@ #include "Helpers.h" #include <AK/Debug.h> #include <AK/QuickSort.h> +#include <LibGUI/Button.h> #include <LibGUI/Painter.h> #include <LibGfx/Font.h> #include <LibGfx/Palette.h> @@ -23,6 +24,7 @@ Game::Game() srand(time(nullptr)); m_delay_timer = Core::Timer::create_single_shot(0, [this] { + dbgln_if(HEARTS_DEBUG, "Continuing game after delay..."); advance_game(); }); @@ -74,6 +76,18 @@ Game::Game() m_players[3].name = "Lisa"; m_players[3].taken_cards_target = { width, height / 2 - Card::height / 2 }; + m_passing_button = add<GUI::Button>("Pass Left"); + constexpr int button_width = 120; + constexpr int button_height = 30; + m_passing_button->set_relative_rect(width / 2 - button_width / 2, height - 3 * outer_border_size - Card::height - button_height, button_width, button_height); + m_passing_button->on_click = [this](unsigned int) { + if (m_state == State::PassingSelect) + m_state = State::PassingSelectConfirmed; + else + m_state = State::Play; + advance_game(); + }; + reset(); }; @@ -88,6 +102,31 @@ void Game::reset() stop_animation(); + m_hand_number = 0; + + m_passing_button->set_enabled(false); + m_passing_button->set_visible(false); + + if (m_hand_number % 4 != 3) { + m_state = State::PassingSelect; + m_human_can_play = true; + switch (passing_direction()) { + case PassingDirection::Left: + m_passing_button->set_text("Pass Left"); + break; + case PassingDirection::Across: + m_passing_button->set_text("Pass Across"); + break; + case PassingDirection::Right: + m_passing_button->set_text("Pass Right"); + break; + default: + VERIFY_NOT_REACHED(); + } + } else + m_state = State::Play; + m_cards_highlighted.clear(); + m_trick.clear_with_capacity(); m_trick_number = 0; @@ -103,6 +142,11 @@ void Game::setup(String player_name) reset(); + if (m_hand_number % 4 != 3) { + m_passing_button->set_visible(true); + m_passing_button->set_focus(false); + } + NonnullRefPtrVector<Card> deck; for (int i = 0; i < Card::card_count; ++i) { @@ -125,7 +169,9 @@ void Game::setup(String player_name) reposition_hand(player); } - advance_game(); + update(); + + continue_game_after_delay(); } void Game::start_animation(NonnullRefPtrVector<Card> cards, Gfx::IntPoint const& end, Function<void()> did_finish_callback, int initial_delay_ms, int steps) @@ -296,6 +342,32 @@ void Game::advance_game() return; } + if (m_state == State::PassingSelect) { + if (!m_players[0].is_human) { + select_cards_for_passing(); + m_state = State::PassingSelectConfirmed; + continue_game_after_delay(); + } + return; + } + + if (m_state == State::PassingSelectConfirmed) { + pass_cards(); + continue_game_after_delay(); + return; + } + + if (m_state == State::PassingAccept) { + if (!m_players[0].is_human) { + m_state = State::Play; + continue_game_after_delay(); + } + return; + } + + clear_highlighted_cards(); + m_passing_button->set_visible(false); + if (m_trick_number == 0 && m_trick.is_empty()) { // Find whoever has 2 of Clubs, they get to play the first card for (auto& player : m_players) { @@ -364,8 +436,10 @@ void Game::keydown_event(GUI::KeyEvent& event) m_players[0].is_human = !m_players[0].is_human; advance_game(); } else if (event.key() == KeyCode::Key_F10) { - if (m_human_can_play) + if (m_human_can_play && m_state == State::Play) play_card(m_players[0], pick_card(m_players[0])); + else if (m_state == State::PassingSelect) + select_cards_for_passing(); } else if (event.shift() && event.key() == KeyCode::Key_F11) dump_state(); } @@ -464,7 +538,18 @@ bool Game::are_hearts_broken() const return false; } -void Game::card_clicked(size_t card_index, Card& card) +void Game::card_clicked_during_passing(size_t, Card& card) +{ + if (!is_card_highlighted(card)) { + if (m_cards_highlighted.size() < 3) + highlight_card(card); + } else + unhighlight_card(card); + + m_passing_button->set_enabled(m_cards_highlighted.size() == 3); +} + +void Game::card_clicked_during_play(size_t card_index, Card& card) { String explanation; if (!is_valid_play(m_players[0], card, &explanation)) { @@ -476,6 +561,14 @@ void Game::card_clicked(size_t card_index, Card& card) update(); } +void Game::card_clicked(size_t card_index, Card& card) +{ + if (m_state == State::PassingSelect) + card_clicked_during_passing(card_index, card); + else + card_clicked_during_play(card_index, card); +} + void Game::mouseup_event(GUI::MouseEvent& event) { GUI::Frame::mouseup_event(event); @@ -520,15 +613,115 @@ bool Game::is_winner(Player& player) return (max_score.value() != sum_points_of_all_cards && player_score == min_score.value()) || player_score == sum_points_of_all_cards; } +static constexpr int card_highlight_offset = -20; + +bool Game::is_card_highlighted(Card& card) +{ + return m_cards_highlighted.contains(card); +} + +void Game::highlight_card(Card& card) +{ + VERIFY(!m_cards_highlighted.contains(card)); + m_cards_highlighted.set(card); + card.set_position(card.position().translated(0, card_highlight_offset)); + update(); +} + +void Game::unhighlight_card(Card& card) +{ + VERIFY(m_cards_highlighted.contains(card)); + m_cards_highlighted.remove(card); + card.set_position(card.position().translated(0, -card_highlight_offset)); + update(); +} + +void Game::clear_highlighted_cards() +{ + for (auto& card : m_cards_highlighted) + card->set_position(card->position().translated(0, -card_highlight_offset)); + m_cards_highlighted.clear(); +} + void Game::reposition_hand(Player& player) { auto card_position = player.first_card_position; for (auto& card : player.hand) { - card->set_position(card_position); + card->set_position(is_card_highlighted(*card) ? card_position.translated(0, card_highlight_offset) : card_position); card_position.translate_by(player.card_offset); } } +void Game::select_cards_for_passing() +{ + clear_highlighted_cards(); + auto selected_cards = m_players[0].pick_cards_to_pass(passing_direction()); + highlight_card(selected_cards[0]); + highlight_card(selected_cards[1]); + highlight_card(selected_cards[2]); + m_passing_button->set_enabled(true); +} + +void Game::pass_cards() +{ + NonnullRefPtrVector<Card> first_player_cards; + for (auto& card : m_cards_highlighted) + first_player_cards.append(*card); + clear_highlighted_cards(); + VERIFY(first_player_cards.size() == 3); + + NonnullRefPtrVector<Card> passed_cards[4]; + passed_cards[0] = first_player_cards; + passed_cards[1] = m_players[1].pick_cards_to_pass(passing_direction()); + passed_cards[2] = m_players[2].pick_cards_to_pass(passing_direction()); + passed_cards[3] = m_players[3].pick_cards_to_pass(passing_direction()); + + for (size_t i = 0; i < 4; i++) { + m_players[i].remove_cards(passed_cards[i]); + + int destination_player_index = i; + switch (passing_direction()) { + case PassingDirection::Left: + destination_player_index += 1; + break; + case PassingDirection::Across: + destination_player_index += 2; + break; + case PassingDirection::Right: + destination_player_index += 3; + break; + default: + VERIFY_NOT_REACHED(); + } + destination_player_index %= 4; + + for (auto& card : passed_cards[i]) { + m_players[destination_player_index].hand.append(card); + if constexpr (!HEARTS_DEBUG) + card.set_upside_down(destination_player_index != 0); + if (destination_player_index == 0) + highlight_card(card); + } + } + + for (auto& player : m_players) { + VERIFY(player.hand.size() == 13); + player.sort_hand(); + reposition_hand(player); + } + + m_state = State::PassingAccept; + m_passing_button->set_text("OK"); + m_passing_button->set_enabled(true); + update(); +} + +PassingDirection Game::passing_direction() const +{ + VERIFY(m_hand_number % 4 != 3); + return static_cast<PassingDirection>(m_hand_number % 4); +} + void Game::paint_event(GUI::PaintEvent& event) { GUI::Frame::paint_event(event); diff --git a/Userland/Games/Hearts/Game.h b/Userland/Games/Hearts/Game.h index 54f3a37373..20e8385200 100644 --- a/Userland/Games/Hearts/Game.h +++ b/Userland/Games/Hearts/Game.h @@ -49,7 +49,14 @@ private: bool other_player_has_lower_value_card(Player& player, Card& card); bool other_player_has_higher_value_card(Player& player, Card& card); - void reposition_hand(Player& player); + void reposition_hand(Player&); + bool is_card_highlighted(Card& card); + void clear_highlighted_cards(); + void highlight_card(Card& card); + void unhighlight_card(Card& card); + void select_cards_for_passing(); + void pass_cards(); + PassingDirection passing_direction() const; void start_animation(NonnullRefPtrVector<Card> cards, Gfx::IntPoint const& end, Function<void()> did_finish_callback, int initial_delay_ms, int steps = 30); void stop_animation(); @@ -60,6 +67,22 @@ private: virtual void timer_event(Core::TimerEvent&) override; void card_clicked(size_t card_index, Card& card); + void card_clicked_during_passing(size_t card_index, Card& card); + void card_clicked_during_play(size_t card_index, Card& card); + + RefPtr<GUI::Button> m_passing_button; + + enum class State { + PassingSelect, + PassingSelectConfirmed, + PassingAccept, + Play, + }; + + State m_state { State::PassingSelect }; + int m_hand_number { 0 }; + + HashTable<NonnullRefPtr<Card>> m_cards_highlighted; Player m_players[4]; NonnullRefPtrVector<Card> m_trick; diff --git a/Userland/Games/Hearts/Player.cpp b/Userland/Games/Hearts/Player.cpp index 92c8706845..c42194734e 100644 --- a/Userland/Games/Hearts/Player.cpp +++ b/Userland/Games/Hearts/Player.cpp @@ -11,6 +11,16 @@ namespace Hearts { +NonnullRefPtrVector<Card> Player::pick_cards_to_pass(PassingDirection) +{ + auto sorted_hand = hand_sorted_by_points_and_value(); + NonnullRefPtrVector<Card> cards; + cards.append(*sorted_hand[0].card); + cards.append(*sorted_hand[1].card); + cards.append(*sorted_hand[2].card); + return cards; +} + Vector<CardWithIndex> Player::hand_sorted_by_points_and_value() const { Vector<CardWithIndex> sorted_hand; @@ -149,4 +159,13 @@ bool Player::has_card_of_type(Card::Type type) return matching_card.has_value(); } +void Player::remove_cards(const NonnullRefPtrVector<Card>& cards) +{ + for (auto& card : cards) { + hand.remove_first_matching([&card](auto& other_card) { + return other_card == card; + }); + } +} + } diff --git a/Userland/Games/Hearts/Player.h b/Userland/Games/Hearts/Player.h index 73435d5109..f225c2a18d 100644 --- a/Userland/Games/Hearts/Player.h +++ b/Userland/Games/Hearts/Player.h @@ -14,6 +14,12 @@ using Cards::Card; namespace Hearts { +enum class PassingDirection { + Left, + Right, + Across +}; + struct CardWithIndex { NonnullRefPtr<Card> card; size_t index; @@ -27,6 +33,7 @@ public: { } + NonnullRefPtrVector<Card> pick_cards_to_pass(PassingDirection); size_t pick_lead_card(Function<bool(Card&)>, Function<bool(Card&)>, Function<bool(Card&)>); Optional<size_t> pick_low_points_high_value_card(Optional<Card::Type> type = {}); Optional<size_t> pick_lower_value_card(Card& other_card); @@ -38,6 +45,7 @@ public: Vector<CardWithIndex> hand_sorted_by_points_and_value() const; void sort_hand() { quick_sort(hand, hearts_card_less); } + void remove_cards(NonnullRefPtrVector<Card> const& cards); Vector<RefPtr<Card>> hand; Vector<RefPtr<Card>> cards_taken; |