Browse Source

Add doom from https://github.com/xMasterX/all-the-plugins

git-subtree-dir: doom
git-subtree-mainline: 129639ded3bc5c35a9cf636a7f07711f36bf910c
git-subtree-split: 4f5bbeb1ea01485b1cd753f89095b092c3dc3e17
Willy-JL 2 years ago
parent
commit
da9eb804fd

+ 4 - 0
doom/.gitignore

@@ -0,0 +1,4 @@
+dist/*
+.vscode
+.clang-format
+.editorconfig

+ 1 - 0
doom/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/xMasterX/all-the-plugins dev base_pack/doom

+ 10 - 0
doom/README.md

@@ -0,0 +1,10 @@
+# Doom Flipper Zero edition
+## Will it run Doom?
+As tradition goes, Doom is being ported to almost every possible embedded electronic device. Therefore I did an attempt to come up with something close to Doom and still compatible on the Flipper Zero's hardware. This is not the actual Doom game but a port made from yet another Doom port to the Arduino Nano - https://github.com/daveruiz/doom-nano/. This port is basically a raycasting engine, using Doom sprites.
+This version is very basic and might be improved over time.
+
+## Credits
+@xMasterX - Porting to latest firmware using new plugins system, fixing many issues, adding sound
+@Svaarich - New logo screen and cool icon
+@hedger - uFBT fixes and some bugfixes
+@p4nic4ttack - First raw implementation based on doom-nano

+ 18 - 0
doom/application.fam

@@ -0,0 +1,18 @@
+App(
+    appid="doom",
+    name="DOOM",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="doom_app",
+    requires=[
+        "gui",
+        "music_player",
+    ],
+    stack_size=4 * 1024,
+    order=75,
+    fap_icon="doom_10px.png",
+    fap_category="Games",
+    fap_icon_assets="assets",
+    fap_author="@xMasterX & @Svarich & @hedger (original code by @p4nic4ttack)",
+    fap_version="1.1",
+    fap_description="Will it run Doom?",
+)

+ 331 - 0
doom/assets.c

@@ -0,0 +1,331 @@
+#include "assets.h"
+
+const uint8_t space[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const uint8_t zero[] = {0x00, 0x60, 0x90, 0x90, 0x90, 0x60};
+const uint8_t one[] = {0x00, 0x20, 0x20, 0x20, 0x20, 0x70};
+const uint8_t two[] = {0x00, 0x60, 0x90, 0x20, 0x40, 0xf0};
+const uint8_t three[] = {0x00, 0x60, 0x90, 0x20, 0x90, 0x60};
+const uint8_t four[] = {0x00, 0x90, 0x90, 0xf0, 0x10, 0x10};
+const uint8_t five[] = {0x00, 0xf0, 0x80, 0xe0, 0x10, 0xe0};
+const uint8_t six[] = {0x00, 0x60, 0x80, 0xe0, 0x90, 0x60};
+const uint8_t seven[] = {0x00, 0xf0, 0x10, 0x10, 0x10, 0x10};
+const uint8_t eight[] = {0x00, 0x60, 0x90, 0x60, 0x90, 0x60};
+const uint8_t nine[] = {0x00, 0x60, 0x90, 0x70, 0x10, 0x60};
+const uint8_t A[] = {0x00, 0x60, 0x90, 0xf0, 0x90, 0x90};
+const uint8_t B[] = {0x00, 0xe0, 0x90, 0xe0, 0x90, 0xe0};
+const uint8_t C[] = {0x00, 0x60, 0x90, 0x80, 0x90, 0x60};
+const uint8_t D[] = {0x00, 0xe0, 0x90, 0x90, 0x90, 0xe0};
+const uint8_t E[] = {0x00, 0xf0, 0x80, 0xe0, 0x80, 0xf0};
+const uint8_t F[] = {0x00, 0xf0, 0x80, 0xe0, 0x80, 0x80};
+const uint8_t G[] = {0x00, 0x60, 0x80, 0x80, 0x90, 0x60};
+const uint8_t H[] = {0x00, 0x90, 0x90, 0xf0, 0x90, 0x90};
+const uint8_t I[] = {0x00, 0x20, 0x20, 0x20, 0x20, 0x20};
+const uint8_t J[] = {0x00, 0x10, 0x10, 0x10, 0x90, 0x60};
+const uint8_t K[] = {0x00, 0x90, 0xa0, 0xc0, 0xa0, 0x90};
+const uint8_t L[] = {0x00, 0x80, 0x80, 0x80, 0x80, 0xf0};
+const uint8_t M[] = {0x00, 0x90, 0xf0, 0x90, 0x90, 0x90};
+const uint8_t N[] = {0x00, 0x90, 0xd0, 0xb0, 0x90, 0x90};
+const uint8_t O[] = {0x00, 0x60, 0x90, 0x90, 0x90, 0x60};
+const uint8_t P[] = {0x00, 0xe0, 0x90, 0xe0, 0x80, 0x80};
+const uint8_t Q[] = {0x00, 0x60, 0x90, 0x90, 0xb0, 0x70};
+const uint8_t R[] = {0x00, 0xe0, 0x90, 0xe0, 0x90, 0x90};
+const uint8_t S[] = {0x00, 0x60, 0x80, 0x60, 0x10, 0xe0};
+const uint8_t T[] = {0x00, 0xe0, 0x40, 0x40, 0x40, 0x40};
+const uint8_t U[] = {0x00, 0x90, 0x90, 0x90, 0x90, 0x60};
+const uint8_t V[] = {0x00, 0x90, 0x90, 0x90, 0x60, 0x60};
+const uint8_t W[] = {0x00, 0x90, 0x90, 0x90, 0xf0, 0x90};
+const uint8_t X[] = {0x00, 0x90, 0x90, 0x60, 0x90, 0x90};
+const uint8_t Y[] = {0x00, 0x90, 0x90, 0x60, 0x60, 0x60};
+const uint8_t Z[] = {0x00, 0xf0, 0x10, 0x60, 0x80, 0xf0};
+const uint8_t dot[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x40};
+const uint8_t comma[] = {0x00, 0x00, 0x00, 0x00, 0x20, 0x40};
+const uint8_t dash[] = {0x00, 0x00, 0x00, 0x60, 0x00, 0x00};
+const uint8_t underscore[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0xf0};
+const uint8_t bracket_open[] = {0x00, 0x20, 0x40, 0x40, 0x40, 0x20};
+const uint8_t bracket_close[] = {0x00, 0x40, 0x20, 0x20, 0x20, 0x40};
+const uint8_t cross_left[] = {0x10, 0x10, 0x70, 0x70, 0x10, 0x10};
+const uint8_t cross_right[] = {0x80, 0x80, 0xe0, 0xe0, 0x80, 0x80};
+const uint8_t pacman_left[] = {0x00, 0x30, 0x50, 0x70, 0x70, 0x00};
+const uint8_t pacman_right[] = {0x00, 0xc0, 0x60, 0xe0, 0xe0, 0xe0};
+const uint8_t box[] = {0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x00};
+const uint8_t* char_arr[48] = {
+    space,
+    zero,
+    one,
+    two,
+    three,
+    four,
+    five,
+    six,
+    seven,
+    eight,
+    nine,
+    A,
+    B,
+    C,
+    D,
+    E,
+    F,
+    G,
+    H,
+    I,
+    J,
+    K,
+    L,
+    M,
+    N,
+    O,
+    P,
+    Q,
+    R,
+    S,
+    T,
+    U,
+    V,
+    W,
+    X,
+    Y,
+    Z,
+    dot,
+    comma,
+    dash,
+    underscore,
+    bracket_open,
+    bracket_close,
+    cross_left,
+    cross_right,
+    pacman_left,
+    pacman_right,
+    box};
+const uint8_t gradient[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00, 0x22, 0x22,
+                            0x00, 0x00, 0x8a, 0x8a, 0x00, 0x00, 0x22, 0x22, 0x00, 0x00, 0xaa, 0xaa,
+                            0x10, 0x10, 0xaa, 0xaa, 0x00, 0x00, 0xaa, 0xaa, 0x01, 0x01, 0xaa, 0xaa,
+                            0x44, 0x44, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 0x44, 0x44, 0xaa, 0xaa,
+                            0x15, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xbb, 0xbb,
+                            0x55, 0x55, 0xaa, 0xea, 0x55, 0x55, 0xbb, 0xbb, 0x55, 0x55, 0xff, 0xff,
+                            0x55, 0x55, 0xfb, 0xfb, 0x55, 0x55, 0xff, 0xff, 0x55, 0x55, 0xbb, 0xbf,
+                            0x57, 0x57, 0xff, 0xff, 0xdd, 0xdd, 0xff, 0xff, 0x77, 0x75, 0xff, 0xff,
+                            0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+//const uint8_t gun[] = {0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0x27, 0xff, 0xff, 0xfe, 0x3b, 0xff, 0xff, 0xfd, 0xfb, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xfd, 0x15, 0xff, 0xff, 0xfb, 0x2e, 0xff, 0xff, 0xf6, 0x77, 0x7f, 0xff, 0xe6, 0xff, 0xff, 0xff, 0xf2, 0x3d, 0x7f, 0xff, 0xd6, 0x7e, 0x3f, 0xff, 0xf4, 0x5d, 0xdf, 0xff, 0xce, 0xbf, 0xbf, 0xff, 0xdc, 0xff, 0x3f, 0xff, 0xec, 0xff, 0xbf, 0xff, 0x8d, 0xfd, 0xff, 0xff, 0xb6, 0xff, 0xbf, 0xfe, 0x1f, 0x57, 0xdf, 0xf8, 0x0e, 0xff, 0xcf, 0xf4, 0x46, 0x1f, 0x17, 0xf8, 0xa3, 0xfc, 0x03, 0xf8, 0x10, 0x00, 0x11, 0xf8, 0x8a, 0x80, 0x2d, 0xe4, 0x44, 0x00, 0x4d, 0xee, 0xa8, 0x82, 0x9b, 0xcd, 0x50, 0x00, 0x17, 0xec, 0xa0, 0x8a, 0x2f, 0xcc, 0x00, 0x04, 0x67, 0xe8, 0x28, 0x1a, 0xff, 0xe4, 0x70, 0x4d, 0xcf, 0xfc, 0x82, 0xa7, 0xef, 0x90, 0x40, 0x13, 0xdf};
+// const uint8_t gun_mask[] = {0xff, 0xff, 0x8f, 0xff, 0xff, 0xfe, 0x03, 0xff, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x0f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0f};
+const uint8_t gun[] = {0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x01, 0xc4, 0x00,
+                       0x00, 0x02, 0x04, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0xea, 0x00,
+                       0x00, 0x04, 0xd1, 0x00, 0x00, 0x09, 0x88, 0x80, 0x00, 0x19, 0x00, 0x00,
+                       0x00, 0x0d, 0xc2, 0x80, 0x00, 0x29, 0x81, 0xc0, 0x00, 0x0b, 0xa2, 0x20,
+                       0x00, 0x31, 0x40, 0x40, 0x00, 0x23, 0x00, 0xc0, 0x00, 0x13, 0x00, 0x40,
+                       0x00, 0x72, 0x02, 0x00, 0x00, 0x49, 0x00, 0x40, 0x01, 0xe0, 0xa8, 0x20,
+                       0x07, 0xf1, 0x00, 0x30, 0x0b, 0xb9, 0xe0, 0xe8, 0x07, 0x5c, 0x03, 0xfc,
+                       0x07, 0xef, 0xff, 0xee, 0x07, 0x75, 0x7f, 0xd2, 0x1b, 0xbb, 0xff, 0xb2,
+                       0x11, 0x57, 0x7d, 0x64, 0x32, 0xaf, 0xff, 0xe8, 0x13, 0x5f, 0x75, 0xd0,
+                       0x33, 0xff, 0xfb, 0x98, 0x17, 0xd7, 0xe5, 0x00, 0x1b, 0x8f, 0xb2, 0x30,
+                       0x03, 0x7d, 0x58, 0x10, 0x6f, 0xbf, 0xec, 0x20};
+const uint8_t gun_mask[] = {0x00, 0x00, 0x70, 0x00, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x03, 0xfe, 0x00,
+                            0x00, 0x07, 0xfe, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00,
+                            0x00, 0x0f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0x80,
+                            0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xf0,
+                            0x00, 0x7f, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xe0,
+                            0x00, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xf0,
+                            0x0f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xfe,
+                            0x1f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff,
+                            0x3f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xf8,
+                            0x7f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xf8,
+                            0x7f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xf0};
+
+const uint8_t
+    imp_inv[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00,
+                 0x02, 0x80, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x01,
+                 0x00, 0x00, 0x01, 0x0f, 0xb3, 0x00, 0x00, 0xd0, 0x4e, 0x00, 0x00, 0x79, 0x8c,
+                 0x00, 0x00, 0x1c, 0x19, 0x00, 0x01, 0x8a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x40, 0x02, 0x08, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x8e,
+                 0x30, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x06, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+                 0x02, 0x20, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x03, 0xe0,
+                 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0xa1, 0x80, 0x01, 0x80, 0x13, 0x00,
+                 0x00, 0xf3, 0x8a, 0x00, 0x00, 0x09, 0x94, 0x00, 0x00, 0x88, 0x38, 0x80, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x02, 0x23, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x80,
+                 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0xe2, 0x80, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x02, 0x20, 0x00,
+                 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+                 0x02, 0x20, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0,
+                 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00,
+                 0x00, 0x1f, 0x00, 0x00, 0x02, 0x2a, 0x80, 0x00, 0x01, 0x05, 0x00, 0x00, 0x01,
+                 0xae, 0x20, 0x00, 0x01, 0x24, 0x40, 0x00, 0x02, 0xac, 0x80, 0x00, 0x02, 0x86,
+                 0x00, 0x00, 0x03, 0x20, 0x20, 0x00, 0x04, 0x30, 0x40, 0x00, 0x0c, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x08, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x1a, 0x00, 0x00, 0x00,
+                 0x1c, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x98,
+                 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00,
+                 0x00, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00, 0x01, 0xd6, 0x80, 0x00,
+                 0x02, 0xbf, 0x80, 0x00, 0x06, 0x61, 0xa0, 0x00, 0x0c, 0xe8, 0x80, 0x00, 0x0c,
+                 0x10, 0x00, 0x00, 0x1a, 0x22, 0x00, 0x00, 0x12, 0x40, 0x00, 0x00, 0x06, 0x0c,
+                 0x00, 0x00, 0x04, 0x0d, 0x00, 0x00, 0x3a, 0x03, 0x00, 0x00, 0x10, 0x02, 0x00,
+                 0x00, 0x60, 0x0a, 0x00, 0x00, 0x50, 0x04, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00,
+                 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x01,
+                 0x18, 0x00, 0x00, 0x01, 0x41, 0x40, 0x02, 0x33, 0xb6, 0x80, 0x01, 0x9c, 0x04,
+                 0x00, 0x08, 0xfa, 0x02, 0x08, 0x05, 0x00, 0x01, 0x0c, 0x27, 0x83, 0xa2, 0x2a,
+                 0x00, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00}; //{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x0f, 0xb3, 0x00, 0x00, 0xd0, 0x4e, 0x00, 0x00, 0x79, 0x8c, 0x00, 0x00, 0x1c, 0x19, 0x00, 0x01, 0x8a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x08, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x8e, 0x30, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const uint8_t imp_mask_inv[] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00,
+    0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x01, 0x07, 0xf1, 0x80,
+    0x00, 0xdf, 0xfe, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80,
+    0x00, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x03, 0xcf, 0xf1, 0xc0, 0x01, 0xc7, 0xf1, 0xc0,
+    0x01, 0x87, 0xf1, 0xc0, 0x03, 0x0f, 0xf9, 0x80, 0x03, 0x0f, 0xfb, 0x80, 0x01, 0x8f, 0xff, 0x80,
+    0x03, 0x9f, 0x79, 0x00, 0x00, 0x1f, 0x7c, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x0f, 0x78, 0x00,
+    0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x30, 0x00,
+    0x00, 0x03, 0x78, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x07, 0xe0, 0x00,
+    0x00, 0x07, 0xc0, 0x00, 0x01, 0x07, 0xe1, 0x00, 0x00, 0x8f, 0xfa, 0x00, 0x00, 0xff, 0xfe, 0x00,
+    0x00, 0x3f, 0xfe, 0x00, 0x01, 0x7f, 0xff, 0x80, 0x00, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80,
+    0x03, 0xcf, 0xfb, 0xc0, 0x03, 0x87, 0xf1, 0xc0, 0x03, 0xcf, 0xf3, 0xc0, 0x01, 0xcf, 0xf1, 0x80,
+    0x00, 0xcf, 0xf1, 0x00, 0x00, 0x0f, 0xfb, 0x80, 0x00, 0x1e, 0x78, 0x00, 0x00, 0x0e, 0x78, 0x00,
+    0x00, 0x1e, 0x78, 0x00, 0x00, 0x0f, 0x70, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x07, 0x70, 0x00,
+    0x00, 0x07, 0x70, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x03, 0x20, 0x00,
+    0x00, 0x07, 0x30, 0x00, 0x00, 0x05, 0x70, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1f, 0x00,
+    0x00, 0x00, 0x1f, 0x00, 0x00, 0x03, 0x3f, 0x80, 0x00, 0x01, 0x3f, 0x00, 0x00, 0x01, 0xff, 0x30,
+    0x00, 0x03, 0xff, 0xc0, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x03, 0xff, 0x80, 0x00, 0x07, 0xff, 0xe0,
+    0x00, 0x07, 0xff, 0xc0, 0x00, 0x05, 0xff, 0xe0, 0x00, 0x00, 0xfc, 0xe0, 0x00, 0x01, 0xfc, 0xe0,
+    0x00, 0x01, 0xfc, 0x70, 0x00, 0x03, 0xfc, 0x38, 0x00, 0x03, 0xfe, 0x70, 0x00, 0x07, 0xfc, 0x00,
+    0x00, 0x07, 0x9e, 0x00, 0x00, 0x0f, 0xbc, 0x00, 0x00, 0x0f, 0x3e, 0x00, 0x00, 0x07, 0x9c, 0x00,
+    0x00, 0x03, 0x9c, 0x00, 0x00, 0x03, 0xb8, 0x00, 0x00, 0x03, 0x98, 0x00, 0x00, 0x01, 0x98, 0x00,
+    0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1f, 0x00,
+    0x00, 0x00, 0x1f, 0x40, 0x00, 0x00, 0x3e, 0x80, 0x00, 0x01, 0xff, 0x80, 0x00, 0x03, 0xff, 0x80,
+    0x00, 0x07, 0xff, 0xe0, 0x00, 0x0e, 0xff, 0xc0, 0x00, 0x0c, 0xff, 0x80, 0x00, 0x1f, 0xfe, 0x00,
+    0x00, 0x13, 0xfc, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x3f, 0x9f, 0x00,
+    0x00, 0x3e, 0x0f, 0x00, 0x00, 0x7c, 0x0f, 0x00, 0x00, 0x78, 0x0f, 0x00, 0x00, 0x78, 0x07, 0x80,
+    0x00, 0x78, 0x07, 0x40, 0x00, 0x38, 0x07, 0x80, 0x00, 0x30, 0x07, 0x00, 0x00, 0x30, 0x01, 0x00,
+    0x01, 0xf0, 0x00, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1c, 0x00,
+    0x00, 0x01, 0x3e, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xe0, 0x01, 0x3f, 0xff, 0xc0,
+    0x01, 0xff, 0xff, 0xc0, 0x19, 0xff, 0xff, 0xe8, 0x7f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xfe,
+    0x1f, 0xc2, 0x07, 0xe0, 0x1f, 0x00, 0x01, 0xe0, 0x0e, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
+}; //{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x01, 0x07, 0xf1, 0x80, 0x00, 0xdf, 0xfe, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80, 0x03, 0xcf, 0xf1, 0xc0, 0x01, 0xc7, 0xf1, 0xc0, 0x01, 0x87, 0xf1, 0xc0, 0x03, 0x0f, 0xf9, 0x80, 0x03, 0x0f, 0xfb, 0x80, 0x01, 0x8f, 0xff, 0x80, 0x03, 0x9f, 0x79, 0x00, 0x00, 0x1f, 0x7c, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x0f, 0x78, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x03, 0x78, 0x00, 0x00, 0x07, 0x30, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00};
+const uint8_t fireball[] = {0x00, 0x00, 0x01, 0x40, 0x0a, 0xb0, 0x0e, 0xd0, 0x00, 0x68, 0x53,
+                            0xb4, 0x0f, 0x48, 0x27, 0x78, 0x17, 0xa8, 0x27, 0xf0, 0x21, 0xd6,
+                            0x02, 0xf8, 0x20, 0x48, 0x06, 0x20, 0x01, 0x00, 0x00, 0x00};
+const uint8_t fireball_mask[] = {0x1f, 0x40, 0x0f, 0xf0, 0x3f, 0xf8, 0x1f, 0xfc, 0x7f, 0xfd, 0x7f,
+                                 0xfc, 0x7f, 0xfd, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+                                 0xff, 0xfe, 0x3f, 0xfe, 0x17, 0xf8, 0x07, 0xf4, 0x01, 0xe0};
+const uint8_t item[] = {0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, 0x77, 0xee, 0x3f,
+                        0xfc, 0x5f, 0xfa, 0x2f, 0xf6, 0x53, 0xcc, 0x3e, 0x7e, 0x5e, 0x7c,
+                        0x38, 0x1e, 0x58, 0x1c, 0x3e, 0x7e, 0x5e, 0x7e, 0x2e, 0xfc, 0x00,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc,
+                        0x17, 0xfc, 0x22, 0x6c, 0x36, 0x44, 0x3f, 0xfc, 0x1f, 0xfc, 0x2b,
+                        0xfc, 0x05, 0x54, 0x02, 0xa8, 0x00, 0x00, 0x00, 0x00};
+const uint8_t item_mask[] = {0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f,
+                             0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe,
+                             0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x3f, 0xfc, 0x00,
+                             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc,
+                             0x1f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f,
+                             0xfc, 0x07, 0xfc, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00};
+
+//const uint8_t door[] = {0xff, 0xff, 0xff, 0xff,0xb2, 0xbd, 0xcd, 0x5b,0x9a, 0xf4, 0x6d, 0x71,0xff, 0xff, 0xff, 0xff,0x00, 0x00, 0x00, 0x00,0xbf, 0xff, 0xff, 0xfd,0x3f, 0x00, 0xfe, 0xfc,0x3e, 0x00, 0xc6, 0xfc,0xbc, 0xaa, 0xfe, 0xbd,0x39, 0x54, 0xc6, 0xbc,0x32, 0x8e, 0xfe, 0xac,0xb5, 0xfe, 0xc6, 0xad,0x3f, 0xe0, 0xfe, 0xac,0x31, 0xe0, 0xc6, 0xac,0xb3, 0xf4, 0xfe, 0xad,0x3f, 0xe8, 0xc6, 0xac,0x3c, 0xf4, 0xd6, 0xac,0xb8, 0xff, 0xfe, 0xad,0x34, 0xc7, 0xfe, 0xfc,0x38, 0xd6, 0x0e, 0x0c,0xb0, 0xd6, 0x4e, 0x0d,0x3f, 0xd6, 0xaf, 0x5c,0x30, 0x47, 0xff, 0xac,0xb7, 0x57, 0xff, 0xfd,0x3f, 0xc6, 0x0e, 0x0c,0x35, 0x56, 0x40, 0x4c,0xb5, 0x46, 0xaa, 0xad,0x35, 0x56, 0x55, 0x4c,0xff, 0xff, 0xff, 0xff,0xb0, 0x1f, 0xf8, 0x0d,0xd9, 0x30, 0x0c, 0x9b,0xff, 0xe0, 0x07, 0xff};
+const uint8_t door[] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x07, 0xe1, 0x8c, 0x00, 0x04,
+    0x00, 0x7c, 0x03, 0x18, 0x60, 0x08, 0x00, 0x3e, 0x0f, 0xf7, 0xdf, 0x00, 0x1f, 0x00, 0xfe, 0x0f,
+    0xbe, 0xf8, 0x3e, 0x00, 0x3f, 0x1f, 0xff, 0xdf, 0x00, 0x1f, 0x81, 0xff, 0x0f, 0xff, 0xf8, 0x7e,
+    0x00, 0x3f, 0x8f, 0xff, 0xdf, 0x00, 0xff, 0xf9, 0xff, 0x1f, 0xff, 0xf8, 0xff, 0x80, 0x3f, 0xc7,
+    0xff, 0xcc, 0x07, 0xff, 0xfc, 0xff, 0x1f, 0xff, 0xe3, 0xff, 0x80, 0x3f, 0xc7, 0xff, 0xc0, 0x07,
+    0xff, 0xfc, 0x7f, 0x0f, 0xfe, 0x03, 0xff, 0xc0, 0x3f, 0xc3, 0xf7, 0xc0, 0x07, 0xdf, 0xf8, 0x3e,
+    0x0f, 0xbe, 0x01, 0xff, 0x80, 0x1f, 0x80, 0xe3, 0x80, 0x07, 0x8f, 0xf8, 0x1e, 0x07, 0x1c, 0x01,
+    0xff, 0x80, 0x3f, 0xc1, 0xff, 0xc0, 0x0f, 0xff, 0xfc, 0x3f, 0x0f, 0xbe, 0x03, 0xff, 0xc0, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0x80, 0x00,
+    0x7f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xe0, 0x00, 0x1f, 0xf0, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff,
+    0xc0, 0x00, 0x00, 0xe0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01,
+    0xe0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01, 0xf0, 0x00, 0x0f,
+    0xf0, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0x81, 0xf0, 0x00, 0x0f, 0xf0, 0xff, 0x00,
+    0x07, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xf0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x0f, 0xff, 0xff,
+    0xff, 0xe1, 0xff, 0xe1, 0xf0, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xe0, 0xff,
+    0xc1, 0xf3, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0xff, 0x81, 0xff, 0xc0,
+    0x0f, 0xf0, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0x01, 0xff, 0xc0, 0x0f, 0xf0, 0xff,
+    0x00, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xe0, 0x0f, 0xf0, 0xff, 0x01, 0xff, 0xff,
+    0xff, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xe0, 0x0f, 0xf0, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xe1,
+    0xff, 0xe1, 0xff, 0xe0, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xc1, 0xff,
+    0xf0, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x8f, 0xe0, 0xff, 0x81, 0xff, 0xff, 0x0f, 0xf0,
+    0xff, 0x1f, 0xff, 0xff, 0xfe, 0x07, 0xe0, 0x7f, 0x81, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff,
+    0xff, 0xfc, 0x07, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0x8f, 0xfc, 0x03,
+    0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0x07, 0xfc, 0x07, 0xe0, 0xff, 0xc1,
+    0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xfe, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f,
+    0xf0, 0xff, 0x0f, 0x9c, 0x00, 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07,
+    0xfc, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07, 0xfc, 0x00, 0x7f,
+    0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xfc, 0x00, 0x7f, 0xff, 0xe0, 0xff,
+    0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xfc, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff,
+    0x8f, 0xf0, 0xff, 0x1f, 0xfe, 0x00, 0x7f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff,
+    0x1f, 0xfc, 0x00, 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xfc, 0x00,
+    0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xf0, 0x00, 0x1f, 0xff, 0xe0,
+    0x7f, 0x81, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xf0, 0x00, 0x1f, 0xff, 0xe0, 0xff, 0x81, 0xff,
+    0xff, 0x8f, 0xf0, 0xff, 0x07, 0xe0, 0x00, 0x3f, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0xff, 0x8f, 0xf0,
+    0xff, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0x1f,
+    0x80, 0x3f, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0x3f, 0xc0, 0x1f, 0xff,
+    0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0x7f, 0xc0, 0x0f, 0xff, 0xe1, 0xff, 0xe1,
+    0xff, 0xff, 0x8f, 0xf0, 0xff, 0x00, 0xff, 0xc0, 0x07, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xff, 0x8f,
+    0xf0, 0xff, 0x01, 0xff, 0xc0, 0x03, 0x8f, 0xc0, 0xc1, 0x81, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x03,
+    0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07, 0xff, 0xc0, 0xff,
+    0x80, 0x00, 0x00, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc1, 0xff, 0x80, 0x00, 0x00,
+    0x01, 0xf3, 0x8e, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xff, 0x9c,
+    0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc0, 0xff, 0xfc, 0x01, 0xff, 0xfe, 0x0f, 0xf0, 0xff,
+    0x0f, 0xff, 0xc3, 0xff, 0xc1, 0xff, 0xfe, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xc3,
+    0xff, 0xc3, 0xff, 0xff, 0x01, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff,
+    0xff, 0x01, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff,
+    0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff, 0xff, 0x8f, 0xf0,
+    0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3, 0xff, 0xff, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x0f, 0xff,
+    0xc3, 0xff, 0xc3, 0xff, 0xff, 0x00, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x0f, 0xff, 0xc3, 0xff, 0xc3,
+    0xff, 0xff, 0x00, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xe3, 0xff, 0xc3, 0xff, 0xff, 0x00,
+    0x3f, 0xfe, 0x0f, 0xf0, 0xff, 0x07, 0xff, 0xf3, 0xff, 0xc1, 0xef, 0xfe, 0x00, 0x3f, 0xfe, 0x0f,
+    0xf0, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xc0, 0x82, 0x00, 0x00, 0x1f, 0xff, 0x0f, 0xf0, 0xff, 0x1f,
+    0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x07, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff,
+    0xc0, 0x00, 0x00, 0x00, 0x07, 0xff, 0x0f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00,
+    0x00, 0x03, 0x8e, 0x0f, 0xf0, 0xff, 0x1f, 0xc1, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x03, 0x88,
+    0x0f, 0xf0, 0xff, 0x0f, 0x80, 0xff, 0xff, 0xc1, 0xff, 0xfc, 0x00, 0xff, 0xfe, 0x0f, 0xf0, 0xff,
+    0x06, 0x00, 0x73, 0xff, 0xc3, 0xff, 0xfe, 0x01, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0x00, 0x03,
+    0xff, 0xc3, 0xff, 0xff, 0x83, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x07, 0x0c, 0x73, 0xff, 0xc3, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x0f, 0xfe, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0,
+    0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff,
+    0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0x8f, 0xf0, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f,
+    0xf0, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xf0, 0xfe, 0x1f,
+    0xfe, 0xfb, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xf0, 0xfc, 0x0f, 0x9e, 0x73, 0xff,
+    0x81, 0xf9, 0xf7, 0xe7, 0x9c, 0xff, 0x03, 0xf0, 0xfc, 0x07, 0xfe, 0xfb, 0xc0, 0x00, 0xf0, 0x00,
+    0x6f, 0xbe, 0xfe, 0x03, 0xf0, 0x3c, 0x07, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xfe,
+    0x03, 0xc0, 0x1c, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0x03, 0x80, 0x1e,
+    0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0x07, 0x80, 0x3f, 0x0f, 0xff, 0xff,
+    0xe0, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0x0f, 0xc0, 0x1f, 0x8f, 0xff, 0xff, 0xe7, 0xff, 0xff,
+    0xfe, 0x7f, 0xff, 0xff, 0x1f, 0x80, 0x1f, 0xc7, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xfe, 0x3f, 0x80, 0x07, 0xc3, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xfc, 0x3e, 0x00,
+    0x07, 0xc1, 0xfe, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xcf, 0xf7, 0xf8, 0x3e, 0x00, 0x01, 0x00, 0xfc,
+    0x7e, 0x7f, 0xff, 0xff, 0xff, 0xe7, 0xe3, 0xf0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff,
+    0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0,
+    0x00, 0x00, 0x00, 0x00};

+ 108 - 0
doom/assets.h

@@ -0,0 +1,108 @@
+#pragma once
+#include <gui/icon.h>
+
+#ifndef _sprites_h
+#define _sprites_h
+
+#define bmp_font_width 24 // in bytes
+#define bmp_font_height 6
+#define bmp_font_width_pxs 192
+#define bmp_font_height_pxs 48
+#define CHAR_MAP " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.,-_(){}[]#"
+#define CHAR_WIDTH 4
+#define CHAR_HEIGHT 6
+
+#define BMP_GUN_WIDTH 32
+#define BMP_GUN_HEIGHT 32
+
+#define BMP_FIRE_WIDTH 24
+#define BMP_FIRE_HEIGHT 20
+
+#define BMP_IMP_WIDTH 32
+#define BMP_IMP_HEIGHT 32
+#define BMP_IMP_COUNT 5
+
+#define BMP_FIREBALL_WIDTH 16
+#define BMP_FIREBALL_HEIGHT 16
+
+#define BMP_DOOR_WIDTH 100
+#define BMP_DOOR_HEIGHT 100
+
+#define BMP_ITEMS_WIDTH 16
+#define BMP_ITEMS_HEIGHT 16
+#define BMP_ITEMS_COUNT 2
+
+#define BMP_LOGO_WIDTH 128
+#define BMP_LOGO_HEIGHT 64
+
+#define GRADIENT_WIDTH 2
+#define GRADIENT_HEIGHT 8
+#define GRADIENT_COUNT 8
+#define GRADIENT_WHITE 7
+#define GRADIENT_BLACK 0
+
+// Fonts
+extern const uint8_t zero[];
+extern const uint8_t one[];
+extern const uint8_t two[];
+extern const uint8_t three[];
+extern const uint8_t four[];
+extern const uint8_t five[];
+extern const uint8_t six[];
+extern const uint8_t seven[];
+extern const uint8_t eight[];
+extern const uint8_t nine[];
+extern const uint8_t A[];
+extern const uint8_t B[];
+extern const uint8_t C[];
+extern const uint8_t D[];
+extern const uint8_t E[];
+extern const uint8_t F[];
+extern const uint8_t G[];
+extern const uint8_t H[];
+extern const uint8_t I[];
+extern const uint8_t J[];
+extern const uint8_t K[];
+extern const uint8_t L[];
+extern const uint8_t M[];
+extern const uint8_t N[];
+extern const uint8_t O[];
+extern const uint8_t P[];
+extern const uint8_t Q[];
+extern const uint8_t R[];
+extern const uint8_t S[];
+extern const uint8_t T[];
+extern const uint8_t U[];
+extern const uint8_t V[];
+extern const uint8_t W[];
+extern const uint8_t X[];
+extern const uint8_t Y[];
+extern const uint8_t Z[];
+extern const uint8_t dot[];
+extern const uint8_t comma[];
+extern const uint8_t dash[];
+extern const uint8_t underscore[];
+extern const uint8_t bracket_open[];
+extern const uint8_t bracket_close[];
+extern const uint8_t cross_left[];
+extern const uint8_t cross_right[];
+extern const uint8_t pacman_left[];
+extern const uint8_t pacman_right[];
+extern const uint8_t box[];
+extern const uint8_t* char_arr[48];
+extern const uint8_t gradient[];
+//extern const uint8_t gun[]
+//extern const uint8_t gun_mask[]
+extern const uint8_t gun[];
+extern const uint8_t gun_mask[];
+
+extern const uint8_t imp_inv[];
+extern const uint8_t imp_mask_inv[];
+extern const uint8_t fireball[];
+extern const uint8_t fireball_mask[];
+extern const uint8_t item[];
+extern const uint8_t item_mask[];
+
+extern const uint8_t door[];
+
+#endif

BIN
doom/assets/door2.png


BIN
doom/assets/door_inv.png


BIN
doom/assets/fire_inv.png


BIN
doom/assets/fireball_inv.png


BIN
doom/assets/fireball_mask_inv.png


BIN
doom/assets/gradient_inv.png


BIN
doom/assets/gun_inv.png


BIN
doom/assets/gun_mask_inv.png


BIN
doom/assets/imp_inv.png


BIN
doom/assets/imp_mask_inv.png


BIN
doom/assets/item_inv.png


BIN
doom/assets/item_mask_inv.png


BIN
doom/assets/logo_inv.png


+ 91 - 0
doom/constants.h

@@ -0,0 +1,91 @@
+#ifndef _constants_h
+#define _constants_h
+#define PB_CONSTEXPR constexpr
+
+#define PI 3.14159265358979323846
+
+// Key pinout
+#define USE_INPUT_PULLUP
+#define K_LEFT 6
+#define K_RIGHT 7
+#define K_UP 8
+#define K_DOWN 3
+#define K_FIRE 10
+
+// SNES Controller
+// uncomment following line to enable snes controller support
+// #define SNES_CONTROLLER
+const uint8_t DATA_CLOCK = 11;
+const uint8_t DATA_LATCH = 12;
+const uint8_t DATA_SERIAL = 13;
+
+// Sound
+const uint8_t SOUND_PIN = 9; // do not change, belongs to used timer
+
+// GFX settings
+#define OPTIMIZE_SSD1306 // Optimizations for SSD1366 displays
+
+#define FRAME_TIME 66.666666 // Desired time per frame in ms (66.666666 is ~15 fps)
+#define RES_DIVIDER 2
+
+/* Higher values will result in lower horizontal resolution when rasterize and lower process and memory usage
+ Lower will require more process and memory, but looks nicer
+ */
+#define Z_RES_DIVIDER 2 // Zbuffer resolution divider. We sacrifice resolution to save memory
+#define DISTANCE_MULTIPLIER 20
+
+/* Distances are stored as uint8_t, multiplying the distance we can obtain more precision taking care 
+ of keep numbers inside the type range. Max is 256 / MAX_RENDER_DEPTH 
+ */
+
+#define MAX_RENDER_DEPTH 12
+#define MAX_SPRITE_DEPTH 8
+
+#define ZBUFFER_SIZE SCREEN_WIDTH / Z_RES_DIVIDER
+
+// Level
+#define LEVEL_WIDTH_BASE 6
+#define LEVEL_WIDTH (1 << LEVEL_WIDTH_BASE)
+#define LEVEL_HEIGHT 57
+#define LEVEL_SIZE LEVEL_WIDTH / 2 * LEVEL_HEIGHT
+
+// scenes
+#define INTRO 0
+#define GAME_PLAY 1
+
+// Game
+#define GUN_TARGET_POS 18
+#define GUN_SHOT_POS GUN_TARGET_POS + 4
+
+#define ROT_SPEED .12
+#define MOV_SPEED .2
+#define MOV_SPEED_INV 5 // 1 / MOV_SPEED
+
+#define JOGGING_SPEED .005
+#define ENEMY_SPEED .02
+#define FIREBALL_SPEED .2
+#define FIREBALL_ANGLES 45 // Num of angles per PI
+
+#define MAX_ENTITIES 10 // Max num of active entities
+#define MAX_STATIC_ENTITIES 28 // Max num of entities in sleep mode
+
+#define MAX_ENTITY_DISTANCE 200 // * DISTANCE_MULTIPLIER
+#define MAX_ENEMY_VIEW 80 // * DISTANCE_MULTIPLIER
+#define ITEM_COLLIDER_DIST 6 // * DISTANCE_MULTIPLIER
+#define ENEMY_COLLIDER_DIST 4 // * DISTANCE_MULTIPLIER
+#define FIREBALL_COLLIDER_DIST 2 // * DISTANCE_MULTIPLIER
+#define ENEMY_MELEE_DIST 6 // * DISTANCE_MULTIPLIER
+#define WALL_COLLIDER_DIST .2
+
+#define ENEMY_MELEE_DAMAGE 8
+#define ENEMY_FIREBALL_DAMAGE 20
+#define GUN_MAX_DAMAGE 20
+
+// display
+const uint8_t SCREEN_WIDTH = 128;
+const uint8_t SCREEN_HEIGHT = 64;
+const uint8_t HALF_WIDTH = SCREEN_WIDTH / 2;
+const uint8_t RENDER_HEIGHT = 56; // raycaster working height (the rest is for the hud)
+const uint8_t HALF_HEIGHT = SCREEN_HEIGHT / 2;
+
+#endif

