mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 10:28:10 +00:00
parent
fab073f33c
commit
ac1eba2f7c
4 changed files with 248 additions and 5 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue