CardStack.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. /*
  2. * Copyright (c) 2020, Till Mayer <till.mayer@web.de>
  3. * Copyright (c) 2023, David Ganz <david.g.ganz@gmail.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "CardStack.h"
  8. namespace Cards {
  9. CardStack::CardStack()
  10. : m_position({ 0, 0 })
  11. , m_type(Type::Invalid)
  12. , m_base(m_position, { Card::width, Card::height })
  13. {
  14. }
  15. CardStack::CardStack(Gfx::IntPoint position, Type type, RefPtr<CardStack> covered_stack)
  16. : m_covered_stack(move(covered_stack))
  17. , m_position(position)
  18. , m_type(type)
  19. , m_rules(rules_for_type(type))
  20. , m_base(m_position, { Card::width, Card::height })
  21. {
  22. VERIFY(type != Type::Invalid);
  23. calculate_bounding_box();
  24. }
  25. void CardStack::clear()
  26. {
  27. m_stack.clear();
  28. m_stack_positions.clear();
  29. }
  30. void CardStack::paint(GUI::Painter& painter, Gfx::Color background_color)
  31. {
  32. auto background_markings_color = (background_color.luminosity() > 64) ? Color(0, 0, 0, 128) : Color(255, 255, 255, 128);
  33. auto draw_background_if_empty = [&]() {
  34. size_t number_of_moving_cards = 0;
  35. for (auto const& card : m_stack)
  36. number_of_moving_cards += card->is_moving() ? 1 : 0;
  37. if (m_covered_stack && !m_covered_stack->is_empty())
  38. return false;
  39. if (!is_empty() && (m_stack.size() != number_of_moving_cards))
  40. return false;
  41. auto paint_rect = m_base;
  42. painter.fill_rect_with_rounded_corners(paint_rect, background_markings_color, Card::card_radius);
  43. paint_rect.shrink(2, 2);
  44. if (m_highlighted) {
  45. auto background_complement = background_color.xored(Color::White);
  46. painter.fill_rect_with_rounded_corners(paint_rect, background_complement, Card::card_radius - 1);
  47. paint_rect.shrink(4, 4);
  48. }
  49. painter.fill_rect_with_rounded_corners(paint_rect, background_color, Card::card_radius - 1);
  50. return true;
  51. };
  52. switch (m_type) {
  53. case Type::Stock:
  54. if (draw_background_if_empty()) {
  55. auto stock_highlight_color = (background_color.luminosity() < 196) ? Color(255, 255, 255, 128) : Color(0, 0, 0, 64);
  56. painter.fill_rect(m_base.shrunken(Card::width / 4, Card::height / 4), stock_highlight_color);
  57. painter.fill_rect(m_base.shrunken(Card::width / 2, Card::height / 2), background_color);
  58. }
  59. break;
  60. case Type::Foundation:
  61. if (draw_background_if_empty()) {
  62. for (int y = 0; y < (m_base.height() - 4) / 8; ++y) {
  63. for (int x = 0; x < (m_base.width() - 4) / 5; ++x) {
  64. painter.draw_rect({ 4 + m_base.x() + x * 5, 4 + m_base.y() + y * 8, 1, 1 }, background_markings_color);
  65. }
  66. }
  67. }
  68. break;
  69. case Type::Play:
  70. case Type::Normal:
  71. draw_background_if_empty();
  72. break;
  73. case Type::Waste:
  74. break;
  75. default:
  76. VERIFY_NOT_REACHED();
  77. }
  78. if (is_empty())
  79. return;
  80. if (m_rules.shift_x == 0 && m_rules.shift_y == 0) {
  81. auto& card = peek();
  82. card.paint(painter);
  83. return;
  84. }
  85. RefPtr<Card> previewed_card;
  86. for (size_t i = 0; i < m_stack.size(); ++i) {
  87. if (auto& card = m_stack[i]; !card->is_moving()) {
  88. if (card->is_previewed()) {
  89. VERIFY(!previewed_card);
  90. previewed_card = card;
  91. continue;
  92. }
  93. auto highlighted = m_highlighted && (i == m_stack.size() - 1);
  94. card->clear_and_paint(painter, Gfx::Color::Transparent, highlighted);
  95. }
  96. }
  97. if (previewed_card)
  98. previewed_card->clear_and_paint(painter, Gfx::Color::Transparent, false);
  99. }
  100. void CardStack::rebound_cards()
  101. {
  102. VERIFY(m_stack_positions.size() == m_stack.size());
  103. size_t card_index = 0;
  104. for (auto& card : m_stack)
  105. card->set_position(m_stack_positions.at(card_index++));
  106. }
  107. ErrorOr<void> CardStack::add_all_grabbed_cards(Gfx::IntPoint click_location, Vector<NonnullRefPtr<Card>>& grabbed, MovementRule movement_rule)
  108. {
  109. VERIFY(grabbed.is_empty());
  110. if (m_type != Type::Normal) {
  111. auto& top_card = peek();
  112. if (top_card.rect().contains(click_location)) {
  113. top_card.set_moving(true);
  114. TRY(grabbed.try_append(top_card));
  115. }
  116. return {};
  117. }
  118. RefPtr<Card> last_intersect;
  119. for (auto& card : m_stack) {
  120. if (card->rect().contains(click_location)) {
  121. if (card->is_upside_down())
  122. continue;
  123. last_intersect = card;
  124. } else if (!last_intersect.is_null()) {
  125. if (grabbed.is_empty()) {
  126. TRY(grabbed.try_append(*last_intersect));
  127. last_intersect->set_moving(true);
  128. }
  129. if (card->is_upside_down()) {
  130. grabbed.clear();
  131. return {};
  132. }
  133. card->set_moving(true);
  134. TRY(grabbed.try_append(card));
  135. }
  136. }
  137. if (grabbed.is_empty() && !last_intersect.is_null()) {
  138. TRY(grabbed.try_append(*last_intersect));
  139. last_intersect->set_moving(true);
  140. }
  141. // verify valid stack
  142. bool valid_stack = true;
  143. uint8_t last_value;
  144. Color last_color;
  145. for (size_t i = 0; i < grabbed.size(); i++) {
  146. auto& card = grabbed.at(i);
  147. if (i != 0) {
  148. bool color_match;
  149. switch (movement_rule) {
  150. case MovementRule::Alternating:
  151. color_match = card->color() != last_color;
  152. break;
  153. case MovementRule::Same:
  154. color_match = card->color() == last_color;
  155. break;
  156. case MovementRule::Any:
  157. color_match = true;
  158. break;
  159. }
  160. if (!color_match || to_underlying(card->rank()) != last_value - 1) {
  161. valid_stack = false;
  162. break;
  163. }
  164. }
  165. last_value = to_underlying(card->rank());
  166. last_color = card->color();
  167. }
  168. if (!valid_stack) {
  169. for (auto& card : grabbed) {
  170. card->set_moving(false);
  171. }
  172. grabbed.clear();
  173. }
  174. return {};
  175. }
  176. void CardStack::update_disabled_cards(MovementRule movement_rule)
  177. {
  178. if (m_stack.is_empty())
  179. return;
  180. for (auto& card : m_stack)
  181. card->set_disabled(false);
  182. Optional<size_t> last_valid_card = {};
  183. uint8_t last_rank;
  184. Color last_color;
  185. for (size_t idx = m_stack.size(); idx > 0; idx--) {
  186. auto i = idx - 1;
  187. auto card = m_stack[i];
  188. if (card->is_upside_down()) {
  189. if (!last_valid_card.has_value())
  190. last_valid_card = i + 1;
  191. break;
  192. }
  193. if (i != m_stack.size() - 1) {
  194. bool color_valid;
  195. switch (movement_rule) {
  196. case MovementRule::Alternating:
  197. color_valid = card->color() != last_color;
  198. break;
  199. case MovementRule::Same:
  200. color_valid = card->color() == last_color;
  201. break;
  202. case MovementRule::Any:
  203. color_valid = true;
  204. break;
  205. }
  206. if (!color_valid || to_underlying(card->rank()) != last_rank + 1) {
  207. last_valid_card = i + 1;
  208. break;
  209. }
  210. }
  211. last_rank = to_underlying(card->rank());
  212. last_color = card->color();
  213. }
  214. if (!last_valid_card.has_value())
  215. return;
  216. for (size_t i = 0; i < last_valid_card.value(); i++)
  217. m_stack[i]->set_disabled(true);
  218. }
  219. bool CardStack::is_allowed_to_push(Card const& card, size_t stack_size, MovementRule movement_rule) const
  220. {
  221. if (m_type == Type::Stock || m_type == Type::Waste || m_type == Type::Play)
  222. return false;
  223. if (m_type == Type::Normal && is_empty()) {
  224. // FIXME: proper solution for this
  225. if (movement_rule == MovementRule::Alternating) {
  226. return card.rank() == Rank::King;
  227. }
  228. return true;
  229. }
  230. if (m_type == Type::Foundation && is_empty())
  231. return card.rank() == Rank::Ace;
  232. if (!is_empty()) {
  233. auto const& top_card = peek();
  234. if (top_card.is_upside_down())
  235. return false;
  236. if (m_type == Type::Foundation) {
  237. // Prevent player from dragging an entire stack of cards to the foundation stack
  238. if (stack_size > 1)
  239. return false;
  240. return top_card.suit() == card.suit() && m_stack.size() == to_underlying(card.rank());
  241. }
  242. if (m_type == Type::Normal) {
  243. bool color_match;
  244. switch (movement_rule) {
  245. case MovementRule::Alternating:
  246. color_match = card.color() != top_card.color();
  247. break;
  248. case MovementRule::Same:
  249. color_match = card.color() == top_card.color();
  250. break;
  251. case MovementRule::Any:
  252. color_match = true;
  253. break;
  254. }
  255. return color_match && to_underlying(top_card.rank()) == to_underlying(card.rank()) + 1;
  256. }
  257. VERIFY_NOT_REACHED();
  258. }
  259. return true;
  260. }
  261. bool CardStack::preview_card(Gfx::IntPoint click_location)
  262. {
  263. RefPtr<Card> last_intersect;
  264. for (auto& card : m_stack) {
  265. if (!card->rect().contains(click_location))
  266. continue;
  267. if (card->is_upside_down())
  268. continue;
  269. last_intersect = card;
  270. }
  271. if (!last_intersect)
  272. return false;
  273. last_intersect->set_previewed(true);
  274. return true;
  275. }
  276. void CardStack::clear_card_preview()
  277. {
  278. for (auto& card : m_stack)
  279. card->set_previewed(false);
  280. }
  281. bool CardStack::make_top_card_visible()
  282. {
  283. if (is_empty())
  284. return false;
  285. auto& top_card = peek();
  286. if (top_card.is_upside_down()) {
  287. top_card.set_upside_down(false);
  288. return true;
  289. }
  290. return false;
  291. }
  292. ErrorOr<void> CardStack::push(NonnullRefPtr<Card> card)
  293. {
  294. auto top_most_position = m_stack_positions.is_empty() ? m_position : m_stack_positions.last();
  295. if (!m_stack.is_empty() && m_stack.size() % m_rules.step == 0) {
  296. if (peek().is_upside_down())
  297. top_most_position.translate_by(m_rules.shift_x, m_rules.shift_y_upside_down);
  298. else
  299. top_most_position.translate_by(m_rules.shift_x, m_rules.shift_y);
  300. }
  301. if (m_type == Type::Stock)
  302. card->set_upside_down(true);
  303. card->set_position(top_most_position);
  304. TRY(m_stack.try_append(card));
  305. TRY(m_stack_positions.try_append(top_most_position));
  306. calculate_bounding_box();
  307. return {};
  308. }
  309. NonnullRefPtr<Card> CardStack::pop()
  310. {
  311. auto card = m_stack.take_last();
  312. calculate_bounding_box();
  313. if (m_type == Type::Stock)
  314. card->set_upside_down(false);
  315. m_stack_positions.take_last();
  316. return card;
  317. }
  318. ErrorOr<void> CardStack::take_all(CardStack& stack)
  319. {
  320. while (!m_stack.is_empty()) {
  321. auto card = m_stack.take_first();
  322. m_stack_positions.take_first();
  323. TRY(stack.push(move(card)));
  324. }
  325. calculate_bounding_box();
  326. return {};
  327. }
  328. void CardStack::calculate_bounding_box()
  329. {
  330. m_bounding_box = Gfx::IntRect(m_position, { Card::width, Card::height });
  331. if (m_stack.is_empty())
  332. return;
  333. uint16_t width = 0;
  334. uint16_t height = 0;
  335. size_t card_position = 0;
  336. for (auto& card : m_stack) {
  337. if (card_position % m_rules.step == 0 && card_position != 0) {
  338. if (card->is_upside_down()) {
  339. width += m_rules.shift_x;
  340. height += m_rules.shift_y_upside_down;
  341. } else {
  342. width += m_rules.shift_x;
  343. height += m_rules.shift_y;
  344. }
  345. }
  346. ++card_position;
  347. }
  348. m_bounding_box.set_size(Card::width + width, Card::height + height);
  349. }
  350. }