+ 280 - 0
doom/display.h

@@ -0,0 +1,280 @@
+#include <gui/gui.h>
+#include <furi_hal.h>
+#include "constants.h"
+#include <doom_icons.h>
+#include "assets.h"
+
+#define CHECK_BIT(var, pos) ((var) & (1 << (pos)))
+
+static const uint8_t bit_mask[8] = {128, 64, 32, 16, 8, 4, 2, 1};
+
+#define pgm_read_byte(addr) (*(const unsigned char*)(addr))
+#define read_bit(b, n) b& pgm_read_byte(bit_mask + n) ? 1 : 0
+//#define read_bit(byte, index) (((unsigned)(byte) >> (index)) & 1)
+
+void drawVLine(uint8_t x, int8_t start_y, int8_t end_y, uint8_t intensity, Canvas* const canvas);
+void drawPixel(int8_t x, int8_t y, bool color, bool raycasterViewport, Canvas* const canvas);
+void drawSprite(
+    int8_t x,
+    int8_t y,
+    const uint8_t* bitmap,
+    const uint8_t* bitmap_mask,
+    int16_t w,
+    int16_t h,
+    uint8_t sprite,
+    double distance,
+    Canvas* const canvas);
+void drawBitmap(
+    int16_t x,
+    int16_t y,
+    const Icon* i,
+    int16_t w,
+    int16_t h,
+    uint16_t color,
+    Canvas* const canvas);
+void drawTextSpace(int8_t x, int8_t y, char* txt, uint8_t space, Canvas* const canvas);
+void drawChar(int8_t x, int8_t y, char ch, Canvas* const canvas);
+void clearRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas);
+void drawGun(
+    int16_t x,
+    int16_t y,
+    const uint8_t* bitmap,
+    int16_t w,
+    int16_t h,
+    uint16_t color,
+    Canvas* const canvas);
+void drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas);
+void drawText(uint8_t x, uint8_t y, uint8_t num, Canvas* const canvas);
+void fadeScreen(uint8_t intensity, bool color, Canvas* const canvas);
+bool getGradientPixel(uint8_t x, uint8_t y, uint8_t i);
+double getActualFps();
+void fps();
+uint8_t reverse_bits(uint8_t num);
+
+// FPS control
+double delta = 1;
+uint32_t lastFrameTime = 0;
+uint8_t zbuffer[128]; /// 128 = screen width & REMOVE WHEN DISPLAY.H IMPLEMENTED
+
+void drawGun(
+    int16_t x,
+    int16_t y,
+    const uint8_t* bitmap,
+    int16_t w,
+    int16_t h,
+    uint16_t color,
+    Canvas* const canvas) {
+    int16_t byteWidth = (w + 7) / 8;
+    uint8_t byte = 0;
+    for(int16_t j = 0; j < h; j++, y++) {
+        for(int16_t i = 0; i < w; i++) {
+            if(i & 7)
+                byte <<= 1;
+            else
+                byte = pgm_read_byte(&bitmap[j * byteWidth + i / 8]);
+            if(byte & 0x80) drawPixel(x + i, y, color, false, canvas);
+        }
+    }
+}
+
+void drawVLine(uint8_t x, int8_t start_y, int8_t end_y, uint8_t intensity, Canvas* const canvas) {
+    UNUSED(intensity);
+    uint8_t dots = end_y - start_y;
+    for(int i = 0; i < dots; i++) {
+        canvas_draw_dot(canvas, x, start_y + i);
+    }
+}
+
+void drawBitmap(
+    int16_t x,
+    int16_t y,
+    const Icon* i,
+    int16_t w,
+    int16_t h,
+    uint16_t color,
+    Canvas* const canvas) {
+    UNUSED(w);
+    UNUSED(h);
+    if(!color) {
+        canvas_invert_color(canvas);
+    }
+    canvas_draw_icon(canvas, x, y, i);
+    if(!color) {
+        canvas_invert_color(canvas);
+    }
+}
+
+void drawText(uint8_t x, uint8_t y, uint8_t num, Canvas* const canvas) {
+    char buf[4];
+    snprintf(buf, 4, "%d", num);
+    drawTextSpace(x, y, buf, 1, canvas);
+}
+
+void drawTextSpace(int8_t x, int8_t y, char* txt, uint8_t space, Canvas* const canvas) {
+    uint8_t pos = x;
+    uint8_t i = 0;
+    char ch;
+    while((ch = txt[i]) != '\0') {
+        drawChar(pos, y, ch, canvas);
+        i++;
+        pos += CHAR_WIDTH + space;
+
+        // shortcut on end of screen
+        if(pos > SCREEN_WIDTH) return;
+    }
+}
+
+// Custom drawBitmap method with scale support, mask, zindex and pattern filling
+void drawSprite(
+    int8_t x,
+    int8_t y,
+    const uint8_t* bitmap,
+    const uint8_t* bitmap_mask,
+    int16_t w,
+    int16_t h,
+    uint8_t sprite,
+    double distance,
+    Canvas* const canvas) {
+    uint8_t tw = (double)w / distance;
+    uint8_t th = (double)h / distance;
+    uint8_t byte_width = w / 8;
+    uint8_t pixel_size = fmax(1, (double)1.0 / (double)distance);
+    uint16_t sprite_offset = byte_width * h * sprite;
+
+    bool pixel;
+    bool maskPixel;
+
+    // Don't draw the whole sprite if the anchor is hidden by z buffer
+    // Not checked per pixel for performance reasons
+    if(zbuffer[(int)(fmin(fmax(x, 0), ZBUFFER_SIZE - 1) / Z_RES_DIVIDER)] <
+       distance * DISTANCE_MULTIPLIER) {
+        return;
+    }
+
+    for(uint8_t ty = 0; ty < th; ty += pixel_size) {
+        // Don't draw out of screen
+        if(y + ty < 0 || y + ty >= RENDER_HEIGHT) {
+            continue;
+        }
+
+        uint8_t sy = ty * distance; // The y from the sprite
+
+        for(uint8_t tx = 0; tx < tw; tx += pixel_size) {
+            uint8_t sx = tx * distance; // The x from the sprite
+            uint16_t byte_offset = sprite_offset + sy * byte_width + sx / 8;
+
+            // Don't draw out of screen
+            if(x + tx < 0 || x + tx >= SCREEN_WIDTH) {
+                continue;
+            }
+
+            maskPixel = read_bit(pgm_read_byte(bitmap_mask + byte_offset), sx % 8);
+
+            if(maskPixel) {
+                pixel = read_bit(pgm_read_byte(bitmap + byte_offset), sx % 8);
+                for(uint8_t ox = 0; ox < pixel_size; ox++) {
+                    for(uint8_t oy = 0; oy < pixel_size; oy++) {
+                        if(bitmap == imp_inv)
+                            drawPixel(x + tx + ox, y + ty + oy, 1, true, canvas);
+                        else
+                            drawPixel(x + tx + ox, y + ty + oy, pixel, true, canvas);
+                    }
+                }
+            }
+        }
+    }
+}
+
+void drawPixel(int8_t x, int8_t y, bool color, bool raycasterViewport, Canvas* const canvas) {
+    if(x < 0 || x >= SCREEN_WIDTH || y < 0 ||
+       y >= (raycasterViewport ? RENDER_HEIGHT : SCREEN_HEIGHT)) {
+        return;
+    }
+    if(color)
+        canvas_draw_dot(canvas, x, y);
+    else {
+        canvas_invert_color(canvas);
+        canvas_draw_dot(canvas, x, y);
+        canvas_invert_color(canvas);
+    }
+}
+
+void drawChar(int8_t x, int8_t y, char ch, Canvas* const canvas) {
+    uint8_t lsb;
+    uint8_t c = 0;
+    while(CHAR_MAP[c] != ch && CHAR_MAP[c] != '\0') c++;
+    for(uint8_t i = 0; i < 6; i++) {
+        //lsb = (char_arr[c][i] >> 4);
+        lsb = reverse_bits(char_arr[c][i]);
+        for(uint8_t n = 0; n < 4; n++) {
+            if(CHECK_BIT(lsb, n)) {
+                drawPixel(x + n, y + i, true, false, canvas);
+            }
+        }
+    }
+}
+
+void clearRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas) {
+    canvas_invert_color(canvas);
+
+    for(int i = 0; i < w; i++) {
+        for(int j = 0; j < h; j++) {
+            canvas_draw_dot(canvas, x + i, y + j);
+        }
+    }
+
+    canvas_invert_color(canvas);
+}
+
+void drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, Canvas* const canvas) {
+    for(int i = 0; i < w; i++) {
+        for(int j = 0; j < h; j++) {
+            canvas_draw_dot(canvas, x + i, y + j);
+        }
+    }
+}
+
+bool getGradientPixel(uint8_t x, uint8_t y, uint8_t i) {
+    if(i == 0) return 0;
+    if(i >= GRADIENT_COUNT - 1) return 1;
+
+    uint8_t index =
+        fmax(0, fmin(GRADIENT_COUNT - 1, i)) * GRADIENT_WIDTH * GRADIENT_HEIGHT // gradient index
+        + y * GRADIENT_WIDTH % (GRADIENT_WIDTH * GRADIENT_HEIGHT) // y byte offset
+        + x / GRADIENT_HEIGHT % GRADIENT_WIDTH; // x byte offset
+    //uint8_t *gradient_data = NULL;
+    //furi_hal_compress_icon_decode(icon_get_data(&I_gradient_inv), &gradient_data);
+    // return the bit based on x
+    return read_bit(pgm_read_byte(gradient + index), x % 8);
+}
+
+void fadeScreen(uint8_t intensity, bool color, Canvas* const canvas) {
+    for(uint8_t x = 0; x < SCREEN_WIDTH; x++) {
+        for(uint8_t y = 0; y < SCREEN_HEIGHT; y++) {
+            if(getGradientPixel(x, y, intensity)) drawPixel(x, y, color, false, canvas);
+        }
+    }
+}
+
+// Adds a delay to limit play to specified fps
+// Calculates also delta to keep movement consistent in lower framerates
+void fps() {
+    while(furi_get_tick() - lastFrameTime < FRAME_TIME)
+        ;
+    delta = (double)(furi_get_tick() - lastFrameTime) / (double)FRAME_TIME;
+    lastFrameTime = furi_get_tick();
+}
+
+double getActualFps() {
+    return 1000 / ((double)FRAME_TIME * (double)delta);
+}
+
+uint8_t reverse_bits(uint8_t num) {
+    unsigned int NO_OF_BITS = sizeof(num) * 8;
+    uint8_t reverse_num = 0;
+    uint8_t i;
+    for(i = 0; i < NO_OF_BITS; i++) {
+        if((num & (1 << i))) reverse_num |= 1 << ((NO_OF_BITS - 1) - i);
+    }
+    return reverse_num;
+}

