2020-08-10 15:54:32 -03:00
/*
* Copyright ( c ) 2020 , the SerenityOS developers .
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-08-10 15:54:32 -03:00
*/
# include "ChessWidget.h"
2020-08-12 01:53:11 -03:00
# include "PromotionDialog.h"
2021-05-14 12:27:38 -03:00
# include <AK/Random.h>
2020-08-10 15:54:32 -03:00
# include <AK/String.h>
2020-12-04 10:26:26 -03:00
# include <LibCore/DateTime.h>
# include <LibCore/File.h>
2020-08-11 17:10:39 -03:00
# include <LibGUI/MessageBox.h>
2020-08-10 15:54:32 -03:00
# include <LibGUI/Painter.h>
2020-08-21 11:53:04 -03:00
# include <LibGfx/Font.h>
2020-12-29 14:25:13 -03:00
# include <LibGfx/FontDatabase.h>
2020-12-10 13:54:11 -03:00
# include <LibGfx/Path.h>
2020-12-04 10:26:26 -03:00
# include <unistd.h>
2020-08-10 15:54:32 -03:00
ChessWidget : : ChessWidget ( )
{
2021-05-17 07:02:38 -03:00
set_piece_set ( " stelar7 " ) ;
2020-08-10 15:54:32 -03:00
}
ChessWidget : : ~ ChessWidget ( )
{
}
void ChessWidget : : paint_event ( GUI : : PaintEvent & event )
{
2021-06-05 02:40:53 -03:00
const int min_size = min ( width ( ) , height ( ) ) ;
const int widget_offset_x = ( window ( ) - > width ( ) - min_size ) / 2 ;
const int widget_offset_y = ( window ( ) - > height ( ) - min_size ) / 2 ;
2021-05-17 07:02:38 -03:00
GUI : : Frame : : paint_event ( event ) ;
2020-08-10 15:54:32 -03:00
GUI : : Painter painter ( * this ) ;
painter . add_clip_rect ( event . rect ( ) ) ;
2021-06-05 02:40:53 -03:00
painter . fill_rect ( { 0 , 0 , width ( ) , height ( ) } , Color : : Black ) ;
painter . translate ( frame_thickness ( ) + widget_offset_x , frame_thickness ( ) + widget_offset_y ) ;
2021-05-17 07:02:38 -03:00
2021-06-05 02:40:53 -03:00
size_t tile_width = min_size / 8 ;
size_t tile_height = min_size / 8 ;
2021-05-16 09:55:20 -03:00
int coord_rank_file = ( side ( ) = = Chess : : Color : : White ) ? 0 : 7 ;
2020-08-10 15:54:32 -03:00
2020-12-10 13:07:10 -03:00
Chess : : Board & active_board = ( m_playback ? board_playback ( ) : board ( ) ) ;
2021-05-20 13:40:48 -03:00
auto & coordinate_font = Gfx : : FontDatabase : : default_font ( ) . bold_variant ( ) ;
2020-08-11 18:03:55 -03:00
Chess : : Square : : for_each ( [ & ] ( Chess : : Square sq ) {
Gfx : : IntRect tile_rect ;
2021-01-09 09:44:11 -03:00
if ( side ( ) = = Chess : : Color : : White ) {
2020-08-11 18:03:55 -03:00
tile_rect = { sq . file * tile_width , ( 7 - sq . rank ) * tile_height , tile_width , tile_height } ;
} else {
tile_rect = { ( 7 - sq . file ) * tile_width , sq . rank * tile_height , tile_width , tile_height } ;
}
2020-08-10 15:54:32 -03:00
2020-08-12 17:41:35 -03:00
painter . fill_rect ( tile_rect , ( sq . is_light ( ) ) ? board_theme ( ) . light_square_color : board_theme ( ) . dark_square_color ) ;
2020-08-10 15:54:32 -03:00
2020-12-10 13:07:10 -03:00
if ( active_board . last_move ( ) . has_value ( ) & & ( active_board . last_move ( ) . value ( ) . to = = sq | | active_board . last_move ( ) . value ( ) . from = = sq ) ) {
2020-08-11 18:03:55 -03:00
painter . fill_rect ( tile_rect , m_move_highlight_color ) ;
}
2020-08-21 11:53:04 -03:00
if ( m_coordinates ) {
auto coord = sq . to_algebraic ( ) ;
auto text_color = ( sq . is_light ( ) ) ? board_theme ( ) . dark_square_color : board_theme ( ) . light_square_color ;
auto shrunken_rect = tile_rect ;
shrunken_rect . shrink ( 4 , 4 ) ;
if ( sq . rank = = coord_rank_file )
2021-05-20 13:40:48 -03:00
painter . draw_text ( shrunken_rect , coord . substring_view ( 0 , 1 ) , coordinate_font , Gfx : : TextAlignment : : BottomRight , text_color ) ;
2020-08-21 11:53:04 -03:00
if ( sq . file = = coord_rank_file )
2021-05-20 13:40:48 -03:00
painter . draw_text ( shrunken_rect , coord . substring_view ( 1 , 1 ) , coordinate_font , Gfx : : TextAlignment : : TopLeft , text_color ) ;
2020-08-21 11:53:04 -03:00
}
2020-12-10 13:54:11 -03:00
for ( auto & m : m_board_markings ) {
if ( m . type ( ) = = BoardMarking : : Type : : Square & & m . from = = sq ) {
Gfx : : Color color = m . secondary_color ? m_marking_secondary_color : ( m . alternate_color ? m_marking_alternate_color : m_marking_primary_color ) ;
painter . fill_rect ( tile_rect , color ) ;
}
}
2020-08-11 18:03:55 -03:00
if ( ! ( m_dragging_piece & & sq = = m_moving_square ) ) {
2020-12-10 13:07:10 -03:00
auto bmp = m_pieces . get ( active_board . get_piece ( sq ) ) ;
2020-08-11 18:03:55 -03:00
if ( bmp . has_value ( ) ) {
painter . draw_scaled_bitmap ( tile_rect , * bmp . value ( ) , bmp . value ( ) - > rect ( ) ) ;
2020-08-10 15:54:32 -03:00
}
}
2020-08-11 18:03:55 -03:00
return IterationDecision : : Continue ;
} ) ;
2020-08-10 15:54:32 -03:00
2020-12-10 13:54:11 -03:00
auto draw_arrow = [ & painter ] ( Gfx : : FloatPoint A , Gfx : : FloatPoint B , float w1 , float w2 , float h , Gfx : : Color color ) {
float dx = B . x ( ) - A . x ( ) ;
float dy = A . y ( ) - B . y ( ) ;
float phi = atan2f ( dy , dx ) ;
2021-04-15 04:36:14 -03:00
float hdx = h * cosf ( phi ) ;
float hdy = h * sinf ( phi ) ;
2020-12-10 13:54:11 -03:00
2021-04-15 04:36:14 -03:00
const auto cos_pi_2_phi = cosf ( float { M_PI_2 } - phi ) ;
const auto sin_pi_2_phi = sinf ( float { M_PI_2 } - phi ) ;
Gfx : : FloatPoint A1 ( A . x ( ) - ( w1 / 2 ) * cos_pi_2_phi , A . y ( ) - ( w1 / 2 ) * sin_pi_2_phi ) ;
Gfx : : FloatPoint B3 ( A . x ( ) + ( w1 / 2 ) * cos_pi_2_phi , A . y ( ) + ( w1 / 2 ) * sin_pi_2_phi ) ;
2020-12-10 13:54:11 -03:00
Gfx : : FloatPoint A2 ( A1 . x ( ) + ( dx - hdx ) , A1 . y ( ) - ( dy - hdy ) ) ;
Gfx : : FloatPoint B2 ( B3 . x ( ) + ( dx - hdx ) , B3 . y ( ) - ( dy - hdy ) ) ;
2021-04-15 04:36:14 -03:00
Gfx : : FloatPoint A3 ( A2 . x ( ) - w2 * cos_pi_2_phi , A2 . y ( ) - w2 * sin_pi_2_phi ) ;
Gfx : : FloatPoint B1 ( B2 . x ( ) + w2 * cos_pi_2_phi , B2 . y ( ) + w2 * sin_pi_2_phi ) ;
2020-12-10 13:54:11 -03:00
auto path = Gfx : : Path ( ) ;
path . move_to ( A ) ;
path . line_to ( A1 ) ;
path . line_to ( A2 ) ;
path . line_to ( A3 ) ;
path . line_to ( B ) ;
path . line_to ( B1 ) ;
path . line_to ( B2 ) ;
path . line_to ( B3 ) ;
path . line_to ( A ) ;
path . close ( ) ;
painter . fill_path ( path , color , Gfx : : Painter : : WindingRule : : EvenOdd ) ;
} ;
for ( auto & m : m_board_markings ) {
if ( m . type ( ) = = BoardMarking : : Type : : Arrow ) {
Gfx : : FloatPoint arrow_start ;
Gfx : : FloatPoint arrow_end ;
2021-01-09 09:44:11 -03:00
if ( side ( ) = = Chess : : Color : : White ) {
2020-12-10 13:54:11 -03:00
arrow_start = { m . from . file * tile_width + tile_width / 2.0f , ( 7 - m . from . rank ) * tile_height + tile_height / 2.0f } ;
arrow_end = { m . to . file * tile_width + tile_width / 2.0f , ( 7 - m . to . rank ) * tile_height + tile_height / 2.0f } ;
} else {
arrow_start = { ( 7 - m . from . file ) * tile_width + tile_width / 2.0f , m . from . rank * tile_height + tile_height / 2.0f } ;
arrow_end = { ( 7 - m . to . file ) * tile_width + tile_width / 2.0f , m . to . rank * tile_height + tile_height / 2.0f } ;
}
Gfx : : Color color = m . secondary_color ? m_marking_secondary_color : ( m . alternate_color ? m_marking_primary_color : m_marking_alternate_color ) ;
draw_arrow ( arrow_start , arrow_end , tile_width / 8.0f , tile_width / 10.0f , tile_height / 2.5f , color ) ;
}
}
2020-08-10 15:54:32 -03:00
if ( m_dragging_piece ) {
2021-03-27 17:59:54 -03:00
if ( m_show_available_moves ) {
Gfx : : IntPoint move_point ;
Gfx : : IntPoint point_offset = { tile_width / 3 , tile_height / 3 } ;
Gfx : : IntSize rect_size = { tile_width / 3 , tile_height / 3 } ;
for ( const auto & square : m_available_moves ) {
if ( side ( ) = = Chess : : Color : : White ) {
move_point = { square . file * tile_width , ( 7 - square . rank ) * tile_height } ;
} else {
move_point = { ( 7 - square . file ) * tile_width , square . rank * tile_height } ;
}
painter . fill_ellipse ( { move_point + point_offset , rect_size } , Gfx : : Color : : LightGray ) ;
}
}
2020-12-10 13:07:10 -03:00
auto bmp = m_pieces . get ( active_board . get_piece ( m_moving_square ) ) ;
2020-08-10 15:54:32 -03:00
if ( bmp . has_value ( ) ) {
auto center = m_drag_point - Gfx : : IntPoint ( tile_width / 2 , tile_height / 2 ) ;
painter . draw_scaled_bitmap ( { center , { tile_width , tile_height } } , * bmp . value ( ) , bmp . value ( ) - > rect ( ) ) ;
}
}
}
void ChessWidget : : mousedown_event ( GUI : : MouseEvent & event )
{
2021-06-05 02:40:53 -03:00
const int min_size = min ( width ( ) , height ( ) ) ;
const int widget_offset_x = ( window ( ) - > width ( ) - min_size ) / 2 ;
const int widget_offset_y = ( window ( ) - > height ( ) - min_size ) / 2 ;
2021-05-17 07:02:38 -03:00
if ( ! frame_inner_rect ( ) . contains ( event . position ( ) ) )
return ;
2020-12-10 13:54:11 -03:00
if ( event . button ( ) = = GUI : : MouseButton : : Right ) {
2021-02-18 05:16:17 -03:00
if ( m_dragging_piece ) {
m_dragging_piece = false ;
2021-05-31 15:19:56 -03:00
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
2021-03-27 17:59:54 -03:00
m_available_moves . clear ( ) ;
2021-02-18 05:16:17 -03:00
} else {
m_current_marking . from = mouse_to_square ( event ) ;
}
2020-12-10 13:54:11 -03:00
return ;
}
m_board_markings . clear ( ) ;
2020-08-10 15:54:32 -03:00
auto square = mouse_to_square ( event ) ;
auto piece = board ( ) . get_piece ( square ) ;
2021-01-09 09:44:11 -03:00
if ( drag_enabled ( ) & & piece . color = = board ( ) . turn ( ) & & ! m_playback ) {
2020-08-10 15:54:32 -03:00
m_dragging_piece = true ;
2021-05-31 15:19:56 -03:00
set_override_cursor ( Gfx : : StandardCursor : : Drag ) ;
2021-06-05 02:40:53 -03:00
m_drag_point = { event . position ( ) . x ( ) - widget_offset_x , event . position ( ) . y ( ) - widget_offset_y } ;
2020-08-10 15:54:32 -03:00
m_moving_square = square ;
2021-03-27 17:59:54 -03:00
m_board . generate_moves ( [ & ] ( Chess : : Move move ) {
if ( move . from = = m_moving_square ) {
m_available_moves . append ( move . to ) ;
}
return IterationDecision : : Continue ;
} ) ;
2020-08-10 15:54:32 -03:00
}
2020-12-10 13:54:11 -03:00
update ( ) ;
2020-08-10 15:54:32 -03:00
}
void ChessWidget : : mouseup_event ( GUI : : MouseEvent & event )
{
2021-05-17 07:02:38 -03:00
if ( ! frame_inner_rect ( ) . contains ( event . position ( ) ) )
return ;
2020-12-10 13:54:11 -03:00
if ( event . button ( ) = = GUI : : MouseButton : : Right ) {
m_current_marking . secondary_color = event . shift ( ) ;
m_current_marking . alternate_color = event . ctrl ( ) ;
m_current_marking . to = mouse_to_square ( event ) ;
auto match_index = m_board_markings . find_first_index ( m_current_marking ) ;
if ( match_index . has_value ( ) ) {
m_board_markings . remove ( match_index . value ( ) ) ;
update ( ) ;
return ;
}
m_board_markings . append ( m_current_marking ) ;
update ( ) ;
return ;
}
2020-08-10 15:54:32 -03:00
if ( ! m_dragging_piece )
return ;
m_dragging_piece = false ;
2021-05-31 15:19:56 -03:00
set_override_cursor ( Gfx : : StandardCursor : : Hand ) ;
2021-03-27 17:59:54 -03:00
m_available_moves . clear ( ) ;
2020-08-10 15:54:32 -03:00
auto target_square = mouse_to_square ( event ) ;
2020-08-12 01:53:11 -03:00
Chess : : Move move = { m_moving_square , target_square } ;
if ( board ( ) . is_promotion_move ( move ) ) {
auto promotion_dialog = PromotionDialog : : construct ( * this ) ;
if ( promotion_dialog - > exec ( ) = = PromotionDialog : : ExecOK )
move . promote_to = promotion_dialog - > selected_piece ( ) ;
}
if ( board ( ) . apply_move ( move ) ) {
2020-12-10 13:07:10 -03:00
m_playback_move_number = board ( ) . moves ( ) . size ( ) ;
m_playback = false ;
m_board_playback = m_board ;
2020-08-11 18:03:55 -03:00
2020-12-10 13:07:10 -03:00
if ( board ( ) . game_result ( ) ! = Chess : : Board : : Result : : NotFinished ) {
2020-08-12 17:41:35 -03:00
bool over = true ;
2020-08-11 18:03:55 -03:00
String msg ;
switch ( board ( ) . game_result ( ) ) {
2020-08-18 18:02:53 -03:00
case Chess : : Board : : Result : : CheckMate :
2021-01-09 09:44:11 -03:00
if ( board ( ) . turn ( ) = = Chess : : Color : : White ) {
2020-08-11 18:03:55 -03:00
msg = " Black wins by Checkmate. " ;
} else {
msg = " White wins by Checkmate. " ;
}
break ;
2020-08-18 18:02:53 -03:00
case Chess : : Board : : Result : : StaleMate :
2020-08-11 18:03:55 -03:00
msg = " Draw by Stalemate. " ;
break ;
2020-08-18 18:02:53 -03:00
case Chess : : Board : : Result : : FiftyMoveRule :
2020-08-12 17:41:35 -03:00
update ( ) ;
if ( GUI : : MessageBox : : show ( window ( ) , " 50 moves have elapsed without a capture. Claim Draw? " , " Claim Draw? " ,
GUI : : MessageBox : : Type : : Information , GUI : : MessageBox : : InputType : : YesNo )
= = GUI : : Dialog : : ExecYes ) {
msg = " Draw by 50 move rule. " ;
} else {
over = false ;
}
break ;
2020-08-18 18:02:53 -03:00
case Chess : : Board : : Result : : SeventyFiveMoveRule :
2020-08-12 17:41:35 -03:00
msg = " Draw by 75 move rule. " ;
2020-08-11 18:03:55 -03:00
break ;
2020-10-02 18:14:37 -03:00
case Chess : : Board : : Result : : ThreeFoldRepetition :
2020-08-12 17:41:35 -03:00
update ( ) ;
if ( GUI : : MessageBox : : show ( window ( ) , " The same board state has repeated three times. Claim Draw? " , " Claim Draw? " ,
GUI : : MessageBox : : Type : : Information , GUI : : MessageBox : : InputType : : YesNo )
= = GUI : : Dialog : : ExecYes ) {
2020-10-02 18:14:37 -03:00
msg = " Draw by threefold repetition. " ;
2020-08-12 17:41:35 -03:00
} else {
over = false ;
}
break ;
2020-10-02 18:14:37 -03:00
case Chess : : Board : : Result : : FiveFoldRepetition :
msg = " Draw by fivefold repetition. " ;
2020-08-12 17:41:35 -03:00
break ;
2020-08-18 18:02:53 -03:00
case Chess : : Board : : Result : : InsufficientMaterial :
2020-08-12 17:41:35 -03:00
msg = " Draw by insufficient material. " ;
2020-08-11 18:03:55 -03:00
break ;
default :
2021-02-23 16:42:32 -03:00
VERIFY_NOT_REACHED ( ) ;
2020-08-11 17:10:39 -03:00
}
2020-08-12 17:41:35 -03:00
if ( over ) {
2021-05-31 15:19:56 -03:00
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
2020-08-12 17:41:35 -03:00
set_drag_enabled ( false ) ;
update ( ) ;
GUI : : MessageBox : : show ( window ( ) , msg , " Game Over " , GUI : : MessageBox : : Type : : Information ) ;
}
2020-08-19 20:53:50 -03:00
} else {
2020-12-17 00:01:58 -03:00
input_engine_move ( ) ;
2020-08-11 17:10:39 -03:00
}
}
2020-08-10 15:54:32 -03:00
update ( ) ;
}
void ChessWidget : : mousemove_event ( GUI : : MouseEvent & event )
{
2021-06-05 02:40:53 -03:00
const int min_size = min ( width ( ) , height ( ) ) ;
const int widget_offset_x = ( window ( ) - > width ( ) - min_size ) / 2 ;
const int widget_offset_y = ( window ( ) - > height ( ) - min_size ) / 2 ;
2021-05-17 07:02:38 -03:00
if ( ! frame_inner_rect ( ) . contains ( event . position ( ) ) )
return ;
2021-05-31 15:19:56 -03:00
if ( m_engine & & board ( ) . turn ( ) ! = side ( ) )
2020-08-10 15:54:32 -03:00
return ;
2021-05-31 15:19:56 -03:00
if ( ! m_dragging_piece ) {
auto square = mouse_to_square ( event ) ;
if ( ! square . in_bounds ( ) )
return ;
auto piece = board ( ) . get_piece ( square ) ;
if ( piece . color = = board ( ) . turn ( ) )
set_override_cursor ( Gfx : : StandardCursor : : Hand ) ;
else
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
return ;
}
2021-06-05 02:40:53 -03:00
m_drag_point = { event . position ( ) . x ( ) - widget_offset_x , event . position ( ) . y ( ) - widget_offset_y } ;
2020-08-10 15:54:32 -03:00
update ( ) ;
}
2020-12-10 13:07:10 -03:00
void ChessWidget : : keydown_event ( GUI : : KeyEvent & event )
{
2021-05-31 15:19:56 -03:00
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
2020-12-10 13:07:10 -03:00
switch ( event . key ( ) ) {
case KeyCode : : Key_Left :
playback_move ( PlaybackDirection : : Backward ) ;
break ;
case KeyCode : : Key_Right :
playback_move ( PlaybackDirection : : Forward ) ;
break ;
case KeyCode : : Key_Up :
playback_move ( PlaybackDirection : : Last ) ;
break ;
case KeyCode : : Key_Down :
playback_move ( PlaybackDirection : : First ) ;
break ;
case KeyCode : : Key_Home :
playback_move ( PlaybackDirection : : First ) ;
break ;
case KeyCode : : Key_End :
playback_move ( PlaybackDirection : : Last ) ;
break ;
default :
return ;
}
update ( ) ;
}
2020-08-10 15:54:32 -03:00
static String set_path = String ( " /res/icons/chess/sets/ " ) ;
static RefPtr < Gfx : : Bitmap > get_piece ( const StringView & set , const StringView & image )
{
StringBuilder builder ;
builder . append ( set_path ) ;
builder . append ( set ) ;
builder . append ( ' / ' ) ;
builder . append ( image ) ;
2021-07-21 13:02:15 -03:00
return Gfx : : Bitmap : : try_load_from_file ( builder . build ( ) ) ;
2020-08-10 15:54:32 -03:00
}
void ChessWidget : : set_piece_set ( const StringView & set )
{
m_piece_set = set ;
2021-01-09 09:44:11 -03:00
m_pieces . set ( { Chess : : Color : : White , Chess : : Type : : Pawn } , get_piece ( set , " white-pawn.png " ) ) ;
m_pieces . set ( { Chess : : Color : : Black , Chess : : Type : : Pawn } , get_piece ( set , " black-pawn.png " ) ) ;
m_pieces . set ( { Chess : : Color : : White , Chess : : Type : : Knight } , get_piece ( set , " white-knight.png " ) ) ;
m_pieces . set ( { Chess : : Color : : Black , Chess : : Type : : Knight } , get_piece ( set , " black-knight.png " ) ) ;
m_pieces . set ( { Chess : : Color : : White , Chess : : Type : : Bishop } , get_piece ( set , " white-bishop.png " ) ) ;
m_pieces . set ( { Chess : : Color : : Black , Chess : : Type : : Bishop } , get_piece ( set , " black-bishop.png " ) ) ;
m_pieces . set ( { Chess : : Color : : White , Chess : : Type : : Rook } , get_piece ( set , " white-rook.png " ) ) ;
m_pieces . set ( { Chess : : Color : : Black , Chess : : Type : : Rook } , get_piece ( set , " black-rook.png " ) ) ;
m_pieces . set ( { Chess : : Color : : White , Chess : : Type : : Queen } , get_piece ( set , " white-queen.png " ) ) ;
m_pieces . set ( { Chess : : Color : : Black , Chess : : Type : : Queen } , get_piece ( set , " black-queen.png " ) ) ;
m_pieces . set ( { Chess : : Color : : White , Chess : : Type : : King } , get_piece ( set , " white-king.png " ) ) ;
m_pieces . set ( { Chess : : Color : : Black , Chess : : Type : : King } , get_piece ( set , " black-king.png " ) ) ;
2020-08-10 15:54:32 -03:00
}
Chess : : Square ChessWidget : : mouse_to_square ( GUI : : MouseEvent & event ) const
{
2021-06-05 02:40:53 -03:00
const int min_size = min ( width ( ) , height ( ) ) ;
const int widget_offset_x = ( window ( ) - > width ( ) - min_size ) / 2 ;
const int widget_offset_y = ( window ( ) - > height ( ) - min_size ) / 2 ;
int tile_width = min_size / 8 ;
int tile_height = min_size / 8 ;
2020-08-10 15:54:32 -03:00
2021-01-09 09:44:11 -03:00
if ( side ( ) = = Chess : : Color : : White ) {
2021-06-05 02:40:53 -03:00
return { 7 - ( ( event . y ( ) - widget_offset_y ) / tile_height ) , ( event . x ( ) - widget_offset_x ) / tile_width } ;
2020-08-10 15:54:32 -03:00
} else {
2021-06-05 02:40:53 -03:00
return { ( event . y ( ) - widget_offset_y ) / tile_height , 7 - ( ( event . x ( ) - widget_offset_x ) / tile_width ) } ;
2020-08-10 15:54:32 -03:00
}
}
2020-08-11 21:59:32 -03:00
2020-08-12 01:53:11 -03:00
RefPtr < Gfx : : Bitmap > ChessWidget : : get_piece_graphic ( const Chess : : Piece & piece ) const
{
return m_pieces . get ( piece ) . value ( ) ;
}
2020-08-11 21:59:32 -03:00
void ChessWidget : : reset ( )
{
2020-12-10 13:54:11 -03:00
m_board_markings . clear ( ) ;
2020-12-10 13:07:10 -03:00
m_playback = false ;
m_playback_move_number = 0 ;
m_board_playback = Chess : : Board ( ) ;
2020-08-18 18:02:53 -03:00
m_board = Chess : : Board ( ) ;
2021-05-14 12:27:38 -03:00
m_side = ( get_random < u32 > ( ) % 2 ) ? Chess : : Color : : White : Chess : : Color : : Black ;
2020-08-11 21:59:32 -03:00
m_drag_enabled = true ;
2020-12-17 00:01:58 -03:00
input_engine_move ( ) ;
2020-08-11 21:59:32 -03:00
update ( ) ;
}
void ChessWidget : : set_board_theme ( const StringView & name )
{
// FIXME: Add some kind of themes.json
2021-01-09 09:44:11 -03:00
// The following Colors have been taken from lichess.org, but i'm pretty sure they took them from chess.com.
2020-08-11 21:59:32 -03:00
if ( name = = " Beige " ) {
m_board_theme = { " Beige " , Color : : from_rgb ( 0xb58863 ) , Color : : from_rgb ( 0xf0d9b5 ) } ;
} else if ( name = = " Green " ) {
m_board_theme = { " Green " , Color : : from_rgb ( 0x86a666 ) , Color : : from_rgb ( 0xffffdd ) } ;
} else if ( name = = " Blue " ) {
m_board_theme = { " Blue " , Color : : from_rgb ( 0x8ca2ad ) , Color : : from_rgb ( 0xdee3e6 ) } ;
} else {
set_board_theme ( " Beige " ) ;
}
}
2020-08-19 20:53:50 -03:00
2020-12-17 00:01:58 -03:00
bool ChessWidget : : want_engine_move ( )
2020-08-19 20:53:50 -03:00
{
2020-12-17 00:01:58 -03:00
if ( ! m_engine )
return false ;
if ( board ( ) . turn ( ) = = side ( ) )
return false ;
return true ;
}
void ChessWidget : : input_engine_move ( )
{
if ( ! want_engine_move ( ) )
2020-08-19 20:53:50 -03:00
return ;
bool drag_was_enabled = drag_enabled ( ) ;
if ( drag_was_enabled )
set_drag_enabled ( false ) ;
2020-12-22 15:47:32 -03:00
set_override_cursor ( Gfx : : StandardCursor : : Wait ) ;
2020-08-20 20:04:08 -03:00
m_engine - > get_best_move ( board ( ) , 4000 , [ this , drag_was_enabled ] ( Chess : : Move move ) {
2020-12-22 15:47:32 -03:00
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
2020-12-17 00:01:58 -03:00
if ( ! want_engine_move ( ) )
return ;
2020-08-19 20:53:50 -03:00
set_drag_enabled ( drag_was_enabled ) ;
2021-02-23 16:42:32 -03:00
VERIFY ( board ( ) . apply_move ( move ) ) ;
2020-12-10 13:07:10 -03:00
m_playback_move_number = m_board . moves ( ) . size ( ) ;
m_playback = false ;
2020-12-10 13:54:11 -03:00
m_board_markings . clear ( ) ;
2020-08-19 20:53:50 -03:00
update ( ) ;
} ) ;
}
2020-12-04 09:17:00 -03:00
2020-12-10 13:07:10 -03:00
void ChessWidget : : playback_move ( PlaybackDirection direction )
{
if ( m_board . moves ( ) . is_empty ( ) )
return ;
m_playback = true ;
2020-12-10 13:54:11 -03:00
m_board_markings . clear ( ) ;
2020-12-10 13:07:10 -03:00
switch ( direction ) {
case PlaybackDirection : : Backward :
if ( m_playback_move_number = = 0 )
return ;
m_board_playback = Chess : : Board ( ) ;
for ( size_t i = 0 ; i < m_playback_move_number - 1 ; i + + )
m_board_playback . apply_move ( m_board . moves ( ) . at ( i ) ) ;
m_playback_move_number - - ;
break ;
case PlaybackDirection : : Forward :
if ( m_playback_move_number + 1 > m_board . moves ( ) . size ( ) ) {
m_playback = false ;
return ;
}
m_board_playback . apply_move ( m_board . moves ( ) . at ( m_playback_move_number + + ) ) ;
if ( m_playback_move_number = = m_board . moves ( ) . size ( ) )
m_playback = false ;
break ;
case PlaybackDirection : : First :
m_board_playback = Chess : : Board ( ) ;
m_playback_move_number = 0 ;
break ;
case PlaybackDirection : : Last :
while ( m_playback ) {
playback_move ( PlaybackDirection : : Forward ) ;
}
break ;
default :
2021-02-23 16:42:32 -03:00
VERIFY_NOT_REACHED ( ) ;
2020-12-10 13:07:10 -03:00
}
update ( ) ;
}
2020-12-10 12:44:38 -03:00
String ChessWidget : : get_fen ( ) const
{
2020-12-10 13:07:10 -03:00
return m_playback ? m_board_playback . to_fen ( ) : m_board . to_fen ( ) ;
2020-12-10 12:44:38 -03:00
}
2020-12-10 13:34:06 -03:00
bool ChessWidget : : import_pgn ( const StringView & import_path )
{
2021-05-12 06:26:43 -03:00
auto file_or_error = Core : : File : : open ( import_path , Core : : OpenMode : : ReadOnly ) ;
2020-12-10 13:34:06 -03:00
if ( file_or_error . is_error ( ) ) {
warnln ( " Couldn't open '{}': {} " , import_path , file_or_error . error ( ) ) ;
return false ;
}
auto & file = * file_or_error . value ( ) ;
m_board = Chess : : Board ( ) ;
ByteBuffer bytes = file . read_all ( ) ;
StringView content = bytes ;
auto lines = content . lines ( ) ;
StringView line ;
size_t i = 0 ;
// Tag Pair Section
// FIXME: Parse these tags when they become relevant
do {
line = lines . at ( i + + ) ;
} while ( ! line . is_empty ( ) | | i > = lines . size ( ) ) ;
// Movetext Section
bool skip = false ;
bool recursive_annotation = false ;
bool future_expansion = false ;
2021-01-09 09:44:11 -03:00
Chess : : Color turn = Chess : : Color : : White ;
2020-12-10 13:34:06 -03:00
String movetext ;
for ( size_t j = i ; j < lines . size ( ) ; j + + )
movetext = String : : formatted ( " {}{} " , movetext , lines . at ( i ) . to_string ( ) ) ;
for ( auto token : movetext . split ( ' ' ) ) {
token = token . trim_whitespace ( ) ;
// FIXME: Parse all of these tokens when we start caring about them
if ( token . ends_with ( " } " ) ) {
skip = false ;
continue ;
}
if ( skip )
continue ;
if ( token . starts_with ( " { " ) ) {
if ( token . ends_with ( " } " ) )
continue ;
skip = true ;
continue ;
}
if ( token . ends_with ( " ) " ) ) {
recursive_annotation = false ;
continue ;
}
if ( recursive_annotation )
continue ;
if ( token . starts_with ( " ( " ) ) {
if ( token . ends_with ( " ) " ) )
continue ;
recursive_annotation = true ;
continue ;
}
if ( token . ends_with ( " > " ) ) {
future_expansion = false ;
continue ;
}
if ( future_expansion )
continue ;
if ( token . starts_with ( " < " ) ) {
if ( token . ends_with ( " > " ) )
continue ;
future_expansion = true ;
continue ;
}
if ( token . starts_with ( " $ " ) )
continue ;
if ( token . contains ( " * " ) )
break ;
// FIXME: When we become able to set more of the game state, fix these end results
if ( token . contains ( " 1-0 " ) ) {
2021-01-09 09:44:11 -03:00
m_board . set_resigned ( Chess : : Color : : Black ) ;
2020-12-10 13:34:06 -03:00
break ;
}
if ( token . contains ( " 0-1 " ) ) {
2021-01-09 09:44:11 -03:00
m_board . set_resigned ( Chess : : Color : : White ) ;
2020-12-10 13:34:06 -03:00
break ;
}
if ( token . contains ( " 1/2-1/2 " ) ) {
break ;
}
if ( ! token . ends_with ( " . " ) ) {
m_board . apply_move ( Chess : : Move : : from_algebraic ( token , turn , m_board ) ) ;
2021-01-09 09:44:11 -03:00
turn = Chess : : opposing_color ( turn ) ;
2020-12-10 13:34:06 -03:00
}
}
2020-12-10 13:54:11 -03:00
m_board_markings . clear ( ) ;
2020-12-10 13:34:06 -03:00
m_board_playback = m_board ;
m_playback_move_number = m_board_playback . moves ( ) . size ( ) ;
m_playback = true ;
update ( ) ;
file . close ( ) ;
return true ;
}
2020-12-04 10:26:26 -03:00
bool ChessWidget : : export_pgn ( const StringView & export_path ) const
{
2021-05-12 06:26:43 -03:00
auto file_or_error = Core : : File : : open ( export_path , Core : : OpenMode : : WriteOnly ) ;
2020-12-04 10:26:26 -03:00
if ( file_or_error . is_error ( ) ) {
warnln ( " Couldn't open '{}': {} " , export_path , file_or_error . error ( ) ) ;
return false ;
}
auto & file = * file_or_error . value ( ) ;
// Tag Pair Section
file . write ( " [Event \" Casual Game \" ] \n " ) ;
file . write ( " [Site \" SerenityOS Chess \" ] \n " ) ;
file . write ( String : : formatted ( " [Date \" {} \" ] \n " , Core : : DateTime : : now ( ) . to_string ( " %Y.%m.%d " ) ) ) ;
file . write ( " [Round \" 1 \" ] \n " ) ;
String username ( getlogin ( ) ) ;
const String player1 = ( ! username . is_empty ( ) ? username : " ? " ) ;
const String player2 = ( ! m_engine . is_null ( ) ? " SerenityOS ChessEngine " : " ? " ) ;
2021-01-09 09:44:11 -03:00
file . write ( String : : formatted ( " [White \" {} \" ] \n " , m_side = = Chess : : Color : : White ? player1 : player2 ) ) ;
file . write ( String : : formatted ( " [Black \" {} \" ] \n " , m_side = = Chess : : Color : : Black ? player1 : player2 ) ) ;
2020-12-04 10:26:26 -03:00
file . write ( String : : formatted ( " [Result \" {} \" ] \n " , Chess : : Board : : result_to_points ( m_board . game_result ( ) , m_board . turn ( ) ) ) ) ;
file . write ( " [WhiteElo \" ? \" ] \n " ) ;
file . write ( " [BlackElo \" ? \" ] \n " ) ;
file . write ( " [Variant \" Standard \" ] \n " ) ;
file . write ( " [TimeControl \" - \" ] \n " ) ;
file . write ( " [Annotator \" SerenityOS Chess \" ] \n " ) ;
file . write ( " \n " ) ;
// Movetext Section
for ( size_t i = 0 , move_no = 1 ; i < m_board . moves ( ) . size ( ) ; i + = 2 , move_no + + ) {
const String white = m_board . moves ( ) . at ( i ) . to_algebraic ( ) ;
if ( i + 1 < m_board . moves ( ) . size ( ) ) {
const String black = m_board . moves ( ) . at ( i + 1 ) . to_algebraic ( ) ;
file . write ( String : : formatted ( " {}. {} {} " , move_no , white , black ) ) ;
} else {
file . write ( String : : formatted ( " {}. {} " , move_no , white ) ) ;
}
}
file . write ( " { " ) ;
file . write ( Chess : : Board : : result_to_string ( m_board . game_result ( ) , m_board . turn ( ) ) ) ;
file . write ( " } " ) ;
file . write ( Chess : : Board : : result_to_points ( m_board . game_result ( ) , m_board . turn ( ) ) ) ;
file . write ( " \n " ) ;
file . close ( ) ;
return true ;
}
2020-12-04 09:17:00 -03:00
void ChessWidget : : flip_board ( )
{
2020-12-17 00:01:58 -03:00
if ( want_engine_move ( ) ) {
GUI : : MessageBox : : show ( window ( ) , " You can only flip the board on your turn. " , " Flip Board " , GUI : : MessageBox : : Type : : Information ) ;
return ;
}
2021-01-09 09:44:11 -03:00
m_side = Chess : : opposing_color ( m_side ) ;
2020-12-17 00:01:58 -03:00
input_engine_move ( ) ;
2020-12-04 09:17:00 -03:00
update ( ) ;
}
2020-12-17 00:01:58 -03:00
int ChessWidget : : resign ( )
2020-12-04 09:17:00 -03:00
{
2020-12-17 00:01:58 -03:00
if ( want_engine_move ( ) ) {
2020-12-04 09:17:00 -03:00
GUI : : MessageBox : : show ( window ( ) , " You can only resign on your turn. " , " Resign " , GUI : : MessageBox : : Type : : Information ) ;
2020-12-17 00:01:58 -03:00
return - 1 ;
2020-12-04 09:17:00 -03:00
}
2020-12-17 00:01:58 -03:00
auto result = GUI : : MessageBox : : show ( window ( ) , " Are you sure you wish to resign? " , " Resign " , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : YesNo ) ;
if ( result ! = GUI : : MessageBox : : ExecYes )
return - 1 ;
2020-12-04 09:17:00 -03:00
board ( ) . set_resigned ( m_board . turn ( ) ) ;
set_drag_enabled ( false ) ;
update ( ) ;
2020-12-04 10:26:26 -03:00
const String msg = Chess : : Board : : result_to_string ( m_board . game_result ( ) , m_board . turn ( ) ) ;
2020-12-04 09:17:00 -03:00
GUI : : MessageBox : : show ( window ( ) , msg , " Game Over " , GUI : : MessageBox : : Type : : Information ) ;
2020-12-17 00:01:58 -03:00
return 0 ;
2020-12-04 09:17:00 -03:00
}