Serenity Operating System
1/*
2 * Copyright (c) 2020, Till Mayer <till.mayer@web.de>
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
27#include "SolitaireWidget.h"
28#include <LibCore/Timer.h>
29#include <LibGUI/Painter.h>
30#include <LibGUI/Window.h>
31#include <time.h>
32
33static const Color s_background_color { Color::from_rgb(0x008000) };
34
35SolitaireWidget::SolitaireWidget(GUI::Window& window, Function<void(uint32_t)>&& on_score_update)
36 : m_on_score_update(move(on_score_update))
37{
38 set_fill_with_background_color(false);
39
40 m_stacks[Stock] = CardStack({ 10, 10 }, CardStack::Type::Stock, 2, 1, 8);
41 m_stacks[Waste] = CardStack({ 10 + Card::width + 10, 10 }, CardStack::Type::Waste, 2, 1, 8);
42 m_stacks[Foundation4] = CardStack({ SolitaireWidget::width - Card::width - 10, 10 }, CardStack::Type::Foundation, 2, 1, 4);
43 m_stacks[Foundation3] = CardStack({ SolitaireWidget::width - 2 * Card::width - 20, 10 }, CardStack::Type::Foundation, 2, 1, 4);
44 m_stacks[Foundation2] = CardStack({ SolitaireWidget::width - 3 * Card::width - 30, 10 }, CardStack::Type::Foundation, 2, 1, 4);
45 m_stacks[Foundation1] = CardStack({ SolitaireWidget::width - 4 * Card::width - 40, 10 }, CardStack::Type::Foundation, 2, 1, 4);
46 m_stacks[Pile1] = CardStack({ 10, 10 + Card::height + 10 }, CardStack::Type::Normal, 0, 15);
47 m_stacks[Pile2] = CardStack({ 10 + Card::width + 10, 10 + Card::height + 10 }, CardStack::Type::Normal, 0, 15);
48 m_stacks[Pile3] = CardStack({ 10 + 2 * Card::width + 20, 10 + Card::height + 10 }, CardStack::Type::Normal, 0, 15);
49 m_stacks[Pile4] = CardStack({ 10 + 3 * Card::width + 30, 10 + Card::height + 10 }, CardStack::Type::Normal, 0, 15);
50 m_stacks[Pile5] = CardStack({ 10 + 4 * Card::width + 40, 10 + Card::height + 10 }, CardStack::Type::Normal, 0, 15);
51 m_stacks[Pile6] = CardStack({ 10 + 5 * Card::width + 50, 10 + Card::height + 10 }, CardStack::Type::Normal, 0, 15);
52 m_stacks[Pile7] = CardStack({ 10 + 6 * Card::width + 60, 10 + Card::height + 10 }, CardStack::Type::Normal, 0, 15);
53
54 m_timer = Core::Timer::construct(1000 / 60, [&]() { tick(window); });
55 m_timer->stop();
56}
57
58SolitaireWidget::~SolitaireWidget()
59{
60}
61
62static float rand_float()
63{
64 return rand() / static_cast<float>(RAND_MAX);
65}
66
67static void make_pile(NonnullRefPtrVector<Card>& cards, CardStack& stack, uint8_t count)
68{
69 for (int i = 1; i < count; ++i) {
70 auto card = cards.take_last();
71 card->set_upside_down(true);
72 stack.push(card);
73 }
74
75 stack.push(cards.take_last());
76}
77
78void SolitaireWidget::tick(GUI::Window& window)
79{
80 if (!is_visible() || !updates_enabled() || !window.is_visible_for_timer_purposes())
81 return;
82
83 if (m_game_over_animation) {
84 if (m_animation.card()->position().x() > SolitaireWidget::width
85 || m_animation.card()->rect().right() < 0) {
86 create_new_animation_card();
87 }
88
89 m_animation.tick();
90 }
91
92 if (m_has_to_repaint || m_game_over_animation) {
93 m_repaint_all = false;
94 update();
95 }
96}
97
98void SolitaireWidget::create_new_animation_card()
99{
100 srand(time(nullptr));
101
102 auto card = Card::construct(static_cast<Card::Type>(rand() % Card::Type::__Count), rand() % Card::card_count);
103 card->set_position({ rand() % (SolitaireWidget::width - Card::width), rand() % (SolitaireWidget::height / 8) });
104
105 int x_sgn = card->position().x() > (SolitaireWidget::width / 2) ? -1 : 1;
106 m_animation = Animation(card, rand_float() + .4, x_sgn * ((rand() % 3) + 2), .6 + rand_float() * .4);
107}
108
109void SolitaireWidget::start_game_over_animation()
110{
111 if (m_game_over_animation)
112 return;
113
114 create_new_animation_card();
115 m_game_over_animation = true;
116}
117
118void SolitaireWidget::stop_game_over_animation()
119{
120 if (!m_game_over_animation)
121 return;
122
123 m_game_over_animation = false;
124 m_repaint_all = true;
125 update();
126}
127
128void SolitaireWidget::setup()
129{
130 stop_game_over_animation();
131
132 for (auto& stack : m_stacks)
133 stack.clear();
134
135 NonnullRefPtrVector<Card> cards;
136 for (int i = 0; i < Card::card_count; ++i) {
137 cards.append(Card::construct(Card::Type::Clubs, i));
138 cards.append(Card::construct(Card::Type::Spades, i));
139 cards.append(Card::construct(Card::Type::Hearts, i));
140 cards.append(Card::construct(Card::Type::Diamonds, i));
141 }
142
143 srand(time(nullptr));
144 for (int i = 0; i < 200; ++i)
145 cards.append(cards.take(rand() % cards.size()));
146
147 make_pile(cards, stack(Pile1), 1);
148 make_pile(cards, stack(Pile2), 2);
149 make_pile(cards, stack(Pile3), 3);
150 make_pile(cards, stack(Pile4), 4);
151 make_pile(cards, stack(Pile5), 5);
152 make_pile(cards, stack(Pile6), 6);
153 make_pile(cards, stack(Pile7), 7);
154
155 while (!cards.is_empty())
156 stack(Stock).push(cards.take_last());
157
158 m_score = 0;
159 update_score(0);
160 update();
161}
162
163void SolitaireWidget::update_score(int to_add)
164{
165 m_score = max(static_cast<int>(m_score) + to_add, 0);
166 m_on_score_update(m_score);
167}
168
169void SolitaireWidget::keydown_event(GUI::KeyEvent& event)
170{
171 if (event.key() == KeyCode::Key_F12)
172 start_game_over_animation();
173}
174
175void SolitaireWidget::mousedown_event(GUI::MouseEvent& event)
176{
177 GUI::Widget::mousedown_event(event);
178
179 if (m_game_over_animation)
180 return;
181
182 auto click_location = event.position();
183 for (auto& to_check : m_stacks) {
184 if (to_check.bounding_box().contains(click_location)) {
185 if (to_check.type() == CardStack::Type::Stock) {
186 auto& waste = stack(Waste);
187 auto& stock = stack(Stock);
188
189 if (stock.is_empty()) {
190 if (waste.is_empty())
191 return;
192
193 while (!waste.is_empty()) {
194 auto card = waste.pop();
195 stock.push(card);
196 }
197
198 stock.set_dirty();
199 waste.set_dirty();
200 m_has_to_repaint = true;
201 update_score(-100);
202 } else {
203 move_card(stock, waste);
204 }
205 } else if (!to_check.is_empty()) {
206 auto& top_card = to_check.peek();
207
208 if (top_card.is_upside_down()) {
209 if (top_card.rect().contains(click_location)) {
210 top_card.set_upside_down(false);
211 to_check.set_dirty();
212 update_score(5);
213 m_has_to_repaint = true;
214 }
215 } else if (m_focused_cards.is_empty()) {
216 to_check.add_all_grabbed_cards(click_location, m_focused_cards);
217 m_mouse_down_location = click_location;
218 to_check.set_focused(true);
219 m_focused_stack = &to_check;
220 m_mouse_down = true;
221 }
222 }
223 break;
224 }
225 }
226}
227
228void SolitaireWidget::mouseup_event(GUI::MouseEvent& event)
229{
230 GUI::Widget::mouseup_event(event);
231
232 if (!m_focused_stack || m_focused_cards.is_empty() || m_game_over_animation)
233 return;
234
235 bool rebound = true;
236 for (auto& stack : m_stacks) {
237 if (stack.is_focused())
238 continue;
239
240 for (auto& focused_card : m_focused_cards) {
241 if (stack.bounding_box().intersects(focused_card.rect())) {
242 if (stack.is_allowed_to_push(m_focused_cards.at(0))) {
243 for (auto& to_intersect : m_focused_cards) {
244 mark_intersecting_stacks_dirty(to_intersect);
245 stack.push(to_intersect);
246 m_focused_stack->pop();
247 }
248
249 m_focused_stack->set_dirty();
250 stack.set_dirty();
251
252 if (m_focused_stack->type() == CardStack::Type::Waste
253 && stack.type() == CardStack::Type::Normal) {
254 update_score(5);
255 } else if (m_focused_stack->type() == CardStack::Type::Waste
256 && stack.type() == CardStack::Type::Foundation) {
257 update_score(10);
258 } else if (m_focused_stack->type() == CardStack::Type::Normal
259 && stack.type() == CardStack::Type::Foundation) {
260 update_score(10);
261 } else if (m_focused_stack->type() == CardStack::Type::Foundation
262 && stack.type() == CardStack::Type::Normal) {
263 update_score(-15);
264 }
265
266 rebound = false;
267 break;
268 }
269 }
270 }
271 }
272
273 if (rebound) {
274 for (auto& to_intersect : m_focused_cards)
275 mark_intersecting_stacks_dirty(to_intersect);
276
277 m_focused_stack->rebound_cards();
278 m_focused_stack->set_dirty();
279 }
280
281 m_mouse_down = false;
282 m_has_to_repaint = true;
283}
284
285void SolitaireWidget::mousemove_event(GUI::MouseEvent& event)
286{
287 GUI::Widget::mousemove_event(event);
288
289 if (!m_mouse_down || m_game_over_animation)
290 return;
291
292 auto click_location = event.position();
293 int dx = click_location.dx_relative_to(m_mouse_down_location);
294 int dy = click_location.dy_relative_to(m_mouse_down_location);
295
296 for (auto& to_intersect : m_focused_cards) {
297 mark_intersecting_stacks_dirty(to_intersect);
298 to_intersect.rect().move_by(dx, dy);
299 }
300
301 m_mouse_down_location = click_location;
302 m_has_to_repaint = true;
303}
304
305void SolitaireWidget::doubleclick_event(GUI::MouseEvent& event)
306{
307 GUI::Widget::doubleclick_event(event);
308
309 if (m_game_over_animation) {
310 start_game_over_animation();
311 setup();
312 return;
313 }
314
315 auto click_location = event.position();
316 for (auto& to_check : m_stacks) {
317 if (to_check.type() == CardStack::Type::Foundation || to_check.type() == CardStack::Type::Stock)
318 continue;
319
320 if (to_check.bounding_box().contains(click_location) && !to_check.is_empty()) {
321 auto& top_card = to_check.peek();
322 if (!top_card.is_upside_down() && top_card.rect().contains(click_location)) {
323 if (stack(Foundation1).is_allowed_to_push(top_card))
324 move_card(to_check, stack(Foundation1));
325 else if (stack(Foundation2).is_allowed_to_push(top_card))
326 move_card(to_check, stack(Foundation2));
327 else if (stack(Foundation3).is_allowed_to_push(top_card))
328 move_card(to_check, stack(Foundation3));
329 else if (stack(Foundation4).is_allowed_to_push(top_card))
330 move_card(to_check, stack(Foundation4));
331 else
332 break;
333
334 update_score(10);
335 }
336 break;
337 }
338 }
339
340 m_has_to_repaint = true;
341}
342
343void SolitaireWidget::check_for_game_over()
344{
345 for (auto& stack : m_stacks) {
346 if (stack.type() != CardStack::Type::Foundation)
347 continue;
348 if (stack.count() != Card::card_count)
349 return;
350 }
351
352 start_game_over_animation();
353}
354
355void SolitaireWidget::move_card(CardStack& from, CardStack& to)
356{
357 auto card = from.pop();
358
359 card->set_moving(true);
360 m_focused_cards.clear();
361 m_focused_cards.append(card);
362 mark_intersecting_stacks_dirty(card);
363 to.push(card);
364
365 from.set_dirty();
366 to.set_dirty();
367
368 m_has_to_repaint = true;
369}
370
371void SolitaireWidget::mark_intersecting_stacks_dirty(Card& intersecting_card)
372{
373 for (auto& stack : m_stacks) {
374 if (intersecting_card.rect().intersects(stack.bounding_box())) {
375 stack.set_dirty();
376 m_has_to_repaint = true;
377 }
378 }
379}
380
381void SolitaireWidget::paint_event(GUI::PaintEvent& event)
382{
383 GUI::Widget::paint_event(event);
384
385 m_has_to_repaint = false;
386 if (m_game_over_animation && m_repaint_all)
387 return;
388
389 GUI::Painter painter(*this);
390
391 if (m_repaint_all) {
392 /* Only start the timer when update() got called from the
393 window manager, or else we might end up with a blank screen */
394 if (!m_timer->is_active())
395 m_timer->start();
396
397 painter.fill_rect(event.rect(), s_background_color);
398
399 for (auto& stack : m_stacks)
400 stack.draw(painter, s_background_color);
401 } else if (!m_game_over_animation) {
402 if (!m_focused_cards.is_empty()) {
403 for (auto& focused_card : m_focused_cards)
404 focused_card.clear(painter, s_background_color);
405 }
406
407 for (auto& stack : m_stacks) {
408 if (stack.is_dirty())
409 stack.draw(painter, s_background_color);
410 }
411
412 if (!m_focused_cards.is_empty()) {
413 for (auto& focused_card : m_focused_cards) {
414 focused_card.draw(painter);
415 focused_card.save_old_position();
416 }
417 }
418 } else if (m_animation.card() != nullptr)
419 m_animation.card()->draw(painter);
420
421 m_repaint_all = true;
422 if (!m_mouse_down) {
423 if (!m_focused_cards.is_empty()) {
424 check_for_game_over();
425 for (auto& card : m_focused_cards)
426 card.set_moving(false);
427 m_focused_cards.clear();
428 }
429
430 if (m_focused_stack) {
431 m_focused_stack->set_focused(false);
432 m_focused_stack = nullptr;
433 }
434 }
435}