2020-08-10 21:54:32 +03:00
/*
* Copyright ( c ) 2020 , the SerenityOS developers .
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
*
* 2. Redistributions in binary form must reproduce the above copyright notice ,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY ,
* OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
# include "ChessWidget.h"
2020-08-12 07:53:11 +03:00
# include "PromotionDialog.h"
2020-08-10 21:54:32 +03:00
# include <AK/String.h>
2020-12-04 16:26:26 +03:00
# include <LibCore/DateTime.h>
# include <LibCore/File.h>
2020-08-11 23:10:39 +03:00
# include <LibGUI/MessageBox.h>
2020-08-10 21:54:32 +03:00
# include <LibGUI/Painter.h>
2020-08-21 17:53:04 +03:00
# include <LibGfx/Font.h>
2020-12-29 20:25:13 +03:00
# include <LibGfx/FontDatabase.h>
2020-12-10 19:54:11 +03:00
# include <LibGfx/Path.h>
2020-12-04 16:26:26 +03:00
# include <unistd.h>
2020-08-10 21:54:32 +03:00
ChessWidget : : ChessWidget ( const StringView & set )
{
set_piece_set ( set ) ;
}
ChessWidget : : ChessWidget ( )
2020-12-04 15:04:53 +03:00
: ChessWidget ( " stelar7 " )
2020-08-10 21:54:32 +03:00
{
}
ChessWidget : : ~ ChessWidget ( )
{
}
void ChessWidget : : paint_event ( GUI : : PaintEvent & event )
{
GUI : : Widget : : paint_event ( event ) ;
GUI : : Painter painter ( * this ) ;
painter . add_clip_rect ( event . rect ( ) ) ;
size_t tile_width = width ( ) / 8 ;
size_t tile_height = height ( ) / 8 ;
2021-01-09 15:44:11 +03:00
unsigned coord_rank_file = ( side ( ) = = Chess : : Color : : White ) ? 0 : 7 ;
2020-08-10 21:54:32 +03:00
2020-12-10 19:07:10 +03:00
Chess : : Board & active_board = ( m_playback ? board_playback ( ) : board ( ) ) ;
2020-08-12 00:03:55 +03:00
Chess : : Square : : for_each ( [ & ] ( Chess : : Square sq ) {
Gfx : : IntRect tile_rect ;
2021-01-09 15:44:11 +03:00
if ( side ( ) = = Chess : : Color : : White ) {
2020-08-12 00: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 21:54:32 +03:00
2020-08-12 23: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 21:54:32 +03:00
2020-12-10 19: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-12 00:03:55 +03:00
painter . fill_rect ( tile_rect , m_move_highlight_color ) ;
}
2020-08-21 17: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 )
2020-12-29 20:25:13 +03:00
painter . draw_text ( shrunken_rect , coord . substring_view ( 0 , 1 ) , Gfx : : FontDatabase : : default_bold_font ( ) , Gfx : : TextAlignment : : BottomRight , text_color ) ;
2020-08-21 17:53:04 +03:00
if ( sq . file = = coord_rank_file )
2020-12-29 20:25:13 +03:00
painter . draw_text ( shrunken_rect , coord . substring_view ( 1 , 1 ) , Gfx : : FontDatabase : : default_bold_font ( ) , Gfx : : TextAlignment : : TopLeft , text_color ) ;
2020-08-21 17:53:04 +03:00
}
2020-12-10 19: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-12 00:03:55 +03:00
if ( ! ( m_dragging_piece & & sq = = m_moving_square ) ) {
2020-12-10 19:07:10 +03:00
auto bmp = m_pieces . get ( active_board . get_piece ( sq ) ) ;
2020-08-12 00:03:55 +03:00
if ( bmp . has_value ( ) ) {
painter . draw_scaled_bitmap ( tile_rect , * bmp . value ( ) , bmp . value ( ) - > rect ( ) ) ;
2020-08-10 21:54:32 +03:00
}
}
2020-08-12 00:03:55 +03:00
return IterationDecision : : Continue ;
} ) ;
2020-08-10 21:54:32 +03:00
2020-12-10 19: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 ) ;
float hdx = h * cos ( phi ) ;
float hdy = h * sin ( phi ) ;
Gfx : : FloatPoint A1 ( A . x ( ) - ( w1 / 2 ) * cos ( M_PI_2 - phi ) , A . y ( ) - ( w1 / 2 ) * sin ( M_PI_2 - phi ) ) ;
Gfx : : FloatPoint B3 ( A . x ( ) + ( w1 / 2 ) * cos ( M_PI_2 - phi ) , A . y ( ) + ( w1 / 2 ) * sin ( M_PI_2 - phi ) ) ;
Gfx : : FloatPoint A2 ( A1 . x ( ) + ( dx - hdx ) , A1 . y ( ) - ( dy - hdy ) ) ;
Gfx : : FloatPoint B2 ( B3 . x ( ) + ( dx - hdx ) , B3 . y ( ) - ( dy - hdy ) ) ;
Gfx : : FloatPoint A3 ( A2 . x ( ) - w2 * cos ( M_PI_2 - phi ) , A2 . y ( ) - w2 * sin ( M_PI_2 - phi ) ) ;
Gfx : : FloatPoint B1 ( B2 . x ( ) + w2 * cos ( M_PI_2 - phi ) , B2 . y ( ) + w2 * sin ( M_PI_2 - phi ) ) ;
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 15:44:11 +03:00
if ( side ( ) = = Chess : : Color : : White ) {
2020-12-10 19: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 21:54:32 +03:00
if ( m_dragging_piece ) {
2020-12-10 19:07:10 +03:00
auto bmp = m_pieces . get ( active_board . get_piece ( m_moving_square ) ) ;
2020-08-10 21: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 )
{
GUI : : Widget : : mousedown_event ( event ) ;
2020-12-10 19:54:11 +03:00
if ( event . button ( ) = = GUI : : MouseButton : : Right ) {
2021-02-18 11:16:17 +03:00
if ( m_dragging_piece ) {
m_dragging_piece = false ;
} else {
m_current_marking . from = mouse_to_square ( event ) ;
}
2020-12-10 19:54:11 +03:00
return ;
}
m_board_markings . clear ( ) ;
2020-08-10 21:54:32 +03:00
auto square = mouse_to_square ( event ) ;
auto piece = board ( ) . get_piece ( square ) ;
2021-01-09 15:44:11 +03:00
if ( drag_enabled ( ) & & piece . color = = board ( ) . turn ( ) & & ! m_playback ) {
2020-08-10 21:54:32 +03:00
m_dragging_piece = true ;
m_drag_point = event . position ( ) ;
m_moving_square = square ;
}
2020-12-10 19:54:11 +03:00
update ( ) ;
2020-08-10 21:54:32 +03:00
}
void ChessWidget : : mouseup_event ( GUI : : MouseEvent & event )
{
GUI : : Widget : : mouseup_event ( event ) ;
2020-12-10 19: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 21:54:32 +03:00
if ( ! m_dragging_piece )
return ;
m_dragging_piece = false ;
auto target_square = mouse_to_square ( event ) ;
2020-08-12 07: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 19:07:10 +03:00
m_playback_move_number = board ( ) . moves ( ) . size ( ) ;
m_playback = false ;
m_board_playback = m_board ;
2020-08-12 00:03:55 +03:00
2020-12-10 19:07:10 +03:00
if ( board ( ) . game_result ( ) ! = Chess : : Board : : Result : : NotFinished ) {
2020-08-12 23:41:35 +03:00
bool over = true ;
2020-08-12 00:03:55 +03:00
String msg ;
switch ( board ( ) . game_result ( ) ) {
2020-08-19 00:02:53 +03:00
case Chess : : Board : : Result : : CheckMate :
2021-01-09 15:44:11 +03:00
if ( board ( ) . turn ( ) = = Chess : : Color : : White ) {
2020-08-12 00:03:55 +03:00
msg = " Black wins by Checkmate. " ;
} else {
msg = " White wins by Checkmate. " ;
}
break ;
2020-08-19 00:02:53 +03:00
case Chess : : Board : : Result : : StaleMate :
2020-08-12 00:03:55 +03:00
msg = " Draw by Stalemate. " ;
break ;
2020-08-19 00:02:53 +03:00
case Chess : : Board : : Result : : FiftyMoveRule :
2020-08-12 23: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-19 00:02:53 +03:00
case Chess : : Board : : Result : : SeventyFiveMoveRule :
2020-08-12 23:41:35 +03:00
msg = " Draw by 75 move rule. " ;
2020-08-12 00:03:55 +03:00
break ;
2020-10-03 00:14:37 +03:00
case Chess : : Board : : Result : : ThreeFoldRepetition :
2020-08-12 23: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-03 00:14:37 +03:00
msg = " Draw by threefold repetition. " ;
2020-08-12 23:41:35 +03:00
} else {
over = false ;
}
break ;
2020-10-03 00:14:37 +03:00
case Chess : : Board : : Result : : FiveFoldRepetition :
msg = " Draw by fivefold repetition. " ;
2020-08-12 23:41:35 +03:00
break ;
2020-08-19 00:02:53 +03:00
case Chess : : Board : : Result : : InsufficientMaterial :
2020-08-12 23:41:35 +03:00
msg = " Draw by insufficient material. " ;
2020-08-12 00:03:55 +03:00
break ;
default :
2021-02-23 22:42:32 +03:00
VERIFY_NOT_REACHED ( ) ;
2020-08-11 23:10:39 +03:00
}
2020-08-12 23:41:35 +03:00
if ( over ) {
set_drag_enabled ( false ) ;
update ( ) ;
GUI : : MessageBox : : show ( window ( ) , msg , " Game Over " , GUI : : MessageBox : : Type : : Information ) ;
}
2020-08-20 02:53:50 +03:00
} else {
2020-12-17 06:01:58 +03:00
input_engine_move ( ) ;
2020-08-11 23:10:39 +03:00
}
}
2020-08-10 21:54:32 +03:00
update ( ) ;
}
void ChessWidget : : mousemove_event ( GUI : : MouseEvent & event )
{
GUI : : Widget : : mousemove_event ( event ) ;
if ( ! m_dragging_piece )
return ;
m_drag_point = event . position ( ) ;
update ( ) ;
}
2020-12-10 19:07:10 +03:00
void ChessWidget : : keydown_event ( GUI : : KeyEvent & event )
{
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 21: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 ) ;
return Gfx : : Bitmap : : load_from_file ( builder . build ( ) ) ;
}
void ChessWidget : : set_piece_set ( const StringView & set )
{
m_piece_set = set ;
2021-01-09 15: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 21:54:32 +03:00
}
Chess : : Square ChessWidget : : mouse_to_square ( GUI : : MouseEvent & event ) const
{
size_t tile_width = width ( ) / 8 ;
size_t tile_height = height ( ) / 8 ;
2021-01-09 15:44:11 +03:00
if ( side ( ) = = Chess : : Color : : White ) {
2020-08-10 21:54:32 +03:00
return { 7 - ( event . y ( ) / tile_height ) , event . x ( ) / tile_width } ;
} else {
return { event . y ( ) / tile_height , 7 - ( event . x ( ) / tile_width ) } ;
}
}
2020-08-12 03:59:32 +03:00
2020-08-12 07:53:11 +03:00
RefPtr < Gfx : : Bitmap > ChessWidget : : get_piece_graphic ( const Chess : : Piece & piece ) const
{
return m_pieces . get ( piece ) . value ( ) ;
}
2020-08-12 03:59:32 +03:00
void ChessWidget : : reset ( )
{
2020-12-10 19:54:11 +03:00
m_board_markings . clear ( ) ;
2020-12-10 19:07:10 +03:00
m_playback = false ;
m_playback_move_number = 0 ;
m_board_playback = Chess : : Board ( ) ;
2020-08-19 00:02:53 +03:00
m_board = Chess : : Board ( ) ;
2021-01-09 15:44:11 +03:00
m_side = ( arc4random ( ) % 2 ) ? Chess : : Color : : White : Chess : : Color : : Black ;
2020-08-12 03:59:32 +03:00
m_drag_enabled = true ;
2020-12-17 06:01:58 +03:00
input_engine_move ( ) ;
2020-08-12 03:59:32 +03:00
update ( ) ;
}
void ChessWidget : : set_board_theme ( const StringView & name )
{
// FIXME: Add some kind of themes.json
2021-01-09 15: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-12 03: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-20 02:53:50 +03:00
2020-12-17 06:01:58 +03:00
bool ChessWidget : : want_engine_move ( )
2020-08-20 02:53:50 +03:00
{
2020-12-17 06: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-20 02:53:50 +03:00
return ;
bool drag_was_enabled = drag_enabled ( ) ;
if ( drag_was_enabled )
set_drag_enabled ( false ) ;
2020-12-22 21:47:32 +03:00
set_override_cursor ( Gfx : : StandardCursor : : Wait ) ;
2020-08-21 02:04:08 +03:00
m_engine - > get_best_move ( board ( ) , 4000 , [ this , drag_was_enabled ] ( Chess : : Move move ) {
2020-12-22 21:47:32 +03:00
set_override_cursor ( Gfx : : StandardCursor : : None ) ;
2020-12-17 06:01:58 +03:00
if ( ! want_engine_move ( ) )
return ;
2020-08-20 02:53:50 +03:00
set_drag_enabled ( drag_was_enabled ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( board ( ) . apply_move ( move ) ) ;
2020-12-10 19:07:10 +03:00
m_playback_move_number = m_board . moves ( ) . size ( ) ;
m_playback = false ;
2020-12-10 19:54:11 +03:00
m_board_markings . clear ( ) ;
2020-08-20 02:53:50 +03:00
update ( ) ;
} ) ;
}
2020-12-04 15:17:00 +03:00
2020-12-10 19:07:10 +03:00
void ChessWidget : : playback_move ( PlaybackDirection direction )
{
if ( m_board . moves ( ) . is_empty ( ) )
return ;
m_playback = true ;
2020-12-10 19:54:11 +03:00
m_board_markings . clear ( ) ;
2020-12-10 19: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 22:42:32 +03:00
VERIFY_NOT_REACHED ( ) ;
2020-12-10 19:07:10 +03:00
}
update ( ) ;
}
2020-12-10 18:44:38 +03:00
String ChessWidget : : get_fen ( ) const
{
2020-12-10 19:07:10 +03:00
return m_playback ? m_board_playback . to_fen ( ) : m_board . to_fen ( ) ;
2020-12-10 18:44:38 +03:00
}
2020-12-10 19:34:06 +03:00
bool ChessWidget : : import_pgn ( const StringView & import_path )
{
auto file_or_error = Core : : File : : open ( import_path , Core : : File : : OpenMode : : ReadOnly ) ;
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 15:44:11 +03:00
Chess : : Color turn = Chess : : Color : : White ;
2020-12-10 19: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 15:44:11 +03:00
m_board . set_resigned ( Chess : : Color : : Black ) ;
2020-12-10 19:34:06 +03:00
break ;
}
if ( token . contains ( " 0-1 " ) ) {
2021-01-09 15:44:11 +03:00
m_board . set_resigned ( Chess : : Color : : White ) ;
2020-12-10 19: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 15:44:11 +03:00
turn = Chess : : opposing_color ( turn ) ;
2020-12-10 19:34:06 +03:00
}
}
2020-12-10 19:54:11 +03:00
m_board_markings . clear ( ) ;
2020-12-10 19: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 16:26:26 +03:00
bool ChessWidget : : export_pgn ( const StringView & export_path ) const
{
auto file_or_error = Core : : File : : open ( export_path , Core : : File : : WriteOnly ) ;
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 15: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 16: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 15:17:00 +03:00
void ChessWidget : : flip_board ( )
{
2020-12-17 06: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 15:44:11 +03:00
m_side = Chess : : opposing_color ( m_side ) ;
2020-12-17 06:01:58 +03:00
input_engine_move ( ) ;
2020-12-04 15:17:00 +03:00
update ( ) ;
}
2020-12-17 06:01:58 +03:00
int ChessWidget : : resign ( )
2020-12-04 15:17:00 +03:00
{
2020-12-17 06:01:58 +03:00
if ( want_engine_move ( ) ) {
2020-12-04 15:17:00 +03:00
GUI : : MessageBox : : show ( window ( ) , " You can only resign on your turn. " , " Resign " , GUI : : MessageBox : : Type : : Information ) ;
2020-12-17 06:01:58 +03:00
return - 1 ;
2020-12-04 15:17:00 +03:00
}
2020-12-17 06: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 15:17:00 +03:00
board ( ) . set_resigned ( m_board . turn ( ) ) ;
set_drag_enabled ( false ) ;
update ( ) ;
2020-12-04 16:26:26 +03:00
const String msg = Chess : : Board : : result_to_string ( m_board . game_result ( ) , m_board . turn ( ) ) ;
2020-12-04 15:17:00 +03:00
GUI : : MessageBox : : show ( window ( ) , msg , " Game Over " , GUI : : MessageBox : : Type : : Information ) ;
2020-12-17 06:01:58 +03:00
return 0 ;
2020-12-04 15:17:00 +03:00
}