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