diff --git a/firmware/application/external/doom/Arial12x12.h b/firmware/application/external/doom/Arial12x12.h new file mode 100644 index 000000000..4dc451eeb --- /dev/null +++ b/firmware/application/external/doom/Arial12x12.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 Mark Thompson + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +// dummy include file to avoid changing original source + +#ifndef __UI_Arial12x12_H__ +#define __UI_Arial12x12_H__ + +#define Arial12x12 (0) + +#endif /*__UI_Arial12x12_H__*/ diff --git a/firmware/application/external/doom/SPI_TFT_ILI9341.h b/firmware/application/external/doom/SPI_TFT_ILI9341.h new file mode 100644 index 000000000..8e1bbd59c --- /dev/null +++ b/firmware/application/external/doom/SPI_TFT_ILI9341.h @@ -0,0 +1,30 @@ +#ifndef __UI_SPI_TFT_ILI9341_H__ +#define __UI_SPI_TFT_ILI9341_H__ + +ui::Painter painter; + +enum { + White, + Blue, + Yellow, + Purple, + Green, + Red, + Maroon, + Orange, + Black, +}; + +static const Color pp_colors[] = { + Color::white(), + Color::blue(), + Color::yellow(), + Color::purple(), + Color::green(), + Color::red(), + Color::magenta(), + Color::orange(), + Color::black(), +}; + +#endif /*__UI_SPI_TFT_ILI9341_H__*/ \ No newline at end of file diff --git a/firmware/application/external/doom/main.cpp b/firmware/application/external/doom/main.cpp new file mode 100644 index 000000000..374041b67 --- /dev/null +++ b/firmware/application/external/doom/main.cpp @@ -0,0 +1,67 @@ +/* + * ------------------------------------------------------------ + * | Made by RocketGod | + * | Find me at https://betaskynet.com | + * | Argh matey! | + * ------------------------------------------------------------ + */ + +#include "ui_doom.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::doom { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::doom + +extern "C" { + +__attribute__((section(".external_app.app_doom.application_information"), used)) application_information_t _application_information_doom = { + (uint8_t*)0x00000000, + ui::external_app::doom::initialize_app, + CURRENT_HEADER_VERSION, + VERSION_MD5, + "Doom", + { + 0x00, + 0x00, + 0x00, + 0x00, + 0x27, + 0xDA, + 0x59, + 0xAD, + 0x89, + 0xA8, + 0x89, + 0xA8, + 0x89, + 0xA8, + 0x89, + 0x88, + 0x89, + 0x88, + 0x89, + 0x88, + 0x59, + 0x8D, + 0x29, + 0x8A, + 0x05, + 0x80, + 0x03, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + }, + ui::Color::red().v, + app_location_t::GAMES, + -1, + {0, 0, 0, 0}, + 0x00000000, +}; +} \ No newline at end of file diff --git a/firmware/application/external/doom/mbed.h b/firmware/application/external/doom/mbed.h new file mode 100644 index 000000000..f2ee47d63 --- /dev/null +++ b/firmware/application/external/doom/mbed.h @@ -0,0 +1,81 @@ +#ifndef __UI_mbed_H__ +#define __UI_mbed_H__ + +using Callback = void (*)(void); + +#define wait_us(x) (void)0 +#define wait(x) chThdSleepMilliseconds(x * 1000) +#define PullUp 1 + +#include "ui_navigation.hpp" + +enum { + dp0, + dp1, + dp2, + dp3, + dp4, + dp5, + dp6, + dp7, + dp8, + dp9, + dp10, + dp11, + dp12, + dp13, + dp14, + dp15, + dp16, + dp17, + dp18, + dp19, + dp20, + dp21, + dp22, + dp23, + dp24, + dp25, +}; + +class Timer { + public: + Timer() { (void)0; }; + void reset() { (void)0; }; + void start() { (void)0; } + uint32_t read_ms() { return 1000; }; + + private: +}; + +static Callback game_update_callback; +static uint32_t game_update_timeout; + +class Ticker { + public: + Ticker() { (void)0; }; + void attach(Callback func, double delay_sec) { + game_update_callback = func; + game_update_timeout = delay_sec * 60; + } + void detach() { + game_update_callback = nullptr; + } + + private: +}; + +static Callback button_callback; + +class InterruptIn { + public: + InterruptIn(int reg) { + (void)reg; + }; + void fall(Callback func) { button_callback = func; }; + void mode(int v) { (void)v; }; + + private: +}; + +#endif /*__UI_mbed_H__*/ \ No newline at end of file diff --git a/firmware/application/external/doom/ui_doom.cpp b/firmware/application/external/doom/ui_doom.cpp new file mode 100644 index 000000000..61af21d84 --- /dev/null +++ b/firmware/application/external/doom/ui_doom.cpp @@ -0,0 +1,1136 @@ +/* + * ------------------------------------------------------------ + * | Made by RocketGod | + * | Find me at https://betaskynet.com | + * | Argh matey! | + * ------------------------------------------------------------ + */ + +#include "ui_doom.hpp" +#include "ui.hpp" +#include "SPI_TFT_ILI9341.h" +#include "mbed.h" + +#define SCREEN_WIDTH 240 +#define SCREEN_HEIGHT 320 +#define RENDER_HEIGHT 280 +#define HALF_WIDTH (SCREEN_WIDTH / 2) +#define HALF_HEIGHT (RENDER_HEIGHT / 2) +#define LEVEL_WIDTH_BASE 6 +#define LEVEL_WIDTH (1 << LEVEL_WIDTH_BASE) +#define LEVEL_HEIGHT 57 +#define LEVEL_SIZE (LEVEL_WIDTH / 2 * LEVEL_HEIGHT) +#define MAX_RENDER_DEPTH 12 +#define MIN_ENTITIES 15 +#define MAX_ENTITIES 25 +#define RES_DIVIDER 2 +#define DISTANCE_MULTIPLIER 20 +#define ROT_SPEED 0.25 +#define MOV_SPEED 1.0 +#define JOGGING_SPEED 1.2 +#define MAX_ENTITY_DISTANCE 200 +#define ITEM_COLLIDER_DIST 6 +#define PI 3.14159265358979323846 +#define GUN_WIDTH 30 +#define GUN_HEIGHT 40 +#define GUN_TARGET_POS 24 +#define GUN_SHOT_POS (GUN_TARGET_POS + 6) +#define ENEMY_SIZE 16 +#define ENEMY_SPEED 0.03 +#define ENEMY_MELEE_DIST 20 +#define ENEMY_MELEE_DAMAGE 2 +#define GUN_MAX_DAMAGE 15 + +#define ACTIVATION_RADIUS 120 +#define STATE_INACTIVE 6 + +#define COLOR_BACKGROUND Black +#define COLOR_WALL_LIGHT Green +#define COLOR_WALL_DARK Maroon +#define COLOR_FLOOR_DARK Black + +struct Coords { + double x, y; +}; + +struct Player { + Coords pos; + Coords dir; + Coords plane; + double velocity; + uint8_t health; + uint8_t keys; + uint8_t ammo; +}; + +struct Entity { + uint16_t uid; + Coords pos; + Coords death_pos; + uint8_t state; + uint8_t health; + uint8_t distance; + uint8_t timer; +}; + +// Stole this level map from Flipper Zero Doom, but he stole it from a guy who stole it and that guy probably stole it too. Argh! +static const uint8_t level[LEVEL_SIZE] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x02, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x90, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF2, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x4F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x20, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0x05, 0x00, 0x00, 0x90, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0xFF, + 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x08, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF2, 0x02, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0x40, 0x80, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x40, 0x00, 0x02, 0x00, 0x90, 0xFF, 0xFF, + 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xF0, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x20, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, + 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x40, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +namespace ui::external_app::doom { + +static Ticker game_timer; +static Player player; +static Entity entities[MAX_ENTITIES]; +static uint8_t num_entities = 0; +static uint16_t kills = 0; +static uint8_t scene = 0; +static bool up, down, left, right, fired; +static double jogging, view_height; +static bool needs_redraw = false; +static bool needs_gun_redraw = false; +static uint8_t gun_pos = GUN_TARGET_POS; +static bool gun_fired = false; +static int prev_gun_x = 0; +static int prev_gun_y = 0; + +Coords create_coords(double x, double y) { + Coords c; + c.x = x; + c.y = y; + return c; +} + +Player create_player(double x, double y) { + Player p; + p.pos = create_coords(x + 0.5, y + 0.5); + p.dir = create_coords(1, 0); + p.plane = create_coords(0, -0.66); + p.velocity = 0; + p.health = 100; + p.keys = 0; + p.ammo = 99; + return p; +} + +Entity create_entity(uint8_t type, uint8_t x, uint8_t y, uint8_t state, uint8_t health) { + Entity e; + e.uid = ((y << 6) | x) << 4 | type; + e.pos = create_coords(x + 0.5, y + 0.5); + e.death_pos = create_coords(x + 0.5, y + 0.5); + e.state = state; + e.health = health; + e.distance = 0; + e.timer = 0; + return e; +} + +uint8_t get_block_at(uint8_t x, uint8_t y) { + if (x >= LEVEL_WIDTH || y >= LEVEL_HEIGHT) return 0xF; + return level[((LEVEL_HEIGHT - 1 - y) * LEVEL_WIDTH + x) / 2] >> (!(x % 2) * 4) & 0b1111; +} + +Coords translate_into_view(Coords pos) { + double sprite_x = pos.x - player.pos.x; + double sprite_y = pos.y - player.pos.y; + double inv_det = 1.0 / (player.plane.x * player.dir.y - player.dir.x * player.plane.y); + double transform_x = inv_det * (player.dir.y * sprite_x - player.dir.x * sprite_y); + double transform_y = inv_det * (-player.plane.y * sprite_x + player.plane.x * sprite_y); + return create_coords(transform_x, transform_y); +} + +void spawn_entity(uint8_t type, uint8_t x, uint8_t y) { + if (num_entities >= MAX_ENTITIES) return; + entities[num_entities] = create_entity(type, x, y, STATE_INACTIVE, 50); + num_entities++; +} + +void spawn_random_entity(uint8_t type) { + if (num_entities >= MAX_ENTITIES) return; + std::srand(LPC_RTC->CTIME0); + uint8_t spawn_x, spawn_y; + do { + spawn_x = std::rand() % LEVEL_WIDTH; + spawn_y = std::rand() % LEVEL_HEIGHT; + } while (get_block_at(spawn_x, spawn_y) == 0xF || + (spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y)); + + entities[num_entities] = create_entity(type, spawn_x, spawn_y, STATE_INACTIVE, 50); + num_entities++; +} + +void remove_entity(uint8_t index) { + for (uint8_t i = index; i < num_entities - 1; i++) { + entities[i] = entities[i + 1]; + } + num_entities--; + + if (num_entities < MIN_ENTITIES) { + uint8_t attempts = 0; + bool spawned = false; + + while (!spawned && attempts < 50) { + std::srand(LPC_RTC->CTIME0 + attempts); + uint8_t spawn_x = std::rand() % LEVEL_WIDTH; + uint8_t spawn_y = std::rand() % LEVEL_HEIGHT; + + int16_t dx = (int16_t)spawn_x - (int16_t)player.pos.x; + int16_t dy = (int16_t)spawn_y - (int16_t)player.pos.y; + uint16_t dist_squared = dx * dx + dy * dy; + + if (dist_squared >= 64 && + get_block_at(spawn_x, spawn_y) != 0xF && + !(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y)) { + entities[num_entities] = create_entity(0x2, spawn_x, spawn_y, STATE_INACTIVE, 50); + entities[num_entities].pos.x = (double)spawn_x + 0.5; + entities[num_entities].pos.y = (double)spawn_y + 0.5; + entities[num_entities].death_pos.x = (double)spawn_x + 0.5; + entities[num_entities].death_pos.y = (double)spawn_y + 0.5; + + if (get_block_at((uint8_t)entities[num_entities].pos.x, (uint8_t)entities[num_entities].pos.y) != 0xF) { + num_entities++; + spawned = true; + } + } + attempts++; + } + } +} + +void initialize_level() { + for (uint8_t y = LEVEL_HEIGHT - 1; y > 0; y--) { + for (uint8_t x = 0; x < LEVEL_WIDTH; x++) { + uint8_t block = get_block_at(x, y); + if (block == 0x1) { + player = create_player(x, y); + break; + } + } + } + num_entities = 0; + + uint8_t initial_enemies = 0; + uint8_t attempts = 0; + + while (initial_enemies < 2 && num_entities < MAX_ENTITIES && attempts < 50) { + std::srand(LPC_RTC->CTIME0 + attempts); + int8_t offset_x = (std::rand() % 11) - 5; + int8_t offset_y = (std::rand() % 11) - 5; + + if (abs(offset_x) < 3 && abs(offset_y) < 3) { + attempts++; + continue; + } + + int16_t temp_x = (int16_t)player.pos.x + offset_x; + int16_t temp_y = (int16_t)player.pos.y + offset_y; + uint8_t spawn_x = (temp_x < 0) ? 0 : (temp_x >= LEVEL_WIDTH) ? LEVEL_WIDTH - 1 + : temp_x; + uint8_t spawn_y = (temp_y < 0) ? 0 : (temp_y >= LEVEL_HEIGHT) ? LEVEL_HEIGHT - 1 + : temp_y; + + if (get_block_at(spawn_x, spawn_y) != 0xF && + !(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y)) { + entities[num_entities] = create_entity(0x2, spawn_x, spawn_y, STATE_INACTIVE, 50); + entities[num_entities].pos.x = (double)spawn_x + 0.5; + entities[num_entities].pos.y = (double)spawn_y + 0.5; + entities[num_entities].death_pos.x = (double)spawn_x + 0.5; + entities[num_entities].death_pos.y = (double)spawn_y + 0.5; + + if (get_block_at((uint8_t)entities[num_entities].pos.x, (uint8_t)entities[num_entities].pos.y) == 0xF) { + attempts++; + continue; + } + + num_entities++; + initial_enemies++; + } + attempts++; + } + + attempts = 0; + while (num_entities < MIN_ENTITIES && attempts < 100) { + std::srand(LPC_RTC->CTIME0 + attempts + 100); + + uint8_t spawn_x = std::rand() % LEVEL_WIDTH; + uint8_t spawn_y = std::rand() % LEVEL_HEIGHT; + + int16_t dx = (int16_t)spawn_x - (int16_t)player.pos.x; + int16_t dy = (int16_t)spawn_y - (int16_t)player.pos.y; + uint16_t dist_squared = dx * dx + dy * dy; + + if (dist_squared >= 25 && + get_block_at(spawn_x, spawn_y) != 0xF && + !(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y)) { + entities[num_entities] = create_entity(0x2, spawn_x, spawn_y, STATE_INACTIVE, 50); + entities[num_entities].pos.x = (double)spawn_x + 0.5; + entities[num_entities].pos.y = (double)spawn_y + 0.5; + entities[num_entities].death_pos.x = (double)spawn_x + 0.5; + entities[num_entities].death_pos.y = (double)spawn_y + 0.5; + + if (get_block_at((uint8_t)entities[num_entities].pos.x, (uint8_t)entities[num_entities].pos.y) == 0xF) { + attempts++; + continue; + } + + num_entities++; + } + attempts++; + } +} + +void fire() { + if (player.ammo > 0) { + uint8_t closest_i = 255; + double min_dist = 1000.0; + for (uint8_t i = 0; i < num_entities; i++) { + if (entities[i].state == 5) continue; + double dx = entities[i].pos.x - player.pos.x; + double dy = entities[i].pos.y - player.pos.y; + double dist = sqrt(dx * dx + dy * dy) * DISTANCE_MULTIPLIER; + double dot = dx * player.dir.x + dy * player.dir.y; + double angle = acos(dot / (dist / DISTANCE_MULTIPLIER)); + if (dist < 100 && angle < 0.2 && dot > 0 && dist < min_dist) { + closest_i = i; + min_dist = dist; + } + } + if (closest_i != 255) { + uint8_t damage = fmin(GUN_MAX_DAMAGE, GUN_MAX_DAMAGE * (100 - min_dist) / 100); + if (damage > 0) { + entities[closest_i].health = fmax(0, entities[closest_i].health - damage); + entities[closest_i].state = 4; + entities[closest_i].timer = 4; + needs_redraw = true; + } + } + player.ammo--; + } +} + +void update_entities() { + uint8_t i = 0; + while (i < num_entities) { + entities[i].distance = sqrt(pow(player.pos.x - entities[i].pos.x, 2) + pow(player.pos.y - entities[i].pos.y, 2)) * DISTANCE_MULTIPLIER; + + if (entities[i].state != 5) { + if (entities[i].distance > ACTIVATION_RADIUS && entities[i].state != STATE_INACTIVE) { + entities[i].state = STATE_INACTIVE; + needs_redraw = true; + } else if (entities[i].distance <= ACTIVATION_RADIUS && entities[i].state == STATE_INACTIVE) { + entities[i].state = 0; + needs_redraw = true; + } + } + + if (entities[i].timer > 0) entities[i].timer--; + + if (entities[i].health == 0 && entities[i].state == 5 && entities[i].timer == 0) { + kills++; + remove_entity(i); + continue; + } + if (entities[i].health == 0 && entities[i].state != 5) { + entities[i].state = 5; + entities[i].timer = 6; + entities[i].death_pos = entities[i].pos; + needs_redraw = true; + } + if (entities[i].state == 4 && entities[i].timer == 0) { + entities[i].state = 0; + needs_redraw = true; + } + + if (entities[i].state != STATE_INACTIVE && entities[i].state != 4 && entities[i].state != 5) { + double dx = player.pos.x - entities[i].pos.x; + double dy = player.pos.y - entities[i].pos.y; + double dist = sqrt(dx * dx + dy * dy); + + if (dist <= (ENEMY_MELEE_DIST / DISTANCE_MULTIPLIER) && entities[i].timer == 0) { + entities[i].state = 3; + player.health = fmax(0, player.health - ENEMY_MELEE_DAMAGE); + entities[i].timer = 30; + needs_redraw = true; + } + + double move_dist = fmin(ENEMY_SPEED, dist - 0.2); + + bool moved = false; + + double new_x = entities[i].pos.x + (dx / dist) * move_dist; + double new_y = entities[i].pos.y + (dy / dist) * move_dist; + if (get_block_at((uint8_t)new_x, (uint8_t)entities[i].pos.y) != 0xF) { + entities[i].pos.x = new_x; + moved = true; + } + if (get_block_at((uint8_t)entities[i].pos.x, (uint8_t)new_y) != 0xF) { + entities[i].pos.y = new_y; + moved = true; + } + + if (!moved) { + bool wall_x_pos = get_block_at((uint8_t)entities[i].pos.x + 1, (uint8_t)entities[i].pos.y) == 0xF; + bool wall_x_neg = get_block_at((uint8_t)entities[i].pos.x - 1, (uint8_t)entities[i].pos.y) == 0xF; + bool wall_y_pos = get_block_at((uint8_t)entities[i].pos.x, (uint8_t)entities[i].pos.y + 1) == 0xF; + bool wall_y_neg = get_block_at((uint8_t)entities[i].pos.x, (uint8_t)entities[i].pos.y - 1) == 0xF; + + uint8_t dir_choice = (entities[i].uid + LPC_RTC->CTIME0) % 4; + + if (wall_x_pos || wall_x_neg) { + double try_y = entities[i].pos.y + (dir_choice < 2 ? 1 : -1) * move_dist; + if (get_block_at((uint8_t)entities[i].pos.x, (uint8_t)try_y) != 0xF) { + entities[i].pos.y = try_y; + moved = true; + } + } else if (wall_y_pos || wall_y_neg) { + double try_x = entities[i].pos.x + (dir_choice < 2 ? 1 : -1) * move_dist; + if (get_block_at((uint8_t)try_x, (uint8_t)entities[i].pos.y) != 0xF) { + entities[i].pos.x = try_x; + moved = true; + } + } + + if (!moved) { + double angle = (entities[i].uid * 17 + LPC_RTC->CTIME0) % 628 / 100.0; + double try_x = entities[i].pos.x + cos(angle) * move_dist; + double try_y = entities[i].pos.y + sin(angle) * move_dist; + + if (get_block_at((uint8_t)try_x, (uint8_t)entities[i].pos.y) != 0xF) { + entities[i].pos.x = try_x; + moved = true; + } + if (get_block_at((uint8_t)entities[i].pos.x, (uint8_t)try_y) != 0xF) { + entities[i].pos.y = try_y; + moved = true; + } + } + } + + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + if (get_block_at((uint8_t)(entities[i].pos.x + dx * 0.3), (uint8_t)(entities[i].pos.y + dy * 0.3)) == 0xF) { + entities[i].pos.x -= dx * 0.05; + entities[i].pos.y -= dy * 0.05; + } + } + } + + if (dist > (ENEMY_MELEE_DIST / DISTANCE_MULTIPLIER)) entities[i].state = 0; + } + i++; + } +} + +void update_game() { + bool state_changed = false; + bool gun_only_change = false; + + if (scene == 1) { + if (player.health > 0) { + if (up) { + player.velocity += (MOV_SPEED - player.velocity) * 0.4; + jogging = fabs(player.velocity) * 5; + up = false; + state_changed = true; + } else if (down) { + player.velocity += (-MOV_SPEED - player.velocity) * 0.4; + jogging = fabs(player.velocity) * 5; + down = false; + state_changed = true; + } else { + double old_velocity = player.velocity; + player.velocity *= 0.5; + jogging = fabs(player.velocity) * 5; + if (fabs(player.velocity) < 0.001) player.velocity = 0; + if (old_velocity != player.velocity) state_changed = true; + } + + if (right) { + double old_dir_x = player.dir.x; + player.dir.x = player.dir.x * cos(-ROT_SPEED) - player.dir.y * sin(-ROT_SPEED); + player.dir.y = old_dir_x * sin(-ROT_SPEED) + player.dir.y * cos(-ROT_SPEED); + double old_plane_x = player.plane.x; + player.plane.x = player.plane.x * cos(-ROT_SPEED) - player.plane.y * sin(-ROT_SPEED); + player.plane.y = old_plane_x * sin(-ROT_SPEED) + player.plane.y * cos(-ROT_SPEED); + right = false; + state_changed = true; + } else if (left) { + double old_dir_x = player.dir.x; + player.dir.x = player.dir.x * cos(ROT_SPEED) - player.dir.y * sin(ROT_SPEED); + player.dir.y = old_dir_x * sin(ROT_SPEED) + player.dir.y * cos(ROT_SPEED); + double old_plane_x = player.plane.x; + player.plane.x = player.plane.x * cos(ROT_SPEED) - player.plane.y * sin(ROT_SPEED); + player.plane.y = old_plane_x * sin(ROT_SPEED) + player.plane.y * cos(ROT_SPEED); + left = false; + state_changed = true; + } + + if (player.velocity != 0) { + view_height = fabs(sin(LPC_RTC->CTIME0 * JOGGING_SPEED)) * 6 * jogging; + } else { + view_height = 0; + } + + if (fabs(player.velocity) > 0.003) { + double new_x = player.pos.x + player.dir.x * player.velocity; + double new_y = player.pos.y + player.dir.y * player.velocity; + bool can_move_x = get_block_at((uint8_t)new_x, (uint8_t)player.pos.y) != 0xF; + bool can_move_y = get_block_at((uint8_t)player.pos.x, (uint8_t)new_y) != 0xF; + for (uint8_t i = 0; i < num_entities; i++) { + if (entities[i].state != 5 && entities[i].state != STATE_INACTIVE) { + double dx = new_x - entities[i].pos.x; + double dy = player.pos.y - entities[i].pos.y; + if (sqrt(dx * dx + dy * dy) < 0.5) { + can_move_x = false; + } + dx = player.pos.x - entities[i].pos.x; + dy = new_y - entities[i].pos.y; + if (sqrt(dx * dx + dy * dy) < 0.5) { + can_move_y = false; + } + } + } + if (can_move_x) { + player.pos.x = new_x; + state_changed = true; + } + if (can_move_y) { + player.pos.y = new_y; + state_changed = true; + } + } else if (player.velocity != 0) { + player.velocity = 0; + state_changed = true; + } + + if (gun_pos > GUN_TARGET_POS) { + gun_pos -= 1; + gun_only_change = true; + } else if (gun_pos < GUN_TARGET_POS) { + gun_pos += 2; + gun_only_change = true; + } else if (fired) { + gun_pos = GUN_SHOT_POS; + fire(); + needs_gun_redraw = true; + gun_only_change = true; + fired = false; + } + } else { + if (view_height > -10) { + view_height--; + state_changed = true; + } + if (gun_pos > 1) { + gun_pos -= 2; + gun_only_change = true; + } + } + update_entities(); + } + + if (state_changed && !gun_only_change) + needs_redraw = true; + else if (gun_only_change) + needs_gun_redraw = true; +} + +static void game_timer_check() { + if (scene == 1) update_game(); +} + +void render_gun(Painter& painter, uint8_t gun_pos, double jogging) { + int x = HALF_WIDTH - GUN_WIDTH / 2 + sin(LPC_RTC->CTIME0 * JOGGING_SPEED) * 10 * jogging; + int y = RENDER_HEIGHT - gun_pos - 29 + fabs(cos(LPC_RTC->CTIME0 * JOGGING_SPEED)) * 8 * jogging; + + prev_gun_x = x; + prev_gun_y = y; + + int recoil = gun_pos > GUN_TARGET_POS ? (gun_pos - GUN_TARGET_POS) / 2 : 0; + + // Gun body - shotgun style with more detail + painter.fill_rectangle({x + 8, y + 8, 18, 12}, pp_colors[Green]); + painter.fill_rectangle({x + 6, y + 10, 2, 8}, pp_colors[Black]); + painter.fill_rectangle({x + 26, y + 10, 2, 8}, pp_colors[Black]); + + // Barrel + painter.fill_rectangle({x + 12, y - 4, 8, 12}, pp_colors[Green]); + painter.fill_rectangle({x + 14, y - 6, 4, 2}, pp_colors[Green]); + + // Barrel shading + painter.fill_rectangle({x + 12, y - 4, 2, 12}, pp_colors[White]); + painter.fill_rectangle({x + 18, y - 4, 2, 12}, pp_colors[Black]); + + // Handle + painter.fill_rectangle({x + 12, y + 20, 8, 20}, pp_colors[Maroon]); + painter.fill_rectangle({x + 10, y + 20, 2, 18}, pp_colors[White]); + painter.fill_rectangle({x + 20, y + 20, 2, 18}, pp_colors[Black]); + + // Trigger guard + painter.fill_rectangle({x + 10, y + 20, 12, 2}, pp_colors[Green]); + painter.fill_rectangle({x + 10, y + 20, 2, 6}, pp_colors[Green]); + painter.fill_rectangle({x + 20, y + 20, 2, 6}, pp_colors[Green]); + painter.fill_rectangle({x + 10, y + 26, 12, 2}, pp_colors[Green]); + + // Trigger + painter.fill_rectangle({x + 14, y + 22 + recoil / 2, 4, 4}, pp_colors[Black]); + + // Pump action + painter.fill_rectangle({x + 8, y + 4, 16, 4}, pp_colors[Maroon]); + painter.fill_rectangle({x + 7, y + 4, 1, 4}, pp_colors[White]); + painter.fill_rectangle({x + 24, y + 4, 1, 4}, pp_colors[Black]); + + // Action slide rails + painter.fill_rectangle({x + 10, y + 8, 1, 8 + recoil}, pp_colors[White]); + painter.fill_rectangle({x + 21, y + 8, 1, 8 + recoil}, pp_colors[Black]); + + // Ammo chamber + painter.fill_rectangle({x + 24, y + 12, 4, 4}, pp_colors[Black]); + + // Muzzle flash + if (gun_pos > GUN_TARGET_POS) { + int flash_size = 10 + (gun_pos - GUN_TARGET_POS) * 2; + int flash_center_x = x + 16; + int flash_center_y = y - 6; + + // Core of flash + painter.fill_rectangle({flash_center_x - flash_size / 4, flash_center_y - flash_size / 2, + flash_size / 2, flash_size}, + pp_colors[White]); + + // Outer yellow glow + painter.fill_rectangle({flash_center_x - flash_size / 2, flash_center_y - flash_size / 4, + flash_size, flash_size / 2}, + pp_colors[Yellow]); + + // Left spike + painter.fill_rectangle({flash_center_x - flash_size, flash_center_y - 2, + flash_size / 2, 4}, + pp_colors[Yellow]); + + // Right spike + painter.fill_rectangle({flash_center_x + flash_size / 2, flash_center_y - 2, + flash_size / 2, 4}, + pp_colors[Yellow]); + + // Top spike + painter.fill_rectangle({flash_center_x - 2, flash_center_y - flash_size, + 4, flash_size / 2}, + pp_colors[Yellow]); + + // Small random sparks + if (LPC_RTC->CTIME0 % 2) { + painter.fill_rectangle({flash_center_x - flash_size / 2 - 2, flash_center_y - flash_size / 2 - 2, + 2, 2}, + pp_colors[Yellow]); + painter.fill_rectangle({flash_center_x + flash_size / 3, flash_center_y - flash_size / 3, + 2, 2}, + pp_colors[Yellow]); + } else { + painter.fill_rectangle({flash_center_x + flash_size / 3, flash_center_y - flash_size / 2, + 2, 2}, + pp_colors[Yellow]); + painter.fill_rectangle({flash_center_x - flash_size / 4, flash_center_y + flash_size / 4, + 2, 2}, + pp_colors[Yellow]); + } + + // Smoke effect + if (gun_pos > GUN_TARGET_POS + 2) { + int smoke_y = flash_center_y - flash_size - 4; + painter.fill_rectangle({flash_center_x - 6, smoke_y, 12, 4}, pp_colors[White]); + painter.fill_rectangle({flash_center_x - 8, smoke_y - 4, 16, 4}, pp_colors[White]); + painter.fill_rectangle({flash_center_x - 6, smoke_y - 8, 12, 4}, pp_colors[White]); + } + } + + // Shell ejection + if (gun_pos > GUN_TARGET_POS && gun_pos < GUN_TARGET_POS + 6) { + int shell_x = x + 24 + (gun_pos - GUN_TARGET_POS) * 2; + int shell_y = y + 10 - (gun_pos - GUN_TARGET_POS); + + painter.fill_rectangle({shell_x, shell_y, 4, 2}, pp_colors[Yellow]); + painter.fill_rectangle({shell_x + 1, shell_y - 1, 2, 4}, pp_colors[Yellow]); + } +} + +void render_entities(Painter& painter) { + for (uint8_t i = 0; i < num_entities; i++) { + if (entities[i].state == STATE_INACTIVE) continue; + Coords transform = translate_into_view(entities[i].state == 5 ? entities[i].death_pos : entities[i].pos); + if (transform.y <= 0.1 || transform.y > MAX_RENDER_DEPTH) continue; + + int16_t sprite_x = HALF_WIDTH * (1.0 + transform.x / transform.y); + int16_t sprite_y = HALF_HEIGHT + view_height / transform.y; + uint8_t base_size = ENEMY_SIZE / fmax(0.1, transform.y); + + if (sprite_x < -base_size || sprite_x > SCREEN_WIDTH + base_size || sprite_y < 0 || sprite_y > RENDER_HEIGHT) continue; + + uint8_t size = base_size * 2; + int16_t half_size = size / 2; + int16_t x_start = sprite_x - half_size; + int16_t y_start = sprite_y - half_size; + + Color body_color = Color(180, 40, 20); + Color spine_color = Color(120, 20, 10); + Color eye_color = Color(255, 165, 0); + Color pupil_color = Color(255, 0, 0); + Color claw_color = Color(60, 60, 60); + Color teeth_color = Color(220, 220, 220); + Color blood_color = Color(150, 0, 0); + Color horn_color = Color(90, 30, 10); + + if (entities[i].state == 5) { + // Death animation - gibbed/flattened demon + uint8_t death_phase = entities[i].timer / 5; + if (death_phase > 3) death_phase = 3; + + if (death_phase == 0) { + // Initial death - falling apart + painter.fill_rectangle({x_start + size / 5, sprite_y, size * 3 / 5, size / 3}, body_color); + painter.fill_rectangle({x_start + size * 2 / 5, sprite_y - size / 6, size / 5, size / 4}, horn_color); + painter.fill_rectangle({x_start + size / 3, sprite_y + size / 8, size / 8, size / 6}, blood_color); + painter.fill_rectangle({x_start + size / 2, sprite_y + size / 6, size / 6, size / 8}, blood_color); + painter.fill_rectangle({x_start + size / 4, sprite_y + size / 4, size / 5, size / 10}, claw_color); + painter.fill_rectangle({x_start + size * 3 / 5, sprite_y + size / 5, size / 5, size / 10}, claw_color); + } else if (death_phase == 1) { + // More splattered + painter.fill_rectangle({x_start + size / 6, sprite_y, size * 2 / 3, size / 4}, body_color); + painter.fill_rectangle({x_start + size / 4, sprite_y + size / 8, size / 6, size / 6}, blood_color); + painter.fill_rectangle({x_start + size / 2, sprite_y + size / 10, size / 6, size / 8}, blood_color); + painter.fill_rectangle({x_start + size * 2 / 3, sprite_y + size / 12, size / 8, size / 8}, blood_color); + } else { + // Final gore puddle + painter.fill_rectangle({x_start + size / 8, sprite_y, size * 3 / 4, size / 5}, body_color); + painter.fill_rectangle({x_start + size / 6, sprite_y - size / 20, size * 2 / 3, size / 10}, blood_color); + painter.fill_rectangle({x_start + size / 4, sprite_y + size / 10, size / 5, size / 12}, blood_color); + painter.fill_rectangle({x_start + size / 2, sprite_y + size / 12, size / 4, size / 10}, blood_color); + } + } else { + // Body + painter.fill_rectangle({x_start + size / 3, y_start + size / 4, size / 3, size * 3 / 4}, body_color); + + // Spine/backbone + painter.fill_rectangle({x_start + size * 5 / 12, y_start + size / 6, size / 6, size / 2}, spine_color); + + // Head + painter.fill_rectangle({x_start + size * 3 / 8, y_start, size * 5 / 16, size / 3}, body_color); + + // Horns + painter.fill_rectangle({x_start + size / 4, y_start - size / 6, size / 8, size / 5}, horn_color); + painter.fill_rectangle({x_start + size * 5 / 8, y_start - size / 6, size / 8, size / 5}, horn_color); + + // Eyes + if (entities[i].state != 4) { + uint8_t eye_anim = (LPC_RTC->CTIME0 % 8 == 0) ? 1 : 0; + + painter.fill_rectangle({x_start + size * 5 / 12, y_start + size / 12, size / 6, size / 6 - eye_anim}, eye_color); + painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 8, size / 20, size / 20}, pupil_color); + + painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 12, size / 6, size / 6 - eye_anim}, eye_color); + painter.fill_rectangle({x_start + size * 17 / 32, y_start + size / 8, size / 20, size / 20}, pupil_color); + } else { + // Damaged eye + painter.fill_rectangle({x_start + size * 5 / 12, y_start + size / 12, size / 6, size / 6}, eye_color); + painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 8, size / 20, size / 20}, pupil_color); + + // Blood from damaged eye + painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 6, size / 10, size / 8}, blood_color); + painter.fill_rectangle({x_start + size / 2, y_start + size / 3, size / 12, size / 12}, blood_color); + } + + // Teeth/mouth + painter.fill_rectangle({x_start + size * 7 / 16, y_start + size * 5 / 16, size / 20, size / 16}, teeth_color); + painter.fill_rectangle({x_start + size / 2, y_start + size * 5 / 16, size / 20, size / 16}, teeth_color); + painter.fill_rectangle({x_start + size * 9 / 16, y_start + size * 5 / 16, size / 20, size / 16}, teeth_color); + + // Attacking state - claws extended + if (entities[i].state == 3) { + uint8_t claw_anim = (LPC_RTC->CTIME0 % 4) / 2; + + if (claw_anim == 0) { + // Claws forward + painter.fill_rectangle({x_start - size / 8, y_start + size * 5 / 12, size / 3, size / 6}, claw_color); + painter.fill_rectangle({x_start - size / 12, y_start + size / 2, size / 5, size / 4}, claw_color); + painter.fill_rectangle({x_start + size * 3 / 4, y_start + size * 5 / 12, size / 3, size / 6}, claw_color); + painter.fill_rectangle({x_start + size * 3 / 4, y_start + size / 2, size / 5, size / 4}, claw_color); + } else { + // Claws slashing + painter.fill_rectangle({x_start, y_start + size * 4 / 12, size / 4, size / 5}, claw_color); + painter.fill_rectangle({x_start + size / 12, y_start + size * 6 / 12, size / 5, size / 3}, claw_color); + painter.fill_rectangle({x_start + size * 3 / 4, y_start + size * 4 / 12, size / 4, size / 5}, claw_color); + painter.fill_rectangle({x_start + size * 2 / 3, y_start + size * 6 / 12, size / 5, size / 3}, claw_color); + } + } else { + // Normal claws + painter.fill_rectangle({x_start + size / 6, y_start + size / 2, size / 5, size / 5}, claw_color); + painter.fill_rectangle({x_start + size * 3 / 5, y_start + size / 2, size / 5, size / 5}, claw_color); + } + + // Legs animation + uint8_t leg_anim = (LPC_RTC->CTIME0 % 4) / 2; + if (entities[i].state == 0 && leg_anim == 0) { + // Walking animation frame 1 + painter.fill_rectangle({x_start + size / 4, y_start + size * 5 / 6, size / 6, size / 5}, body_color); + painter.fill_rectangle({x_start + size / 3, y_start + size * 11 / 12, size / 10, size / 10}, spine_color); + painter.fill_rectangle({x_start + size * 7 / 12, y_start + size * 3 / 4, size / 6, size / 4}, body_color); + painter.fill_rectangle({x_start + size * 2 / 3, y_start + size * 5 / 6, size / 10, size / 5}, spine_color); + } else { + // Walking animation frame 2 + painter.fill_rectangle({x_start + size / 4, y_start + size * 3 / 4, size / 6, size / 4}, body_color); + painter.fill_rectangle({x_start + size / 3, y_start + size * 5 / 6, size / 10, size / 5}, spine_color); + painter.fill_rectangle({x_start + size * 7 / 12, y_start + size * 5 / 6, size / 6, size / 5}, body_color); + painter.fill_rectangle({x_start + size * 2 / 3, y_start + size * 11 / 12, size / 10, size / 10}, spine_color); + } + + // Injury effects for damaged state + if (entities[i].state == 4) { + painter.fill_rectangle({x_start + size / 2, y_start + size / 3, size / 10, size / 8}, blood_color); + painter.fill_rectangle({x_start + size * 5 / 12, y_start + size * 7 / 12, size / 8, size / 8}, blood_color); + painter.fill_rectangle({x_start + size * 3 / 5, y_start + size / 2, size / 10, size / 10}, blood_color); + } + } + } +} + +void render_map(Painter& painter, bool full_clear, int16_t x_start = 0, int16_t x_end = SCREEN_WIDTH) { + if (full_clear) { + painter.fill_rectangle({0, 0, SCREEN_WIDTH, RENDER_HEIGHT / 2}, Color(64, 64, 128)); + painter.fill_rectangle({0, RENDER_HEIGHT / 2, SCREEN_WIDTH, RENDER_HEIGHT / 2}, Color(32, 32, 32)); + } + + for (uint8_t x = x_start; x < x_end; x += RES_DIVIDER) { + double camera_x = 2 * (double)x / SCREEN_WIDTH - 1; + double ray_x = player.dir.x + player.plane.x * camera_x; + double ray_y = player.dir.y + player.plane.y * camera_x; + + if (fabs(ray_x) < 0.00001) ray_x = 0.00001; + if (fabs(ray_y) < 0.00001) ray_y = 0.00001; + + uint8_t map_x = (uint8_t)player.pos.x; + uint8_t map_y = (uint8_t)player.pos.y; + double delta_x = fabs(1 / ray_x); + double delta_y = fabs(1 / ray_y); + + int8_t step_x, step_y; + double side_x, side_y; + + if (ray_x < 0) { + step_x = -1; + side_x = (player.pos.x - map_x) * delta_x; + } else { + step_x = 1; + side_x = (map_x + 1.0 - player.pos.x) * delta_x; + } + if (ray_y < 0) { + step_y = -1; + side_y = (player.pos.y - map_y) * delta_y; + } else { + step_y = 1; + side_y = (map_y + 1.0 - player.pos.y) * delta_y; + } + + uint8_t depth = 0; + bool hit = false; + bool side = false; + double perpWallDist = 0.0; + + bool close_wall_handled = false; + uint8_t current_block = get_block_at(map_x, map_y); + if (current_block == 0xF) { + hit = true; + perpWallDist = 0.1; + close_wall_handled = true; + } else { + uint8_t check_blocks[4][3] = { + {(uint8_t)(map_x + 1), map_y, 0}, + {(uint8_t)(map_x - 1), map_y, 0}, + {map_x, (uint8_t)(map_y + 1), 1}, + {map_x, (uint8_t)(map_y - 1), 1}}; + + for (int i = 0; i < 4; i++) { + if (get_block_at(check_blocks[i][0], check_blocks[i][1]) == 0xF) { + double dist_to_wall; + if (check_blocks[i][2] == 0) { + dist_to_wall = (check_blocks[i][0] > map_x) ? check_blocks[i][0] - player.pos.x : player.pos.x - check_blocks[i][0]; + } else { + dist_to_wall = (check_blocks[i][1] > map_y) ? check_blocks[i][1] - player.pos.y : player.pos.y - check_blocks[i][1]; + } + + if (dist_to_wall < 0.2) { + double dot_product; + if (check_blocks[i][2] == 0) { + double wall_normal_x = (check_blocks[i][0] > map_x) ? -1.0 : 1.0; + dot_product = ray_x * wall_normal_x; + } else { + double wall_normal_y = (check_blocks[i][1] > map_y) ? -1.0 : 1.0; + dot_product = ray_y * wall_normal_y; + } + + if (dot_product < 0) { + hit = true; + side = (check_blocks[i][2] == 1); + perpWallDist = fmax(0.1, dist_to_wall); + close_wall_handled = true; + break; + } + } + } + } + } + + if (!close_wall_handled) { + while (!hit && depth < MAX_RENDER_DEPTH) { + if (side_x < side_y) { + side_x += delta_x; + map_x += step_x; + side = false; + } else { + side_y += delta_y; + map_y += step_y; + side = true; + } + + uint8_t block = get_block_at(map_x, map_y); + if (block == 0xF) { + hit = true; + if (side == false) { + perpWallDist = (map_x - player.pos.x + (1 - step_x) / 2) / ray_x; + } else { + perpWallDist = (map_y - player.pos.y + (1 - step_y) / 2) / ray_y; + } + } + depth++; + } + } + + if (perpWallDist <= 0) perpWallDist = 0.1; + + int start_y = 0; + int end_y = RENDER_HEIGHT; + + if (hit) { + uint8_t line_height = fmin(255, RENDER_HEIGHT / perpWallDist); + + start_y = view_height / perpWallDist - line_height / 2 + RENDER_HEIGHT / 2; + end_y = view_height / perpWallDist + line_height / 2 + RENDER_HEIGHT / 2; + + if (start_y < 0) start_y = 0; + if (end_y > RENDER_HEIGHT) end_y = RENDER_HEIGHT; + + uint8_t brightness = fmax(64, 255 - (perpWallDist * 20)); + uint8_t noise = ((map_x * 17 + map_y * 23) & 0x0F); + + Color wall_color; + if (!side) { + wall_color = Color(brightness / 4, brightness - noise, brightness / 4); + } else { + wall_color = Color(brightness / 5, (brightness - noise) * 0.8, brightness / 5); + } + + painter.fill_rectangle({x, start_y, RES_DIVIDER, end_y - start_y}, wall_color); + } + + if (!full_clear && hit) { + if (start_y > 0) { + painter.fill_rectangle({x, 0, RES_DIVIDER, start_y}, Color(64, 64, 128)); + } + if (end_y < RENDER_HEIGHT) { + painter.fill_rectangle({x, end_y, RES_DIVIDER, RENDER_HEIGHT - end_y}, Color(32, 32, 32)); + } + } + } +} + +DoomView::DoomView(NavigationView& nav) + : nav_{nav} { + add_children({&dummy}); + game_timer.attach(&game_timer_check, 1.0 / 60.0); +} + +void DoomView::on_show() { + dummy.focus(); +} + +void DoomView::paint(Painter& painter) { + if (!initialized) { + initialized = true; + std::srand(LPC_RTC->CTIME0); + scene = 0; + up = down = left = right = fired = false; + jogging = view_height = 0; + gun_pos = GUN_TARGET_POS; + gun_fired = false; + num_entities = 0; + kills = 0; + prev_gun_x = 0; + prev_gun_y = 0; + painter.fill_rectangle({0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}, pp_colors[COLOR_BACKGROUND]); + needs_redraw = true; + needs_gun_redraw = false; + } + + if (scene == 0) { + auto style_yellow = *ui::Theme::getInstance()->fg_yellow; + painter.draw_string({50, 40}, style_yellow, "* * * DOOM * * *"); + auto style_green = *ui::Theme::getInstance()->fg_green; + painter.draw_string({15, 240}, style_green, "** PRESS SELECT TO START **"); + } else if (scene == 1) { + if (needs_redraw || (needs_gun_redraw && jogging > 0)) { + bool full_clear = (player.velocity == 0 || !prev_velocity_moving); + render_map(painter, full_clear); + render_entities(painter); + render_gun(painter, gun_pos, jogging); + painter.fill_rectangle({0, RENDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - RENDER_HEIGHT}, pp_colors[COLOR_BACKGROUND]); + auto style_yellow = *ui::Theme::getInstance()->fg_yellow; + auto style_red = *ui::Theme::getInstance()->fg_red; + auto style_blue = *ui::Theme::getInstance()->fg_blue; + painter.draw_string({5, RENDER_HEIGHT + 5}, style_yellow, "Health: " + std::to_string(player.health)); + painter.draw_string({100, RENDER_HEIGHT + 5}, style_red, "Kills: " + std::to_string(kills)); + painter.draw_string({170, RENDER_HEIGHT + 5}, style_blue, "Ammo: " + std::to_string(player.ammo)); + prev_velocity_moving = (player.velocity != 0); + needs_redraw = false; + needs_gun_redraw = false; + } else if (needs_gun_redraw) { + int current_x = HALF_WIDTH - GUN_WIDTH / 2 + sin(LPC_RTC->CTIME0 * JOGGING_SPEED) * 10 * jogging; + int current_y = RENDER_HEIGHT - gun_pos - 29 + fabs(cos(LPC_RTC->CTIME0 * JOGGING_SPEED)) * 8 * jogging; + int flash_height = (gun_pos > GUN_TARGET_POS) ? (gun_pos - GUN_TARGET_POS) * 2 + 10 : 0; + int flash_width = (gun_pos > GUN_TARGET_POS) ? GUN_WIDTH + (gun_pos - GUN_TARGET_POS) * 2 : GUN_WIDTH; + int min_x = fmin(current_x, prev_gun_x) - 10; + int min_y = fmin(current_y, prev_gun_y) - flash_height - 10; + int max_x = fmax(current_x, prev_gun_x) + flash_width + 10; + int max_y = fmax(current_y, prev_gun_y) + GUN_HEIGHT + 10; + min_x = fmax(0, min_x); + min_y = fmax(0, min_y); + max_x = fmin(SCREEN_WIDTH, max_x); + max_y = fmin(RENDER_HEIGHT, max_y); + render_map(painter, false, min_x, max_x); + render_entities(painter); + render_gun(painter, gun_pos, jogging); + painter.fill_rectangle({0, RENDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - RENDER_HEIGHT}, pp_colors[COLOR_BACKGROUND]); + auto style_yellow = *ui::Theme::getInstance()->fg_yellow; + auto style_red = *ui::Theme::getInstance()->fg_red; + auto style_blue = *ui::Theme::getInstance()->fg_blue; + painter.draw_string({5, RENDER_HEIGHT + 5}, style_yellow, "Health: " + std::to_string(player.health)); + painter.draw_string({100, RENDER_HEIGHT + 5}, style_red, "Kills: " + std::to_string(kills)); + painter.draw_string({170, RENDER_HEIGHT + 5}, style_blue, "Ammo: " + std::to_string(player.ammo)); + needs_gun_redraw = false; + } + } +} + +void DoomView::frame_sync() { + if (scene == 1) { + update_game(); + if (needs_redraw || needs_gun_redraw) set_dirty(); + } +} + +bool DoomView::on_key(const KeyEvent key) { + if (key == KeyEvent::Select && scene == 0) { + scene = 1; + initialize_level(); + needs_redraw = true; + set_dirty(); + return true; + } + if (scene == 1) { + if (player.health > 0) { + if (key == KeyEvent::Up) { + up = true; + needs_redraw = true; + set_dirty(); + return true; + } + if (key == KeyEvent::Down) { + down = true; + needs_redraw = true; + set_dirty(); + return true; + } + if (key == KeyEvent::Left) { + left = true; + needs_redraw = true; + set_dirty(); + return true; + } + if (key == KeyEvent::Right) { + right = true; + needs_redraw = true; + set_dirty(); + return true; + } + if (key == KeyEvent::Select) { + fired = true; + set_dirty(); + return true; + } + } else if (key == KeyEvent::Select) { + scene = 0; + initialized = false; + needs_redraw = true; + set_dirty(); + return true; + } + } + return false; +} + +} // namespace ui::external_app::doom \ No newline at end of file diff --git a/firmware/application/external/doom/ui_doom.hpp b/firmware/application/external/doom/ui_doom.hpp new file mode 100644 index 000000000..0515073cf --- /dev/null +++ b/firmware/application/external/doom/ui_doom.hpp @@ -0,0 +1,42 @@ +/* + * ------------------------------------------------------------ + * | Made by RocketGod | + * | Find me at https://betaskynet.com | + * | Argh matey! | + * ------------------------------------------------------------ + */ + +#ifndef __UI_DOOM_H__ +#define __UI_DOOM_H__ + +#include "ui_widget.hpp" +#include "ui_navigation.hpp" +#include "message.hpp" + +namespace ui::external_app::doom { + +class DoomView : public View { + public: + DoomView(NavigationView& nav); + void on_show() override; + std::string title() const override { return "Doom"; } + void focus() override { dummy.focus(); } + void paint(Painter& painter) override; + void frame_sync(); + bool on_key(const KeyEvent key) override; + + private: + NavigationView& nav_; + Button dummy{{240, 0, 0, 0}, ""}; + bool initialized{false}; + bool prev_velocity_moving{false}; + MessageHandlerRegistration message_handler_frame_sync{ + Message::ID::DisplayFrameSync, + [this](const Message* const) { + this->frame_sync(); + }}; +}; + +} // namespace ui::external_app::doom + +#endif /*__UI_DOOM_H__*/ \ No newline at end of file diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 6e3f84c69..d45551afc 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -8,6 +8,9 @@ set(EXTCPPSRC external/breakout/main.cpp external/breakout/ui_breakout.cpp + #doom + external/doom/main.cpp + external/doom/ui_doom.cpp #snake external/snake/main.cpp external/snake/ui_snake.cpp @@ -205,6 +208,7 @@ set(EXTAPPLIST keyfob tetris breakout + doom snake extsensors foxhunt_rx diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index 71ba55f6d..787e0b979 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -38,6 +38,8 @@ MEMORY ram_external_app_keyfob(rwx) : org = 0xADBD0000, len = 32k ram_external_app_tetris(rwx) : org = 0xADBE0000, len = 32k ram_external_app_extsensors(rwx) : org = 0xADBF0000, len = 32k + ram_external_app_breakout(rwx) : org = 0xADE00000, len = 32k + ram_external_app_doom(rwx) : org = 0xADE20000, len = 32k ram_external_app_foxhunt_rx(rwx) : org = 0xADC00000, len = 32k ram_external_app_audio_test(rwx) : org = 0xADC10000, len = 32k ram_external_app_wardrivemap(rwx) : org = 0xADC20000, len = 32k @@ -168,6 +170,12 @@ SECTIONS *(*ui*external_app*snake*); } > ram_external_app_snake + .external_app_doom : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_doom.application_information)); + *(*ui*external_app*doom*); + } > ram_external_app_doom + .external_app_extsensors : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_extsensors.application_information));