+ 1105 - 0
doom/doom.c

@@ -0,0 +1,1105 @@
+#include <furi.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/time.h>
+#include "sound.h"
+#include "display.h"
+#include "assets.h"
+#include "constants.h"
+#include "entities.h"
+#include "types.h"
+#include "level.h"
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <dolphin/dolphin.h>
+
+#define SOUND
+
+// Useful macros
+#define swap(a, b)          \
+    do {                    \
+        typeof(a) temp = a; \
+        a = b;              \
+        b = temp;           \
+    } while(0)
+#define sign(a, b) (double)(a > b ? 1 : (b > a ? -1 : 0))
+#define pgm_read_byte(addr) (*(const unsigned char*)(addr))
+
+typedef enum {
+    EventTypeTick,
+    EventTypeKey,
+} EventType;
+
+typedef struct {
+    EventType type;
+    InputEvent input;
+} PluginEvent;
+
+typedef struct {
+    FuriMutex* mutex;
+    Player player;
+    Entity entity[MAX_ENTITIES];
+    StaticEntity static_entity[MAX_STATIC_ENTITIES];
+    uint8_t num_entities;
+    uint8_t num_static_entities;
+
+    uint8_t scene;
+    uint8_t gun_pos;
+    double jogging;
+    double view_height;
+    bool init;
+
+    bool up;
+    bool down;
+    bool left;
+    bool right;
+    bool fired;
+    bool gun_fired;
+
+    double rot_speed;
+    double old_dir_x;
+    double old_plane_x;
+    NotificationApp* notify;
+#ifdef SOUND
+    MusicPlayer* music_instance;
+    bool intro_sound;
+#endif
+} PluginState;
+
+static const NotificationSequence sequence_short_sound = {
+    &message_note_c5,
+    &message_delay_50,
+    &message_sound_off,
+    NULL,
+};
+static const NotificationSequence sequence_long_sound = {
+    &message_note_c3,
+    &message_delay_100,
+    &message_sound_off,
+    NULL,
+};
+
+Coords translateIntoView(Coords* pos, PluginState* const plugin_state);
+void updateHud(Canvas* const canvas, PluginState* const plugin_state);
+// general
+
+bool invert_screen = false;
+uint8_t flash_screen = 0;
+
+// game
+// player and entities
+
+uint8_t getBlockAt(const uint8_t level[], uint8_t x, uint8_t y) {
+    if(x >= LEVEL_WIDTH || y >= LEVEL_HEIGHT) {
+        return E_FLOOR;
+    }
+
+    // y is read in inverse order
+    return pgm_read_byte(level + (((LEVEL_HEIGHT - 1 - y) * LEVEL_WIDTH + x) / 2)) >>
+               (!(x % 2) * 4) // displace part of wanted bits
+           & 0b1111; // mask wanted bits
+}
+
+// Finds the player in the map
+void initializeLevel(const uint8_t level[], PluginState* const plugin_state) {
+    for(uint8_t y = LEVEL_HEIGHT - 1; y > 0; y--) {
+        for(uint8_t x = 0; x < LEVEL_WIDTH; x++) {
+            uint8_t block = getBlockAt(level, x, y);
+
+            if(block == E_PLAYER) {
+                plugin_state->player = create_player(x, y);
+                return;
+            }
+
+            // todo create other static entities
+        }
+    }
+}
+
+bool isSpawned(UID uid, PluginState* const plugin_state) {
+    for(uint8_t i = 0; i < plugin_state->num_entities; i++) {
+        if(plugin_state->entity[i].uid == uid) return true;
+    }
+
+    return false;
+}
+
+bool isStatic(UID uid, PluginState* const plugin_state) {
+    for(uint8_t i = 0; i < plugin_state->num_static_entities; i++) {
+        if(plugin_state->static_entity[i].uid == uid) return true;
+    }
+
+    return false;
+}
+
+void spawnEntity(uint8_t type, uint8_t x, uint8_t y, PluginState* const plugin_state) {
+    // Limit the number of spawned entities
+    if(plugin_state->num_entities >= MAX_ENTITIES) {
+        return;
+    }
+
+    // todo: read static entity status
+
+    switch(type) {
+    case E_ENEMY:
+        plugin_state->entity[plugin_state->num_entities] = create_enemy(x, y);
+        plugin_state->num_entities++;
+        break;
+
+    case E_KEY:
+        plugin_state->entity[plugin_state->num_entities] = create_key(x, y);
+        plugin_state->num_entities++;
+        break;
+
+    case E_MEDIKIT:
+        plugin_state->entity[plugin_state->num_entities] = create_medikit(x, y);
+        plugin_state->num_entities++;
+        break;
+    }
+}
+
+void spawnFireball(double x, double y, PluginState* const plugin_state) {
+    // Limit the number of spawned entities
+    if(plugin_state->num_entities >= MAX_ENTITIES) {
+        return;
+    }
+
+    UID uid = create_uid(E_FIREBALL, x, y);
+    // Remove if already exists, don't throw anything. Not the best, but shouldn't happen too often
+    if(isSpawned(uid, plugin_state)) return;
+
+    // Calculate direction. 32 angles
+    int16_t dir =
+        FIREBALL_ANGLES + atan2(y - plugin_state->player.pos.y, x - plugin_state->player.pos.x) /
+                              (double)PI * FIREBALL_ANGLES;
+    if(dir < 0) dir += FIREBALL_ANGLES * 2;
+    plugin_state->entity[plugin_state->num_entities] = create_fireball(x, y, dir);
+    plugin_state->num_entities++;
+}
+
+void removeEntity(UID uid, PluginState* const plugin_state) {
+    uint8_t i = 0;
+    bool found = false;
+
+    while(i < plugin_state->num_entities) {
+        if(!found && plugin_state->entity[i].uid == uid) {
+            // todo: doze it
+            found = true;
+            plugin_state->num_entities--;
+        }
+
+        // displace entities
+        if(found) {
+            plugin_state->entity[i] = plugin_state->entity[i + 1];
+        }
+
+        i++;
+    }
+}
+
+void removeStaticEntity(UID uid, PluginState* const plugin_state) {
+    uint8_t i = 0;
+    bool found = false;
+
+    while(i < plugin_state->num_static_entities) {
+        if(!found && plugin_state->static_entity[i].uid == uid) {
+            found = true;
+            plugin_state->num_static_entities--;
+        }
+
+        // displace entities
+        if(found) {
+            plugin_state->static_entity[i] = plugin_state->static_entity[i + 1];
+        }
+
+        i++;
+    }
+}
+
+UID detectCollision(
+    const uint8_t level[],
+    Coords* pos,
+    double relative_x,
+    double relative_y,
+    bool only_walls,
+    PluginState* const plugin_state) {
+    // Wall collision
+    uint8_t round_x = (int)(pos->x + relative_x);
+    uint8_t round_y = (int)(pos->y + relative_y);
+    uint8_t block = getBlockAt(level, round_x, round_y);
+
+    if(block == E_WALL) {
+        // playSound(hit_wall_snd, HIT_WALL_SND_LEN);
+        return create_uid(block, round_x, round_y);
+    }
+
+    if(only_walls) {
+        return UID_null;
+    }
+
+    // Entity collision
+    for(uint8_t i = 0; i < plugin_state->num_entities; i++) {
+        // Don't collide with itself
+        if(&(plugin_state->entity[i].pos) == pos) {
+            continue;
+        }
+
+        uint8_t type = uid_get_type(plugin_state->entity[i].uid);
+
+        // Only ALIVE enemy collision
+        if(type != E_ENEMY || plugin_state->entity[i].state == S_DEAD ||
+           plugin_state->entity[i].state == S_HIDDEN) {
+            continue;
+        }
+
+        Coords new_coords = {
+            plugin_state->entity[i].pos.x - relative_x,
+            plugin_state->entity[i].pos.y - relative_y};
+        uint8_t distance = coords_distance(pos, &new_coords);
+
+        // Check distance and if it's getting closer
+        if(distance < ENEMY_COLLIDER_DIST && distance < plugin_state->entity[i].distance) {
+            return plugin_state->entity[i].uid;
+        }
+    }
+
+    return UID_null;
+}
+
+// Shoot
+void fire(PluginState* const plugin_state) {
+    //playSound(shoot_snd, SHOOT_SND_LEN);
+
+    for(uint8_t i = 0; i < plugin_state->num_entities; i++) {
+        // Shoot only ALIVE enemies
+        if(uid_get_type(plugin_state->entity[i].uid) != E_ENEMY ||
+           plugin_state->entity[i].state == S_DEAD || plugin_state->entity[i].state == S_HIDDEN) {
+            continue;
+        }
+
+        Coords transform = translateIntoView(&(plugin_state->entity[i].pos), plugin_state);
+        if(fabs(transform.x) < 20 && transform.y > 0) {
+            uint8_t damage = (double)fmin(
+                GUN_MAX_DAMAGE,
+                GUN_MAX_DAMAGE / (fabs(transform.x) * plugin_state->entity[i].distance) / 5);
+            if(damage > 0) {
+                plugin_state->entity[i].health = fmax(0, plugin_state->entity[i].health - damage);
+                plugin_state->entity[i].state = S_HIT;
+                plugin_state->entity[i].timer = 4;
+            }
+        }
+    }
+}
+
+UID updatePosition(
+    const uint8_t level[],
+    Coords* pos,
+    double relative_x,
+    double relative_y,
+    bool only_walls,
+    PluginState* const plugin_state) {
+    UID collide_x = detectCollision(level, pos, relative_x, 0, only_walls, plugin_state);
+    UID collide_y = detectCollision(level, pos, 0, relative_y, only_walls, plugin_state);
+
+    if(!collide_x) pos->x += relative_x;
+    if(!collide_y) pos->y += relative_y;
+
+    return collide_x || collide_y || UID_null;
+}
+
+void updateEntities(const uint8_t level[], Canvas* const canvas, PluginState* const plugin_state) {
+    uint8_t i = 0;
+    while(i < plugin_state->num_entities) {
+        // update distance
+        plugin_state->entity[i].distance =
+            coords_distance(&(plugin_state->player.pos), &(plugin_state->entity[i].pos));
+
+        // Run the timer. Works with actual frames.
+        // Todo: use delta here. But needs double type and more memory
+        if(plugin_state->entity[i].timer > 0) plugin_state->entity[i].timer--;
+
+        // too far away. put it in doze mode
+        if(plugin_state->entity[i].distance > MAX_ENTITY_DISTANCE) {
+            removeEntity(plugin_state->entity[i].uid, plugin_state);
+            // don't increase 'i', since current one has been removed
+            continue;
+        }
+
+        // bypass render if hidden
+        if(plugin_state->entity[i].state == S_HIDDEN) {
+            i++;
+            continue;
+        }
+
+        uint8_t type = uid_get_type(plugin_state->entity[i].uid);
+
+        switch(type) {
+        case E_ENEMY: {
+            // Enemy "IA"
+            if(plugin_state->entity[i].health == 0) {
+                if(plugin_state->entity[i].state != S_DEAD) {
+                    plugin_state->entity[i].state = S_DEAD;
+                    plugin_state->entity[i].timer = 6;
+                }
+            } else if(plugin_state->entity[i].state == S_HIT) {
+                if(plugin_state->entity[i].timer == 0) {
+                    // Back to alert state
+                    plugin_state->entity[i].state = S_ALERT;
+                    plugin_state->entity[i].timer = 40; // delay next fireball thrown
+                }
+            } else if(plugin_state->entity[i].state == S_FIRING) {
+                if(plugin_state->entity[i].timer == 0) {
+                    // Back to alert state
+                    plugin_state->entity[i].state = S_ALERT;
+                    plugin_state->entity[i].timer = 40; // delay next fireball throwm
+                }
+            } else {
+                // ALERT STATE
+                if(plugin_state->entity[i].distance > ENEMY_MELEE_DIST &&
+                   plugin_state->entity[i].distance < MAX_ENEMY_VIEW) {
+                    if(plugin_state->entity[i].state != S_ALERT) {
+                        plugin_state->entity[i].state = S_ALERT;
+                        plugin_state->entity[i].timer = 20; // used to throw fireballs
+                    } else {
+                        if(plugin_state->entity[i].timer == 0) {
+                            // Throw a fireball
+                            spawnFireball(
+                                plugin_state->entity[i].pos.x,
+                                plugin_state->entity[i].pos.y,
+                                plugin_state);
+                            plugin_state->entity[i].state = S_FIRING;
+                            plugin_state->entity[i].timer = 6;
+                        } else {
+                            // move towards to the player.
+                            updatePosition(
+                                level,
+                                &(plugin_state->entity[i].pos),
+                                sign(plugin_state->player.pos.x, plugin_state->entity[i].pos.x) *
+                                    (double)ENEMY_SPEED * 1, // NOT SURE (delta)
+                                sign(plugin_state->player.pos.y, plugin_state->entity[i].pos.y) *
+                                    (double)ENEMY_SPEED * 1, // NOT SURE (delta)
+                                true,
+                                plugin_state);
+                        }
+                    }
+                } else if(plugin_state->entity[i].distance <= ENEMY_MELEE_DIST) {
+                    if(plugin_state->entity[i].state != S_MELEE) {
+                        // Preparing the melee attack
+                        plugin_state->entity[i].state = S_MELEE;
+                        plugin_state->entity[i].timer = 10;
+                    } else if(plugin_state->entity[i].timer == 0) {
+                        // Melee attack
+                        plugin_state->player.health =
+                            fmax(0, plugin_state->player.health - ENEMY_MELEE_DAMAGE);
+                        plugin_state->entity[i].timer = 14;
+                        flash_screen = 1;
+                        updateHud(canvas, plugin_state);
+                    }
+                } else {
+                    // stand
+                    plugin_state->entity[i].state = S_STAND;
+                }
+            }
+            break;
+        }
+
+        case E_FIREBALL: {
+            if(plugin_state->entity[i].distance < FIREBALL_COLLIDER_DIST) {
+                // Hit the player and disappear
+                plugin_state->player.health =
+                    fmax(0, plugin_state->player.health - ENEMY_FIREBALL_DAMAGE);
+                flash_screen = 1;
+                updateHud(canvas, plugin_state);
+                removeEntity(plugin_state->entity[i].uid, plugin_state);
+                continue; // continue in the loop
+            } else {
+                // Move. Only collide with walls.
+                // Note: using health to store the angle of the movement
+                UID collided = updatePosition(
+                    level,
+                    &(plugin_state->entity[i].pos),
+                    cos((double)plugin_state->entity[i].health / FIREBALL_ANGLES * (double)PI) *
+                        (double)FIREBALL_SPEED,
+                    sin((double)plugin_state->entity[i].health / FIREBALL_ANGLES * (double)PI) *
+                        (double)FIREBALL_SPEED,
+                    true,
+                    plugin_state);
+
+                if(collided) {
+                    removeEntity(plugin_state->entity[i].uid, plugin_state);
+                    continue; // continue in the entity check loop
+                }
+            }
+            break;
+        }
+
+        case E_MEDIKIT: {
+            if(plugin_state->entity[i].distance < ITEM_COLLIDER_DIST) {
+                // pickup
+                notification_message(plugin_state->notify, &sequence_long_sound);
+                //playSound(medkit_snd, MEDKIT_SND_LEN);
+                plugin_state->entity[i].state = S_HIDDEN;
+                plugin_state->player.health = fmin(100, plugin_state->player.health + 50);
+                updateHud(canvas, plugin_state);
+                flash_screen = 1;
+            }
+            break;
+        }
+
+        case E_KEY: {
+            if(plugin_state->entity[i].distance < ITEM_COLLIDER_DIST) {
+                // pickup
+                notification_message(plugin_state->notify, &sequence_long_sound);
+                //playSound(get_key_snd, GET_KEY_SND_LEN);
+                plugin_state->entity[i].state = S_HIDDEN;
+                plugin_state->player.keys++;
+                updateHud(canvas, plugin_state);
+                flash_screen = 1;
+            }
+            break;
+        }
+        }
+
+        i++;
+    }
+}
+
+// The map raycaster. Based on https://lodev.org/cgtutor/raycasting.html
+void renderMap(
+    const uint8_t level[],
+    double view_height,
+    Canvas* const canvas,
+    PluginState* const plugin_state) {
+    UID last_uid = 0; // NOT SURE ?
+
+    for(uint8_t x = 0; x < SCREEN_WIDTH; x += RES_DIVIDER) {
+        double camera_x = 2 * (double)x / SCREEN_WIDTH - 1;
+        double ray_x = plugin_state->player.dir.x + plugin_state->player.plane.x * camera_x;
+        double ray_y = plugin_state->player.dir.y + plugin_state->player.plane.y * camera_x;
+        uint8_t map_x = (uint8_t)plugin_state->player.pos.x;
+        uint8_t map_y = (uint8_t)plugin_state->player.pos.y;
+        Coords map_coords = {plugin_state->player.pos.x, plugin_state->player.pos.y};
+        double delta_x = fabs(1 / ray_x);
+        double delta_y = fabs(1 / ray_y);
+
+        int8_t step_x;
+        int8_t step_y;
+        double side_x;
+        double side_y;
+
+        if(ray_x < 0) {
+            step_x = -1;
+            side_x = (plugin_state->player.pos.x - map_x) * delta_x;
+        } else {
+            step_x = 1;
+            side_x = (map_x + (double)1.0 - plugin_state->player.pos.x) * delta_x;
+        }
+
+        if(ray_y < 0) {
+            step_y = -1;
+            side_y = (plugin_state->player.pos.y - map_y) * delta_y;
+        } else {
+            step_y = 1;
+            side_y = (map_y + (double)1.0 - plugin_state->player.pos.y) * delta_y;
+        }
+
+        // Wall detection
+        uint8_t depth = 0;
+        bool hit = 0;
+        bool side;
+        while(!hit && depth < MAX_RENDER_DEPTH) {
+            if(side_x < side_y) {
+                side_x += delta_x;
+                map_x += step_x;
+                side = 0;
+            } else {
+                side_y += delta_y;
+                map_y += step_y;
+                side = 1;
+            }
+
+            uint8_t block = getBlockAt(level, map_x, map_y);
+
+            if(block == E_WALL) {
+                hit = 1;
+            } else {
+                // Spawning entities here, as soon they are visible for the
+                // player. Not the best place, but would be a very performance
+                // cost scan for them in another loop
+                if(block == E_ENEMY || (block & 0b00001000) /* all collectable items */) {
+                    // Check that it's close to the player
+                    if(coords_distance(&(plugin_state->player.pos), &map_coords) <
+                       MAX_ENTITY_DISTANCE) {
+                        UID uid = create_uid(block, map_x, map_y);
+                        if(last_uid != uid && !isSpawned(uid, plugin_state)) {
+                            spawnEntity(block, map_x, map_y, plugin_state);
+                            last_uid = uid;
+                        }
+                    }
+                }
+            }
+
+            depth++;
+        }
+
+        if(hit) {
+            double distance;
+
+            if(side == 0) {
+                distance =
+                    fmax(1, (map_x - plugin_state->player.pos.x + (1 - step_x) / 2) / ray_x);
+            } else {
+                distance =
+                    fmax(1, (map_y - plugin_state->player.pos.y + (1 - step_y) / 2) / ray_y);
+            }
+
+            // store zbuffer value for the column
+            zbuffer[x / Z_RES_DIVIDER] = fmin(distance * DISTANCE_MULTIPLIER, 255);
+
+            // rendered line height
+            uint8_t line_height = RENDER_HEIGHT / distance;
+
+            drawVLine(
+                x,
+                view_height / distance - line_height / 2 + RENDER_HEIGHT / 2,
+                view_height / distance + line_height / 2 + RENDER_HEIGHT / 2,
+                GRADIENT_COUNT - (int)distance / MAX_RENDER_DEPTH * GRADIENT_COUNT - side * 2,
+                canvas);
+        }
+    }
+}
+
+// Sort entities from far to close
+uint8_t sortEntities(PluginState* const plugin_state) {
+    uint8_t gap = plugin_state->num_entities;
+    bool swapped = false;
+    while(gap > 1 || swapped) {
+        //shrink factor 1.3
+        gap = (gap * 10) / 13;
+        if(gap == 9 || gap == 10) gap = 11;
+        if(gap < 1) gap = 1;
+        swapped = false;
+        for(uint8_t i = 0; i < plugin_state->num_entities - gap; i++) {
+            uint8_t j = i + gap;
+            if(plugin_state->entity[i].distance < plugin_state->entity[j].distance) {
+                swap(plugin_state->entity[i], plugin_state->entity[j]);
+                swapped = true;
+            }
+        }
+    }
+    return swapped;
+}
+
+Coords translateIntoView(Coords* pos, PluginState* const plugin_state) {
+    //translate sprite position to relative to camera
+    double sprite_x = pos->x - plugin_state->player.pos.x;
+    double sprite_y = pos->y - plugin_state->player.pos.y;
+
+    //required for correct matrix multiplication
+    double inv_det =
+        ((double)1.0 /
+         ((double)plugin_state->player.plane.x * (double)plugin_state->player.dir.y -
+          (double)plugin_state->player.dir.x * (double)plugin_state->player.plane.y));
+    double transform_x =
+        inv_det * (plugin_state->player.dir.y * sprite_x - plugin_state->player.dir.x * sprite_y);
+    double transform_y = inv_det * (-plugin_state->player.plane.y * sprite_x +
+                                    plugin_state->player.plane.x * sprite_y); // Z in screen
+    Coords res = {transform_x, transform_y};
+    return res;
+}
+
+void renderEntities(double view_height, Canvas* const canvas, PluginState* const plugin_state) {
+    sortEntities(plugin_state);
+
+    for(uint8_t i = 0; i < plugin_state->num_entities; i++) {
+        if(plugin_state->entity[i].state == S_HIDDEN) continue;
+
+        Coords transform = translateIntoView(&(plugin_state->entity[i].pos), plugin_state);
+
+        // don´t render if behind the player or too far away
+        if(transform.y <= (double)0.1 || transform.y > MAX_SPRITE_DEPTH) {
+            continue;
+        }
+
+        int16_t sprite_screen_x = HALF_WIDTH * ((double)1.0 + transform.x / transform.y);
+        int8_t sprite_screen_y = RENDER_HEIGHT / 2 + view_height / transform.y;
+        uint8_t type = uid_get_type(plugin_state->entity[i].uid);
+
+        // don´t try to render if outside of screen
+        // doing this pre-shortcut due int16 -> int8 conversion makes out-of-screen
+        // values fit into the screen space
+        if(sprite_screen_x < -HALF_WIDTH || sprite_screen_x > SCREEN_WIDTH + HALF_WIDTH) {
+            continue;
+        }
+
+        switch(type) {
+        case E_ENEMY: {
+            uint8_t sprite;
+            if(plugin_state->entity[i].state == S_ALERT) {
+                // walking
+                sprite = ((int)furi_get_tick() / 500) % 2;
+            } else if(plugin_state->entity[i].state == S_FIRING) {
+                // fireball
+                sprite = 2;
+            } else if(plugin_state->entity[i].state == S_HIT) {
+                // hit
+                sprite = 3;
+            } else if(plugin_state->entity[i].state == S_MELEE) {
+                // melee atack
+                sprite = plugin_state->entity[i].timer > 10 ? 2 : 1;
+            } else if(plugin_state->entity[i].state == S_DEAD) {
+                // dying
+                sprite = plugin_state->entity[i].timer > 0 ? 3 : 4;
+            } else {
+                // stand
+                sprite = 0;
+            }
+
+            drawSprite(
+                sprite_screen_x - BMP_IMP_WIDTH * (double).5 / transform.y,
+                sprite_screen_y - 8 / transform.y,
+                imp_inv,
+                imp_mask_inv,
+                BMP_IMP_WIDTH,
+                BMP_IMP_HEIGHT,
+                sprite,
+                transform.y,
+                canvas);
+            break;
+        }
+
+        case E_FIREBALL: {
+            drawSprite(
+                sprite_screen_x - BMP_FIREBALL_WIDTH / 2 / transform.y,
+                sprite_screen_y - BMP_FIREBALL_HEIGHT / 2 / transform.y,
+                fireball,
+                fireball_mask,
+                BMP_FIREBALL_WIDTH,
+                BMP_FIREBALL_HEIGHT,
+                0,
+                transform.y,
+                canvas);
+            break;
+        }
+
+        case E_MEDIKIT: {
+            drawSprite(
+                sprite_screen_x - BMP_ITEMS_WIDTH / 2 / transform.y,
+                sprite_screen_y + 5 / transform.y,
+                item,
+                item_mask,
+                BMP_ITEMS_WIDTH,
+                BMP_ITEMS_HEIGHT,
+                0,
+                transform.y,
+                canvas);
+            break;
+        }
+
+        case E_KEY: {
+            drawSprite(
+                sprite_screen_x - BMP_ITEMS_WIDTH / 2 / transform.y,
+                sprite_screen_y + 5 / transform.y,
+                item,
+                item_mask,
+                BMP_ITEMS_WIDTH,
+                BMP_ITEMS_HEIGHT,
+                1,
+                transform.y,
+                canvas);
+            break;
+        }
+        }
+    }
+}
+
+void renderGun(uint8_t gun_pos, double amount_jogging, Canvas* const canvas) {
+    // jogging
+    char x = 48 + sin((double)furi_get_tick() * (double)JOGGING_SPEED) * 10 * amount_jogging;
+    char y = RENDER_HEIGHT - gun_pos +
+             fabs(cos((double)furi_get_tick() * (double)JOGGING_SPEED)) * 8 * amount_jogging;
+
+    if(gun_pos > GUN_SHOT_POS - 2) {
+        // Gun fire
+        drawBitmap(x + 6, y - 11, &I_fire_inv, BMP_FIRE_WIDTH, BMP_FIRE_HEIGHT, 1, canvas);
+    }
+
+    // Don't draw over the hud!
+    uint8_t clip_height = fmax(0, fmin(y + BMP_GUN_HEIGHT, RENDER_HEIGHT) - y);
+
+    // Draw the gun (black mask + actual sprite).
+    drawBitmap(x, y, &I_gun_mask_inv, BMP_GUN_WIDTH, clip_height, 0, canvas);
+    drawBitmap(x, y, &I_gun_inv, BMP_GUN_WIDTH, clip_height, 1, canvas);
+    //drawGun(x,y,gun_mask, BMP_GUN_WIDTH, clip_height, 0, canvas);
+    //drawGun(x,y,gun, BMP_GUN_WIDTH, clip_height, 1, canvas);
+}
+
+// Only needed first time
+void renderHud(Canvas* const canvas, PluginState* plugin_state) {
+    drawTextSpace(2, 58, "{}", 0, canvas); // Health symbol
+    drawTextSpace(40, 58, "[]", 0, canvas); // Keys symbol
+    updateHud(canvas, plugin_state);
+}
+
+// Render values for the HUD
+void updateHud(Canvas* const canvas, PluginState* plugin_state) {
+    clearRect(12, 58, 15, 6, canvas);
+    clearRect(50, 58, 15, 6, canvas);
+    drawText(12, 58, plugin_state->player.health, canvas);
+    drawText(50, 58, plugin_state->player.keys, canvas);
+}
+
+// Debug stats
+void renderStats(Canvas* const canvas, PluginState* plugin_state) {
+    clearRect(58, 58, 70, 6, canvas);
+    drawText(114, 58, (int)getActualFps(), canvas);
+    drawText(82, 58, plugin_state->num_entities, canvas);
+    // drawText(94, 58, freeMemory());
+}
+
+// Intro screen
+void loopIntro(Canvas* const canvas) {
+    canvas_draw_icon(canvas, 0, 0, &I_logo_inv);
+    //drawTextSpace(SCREEN_WIDTH / 2 - 25, SCREEN_HEIGHT * .8, "PRESS FIRE", 1, canvas);
+}
+
+static void render_callback(Canvas* const canvas, void* ctx) {
+    furi_assert(ctx);
+    PluginState* plugin_state = ctx;
+    furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
+
+    canvas_set_font(canvas, FontPrimary);
+
+    switch(plugin_state->scene) {
+    case INTRO: {
+        loopIntro(canvas);
+        break;
+    }
+    case GAME_PLAY: {
+        updateEntities(sto_level_1, canvas, plugin_state);
+
+        renderGun(plugin_state->gun_pos, plugin_state->jogging, canvas);
+        renderMap(sto_level_1, plugin_state->view_height, canvas, plugin_state);
+
+        renderEntities(plugin_state->view_height, canvas, plugin_state);
+
+        renderHud(canvas, plugin_state);
+        updateHud(canvas, plugin_state);
+        renderStats(canvas, plugin_state);
+        break;
+    }
+    }
+    furi_mutex_release(plugin_state->mutex);
+}
+
+static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+    furi_assert(event_queue);
+
+    PluginEvent event = {.type = EventTypeKey, .input = *input_event};
+    furi_message_queue_put(event_queue, &event, 0);
+}
+
+static void doom_state_init(PluginState* const plugin_state) {
+    plugin_state->notify = furi_record_open(RECORD_NOTIFICATION);
+    plugin_state->num_entities = 0;
+    plugin_state->num_static_entities = 0;
+
+    plugin_state->scene = INTRO;
+    plugin_state->gun_pos = 0;
+    plugin_state->view_height = 0;
+    plugin_state->init = true;
+
+    plugin_state->up = false;
+    plugin_state->down = false;
+    plugin_state->left = false;
+    plugin_state->right = false;
+    plugin_state->fired = false;
+    plugin_state->gun_fired = false;
+#ifdef SOUND
+
+    plugin_state->music_instance = malloc(sizeof(MusicPlayer));
+    plugin_state->music_instance->model = malloc(sizeof(MusicPlayerModel));
+    memset(
+        plugin_state->music_instance->model->duration_history,
+        0xff,
+        MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
+    memset(
+        plugin_state->music_instance->model->semitone_history,
+        0xff,
+        MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
+    plugin_state->music_instance->model->volume = 2;
+
+    plugin_state->music_instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    //plugin_state->music_instance->view_port = view_port_alloc();
+
+    plugin_state->music_instance->worker = music_player_worker_alloc();
+    //music_player_worker_set_volume(plugin_state->music_instance->worker, 0.75);
+    music_player_worker_set_volume(
+        plugin_state->music_instance->worker,
+        MUSIC_PLAYER_VOLUMES[plugin_state->music_instance->model->volume]);
+    plugin_state->intro_sound = true;
+    //init_sound(plugin_state->music_instance);
+#endif
+}
+
+static void doom_game_update_timer_callback(FuriMessageQueue* event_queue) {
+    furi_assert(event_queue);
+
+    PluginEvent event = {.type = EventTypeTick};
+    furi_message_queue_put(event_queue, &event, 0);
+}
+
+static void doom_game_tick(PluginState* const plugin_state) {
+    if(plugin_state->scene == GAME_PLAY) {
+        //fps();
+        //player is alive
+        if(plugin_state->player.health > 0) {
+            if(plugin_state->up) {
+                plugin_state->player.velocity +=
+                    ((double)MOV_SPEED - plugin_state->player.velocity) * (double).4;
+                plugin_state->jogging = fabs(plugin_state->player.velocity) * MOV_SPEED_INV;
+                //plugin_state->up = false;
+            } else if(plugin_state->down) {
+                plugin_state->player.velocity +=
+                    (-(double)MOV_SPEED - plugin_state->player.velocity) * (double).4;
+                plugin_state->jogging = fabs(plugin_state->player.velocity) * MOV_SPEED_INV;
+                //plugin_state->down = false;
+            } else {
+                plugin_state->player.velocity *= (double).5;
+                plugin_state->jogging = fabs(plugin_state->player.velocity) * MOV_SPEED_INV;
+            }
+
+            if(plugin_state->right) {
+                plugin_state->rot_speed = (double)ROT_SPEED * delta;
+                plugin_state->old_dir_x = plugin_state->player.dir.x;
+                plugin_state->player.dir.x =
+                    plugin_state->player.dir.x * cos(-(plugin_state->rot_speed)) -
+                    plugin_state->player.dir.y * sin(-(plugin_state->rot_speed));
+                plugin_state->player.dir.y =
+                    plugin_state->old_dir_x * sin(-(plugin_state->rot_speed)) +
+                    plugin_state->player.dir.y * cos(-(plugin_state->rot_speed));
+                plugin_state->old_plane_x = plugin_state->player.plane.x;
+                plugin_state->player.plane.x =
+                    plugin_state->player.plane.x * cos(-(plugin_state->rot_speed)) -
+                    plugin_state->player.plane.y * sin(-(plugin_state->rot_speed));
+                plugin_state->player.plane.y =
+                    plugin_state->old_plane_x * sin(-(plugin_state->rot_speed)) +
+                    plugin_state->player.plane.y * cos(-(plugin_state->rot_speed));
+
+                //plugin_state->right = false;
+            } else if(plugin_state->left) {
+                plugin_state->rot_speed = (double)ROT_SPEED * delta;
+                plugin_state->old_dir_x = plugin_state->player.dir.x;
+                plugin_state->player.dir.x =
+                    plugin_state->player.dir.x * cos(plugin_state->rot_speed) -
+                    plugin_state->player.dir.y * sin(plugin_state->rot_speed);
+                plugin_state->player.dir.y =
+                    plugin_state->old_dir_x * sin(plugin_state->rot_speed) +
+                    plugin_state->player.dir.y * cos(plugin_state->rot_speed);
+                plugin_state->old_plane_x = plugin_state->player.plane.x;
+                plugin_state->player.plane.x =
+                    plugin_state->player.plane.x * cos(plugin_state->rot_speed) -
+                    plugin_state->player.plane.y * sin(plugin_state->rot_speed);
+                plugin_state->player.plane.y =
+                    plugin_state->old_plane_x * sin(plugin_state->rot_speed) +
+                    plugin_state->player.plane.y * cos(plugin_state->rot_speed);
+                //plugin_state->left = false;
+            }
+            plugin_state->view_height =
+                fabs(sin((double)furi_get_tick() * (double)JOGGING_SPEED)) * 6 *
+                plugin_state->jogging;
+
+            if(plugin_state->gun_pos > GUN_TARGET_POS) {
+                // Right after fire
+                plugin_state->gun_pos -= 1;
+            } else if(plugin_state->gun_pos < GUN_TARGET_POS) {
+                plugin_state->gun_pos += 2;
+            } else if(!plugin_state->gun_fired && plugin_state->fired) {
+                //furi_hal_speaker_start(20480 / 10, 0.45f);
+                /*#ifdef SOUND
+        music_player_worker_start(plugin_state->music_instance->worker);
+#endif*/
+                plugin_state->gun_pos = GUN_SHOT_POS;
+                plugin_state->gun_fired = true;
+                plugin_state->fired = false;
+                fire(plugin_state);
+
+            } else if(plugin_state->gun_fired && !plugin_state->fired) {
+                //furi_hal_speaker_stop();
+                plugin_state->gun_fired = false;
+
+                notification_message(plugin_state->notify, &sequence_short_sound);
+
+                /*#ifdef SOUND
+        music_player_worker_stop(plugin_state->music_instance->worker);
+#endif*/
+            }
+        } else {
+            // Player is dead
+            if(plugin_state->view_height > -10) plugin_state->view_height--;
+            if(plugin_state->gun_pos > 1) plugin_state->gun_pos -= 2;
+        }
+
+        if(fabs(plugin_state->player.velocity) > (double)0.003) {
+            updatePosition(
+                sto_level_1,
+                &(plugin_state->player.pos),
+                plugin_state->player.dir.x * plugin_state->player.velocity * delta,
+                plugin_state->player.dir.y * plugin_state->player.velocity * delta,
+                false,
+                plugin_state);
+        } else {
+            plugin_state->player.velocity = 0;
+        }
+    }
+}
+
+int32_t doom_app() {
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
+    PluginState* plugin_state = malloc(sizeof(PluginState));
+    doom_state_init(plugin_state);
+    plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if(!plugin_state->mutex) {
+        FURI_LOG_E("Doom_game", "cannot create mutex\r\n");
+        furi_record_close(RECORD_NOTIFICATION);
+        furi_message_queue_free(event_queue);
+        free(plugin_state);
+        return 255;
+    }
+    FuriTimer* timer =
+        furi_timer_alloc(doom_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
+    furi_timer_start(timer, furi_kernel_get_tick_frequency() / 12);
+    // Set system callbacks
+    ViewPort* view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, render_callback, plugin_state);
+    view_port_input_callback_set(view_port, input_callback, event_queue);
+
+    // Open GUI and register view_port
+    Gui* gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    //////////////////////////////////
+    plugin_state->init = false;
+
+    PluginEvent event;
+#ifdef SOUND
+    music_player_worker_load_rtttl_from_string(plugin_state->music_instance->worker, dsintro);
+    music_player_worker_start(plugin_state->music_instance->worker);
+#endif
+    // Call dolphin deed on game start
+    dolphin_deed(DolphinDeedPluginGameStart);
+
+    for(bool processing = true; processing;) {
+        FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+        furi_mutex_acquire(plugin_state->mutex, FuriWaitForever);
+#ifdef SOUND
+        furi_check(
+            furi_mutex_acquire(plugin_state->music_instance->model_mutex, FuriWaitForever) ==
+            FuriStatusOk);
+#endif
+        if(event_status == FuriStatusOk) {
+            // press events
+            if(event.type == EventTypeKey) {
+                if(event.input.key == InputKeyBack) {
+                    processing = false;
+#ifdef SOUND
+                    if(plugin_state->intro_sound) {
+                        furi_mutex_release(plugin_state->music_instance->model_mutex);
+                        music_player_worker_stop(plugin_state->music_instance->worker);
+                    }
+#endif
+                }
+
+                if(event.input.type == InputTypePress) {
+                    if(plugin_state->scene == INTRO && event.input.key == InputKeyOk) {
+                        plugin_state->scene = GAME_PLAY;
+                        initializeLevel(sto_level_1, plugin_state);
+#ifdef SOUND
+                        furi_mutex_release(plugin_state->music_instance->model_mutex);
+                        music_player_worker_stop(plugin_state->music_instance->worker);
+                        plugin_state->intro_sound = false;
+#endif
+                        goto skipintro;
+                    }
+
+                    //While playing game
+                    if(plugin_state->scene == GAME_PLAY) {
+                        // If the player is alive
+                        if(plugin_state->player.health > 0) {
+                            //Player speed
+                            if(event.input.key == InputKeyUp) {
+                                plugin_state->up = true;
+                            } else if(event.input.key == InputKeyDown) {
+                                plugin_state->down = true;
+                            }
+                            // Player rotation
+                            if(event.input.key == InputKeyRight) {
+                                plugin_state->right = true;
+                            } else if(event.input.key == InputKeyLeft) {
+                                plugin_state->left = true;
+                            }
+                            if(event.input.key == InputKeyOk) {
+                                /*#ifdef SOUND
+                        music_player_worker_load_rtttl_from_string(plugin_state->music_instance->worker, dspistol);
+#endif*/
+                                if(plugin_state->fired) {
+                                    plugin_state->fired = false;
+                                } else {
+                                    plugin_state->fired = true;
+                                }
+                            }
+                        } else {
+                            // Player is dead
+                            if(event.input.key == InputKeyOk) plugin_state->scene = INTRO;
+                        }
+                    }
+                }
+                if(event.input.type == InputTypeRelease) {
+                    if(plugin_state->player.health > 0) {
+                        //Player speed
+                        if(event.input.key == InputKeyUp) {
+                            plugin_state->up = false;
+                        } else if(event.input.key == InputKeyDown) {
+                            plugin_state->down = false;
+                        }
+                        // Player rotation
+                        if(event.input.key == InputKeyRight) {
+                            plugin_state->right = false;
+                        } else if(event.input.key == InputKeyLeft) {
+                            plugin_state->left = false;
+                        }
+                    }
+                }
+            }
+
+        skipintro:
+            if(event.type == EventTypeTick) {
+                doom_game_tick(plugin_state);
+            }
+        }
+#ifdef SOUND
+        furi_mutex_release(plugin_state->music_instance->model_mutex);
+#endif
+
+        furi_mutex_release(plugin_state->mutex);
+        view_port_update(view_port);
+    }
+#ifdef SOUND
+    music_player_worker_free(plugin_state->music_instance->worker);
+    furi_mutex_free(plugin_state->music_instance->model_mutex);
+    free(plugin_state->music_instance->model);
+    free(plugin_state->music_instance);
+#endif
+    furi_record_close(RECORD_NOTIFICATION);
+    furi_timer_free(timer);
+    view_port_enabled_set(view_port, false);
+    gui_remove_view_port(gui, view_port);
+    furi_record_close(RECORD_GUI);
+    view_port_free(view_port);
+    furi_mutex_free(plugin_state->mutex);
+    furi_message_queue_free(event_queue);
+    free(plugin_state);
+    return 0;
+}

BIN
doom/doom_10px.png


+ 504 - 0
doom/doom_music_player_worker.c

@@ -0,0 +1,504 @@
+#include "doom_music_player_worker.h"
+
+#include <furi_hal.h>
+#include <furi.h>
+
+#include <storage/storage.h>
+#include <lib/flipper_format/flipper_format.h>
+
+#include <m-array.h>
+
+#define TAG "MusicPlayerWorker"
+
+#define MUSIC_PLAYER_FILETYPE "Flipper Music Format"
+#define MUSIC_PLAYER_VERSION 0
+
+#define SEMITONE_PAUSE 0xFF
+
+#define NOTE_C4 261.63f
+#define NOTE_C4_SEMITONE (4.0f * 12.0f)
+#define TWO_POW_TWELTH_ROOT 1.059463094359f
+
+typedef struct {
+    uint8_t semitone;
+    uint8_t duration;
+    uint8_t dots;
+} NoteBlock;
+
+ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);
+
+struct MusicPlayerWorker {
+    FuriThread* thread;
+    bool should_work;
+
+    MusicPlayerWorkerCallback callback;
+    void* callback_context;
+
+    float volume;
+    uint32_t bpm;
+    uint32_t duration;
+    uint32_t octave;
+    NoteBlockArray_t notes;
+};
+
+static int32_t music_player_worker_thread_callback(void* context) {
+    furi_assert(context);
+    MusicPlayerWorker* instance = context;
+
+    NoteBlockArray_it_t it;
+    NoteBlockArray_it(it, instance->notes);
+    if(furi_hal_speaker_acquire(1000)) {
+        while(instance->should_work) {
+            if(NoteBlockArray_end_p(it)) {
+                NoteBlockArray_it(it, instance->notes);
+                furi_delay_ms(10);
+            } else {
+                NoteBlock* note_block = NoteBlockArray_ref(it);
+
+                float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
+                float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
+                float duration = 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm /
+                                 note_block->duration;
+                uint32_t dots = note_block->dots;
+                while(dots > 0) {
+                    duration += duration / 2;
+                    dots--;
+                }
+                uint32_t next_tick = furi_get_tick() + duration;
+                float volume = instance->volume;
+
+                if(instance->callback) {
+                    instance->callback(
+                        note_block->semitone,
+                        note_block->dots,
+                        note_block->duration,
+                        0.0,
+                        instance->callback_context);
+                }
+
+                furi_hal_speaker_stop();
+                furi_hal_speaker_start(frequency, volume);
+                while(instance->should_work && furi_get_tick() < next_tick) {
+                    volume *= 0.9945679;
+                    furi_hal_speaker_set_volume(volume);
+                    furi_delay_ms(2);
+                }
+                NoteBlockArray_next(it);
+            }
+        }
+
+        furi_hal_speaker_stop();
+        furi_hal_speaker_release();
+    } else {
+        FURI_LOG_E(TAG, "Speaker system is busy with another process.");
+    }
+
+    return 0;
+}
+
+MusicPlayerWorker* music_player_worker_alloc() {
+    MusicPlayerWorker* instance = malloc(sizeof(MusicPlayerWorker));
+
+    NoteBlockArray_init(instance->notes);
+
+    instance->thread = furi_thread_alloc();
+    furi_thread_set_name(instance->thread, "MusicPlayerWorker");
+    furi_thread_set_stack_size(instance->thread, 1024);
+    furi_thread_set_context(instance->thread, instance);
+    furi_thread_set_callback(instance->thread, music_player_worker_thread_callback);
+
+    instance->volume = 1.0f;
+
+    return instance;
+}
+
+void music_player_worker_free(MusicPlayerWorker* instance) {
+    furi_assert(instance);
+    furi_thread_free(instance->thread);
+    NoteBlockArray_clear(instance->notes);
+    free(instance);
+}
+
+static bool is_digit(const char c) {
+    return isdigit(c) != 0;
+}
+
+static bool is_letter(const char c) {
+    return islower(c) != 0 || isupper(c) != 0;
+}
+
+static bool is_space(const char c) {
+    return c == ' ' || c == '\t';
+}
+
+static size_t extract_number(const char* string, uint32_t* number) {
+    size_t ret = 0;
+    while(is_digit(*string)) {
+        *number *= 10;
+        *number += (*string - '0');
+        string++;
+        ret++;
+    }
+    return ret;
+}
+
+static size_t extract_dots(const char* string, uint32_t* number) {
+    size_t ret = 0;
+    while(*string == '.') {
+        *number += 1;
+        string++;
+        ret++;
+    }
+    return ret;
+}
+
+static size_t extract_char(const char* string, char* symbol) {
+    if(is_letter(*string)) {
+        *symbol = *string;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+static size_t extract_sharp(const char* string, char* symbol) {
+    if(*string == '#' || *string == '_') {
+        *symbol = '#';
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+static size_t skip_till(const char* string, const char symbol) {
+    size_t ret = 0;
+    while(*string != '\0' && *string != symbol) {
+        string++;
+        ret++;
+    }
+    if(*string != symbol) {
+        ret = 0;
+    }
+    return ret;
+}
+
+static bool music_player_worker_add_note(
+    MusicPlayerWorker* instance,
+    uint8_t semitone,
+    uint8_t duration,
+    uint8_t dots) {
+    NoteBlock note_block;
+
+    note_block.semitone = semitone;
+    note_block.duration = duration;
+    note_block.dots = dots;
+
+    NoteBlockArray_push_back(instance->notes, note_block);
+
+    return true;
+}
+
+static int8_t note_to_semitone(const char note) {
+    switch(note) {
+    case 'C':
+        return 0;
+    // C#
+    case 'D':
+        return 2;
+    // D#
+    case 'E':
+        return 4;
+    case 'F':
+        return 5;
+    // F#
+    case 'G':
+        return 7;
+    // G#
+    case 'A':
+        return 9;
+    // A#
+    case 'B':
+        return 11;
+    default:
+        return 0;
+    }
+}
+
+static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const char* string) {
+    const char* cursor = string;
+    bool result = true;
+
+    while(*cursor != '\0') {
+        if(!is_space(*cursor)) {
+            uint32_t duration = 0;
+            char note_char = '\0';
+            char sharp_char = '\0';
+            uint32_t octave = 0;
+            uint32_t dots = 0;
+
+            // Parsing
+            cursor += extract_number(cursor, &duration);
+            cursor += extract_char(cursor, &note_char);
+            cursor += extract_sharp(cursor, &sharp_char);
+            cursor += extract_number(cursor, &octave);
+            cursor += extract_dots(cursor, &dots);
+
+            // Post processing
+            note_char = toupper(note_char);
+            if(!duration) {
+                duration = instance->duration;
+            }
+            if(!octave) {
+                octave = instance->octave;
+            }
+
+            // Validation
+            bool is_valid = true;
+            is_valid &= (duration >= 1 && duration <= 128);
+            is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P');
+            is_valid &= (sharp_char == '#' || sharp_char == '\0');
+            is_valid &= (octave <= 16);
+            is_valid &= (dots <= 16);
+            if(!is_valid) {
+                FURI_LOG_E(
+                    TAG,
+                    "Invalid note: %lu%c%c%lu.%lu",
+                    duration,
+                    note_char == '\0' ? '_' : note_char,
+                    sharp_char == '\0' ? '_' : sharp_char,
+                    octave,
+                    dots);
+                result = false;
+                break;
+            }
+
+            // Note to semitones
+            uint8_t semitone = 0;
+            if(note_char == 'P') {
+                semitone = SEMITONE_PAUSE;
+            } else {
+                semitone += octave * 12;
+                semitone += note_to_semitone(note_char);
+                semitone += sharp_char == '#' ? 1 : 0;
+            }
+
+            if(music_player_worker_add_note(instance, semitone, duration, dots)) {
+                FURI_LOG_D(
+                    TAG,
+                    "Added note: %c%c%lu.%lu = %u %lu",
+                    note_char == '\0' ? '_' : note_char,
+                    sharp_char == '\0' ? '_' : sharp_char,
+                    octave,
+                    dots,
+                    semitone,
+                    duration);
+            } else {
+                FURI_LOG_E(
+                    TAG,
+                    "Invalid note: %c%c%lu.%lu = %u %lu",
+                    note_char == '\0' ? '_' : note_char,
+                    sharp_char == '\0' ? '_' : sharp_char,
+                    octave,
+                    dots,
+                    semitone,
+                    duration);
+            }
+            cursor += skip_till(cursor, ',');
+        }
+
+        if(*cursor != '\0') cursor++;
+    }
+
+    return result;
+}
+
+bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_assert(file_path);
+
+    bool ret = false;
+    if(strcasestr(file_path, ".fmf")) {
+        ret = music_player_worker_load_fmf_from_file(instance, file_path);
+    } else {
+        ret = music_player_worker_load_rtttl_from_file(instance, file_path);
+    }
+    return ret;
+}
+
+bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_assert(file_path);
+
+    bool result = false;
+    FuriString* temp_str;
+    temp_str = furi_string_alloc();
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* file = flipper_format_file_alloc(storage);
+
+    do {
+        if(!flipper_format_file_open_existing(file, file_path)) break;
+
+        uint32_t version = 0;
+        if(!flipper_format_read_header(file, temp_str, &version)) break;
+        if(furi_string_cmp_str(temp_str, MUSIC_PLAYER_FILETYPE) ||
+           (version != MUSIC_PLAYER_VERSION)) {
+            FURI_LOG_E(TAG, "Incorrect file format or version");
+            break;
+        }
+
+        if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) {
+            FURI_LOG_E(TAG, "BPM is missing");
+            break;
+        }
+        if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) {
+            FURI_LOG_E(TAG, "Duration is missing");
+            break;
+        }
+        if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) {
+            FURI_LOG_E(TAG, "Octave is missing");
+            break;
+        }
+
+        if(!flipper_format_read_string(file, "Notes", temp_str)) {
+            FURI_LOG_E(TAG, "Notes is missing");
+            break;
+        }
+
+        if(!music_player_worker_parse_notes(instance, furi_string_get_cstr(temp_str))) {
+            break;
+        }
+
+        result = true;
+    } while(false);
+
+    furi_record_close(RECORD_STORAGE);
+    flipper_format_free(file);
+    furi_string_free(temp_str);
+
+    return result;
+}
+
+bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_assert(file_path);
+
+    bool result = false;
+    FuriString* content;
+    content = furi_string_alloc();
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    File* file = storage_file_alloc(storage);
+
+    do {
+        if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+            FURI_LOG_E(TAG, "Unable to open file");
+            break;
+        };
+
+        uint16_t ret = 0;
+        do {
+            uint8_t buffer[65] = {0};
+            ret = storage_file_read(file, buffer, sizeof(buffer) - 1);
+            for(size_t i = 0; i < ret; i++) {
+                furi_string_push_back(content, buffer[i]);
+            }
+        } while(ret > 0);
+
+        furi_string_trim(content);
+        if(!furi_string_size(content)) {
+            FURI_LOG_E(TAG, "Empty file");
+            break;
+        }
+
+        if(!music_player_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) {
+            FURI_LOG_E(TAG, "Invalid file content");
+            break;
+        }
+
+        result = true;
+    } while(0);
+
+    storage_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+    furi_string_free(content);
+
+    return result;
+}
+
+bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string) {
+    furi_assert(instance);
+
+    const char* cursor = string;
+
+    // Skip name
+    cursor += skip_till(cursor, ':');
+    if(*cursor != ':') {
+        return false;
+    }
+
+    // Duration
+    cursor += skip_till(cursor, '=');
+    if(*cursor != '=') {
+        return false;
+    }
+    cursor++;
+    cursor += extract_number(cursor, &instance->duration);
+
+    // Octave
+    cursor += skip_till(cursor, '=');
+    if(*cursor != '=') {
+        return false;
+    }
+    cursor++;
+    cursor += extract_number(cursor, &instance->octave);
+
+    // BPM
+    cursor += skip_till(cursor, '=');
+    if(*cursor != '=') {
+        return false;
+    }
+    cursor++;
+    cursor += extract_number(cursor, &instance->bpm);
+
+    // Notes
+    cursor += skip_till(cursor, ':');
+    if(*cursor != ':') {
+        return false;
+    }
+    cursor++;
+    if(!music_player_worker_parse_notes(instance, cursor)) {
+        return false;
+    }
+
+    return true;
+}
+
+void music_player_worker_set_callback(
+    MusicPlayerWorker* instance,
+    MusicPlayerWorkerCallback callback,
+    void* context) {
+    furi_assert(instance);
+    instance->callback = callback;
+    instance->callback_context = context;
+}
+
+void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume) {
+    furi_assert(instance);
+    instance->volume = volume;
+}
+
+void music_player_worker_start(MusicPlayerWorker* instance) {
+    furi_assert(instance);
+    furi_assert(instance->should_work == false);
+
+    instance->should_work = true;
+    furi_thread_start(instance->thread);
+}
+
+void music_player_worker_stop(MusicPlayerWorker* instance) {
+    furi_assert(instance);
+    furi_assert(instance->should_work == true);
+
+    instance->should_work = false;
+    furi_thread_join(instance->thread);
+}

