Serenity Operating System
at hosted 435 lines 15 kB view raw
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}