CardStack.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /*
  2. * Copyright (c) 2020, Till Mayer <till.mayer@web.de>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "CardStack.h"
  7. namespace Cards {
  8. CardStack::CardStack()
  9. : m_position({ 0, 0 })
  10. , m_type(Type::Invalid)
  11. , m_base(m_position, { Card::width, Card::height })
  12. {
  13. }
  14. CardStack::CardStack(Gfx::IntPoint const& position, Type type, RefPtr<CardStack> covered_stack)
  15. : m_covered_stack(move(covered_stack))
  16. , m_position(position)
  17. , m_type(type)
  18. , m_rules(rules_for_type(type))
  19. , m_base(m_position, { Card::width, Card::height })
  20. {
  21. VERIFY(type != Type::Invalid);
  22. calculate_bounding_box();
  23. }
  24. void CardStack::clear()
  25. {
  26. m_stack.clear();
  27. m_stack_positions.clear();
  28. }
  29. void CardStack::paint(GUI::Painter& painter, Gfx::Color const& background_color)
  30. {
  31. auto draw_background_if_empty = [&]() {
  32. size_t number_of_moving_cards = 0;
  33. for (auto const& card : m_stack)
  34. number_of_moving_cards += card.is_moving() ? 1 : 0;
  35. if (m_covered_stack && !m_covered_stack->is_empty())
  36. return false;
  37. if (!is_empty() && (m_stack.size() != number_of_moving_cards))
  38. return false;
  39. painter.fill_rect_with_rounded_corners(m_base, background_color.darkened(0.5), Card::card_radius);
  40. painter.fill_rect_with_rounded_corners(m_base.shrunken(2, 2), background_color, Card::card_radius - 1);
  41. return true;
  42. };
  43. switch (m_type) {
  44. case Type::Stock:
  45. if (draw_background_if_empty()) {
  46. painter.fill_rect(m_base.shrunken(Card::width / 4, Card::height / 4), background_color.lightened(1.5));
  47. painter.fill_rect(m_base.shrunken(Card::width / 2, Card::height / 2), background_color);
  48. }
  49. break;
  50. case Type::Foundation:
  51. if (draw_background_if_empty()) {
  52. for (int y = 0; y < (m_base.height() - 4) / 8; ++y) {
  53. for (int x = 0; x < (m_base.width() - 4) / 5; ++x) {
  54. painter.draw_rect({ 4 + m_base.x() + x * 5, 4 + m_base.y() + y * 8, 1, 1 }, background_color.darkened(0.5));
  55. }
  56. }
  57. }
  58. break;
  59. case Type::Play:
  60. case Type::Normal:
  61. draw_background_if_empty();
  62. break;
  63. case Type::Waste:
  64. break;
  65. default:
  66. VERIFY_NOT_REACHED();
  67. }
  68. if (is_empty())
  69. return;
  70. if (m_rules.shift_x == 0 && m_rules.shift_y == 0) {
  71. auto& card = peek();
  72. card.paint(painter);
  73. return;
  74. }
  75. for (auto& card : m_stack) {
  76. if (!card.is_moving())
  77. card.clear_and_paint(painter, Gfx::Color::Transparent);
  78. }
  79. }
  80. void CardStack::rebound_cards()
  81. {
  82. VERIFY(m_stack_positions.size() == m_stack.size());
  83. size_t card_index = 0;
  84. for (auto& card : m_stack)
  85. card.set_position(m_stack_positions.at(card_index++));
  86. }
  87. void CardStack::add_all_grabbed_cards(Gfx::IntPoint const& click_location, NonnullRefPtrVector<Card>& grabbed, MovementRule movement_rule)
  88. {
  89. VERIFY(grabbed.is_empty());
  90. if (m_type != Type::Normal) {
  91. auto& top_card = peek();
  92. if (top_card.rect().contains(click_location)) {
  93. top_card.set_moving(true);
  94. grabbed.append(top_card);
  95. }
  96. return;
  97. }
  98. RefPtr<Card> last_intersect;
  99. for (auto& card : m_stack) {
  100. if (card.rect().contains(click_location)) {
  101. if (card.is_upside_down())
  102. continue;
  103. last_intersect = card;
  104. } else if (!last_intersect.is_null()) {
  105. if (grabbed.is_empty()) {
  106. grabbed.append(*last_intersect);
  107. last_intersect->set_moving(true);
  108. }
  109. if (card.is_upside_down()) {
  110. grabbed.clear();
  111. return;
  112. }
  113. card.set_moving(true);
  114. grabbed.append(card);
  115. }
  116. }
  117. if (grabbed.is_empty() && !last_intersect.is_null()) {
  118. grabbed.append(*last_intersect);
  119. last_intersect->set_moving(true);
  120. }
  121. // verify valid stack
  122. bool valid_stack = true;
  123. uint8_t last_value;
  124. Color last_color;
  125. for (size_t i = 0; i < grabbed.size(); i++) {
  126. auto& card = grabbed.at(i);
  127. if (i != 0) {
  128. bool color_match;
  129. switch (movement_rule) {
  130. case MovementRule::Alternating:
  131. color_match = card.color() != last_color;
  132. break;
  133. case MovementRule::Same:
  134. color_match = card.color() == last_color;
  135. break;
  136. case MovementRule::Any:
  137. color_match = true;
  138. break;
  139. }
  140. if (!color_match || to_underlying(card.rank()) != last_value - 1) {
  141. valid_stack = false;
  142. break;
  143. }
  144. }
  145. last_value = to_underlying(card.rank());
  146. last_color = card.color();
  147. }
  148. if (!valid_stack) {
  149. for (auto& card : grabbed) {
  150. card.set_moving(false);
  151. }
  152. grabbed.clear();
  153. }
  154. }
  155. bool CardStack::is_allowed_to_push(Card const& card, size_t stack_size, MovementRule movement_rule) const
  156. {
  157. if (m_type == Type::Stock || m_type == Type::Waste || m_type == Type::Play)
  158. return false;
  159. if (m_type == Type::Normal && is_empty()) {
  160. // FIXME: proper solution for this
  161. if (movement_rule == MovementRule::Alternating) {
  162. return card.rank() == Rank::King;
  163. }
  164. return true;
  165. }
  166. if (m_type == Type::Foundation && is_empty())
  167. return card.rank() == Rank::Ace;
  168. if (!is_empty()) {
  169. auto const& top_card = peek();
  170. if (top_card.is_upside_down())
  171. return false;
  172. if (m_type == Type::Foundation) {
  173. // Prevent player from dragging an entire stack of cards to the foundation stack
  174. if (stack_size > 1)
  175. return false;
  176. return top_card.suit() == card.suit() && m_stack.size() == to_underlying(card.rank());
  177. }
  178. if (m_type == Type::Normal) {
  179. bool color_match;
  180. switch (movement_rule) {
  181. case MovementRule::Alternating:
  182. color_match = card.color() != top_card.color();
  183. break;
  184. case MovementRule::Same:
  185. color_match = card.color() == top_card.color();
  186. break;
  187. case MovementRule::Any:
  188. color_match = true;
  189. break;
  190. }
  191. return color_match && to_underlying(top_card.rank()) == to_underlying(card.rank()) + 1;
  192. }
  193. VERIFY_NOT_REACHED();
  194. }
  195. return true;
  196. }
  197. bool CardStack::make_top_card_visible()
  198. {
  199. if (is_empty())
  200. return false;
  201. auto& top_card = peek();
  202. if (top_card.is_upside_down()) {
  203. top_card.set_upside_down(false);
  204. return true;
  205. }
  206. return false;
  207. }
  208. void CardStack::push(NonnullRefPtr<Card> card)
  209. {
  210. auto top_most_position = m_stack_positions.is_empty() ? m_position : m_stack_positions.last();
  211. if (!m_stack.is_empty() && m_stack.size() % m_rules.step == 0) {
  212. if (peek().is_upside_down())
  213. top_most_position.translate_by(m_rules.shift_x, m_rules.shift_y_upside_down);
  214. else
  215. top_most_position.translate_by(m_rules.shift_x, m_rules.shift_y);
  216. }
  217. if (m_type == Type::Stock)
  218. card->set_upside_down(true);
  219. card->set_position(top_most_position);
  220. m_stack.append(card);
  221. m_stack_positions.append(top_most_position);
  222. calculate_bounding_box();
  223. }
  224. NonnullRefPtr<Card> CardStack::pop()
  225. {
  226. auto card = m_stack.take_last();
  227. calculate_bounding_box();
  228. if (m_type == Type::Stock)
  229. card->set_upside_down(false);
  230. m_stack_positions.take_last();
  231. return card;
  232. }
  233. void CardStack::move_to_stack(CardStack& stack)
  234. {
  235. while (!m_stack.is_empty()) {
  236. auto card = m_stack.take_first();
  237. m_stack_positions.take_first();
  238. stack.push(move(card));
  239. }
  240. calculate_bounding_box();
  241. }
  242. void CardStack::calculate_bounding_box()
  243. {
  244. m_bounding_box = Gfx::IntRect(m_position, { Card::width, Card::height });
  245. if (m_stack.is_empty())
  246. return;
  247. uint16_t width = 0;
  248. uint16_t height = 0;
  249. size_t card_position = 0;
  250. for (auto& card : m_stack) {
  251. if (card_position % m_rules.step == 0 && card_position != 0) {
  252. if (card.is_upside_down()) {
  253. width += m_rules.shift_x;
  254. height += m_rules.shift_y_upside_down;
  255. } else {
  256. width += m_rules.shift_x;
  257. height += m_rules.shift_y;
  258. }
  259. }
  260. ++card_position;
  261. }
  262. m_bounding_box.set_size(Card::width + width, Card::height + height);
  263. }
  264. }