+ 44 - 0
doom/doom_music_player_worker.h

@@ -0,0 +1,44 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*MusicPlayerWorkerCallback)(
+    uint8_t semitone,
+    uint8_t dots,
+    uint8_t duration,
+    float position,
+    void* context);
+
+typedef struct MusicPlayerWorker MusicPlayerWorker;
+
+MusicPlayerWorker* music_player_worker_alloc();
+
+void music_player_worker_free(MusicPlayerWorker* instance);
+
+bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path);
+
+bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path);
+
+bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path);
+
+bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string);
+
+void music_player_worker_set_callback(
+    MusicPlayerWorker* instance,
+    MusicPlayerWorkerCallback callback,
+    void* context);
+
+void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume);
+
+void music_player_worker_start(MusicPlayerWorker* instance);
+
+void music_player_worker_stop(MusicPlayerWorker* instance);
+
+#ifdef __cplusplus
+}
+#endif

+ 42 - 0
doom/entities.c

@@ -0,0 +1,42 @@
+#include "entities.h"
+
+//extern "C"
+/*Player create_player(double x, double y){
+	return {create_coords((double) x + (double)0.5, (double) y + (double)0.5), create_coords(1, 0), create_coords(0, -0.66), 0, 100, 0};
+}*/
+
+Player create_player(double x, double y) {
+    Player p;
+    p.pos = create_coords((double)x + (double)0.5, (double)y + (double)0.5);
+    p.dir = create_coords(1, 0);
+    p.plane = create_coords(0, -0.66);
+    p.velocity = 0;
+    p.health = 100;
+    p.keys = 0;
+    return p; //{create_coords((double) x + (double)0.5, (double) y + (double)0.5), create_coords(1, 0), create_coords(0, -0.66), 0, 100, 0};
+}
+
+//extern "C"
+Entity
+    create_entity(uint8_t type, uint8_t x, uint8_t y, uint8_t initialState, uint8_t initialHealth) {
+    UID uid = create_uid(type, x, y);
+    Coords pos = create_coords((double)x + (double).5, (double)y + (double).5);
+    Entity new_entity; // = { uid, pos, initialState, initialHealth, 0, 0 };
+    new_entity.uid = uid;
+    new_entity.pos = pos;
+    new_entity.state = initialState;
+    new_entity.health = initialHealth;
+    new_entity.distance = 0;
+    new_entity.timer = 0;
+    return new_entity;
+}
+
+//extern "C"
+StaticEntity crate_static_entity(UID uid, uint8_t x, uint8_t y, bool active) {
+    StaticEntity ent;
+    ent.uid = uid;
+    ent.x = x;
+    ent.y = y;
+    ent.active = active;
+    return ent;
+}

