Serenity Operating System
at portability 253 lines 7.6 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 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 "SnakeGame.h" 28#include <LibGUI/FontDatabase.h> 29#include <LibGUI/Painter.h> 30#include <LibGfx/Bitmap.h> 31#include <LibGfx/Font.h> 32#include <stdlib.h> 33#include <time.h> 34 35SnakeGame::SnakeGame() 36{ 37 set_font(GFontDatabase::the().get_by_name("Liza Regular")); 38 m_fruit_bitmaps.append(*Gfx::Bitmap::load_from_file("/res/icons/snake/paprika.png")); 39 m_fruit_bitmaps.append(*Gfx::Bitmap::load_from_file("/res/icons/snake/eggplant.png")); 40 m_fruit_bitmaps.append(*Gfx::Bitmap::load_from_file("/res/icons/snake/cauliflower.png")); 41 m_fruit_bitmaps.append(*Gfx::Bitmap::load_from_file("/res/icons/snake/tomato.png")); 42 srand(time(nullptr)); 43 reset(); 44 45 m_high_score = 0; 46 m_high_score_text = "Best: 0"; 47} 48 49SnakeGame::~SnakeGame() 50{ 51} 52 53void SnakeGame::reset() 54{ 55 m_head = { m_rows / 2, m_columns / 2 }; 56 m_tail.clear_with_capacity(); 57 m_length = 2; 58 m_score = 0; 59 m_score_text = "Score: 0"; 60 m_velocity_queue.clear(); 61 stop_timer(); 62 start_timer(100); 63 spawn_fruit(); 64 update(); 65} 66 67bool SnakeGame::is_available(const Coordinate& coord) 68{ 69 for (size_t i = 0; i < m_tail.size(); ++i) { 70 if (m_tail[i] == coord) 71 return false; 72 } 73 if (m_head == coord) 74 return false; 75 if (m_fruit == coord) 76 return false; 77 return true; 78} 79 80void SnakeGame::spawn_fruit() 81{ 82 Coordinate coord; 83 for (;;) { 84 coord.row = rand() % m_rows; 85 coord.column = rand() % m_columns; 86 if (is_available(coord)) 87 break; 88 } 89 m_fruit = coord; 90 m_fruit_type = rand() % m_fruit_bitmaps.size(); 91} 92 93Gfx::Rect SnakeGame::score_rect() const 94{ 95 int score_width = font().width(m_score_text); 96 return { width() - score_width - 2, height() - font().glyph_height() - 2, score_width, font().glyph_height() }; 97} 98 99Gfx::Rect SnakeGame::high_score_rect() const 100{ 101 int high_score_width = font().width(m_high_score_text); 102 return { 2, height() - font().glyph_height() - 2, high_score_width, font().glyph_height() }; 103} 104 105void SnakeGame::timer_event(Core::TimerEvent&) 106{ 107 Vector<Coordinate> dirty_cells; 108 109 m_tail.prepend(m_head); 110 111 if (m_tail.size() > m_length) { 112 dirty_cells.append(m_tail.last()); 113 m_tail.take_last(); 114 } 115 116 if (!m_velocity_queue.is_empty()) 117 m_velocity = m_velocity_queue.dequeue(); 118 119 dirty_cells.append(m_head); 120 121 m_head.row += m_velocity.vertical; 122 m_head.column += m_velocity.horizontal; 123 124 m_last_velocity = m_velocity; 125 126 if (m_head.row >= m_rows) 127 m_head.row = 0; 128 if (m_head.row < 0) 129 m_head.row = m_rows - 1; 130 if (m_head.column >= m_columns) 131 m_head.column = 0; 132 if (m_head.column < 0) 133 m_head.column = m_columns - 1; 134 135 dirty_cells.append(m_head); 136 137 for (size_t i = 0; i < m_tail.size(); ++i) { 138 if (m_head == m_tail[i]) { 139 game_over(); 140 return; 141 } 142 } 143 144 if (m_head == m_fruit) { 145 ++m_length; 146 ++m_score; 147 m_score_text = String::format("Score: %u", m_score); 148 if (m_score > m_high_score) { 149 m_high_score = m_score; 150 m_high_score_text = String::format("Best: %u", m_high_score); 151 update(high_score_rect()); 152 } 153 update(score_rect()); 154 dirty_cells.append(m_fruit); 155 spawn_fruit(); 156 dirty_cells.append(m_fruit); 157 } 158 159 for (auto& coord : dirty_cells) { 160 update(cell_rect(coord)); 161 } 162} 163 164void SnakeGame::keydown_event(GUI::KeyEvent& event) 165{ 166 switch (event.key()) { 167 case KeyCode::Key_A: 168 case KeyCode::Key_Left: 169 if (last_velocity().horizontal == 1) 170 break; 171 queue_velocity(0, -1); 172 break; 173 case KeyCode::Key_D: 174 case KeyCode::Key_Right: 175 if (last_velocity().horizontal == -1) 176 break; 177 queue_velocity(0, 1); 178 break; 179 case KeyCode::Key_W: 180 case KeyCode::Key_Up: 181 if (last_velocity().vertical == 1) 182 break; 183 queue_velocity(-1, 0); 184 break; 185 case KeyCode::Key_S: 186 case KeyCode::Key_Down: 187 if (last_velocity().vertical == -1) 188 break; 189 queue_velocity(1, 0); 190 break; 191 default: 192 break; 193 } 194} 195 196Gfx::Rect SnakeGame::cell_rect(const Coordinate& coord) const 197{ 198 auto game_rect = rect(); 199 auto cell_size = Gfx::Size(game_rect.width() / m_columns, game_rect.height() / m_rows); 200 return { 201 coord.column * cell_size.width(), 202 coord.row * cell_size.height(), 203 cell_size.width(), 204 cell_size.height() 205 }; 206} 207 208void SnakeGame::paint_event(GUI::PaintEvent& event) 209{ 210 GUI::Painter painter(*this); 211 painter.add_clip_rect(event.rect()); 212 painter.fill_rect(event.rect(), Color::Black); 213 214 painter.fill_rect(cell_rect(m_head), Color::Yellow); 215 for (auto& part : m_tail) { 216 auto rect = cell_rect(part); 217 painter.fill_rect(rect, Color::from_rgb(0xaaaa00)); 218 219 Gfx::Rect left_side(rect.x(), rect.y(), 2, rect.height()); 220 Gfx::Rect top_side(rect.x(), rect.y(), rect.width(), 2); 221 Gfx::Rect right_side(rect.right() - 1, rect.y(), 2, rect.height()); 222 Gfx::Rect bottom_side(rect.x(), rect.bottom() - 1, rect.width(), 2); 223 painter.fill_rect(left_side, Color::from_rgb(0xcccc00)); 224 painter.fill_rect(right_side, Color::from_rgb(0x888800)); 225 painter.fill_rect(top_side, Color::from_rgb(0xcccc00)); 226 painter.fill_rect(bottom_side, Color::from_rgb(0x888800)); 227 } 228 229 painter.draw_scaled_bitmap(cell_rect(m_fruit), m_fruit_bitmaps[m_fruit_type], m_fruit_bitmaps[m_fruit_type].rect()); 230 231 painter.draw_text(high_score_rect(), m_high_score_text, Gfx::TextAlignment::TopLeft, Color::from_rgb(0xfafae0)); 232 painter.draw_text(score_rect(), m_score_text, Gfx::TextAlignment::TopLeft, Color::White); 233} 234 235void SnakeGame::game_over() 236{ 237 reset(); 238} 239 240void SnakeGame::queue_velocity(int v, int h) 241{ 242 if (last_velocity().vertical == v && last_velocity().horizontal == h) 243 return; 244 m_velocity_queue.enqueue({ v, h }); 245} 246 247const SnakeGame::Velocity& SnakeGame::last_velocity() const 248{ 249 if (!m_velocity_queue.is_empty()) 250 return m_velocity_queue.last(); 251 252 return m_last_velocity; 253}