ChessWidget.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include "ChessWidget.h"
  27. #include "PromotionDialog.h"
  28. #include <AK/String.h>
  29. #include <LibGUI/MessageBox.h>
  30. #include <LibGUI/Painter.h>
  31. ChessWidget::ChessWidget(const StringView& set)
  32. {
  33. set_piece_set(set);
  34. }
  35. ChessWidget::ChessWidget()
  36. : ChessWidget("test")
  37. {
  38. }
  39. ChessWidget::~ChessWidget()
  40. {
  41. }
  42. void ChessWidget::paint_event(GUI::PaintEvent& event)
  43. {
  44. GUI::Widget::paint_event(event);
  45. GUI::Painter painter(*this);
  46. painter.add_clip_rect(event.rect());
  47. size_t tile_width = width() / 8;
  48. size_t tile_height = height() / 8;
  49. Chess::Square::for_each([&](Chess::Square sq) {
  50. Gfx::IntRect tile_rect;
  51. if (side() == Chess::Colour::White) {
  52. tile_rect = { sq.file * tile_width, (7 - sq.rank) * tile_height, tile_width, tile_height };
  53. } else {
  54. tile_rect = { (7 - sq.file) * tile_width, sq.rank * tile_height, tile_width, tile_height };
  55. }
  56. painter.fill_rect(tile_rect, (sq.is_light()) ? board_theme().light_square_color : board_theme().dark_square_color);
  57. if (board().last_move().has_value() && (board().last_move().value().to == sq || board().last_move().value().from == sq)) {
  58. painter.fill_rect(tile_rect, m_move_highlight_color);
  59. }
  60. if (!(m_dragging_piece && sq == m_moving_square)) {
  61. auto bmp = m_pieces.get(board().get_piece(sq));
  62. if (bmp.has_value()) {
  63. painter.draw_scaled_bitmap(tile_rect, *bmp.value(), bmp.value()->rect());
  64. }
  65. }
  66. return IterationDecision::Continue;
  67. });
  68. if (m_dragging_piece) {
  69. auto bmp = m_pieces.get(board().get_piece(m_moving_square));
  70. if (bmp.has_value()) {
  71. auto center = m_drag_point - Gfx::IntPoint(tile_width / 2, tile_height / 2);
  72. painter.draw_scaled_bitmap({ center, { tile_width, tile_height } }, *bmp.value(), bmp.value()->rect());
  73. }
  74. }
  75. }
  76. void ChessWidget::resize_event(GUI::ResizeEvent& event)
  77. {
  78. GUI::Widget::resize_event(event);
  79. }
  80. void ChessWidget::mousedown_event(GUI::MouseEvent& event)
  81. {
  82. GUI::Widget::mousedown_event(event);
  83. auto square = mouse_to_square(event);
  84. auto piece = board().get_piece(square);
  85. if (drag_enabled() && piece.colour == board().turn()) {
  86. m_dragging_piece = true;
  87. m_drag_point = event.position();
  88. m_moving_square = square;
  89. update();
  90. }
  91. }
  92. void ChessWidget::mouseup_event(GUI::MouseEvent& event)
  93. {
  94. GUI::Widget::mouseup_event(event);
  95. if (!m_dragging_piece)
  96. return;
  97. m_dragging_piece = false;
  98. auto target_square = mouse_to_square(event);
  99. Chess::Move move = { m_moving_square, target_square };
  100. if (board().is_promotion_move(move)) {
  101. auto promotion_dialog = PromotionDialog::construct(*this);
  102. if (promotion_dialog->exec() == PromotionDialog::ExecOK)
  103. move.promote_to = promotion_dialog->selected_piece();
  104. }
  105. if (board().apply_move(move)) {
  106. if (board().game_result() != Chess::Result::NotFinished) {
  107. bool over = true;
  108. String msg;
  109. switch (board().game_result()) {
  110. case Chess::Result::CheckMate:
  111. if (board().turn() == Chess::Colour::White) {
  112. msg = "Black wins by Checkmate.";
  113. } else {
  114. msg = "White wins by Checkmate.";
  115. }
  116. break;
  117. case Chess::Result::StaleMate:
  118. msg = "Draw by Stalemate.";
  119. break;
  120. case Chess::Result::FiftyMoveRule:
  121. update();
  122. if (GUI::MessageBox::show(window(), "50 moves have elapsed without a capture. Claim Draw?", "Claim Draw?",
  123. GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::YesNo)
  124. == GUI::Dialog::ExecYes) {
  125. msg = "Draw by 50 move rule.";
  126. } else {
  127. over = false;
  128. }
  129. break;
  130. case Chess::Result::SeventyFiveMoveRule:
  131. msg = "Draw by 75 move rule.";
  132. break;
  133. case Chess::Result::ThreeFoldRepitition:
  134. update();
  135. if (GUI::MessageBox::show(window(), "The same board state has repeated three times. Claim Draw?", "Claim Draw?",
  136. GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::YesNo)
  137. == GUI::Dialog::ExecYes) {
  138. msg = "Draw by threefold repitition.";
  139. } else {
  140. over = false;
  141. }
  142. break;
  143. case Chess::Result::FiveFoldRepitition:
  144. msg = "Draw by fivefold repitition.";
  145. break;
  146. case Chess::Result::InsufficientMaterial:
  147. msg = "Draw by insufficient material.";
  148. break;
  149. default:
  150. ASSERT_NOT_REACHED();
  151. }
  152. if (over) {
  153. set_drag_enabled(false);
  154. update();
  155. GUI::MessageBox::show(window(), msg, "Game Over", GUI::MessageBox::Type::Information);
  156. }
  157. }
  158. }
  159. update();
  160. }
  161. void ChessWidget::mousemove_event(GUI::MouseEvent& event)
  162. {
  163. GUI::Widget::mousemove_event(event);
  164. if (!m_dragging_piece)
  165. return;
  166. m_drag_point = event.position();
  167. update();
  168. }
  169. static String set_path = String("/res/icons/chess/sets/");
  170. static RefPtr<Gfx::Bitmap> get_piece(const StringView& set, const StringView& image)
  171. {
  172. StringBuilder builder;
  173. builder.append(set_path);
  174. builder.append(set);
  175. builder.append('/');
  176. builder.append(image);
  177. return Gfx::Bitmap::load_from_file(builder.build());
  178. }
  179. void ChessWidget::set_piece_set(const StringView& set)
  180. {
  181. m_piece_set = set;
  182. m_pieces.set({ Chess::Colour::White, Chess::Type::Pawn }, get_piece(set, "white-pawn.png"));
  183. m_pieces.set({ Chess::Colour::Black, Chess::Type::Pawn }, get_piece(set, "black-pawn.png"));
  184. m_pieces.set({ Chess::Colour::White, Chess::Type::Knight }, get_piece(set, "white-knight.png"));
  185. m_pieces.set({ Chess::Colour::Black, Chess::Type::Knight }, get_piece(set, "black-knight.png"));
  186. m_pieces.set({ Chess::Colour::White, Chess::Type::Bishop }, get_piece(set, "white-bishop.png"));
  187. m_pieces.set({ Chess::Colour::Black, Chess::Type::Bishop }, get_piece(set, "black-bishop.png"));
  188. m_pieces.set({ Chess::Colour::White, Chess::Type::Rook }, get_piece(set, "white-rook.png"));
  189. m_pieces.set({ Chess::Colour::Black, Chess::Type::Rook }, get_piece(set, "black-rook.png"));
  190. m_pieces.set({ Chess::Colour::White, Chess::Type::Queen }, get_piece(set, "white-queen.png"));
  191. m_pieces.set({ Chess::Colour::Black, Chess::Type::Queen }, get_piece(set, "black-queen.png"));
  192. m_pieces.set({ Chess::Colour::White, Chess::Type::King }, get_piece(set, "white-king.png"));
  193. m_pieces.set({ Chess::Colour::Black, Chess::Type::King }, get_piece(set, "black-king.png"));
  194. }
  195. Chess::Square ChessWidget::mouse_to_square(GUI::MouseEvent& event) const
  196. {
  197. size_t tile_width = width() / 8;
  198. size_t tile_height = height() / 8;
  199. if (side() == Chess::Colour::White) {
  200. return { 7 - (event.y() / tile_height), event.x() / tile_width };
  201. } else {
  202. return { event.y() / tile_height, 7 - (event.x() / tile_width) };
  203. }
  204. }
  205. RefPtr<Gfx::Bitmap> ChessWidget::get_piece_graphic(const Chess::Piece& piece) const
  206. {
  207. return m_pieces.get(piece).value();
  208. }
  209. void ChessWidget::reset()
  210. {
  211. m_board = Chess();
  212. m_drag_enabled = true;
  213. update();
  214. }
  215. void ChessWidget::set_board_theme(const StringView& name)
  216. {
  217. // FIXME: Add some kind of themes.json
  218. // The following Colours have been taken from lichess.org, but i'm pretty sure they took them from chess.com.
  219. if (name == "Beige") {
  220. m_board_theme = { "Beige", Color::from_rgb(0xb58863), Color::from_rgb(0xf0d9b5) };
  221. } else if (name == "Green") {
  222. m_board_theme = { "Green", Color::from_rgb(0x86a666), Color::from_rgb(0xffffdd) };
  223. } else if (name == "Blue") {
  224. m_board_theme = { "Blue", Color::from_rgb(0x8ca2ad), Color::from_rgb(0xdee3e6) };
  225. } else {
  226. set_board_theme("Beige");
  227. }
  228. }