+ 56 - 0
doom/entities.h

@@ -0,0 +1,56 @@
+#ifndef _entities_h
+#define _entities_h
+#include <stdint.h>
+#include <stdbool.h>
+#include "types.h"
+
+// Shortcuts
+//#define create_player(x, y)   {create_coords((double) x + (double)0.5, (double) y + (double)0.5), create_coords(1, 0), create_coords(0, -0.66), 0, 100}
+
+#define create_enemy(x, y) create_entity(E_ENEMY, x, y, S_STAND, 50)
+#define create_medikit(x, y) create_entity(E_MEDIKIT, x, y, S_STAND, 0)
+#define create_key(x, y) create_entity(E_KEY, x, y, S_STAND, 0)
+#define create_fireball(x, y, dir) create_entity(E_FIREBALL, x, y, S_STAND, dir)
+#define create_door(x, y) create_entity(E_DOOR, x, y, S_STAND, 0)
+
+// entity statuses
+#define S_STAND 0
+#define S_ALERT 1
+#define S_FIRING 2
+#define S_MELEE 3
+#define S_HIT 4
+#define S_DEAD 5
+#define S_HIDDEN 6
+#define S_OPEN 7
+#define S_CLOSE 8
+
+typedef struct Player {
+    Coords pos;
+    Coords dir;
+    Coords plane;
+    double velocity;
+    uint8_t health;
+    uint8_t keys;
+} Player;
+
+typedef struct Entity {
+    UID uid;
+    Coords pos;
+    uint8_t state;
+    uint8_t health; // angle for fireballs
+    uint8_t distance;
+    uint8_t timer;
+} Entity;
+
+typedef struct StaticEntity {
+    UID uid;
+    uint8_t x;
+    uint8_t y;
+    bool active;
+} StaticEntity;
+
+Entity
+    create_entity(uint8_t type, uint8_t x, uint8_t y, uint8_t initialState, uint8_t initialHealth);
+StaticEntity create_static_entity(UID uid, uint8_t x, uint8_t y, bool active);
+Player create_player(double x, double y);
+#endif

