2021-06-15 21:37:29 -03:00
/*
* Copyright ( c ) 2021 , Jamie Mansfield < jmansfield @ cadixdev . org >
2022-01-06 15:04:19 -03:00
* Copyright ( c ) 2022 , Jonas Höpner < me @ jonashoepner . de >
2022-02-16 15:44:57 -03:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2021-06-15 21:37:29 -03:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include "Game.h"
2021-07-23 19:58:40 -03:00
# include <AK/Random.h>
2022-01-06 15:04:19 -03:00
# include <LibGUI/Event.h>
2021-06-15 21:37:29 -03:00
# include <LibGUI/Painter.h>
# include <LibGfx/Palette.h>
REGISTER_WIDGET ( Spider , Game ) ;
namespace Spider {
static constexpr uint8_t new_game_animation_delay = 2 ;
2021-12-01 20:36:24 -03:00
static constexpr uint8_t draw_animation_delay = 2 ;
2021-06-15 21:37:29 -03:00
static constexpr int s_timer_interval_ms = 1000 / 60 ;
Game : : Game ( )
{
m_stacks . append ( adopt_ref ( * new CardStack ( { 10 , Game : : height - Card : : height - 10 } , CardStack : : Type : : Waste ) ) ) ;
m_stacks . append ( adopt_ref ( * new CardStack ( { Game : : width - Card : : width - 10 , Game : : height - Card : : height - 10 } , CardStack : : Type : : Stock ) ) ) ;
for ( int i = 0 ; i < 10 ; i + + ) {
m_stacks . append ( adopt_ref ( * new CardStack ( { 10 + i * ( Card : : width + 10 ) , 10 } , CardStack : : Type : : Normal ) ) ) ;
}
}
void Game : : setup ( Mode mode )
{
if ( m_new_game_animation )
stop_timer ( ) ;
m_mode = mode ;
if ( on_game_end )
on_game_end ( GameOverReason : : NewGame , m_score ) ;
for ( auto & stack : m_stacks )
stack . clear ( ) ;
m_new_game_animation_pile = 0 ;
m_score = 500 ;
update_score ( 0 ) ;
2021-07-23 19:57:36 -03:00
NonnullRefPtrVector < Card > deck ;
deck . ensure_capacity ( Card : : card_count * 2 ) ;
2021-06-15 21:37:29 -03:00
for ( int i = 0 ; i < Card : : card_count ; + + i ) {
switch ( m_mode ) {
case Mode : : SingleSuit :
for ( int j = 0 ; j < 8 ; j + + ) {
2022-08-20 16:21:33 -03:00
deck . append ( Card : : construct ( Cards : : Suit : : Spades , static_cast < Cards : : Rank > ( i ) ) ) ;
2021-06-15 21:37:29 -03:00
}
break ;
case Mode : : TwoSuit :
for ( int j = 0 ; j < 4 ; j + + ) {
2022-08-20 16:21:33 -03:00
deck . append ( Card : : construct ( Cards : : Suit : : Spades , static_cast < Cards : : Rank > ( i ) ) ) ;
deck . append ( Card : : construct ( Cards : : Suit : : Hearts , static_cast < Cards : : Rank > ( i ) ) ) ;
2021-06-15 21:37:29 -03:00
}
break ;
default :
VERIFY_NOT_REACHED ( ) ;
break ;
}
}
2021-07-23 19:57:36 -03:00
m_new_deck . clear_with_capacity ( ) ;
m_new_deck . ensure_capacity ( deck . size ( ) ) ;
while ( ! deck . is_empty ( ) )
2021-07-23 19:58:40 -03:00
m_new_deck . append ( deck . take ( get_random_uniform ( deck . size ( ) ) ) ) ;
2021-06-15 21:37:29 -03:00
2021-08-05 07:11:08 -03:00
m_focused_stack = nullptr ;
m_focused_cards . clear ( ) ;
2021-06-15 21:37:29 -03:00
m_new_game_animation = true ;
start_timer ( s_timer_interval_ms ) ;
update ( ) ;
}
void Game : : start_timer_if_necessary ( )
{
if ( on_game_start & & m_waiting_for_new_game ) {
on_game_start ( ) ;
m_waiting_for_new_game = false ;
}
}
void Game : : update_score ( int delta )
{
m_score = max ( static_cast < int > ( m_score ) + delta , 0 ) ;
if ( on_score_update )
on_score_update ( m_score ) ;
}
void Game : : draw_cards ( )
{
// draw a single card from the stock for each pile
auto & stock_pile = stack ( Stock ) ;
if ( stock_pile . is_empty ( ) )
return ;
update_score ( - 1 ) ;
2021-12-01 20:36:24 -03:00
m_draw_animation = true ;
2022-01-02 13:20:01 -03:00
m_original_stock_rect = stock_pile . bounding_box ( ) ;
2021-12-01 20:36:24 -03:00
start_timer ( s_timer_interval_ms ) ;
2021-07-15 11:57:03 -03:00
}
2021-06-15 21:37:29 -03:00
2021-07-15 11:57:03 -03:00
void Game : : mark_intersecting_stacks_dirty ( Card & intersecting_card )
{
for ( auto & stack : m_stacks ) {
if ( intersecting_card . rect ( ) . intersects ( stack . bounding_box ( ) ) )
update ( stack . bounding_box ( ) ) ;
}
update ( intersecting_card . rect ( ) ) ;
2021-06-15 21:37:29 -03:00
}
2021-07-15 13:00:07 -03:00
void Game : : ensure_top_card_is_visible ( NonnullRefPtr < CardStack > stack )
{
if ( stack - > is_empty ( ) )
return ;
auto & top_card = stack - > peek ( ) ;
if ( top_card . is_upside_down ( ) ) {
top_card . set_upside_down ( false ) ;
update ( top_card . rect ( ) ) ;
}
}
2021-06-15 21:37:29 -03:00
void Game : : detect_full_stacks ( )
{
auto & completed_stack = stack ( Completed ) ;
for ( auto pile : piles ) {
auto & current_pile = stack ( pile ) ;
bool started = false ;
uint8_t last_value ;
Color color ;
for ( size_t i = current_pile . stack ( ) . size ( ) ; i > 0 ; i - - ) {
auto & card = current_pile . stack ( ) . at ( i - 1 ) ;
if ( card . is_upside_down ( ) )
break ;
if ( ! started ) {
2022-08-20 16:21:33 -03:00
if ( card . rank ( ) ! = Cards : : Rank : : Ace )
2021-06-15 21:37:29 -03:00
break ;
started = true ;
color = card . color ( ) ;
2022-08-20 16:21:33 -03:00
} else if ( to_underlying ( card . rank ( ) ) ! = last_value + 1 | | card . color ( ) ! = color ) {
2021-06-15 21:37:29 -03:00
break ;
2022-08-20 16:21:33 -03:00
} else if ( card . rank ( ) = = Cards : : Rank : : King ) {
2021-06-15 21:37:29 -03:00
// we have a full set
2021-07-15 11:57:03 -03:00
auto original_current_rect = current_pile . bounding_box ( ) ;
2021-06-15 21:37:29 -03:00
for ( size_t j = 0 ; j < Card : : card_count ; j + + ) {
completed_stack . push ( current_pile . pop ( ) ) ;
}
2021-07-15 11:57:03 -03:00
update ( original_current_rect ) ;
update ( completed_stack . bounding_box ( ) ) ;
2021-07-15 13:00:07 -03:00
ensure_top_card_is_visible ( current_pile ) ;
2021-06-15 21:37:29 -03:00
update_score ( 101 ) ;
}
2022-08-20 16:21:33 -03:00
last_value = to_underlying ( card . rank ( ) ) ;
2021-06-15 21:37:29 -03:00
}
}
detect_victory ( ) ;
}
void Game : : detect_victory ( )
{
for ( auto pile : piles ) {
auto & current_pile = stack ( pile ) ;
if ( ! current_pile . is_empty ( ) )
return ;
}
if ( on_game_end )
on_game_end ( GameOverReason : : Victory , m_score ) ;
}
void Game : : paint_event ( GUI : : PaintEvent & event )
{
2022-08-20 10:18:12 -03:00
Gfx : : Color background_color = this - > background_color ( ) ;
2021-06-15 21:37:29 -03:00
GUI : : Frame : : paint_event ( event ) ;
GUI : : Painter painter ( * this ) ;
painter . add_clip_rect ( frame_inner_rect ( ) ) ;
painter . add_clip_rect ( event . rect ( ) ) ;
if ( ! m_focused_cards . is_empty ( ) ) {
for ( auto & focused_card : m_focused_cards )
2022-08-20 10:18:12 -03:00
focused_card . clear ( painter , background_color ) ;
2021-06-15 21:37:29 -03:00
}
for ( auto & stack : m_stacks ) {
2022-08-20 10:18:12 -03:00
stack . draw ( painter , background_color ) ;
2021-06-15 21:37:29 -03:00
}
if ( ! m_focused_cards . is_empty ( ) ) {
for ( auto & focused_card : m_focused_cards ) {
focused_card . draw ( painter ) ;
focused_card . save_old_position ( ) ;
}
}
if ( ! m_mouse_down ) {
if ( ! m_focused_cards . is_empty ( ) ) {
for ( auto & card : m_focused_cards )
card . set_moving ( false ) ;
m_focused_cards . clear ( ) ;
}
if ( m_focused_stack ) {
m_focused_stack - > set_focused ( false ) ;
m_focused_stack = nullptr ;
}
}
}
void Game : : mousedown_event ( GUI : : MouseEvent & event )
{
GUI : : Frame : : mousedown_event ( event ) ;
2021-12-01 20:36:24 -03:00
if ( m_new_game_animation | | m_draw_animation )
2021-06-15 21:37:29 -03:00
return ;
auto click_location = event . position ( ) ;
for ( auto & to_check : m_stacks ) {
if ( to_check . type ( ) = = CardStack : : Type : : Waste )
continue ;
if ( to_check . bounding_box ( ) . contains ( click_location ) ) {
if ( to_check . type ( ) = = CardStack : : Type : : Stock ) {
2021-07-27 08:42:50 -03:00
start_timer_if_necessary ( ) ;
2021-06-15 21:37:29 -03:00
draw_cards ( ) ;
} else if ( ! to_check . is_empty ( ) ) {
auto & top_card = to_check . peek ( ) ;
if ( top_card . is_upside_down ( ) ) {
if ( top_card . rect ( ) . contains ( click_location ) ) {
top_card . set_upside_down ( false ) ;
start_timer_if_necessary ( ) ;
update ( top_card . rect ( ) ) ;
}
} else if ( m_focused_cards . is_empty ( ) ) {
2022-03-17 18:01:03 -03:00
to_check . add_all_grabbed_cards ( click_location , m_focused_cards , Cards : : CardStack : : MovementRule : : Same ) ;
2021-06-15 21:37:29 -03:00
m_mouse_down_location = click_location ;
if ( m_focused_stack )
m_focused_stack - > set_focused ( false ) ;
to_check . set_focused ( true ) ;
m_focused_stack = & to_check ;
2022-01-06 15:04:19 -03:00
// When the user wants to automatically move cards, do not go into the drag mode.
if ( event . button ( ) ! = GUI : : MouseButton : : Secondary )
m_mouse_down = true ;
2021-06-15 21:37:29 -03:00
start_timer_if_necessary ( ) ;
}
}
break ;
}
}
}
2022-01-06 15:04:19 -03:00
void Game : : move_focused_cards ( CardStack & stack )
{
for ( auto & to_intersect : m_focused_cards ) {
mark_intersecting_stacks_dirty ( to_intersect ) ;
stack . push ( to_intersect ) ;
( void ) m_focused_stack - > pop ( ) ;
}
update_score ( - 1 ) ;
update ( m_focused_stack - > bounding_box ( ) ) ;
update ( stack . bounding_box ( ) ) ;
detect_full_stacks ( ) ;
ensure_top_card_is_visible ( * m_focused_stack ) ;
}
2021-06-15 21:37:29 -03:00
void Game : : mouseup_event ( GUI : : MouseEvent & event )
{
GUI : : Frame : : mouseup_event ( event ) ;
2021-12-01 20:36:24 -03:00
if ( ! m_focused_stack | | m_focused_cards . is_empty ( ) | | m_new_game_animation | | m_draw_animation )
2021-06-15 21:37:29 -03:00
return ;
bool rebound = true ;
2022-01-06 15:04:19 -03:00
if ( event . button ( ) = = GUI : : MouseButton : : Secondary ) {
// This enables the game to move the focused cards to the first possible stack excluding empty stacks.
2022-05-29 07:50:10 -03:00
// NOTE: This ignores empty stacks, as the game has no undo button, and a card, which has been moved to an empty stack without any other possibilities is not reversible.
2022-01-06 15:04:19 -03:00
for ( auto & stack : m_stacks ) {
if ( stack . is_focused ( ) )
continue ;
2021-06-15 21:37:29 -03:00
2022-03-17 18:01:03 -03:00
if ( stack . is_allowed_to_push ( m_focused_cards . at ( 0 ) , m_focused_cards . size ( ) , Cards : : CardStack : : MovementRule : : Any ) & & ! stack . is_empty ( ) ) {
2022-01-06 15:04:19 -03:00
move_focused_cards ( stack ) ;
2021-07-15 13:00:07 -03:00
2022-01-06 15:04:19 -03:00
rebound = false ;
break ;
}
}
} else {
for ( auto & stack : m_stacks ) {
if ( stack . is_focused ( ) )
continue ;
for ( auto & focused_card : m_focused_cards ) {
if ( stack . bounding_box ( ) . intersects ( focused_card . rect ( ) ) ) {
2022-03-17 18:01:03 -03:00
if ( stack . is_allowed_to_push ( m_focused_cards . at ( 0 ) , m_focused_cards . size ( ) , Cards : : CardStack : : MovementRule : : Any ) ) {
2022-01-06 15:04:19 -03:00
move_focused_cards ( stack ) ;
rebound = false ;
break ;
}
2021-06-15 21:37:29 -03:00
}
}
}
}
if ( rebound ) {
2021-07-15 11:57:03 -03:00
for ( auto & to_intersect : m_focused_cards )
mark_intersecting_stacks_dirty ( to_intersect ) ;
2021-06-15 21:37:29 -03:00
m_focused_stack - > rebound_cards ( ) ;
2021-07-15 11:57:03 -03:00
update ( m_focused_stack - > bounding_box ( ) ) ;
2021-06-15 21:37:29 -03:00
}
m_mouse_down = false ;
}
void Game : : mousemove_event ( GUI : : MouseEvent & event )
{
GUI : : Frame : : mousemove_event ( event ) ;
2021-12-01 20:36:24 -03:00
if ( ! m_mouse_down | | m_new_game_animation | | m_draw_animation )
2021-06-15 21:37:29 -03:00
return ;
auto click_location = event . position ( ) ;
int dx = click_location . dx_relative_to ( m_mouse_down_location ) ;
int dy = click_location . dy_relative_to ( m_mouse_down_location ) ;
for ( auto & to_intersect : m_focused_cards ) {
2021-07-15 11:57:03 -03:00
mark_intersecting_stacks_dirty ( to_intersect ) ;
2021-06-15 21:37:29 -03:00
to_intersect . rect ( ) . translate_by ( dx , dy ) ;
2021-07-15 11:57:03 -03:00
update ( to_intersect . rect ( ) ) ;
2021-06-15 21:37:29 -03:00
}
m_mouse_down_location = click_location ;
}
void Game : : timer_event ( Core : : TimerEvent & )
{
if ( m_new_game_animation ) {
2021-07-15 11:25:54 -03:00
if ( m_new_game_animation_delay < new_game_animation_delay ) {
+ + m_new_game_animation_delay ;
} else {
m_new_game_animation_delay = 0 ;
auto & current_pile = stack ( piles . at ( m_new_game_animation_pile ) ) ;
// for first 4 piles, draw 6 cards
// for last 6 piles, draw 5 cards
size_t cards_to_draw = m_new_game_animation_pile < 4 ? 6 : 5 ;
if ( current_pile . count ( ) < ( cards_to_draw - 1 ) ) {
auto card = m_new_deck . take_last ( ) ;
card - > set_upside_down ( true ) ;
current_pile . push ( card ) ;
} else {
current_pile . push ( m_new_deck . take_last ( ) ) ;
+ + m_new_game_animation_pile ;
}
update ( current_pile . bounding_box ( ) ) ;
if ( m_new_game_animation_pile = = piles . size ( ) ) {
VERIFY ( m_new_deck . size ( ) = = 50 ) ;
auto & stock_pile = stack ( Stock ) ;
while ( ! m_new_deck . is_empty ( ) )
stock_pile . push ( m_new_deck . take_last ( ) ) ;
update ( stock_pile . bounding_box ( ) ) ;
m_new_game_animation = false ;
m_waiting_for_new_game = true ;
stop_timer ( ) ;
}
}
2021-12-01 20:36:24 -03:00
} else if ( m_draw_animation ) {
if ( m_draw_animation_delay < draw_animation_delay ) {
+ + m_draw_animation_delay ;
} else {
auto & stock_pile = stack ( Stock ) ;
auto & current_pile = stack ( piles . at ( m_draw_animation_pile ) ) ;
auto card = stock_pile . pop ( ) ;
card - > set_upside_down ( false ) ;
current_pile . push ( card ) ;
update ( current_pile . bounding_box ( ) ) ;
+ + m_draw_animation_pile ;
if ( m_draw_animation_pile = = piles . size ( ) ) {
2022-01-02 13:20:01 -03:00
update ( m_original_stock_rect ) ;
2021-12-01 20:36:24 -03:00
detect_full_stacks ( ) ;
m_draw_animation = false ;
m_draw_animation_delay = 0 ;
m_draw_animation_pile = 0 ;
stop_timer ( ) ;
}
}
2021-06-15 21:37:29 -03:00
}
}
}