2020-11-09 07:40:35 -03:00
/*
* Copyright ( c ) 2020 , Andreas Kling < kling @ serenityos . org >
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-11-09 07:40:35 -03:00
*/
# include "Game.h"
2020-12-02 19:49:42 -03:00
# include "LevelSelectDialog.h"
2021-05-14 12:27:38 -03:00
# include <AK/Random.h>
2020-11-09 07:40:35 -03:00
# include <LibGUI/Application.h>
# include <LibGUI/MessageBox.h>
# include <LibGUI/Painter.h>
# include <LibGfx/StandardCursor.h>
2021-03-12 13:29:37 -03:00
# include <unistd.h>
2020-11-09 07:40:35 -03:00
namespace Breakout {
Game : : Game ( )
{
set_override_cursor ( Gfx : : StandardCursor : : Hidden ) ;
2020-12-02 19:49:42 -03:00
auto level_dialog = LevelSelectDialog : : show ( m_board , window ( ) ) ;
if ( level_dialog ! = GUI : : Dialog : : ExecOK )
m_board = - 1 ;
2020-12-19 18:50:48 -03:00
set_paused ( false ) ;
2020-11-09 07:40:35 -03:00
start_timer ( 16 ) ;
reset ( ) ;
}
Game : : ~ Game ( )
{
}
void Game : : reset_paddle ( )
{
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_paddle . rect ) ) ;
2020-11-10 09:55:06 -03:00
m_paddle . moving_left = false ;
m_paddle . moving_right = false ;
2020-11-09 07:40:35 -03:00
m_paddle . rect = { game_width / 2 - 40 , game_height - 20 , 80 , 16 } ;
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_paddle . rect ) ) ;
2020-11-09 07:40:35 -03:00
}
void Game : : reset ( )
{
2021-07-11 20:11:31 -03:00
update ( lives_left_rect ( ) ) ;
2020-12-19 18:50:48 -03:00
m_lives = 3 ;
2021-07-11 20:11:31 -03:00
update ( lives_left_rect ( ) ) ;
2020-12-19 18:50:48 -03:00
m_pause_count = 0 ;
m_cheater = false ;
2020-11-09 07:40:35 -03:00
reset_ball ( ) ;
reset_paddle ( ) ;
generate_bricks ( ) ;
}
void Game : : generate_bricks ( )
{
2020-12-19 18:50:48 -03:00
m_bricks = { } ;
2020-11-09 07:40:35 -03:00
Gfx : : Color colors [ ] = {
Gfx : : Color : : Red ,
Gfx : : Color : : Green ,
Gfx : : Color : : Blue ,
Gfx : : Color : : Yellow ,
Gfx : : Color : : Magenta ,
Gfx : : Color : : Cyan ,
Gfx : : Color : : LightGray ,
} ;
2020-12-02 19:49:42 -03:00
Vector < Brick > boards [ ] = {
// :^)
Vector ( {
Brick ( 0 , 0 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 0 , 4 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 1 , 2 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 1 , 5 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 2 , 1 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 2 , 3 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 2 , 6 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 3 , 6 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 4 , 0 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 4 , 6 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 5 , 6 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 6 , 5 , colors [ 3 ] , 40 , 12 , 100 ) ,
Brick ( 7 , 4 , colors [ 3 ] , 40 , 12 , 100 ) ,
} )
} ;
if ( m_board ! = - 1 ) {
m_bricks = boards [ m_board ] ;
2021-07-11 20:11:31 -03:00
for ( auto & brick : m_bricks )
update ( enclosing_int_rect ( brick . rect ) ) ;
2020-12-02 19:49:42 -03:00
} else {
// Rainbow
for ( int row = 0 ; row < 7 ; + + row ) {
for ( int column = 0 ; column < 10 ; + + column ) {
Brick brick ( row , column , colors [ row ] ) ;
m_bricks . append ( brick ) ;
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( brick . rect ) ) ;
2020-12-02 19:49:42 -03:00
}
2020-11-09 07:40:35 -03:00
}
}
}
2020-12-19 18:50:48 -03:00
void Game : : set_paused ( bool paused )
{
m_paused = paused ;
if ( m_paused ) {
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
m_pause_count + + ;
} else {
set_override_cursor ( Gfx : : StandardCursor : : Hidden ) ;
}
2021-07-11 20:11:31 -03:00
update ( pause_rect ( ) ) ;
2020-12-19 18:50:48 -03:00
}
2020-11-09 07:40:35 -03:00
void Game : : timer_event ( Core : : TimerEvent & )
{
2020-12-19 18:50:48 -03:00
if ( m_paused )
return ;
2020-11-09 07:40:35 -03:00
tick ( ) ;
}
void Game : : paint_event ( GUI : : PaintEvent & event )
{
GUI : : Painter painter ( * this ) ;
painter . add_clip_rect ( event . rect ( ) ) ;
painter . fill_rect ( rect ( ) , Color : : Black ) ;
2020-11-10 09:54:19 -03:00
painter . fill_ellipse ( enclosing_int_rect ( m_ball . rect ( ) ) , Color : : Red ) ;
2020-11-09 07:40:35 -03:00
2020-11-10 09:54:19 -03:00
painter . fill_rect ( enclosing_int_rect ( m_paddle . rect ) , Color : : White ) ;
2020-11-09 07:40:35 -03:00
for ( auto & brick : m_bricks ) {
if ( ! brick . dead )
2020-11-10 09:54:19 -03:00
painter . fill_rect ( enclosing_int_rect ( brick . rect ) , brick . color ) ;
2020-11-09 07:40:35 -03:00
}
2020-12-19 18:50:48 -03:00
2021-07-11 20:11:31 -03:00
painter . draw_text ( lives_left_rect ( ) , String : : formatted ( " Lives: {} " , m_lives ) , Gfx : : TextAlignment : : Center , Color : : White ) ;
2020-12-19 18:50:48 -03:00
if ( m_paused ) {
const char * msg = m_cheater ? " C H E A T E R " : " P A U S E D " ;
2021-07-11 20:11:31 -03:00
painter . draw_text ( pause_rect ( ) , msg , Gfx : : TextAlignment : : Center , Color : : White ) ;
2020-12-19 18:50:48 -03:00
}
2020-11-09 07:40:35 -03:00
}
void Game : : keyup_event ( GUI : : KeyEvent & event )
{
2020-12-19 18:50:48 -03:00
if ( m_paused )
return ;
2020-11-09 07:40:35 -03:00
switch ( event . key ( ) ) {
2021-09-15 02:17:53 -03:00
case Key_A :
2021-09-15 04:22:35 -03:00
[[fallthrough]] ;
2020-11-09 07:40:35 -03:00
case Key_Left :
m_paddle . moving_left = false ;
break ;
2021-09-15 02:17:53 -03:00
case Key_D :
2021-09-15 04:22:35 -03:00
[[fallthrough]] ;
2020-11-09 07:40:35 -03:00
case Key_Right :
m_paddle . moving_right = false ;
break ;
default :
break ;
}
}
void Game : : keydown_event ( GUI : : KeyEvent & event )
{
2020-12-19 18:50:48 -03:00
if ( m_paused )
return ;
2020-11-09 07:40:35 -03:00
switch ( event . key ( ) ) {
case Key_Escape :
GUI : : Application : : the ( ) - > quit ( ) ;
break ;
2021-09-15 02:17:53 -03:00
case Key_A :
2021-09-15 04:22:35 -03:00
[[fallthrough]] ;
2020-11-09 07:40:35 -03:00
case Key_Left :
m_paddle . moving_left = true ;
break ;
2021-09-15 02:17:53 -03:00
case Key_D :
2021-09-15 04:22:35 -03:00
[[fallthrough]] ;
2020-11-09 07:40:35 -03:00
case Key_Right :
m_paddle . moving_right = true ;
break ;
default :
break ;
}
}
void Game : : mousemove_event ( GUI : : MouseEvent & event )
{
2020-12-19 18:50:48 -03:00
if ( m_paused )
return ;
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_paddle . rect ) ) ;
2020-11-10 09:54:19 -03:00
float new_paddle_x = event . x ( ) - m_paddle . rect . width ( ) / 2 ;
new_paddle_x = max ( 0.0f , new_paddle_x ) ;
2020-11-09 07:40:35 -03:00
new_paddle_x = min ( game_width - m_paddle . rect . width ( ) , new_paddle_x ) ;
m_paddle . rect . set_x ( new_paddle_x ) ;
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_paddle . rect ) ) ;
2020-11-09 07:40:35 -03:00
}
void Game : : reset_ball ( )
{
2020-12-15 08:59:20 -03:00
int position_x_min = ( game_width / 2 ) - 50 ;
int position_x_max = ( game_width / 2 ) + 50 ;
2021-05-14 12:27:38 -03:00
int position_x = get_random < u32 > ( ) % ( position_x_max - position_x_min + 1 ) + position_x_min ;
2020-12-15 08:59:20 -03:00
int position_y = 200 ;
2021-05-14 12:27:38 -03:00
int velocity_x = get_random < u32 > ( ) % 3 + 1 ;
2020-12-15 08:59:20 -03:00
int velocity_y = 3 + ( 3 - velocity_x ) ;
2021-05-14 12:27:38 -03:00
if ( get_random < u32 > ( ) % 2 )
2020-12-15 08:59:20 -03:00
velocity_x = velocity_x * - 1 ;
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_ball . rect ( ) ) ) ;
2020-11-09 07:40:35 -03:00
m_ball = { } ;
2020-12-15 08:59:20 -03:00
m_ball . position = { position_x , position_y } ;
m_ball . velocity = { velocity_x , velocity_y } ;
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_ball . rect ( ) ) ) ;
2020-11-09 07:40:35 -03:00
}
void Game : : hurt ( )
{
stop_timer ( ) ;
2021-07-11 20:11:31 -03:00
update ( lives_left_rect ( ) ) ;
2020-12-19 18:50:48 -03:00
m_lives - - ;
2021-07-11 20:11:31 -03:00
update ( lives_left_rect ( ) ) ;
2020-12-19 18:50:48 -03:00
if ( m_lives < = 0 ) {
GUI : : MessageBox : : show ( window ( ) , " You lose! " , " Breakout " , GUI : : MessageBox : : Type : : Information , GUI : : MessageBox : : InputType : : OK ) ;
reset ( ) ;
}
sleep ( 1 ) ;
2020-11-09 07:40:35 -03:00
reset_ball ( ) ;
reset_paddle ( ) ;
start_timer ( 16 ) ;
}
void Game : : win ( )
{
stop_timer ( ) ;
2020-12-19 18:50:48 -03:00
update ( ) ;
if ( m_cheater ) {
GUI : : MessageBox : : show ( window ( ) , " You cheated not only the game, but yourself. " , " Breakout " , GUI : : MessageBox : : Type : : Information , GUI : : MessageBox : : InputType : : OK ) ;
} else {
GUI : : MessageBox : : show ( window ( ) , " You win! " , " Breakout " , GUI : : MessageBox : : Type : : Information , GUI : : MessageBox : : InputType : : OK ) ;
}
2020-11-09 07:40:35 -03:00
reset ( ) ;
start_timer ( 16 ) ;
}
void Game : : tick ( )
{
auto new_ball = m_ball ;
2020-11-10 09:54:19 -03:00
new_ball . position + = new_ball . velocity ;
2020-11-09 07:40:35 -03:00
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_ball . rect ( ) ) ) ;
2020-11-10 09:54:19 -03:00
if ( new_ball . x ( ) < new_ball . radius | | new_ball . x ( ) > game_width - new_ball . radius ) {
new_ball . position . set_x ( m_ball . x ( ) ) ;
new_ball . velocity . set_x ( new_ball . velocity . x ( ) * - 1 ) ;
2020-11-09 07:40:35 -03:00
}
2020-11-10 09:54:19 -03:00
if ( new_ball . y ( ) < new_ball . radius ) {
new_ball . position . set_y ( m_ball . y ( ) ) ;
new_ball . velocity . set_y ( new_ball . velocity . y ( ) * - 1 ) ;
2020-11-09 07:40:35 -03:00
}
2020-11-10 09:54:19 -03:00
if ( new_ball . y ( ) > game_height - new_ball . radius ) {
2020-11-09 07:40:35 -03:00
hurt ( ) ;
return ;
}
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( new_ball . rect ( ) ) ) ;
2020-11-09 07:40:35 -03:00
if ( new_ball . rect ( ) . intersects ( m_paddle . rect ) ) {
2021-09-10 11:59:33 -03:00
if ( m_ball . y ( ) < new_ball . y ( ) ) {
new_ball . position . set_y ( m_ball . y ( ) ) ;
}
new_ball . velocity . set_y ( fabs ( new_ball . velocity . y ( ) ) * - 1 ) ;
2020-11-10 10:13:09 -03:00
float distance_to_middle_of_paddle = new_ball . x ( ) - m_paddle . rect . center ( ) . x ( ) ;
float relative_impact_point = distance_to_middle_of_paddle / m_paddle . rect . width ( ) ;
new_ball . velocity . set_x ( relative_impact_point * 7 ) ;
2020-11-09 07:40:35 -03:00
}
for ( auto & brick : m_bricks ) {
if ( brick . dead )
continue ;
2020-11-09 09:15:30 -03:00
if ( new_ball . rect ( ) . intersects ( brick . rect ) ) {
2020-11-09 07:40:35 -03:00
brick . dead = true ;
2020-11-09 09:15:30 -03:00
auto overlap = new_ball . rect ( ) . intersected ( brick . rect ) ;
2020-11-09 07:40:35 -03:00
if ( overlap . width ( ) < overlap . height ( ) ) {
2020-11-10 09:54:19 -03:00
new_ball . position . set_x ( m_ball . x ( ) ) ;
new_ball . velocity . set_x ( new_ball . velocity . x ( ) * - 1 ) ;
2020-11-09 07:40:35 -03:00
} else {
2020-11-10 09:54:19 -03:00
new_ball . position . set_y ( m_ball . y ( ) ) ;
new_ball . velocity . set_y ( new_ball . velocity . y ( ) * - 1 ) ;
2020-11-09 07:40:35 -03:00
}
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( brick . rect ) ) ;
2020-11-09 07:40:35 -03:00
break ;
}
}
bool has_live_bricks = false ;
for ( auto & brick : m_bricks ) {
if ( ! brick . dead ) {
has_live_bricks = true ;
break ;
}
}
if ( ! has_live_bricks ) {
win ( ) ;
return ;
}
if ( m_paddle . moving_left ) {
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_paddle . rect ) ) ;
2020-11-10 09:54:19 -03:00
m_paddle . rect . set_x ( max ( 0.0f , m_paddle . rect . x ( ) - m_paddle . speed ) ) ;
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_paddle . rect ) ) ;
2020-11-09 07:40:35 -03:00
}
if ( m_paddle . moving_right ) {
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_paddle . rect ) ) ;
2020-11-09 07:40:35 -03:00
m_paddle . rect . set_x ( min ( game_width - m_paddle . rect . width ( ) , m_paddle . rect . x ( ) + m_paddle . speed ) ) ;
2021-07-11 20:11:31 -03:00
update ( enclosing_int_rect ( m_paddle . rect ) ) ;
2020-11-09 07:40:35 -03:00
}
m_ball = new_ball ;
2020-12-19 18:50:48 -03:00
if ( m_pause_count > 50 )
m_cheater = true ;
2020-11-09 07:40:35 -03:00
}
}