BIN
doom/img/1.png


BIN
doom/img/2.png


BIN
doom/img/3.png


+ 188 - 0
doom/level.h

@@ -0,0 +1,188 @@
+#ifndef _level_h
+#define _level_h
+
+#include "constants.h"
+
+/*
+  Based on E1M1 from Wolfenstein 3D
+
+  ################################################################
+  #############################...........########################
+  ######....###################........E..########################
+  ######....########..........#...........#...####################
+  ######.....#######..........L.....E.......M.####################
+  ######.....#######..........#...........#...####################
+  ##################...########...........########################
+  ######.........###...########...........########################
+  ######.........###...#############D#############################
+  ######.........#......E##########...############################
+  ######....E....D...E...##########...############################
+  ######.........#.......##########...############################
+  ######....E....##################...############################
+  #...##.........##################...############################
+  #.K.######D######################...############################
+  #...#####...###############...#E.....K##########################
+  ##D######...###############..####...############################
+  #...#####...###############..####...############################
+  #...#...#...###############..####...############################
+  #...D...#...#####################...############################
+  #...#...#...#####################...############################
+  #...######D#######################L#############################
+  #.E.##.........#################.....#################........##
+  #...##.........############...............############........##
+  #...##...E.....############...............############........##
+  #....#.........############...E.......E....#.........#........##
+  #....L....K....############................D....E....D....E...##
+  #....#.........############................#.........#........##
+  #...##.....E...############...............####....####........##
+  #...##.........############...............#####..#####.....M..##
+  #...##.........#################.....##########..#####........##
+  #...######L#######################D############..###############
+  #...#####...#####################...###########..###############
+  #E.E#####...#####################...###########..###############
+  #...#...#...#####################.E.###########..###############
+  #...D.M.#...#####################...###########..###############
+  #...#...#...#####################...###########..###.#.#.#.#####
+  #...#####...#####################...###########...#.........####
+  #...#####...#####################...###########...D....E..K.####
+  #................##......########...###########...#.........####
+  #....E........E...L...E...X######...################.#.#.#.#####
+  #................##......########...############################
+  #################################...############################
+  #############..#..#..#############L#############################
+  ###########....#..#.########....#...#....#######################
+  #############.....##########.P..D...D....#######################
+  ############################....#...#....#######################
+  ##############..#################...############################
+  ##############..############....#...#....#######################
+  ############################....D...D....#######################
+  ############################....#...#....#######################
+  #################################...############################
+  ############################.............#######################
+  ############################..........EK.#######################
+  ############################.............#######################
+  ################################################################
+*/
+
+/*
+   Same map above built from some regexp replacements using the legend above.
+   Using this way lets me use only 4 bit to store each block
+*/
+const uint8_t sto_level_1[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,
+};
+
+#endif

+ 34 - 0
doom/sound.h

@@ -0,0 +1,34 @@
+#ifndef sound_h
+#define sound_h
+#include <furi.h>
+#include <furi_hal.h>
+#include <stdint.h>
+#include "doom_music_player_worker.h"
+
+//static const char dspistol[] = "AnyConv:d=,o=,b=120:408,40p,40p,40p,40p,405,40p,40p,40p,405,30p.,30p.,30p.,13p";
+static const char dsintro[] =
+    "Doom:d=32,o=4,b=56:f,f,f5,f,f,d#5,f,f,c#5,f,f,b,f,f,c5,c#5,f,f,f5,f,f,d#5,f,f,c#5,f,f,8b.,f,f,f5,f,f,d#5,f,f,c#5,f,f,b,f,f,c5,c#5,f,f,f5,f,f,d#5,f,f,c#5,f,f,8b.,a#,a#,a#5,a#,a#,g#5,a#,a#,f#5,a#,a#,e5,a#,a#,f5,f#5,a#,a#,a#5,a#,a#,g#5,a#,a#,f#5,a#,a#,8e5";
+//static const char dsgetpow[] = "dsgetpow:d=,o=,b=120:407,40p,30.6,407,40p,406,40p,407,40p,40p,407,30p.,407";
+//static const char dsnoway[] = "dsnoway:d=,o=,b=120:407,30.4";
+
+#define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4
+static const float MUSIC_PLAYER_VOLUMES[] = {0, .25, .5, .75, 1};
+
+typedef struct {
+    uint8_t semitone_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE];
+    uint8_t duration_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE];
+
+    uint8_t volume;
+    uint8_t semitone;
+    uint8_t dots;
+    uint8_t duration;
+    float position;
+} MusicPlayerModel;
+
+typedef struct {
+    MusicPlayerModel* model;
+    MusicPlayerWorker* worker;
+    FuriMutex** model_mutex;
+} MusicPlayer;
+
+#endif

+ 33 - 0
doom/types.c

@@ -0,0 +1,33 @@
+#include "types.h"
+
+/*template <class T>
+inline T sq(T value) {
+    return value * value;
+}*/
+
+double sq(double val) {
+    return val * val;
+}
+
+//extern "C"
+Coords create_coords(double x, double y) {
+    Coords cord;
+    cord.x = x;
+    cord.y = y;
+    return cord;
+}
+
+//extern "C"
+uint8_t coords_distance(Coords* a, Coords* b) {
+    return sqrt(sq(a->x - b->x) + sq(a->y - b->y)) * 20;
+}
+
+//extern "C"
+UID create_uid(uint8_t type, uint8_t x, uint8_t y) {
+    return ((y << 6) | x) << 4 | type;
+}
+
+//extern "C"
+uint8_t uid_get_type(UID uid) {
+    return uid & 0x0F;
+}

+ 36 - 0
doom/types.h

@@ -0,0 +1,36 @@
+#ifndef _types_h
+#define _types_h
+
+#include <stdint.h>
+#include <math.h>
+//#include "constants.h"
+
+#define UID_null 0
+
+// Entity types (legend applies to level.h)
+#define E_FLOOR 0x0 // . (also null)
+#define E_WALL 0xF // #
+#define E_PLAYER 0x1 // P
+#define E_ENEMY 0x2 // E
+#define E_DOOR 0x4 // D
+#define E_LOCKEDDOOR 0x5 // L
+#define E_EXIT 0x7 // X
+// collectable entities >= 0x8
+#define E_MEDIKIT 0x8 // M
+#define E_KEY 0x9 // K
+#define E_FIREBALL 0xA // not in map
+
+typedef uint16_t UID;
+typedef uint8_t EType;
+
+typedef struct Coords {
+    double x;
+    double y;
+} Coords;
+
+UID create_uid(EType type, uint8_t x, uint8_t y);
+EType uid_get_type(UID uid);
+Coords create_coords(double x, double y);
+uint8_t coords_distance(Coords* a, Coords* b);
+
+#endif