diff --git a/.gitignore b/.gitignore
index 259148f..c3bc12c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,10 @@
*.exe
*.out
*.app
+
+# VsCode/PlatformIO
+.pio
+.vscode/.browse.c_cpp.db*
+.vscode/c_cpp_properties.json
+.vscode/launch.json
+.vscode/ipch
diff --git a/HowToMakeResources.md b/HowToMakeResources.md
new file mode 100644
index 0000000..7b92f49
--- /dev/null
+++ b/HowToMakeResources.md
@@ -0,0 +1,200 @@
+# How to make resources
+
+**Since we cannot release the resources due to rights issues,**
+we will provide information on how to make your own.
+**We do not accept questions about resources, please be patient.**
+
+## Overview
+Resources required in the game are loaded from the SD card (exclude font source file).
+Please place each resource in the designated location.
+
+## Bitmap font entity source file
+The df88_font entity must be prepared.
+Source will comile and put to PROGMEM.
+
+see also df88.hpp
+```
+extern const ::lgfx::GFXfont df88_gfx_font;
+```
+
+### Filename
+Source : df88.cpp
+Put source to ./src
+
+### Font source image
+ASCII CODE from 0x20 to 0x5F
+```
+ '"#$%&'()*+,-./0123456789:;<=>?©ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
+```
+### Remarks
+You can use [make_lgfxbmpfont.py](https://github.com/GOB52/bitmap_tools) to make source file from bitmap.
+
+### e.g.
+
+```C++
+#include
+#include
+#include
+#include "df88.hpp"
+
+static const std::uint8_t df88_bitmaps[] PROGMEM =
+{
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' '
+ 0x1c,......
+ .
+ .
+ .
+
+};
+static const lgfx::GFXglyph df88_glyphs[] PROGMEM =
+{
+ { 0, 8, 8, 8, 0, -8 }, //' '
+ { 8, 8, 8, 8, 0, -8 }, //'!'
+ .
+ .
+ .
+};
+
+const lgfx::GFXfont df88_font PROGMEM =
+{
+ (std::uint8_t*)df88_bitmaps,
+ (lgfx::GFXglyph*)df88_glyphs,
+ 0x20, // ' '
+ 0x5f, // '_'
+ 8,
+};
+```
+
+## Graphics resources
+
+### Format
+Windows bitmap(v3) 4bit depth 16 colors.
+Palette index 0 is transparent palette, so you can set 15 colors freely each bitmaps.
+
+### Location
+/res/td/
+
+### Remarks
+You can use [reorder_palette.py](https://github.com/GOB52/bitmap_tools) to sort the bitmap palette colors.
+
+### Elements
+
+#### Advertise
+##### Logo
+Filename: "logo.bmp"
+Width: 224 Height: 64
+![logo.bmp](./doc/logo_sample.png)
+
+#### Character
+##### My ship
+Player ship and bullet.
+Filename: "sh.bmp"
+Width: 88 Height: 32
+![sh.bmp](./doc/sh_sample.png)
+
+##### Enemy
+Mine, Yazuka, and Crash
+Filename: "enemy.bmp"
+Width: 96 Height: 48
+![enemy.bmp](./doc/enemy_sample.png)
+
+#### Background
+##### Wave and spot
+Filename: "bg0.bmp"
+Width: 64 Height: 160
+Wave cloud patern that horizontal direction must be connected bg0 anf bg1.
+No transparent.
+Those palette colors must exists in bitmap for animate palette.
+![#306040](https://via.placeholder.com/15/306040/000000?text=+) `RGB(48,96,64)`
+![#48A068](https://via.placeholder.com/15/48a068/000000?text=+) `RGB(72,160,104)`
+![#70E090](https://via.placeholder.com/15/70E090/000000?text=+) `RGB(112,224,144)`
+
+![bg0.bmp](./doc/bg0_sample.png)
+
+Filename: "bg1.bmp"
+Width: 96 Height: 160
+Great spot patern that horizontal direction must be connected bg0 and bg1.
+No transparent.
+Those palette colors must exists in bitmap for animate palette.
+![#306040](https://via.placeholder.com/15/306040/000000?text=+) `RGB(48,96,64)`
+![#48A068](https://via.placeholder.com/15/48a068/000000?text=+) `RGB(72,160,104)`
+![#70E090](https://via.placeholder.com/15/70E090/000000?text=+) `RGB(112,224,144)`
+
+![bg1.bmp](./doc/bg1_sample.png)
+
+##### Rock surface
+Upper/lower rock surfaces.
+Filename: "rock.bmp"
+Width: 320 Height: 16
+Rock surface that that horizontal direction must be connected.
+No transparent.
+![bg0.bmp](./doc/rock_sample.png)
+
+##### Branch rock
+Using when choose next stage.
+Filename: "branch.bmp"
+Width: 160 Height: 16
+Branch rock that that horizontal direction must be connected.
+![branch.bmp](./doc/branch_sample.png)
+
+#### Information
+##### Frame for zone,remaining
+For information.
+Filename: "zone.bmp"
+Width: 80 Height: 40
+No transparent.
+![zone.bmp](./doc/zone_sample.png)
+
+#### Effect
+##### Explosion
+Filename: "bomb.bmp"
+Width: 256 Height: 64
+![bomb.bmp](./doc/bomb_sample.png)
+
+#### Boss
+##### Coelacanth
+Body,parts and bullet.
+Filename: "kf.bmp"
+Width: 224 Height: 232
+Those palette colors must exists in bitmap.
+Palette of colors will be grayscaled when the boss appears, leaves, and escapes.
+![#00185a](https://via.placeholder.com/15/00185a/000000?text=+) `RGB(0,24,90)`
+![#005abd](https://via.placeholder.com/15/005abd/000000?text=+) `RGB(0,90,189)`
+![#00297b](https://via.placeholder.com/15/00297b/000000?text=+) `RGB(0,41,123)`
+
+![kf.bmp](./doc/kf_sample.png)
+
+
+## Sound resources
+
+### Format
+Wave file format, linear PCM, 8bit mono
+
+### Location
+
+/res/td/wav/
+
+### BGM
+
+|Title|Filename|
+|:---|:---|
+|Insert coin|IC.wav|
+|Insert coin B|ICB.wav|
+|Stage A,etc|CN.wav|
+|Stage F,etc|CMT.wav|
+|Stage C, etc|CAW.wav|
+|Stage B, etc|IB.wav|
+|Stage E,etc|TS.wav|
+|Warning|W!.wav|
+|Boss1|B1.wav|
+|Boss2|B2.wav|
+|Boss3|B3.wav|
+|Boss4|B4.wav|
+|Boss5|B5.wav|
+|Boss6|B6.wav|
+|Boss7|B7.wav|
+|Round clear|RC.wav|
+|Ending|ED.wav|
+|Name entry|NAME.wav|
+|Gameover|OVER.wav|
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..21c077c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 GOB
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.ja.md b/README.ja.md
new file mode 100644
index 0000000..ac7203e
--- /dev/null
+++ b/README.ja.md
@@ -0,0 +1,110 @@
+# TinyDarius
+
+開発中です。現在ステージAのみ。
+自作ライブラリの[ドッグフーディング](https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%83%E3%82%B0%E3%83%95%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0)も兼ねています。
+**権利上の問題で、画像や音のリソースデータは含まれておりません。**
+リソースを独自で作成する際の指針は[こちら](HowToMakeResources.md)
+
+
+## 概要
+アーケードゲーム"ダライアス"からボスラッシュを作りました。
+エミュレーター上での動作ではなく、独自のコードで動作しています。
+その為実際の挙動とは異なります。
+オリジナルの開発者ならびに製作会社に対し敬意を評します。
+
+## 動作ハード
+M5Stack Basic,Gray
+
+### 必要なもの
+M5Stack Faces + GB Face
+
+## ビルド方法
+[ArduinoIDE](https://www.arduino.cc/en/software) または [Visual Studio Code](https://code.visualstudio.com/) + [PlatformIO](https://platformio.org/) にてビルド可能です。
+各環境の整備方法などはそれぞれのページを参照してくだい。
+
+### 必要なライブラリ
+[M5Stack](https://github.com/m5stack/M5Stack) 0.4.0
+[LovyanGFX](https://github.com/lovyan03/LovyanGFX) 0.4.17 (support v0,v1)
+[SdFat](https://github.com/greiman/SdFat) 2.1.2
+[goblib](https://github.com/GOB52/goblib) 0.1.0
+[goblib_m5s](https://github.com/GOB52/goblib_m5s) 0.1.0
+
+### platform.ini for PlatformIO
+
+#### 設定
+
+|項目 |設定値 |
+|:---|:---|
+|platform | espressif32@3.5.0 |
+|board | m5stack-core-esp32 or m5stack-gray|
+
+
+#### ビルド種別
+
+|env|説明|備考|
+|:---|:---|:---|
+|master|マスタービルド (LovyanGFX v0)|デバッグ機能なし|
+|master\_v1|マスタービルド (LovyanGFX v1)|デバッグ機能なし|
+|release|リリースビルド (LovyanGFX v0)|デバッグ機能あり|
+|release\_v1|リリースビルド (LovyanGFX v1)|デバッグ機能あり|
+|debug|デバッグビルド (LovyanGFX v0)|デバッグ機能あり|
+|debug|デバッグビルド (LovyanGFX v1)|デバッグ機能あり|
+
+master または master\_v1 でのビルドを推奨します。
+私は ArduinoIDE と PlatformIO で M5Stack を除くライブラリを [lib\_extra\_dirs](https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-extra-dirs) を介して共有使用しています。(ライブラリのインストールは ArduinoIDE を使用)
+ご自分の環境に合わせて platform.ini を書き換えてください。
+
+### TinyDarius.ino for ArduinoIDE
+
+TinyDarius.ino 自体は空のファイルです。 setup(),loop() は ./src/main.cpp にあります。
+ビルドは platform.ini の release 相当のものとなります。
+他の env 相当でのビルドにするには platform.txt を書き換える必要があります。
+env の記述を参考にオプションを設定してください。
+
+#### 設定
+
+ボードマネージャー **M5Stack version 1.0.9**
+
+[Menu] - [Tool]
+
+|項目|設定値|
+|:---|:---|
+|Board|M5Stack-Core-ESP32|
+|Flash Frequency|80|
+|Flash Mode|QIO|
+|Partation Scheme|Default|
+|Core Debug|any |
+
+
+## 遊び方
+
+|ボタン|説明|
+|:---|:---|
+|セレクト|コイン投入|
+|スタート|クレジットがあればゲーム開始|
+|十字|自機の移動|
+|A| 弾発射(ソフトウェアリピート付)|
+
+1. コイン投入(Select押下)
+1. スタート押下してゲーム開始
+1. 敵が出てくるまで動けません(少々お待ちください)
+1. ボスを倒します。(時間切れになるとボスは逃げます)
+1. 自機の位置によって次のステージを選びます(障害物に注意)
+1. 全てのボスを倒せばゲームクリア
+
+## 実装予定
+- ボスの追加
+- ラウンドクリア時のタイムボーナス
+- 効果音の追加
+- スコアランキングの保存と読み込み
+- 自機のミサイルとパワーアップは...作るかもしれないし、しないかも。
+
+## 謝辞
+**[@KojiSaito](https://twitter.com/kojisaito)** M5Stack 向けプログラミングをしているのを見て私も M5Stack を買ってしまいました。M5Stack への道を開いてくれたことに感謝します。
+**[@Lovyan03](https://github.com/lovyan03)** クールで有用なライブラリを作り続けている御仁。 LovyanGFX があったからこそゲームを作ることができました。
+
+開発中、様々な助言を頂いた皆様にも感謝申し上げます。
+
+
+Have a happy coding :)
+
diff --git a/README.md b/README.md
index 8628468..61bf2f5 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,117 @@
-# TinyDarius
\ No newline at end of file
+# TinyDarius
+
+[Japanese/日本語](./README.ja.md)
+
+Work in progress. Now only stage A.
+It also serves as [dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) for my libraries (goblib,goblib_m5s).
+
+**Due to rights issues, image and sound resource data are not included.**
+For guidelines on creating your own resources [here](HowToMakeResources.md)
+
+## Overview
+Boss rush from arcade game "DARIUS".
+We are not running it on an emulator, but on our own code.
+Therefore, the behavior is different from the real thing.
+I have nothing but respect for the people and company that created "DARIUS".
+
+## Support hardware
+M5Stack Basic,Gray
+
+### Require
+M5Stack Faces + GB Face
+
+## How to build
+
+You can build on [ArduinoIDE](https://www.arduino.cc/en/software) or [PlatformIO](https://platformio.org/) with [Visual Studio Code](https://code.visualstudio.com/).
+Please refer to the respective Webpage for instructions on how to build each environment.
+
+### Require
+[M5Stack](https://github.com/m5stack/M5Stack) 0.4.0
+[LovyanGFX](https://github.com/lovyan03/LovyanGFX) 0.4.17 (support v0,v1)
+[SdFat](https://github.com/greiman/SdFat) 2.1.2
+[goblib](https://github.com/GOB52/goblib) 0.1.0
+[goblib_m5s](https://github.com/GOB52/goblib_m5s) 0.1.0
+
+### platform.ini for PlatformIO
+
+#### Settings
+
+|Item | Settings|
+|:---|:---|
+|platform | espressif32@3.5.0 |
+|board | m5stack-core-esp32 or m5stack-gray|
+
+#### Build env
+
+|env|description|remarks|
+|:---|:---|:---|
+|master|Master build with LovyanGFX v0| No debugging features|
+|master\_v1|Master build with LovyanGFX v1| No debugging features|
+|release|Release build with LovyanGFX v0| Debugging features available|
+|release\_v1|Release build with LovyanGFX v1| Debugging features available|
+|debug|Debug build with LovyanGFX v0|Debugging features available|
+|debug|Debug build with LovyanGFX v1|Debugging features available|
+
+Recommend building with master or master\_v1
+I shared libraries between ArduinoIDE and PlatformIO, so use [lib\_extra\_dirs](https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-extra-dirs) and installed libraries (exclude M5Stack) by ArduinoIDE.
+Please edit platform.ini according to your environment.
+
+
+### TinyDarius.ino for ArduinoIDE
+
+TinyDarius.ino is empty file. setup() and loop() in ./src/main.cpp.
+The build will be equivalent to release in platform.ini.
+If you want to change building env,then you need edit platform.txt
+Please refer to the env description to set the options.
+
+
+#### Settings
+
+BoardManager **M5Stack version 1.0.9**
+
+[Menu] - [Tool]
+
+|Menu|Settings|
+|:---|:---|
+|Board|M5Stack-Core-ESP32|
+|Flash Frequency|80|
+|Flash Mode|QIO|
+|Partation Scheme|Default|
+|Core Debug|any |
+
+## How to play
+
+|Button|Description|
+|:---|:---|
+|Select|Insert coin|
+|Start|Start game if credits exists|
+|Cross|Moving my ship|
+|A| Shot(Software rapid shot)|
+
+1. Please inert coin.(Push select)
+1. Push start and start game.
+1. Can't move until enemies appear.(Please wait)
+1. Defeat the boss in time. (The boss will escape when the time is up)
+1. Choose next stage by ship position.(Be careful for obstacles)
+1. Game clear when you defeat all boss.
+
+
+## Scheduled for implementation
+(Maybe if I have time I will make an effort to do so to the extent possible)
+
+- Add more boss.
+- Add time bonus on round clear.
+- Add more SFX.
+- Save/load score ranking.
+- Add missile and power up for ship... hmm, May or may not be implemented.
+
+
+## Special thanks
+
+**[@KojiSaito](https://twitter.com/kojisaito)** Influenced by his programming for M5Stack, I also bought M5Stack. Thanks to he has shown me the path to M5Stack development.
+**[@Lovyan03](https://github.com/lovyan03)** He is making many cool and useful libraries. Thanks to LovaynGFX I was able to create a game for M5Stack.
+
+Thanks also to the other people who gave me all kinds of advice during the development.
+
+Have a happy coding :)
+
diff --git a/TinyDarius.ino b/TinyDarius.ino
new file mode 100644
index 0000000..21c8a3a
--- /dev/null
+++ b/TinyDarius.ino
@@ -0,0 +1,3 @@
+
+// setup(),loop() in src/main.cpp
+
diff --git a/doc/bg0_sample.png b/doc/bg0_sample.png
new file mode 100644
index 0000000..d0cf9a2
Binary files /dev/null and b/doc/bg0_sample.png differ
diff --git a/doc/bg1_sample.png b/doc/bg1_sample.png
new file mode 100644
index 0000000..d438bdb
Binary files /dev/null and b/doc/bg1_sample.png differ
diff --git a/doc/bomb_sample.png b/doc/bomb_sample.png
new file mode 100644
index 0000000..6ea8144
Binary files /dev/null and b/doc/bomb_sample.png differ
diff --git a/doc/branch_sample.png b/doc/branch_sample.png
new file mode 100644
index 0000000..489e676
Binary files /dev/null and b/doc/branch_sample.png differ
diff --git a/doc/enemy_sample.png b/doc/enemy_sample.png
new file mode 100644
index 0000000..91c8d3a
Binary files /dev/null and b/doc/enemy_sample.png differ
diff --git a/doc/kf_sample.png b/doc/kf_sample.png
new file mode 100644
index 0000000..b9f8bbe
Binary files /dev/null and b/doc/kf_sample.png differ
diff --git a/doc/logo_sample.png b/doc/logo_sample.png
new file mode 100644
index 0000000..1df3f1d
Binary files /dev/null and b/doc/logo_sample.png differ
diff --git a/doc/rock_sample.png b/doc/rock_sample.png
new file mode 100644
index 0000000..66269bc
Binary files /dev/null and b/doc/rock_sample.png differ
diff --git a/doc/sh_sample.png b/doc/sh_sample.png
new file mode 100644
index 0000000..833bbbd
Binary files /dev/null and b/doc/sh_sample.png differ
diff --git a/doc/zone_sample.png b/doc/zone_sample.png
new file mode 100644
index 0000000..ea661c1
Binary files /dev/null and b/doc/zone_sample.png differ
diff --git a/platformio.ini b/platformio.ini
new file mode 100644
index 0000000..4f6b5a5
--- /dev/null
+++ b/platformio.ini
@@ -0,0 +1,86 @@
+; PlatformIO Project Configuration File
+;
+; Build options: build flags, source filter
+; Upload options: custom upload port, speed and extra flags
+; Library options: dependencies, extra library storages
+; Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+
+;-----------------------------------------------------------------------
+; TinyDarius
+;-----------------------------------------------------------------------
+
+[env]
+platform = espressif32@3.5.0
+
+board = m5stack-core-esp32
+board_build.flash_mode = qio
+board_build.f_flash = 80000000L
+framework = arduino
+
+;The libraries are shared between ArduinoIDE and PlatformIO.
+;lib_extra_dirs is the ArduinoIDE library directory.
+lib_extra_dirs = ~/projects/M5Stack/libraries/
+lib_ldf_mode = deep
+lib_deps = m5stack/M5Stack@0.4.0
+
+monitor_speed = 115200
+;monitor_filters = esp32_exception_decoder,time
+monitor_filters = time, esp32_exception_decoder
+upload_speed = 921600
+
+;extra_scripts = pre:rename_bin.py
+
+build_flags = -Wall -Wextra -Wreturn-local-addr
+
+;-----------------------------------------------------------------------
+[env:master]
+build_type = release
+build_flags = ${env.build_flags}
+ -DLGFX_USE_V0
+ -DNDEBUG
+build_unflags = -g3
+
+[env:master_v1]
+build_type = release
+build_flags = ${env.build_flags}
+ -DLGFX_USE_V1
+ -DNDEBUG
+build_unflags = -g3
+
+;-----------------------------------------------------------------------
+[env:release]
+build_type = release
+build_flags = ${env.build_flags}
+ -DLGFX_USE_V0
+
+[env:release_v1]
+build_type = release
+build_flags = ${env.build_flags}
+ -DLGFX_USE_V1
+
+;-----------------------------------------------------------------------
+[env:debug]
+build_type = debug
+build_flags = ${env.build_flags}
+ -DDEBUG
+ -DLGFX_USE_V0
+debug_build_flags = -O0
+ -DCORE_DEBUG_LEVEL=5
+
+[env:debug_v1]
+build_type = debug
+build_flags = ${env.build_flags}
+ -DDEBUG
+ -DLGFX_USE_V1
+debug_build_flags = -O0
+ -DCORE_DEBUG_LEVEL=5
+
+
+;-----------------------------------------------------------------------
+;[env:prepro]
+;build_type = release
+;build_flags = -E
diff --git a/rename_bin.py b/rename_bin.py
new file mode 100644
index 0000000..6acc1f6
--- /dev/null
+++ b/rename_bin.py
@@ -0,0 +1,19 @@
+# rename from firmware.bin to projectname.bin
+#
+# platform.ini
+# extra_scripts = pre:rename_bin.py
+#
+Import("env")
+import os
+
+project_name = os.path.basename(os.path.dirname(env["PROJECT_CONFIG"]))
+
+env.Replace(PROGNAME="%s" % project_name)
+
+#
+# If you want to rename to project name + env name then use it.
+#
+#env.Replace(PROGNAME="%s_%s" % (project_name, env["PIOENV"]));
+
+
+
diff --git a/src/app.cpp b/src/app.cpp
new file mode 100644
index 0000000..9d96824
--- /dev/null
+++ b/src/app.cpp
@@ -0,0 +1,368 @@
+/*!
+ TinyDarius
+
+ @file app.cpp
+ @brief Application class
+*/
+#include
+#ifdef min
+#undef min
+#endif
+#include
+
+#include "debug.hpp"
+#include "app.hpp"
+#include "game.hpp"
+#include "constants.hpp"
+#if __has_include("df88.cpp")
+#include "df88.hpp"
+#endif
+#include "renderer.hpp"
+#include "sound.hpp"
+#include "game_obj.hpp"
+#include "typedef.hpp"
+#include "scene_manager.hpp"
+
+#include
+#include
+#include
+#include
+using goblib::m5s::CpuUsage;
+#include
+#include
+#include
+
+
+#ifndef NDEBUG
+#include "scene/scene_debug.hpp"
+#endif
+#include "scene/scene_advertise.hpp"
+//#include "scene/scene_stage.hpp"
+#include "scene/scene_game.hpp"
+
+#include
+#include
+
+using goblib::m5s::FaceGB;
+using goblib::lgfx::GSprite;
+
+namespace
+{
+constexpr std::int32_t SPLIT = 6;
+constexpr std::int32_t STRIP_HEIGHT = SCREEN_HEIGHT / SPLIT;
+static_assert(SCREEN_HEIGHT % SPLIT == 0, "Please make it divisible by SPLIT.");
+}
+
+std::int32_t ScopedReleaseBus::_nest = 0;
+
+TinyDarius::TinyDarius()
+ : goblib::App()
+ , goblib::Singleton()
+ , _output(nullptr)
+ , _sprite{}
+ , _taskTree(nullptr)
+ , _sceneManager(nullptr)
+ , _renderer(nullptr)
+ , _input(nullptr)
+ , _game(nullptr)
+ , _credits(0)
+#ifndef NDEBUG
+ , _freeHeap(0)
+ , _debug(false)
+#endif
+ {}
+
+TinyDarius::~TinyDarius()
+{
+ finalize();
+}
+
+void TinyDarius::setup(LGFX* output)
+{
+ assert(output);
+
+ _output = output;
+
+ _output->init();
+ _output->setRotation(1);
+ _output->setBrightness(80);
+
+ _output->fillScreen(TFT_WHITE);
+
+ assert(_output->width() == SCREEN_WIDTH);
+ assert(_output->height() == SCREEN_HEIGHT);
+
+ for(auto& p : _sprite)
+ {
+ p = new GSprite();
+ assert(p);
+ p->setColorDepth(_output->getColorDepth());
+ if(!p->createSprite(SCREEN_WIDTH, STRIP_HEIGHT))
+ {
+ abort();
+ }
+#if __has_include("df88.cpp")
+ p->setFont(&df88_gfx_font);
+#endif
+ p->setAddrWindow(0, 0, SCREEN_WIDTH, STRIP_HEIGHT);
+ }
+
+ // Occupy the bus.
+ _output->startWrite();
+ _output->setAddrWindow(0, 0, _output->width(), _output->height());
+
+ _taskTree = new TdTaskTree();
+ assert(_taskTree);
+ _sceneManager = new SceneManager(*_taskTree, PRIORITY_SCENE_MAANGER);
+ assert(_sceneManager);
+
+ _renderer = new goblib::graph::Renderer2D(96);
+ assert(_renderer);
+
+ _input = new FaceGB();
+ assert(_input);
+ _input->setup();
+
+ _game = new Game();
+ assert(_game);
+
+#ifndef NDEBUG
+ auto s = new SceneDebug();
+ assert(s);
+ _sceneManager->push(s);
+ _freeHeap = esp_get_free_heap_size();
+ printDebugInformation();
+#else
+ auto s = new SceneAdvertise();
+ assert(s);
+ _sceneManager->push(s);
+#endif
+}
+
+void TinyDarius::finalize()
+{
+ goblib::safeDelete(_game);
+ goblib::safeDelete(_input);
+ goblib::safeDelete(_renderer);
+
+ if(_taskTree)
+ {
+ _taskTree->clear();
+ }
+ goblib::safeDelete(_sceneManager);
+ goblib::safeDelete(_taskTree);
+ for(auto& e : _sprite)
+ {
+ goblib::safeDelete(e);
+ }
+}
+
+
+void TinyDarius::endWrite()
+{
+ _output->endWrite();
+}
+
+void TinyDarius::startWrite()
+{
+ _output->startWrite();
+}
+
+void TinyDarius::fixedUpdate()
+{
+ // GOBLIB_SCOPED_PROFILE_HIGH("fixed");
+ SoundSystem::instance().pump();
+}
+
+void TinyDarius::update(float delta)
+{
+ // GOBLIB_SCOPED_PROFILE_HIGH("update");
+
+ _input->pump();
+
+ if(_input->wasPressedEqual(FaceGB::Button::Select) && _credits < CREDIT_MAX)
+ {
+ SoundSystem::instance().playSfx(SFX::InsertCoin);
+ ++_credits;
+ }
+
+#ifndef NDEBUG
+ M5.update();
+ if(M5.BtnB.wasPressed())
+ {
+ _debug = !_debug;
+ GameObj::showHitBox(_debug);
+ }
+ if(M5.BtnC.wasPressed())
+ {
+ _taskTree->pauseGlobal(!_taskTree->isPauseGlobal());
+ }
+ if(M5.BtnA.wasPressed())
+ {
+ printDebugInformation();
+ }
+#endif
+
+ if(!_taskTree->isPauseGlobal())
+ {
+ _game->pump();
+ }
+ _taskTree->pump(delta);
+}
+
+#ifndef NDEBUG
+void TinyDarius::printDebugInformation() const
+{
+ printf("%s\n", "----------------------");
+
+ _freeHeap = esp_get_free_heap_size();
+ printf("H:%u LH:%u FH:%u\n"
+ , _freeHeap
+ , heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)
+ , heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT)
+ );
+ // heap_caps_print_heap_info(MALLOC_CAP_8BIT);
+ _sceneManager->print();
+ _taskTree->print();
+ _renderer->print();
+ GameObj::print();
+}
+
+void TinyDarius::drawDebugInformation(GSprite* spr) const
+{
+ // GOBLIB_SCOPED_LEAK_ABORT();
+
+ int yoff = SCREEN_HEIGHT/SPLIT - 28;
+ auto cpu0 = CpuUsage::cpu0();
+ auto cpu1 = CpuUsage::cpu1();
+
+#define FWID 6
+#define FHGT 8
+ spr->setTextColor(TFT_WHITE, TFT_BLACK);
+
+ spr->writeFillRect(0,yoff, 320, 4, TFT_BLACK);
+ spr->writeFillRect(0,yoff, 1.6f * cpu0, 2, cpu0 < 100.0f ? TFT_WHITE : TFT_MAGENTA);
+ spr->writeFillRect(0,yoff + 2, 1.6f * cpu1, 2, cpu1 < 100.0f ? TFT_WHITE : TFT_MAGENTA);
+
+ spr->setCursor(0, yoff + FHGT * 1);
+ //spr->printf("F:%2.1f", fps()); // ATTENTION: Leak memory if output float!
+ float fi = 0, fp;
+ fp = std::modf(fps(), &fi);
+ spr->printf("F:%2d.%-1d", static_cast(fi), static_cast(fp*10));
+
+ spr->setCursor(FWID * 12, yoff + FHGT * 1);
+ spr->printf("T:%zu R:%zu", _taskTree->size(), _renderer->size());
+
+ spr->setCursor(FWID * 12, yoff + FHGT * 2);
+ //spr->printf("D:%1.3f", delta());
+ fp = std::modf(delta(), &fi);
+ spr->printf("%d.%-4d", static_cast(fi), static_cast(fp*1000));
+
+ spr->setCursor(0, yoff + FHGT * 2);
+ spr->printf("H:%u", _freeHeap);
+
+ fp = std::modf(cpu0, &fi);
+ spr->setCursor(41 * FWID, yoff + 1 * FHGT);
+ //spr->printf("C0:%3.2f", cpu0);
+ spr->printf("C0:%3d.%-2d", static_cast(fi), static_cast(fp*100));
+
+ //spr->setCursor(30 * FWID, yoff + 1 * FHGT);
+ //spr->printf("C0:%d/%d %3.2f", CpuUsage::get0_i(), CpuUsage::get0_t(), CpuUsage::get0());
+ // CpuUsage::reset0();
+ fp = std::modf(cpu1, &fi);
+ spr->setCursor(41 * FWID, yoff + 2 * FHGT);
+ //spr->printf("C1:%3.2f", cpu1);
+ spr->printf("C1:%3d.%-2d", static_cast(fi), static_cast(fp*100));
+
+ if(_taskTree->isPauseGlobal())
+ {
+ spr->setCursor(0,0);
+ spr->printf("%s", "PAUSE");
+ }
+}
+#endif
+
+void TinyDarius::render()
+{
+ // GOBLIB_SCOPED_PROFILE_HIGH("render");
+
+ constexpr std::size_t tlen = SCREEN_WIDTH * STRIP_HEIGHT;
+ std::int_fast8_t cnt = SPLIT;
+ std::int_fast8_t cur = 0;
+ std::int32_t y = 0;
+ while(cnt--)
+ {
+ _sprite[cur]->clear();
+
+ RenderArg arg{y, _sprite[cur] };
+ _renderer->render(&arg);
+
+ if(cnt == 0)
+ {
+ _sprite[cur]->setCursor(SCREEN_WIDTH - 8*10, SCREEN_HEIGHT - 16 + y);
+ _sprite[cur]->printf("CREDIT: %u", credits());
+#ifndef NDEBUG
+ if(_debug)
+ {
+ drawDebugInformation(_sprite[cur]);
+ }
+#endif
+ }
+
+ _output->pushPixelsDMA(static_cast(_sprite[cur]->getBuffer()), tlen);
+ y -= STRIP_HEIGHT;
+ cur ^= 1;
+ }
+
+}
+
+void TinyDarius::reserveInsertNode(goblib::Task* t, goblib::Task* parent)
+{
+ _taskTree->reserveInsertNode(t, parent);
+}
+
+void TinyDarius::sendBroadcastMessage(goblib::TaskMessage& m, goblib::Task* t)
+{
+ _taskTree->sendBroadcastMessage(m, t);
+}
+;
+void TinyDarius::postBroadcastMessage(goblib::TaskMessage& m, goblib::Task* t)
+{
+ _taskTree->postBroadcastMessage(m, t);
+}
+
+void TinyDarius::insertRenderObj(goblib::graph::RenderObj2D* r)
+{
+ _renderer->insert(r);
+}
+
+void TinyDarius::removeRenderObj(goblib::graph::RenderObj2D* r)
+{
+ _renderer->remove(r);
+}
+
+void TinyDarius::startGame()
+{
+ if(credits() == 0) { return; }
+
+ --_credits;
+ _game->start(0);
+ // _sceneManager->push(new SceneStage(_game->stage()));
+ auto sg = new SceneGame();
+ assert(sg);
+ _sceneManager->push(sg);
+}
+
+void TinyDarius::endGame()
+{
+ auto a = new SceneAdvertise();
+ assert(a);
+ _sceneManager->push(a);
+}
+
+/*
+void TinyDarius::nextStage()
+{
+}
+*/
+
diff --git a/src/app.hpp b/src/app.hpp
new file mode 100644
index 0000000..508dd2f
--- /dev/null
+++ b/src/app.hpp
@@ -0,0 +1,142 @@
+/*!
+ TinyDarius
+
+ @file app.hpp
+ @brief Application class
+*/
+#ifndef TD_APP_HPP
+#define TD_APP_HPP
+
+#include "typedef.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef LGFX_USE_V1
+#include
+#else
+class LGFX;
+#endif
+
+namespace goblib
+{
+class Task;
+class SceneTask;
+class SceneManageTask;
+struct TaskMessage;
+templateclass TaskTree;
+namespace graph
+{
+class RenderObj2D;
+class Renderer2D;
+}
+
+namespace m5s
+{
+class FaceGB;
+}
+}
+class Game;
+using TdTaskTree = goblib::TaskTree;
+using AppClock = goblib::m5s::esp_clock;
+//using app_clock = std::chrono::steady_clock;
+
+/*! @brief Application class */
+class TinyDarius : public goblib::App, goblib::Singleton
+{
+ public:
+ using PointerType = std::unique_ptr;
+ using Singleton::instance;
+ using Singleton::create;
+
+ virtual ~TinyDarius();
+
+ void setup(LGFX* output);
+ void finalize();
+
+ virtual void fixedUpdate() override;
+ virtual void update(float delta) override;
+ virtual void render() override;
+
+ std::uint8_t credits() const { return _credits; }
+
+ void startGame();
+ void endGame();
+
+ /// @name Bus release.occupancy
+ /// @warning Need to release BUS to access SD card.
+ // @{
+ void endWrite(); //!< Release
+ void startWrite(); //!< Occupy
+ // @}
+
+ /// @name Any instance
+ /// @{
+ GOBLIB_INLINE goblib::m5s::FaceGB& input() { return *_input; }
+ GOBLIB_INLINE Game& game() { return *_game; }
+ GOBLIB_INLINE TdTaskTree& task() { return *_taskTree; }
+ /// @}
+
+ /// @name Wrapped
+ /// @{
+ void reserveInsertNode(goblib::Task* t, goblib::Task* parent = nullptr);
+ void sendBroadcastMessage(goblib::TaskMessage& m, goblib::Task* t = nullptr);
+ void postBroadcastMessage(goblib::TaskMessage& m, goblib::Task* t = nullptr);
+
+ void insertRenderObj(goblib::graph::RenderObj2D* r);
+ void removeRenderObj(goblib::graph::RenderObj2D* r);
+ /// @}
+
+ protected:
+ friend class goblib::Singleton;
+ TinyDarius();
+
+ virtual void sleep_until(const std::chrono::time_point& abs_time) override
+ {
+ auto us = std::chrono::duration_cast(abs_time - AppClock::now()).count();
+ auto ms = us > 0 ? us / 1000 : 0;
+ delay(ms);
+ while(AppClock::now() < abs_time){ taskYIELD(); }
+ }
+
+ private:
+#ifndef NDEBUG
+ void printDebugInformation() const;
+ void drawDebugInformation(goblib::lgfx::GSprite* spr) const;
+#endif
+
+ private:
+ LGFX* _output;
+ std::array _sprite; // for translate by DMA.
+ TdTaskTree* _taskTree;
+ goblib::SceneManageTask* _sceneManager;
+ goblib::graph::Renderer2D* _renderer;
+ goblib::m5s::FaceGB* _input;
+ Game* _game;
+ std::uint8_t _credits;
+
+ constexpr static std::uint8_t CREDIT_MAX = 9;
+
+#ifndef NDEBUG
+ mutable std::uint32_t _freeHeap;
+ bool _debug;
+#endif
+
+};
+
+/*! Scoped release and occupay BUS */
+class ScopedReleaseBus
+{
+ public:
+ ScopedReleaseBus() { if(_nest++ == 0) { TinyDarius::instance().endWrite(); }}
+ ~ScopedReleaseBus() { if(--_nest == 0) { TinyDarius::instance().startWrite(); }}
+
+ private:
+ static std::int32_t _nest;
+};
+#define SCOPED_RELEASE_BUS() ScopedReleaseBus GOBLIB_CONCAT(sr_,__LINE__)
+
+#endif
diff --git a/src/background/branch_rock.cpp b/src/background/branch_rock.cpp
new file mode 100644
index 0000000..ca3390c
--- /dev/null
+++ b/src/background/branch_rock.cpp
@@ -0,0 +1,106 @@
+/*!
+ TinyDarius
+
+ @file branch_rock.cpp
+ @brief Rocks for branching that appear when cleared.
+*/
+#include
+
+#include "../debug.hpp"
+#include "branch_rock.hpp"
+#include "../app.hpp"
+#include "../renderer.hpp"
+#include "../utility.hpp"
+#include "../constants.hpp"
+
+#include
+using goblib::lgfx::GSprite;
+using goblib::lgfx::GSprite4;
+#include
+using goblib::lgfx::AnimatedPalette;
+#include
+
+#include
+
+namespace
+{
+constexpr char BITMAP_PATH[] = "/res/td/branch.bmp";
+constexpr Pos2 INITIAL_POS(SCREEN_WIDTH, SCREEN_HEIGHT/2 - 8);
+constexpr Pos2 VELOCITY(-12, 0);
+constexpr HitBox HBOX{Pos2(0,0), Rect2(0,0, SCREEN_WIDTH * 2, 16)};
+}
+
+BranchRock::BranchRock()
+ : GameObj(PRIORITY_BRANCHROCK, ORDER_BRANCHROCK, CATEGORY_WALL, "branchrock")
+ , _sprite(nullptr)
+ , _apalette(nullptr)
+{
+ _sprite = new GSprite4();
+ assert(_sprite);
+ _apalette = new AnimatedPalette(16, _sprite);
+ assert(_apalette);
+
+ _hbox = HBOX;
+ move(INITIAL_POS);
+}
+
+BranchRock::~BranchRock()
+{
+ // TD_PRINT_FUNCTION();
+ goblib::safeDelete(_apalette);
+ goblib::safeDelete(_sprite);
+}
+
+void BranchRock::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool BranchRock::onInitialize()
+{
+ if(createFromBitmap(*_sprite, BITMAP_PATH))
+ {
+ _sprite->makePaletteTable256();
+ _apalette->from(*_sprite);
+ return GameObj::onInitialize();
+ }
+ kill();
+ return false;
+}
+
+bool BranchRock::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void BranchRock::onExecute(const float delta)
+{
+ offset(VELOCITY);
+ if(x() < -_sprite->width())
+ {
+ offset(Pos2(_sprite->width(), 0));
+ // keep hitbox in screen
+ _hbox.move(Pos2::pos_type(0), y());
+ }
+
+ if(_apalette->pump())
+ {
+ _sprite->makePaletteTable256();
+ }
+}
+
+void BranchRock::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ std::int16_t xx = static_cast(x());
+ std::int16_t yy = static_cast(y());
+
+ _sprite->pushSpriteTo16(target, xx, yy + rarg->yorigin, 0);
+ _sprite->pushSpriteTo16(target, xx + _sprite->width() * 1, yy + rarg->yorigin, 0);
+ _sprite->pushSpriteTo16(target, xx + _sprite->width() * 2, yy + rarg->yorigin, 0);
+
+ GameObj::render(arg);
+}
diff --git a/src/background/branch_rock.hpp b/src/background/branch_rock.hpp
new file mode 100644
index 0000000..6869d99
--- /dev/null
+++ b/src/background/branch_rock.hpp
@@ -0,0 +1,45 @@
+/*!
+ TinyDarius
+
+ @file branch_rock.hpp
+ @brief Rocks for branching that appear when cleared.
+*/
+#pragma once
+#ifndef TD_BRANCH_ROCK_HPP
+#define TD_BRANCH_ROCK_HPP
+
+#include
+#include "../game_obj.hpp"
+
+namespace goblib { namespace lgfx {
+class GSprite4;
+class AnimatedPalette;
+}}
+
+/*! @brief Rock for branching */
+class BranchRock : public GameObj
+{
+ public:
+ BranchRock();
+ virtual ~BranchRock();
+
+ virtual void render(void* arg) override;
+
+ GOBLIB_INLINE virtual void show(bool b) override { RenderObj2D::show(b); setHitable(b); }
+ GOBLIB_INLINE void show() { show(true); }
+ GOBLIB_INLINE void hide() { show(false); }
+
+ GOBLIB_INLINE goblib::lgfx::AnimatedPalette* animatedPalette() { return _apalette; }
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ virtual void onExecute(const float delta) override;
+
+ protected:
+ goblib::lgfx::GSprite4* _sprite;
+ goblib::lgfx::AnimatedPalette* _apalette;
+};
+
+#endif
diff --git a/src/background/mask.cpp b/src/background/mask.cpp
new file mode 100644
index 0000000..617b53c
--- /dev/null
+++ b/src/background/mask.cpp
@@ -0,0 +1,34 @@
+/*!
+ TinyDarius
+
+ @file mask.cpp
+ @brief Masking
+*/
+#include
+
+#include "mask.hpp"
+#include "../debug.hpp"
+#include "../renderer.hpp"
+
+#include
+using goblib::lgfx::GSprite;
+
+Mask::~Mask()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void Mask::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+void Mask::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ target->fillRect(_rect.left(), _rect.top() + rarg->yorigin, _rect.width(), _rect.height(), CLR_BLACK);
+ // target->fillRect(_rect.left(), _rect.top() + rarg->yorigin, _rect.width(), _rect.height(), CLR_RED);
+}
diff --git a/src/background/mask.hpp b/src/background/mask.hpp
new file mode 100644
index 0000000..5153d9d
--- /dev/null
+++ b/src/background/mask.hpp
@@ -0,0 +1,30 @@
+/*!
+ TinyDarius
+
+ @file mask.hpp
+ @brief Masking
+*/
+#pragma once
+#ifndef TD_MASK_HPP
+#define TD_MASK_HPP
+
+#include "../game_obj.hpp"
+#include "../typedef.hpp"
+#include "../constants.hpp"
+
+class Mask : public GameObj
+{
+ public:
+ explicit Mask(const Rect2& rect) : GameObj(PRIORITY_MASK, ORDER_MASK, CATEGORY_NONE, "mask"),_rect(rect) {}
+ virtual ~Mask();
+
+ virtual void render(void* arg) override;
+
+ protected:
+ virtual void onUnchain() override;
+
+ protected:
+ Rect2 _rect;
+};
+
+#endif
diff --git a/src/background/rock_surface.cpp b/src/background/rock_surface.cpp
new file mode 100644
index 0000000..6525fbc
--- /dev/null
+++ b/src/background/rock_surface.cpp
@@ -0,0 +1,149 @@
+/*!
+ TinyDarius
+
+ @file rock_surface.hpp
+ @brief upper/lower rock surface
+*/
+#include
+
+#include "rock_surface.hpp"
+#include "../debug.hpp"
+#include "../app.hpp"
+#include "../renderer.hpp"
+#include "../utility.hpp"
+#include "../constants.hpp"
+
+#include
+using goblib::lgfx::GSprite;
+using goblib::lgfx::GSprite4;
+#include
+using goblib::lgfx::AnimatedPalette;
+#include
+
+#include
+
+namespace
+{
+constexpr char BITMAP_PATH[] = "/res/td/rock.bmp";
+constexpr HitBox HIT_BOX{ Pos2(0,0), Rect2(0,0, SCREEN_WIDTH, 16) };
+}
+const std::array RockSurfaceUpper::VELOCITY_TABLE =
+{
+ Pos2(8, 0),
+ Pos2(12, 0),
+};
+
+RockSurfaceUpper:: RockSurfaceUpper(std::int32_t left, std::int32_t top)
+ : GameObj(PRIORITY_ROCKSURFACE, ORDER_ROCKSURFACE, CATEGORY_WALL, "rocksurface")
+ , _sprite(nullptr)
+ , _apalette(nullptr)
+ , _velocity(VELOCITY_TABLE[Velocity::Normal])
+{
+ _sprite = new goblib::lgfx::GSprite4();
+ assert(_sprite);
+ _apalette = new AnimatedPalette(16, _sprite);
+ assert(_apalette);
+
+ _hbox = HIT_BOX;;
+ move(Pos2(left, top));
+}
+
+RockSurfaceUpper:: ~RockSurfaceUpper()
+{
+ // TD_PRINT_FUNCTION();
+ goblib::safeDelete(_apalette);
+ goblib::safeDelete(_sprite);
+}
+
+void RockSurfaceUpper::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool RockSurfaceUpper::onInitialize()
+{
+ if(createFromBitmap(*_sprite, BITMAP_PATH))
+ {
+ _sprite->makePaletteTable256();
+ _apalette->from(*_sprite);
+ return GameObj::onInitialize();
+ }
+ kill();
+ return false;
+}
+
+bool RockSurfaceUpper::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void RockSurfaceUpper::onExecute(const float delta)
+{
+ // loop
+ auto np = pos() + _velocity;
+ if(np.x() > RENDER_WIDTH)
+ {
+ auto left = static_cast(np.x()) % RENDER_WIDTH;
+ np = Pos2(Pos2::pos_type(left), y());
+ }
+ move(np);
+
+ if(_apalette->pump())
+ {
+ _sprite->makePaletteTable256();
+ }
+ // keep hitbox in screen
+ _hbox.move(Pos2::pos_type(0), y());
+}
+
+void RockSurfaceUpper::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ std::int32_t left = -(static_cast(x()) % RENDER_WIDTH);
+ std::int32_t yy = static_cast(y());
+
+ assert(left >= -320 && "Illegal left");
+
+ _sprite->pushSpriteTo16(target, left, yy + rarg->yorigin);
+ _sprite->pushSpriteTo16(target, left + SCREEN_WIDTH, yy + rarg->yorigin);
+
+ GameObj::render(arg);
+}
+
+RockSurfaceLower:: RockSurfaceLower(std::int32_t left, std::int32_t top)
+ : RockSurfaceUpper(left, top)
+{
+ // keep hitbox in screen
+ _hbox = HIT_BOX;
+ _hbox.move(Pos2(0, top));
+}
+
+bool RockSurfaceLower::onInitialize()
+{
+ goblib::lgfx::GSprite4 tmp;
+
+ if(createFromBitmap(tmp, BITMAP_PATH))
+ {
+ // Create reversed virtical, because reverce rendering on th fly is slow.
+ auto wid = tmp.width();
+ auto hgt = tmp.height();
+ _sprite->setColorDepth(tmp.getColorDepth());
+ _sprite->createSprite(wid, hgt);
+ tmp.setPivot(wid * 0.5f - 0.5f, hgt * 0.5f - 0.5f);
+ tmp.pushRotateZoom(_sprite, wid/2 - 0.5f, hgt/2 - 0.5f, 0, 1.0f, -1.0f);
+ auto* ps = tmp.getPalette();
+ auto* ds = _sprite->getPalette();
+ auto len = tmp.getPaletteCount();;
+ while(len--) { *ds++ = *ps++; }
+
+ _sprite->makePaletteTable256();
+ _apalette->from(*_sprite);
+
+ return GameObj::onInitialize();
+ }
+ kill();
+ return false;
+}
diff --git a/src/background/rock_surface.hpp b/src/background/rock_surface.hpp
new file mode 100644
index 0000000..519b340
--- /dev/null
+++ b/src/background/rock_surface.hpp
@@ -0,0 +1,57 @@
+/*!
+ TinyDarius
+
+ @file rock_surface.hpp
+ @brief upper/lower rock surface
+*/
+#pragma once
+#ifndef TD_ROCK_SURFACE_HPP
+#define TD_ROCK_SURFACE_HPP
+
+#include "../game_obj.hpp"
+
+namespace goblib { namespace lgfx {
+class GSprite4;
+class AnimatedPalette;
+}}
+
+
+class RockSurfaceUpper : public GameObj
+{
+ public:
+ explicit RockSurfaceUpper(std::int32_t left, std::int32_t top);
+ virtual ~RockSurfaceUpper();
+
+ virtual void render(void* arg) override;
+
+ enum Velocity : std::uint8_t { Normal, Boss, Max };
+ void setVelocity(const Velocity v) { assert(v < Velocity::Max && "Too big"); _velocity = VELOCITY_TABLE[v]; }
+
+ goblib::lgfx::AnimatedPalette* animatedPalette() { return _apalette; }
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ //virtual void onReceive (const Task::Message& /*msg*/) override {}
+ virtual void onExecute(const float delta) override;
+
+ protected:
+ goblib::lgfx::GSprite4* _sprite; // array ptr
+ goblib::lgfx::AnimatedPalette* _apalette;
+ Pos2 _velocity;
+
+ static const std::array VELOCITY_TABLE;
+ constexpr static std::int32_t RENDER_WIDTH = 320;
+};
+
+class RockSurfaceLower : public RockSurfaceUpper
+{
+ public:
+ explicit RockSurfaceLower(std::int32_t left, std::int32_t top);
+ protected:
+ virtual bool onInitialize() override;
+
+};
+
+#endif
diff --git a/src/background/wave_spot.cpp b/src/background/wave_spot.cpp
new file mode 100644
index 0000000..7655902
--- /dev/null
+++ b/src/background/wave_spot.cpp
@@ -0,0 +1,213 @@
+/*!
+ TinyDarius
+
+ @file wave_spot.cpp
+ @brief Background wave and spot
+*/
+#include
+
+#include "wave_spot.hpp"
+#include "../debug.hpp"
+#include "../app.hpp"
+#include "../renderer.hpp"
+#include "../utility.hpp"
+#include "../constants.hpp"
+#include "../stage.hpp"
+
+#include
+using goblib::lgfx::GSprite;
+using goblib::lgfx::GSprite4;
+#include
+using goblib::lgfx::AnimatedPalette;
+#include
+
+#include
+#include
+
+using WSColorChange = std::array;
+
+namespace
+{
+constexpr char BITMAP_PATH_0[] = "/res/td/bg0.bmp";
+constexpr char BITMAP_PATH_1[] = "/res/td/bg1.bmp";
+
+/* orignal
+ RGBColor(48,96,64),
+ RGBColor(72,160,104),
+ RGBColor(112,224,144)
+*/
+
+constexpr WSColorChange wscc_blue =
+{
+ ColorChange{ RGBColor(48,96,64), RGBColor(48,66,96) },
+ ColorChange{ RGBColor(72,160,104), RGBColor(72,104,160) },
+ ColorChange{ RGBColor(112,224,144), RGBColor(112,153,224) }
+};
+
+constexpr WSColorChange wscc_orange =
+{
+ ColorChange{ RGBColor(48,96,64), RGBColor(96,66,48) },
+ ColorChange{ RGBColor(72,160,104), RGBColor(160,104,72) },
+ ColorChange{ RGBColor(112,224,144), RGBColor(224,153,112), }
+};
+
+constexpr WSColorChange wscc_purple =
+{
+ ColorChange{ RGBColor(48,96,64), RGBColor(96,48,96) },
+ ColorChange{ RGBColor(72,160,104), RGBColor(160,104,160) },
+ ColorChange{ RGBColor(112,224,144), RGBColor(224,144,224) }
+};
+
+const WSColorChange wscc_table[Stage::MAX] =
+{
+ wscc_blue, //A
+ //
+ wscc_purple, //B
+ wscc_orange, //C
+ //
+ wscc_blue, // D
+ wscc_blue, // E
+ wscc_blue, // F
+ //
+ wscc_blue, // G
+ wscc_blue, // H
+ wscc_blue, // I
+ wscc_blue, // J
+ //
+ wscc_blue, // K
+ wscc_blue, // L
+ wscc_blue, // M
+ wscc_blue, // N
+ wscc_blue, // O
+ //
+ wscc_blue, // P
+ wscc_blue, // Q
+ wscc_blue, // R
+ wscc_blue, // S
+ wscc_blue, // T
+ wscc_blue, // U
+ //
+ wscc_blue, // V
+ wscc_blue, // W
+ wscc_blue, // X
+ wscc_blue, // Y
+ wscc_blue, // Z
+ wscc_blue, // V'
+ wscc_blue, // Z'
+};
+
+constexpr std::int32_t ROUND_TRIP_TIMES = 180;
+
+constexpr Pos2 VELOCITY = Pos2(4, 0);
+constexpr Pos2 INITIAL_POS = Pos2(0, 40);
+
+constexpr std::int32_t WIDTH_0 = 64;
+constexpr std::int32_t WIDTH_1 = 96;
+constexpr std::int32_t WIDTH_ALL = WIDTH_0 * 3 + WIDTH_1 * 2;
+//
+}
+
+WaveSpot::WaveSpot()
+ : GameObj(PRIORITY_WAVESPOT, ORDER_WAVESPOT, CATEGORY_WALL, "wavespot")
+ , _sprites(nullptr)
+ , _apalette(nullptr)
+ , _opalettes()
+{
+ _sprites = new GSprite4[SpriteMax];
+ assert(_sprites);
+ _apalette = new AnimatedPalette(16, _sprites);
+ assert(_apalette);
+
+ move(INITIAL_POS);
+ _opalettes.fill(RGBColor());
+}
+
+WaveSpot:: ~WaveSpot()
+{
+ // TD_PRINT_FUNCTION();
+ goblib::safeDelete(_apalette);
+ goblib::safeDeleteArray(_sprites);
+}
+
+void WaveSpot::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool WaveSpot::onInitialize()
+{
+ if(!createFromBitmap(_sprites[Sprite_0], BITMAP_PATH_0))
+ {
+ kill();
+ return false;
+ }
+ if(!createFromBitmap(_sprites[Sprite_1], BITMAP_PATH_1))
+ {
+ kill();
+ return false;
+ }
+
+ getPaletteColors(_opalettes.data(), _opalettes.size(), _sprites[Sprite_0]);
+
+ _apalette->from(_sprites[Sprite_0]);
+ _sprites[Sprite_0].makePaletteTable256();
+ _sprites[Sprite_1].makePaletteTable256();
+
+ return GameObj::onInitialize();
+}
+bool WaveSpot::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void WaveSpot::onExecute(const float delta)
+{
+ // move(Pos2::pos_type(static_cast(x() + VELOCITY_X)), y());
+ // loop
+ auto np = pos() + VELOCITY;
+ if(np.x() > WIDTH_ALL)
+ {
+ auto left = static_cast(np.x()) % WIDTH_ALL;
+ np = Pos2(Pos2::pos_type(left), y());
+ }
+ move(np);
+
+ offset(VELOCITY);
+
+ if(_apalette->pump())
+ {
+ _apalette->to(_sprites[Sprite_1]);
+ _sprites[Sprite_0].makePaletteTable256();
+ _sprites[Sprite_1].makePaletteTable256();
+ }
+}
+
+void WaveSpot::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ std::int32_t left = -(static_cast(x()) % WIDTH_ALL);
+ std::int32_t top = static_cast(y()) + rarg->yorigin;
+
+ _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0;
+ _sprites[Sprite_1].pushSpriteTo16(target, left, top); left += WIDTH_1;
+ _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0;
+ _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0;
+ _sprites[Sprite_1].pushSpriteTo16(target, left, top); left += WIDTH_1;
+
+ _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0;
+ _sprites[Sprite_1].pushSpriteTo16(target, left, top); left += WIDTH_1;
+ _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0;
+ _sprites[Sprite_0].pushSpriteTo16(target, left, top); left += WIDTH_0;
+ _sprites[Sprite_1].pushSpriteTo16(target, left, top); left += WIDTH_1;
+}
+
+void WaveSpot::roundTripPalette(std::uint8_t stageNo)
+{
+ assert(stageNo < goblib::size(wscc_table) && "Invalid stageno");
+
+ auto table = &wscc_table[goblib::clamp(static_cast(stageNo), 0U, goblib::size(wscc_table))];
+ roundTripPaletteColors(*_apalette, table->data(), table->size(), ROUND_TRIP_TIMES);
+}
diff --git a/src/background/wave_spot.hpp b/src/background/wave_spot.hpp
new file mode 100644
index 0000000..2428871
--- /dev/null
+++ b/src/background/wave_spot.hpp
@@ -0,0 +1,43 @@
+/*!
+ TinyDarius
+
+ @file wave_spot.hpp
+ @brief Background wave and spot
+*/
+#pragma once
+#ifndef TD_WAVE_SPOT_HPP
+#define TD_WAVE_SPOT_HPP
+
+#include "../game_obj.hpp"
+
+namespace goblib { namespace lgfx {
+class GSprite4;
+class AnimatedPalette;
+}}
+
+class WaveSpot : public GameObj
+{
+ public:
+ WaveSpot();
+ virtual ~WaveSpot();
+
+ virtual void render(void* arg) override;
+
+ goblib::lgfx::AnimatedPalette* animatedPalette() { return _apalette; }
+ void roundTripPalette(std::uint8_t stageNo);
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ //virtual void onReceive (const Task::Message& /*msg*/) override {}
+ virtual void onExecute(const float delta) override;
+
+ private:
+ enum { Sprite_0, Sprite_1, SpriteMax };
+ goblib::lgfx::GSprite4* _sprites;
+ goblib::lgfx::AnimatedPalette* _apalette;
+ std::array _opalettes; // original palette colors.
+};
+
+#endif
diff --git a/src/behavior.cpp b/src/behavior.cpp
new file mode 100644
index 0000000..a2aad09
--- /dev/null
+++ b/src/behavior.cpp
@@ -0,0 +1,79 @@
+/*!
+ TinyDarius
+
+ @file behavior.cpp
+ @brief Behavior for objects
+*/
+#include "behavior.hpp"
+#include "game_obj.hpp"
+#include "constants.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+//
+void UniformLinearBehavior::pump(const float delta)
+{
+ _attach.offset(Pos2(std::cos(_radian) * _velocity, std::sin(_radian) * _velocity));
+ Behavior::pump(delta);
+}
+
+#if 0
+//
+void HomingBehavior::pump(const float delta)
+{
+ Pos2 vpos = _target.pos() - _attach.pos();
+ _radian = std::atan2(vpos.y(), vpos.x());
+
+ AccelarationBehavior::pump(delta);
+}
+#endif
+
+//
+void FlutteringBehavior::pump(const float delta)
+{
+ _attach.move(_bpos.x() + goblib::roundTrip(counter(), _range),
+ _bpos.y() + _range - goblib::roundTrip(counter() + _range, _range * 2));
+ Behavior::pump(delta);
+}
+
+OrbitalBehavior::OrbitalBehavior(GameObj& attach, fx16 a, fx16 b, Tangle startAngle, fx16 avelocity, VecFx16 velocity, Tangle rotate)
+ : Behavior(attach)
+ , _pos(VecFx16::pos_type(attach.x()), VecFx16::pos_type(attach.y()))
+ , _a(a), _b(b)
+ , _angle(startAngle), _avelocity(avelocity), _off(velocity), _rotate(rotate)
+{
+ // printf("%s:%d,%d\n", __func__, _pos.x().value(), _pos.y().value());
+}
+
+void OrbitalBehavior::pump(const float delta)
+{
+ fx16 rc = table_cos(_rotate.value());
+ fx16 rs = table_sin(_rotate.value());
+ fx16 ac = table_cos(_angle.value());
+ fx16 as = table_sin(_angle.value());
+
+ _pos += VecFx16( _a * ac * rc - _b * as * rs,
+ _a * ac * rs + _b * as * rc);
+ _pos += _off;
+
+ // printf("%d,%d : %f\n", _pos.x().value(), _pos.y().value(), _angle.value());
+
+ _attach.move(static_cast(_pos.x()), static_cast(_pos.y()));
+
+ _angle += _avelocity;
+}
+
+//
+MineBehavior::MineBehavior(GameObj& attach, fx16 a, fx16 b, Tangle startAngle, fx16 avelocity, VecFx16 velocity, Tangle rotate)
+ : OrbitalBehavior(attach, a, b, startAngle, avelocity, velocity, rotate)
+{}
+
+void MineBehavior::pump(const float delta)
+{
+ OrbitalBehavior::pump(delta);
+ if(_attach.x() < 64) { _off.zero(); }
+}
diff --git a/src/behavior.hpp b/src/behavior.hpp
new file mode 100644
index 0000000..c7aed2b
--- /dev/null
+++ b/src/behavior.hpp
@@ -0,0 +1,152 @@
+/*!
+ TinyDarius
+
+ @file behavior.hpp
+ @brief Behavior for objects
+*/
+#pragma once
+#ifndef TD_BEHAVIOR_HPP
+#define TD_BEHAVIOR_HPP
+
+#include "typedef.hpp"
+#include "math_table.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+
+class GameObj;
+
+
+/*! @brief Behavior base */
+class Behavior
+{
+ public:
+ explicit Behavior(GameObj& attach) : _attach(attach), _counter(0) {}
+ virtual ~Behavior(){}
+
+ GOBLIB_INLINE virtual void pump(const float /*delta*/){ ++_counter; }
+ void reset() { _counter = 0; }
+
+ protected:
+ std::uint32_t counter() const { return _counter; }
+
+ protected:
+ GameObj& _attach;
+
+ private:
+ std::uint32_t _counter;
+};
+
+/*! @brief No movement */
+class FixedBehavior : public Behavior
+{
+ public:
+ FixedBehavior(GameObj& attach) : Behavior(attach) {}
+};
+
+/*! @brief Uniform linear motion */
+class UniformLinearBehavior : public Behavior
+{
+ public:
+ UniformLinearBehavior(GameObj& attach, float velocity, float radian)
+ : Behavior(attach), _velocity(velocity), _radian(radian) {}
+
+ virtual void pump(const float delta) override;
+
+ protected:
+ float _velocity;
+ float _radian;
+};
+
+/*! @brief Linear motion with accelaration */
+class AccelarationBehavior : public UniformLinearBehavior
+{
+ public:
+ AccelarationBehavior(GameObj& attach, float velocity, float radian, float acc)
+ : UniformLinearBehavior(attach, velocity, radian), _acc(acc) {}
+
+ virtual void pump(const float delta) override
+ {
+ UniformLinearBehavior::pump(delta);
+ _velocity += _acc;
+ }
+
+ protected:
+ float _acc;
+};
+
+#if 0
+// @brief Homing to target.
+class HomingBehavior : public AccelarationBehavior
+{
+ public:
+ HomingBehavior(GameObj& attach, float velocity, float radian, float acc, GameObj& target)
+ : AccelarationBehavior(attach, velocity, radian, acc), _target(target) {}
+
+ virtual void pump(const float delta) override;
+
+ protected:
+ GameObj& _target;
+};
+#endif
+
+// @brief Flutter
+class FlutteringBehavior : public Behavior
+{
+ public:
+ FlutteringBehavior(GameObj& attach, const Pos2& bpos, std::uint32_t range)
+ : Behavior(attach)
+ , _bpos(bpos)
+ , _range(range)
+ { assert(goblib::math::is_powerof2(range) && "range must be power of 2"); }
+
+ virtual void pump(const float delta) override;
+
+ protected:
+ const Pos2 _bpos; // base position
+ std::uint32_t _range;
+};
+
+// @brief Orbital
+class OrbitalBehavior : public Behavior
+{
+ public:
+ /*!
+ @param attach target object
+ @param a ellipse circumference step a
+ @param b ellipse circumference step b
+ @param startAngle start angle for calculate circumference
+ @param avelocity angular velocity
+ @param offset velocity
+ @rotate ellipse rotation angle
+ */
+ OrbitalBehavior(GameObj& attach, fx16 a, fx16 b, Tangle startAngle, fx16 avelocity, VecFx16 velocity, Tangle rotate = 0);
+
+ virtual void pump(const float delta) override;
+
+ protected:
+ VecFx16 _pos;
+ fx16 _a, _b; // radius A,B
+ fx16 _angle; // calc for elliptice circumference.
+ fx16 _avelocity;
+ VecFx16 _off;
+ fx16 _rotate; // ellipse rotate angle.
+};
+
+
+
+// @brief for Mine
+class MineBehavior : public OrbitalBehavior
+{
+ public:
+ MineBehavior(GameObj& attach, fx16 a, fx16 b, Tangle startAngle, fx16 avelocity, VecFx16 velocity, Tangle rotate = 0);
+ virtual void pump(const float delta) override;
+
+ private:
+ bool _orbit;
+};
+
+#endif
diff --git a/src/boss/boss.cpp b/src/boss/boss.cpp
new file mode 100644
index 0000000..62b0ef6
--- /dev/null
+++ b/src/boss/boss.cpp
@@ -0,0 +1,478 @@
+/*!
+ TinyDarius
+
+ @file boss.hpp
+ @brief Boss base
+*/
+#include
+
+#include "boss.hpp"
+#include "../debug.hpp"
+#include "../app.hpp"
+#include "../game.hpp"
+#include "../renderer.hpp"
+#include "../utility.hpp"
+#include "../constants.hpp"
+#include "../behavior.hpp"
+#include "../effect/explosion.hpp"
+#include "../enemy/enemy.hpp"
+
+#include "king_fossil.hpp"
+//#include "electric_fan.hpp"
+
+#include
+using goblib::lgfx::GSprite;
+#include
+using goblib::lgfx::GCellSprite4;
+#include
+#include
+#include
+
+#include
+
+namespace
+{
+constexpr Pos2 INITIAL_POS(SCREEN_WIDTH, SCREEN_HEIGHT);
+constexpr Pos2 LEAVE_TO(80, 240);
+constexpr std::int32_t LEAVE_TIMES = 6_fsec;
+};
+
+Boss* Boss::create(std::int8_t stage)
+{
+ Boss* p = nullptr;
+ switch(stage)
+ {
+ case 0: p = new KingFossil(); break; // A
+ // case 1: p = new ElectricFan(ElectricFan::Type::B); break; // B
+ // case 2: p = new ElectricFan(ElectricFan::Type::C); break; // C
+ /*
+ case 3: p = new DualShears(DualShears::Type::D); break; // D
+ case 4: p = new DualShears(DualShears::Type::E); break; // E
+ case 5: p = new DualShears(DualShears::Type::F); break; // F
+ */
+ default: p = new KingFossil(); break;
+ }
+ return p;
+}
+
+Boss::Boss(std::uint32_t pts)
+ : GameObj(PRIORITY_BOSS, ORDER_BOSS_BODY, CATEGORY_BOSS, "boss")
+ , _sprite(nullptr)
+ , _bodyRect()
+ , _behavior(nullptr)
+ , _hp(0)
+ , _partials()
+ , _apalette(nullptr)
+ , _palettes{}
+ , _function(&Boss::onNop)
+ , _from(), _to()
+ , _to_times(0)
+ , _status(Status::Nop)
+ , _score(pts)
+{
+ _sprite = new GCellSprite4();
+ assert(_sprite);
+
+ _apalette = new goblib::lgfx::AnimatedPalette(16, _sprite);
+ assert(_apalette);
+
+ _score.insertObserver(TinyDarius::instance().game());
+
+ move(INITIAL_POS);
+}
+
+Boss::~Boss()
+{
+ // TD_PRINT_FUNCTION();
+ goblib::safeDelete(_apalette);
+ goblib::safeDelete(_behavior);
+ goblib::safeDelete(_sprite);
+}
+
+void Boss::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool Boss::onInitialize()
+{
+ // Store original palettes.
+ getPaletteColors(_palettes.data(), _palettes.size(), *_sprite);
+ _sprite->makePaletteTable256();
+
+ return GameObj::onInitialize();
+}
+
+bool Boss::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void Boss::onExecute(const float delta)
+{
+ (this->*_function)(delta);
+ if(_apalette->pump())
+ {
+ _sprite->makePaletteTable256();
+ }
+ GameObj::onExecute(delta);
+}
+
+void Boss::onHit(GameObj* o, const Rect2& hit)
+{
+ if(o->category() == CATEGORY_BULLET)
+ {
+ --_hp;
+ }
+}
+
+void Boss::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ _sprite->pushCellTo16(target, _bodyRect, static_cast(x()), static_cast(y()) + rarg->yorigin, 0);
+
+ GameObj::render(arg);
+}
+
+void Boss::_appear(const ColorChange* gray, std::size_t gsz, const Pos2& to, std::uint32_t times)
+{
+ TD_PRINTF("%s\n", __PRETTY_FUNCTION__);
+ _function = &Boss::onAppear;
+ _status = Status::Appear;
+
+ move(INITIAL_POS);
+ resetAnimation();
+ disableHit();
+ resume();
+ pausePartials();
+ show();
+
+ applyPaletteColors(*_sprite, gray, gsz);
+ _apalette->from(*_sprite);
+ _sprite->makePaletteTable256();
+
+ moveTo(to, times);
+}
+
+void Boss::onAppear(const float delta)
+{
+ if(pumpMove())
+ {
+ move(_to);
+ prepare();
+ }
+}
+
+void Boss::prepare()
+{
+ TD_PRINTF("%s\n", __PRETTY_FUNCTION__);
+ _function = &Boss::onPrepare;
+ _status = Status::Prepare;;
+ towardOriginalColor(*_sprite, 60);
+}
+
+void Boss::onPrepare(const float delta)
+{
+ if(_apalette->empty())
+ {
+ battle();
+ }
+}
+
+void Boss::battle()
+{
+ TD_PRINTF("%s\n", __PRETTY_FUNCTION__);
+ _function = &Boss::onBattle;
+ _status = Status::Battle;
+ resumePartials();
+ enableHit();
+ resetCounter();
+ if(_behavior) { _behavior->reset(); }
+}
+
+void Boss::onBattle(const float delta)
+{
+ if(_behavior) { _behavior->pump(delta); }
+ if(_hp <= 0)
+ {
+ defeat();
+ }
+}
+
+void Boss::_defeat(const ColorChange* gray, std::size_t gsz, const ExplosionSetting* eset, std::size_t esz)
+{
+ TD_PRINTF("%s\n", __PRETTY_FUNCTION__);
+ _function = &Boss::onDefeat;
+ _status = Status::Defeat;
+ resetCounter();
+
+ pausePartials();
+ disableHit();
+ _apalette->pause();
+
+ goblib::TaskMessage msg;
+ msg.msg = MSG_DEFEAT_BOSS;
+ msg.arg = this;
+ TinyDarius::instance().postBroadcastMessage(msg);
+
+ towardPaletteColors(*_apalette, gray, gsz, 60);
+ explode(eset, esz);
+
+ _score.notify();
+}
+
+void Boss::onDefeat(const float delta)
+{
+ if(counter() > 3_fsec)
+ {
+ _apalette->resume();
+ if(_apalette->empty())
+ {
+ leave();
+ }
+ }
+}
+
+void Boss::_escape(const ColorChange* gray, std::size_t gsz)
+{
+ TD_PRINTF("%s\n", __PRETTY_FUNCTION__);
+ _function = &Boss::onEscape;
+ _status = Status::Escape;
+ resetCounter();
+ _hp = 0;
+
+ disableHit();
+ pausePartials();
+ _apalette->resume();
+ towardPaletteColors(*_apalette, gray, gsz, 60);
+}
+
+
+void Boss::onEscape(const float delta)
+{
+ if(counter() > 4_fsec)
+ {
+ if(_apalette->empty())
+ {
+ leave();
+ }
+ }
+}
+
+void Boss::leave()
+{
+ TD_PRINTF("%s\n", __PRETTY_FUNCTION__);
+ moveTo(LEAVE_TO, LEAVE_TIMES);
+ _function = &Boss::onLeave;
+ _status = Status::Leave;
+ resetCounter();
+}
+
+void Boss::onLeave(const float delta)
+{
+ if(pumpMove())
+ {
+ move(_to);
+ nop();
+ }
+}
+
+bool Boss::pumpMove()
+{
+ auto t = goblib::easing::linear(static_cast(counter()) / _to_times);
+ auto diff = _to - _from;
+ diff *= t;
+ move(_from + diff);
+
+ return counter() >= _to_times;
+}
+
+void Boss::explode(const ExplosionSetting* table, std::size_t size)
+{
+ while(size--)
+ {
+ auto e = new Explosion(table->rpos.x(), table->rpos.y(), ORDER_EXPLOSION_BOSS, static_cast(table->type),
+ this, -1, true);
+ assert(e);
+ TinyDarius::instance().reserveInsertNode(e, this);
+ ++table;
+ }
+}
+
+void Boss::hideEffect()
+{
+ goblib::TaskMessage msg;
+ msg.msg = MSG_HIDE_EFFECT;
+ msg.arg = this;
+ TinyDarius::instance().sendBroadcastMessage(msg, this);
+}
+
+void Boss::towardOriginalColor(goblib::lgfx::GCellSprite4& sprite, std::uint32_t times)
+{
+ int_fast8_t i = 0;
+ for(auto& e : _palettes)
+ {
+ _apalette->toward(i++, e, times);
+ }
+}
+
+//
+Boss::Partial::Partial(Boss& parent, goblib::lgfx::GCellSprite4& sprite, const PartialAnimation& animation, std::int32_t relative_priority, std::int32_t relative_order, std::int32_t hp, std::uint32_t pts)
+ : GameObj(PRIORITY_BOSS + relative_priority, ORDER_BOSS_BODY + relative_order, CATEGORY_BOSS, "bosspartial")
+ , _parent(parent)
+ , _sprite(sprite)
+ , _animation(animation)
+ , _hp(hp)
+ , _score(pts)
+{
+ pauseAnimation();
+ _score.insertObserver(TinyDarius::instance().game());
+}
+
+Boss::Partial::~Partial()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void Boss::Partial::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool Boss::Partial::onInitialize()
+{
+ return GameObj::onInitialize();
+}
+
+bool Boss::Partial::onRelease()
+{
+ return GameObj::onRelease();
+}
+void Boss::Partial::onExecute(const float delta)
+{
+ _animation.pump();
+ move(_parent.x() + _animation.rx + _animation.offsetX(), _parent.y() + _animation.ry + _animation.offsetY());
+}
+
+void Boss::Partial::onHit(GameObj* o, const Rect2& hit)
+{
+ if(o->category() == CATEGORY_BULLET)
+ {
+ Pos2i spos(hit.center().x() - static_cast(pos().x()),
+ hit.center().y() - static_cast(pos().y()));
+
+ explode(spos);
+
+ if(--_hp <= 0)
+ {
+ _score.notify();
+ disableHit();
+ hide();
+ }
+ }
+}
+
+void Boss::Partial::explode(const Pos2& pos)
+{
+ auto exp = new Explosion(pos, ORDER_EXPLOSION_BOSS, Explosion::Random, this);
+ assert(exp);
+ TinyDarius::instance().reserveInsertNode(exp, this);
+}
+
+void Boss::Partial::reflect(const Pos2& pos)
+{
+ auto r = new Reflection(pos.x(), pos.y());
+ assert(r);
+ TinyDarius::instance().reserveInsertNode(r, this);
+}
+
+void Boss::Partial::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ _sprite.pushCellTo16(target, _animation.rect(), static_cast(x()), static_cast(y()) + rarg->yorigin, 0);
+
+ GameObj::render(arg);
+}
+
+//
+Boss::Bullet::Bullet(GameObj& parent, goblib::lgfx::GCellSprite4& sprite, const PartialAnimation& animation)
+ : GameObj(PRIORITY_BOSS_BULLET, ORDER_BOSS_BULLET, CATEGORY_ENEMY_BULLET, "bossbullet")
+ , _parent(parent)
+ , _sprite(sprite)
+ , _animation(animation)
+ , _behavior(nullptr)
+{
+}
+
+Boss::Bullet::~Bullet()
+{
+ // TD_PRINT_FUNCTION();
+ goblib::safeDelete(_behavior);
+}
+
+void Boss::Bullet::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+
+}
+
+bool Boss::Bullet::onInitialize()
+{
+ return GameObj::onInitialize();
+}
+
+bool Boss::Bullet::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void Boss::Bullet::onExecute(const float delta)
+{
+ _animation.pump();
+ if(_behavior) { _behavior->pump(delta); }
+}
+
+void Boss::Bullet::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ _sprite.pushCellTo16(target, _animation.rect(), static_cast(x()), static_cast(y()) + rarg->yorigin, 0);
+
+ GameObj::render(arg);
+}
+
+//
+Boss::Hit::Hit(Boss& parent, const HitBox& hbox)
+ : GameObj(PRIORITY_BOSS, ORDER_BOSS_BODY - 2, CATEGORY_BIT_OBSTACLE, "bosshitbox")
+ , _parent(parent)
+ , _chbox(hbox)
+{
+ _hbox = hbox;
+ move(_parent.x(), _parent.y());
+}
+
+void Boss::Hit::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+
+}
+
+void Boss::Hit::onExecute(const float)
+{
+ move(_parent.x(), _parent.y());
+}
+
+void Boss::Hit::render(void* arg)
+{
+ GameObj::render(arg);
+}
diff --git a/src/boss/boss.hpp b/src/boss/boss.hpp
new file mode 100644
index 0000000..f8c5672
--- /dev/null
+++ b/src/boss/boss.hpp
@@ -0,0 +1,285 @@
+/*!
+ TinyDarius
+
+ @file boss.hpp
+ @brief Boss base
+*/
+#pragma once
+#ifndef TD_BOSS_HPP
+#define TD_BOSS_HPP
+
+#include "../game_obj.hpp"
+#include "../partial_animation.hpp"
+#include "../score.hpp"
+#include "../hit_box.hpp"
+#include
+#include
+#include
+
+namespace goblib { namespace lgfx {
+class GCellSprite4;
+class AnimatedPalette;
+}}
+class Behavior;
+struct ColorChange;
+
+/*! @brief Boss base class */
+class Boss : public GameObj
+{
+ public:
+ enum Status : int32_t
+ {
+ Nop,
+ Appear,
+ Prepare,
+ Battle,
+ Defeat,
+ Escape,
+ Leave,
+ };
+ constexpr static std::size_t PARTIAL_MAX = 12;
+
+ /*! @brief Partial parts of Boss */
+ class Partial : public GameObj
+ {
+ public:
+ /*!
+ @param parent Boss object
+ @param sprite Shared sprite
+ @param animation animation cell and sequences
+ @param relative_priority relative priority with parent
+ @param relative_order relative rendering order with parent
+ @param hp Hitpoint
+ @param pts Score if breaked
+ */
+ Partial(Boss& parent, goblib::lgfx::GCellSprite4& sprite, const PartialAnimation& animation,std::int32_t relative_priority, std::int32_t relative_order, std::int32_t hp = 1, std::uint32_t pts = 0);
+ virtual ~Partial();
+
+ GOBLIB_INLINE void pauseAnimation(bool b) { _animation.pause(b); }
+ GOBLIB_INLINE void pauseAnimation() { pauseAnimation(true); }
+ GOBLIB_INLINE void resumeAnimation() { pauseAnimation(false); }
+ GOBLIB_INLINE void resetAnimation() { _animation.reset(); }
+
+ virtual void render(void* arg) override;
+
+ GOBLIB_INLINE std::int32_t hp() const { return _hp; }
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ virtual void onExecute(const float delta) override;
+
+ virtual void onHit(GameObj* o, const Rect2& hit) override;
+
+ void explode(const Pos2& pos);
+ void reflect(const Pos2& pos);
+
+ protected:
+ Boss& _parent;
+ goblib::lgfx::GCellSprite4& _sprite;
+ PartialAnimation _animation;
+ std::int32_t _hp;
+ Score _score;
+ };
+
+ /*! @brief Bullets of Boss */
+ class Bullet : public GameObj
+ {
+ public:
+ /*!
+ @param parent Parent object
+ @param sprite Shared sprite
+ @param animation animation cell and sequences
+ */
+ Bullet(GameObj& parent, goblib::lgfx::GCellSprite4& sprite, const PartialAnimation& animation);
+ virtual ~Bullet();
+
+ virtual void render(void* arg) override;
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ virtual void onExecute(const float delta) override;
+
+ protected:
+ GameObj& _parent;
+ goblib::lgfx::GCellSprite4& _sprite;
+ PartialAnimation _animation;
+ Behavior* _behavior;
+ };
+
+ /*! @brief Hitbox (NO rendering) */
+ class Hit : public GameObj
+ {
+ public:
+ Hit(Boss& parent, const HitBox& hbox /* relative */);
+ virtual void render(void* arg) override;
+ protected:
+ virtual void onUnchain() override;
+ virtual void onExecute(const float delta) override;
+ private:
+ Boss& _parent;
+ const HitBox _chbox;
+ };
+
+ /*! @brief Explosions at defeat */
+ struct ExplosionSetting
+ {
+ Pos2 rpos; // relative position from body
+ std::int32_t type; // Explosion::Type
+ };
+
+ //
+ static Boss* create(std::int8_t stage);
+ virtual ~Boss();
+
+ virtual void render(void* arg) override;
+
+ /// @name Property
+ /// @{
+ virtual const char* name() const = 0;
+ GOBLIB_INLINE goblib::lgfx::GCellSprite4* sprite() { return _sprite; }
+ GOBLIB_INLINE Status status() const { return _status; }
+ GOBLIB_INLINE bool isDead() const { return _hp <= 0; }
+ GOBLIB_INLINE bool isNop() const { return status() == Status::Nop; }
+ /// @}
+
+ virtual void show(bool b) override
+ {
+ for(auto& e : _partials) { e->show(b && (e->hp() > 0)); }
+ RenderObj2D::show(b);
+ }
+ GOBLIB_INLINE void show() { show(true); }
+ GOBLIB_INLINE void hide() { show(false); }
+
+ GOBLIB_INLINE void pause(bool b)
+ {
+ Task::pause(b, false);
+ pausePartials(b);
+ }
+ GOBLIB_INLINE void pause() { pause(true); }
+ GOBLIB_INLINE void resume() { pause(false); }
+
+ GOBLIB_INLINE void virtual setHitable(bool b) override
+ {
+ GameObj::setHitable(b);
+ setHitablePartials(b);
+ setHitableHits(b);
+ }
+ GOBLIB_INLINE void enableHit() { setHitable(true); }
+ GOBLIB_INLINE void disableHit() { setHitable(false); }
+
+ void hideEffect();
+
+ GOBLIB_INLINE std::int32_t hp() const { return _hp; }
+
+ /// @name Change status
+ /// @{
+ virtual void appear() = 0;
+ virtual void escape() = 0;
+ /// @}
+
+ virtual void onHit(GameObj* o, const Rect2& hit) override;
+
+ protected:
+ explicit Boss(std::uint32_t pts);
+
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ virtual void onExecute(const float delta) override;
+
+ /// @name function for status
+ /// @{
+ virtual void onAppear(const float delta);
+ virtual void onPrepare(const float delta);
+ virtual void onBattle(const float delta);
+ virtual void onDefeat(const float delta);
+ virtual void onEscape(const float delta);
+ virtual void onLeave(const float delta);
+ /// @}
+
+ /// @name Change status
+ /// @{
+ void prepare();
+ void battle();
+ virtual void defeat() = 0;
+ void leave();
+
+ void _appear(const ColorChange* gray, std::size_t gsz, const Pos2& to, std::uint32_t times);
+ void _defeat(const ColorChange* gray, std::size_t gsz, const ExplosionSetting* eset, std::size_t esz);
+ void _escape(const ColorChange* gray, std::size_t gsz);
+ /// @}
+
+ void explode(const ExplosionSetting* table, std::size_t size);
+
+ void pausePartials(bool b)
+ {
+ for(auto& e : _partials) { e->pauseAnimation(b); }
+ }
+ GOBLIB_INLINE void pausePartials() { pausePartials(true); }
+ GOBLIB_INLINE void resumePartials() { pausePartials(false); }
+
+ GOBLIB_INLINE void setHitablePartials(bool b)
+ {
+ for(auto& e : _partials) { e->setHitable(b); }
+ }
+ GOBLIB_INLINE void enableHitPartials() { setHitablePartials(true); }
+ GOBLIB_INLINE void disableHitPartials() { setHitablePartials(true); }
+
+ GOBLIB_INLINE void setHitableHits(bool b)
+ {
+ for(auto& e : _hits) { e->setHitable(b); }
+ }
+ GOBLIB_INLINE void enableHitHits() { setHitableHits(true); }
+ GOBLIB_INLINE void disableHitHits() { setHitableHits(true); }
+
+
+ GOBLIB_INLINE void resetAnimation()
+ {
+ for(auto& e : _partials) { e->resetAnimation(); }
+ }
+
+ void moveTo(const Pos2& to, std::int32_t times)
+ {
+ _from = pos();
+ _to = to;
+ _to_times = times;
+ resetCounter();
+ }
+
+ void towardOriginalColor(goblib::lgfx::GCellSprite4& sprite, std::uint32_t times);
+
+ private:
+ void nop()
+ {
+ _function = &Boss::onNop;
+ _status = Status::Nop;
+ resetCounter();
+ }
+ void onNop(const float) {}
+
+ bool pumpMove();
+
+ protected:
+ goblib::lgfx::GCellSprite4* _sprite;
+ goblib::lgfx::CellRect _bodyRect;
+ Behavior* _behavior;
+ std::int32_t _hp;
+ goblib::FixedVector _partials;
+ goblib::FixedVector _hits;
+ goblib::lgfx::AnimatedPalette* _apalette;
+ std::array _palettes; // Original palettes.
+
+ using pump_function = void(Boss::*)(float);
+ pump_function _function;
+
+ Pos2 _from, _to;
+ std::int32_t _to_times;
+ Status _status;
+ Score _score;
+};
+
+#endif
diff --git a/src/boss/king_fossil.cpp b/src/boss/king_fossil.cpp
new file mode 100644
index 0000000..7fe894c
--- /dev/null
+++ b/src/boss/king_fossil.cpp
@@ -0,0 +1,476 @@
+/*!
+ TinyDarius
+
+ @file king_fossil.cpp
+ @brief Coelacanth
+*/
+#include
+
+#include "king_fossil.hpp"
+#include "../debug.hpp"
+#include "../app.hpp"
+#include "../utility.hpp"
+#include "../constants.hpp"
+#include "../behavior.hpp"
+#include "../effect/explosion.hpp"
+
+#include
+using goblib::lgfx::GSprite;
+using goblib::lgfx::GCellSprite4;
+#include
+#include
+#include
+#include
+using goblib::suffix::operator"" _u8;
+using goblib::suffix::operator"" _i16;
+
+#include
+
+using goblib::lgfx::CellRect;
+using Seq = goblib::graph::Sequence;
+
+namespace
+{
+constexpr char BITMAP_PATH[] = "/res/td/kf.bmp";
+constexpr CellRect BODY_RECT = {0, 0, 152, 72};
+
+constexpr Pos2 APPEAR_TO(SCREEN_WIDTH - 196, SCREEN_HEIGHT/2 - 40);
+constexpr std::int32_t APPEAR_TIMES = 6_fsec;
+
+// Hitpoint
+#if 1
+constexpr std::int32_t HP_BODY = 30;
+#else
+constexpr std::int32_t HP_BODY = 1;
+#endif
+constexpr std::int32_t HP_DORSAFIN = 16;
+constexpr std::int32_t HP_ANALFIN = 16;
+
+// Partial gray scale
+constexpr ColorChange GRAY_SCALE[] =
+{
+ { RGBColor(0,24,90), RGBColor(32,32,64) },
+ { RGBColor(0,90,189), RGBColor(80,80,128) },
+ { RGBColor(0,41,123), RGBColor(64,64,96) },
+};
+
+
+// Explosion on defeat
+constexpr Boss::ExplosionSetting EXPLOSION_TABLE[] =
+{
+ { Pos2( 16, 32), Explosion::Type::Type_1 },
+ { Pos2( 80, 16), Explosion::Type::Type_0 },
+ { Pos2( 64, 48), Explosion::Type::Type_2 },
+ { Pos2(152, 16), Explosion::Type::Type_1 },
+ { Pos2(128, 56), Explosion::Type::Type_0 },
+ { Pos2(184, 40), Explosion::Type::Type_2 },
+};
+
+// Hitbox
+constexpr HitBox HBOX_BULLET = { Pos2(1,1), Rect2(0,0,8-1*2, 8-1*2)};
+constexpr HitBox HBOX_HEAD = { Pos2(24,-8), Rect2(0, 0, 24, 32) };
+constexpr HitBox HBOX_DORSA = { Pos2(8,4), Rect2(0, 0, 32, 12) };
+constexpr HitBox HBOX_ANAL = { Pos2(8,0), Rect2(0, 0, 32, 16) };
+constexpr HitBox HBOX_BODY[] =
+{
+ { Pos2( 40, 16), Rect2(0, 0, 80, 48) },
+ { Pos2(120, 24), Rect2(0, 0, 72, 32) },
+ { Pos2(88, 0), Rect2(0, 0, 32, 16) },
+};
+
+//
+}
+
+//
+class KingFossil::Head : public Boss::Partial
+{
+ public:
+ Head(KingFossil* parent)
+ : Boss::Partial(*parent, *parent->sprite(),
+ {
+ -8, 32,
+ {
+ CellRect( 0, 72, 56, 40),
+ CellRect( 56, 72, 56, 40),
+ CellRect(112, 72, 56, 48),
+ CellRect(168, 72, 56, 56),
+ },
+ {
+ Seq(Seq::Draw, 0, 12_u8), // 0
+ Seq(Seq::Draw, 1, 6_u8), // 12
+ Seq(Seq::Draw, 2, 6_u8), // 18
+ Seq(Seq::Draw, 3, 48_u8), // 24
+ Seq(Seq::Draw, 2, 6_u8), // 72
+ Seq(Seq::Draw, 1, 6_u8), // 78
+ Seq(Seq::Draw, 0, 12_u8), // 84
+ Seq(Seq::Goto, 0_u8), // 96
+ }
+ },
+ 1, -1)
+ {
+ _hbox = HBOX_HEAD;
+ }
+
+ protected:
+ void onHit(GameObj* o, const Rect2& r) override
+ {
+ if(o->category() != CATEGORY_BULLET) { return; }
+
+ if(_animation.index() == 3)
+ {
+ Pos2i spos(r.center().x() - static_cast(pos().x()),
+ r.center().y() - static_cast(pos().y()));
+ explode(spos);
+
+ _parent.onHit(o, r);
+ if(_parent.hp() <= 0) { disableHit(); }
+ }
+ else
+ {
+ reflect(r.center());
+ }
+ }
+};
+
+//
+class KingFossil::PelvicFin : public Boss::Partial
+{
+ public:
+ PelvicFin(KingFossil* parent)
+ : Boss::Partial(*parent, *parent->sprite(),
+ { 48, 56,
+ {
+ CellRect( 0, 168, 48, 24),
+ CellRect( 48, 168, 48, 24),
+ CellRect( 96, 168, 48, 16),
+ },
+ {
+ Seq(Seq::Draw, 0, 12_u8),
+ Seq(Seq::Draw, 1, 12_u8),
+ Seq(Seq::Draw, 2, 12_u8),
+ Seq(Seq::Draw, 1, 12_u8),
+ Seq(Seq::Goto, 0_u8),
+ }
+ },
+ 1, -1)
+ {}
+};
+
+//
+class KingFossil::DorsaFin : public Boss::Partial
+{
+ public:
+ DorsaFin(KingFossil* parent)
+ : Boss::Partial(*parent, *parent->sprite(),
+ { 88, -16,
+ {
+ CellRect(0, 200, 64, 32)
+ },
+ { // No animation
+ Seq(Seq::Draw, 0, 255_u8),
+ Seq(Seq::Goto, 0_u8),
+
+ }
+ },
+ 1, -1,
+ HP_DORSAFIN,
+ PTS_BOSS_PARTIAL)
+ {
+ _hbox = HBOX_DORSA;
+ }
+};
+
+//
+class KingFossil::AnalFin : public Boss::Partial
+{
+ public:
+ AnalFin(KingFossil* parent)
+ : Boss::Partial(*parent, *parent->sprite(),
+ { 112, 56,
+ {
+ CellRect(152, 184, 64, 24),
+ CellRect(152, 128, 64, 24),
+ CellRect(152, 152, 64, 32),
+ },
+ {
+ Seq(Seq::Draw, 0, 6_u8),
+ Seq(Seq::Draw, 1, 6_u8),
+ Seq(Seq::Draw, 2, 16_u8),
+ Seq(Seq::Draw, 1, 6_u8),
+ Seq(Seq::Draw, 0, 6_u8),
+ Seq(Seq::Goto, 0_u8),
+ }
+ },
+ 1, -1,
+ HP_ANALFIN,
+ PTS_BOSS_PARTIAL)
+ {
+ _hbox = HBOX_ANAL;
+ }
+};
+
+//
+class KingFossil::AdiposeFin : public Boss::Partial
+{
+ public:
+ AdiposeFin(KingFossil* parent)
+ : Boss::Partial(*parent, *parent->sprite(),
+ { 124, 0,
+ {
+ CellRect( 64, 200, 32, 24),
+#if 0
+ CellRect( 96, 200, 32, 24),
+ CellRect(128, 200, 32, 24),
+#endif
+ },
+ {
+ Seq(Seq::Draw, 0, 2_u8),
+#if 0
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Draw, 2, 4_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Draw, 0, 2_u8),
+#endif
+ Seq(Seq::Goto, 0_u8),
+ }
+ },
+ 1, -1)
+ {}
+};
+
+//
+class KingFossil::Tail : public Boss::Partial
+{
+ public:
+ Tail(KingFossil* parent)
+ : Boss::Partial(*parent, *parent->sprite(),
+ { 152, 16,
+ {
+ CellRect(152, 16, 64, 48),
+ CellRect( 0, 112, 56, 48),
+ CellRect( 56, 112, 48, 48),
+ },
+ {
+ Seq(Seq::Draw, 0, 8_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Draw, 2, 8_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Draw, 0, 8_u8),
+ Seq(Seq::Goto, 0_u8),
+ }
+ },
+ 1, -1)
+ {}
+};
+
+//
+KingFossil::Bullet::Bullet(GameObj& parent, GCellSprite4& sprite, float radian, KingFossil& kf)
+ : Boss::Bullet(parent, sprite,
+ { -4, 4,
+ {
+ CellRect(176,208, 8, 8),
+ CellRect(184,208, 8, 8),
+ CellRect(192,208, 8, 8),
+ CellRect(200,208, 8, 8),
+ CellRect(208,208, 8, 8),
+ CellRect(216,208, 8, 8),
+ },
+ {
+ Seq(Seq::Draw, 0, 4_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Draw, 2, 4_u8),
+ Seq(Seq::Draw, 3, 4_u8),
+ Seq(Seq::Draw, 4, 4_u8),
+ Seq(Seq::Draw, 5, 4_u8),
+ Seq(Seq::Goto, 3_u8),
+ }
+ })
+ , _kf(kf)
+{
+ _behavior = new UniformLinearBehavior(*this, VELOCITY, radian);
+ assert(_behavior);
+ _hbox = HBOX_BULLET;
+ move(parent.x() + _animation.rx, parent.y() + _animation.ry);
+}
+KingFossil::Bullet::~Bullet()
+{
+ // TD_PRINT_FUNCTION();
+ goblib::safeDelete(_behavior);
+}
+
+void KingFossil::Bullet::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ // Don't delete, return to object pool.
+ _kf._bullets.destruct(this);
+ GameObj::onUnchain();
+}
+
+void KingFossil::Bullet::onExecute(const float delta)
+{
+ Rect2 r = Rect2(pos(), 8, 8);
+ Boss::Bullet::onExecute(delta);
+ if(!r.overlaps(FIELD_RECT)) { release(); }
+}
+
+//
+class KingFossil::Launcher : public Boss::Partial
+{
+ public:
+ Launcher(KingFossil* parent)
+ : Boss::Partial(*parent, *parent->sprite(),
+ { 16, 40,
+ {
+ CellRect(104,120, 32, 16),
+ CellRect(104,136, 32, 16),
+ CellRect(104,152, 32, 16)
+ },
+ { // Synchronize with the animation of head.
+ Seq(Seq::Offset, 0_i16, 0_i16),
+ Seq(Seq::Draw, 0, 12_u8), // 0
+ Seq(Seq::Offset, -4_i16, 0_i16),
+ Seq(Seq::Draw, 0, 3_u8), // 12
+ Seq(Seq::Offset, -8_i16, 0_i16),
+ Seq(Seq::Draw, 0, 3_u8), // 15
+ Seq(Seq::Offset, -12_i16, 0_i16),
+ Seq(Seq::Draw, 0, 3_u8), // 18
+ Seq(Seq::Offset, -16_i16, 0_i16),
+ Seq(Seq::Draw, 0, 3_u8), // 21
+ Seq(Seq::LoopS, 3),
+ Seq(Seq::Draw, 1, 4_u8), // 24,40,56
+ Seq(Seq::Draw, 2, 4_u8), // 28,44,60
+ Seq(Seq::Callback),
+ Seq(Seq::Draw, 2, 4_u8), // 32,48,64
+ Seq(Seq::Draw, 0, 4_u8), // 36,52,68
+ Seq(Seq::LoopE),
+ Seq(Seq::Offset, -12_i16, 0_i16),
+ Seq(Seq::Draw, 0, 3_u8), // 72
+ Seq(Seq::Offset, -8_i16, 0_i16),
+ Seq(Seq::Draw, 0, 3_u8), // 75
+ Seq(Seq::Offset, -4_i16, 0_i16),
+ Seq(Seq::Draw, 0, 3_u8), // 78
+ Seq(Seq::Offset, -0_i16, 0_i16),
+ Seq(Seq::Draw, 0, 3_u8), // 81
+ Seq(Seq::Draw, 0, 12_u8), // 84
+ Seq(Seq::Goto, 0_u8), // 96
+ }
+ },
+ 1, 1)
+ , _kf(*parent)
+ {
+ _animation.sequencer.setCallback(shoot, this);
+ }
+
+ static void shoot(void* arg, std::uint8_t cur)
+ {
+ static_cast(arg)->_shoot(cur);
+ }
+
+ void _shoot(std::uint8_t /*cur*/)
+ {
+ for(std::int_fast8_t i=0; i < 5; ++i)
+ {
+ // auto bullet = new KingFossil::Bullet(*this, _sprite, goblib::math::deg2rad(180.0f - 45.0f + i * 22.5f));
+ auto bullet = _kf._bullets.construct(*this, _sprite, goblib::math::deg2rad(180.0f - 45.0f + i * 22.5f),_kf);
+ assert(bullet);
+ TinyDarius::instance().reserveInsertNode(bullet, this);
+ }
+ }
+
+ private:
+ KingFossil& _kf;
+};
+
+
+//
+const char KingFossil::_name[] = "KING FOSSIL";
+
+KingFossil::KingFossil() : Boss(PTS_BOSS_BASE * 1), _bullets(BULLET_MAX)
+
+{
+ _bodyRect = BODY_RECT;
+ _hp = HP_BODY;
+
+ _behavior = new FlutteringBehavior(*this, APPEAR_TO, 32);
+}
+
+KingFossil::~KingFossil()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+bool KingFossil::onInitialize()
+{
+ // *1 Wait until all children are initialized.
+ if(_sprite->getBuffer())
+ {
+ return std::all_of(_partials.begin(), _partials.end(), [](const Partial* p) { return p->isExecute(); })
+ && std::all_of(_hits.begin(), _hits.end(), [](const Hit* p) { return p->isExecute(); });
+ }
+
+ if(createFromBitmap(*_sprite, BITMAP_PATH))
+ {
+ _partials.emplace_back(new Head(this));
+ _partials.emplace_back(new PelvicFin(this));
+ _partials.emplace_back(new DorsaFin(this));
+ _partials.emplace_back(new AnalFin(this));
+ _partials.emplace_back(new AdiposeFin(this));
+ _partials.emplace_back(new Tail(this));
+ _partials.emplace_back(new Launcher(this));
+
+ TinyDarius& app = TinyDarius::instance();
+ for(auto& e : _partials)
+ {
+ assert(e);
+ app.reserveInsertNode(e, this);
+ }
+
+ std::for_each(std::begin(HBOX_BODY), std::end(HBOX_BODY), [this](const HitBox& h)
+ {
+ auto hb = new Boss::Hit(*this, h);
+ assert(hb);
+ this->_hits.push_back(hb);
+ TinyDarius::instance().reserveInsertNode(hb, this);
+ });
+
+ Boss::onInitialize();
+
+ applyPaletteColors(*_sprite, GRAY_SCALE, goblib::size(GRAY_SCALE));
+ _apalette->from(*_sprite);
+ _sprite->makePaletteTable256();
+ return false; // see also *1
+ }
+
+ kill();
+ return false;
+}
+
+bool KingFossil::onRelease()
+{
+ return Boss::onRelease();
+}
+
+void KingFossil::onExecute(const float delta)
+{
+ Boss::onExecute(delta);
+}
+
+void KingFossil::render(void* arg)
+{
+ Boss::render(arg);
+}
+
+void KingFossil::appear()
+{
+ Boss::_appear(GRAY_SCALE, goblib::size(GRAY_SCALE), APPEAR_TO, APPEAR_TIMES);
+}
+
+void KingFossil::defeat()
+{
+ Boss::_defeat(GRAY_SCALE, goblib::size(GRAY_SCALE), EXPLOSION_TABLE, goblib::size(EXPLOSION_TABLE));
+}
+
+void KingFossil::escape()
+{
+ Boss::_escape(GRAY_SCALE, goblib::size(GRAY_SCALE));
+}
diff --git a/src/boss/king_fossil.hpp b/src/boss/king_fossil.hpp
new file mode 100644
index 0000000..c4bce28
--- /dev/null
+++ b/src/boss/king_fossil.hpp
@@ -0,0 +1,70 @@
+/*!
+ TinyDarius
+
+ @file king_fossil.hpp
+ @brief Coelacanth
+*/
+#pragma once
+#ifndef TD_KING_FOSSIL_HPP
+#define TD_KING_FOSSIL_HPP
+
+#include "boss.hpp"
+#include
+
+namespace goblib { namespace lgfx { class GCellSprite4; }}
+
+
+class KingFossil : public Boss
+{
+ public:
+ class Head;
+ class PelvicFin;
+ class DorsaFin;
+ class AnalFin;
+ class AdiposeFin;
+ class Tail;
+ class Launcher;
+
+ class Bullet : public Boss::Bullet
+ {
+ constexpr static float VELOCITY = 6.0f;
+
+ public:
+ Bullet(GameObj& parent, goblib::lgfx::GCellSprite4& sprite, const float radian, KingFossil& kf);
+ ~Bullet();
+
+ protected:
+ virtual void onUnchain() override;
+ virtual void onExecute(const float delta) override;
+
+ private:
+ KingFossil& _kf;
+ };
+
+ KingFossil();
+ virtual ~KingFossil();
+
+ virtual const char* name() const { return _name; }
+
+ virtual void render(void* arg) override;
+
+ protected:
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ //virtual void onReceive (const Task::Message& /*msg*/) override {}
+ virtual void onExecute(const float delta) override;
+
+ virtual void appear() override;
+ virtual void escape() override;
+ virtual void defeat() override;
+
+ private:
+
+ goblib::ObjectPool _bullets;
+
+ constexpr static std::size_t BULLET_MAX = 15; // 5way * 3times
+ static const char _name[];
+ // constexpr static std::int32_t _width = 200;
+ // constexpr static std::int32_t _height = 80;
+};
+#endif
diff --git a/src/constants.hpp b/src/constants.hpp
new file mode 100644
index 0000000..f410f17
--- /dev/null
+++ b/src/constants.hpp
@@ -0,0 +1,149 @@
+/*!
+ TinyDarius
+
+ @file constants.hpp
+ @brief Constants definition
+*/
+#ifndef TD_CONSTANTS_HPP
+#define TD_CONSTANTS_HPP
+
+#include
+#include "typedef.hpp"
+
+// Screen width/height
+constexpr std::int32_t SCREEN_WIDTH = 320;
+constexpr std::int32_t SCREEN_HEIGHT = 240;
+
+// Palette definition using RGB888.
+constexpr std::uint32_t CLR_BLACK = 0x00000000U;
+constexpr std::uint32_t CLR_WHITE = 0x00FFFFFFU;
+constexpr std::uint32_t CLR_GRAY = 0x00808080U;
+constexpr std::uint32_t CLR_RED = 0x00FF0000U;
+constexpr std::uint32_t CLR_YELLOW = 0x00FFFF00U;
+constexpr std::uint32_t CLR_MAGENTA = 0x00FF00FFU;
+constexpr std::uint32_t CLR_GREEN = 0x0000FF00U;
+
+// Transpalent palette index
+constexpr std::int32_t TRANPARENT_PALETTE_INDEX = 0;
+
+// Task priority
+constexpr std::uint32_t PRIORITY_DEBUG = 0;
+constexpr std::uint32_t PRIORITY_SCENE_MAANGER = 0;
+constexpr std::uint32_t PRIORITY_ADVERTISE = 100;
+constexpr std::uint32_t PRIORITY_GAME = 100;
+constexpr std::uint32_t PRIORITY_STAGE = 100;
+constexpr std::uint32_t PRIORITY_WAVESPOT = 200;
+constexpr std::uint32_t PRIORITY_ROCKSURFACE = 210;
+constexpr std::uint32_t PRIORITY_BRANCHROCK = 210;
+constexpr std::uint32_t PRIORITY_SILVERHAWK = 1000;
+constexpr std::uint32_t PRIORITY_BULLET = 1001;
+constexpr std::uint32_t PRIORITY_BOSS = 1100;
+constexpr std::uint32_t PRIORITY_BOSS_BULLET = 1110;
+constexpr std::uint32_t PRIORITY_ENEMY = 1200;
+constexpr std::uint32_t PRIORITY_ENEMY_BULLET = 1210;
+constexpr std::uint32_t PRIORITY_EXPLOSION = 2000;
+constexpr std::uint32_t PRIORITY_MESSAGE = 2100;
+constexpr std::uint32_t PRIORITY_INFORMATION = 2200;
+constexpr std::uint32_t PRIORITY_MASK = 2300;
+
+// Task category(for Hitcheck)
+constexpr std::uint32_t CATEGORY_BIT_BULLET = 0X80000000;
+constexpr std::uint32_t CATEGORY_BIT_BOSS = 0X40000000;
+constexpr std::uint32_t CATEGORY_BIT_ENEMY = 0X20000000;
+constexpr std::uint32_t CATEGORY_BIT_OBSTACLE= 0X10000000;
+constexpr std::uint32_t CATEGORY_BIT_PLAYER = 0X08000000;
+//
+constexpr std::uint32_t CATEGORY_NONE = 0X00000000;
+constexpr std::uint32_t CATEGORY_PLAYER = CATEGORY_BIT_PLAYER;;
+constexpr std::uint32_t CATEGORY_BULLET = CATEGORY_BIT_PLAYER | CATEGORY_BIT_BULLET;
+constexpr std::uint32_t CATEGORY_BOSS = CATEGORY_BIT_BOSS;
+constexpr std::uint32_t CATEGORY_ENEMY = CATEGORY_BIT_ENEMY;
+constexpr std::uint32_t CATEGORY_ENEMY_BULLET = CATEGORY_BIT_ENEMY | CATEGORY_BIT_BULLET;
+constexpr std::uint32_t CATEGORY_WALL = CATEGORY_BIT_OBSTACLE;
+
+
+// Rendering order (like the Z length)
+constexpr std::uint32_t ORDER_DEBUG = 0;
+constexpr std::uint32_t ORDER_ADVERTISEE = 1000;
+constexpr std::uint32_t ORDER_MESSAGE = 40;
+constexpr std::uint32_t ORDER_INFORMATION = 50;
+constexpr std::uint32_t ORDER_MASK = 75;;
+constexpr std::uint32_t ORDER_EXPLOSION = 100;
+constexpr std::uint32_t ORDER_ROCKSURFACE = 200;
+constexpr std::uint32_t ORDER_BRANCHROCK = 200;
+constexpr std::uint32_t ORDER_EXPLOSION_BOSS = 250;
+constexpr std::uint32_t ORDER_EXPLOSION_ENEMY = 260;
+constexpr std::uint32_t ORDER_BOSS_BULLET = 500;
+constexpr std::uint32_t ORDER_ENEMY = 510;
+constexpr std::uint32_t ORDER_ENEMY_BULLET = 520;
+constexpr std::uint32_t ORDER_BULLET = 900;
+constexpr std::uint32_t ORDER_SILVERHAWK = 1000;
+constexpr std::uint32_t ORDER_BOSS_BODY = 2000;
+constexpr std::uint32_t ORDER_WAVESPOT = 5000;
+
+// Clipping rect
+constexpr Rect2 SCREEN_RECT = { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT };
+constexpr Rect2 FIELD_RECT = { 0, 24, SCREEN_WIDTH, SCREEN_HEIGHT - 24*2 };
+
+// Task message
+constexpr std::uint32_t MSG_DEFEAT_BOSS = 52;
+constexpr std::uint32_t MSG_VANISH_ENEMY = 53;
+constexpr std::uint32_t MSG_HIDE_EFFECT = 54;
+constexpr std::uint32_t MSG_ROUND_CLEAR = 55;
+constexpr std::uint32_t MSG_GAME_OVER = 56;
+
+// Scene ID
+constexpr std::uint32_t SCENE_DEBUG = 1;
+constexpr std::uint32_t SCENE_DEBUG_SOUND = 2;
+constexpr std::uint32_t SCENE_ADVERTISE = 10;
+constexpr std::uint32_t SCENE_GAME = 100;
+constexpr std::uint32_t SCENE_STAGE = 101;
+
+// BGM ID (Streaming by SD file)
+enum BGM : std::uint8_t
+{
+ InsertCoin,
+ InsertCoinB,
+ // for stage
+ CaptainNeo,
+ ChaosMainTheme,
+ CosmicAirWay,
+ InorganicBeat,
+ TheSea,
+ // for boss
+ Warning,
+ BOSS_1,
+ BOSS_2,
+ BOSS_3,
+ BOSS_4,
+ BOSS_5,
+ BOSS_6,
+ BOSS_7,
+ RoundClear,
+ // for gameover
+ Ending,
+ Name,
+ Gameover,
+ Max
+};
+
+// SFX ID
+enum class SFX : std::uint8_t
+{
+ InsertCoin,
+ InsertCoinB,
+ Shhot,
+ Explosion,
+ Max
+};
+
+
+// Score
+constexpr std::uint32_t PTS_REMAIN_BONUS = 100000;
+constexpr std::uint32_t PTS_TIME_BONUS = 100;
+constexpr std::uint32_t PTS_MINE = 100;
+constexpr std::uint32_t PTS_YAZUKA = 200;
+constexpr std::uint32_t PTS_BOSS_PARTIAL = 5000;
+constexpr std::uint32_t PTS_BOSS_BASE = 10000;
+
+#endif
diff --git a/src/debug.hpp b/src/debug.hpp
new file mode 100644
index 0000000..076f05a
--- /dev/null
+++ b/src/debug.hpp
@@ -0,0 +1,40 @@
+/*!
+ TinyDarius
+
+ @file debug.hpp
+ @brief For debug
+*/
+#pragma once
+#ifndef TD_DEBUG_HPP
+#define TD_DEBUG_HPP
+
+#if !defined(NDEBUG) || defined(DOXYGEN_PROCESS)
+
+#include
+#include
+#include
+
+
+#define TD_PRINT_FUNCTION() do { printf("%s\n", __PRETTY_FUNCTION__); }while(0)
+#define TD_PRINTF(fmt, ...) do { printf((fmt), __VA_ARGS__); }while(0)
+
+#else
+
+#define TD_PRINT_FUNCTION() /* nop */
+#define TD_PRINTF(fmt, ...) /* nop */
+
+#endif
+
+
+#if defined(GOBLIB_ENABLE_PROFILE)
+using EspInstrument = goblib::profile::MeasuringInstrument;
+#define TD_SCOPED_PROFILE(tag) EspInstrument GOBLIB_CONCAT(pf_,__LINE__)((#tag))
+
+#else
+
+#define TD_SCOPED_PROFILE(tag) /* nop */
+
+#endif
+
+//
+#endif
diff --git a/src/df88.hpp b/src/df88.hpp
new file mode 100644
index 0000000..d53af8e
--- /dev/null
+++ b/src/df88.hpp
@@ -0,0 +1,13 @@
+/*!
+ TinyDarius
+
+ @file df88.hpp
+ @brief 8x8 Font
+*/
+#ifdef LGFX_USE_V1
+#include
+#else
+#include
+#endif
+
+extern const ::lgfx::GFXfont df88_gfx_font;
diff --git a/src/effect/block_mask.cpp b/src/effect/block_mask.cpp
new file mode 100644
index 0000000..d59d523
--- /dev/null
+++ b/src/effect/block_mask.cpp
@@ -0,0 +1,112 @@
+/*!
+ TinyDarius
+
+ @file block_mask.cpp
+ @brief Mask by block
+*/
+#include
+
+#include "block_mask.hpp"
+#include "../debug.hpp"
+#include "../renderer.hpp"
+#include "../constants.hpp"
+#include
+using goblib::lgfx::GSprite;
+
+namespace
+{
+/* fill order
+ 00,01,02,03
+ 11,12,13,04
+ 10,15,14,05
+ 09,08,07,06
+*/
+constexpr Pos2i MASK_TABLE[16] =
+{
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 0, BlockMask::INNER_BLOCK_HEIGHT * 0),
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 1, BlockMask::INNER_BLOCK_HEIGHT * 0),
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 2, BlockMask::INNER_BLOCK_HEIGHT * 0),
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 3, BlockMask::INNER_BLOCK_HEIGHT * 0),
+ //
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 3, BlockMask::INNER_BLOCK_HEIGHT * 1),
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 3, BlockMask::INNER_BLOCK_HEIGHT * 2),
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 3, BlockMask::INNER_BLOCK_HEIGHT * 3),
+ //
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 2, BlockMask::INNER_BLOCK_HEIGHT * 3),
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 1, BlockMask::INNER_BLOCK_HEIGHT * 3),
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 0, BlockMask::INNER_BLOCK_HEIGHT * 3),
+ //
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 0, BlockMask::INNER_BLOCK_HEIGHT * 2),
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 0, BlockMask::INNER_BLOCK_HEIGHT * 1),
+ //
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 1, BlockMask::INNER_BLOCK_HEIGHT * 1),
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 2, BlockMask::INNER_BLOCK_HEIGHT * 1),
+ //
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 2, BlockMask::INNER_BLOCK_HEIGHT * 2),
+ //
+ Pos2i(BlockMask::INNER_BLOCK_WIDTH * 1, BlockMask::INNER_BLOCK_HEIGHT * 2),
+};
+
+// If the bit is standing, draw inner block.
+constexpr std::uint32_t STEP_BIT[16] =
+{
+ 0xFFFF, 0xFFFE, 0xFFFC,0xFFF8,
+ 0xFFF0, 0xFFE0, 0xFFC0,0xFF80,
+ 0xFF00, 0xFE00, 0xFC00,0xF800,
+ 0xF000, 0xE000, 0xC000,0x8000,
+};
+
+
+}
+BlockMask::BlockMask()
+ : GameObj(PRIORITY_MASK, ORDER_MASK, CATEGORY_NONE, "blockmask")
+{
+}
+
+BlockMask::~BlockMask()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void BlockMask::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+void BlockMask::onExecute(const float delta)
+{
+ if(!busy()) { release(); }
+ GameObj::onExecute(delta);
+}
+
+void BlockMask::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ ScopedClip(*target, FIELD_RECT, rarg->yorigin);
+
+ for(std::int32_t y = 0; y < FIELD_RECT.height(); y += BLOCK_HEIGHT)
+ {
+ for(std::int32_t x = 0; x < FIELD_RECT.width(); x += BLOCK_WIDTH)
+ {
+ fill(target, x, y + rarg->yorigin, step());
+ }
+ }
+}
+
+void BlockMask::fill(GSprite* sprite, const std::int32_t x, const std::int32_t y, const std::uint32_t step)
+{
+ if(step >=16) { return; }
+
+ std::uint32_t bit = STEP_BIT[step];
+
+ for(std::int_fast8_t i = 0; i < 16; ++i)
+ {
+ if(bit & (1UL << i))
+ {
+ sprite->fillRect(x + MASK_TABLE[i].x(), y + MASK_TABLE[i].y(), INNER_BLOCK_WIDTH, INNER_BLOCK_HEIGHT, CLR_BLACK);
+ }
+ }
+}
diff --git a/src/effect/block_mask.hpp b/src/effect/block_mask.hpp
new file mode 100644
index 0000000..f8130d6
--- /dev/null
+++ b/src/effect/block_mask.hpp
@@ -0,0 +1,39 @@
+/*!
+ TinyDarius
+
+ @file block_mask.hpp
+ @brief Mask by block
+*/
+#pragma once
+#ifndef TD_BLOCK_MASK_HPP
+#define TD_BLOCK_MASK_HPP
+
+#include
+#include "../game_obj.hpp"
+#include
+
+/*! @brief Masking by spiral blocks */
+class BlockMask : public GameObj
+{
+ public:
+ constexpr static std::int32_t BLOCK_WIDTH = 16;
+ constexpr static std::int32_t BLOCK_HEIGHT = 16;
+ constexpr static std::int32_t INNER_BLOCK_WIDTH = 4;
+ constexpr static std::int32_t INNER_BLOCK_HEIGHT = 4;
+
+ BlockMask();
+ virtual~BlockMask();
+ virtual void render(void* arg) override;
+
+ GOBLIB_INLINE bool busy() const { return step() < 16; }
+
+ protected:
+ virtual void onUnchain() override;
+ virtual void onExecute(const float delta);
+
+
+ private:
+ GOBLIB_INLINE std::uint32_t step() const { return counter() >> 1; }
+ void fill(goblib::lgfx::GSprite* sprite, const std::int32_t x, const std::int32_t y, const std::uint32_t step);
+};
+#endif
diff --git a/src/effect/explosion.cpp b/src/effect/explosion.cpp
new file mode 100644
index 0000000..33274d8
--- /dev/null
+++ b/src/effect/explosion.cpp
@@ -0,0 +1,200 @@
+/*!
+ TinyDarius
+
+ @file explosion.cpp
+ @brief Explosion
+*/
+#include
+
+#include "explosion.hpp"
+#include "../debug.hpp"
+#include "../app.hpp"
+#include "../renderer.hpp"
+#include "../utility.hpp"
+#include "../constants.hpp"
+
+#include
+#include
+using goblib::lgfx::GSprite;
+using goblib::lgfx::GCellSprite4;
+#include
+#include
+using goblib::suffix::operator"" _u8;
+
+#include
+#include
+#include
+
+using goblib::lgfx::CellRect;
+using Seq = goblib::graph::Sequence;
+
+namespace
+{
+constexpr static char BITMAP_PATH[] = "/res/td/bomb.bmp";
+}
+
+const PartialAnimation Explosion::_animations[Explosion::Type::Max] =
+{
+ // type 0
+ {
+ 0,0,
+ {
+ CellRect( 0, 0, 32, 32),
+ CellRect( 32, 0, 32, 32),
+ CellRect( 64, 0, 32, 32),
+ CellRect( 96, 0, 32, 32),
+ CellRect(128, 0, 32, 32),
+ },
+ {
+ Seq(Seq::Draw, 0, 4_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Draw, 2, 6_u8),
+ Seq(Seq::Draw, 3, 6_u8),
+ Seq(Seq::Draw, 4, 4_u8),
+ }
+ },
+ // type 1
+ {
+ 0,0,
+ {
+ CellRect( 0, 32, 32, 32),
+ CellRect( 32, 32, 32, 32),
+ CellRect( 64, 32, 32, 32),
+ },
+ {
+ Seq(Seq::Draw, 0, 4_u8),
+ Seq(Seq::Draw, 1, 6_u8),
+ Seq(Seq::Draw, 2, 4_u8),
+ }
+ },
+ // type 2
+ {
+ 0,0,
+ {
+ CellRect( 96, 32, 32, 32),
+ CellRect(128, 32, 32, 32),
+ CellRect(160, 32, 32, 32),
+ CellRect(192, 32, 32, 32),
+ CellRect(224, 32, 32, 32),
+ },
+ {
+ Seq(Seq::Draw, 0, 4_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Draw, 2, 6_u8),
+ Seq(Seq::Draw, 3, 6_u8),
+ Seq(Seq::Draw, 4, 4_u8),
+ }
+ }
+};
+
+
+goblib::lgfx::GCellSprite4* Explosion::_sprite;
+
+
+void Explosion::setup()
+{
+ ScopedReleaseBus();
+
+ if(_sprite) { return; }
+
+ _sprite = new goblib::lgfx::GCellSprite4();
+ assert(_sprite);
+ if(!createFromBitmap(*_sprite, BITMAP_PATH)) { assert(0); }
+ _sprite->makePaletteTable256();
+}
+
+void Explosion::finalize()
+{
+ goblib::safeDelete(_sprite);
+
+}
+
+Explosion::Explosion(const Pos2::pos_type cx, const Pos2::pos_type cy, std::uint32_t order, Type t, GameObj* follow, std::int32_t apos, bool repeat)
+ : GameObj(PRIORITY_EXPLOSION, order, CATEGORY_NONE, "explosion")
+ , _cpos(cx,cy)
+ , _animation()
+ , _follow(follow)
+ , _repeat(repeat)
+{
+ std::mt19937 re(esp_random()); // esp_random() is hardware RNG.
+ if(t == Type::Random)
+ {
+ std::uniform_int_distribution<> dist(0, goblib::to_underlying(Type::Max)-1);
+ t = static_cast(dist(re));
+ }
+ assert(t >=0 );
+ assert(t < Type::Max);
+ t = static_cast(goblib::clamp(goblib::to_underlying(t), goblib::to_underlying(Type::Type_0), goblib::to_underlying(Type::Type_2)));
+ _animation = _animations[t];
+
+ auto left = cx + (_follow ? _follow->x() : Pos2::pos_type(0)) - WIDTH/2;
+ auto top = cy + (_follow ? _follow->y() : Pos2::pos_type(0)) - HEIGHT/2;
+ move(left, top);
+
+ if(apos < 0)
+ {
+ std::uniform_int_distribution<> dist(0U, _animation.sequencer.stepSize() -1);
+ apos = dist(re);
+ }
+ while(apos-- > 0) { _animation.pump(); }
+
+}
+
+Explosion:: ~Explosion()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void Explosion::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool Explosion::onInitialize()
+{
+ return GameObj::onInitialize();
+}
+
+bool Explosion::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void Explosion::onReceive(const goblib::TaskMessage& msg)
+{
+ if(msg.msg == MSG_HIDE_EFFECT)
+ {
+ release();
+ }
+}
+
+void Explosion::onExecute(const float delta)
+{
+ if(_follow)
+ {
+ move(_cpos.x() + _follow->x() - WIDTH/2, _cpos.y() + _follow->y() - HEIGHT/2);
+ }
+
+ _animation.pump();
+
+ if(_animation.isFinish())
+ {
+ if(_repeat)
+ {
+ _animation.reset();
+ }
+ else
+ {
+ release();
+ }
+ }
+}
+
+void Explosion::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ _sprite->pushCellTo16(target, _animation.rect(), static_cast(x()), static_cast(y()) + rarg->yorigin, 0);
+}
diff --git a/src/effect/explosion.hpp b/src/effect/explosion.hpp
new file mode 100644
index 0000000..bdb2620
--- /dev/null
+++ b/src/effect/explosion.hpp
@@ -0,0 +1,72 @@
+/*!
+ TinyDarius
+
+ @file explosion.hpp
+ @brief Explosion
+*/
+#pragma once
+#ifndef TD_EXPLOSION_HPP
+#define TD_EXPLOSION_HPP
+
+#include "../game_obj.hpp"
+#include "../typedef.hpp"
+#include "../partial_animation.hpp"
+#include "../constants.hpp"
+#include
+
+namespace goblib { namespace lgfx {
+class GCellSprite4;
+}}
+
+class Explosion : public GameObj
+{
+ public:
+ enum Type : std::int32_t
+ {
+ Random = -1,
+ Type_0,
+ Type_1,
+ Type_2,
+ Max
+ };
+
+ static void setup();
+ static void finalize();
+
+ Explosion(const Pos2& pos, std::uint32_t order, Type t = Type::Random, GameObj* follow = nullptr, std::int32_t apos = 0, bool repeat = false)
+ : Explosion(pos.x(), pos.y(), order, t, follow, apos, repeat) {}
+ /*
+ @param cx Relative center-left from following GameObj (Absolute if follow is nullptr)
+ @param cy Relative center-top from following GameObj (Absolute if follow is nullptr)
+ @param order rendering order
+ @param t Animation type
+ @param follow GameObj to follow
+ @param apos Start sequence position for animation(start random position if negative)
+ @param repeat Playing animation loop inifinty?
+ */
+ Explosion(const Pos2::pos_type cx, const Pos2::pos_type cy, std::uint32_t order, Type t = Type::Random, GameObj* follow = nullptr, std::int32_t apos = 0, bool repeat = false);
+ virtual ~Explosion();
+
+ virtual void render(void* arg) override;
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ virtual void onReceive (const goblib::TaskMessage& msg) override;
+ virtual void onExecute(const float delta) override;
+
+ static goblib::lgfx::GCellSprite4* _sprite;;
+ static const PartialAnimation _animations[Type::Max];
+
+ private:
+ const Pos2 _cpos;
+ PartialAnimation _animation;
+ const GameObj* _follow;
+ const bool _repeat;
+
+ constexpr static std::int32_t WIDTH = 32;
+ constexpr static std::int32_t HEIGHT = 32;
+};
+
+#endif
diff --git a/src/enemy/enemy.cpp b/src/enemy/enemy.cpp
new file mode 100644
index 0000000..6656f9e
--- /dev/null
+++ b/src/enemy/enemy.cpp
@@ -0,0 +1,437 @@
+/*!
+ TinyDarius
+
+ @file enemy.cpp
+ @brief Enemies and effect
+*/
+#include
+
+#include "enemy.hpp"
+#include "../debug.hpp"
+#include "../app.hpp"
+#include "../game.hpp"
+#include "../renderer.hpp"
+#include "../utility.hpp"
+#include "../constants.hpp"
+#include "../behavior.hpp"
+
+#include
+using goblib::lgfx::GSprite;
+using goblib::lgfx::GCellSprite4;
+#include
+using goblib::suffix::operator"" _u8;
+#include
+#include
+
+#include
+#include
+#include
+
+using goblib::lgfx::CellRect;
+using Seq = goblib::graph::Sequence;
+
+namespace
+{
+constexpr char BITMAP_PATH[] = "/res/td/enemy.bmp";
+
+constexpr HitBox HBOX_MINE = { Pos2(2,2), Rect2(0, 0, 16-2*2, 16-2*2) };
+constexpr HitBox HBOX_YAZUKA = { Pos2(2,2), Rect2(0, 0, 16-2*2, 16-2*2) };
+}
+
+goblib::lgfx::GCellSprite4* Enemy::_sprite;
+
+const PartialAnimation Enemy::_animation_table[] =
+{
+ { // Mine
+ 0,0,
+ {
+ CellRect( 24, 32, 16, 16),
+ CellRect( 40, 32, 16, 16),
+ CellRect( 56, 32, 16, 16),
+ CellRect( 72, 32, 16, 16),
+ },
+ {
+ Seq(Seq::Draw, 0, 4_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Draw, 2, 4_u8),
+ Seq(Seq::Draw, 3, 8_u8),
+ Seq(Seq::Draw, 2, 4_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Goto, 0_u8),
+ },
+ },
+ { // Yazuka(CW)
+ 0,0,
+ {
+ CellRect( 0, 16, 16, 16),
+ CellRect( 16, 16, 16, 16),
+ CellRect( 32, 16, 16, 16),
+ CellRect( 48, 16, 16, 16),
+ CellRect( 64, 16, 16, 16),
+ CellRect( 80, 16, 16, 16),
+ },
+ {
+ Seq(Seq::Draw, 0, 4_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Draw, 2, 4_u8),
+ Seq(Seq::Draw, 3, 4_u8),
+ Seq(Seq::Draw, 4, 4_u8),
+ Seq(Seq::Draw, 5, 4_u8),
+ Seq(Seq::Goto, 0_u8),
+ }
+ },
+ { // Yazuka(CCW)
+ 0,0,
+ {
+ CellRect( 0, 16, 16, 16),
+ CellRect( 16, 16, 16, 16),
+ CellRect( 32, 16, 16, 16),
+ CellRect( 48, 16, 16, 16),
+ CellRect( 64, 16, 16, 16),
+ CellRect( 80, 16, 16, 16),
+ },
+ {
+ Seq(Seq::Draw, 0, 4_u8),
+ Seq(Seq::Draw, 5, 4_u8),
+ Seq(Seq::Draw, 4, 4_u8),
+ Seq(Seq::Draw, 3, 4_u8),
+ Seq(Seq::Draw, 2, 4_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ Seq(Seq::Goto, 0_u8),
+ }
+ },
+ { // Crash
+ 0,0,
+ {
+ CellRect( 32, 0, 16, 16),
+ CellRect( 48, 0, 16, 16),
+ CellRect( 64, 0, 16, 16),
+ CellRect( 80, 0, 16, 16),
+ },
+ {
+ Seq(Seq::Draw, 0, 2_u8),
+ Seq(Seq::Draw, 1, 2_u8),
+ Seq(Seq::Draw, 2, 3_u8),
+ Seq(Seq::Draw, 3, 4_u8),
+ }
+ },
+ { // Reflection
+ 0,0,
+ {
+ CellRect( 0, 0, 16, 16),
+ CellRect( 16, 0, 16, 16),
+ },
+ {
+ Seq(Seq::Draw, 0, 1_u8),
+ Seq(Seq::Draw, 1, 4_u8),
+ }
+ }
+};
+
+void Enemy::setup()
+{
+ ScopedReleaseBus();
+ if(_sprite) { return; }
+
+ _sprite = new goblib::lgfx::GCellSprite4();
+ assert(_sprite);
+ if(!createFromBitmap(*_sprite, BITMAP_PATH)) { assert(0); }
+ _sprite->makePaletteTable256();
+}
+
+void Enemy::finalize()
+{
+ goblib::safeDelete(_sprite);
+}
+
+Enemy::Enemy(const Pos2::pos_type cx, const Pos2::pos_type cy, const PartialAnimation& animation,
+ const std::uint32_t priority, const std::uint32_t order, const std::uint32_t category, const std::uint32_t score, const char* tag)
+ : GameObj(priority, order, category, tag)
+ , _animation(animation)
+ , _behavior(nullptr)
+ , _score(score)
+ , _alive(true)
+{
+ move(cx, cy);
+ _score.insertObserver(TinyDarius::instance().game());
+}
+
+Enemy::~Enemy()
+{
+ // TD_PRINT_FUNCTION();
+ goblib::safeDelete(_behavior);
+}
+
+void Enemy::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool Enemy::onInitialize()
+{
+ return GameObj::onInitialize();
+}
+
+bool Enemy::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void Enemy::onReceive (const goblib::TaskMessage& msg)
+{
+ if(msg.msg == MSG_VANISH_ENEMY)
+ {
+ defeat();
+ }
+}
+
+void Enemy::onExecute(const float delta)
+{
+ if(_behavior) { _behavior->pump(delta); }
+ _animation.pump();
+ if(_animation.isFinish()) { release(); }
+ GameObj::onExecute(delta);
+}
+
+void Enemy::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+
+ _sprite->pushCellTo16(target, _animation.rect(), static_cast(x()), static_cast(y()) + rarg->yorigin, 0);
+ GameObj::render(arg);
+}
+
+void Enemy::onHit(GameObj* o, const Rect2& r)
+{
+ if(o->category() == CATEGORY_BULLET)
+ {
+ _alive = false;
+ _score.notify();
+ defeat();
+ }
+}
+
+void Enemy::defeat()
+{
+ disableHit();
+ hide();
+
+ auto crash = new ::Crash(*this);
+ assert(crash);
+ TinyDarius::instance().reserveInsertNode(crash, this);
+}
+
+
+//
+Crash::Crash(const Pos2::pos_type cx, const Pos2::pos_type cy)
+ : Enemy(cx,cy, _animation_table[Type::Crash], PRIORITY_EXPLOSION, ORDER_EXPLOSION_ENEMY, CATEGORY_NONE, 0, "crash")
+ , _attach(nullptr)
+{
+ move(cx - WIDTH/2, cy - HEIGHT/2);
+}
+
+Crash::Crash(GameObj& attach)
+ : Enemy(attach.x(), attach.y(), _animation_table[Type::Crash], PRIORITY_EXPLOSION, ORDER_EXPLOSION_ENEMY, CATEGORY_NONE, 0, "crash")
+ , _attach(&attach)
+{
+}
+
+Crash::~Crash()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void Crash::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+void Crash::onExecute(const float delta)
+{
+ Enemy::onExecute(delta);
+ if(_attach)
+ {
+ move(_attach->x(), _attach->y());
+ if(isRelease() || isKill())
+ {
+ _attach->release();
+ }
+ }
+}
+
+//
+Reflection::Reflection(const Pos2::pos_type cx, const Pos2::pos_type cy)
+ : Enemy(cx,cy, _animation_table[Type::Reflection], PRIORITY_EXPLOSION, ORDER_EXPLOSION, CATEGORY_NONE, 0, "reflection")
+{
+ move(cx - WIDTH/2, cy - HEIGHT/2);
+}
+
+Reflection::~Reflection()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void Reflection::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+//
+Mine::Mine(MineGenerator& generator, const Pos2& cpos, const std::int32_t split)
+ : Enemy(cpos, _animation_table[Type::Mine], PRIORITY_ENEMY, ORDER_ENEMY, CATEGORY_ENEMY, PTS_MINE, "mine")
+ , _generator(generator)
+ , _split(split)
+{
+ _hbox = HBOX_MINE;
+
+ auto range = 5 - goblib::clamp(split, 0, 4); // 1~5
+
+ std::mt19937 gen(esp_random());
+ std::uniform_real_distribution<> aa(range * 0.2f, range * 1.2f);
+ std::uniform_real_distribution<> angle(0.25f, 1.0f);
+ std::uniform_int_distribution<> rot(0,31);
+
+ auto a = fx16(aa(gen));
+ auto b = fx16(aa(gen));
+ auto av = fx16(angle(gen));
+ if(gen()&1) { av = -av; }
+ auto sa = rot(gen);
+ auto erot = rot(gen);
+
+ _behavior = new MineBehavior(*this, a, b, sa, av, VecFx16(fx16(-1.0f/4.0f), fx16()), erot);
+ assert(_behavior);
+}
+
+Mine::~Mine()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void Mine::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ // Don't delete, return to object pool.
+ _generator._pool.destruct(this);
+ GameObj::onUnchain();
+}
+
+void Mine::onHit(GameObj* o, const Rect2& r)
+{
+ Enemy::onHit(o, r);
+ if(_split <= 0) { return; }
+ split();
+ split();
+}
+
+void Mine::onExecute(const float delta)
+{
+ Enemy::onExecute(delta);
+ if(isExecute() && _split > 0 && counter() >= SPLIT_TIME)
+ {
+ split();
+ split();
+ _split = 0;
+ }
+}
+
+void Mine::split()
+{
+ if(_split > 0)
+ {
+ std::mt19937 gen(esp_random());
+ std::uniform_int_distribution<> dist(-8, 8);
+ _generator.spawn(Pos2(x() + dist(gen), y() + dist(gen)), _split - 1);
+ }
+}
+
+void MineGenerator::spawn()
+{
+ spawn(Pos2(SCREEN_WIDTH, SCREEN_HEIGHT/2), 4);
+}
+
+void MineGenerator::spawn(const Pos2& cpos, const std::int32_t split)
+{
+ auto p = _pool.construct(*this, cpos, split);
+ if(p){ TinyDarius::instance().reserveInsertNode(p, &_belongto); }
+}
+
+
+Yazuka::Yazuka(YazukaGenerator& generator, const Pos2& cpos, bool cw,
+ fx16 a, fx16 b, Tangle start, fx16 av, VecFx16 ov, Tangle rotate, std::int32_t wait)
+ : Enemy(cpos, _animation_table[cw ? Type::YazukaCW : Type::YazukaCCW], PRIORITY_ENEMY, ORDER_ENEMY, CATEGORY_ENEMY, PTS_YAZUKA, (cw ? "yazukaCW" : "yazukaCCW") )
+ , _generator(generator)
+ , _wait(wait)
+{
+ _hbox = HBOX_YAZUKA;
+ _behavior = new OrbitalBehavior(*this, a, b, start, av, ov, rotate);
+ assert(_behavior);
+ show(wait <= 0);
+}
+
+Yazuka::~Yazuka()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void Yazuka::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ // Don't delete, return to object pool.
+ _generator._pool.destruct(this);
+ GameObj::onUnchain();
+}
+
+void Yazuka::onExecute(const float delta)
+{
+ show(_alive && _wait <= 0);
+
+ if(_wait <= 0)
+ {
+ Enemy::onExecute(delta);
+ if(x() < -_animation.rect().width() ) { release(); return; }
+ }
+ --_wait;
+}
+
+//
+#ifdef DEBUG
+const std::uint32_t YazukaGenerator::MEMBER_MAX = 6;
+#endif
+
+void YazukaGenerator::spawn()
+{
+ std::mt19937 gen(esp_random());
+ std::uniform_int_distribution<> du(0,1);
+ bool upper = du(gen);
+
+ std::uniform_int_distribution<> upperY(FIELD_RECT.top(), FIELD_RECT.top() + 16);
+ std::uniform_int_distribution<> lowerY(FIELD_RECT.bottom() -16, FIELD_RECT.bottom());
+ std::uniform_int_distribution<>& ypos = upper ? upperY : lowerY;
+
+ auto members = goblib::clamp(3 + _generated++/2, 3U, MEMBER_MAX);
+ Pos2 spos( FIELD_RECT.right(), ypos(gen));
+
+ std::uniform_real_distribution<> aa(2.0f, 6.0f);
+ std::uniform_real_distribution<> bb(0.5f, 3.0f);
+ std::uniform_int_distribution<> rot_upper(9,15);
+ std::uniform_int_distribution<> rot_lower(1,7);
+ std::uniform_int_distribution<>& rot = upper ? rot_upper : rot_lower;
+
+ auto a = fx16(aa(gen));
+ auto b = fx16(bb(gen));
+ auto av = upper ? fx16(0.5f) : -fx16(0.5f);
+ auto er = rot(gen);
+
+ for(decltype(members) i=0; i < members; ++i)
+ {
+ auto p = _pool.construct(*this, spos, upper, a, b, 0, av, VecFx16(fx16(-1),fx16(0)), er, i * 4);
+ if(p) {TinyDarius::instance().reserveInsertNode(p, &_belongto); }
+ }
+}
diff --git a/src/enemy/enemy.hpp b/src/enemy/enemy.hpp
new file mode 100644
index 0000000..c800d03
--- /dev/null
+++ b/src/enemy/enemy.hpp
@@ -0,0 +1,179 @@
+/*!
+ TinyDarius
+
+ @file enemy.hpp
+ @brief Enemies and effect
+*/
+#pragma once
+#ifndef TD_ENEMY_HPP
+#define TD_ENEMY_HPP
+
+#include "../typedef.hpp"
+#include "../math_table.hpp"
+#include "../game_obj.hpp"
+#include "../partial_animation.hpp"
+#include "../score.hpp"
+#include
+#include
+#include
+
+namespace goblib { namespace lgfx { class GCellSprite4; }}
+class Behavior;
+
+/*! @brief Enemy base */
+class Enemy : public GameObj
+{
+ public:
+ enum Type { Mine, YazukaCW, YazukaCCW, Crash, Reflection, Max };
+
+ Enemy(const Pos2 pos, const PartialAnimation& animation,
+ const std::uint32_t priority, const std::uint32_t order, const std::uint32_t category, const std::uint32_t score, const char* tag = "enemy")
+ : Enemy(pos.x(), pos.y(), animation, priority, order, category, score, tag) {}
+ Enemy(const Pos2::pos_type cx, const Pos2::pos_type cy, const PartialAnimation& animation,
+ const std::uint32_t priority, const std::uint32_t order, const std::uint32_t category, const std::uint32_t score, const char* tag = "enemy");
+ virtual ~Enemy();
+
+ static void setup();
+ static void finalize();
+
+ virtual void render(void* arg) override;
+ virtual void onHit(GameObj* o, const Rect2& r) override;
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ virtual void onReceive (const goblib::TaskMessage& msg) override;
+ virtual void onExecute(const float delta) override;
+
+ void defeat();
+ GOBLIB_INLINE bool alive() const { return _alive; }
+
+ protected:
+ PartialAnimation _animation;
+ Behavior* _behavior;
+ Score _score;
+ bool _alive;
+
+ static goblib::lgfx::GCellSprite4* _sprite; // shared sprite
+ static const PartialAnimation _animation_table[Type::Max];
+};
+
+/*! Crash effect (Shared enemy sprite)
+ */
+class Crash : public Enemy
+{
+ constexpr static std::int16_t WIDTH = 16;
+ constexpr static std::int16_t HEIGHT = 16;
+
+ public:
+ Crash(const Pos2& pos) : Crash(pos.x(), pos.y()) {}
+ Crash(const Pos2::pos_type cx, const Pos2::pos_type cy);
+ Crash(GameObj& attach);
+ virtual ~Crash();
+
+ protected:
+ virtual void onUnchain() override;
+ virtual void onExecute(const float delta) override;
+
+ private:
+ GameObj* _attach;
+
+};
+
+/*! Reflection effect (Shared enemy sprite)
+ */
+class Reflection : public Enemy
+{
+ constexpr static std::int16_t WIDTH = 16;
+ constexpr static std::int16_t HEIGHT = 16;
+
+ public:
+ Reflection(const Pos2& pos) : Reflection(pos.x(), pos.y()) {}
+ Reflection(const Pos2::pos_type cx, const Pos2::pos_type cy);
+ virtual ~Reflection();
+
+ protected:
+ virtual void onUnchain() override;
+};
+
+
+class MineGenerator;
+
+/*! Floating mine */
+class Mine : public Enemy
+{
+ public:
+ Mine(MineGenerator& generator, const Pos2& cpos, const std::int32_t split);
+ virtual ~Mine();
+
+ virtual void onHit(GameObj* o, const Rect2& r) override;
+
+ protected:
+ virtual void onUnchain() override;
+ virtual void onExecute(const float delta) override;
+ void split();
+
+ private:
+ MineGenerator& _generator;
+ std::int32_t _split;
+ constexpr static std::uint32_t SPLIT_TIME = 3_fsec;
+};
+
+
+/*! Generator for Mine */
+class MineGenerator
+{
+ public:
+ explicit MineGenerator(goblib::Task& t) : _belongto(t), _pool(POOL_MAX) {}
+ void spawn();
+ void spawn(const Pos2& pos, const std::int32_t split);
+
+ private:
+ friend class Mine;
+ goblib::Task& _belongto;
+ goblib::ObjectPool _pool;
+ constexpr static std::size_t POOL_MAX = 24;
+};
+
+class YazukaGenerator;
+
+/*! Yazuka */
+class Yazuka : public Enemy
+{
+ public:
+ Yazuka(YazukaGenerator& generator, const Pos2& cpos, bool cw,
+ fx16 a, fx16 b, Tangle start, fx16 av, VecFx16 ov, Tangle rotate, std::int32_t wait);
+ virtual ~Yazuka();
+
+ protected:
+ virtual void onUnchain() override;
+ virtual void onExecute(const float delta) override;
+
+ private:
+ YazukaGenerator& _generator;
+ std::int32_t _wait;
+};
+
+/*! Generator for Yazuka */
+class YazukaGenerator
+{
+ public:
+ explicit YazukaGenerator(goblib::Task& t) : _belongto(t), _pool(POOL_MAX), _generated(0) {}
+ void reset() { _generated = 0; }
+ void spawn();
+
+ private:
+ friend class Yazuka;
+ goblib::Task& _belongto;
+ goblib::ObjectPool _pool;
+ std::uint32_t _generated;
+ constexpr static std::size_t POOL_MAX = 18;
+#ifndef DEBUG
+ constexpr static std::uint32_t MEMBER_MAX = 6;
+#else
+ static const std::uint32_t MEMBER_MAX;
+#endif
+};
+
+#endif
diff --git a/src/game.hpp b/src/game.hpp
new file mode 100644
index 0000000..b642bb1
--- /dev/null
+++ b/src/game.hpp
@@ -0,0 +1,106 @@
+/*!
+ TinyDarius
+
+ @file game.hpp
+ @brief Game status and management
+*/
+#pragma once
+#ifndef TD_GAME_HPP
+#define TD_GAME_HPP
+
+#include "stage.hpp"
+#include "score.hpp"
+#include
+#include
+#include
+#include
+
+
+class Game : public goblib::Observer
+{
+ public:
+ constexpr static std::uint32_t INITIAL_REMAINING = 3;
+#ifdef DEBUG
+ enum : std::int8_t { HISTORY_END = -1 };
+#else
+ constexpr static std::int8_t HISTORY_END = -1;
+#endif
+ using History = std::array;
+
+ Game() : _score(0), _time(0), _stage(HISTORY_END), _index(0), _remaining(INITIAL_REMAINING), _pause(true)
+ {
+ _history.fill(HISTORY_END);
+ }
+ ~Game(){}
+
+ /// @name Property
+ /// @{
+ const Stage& stage() const { return _stage >= 0 ? Stage::table[_stage] : Stage::terminator; }
+ const History& history() const { return _history; }
+
+ GOBLIB_INLINE std::uint8_t remaining() const { return _remaining; }
+ GOBLIB_INLINE std::int32_t remainingTime() const { return _time; }
+ GOBLIB_INLINE std::uint32_t score() const { return _score; }
+ /// @}
+
+ GOBLIB_INLINE bool isPause() const { return _pause; }
+ GOBLIB_INLINE bool isTimeup() const { return _time <= 0; }
+ GOBLIB_INLINE bool isGameover() const { return _remaining <= 0; }
+
+ GOBLIB_INLINE void pump() { if(!isPause() && _time > 0) { --_time; } }
+
+ /// @name Operation
+ /// @{
+ void start(std::int32_t sindex = 0)
+ {
+ assert(sindex >= 0 && sindex < Stage::MAX && "sindex out of range");
+ _stage = sindex;
+ _score = 0;
+ _remaining = INITIAL_REMAINING;
+ _index = 0;
+ _history.fill(HISTORY_END);
+ _history[_index] = _stage;
+ _time = stage().time;
+ pause(true);
+ }
+
+ GOBLIB_INLINE void pause(bool b) { _pause = b; }
+ GOBLIB_INLINE void pause() { pause(true); }
+ GOBLIB_INLINE void resume() { pause(false); }
+
+ GOBLIB_INLINE void extend() { if(_remaining < 255) {++_remaining; }}
+ GOBLIB_INLINE void dead() { if(_remaining) {--_remaining; } }
+
+ void chooseNext(bool upper)
+ {
+ auto s = stage();
+ if(_index >= Stage::HISTORY_MAX || s.current < 0) { return; }
+
+ auto next = s.next();
+ _stage = upper ? next.first : next.second;
+ _history[++_index] = _stage;
+ _time = stage().time;
+ pause(true);
+ }
+ /// @}
+
+ /// @name Receive notify
+ /// @{
+ // Score
+ GOBLIB_INLINE virtual void onNotify(const Score* s,void* arg) { _score += s->score(); }
+ /// @}
+
+ private:
+ std::uint32_t _score;
+ std::int32_t _time;
+ std::int8_t _stage; // current stage index
+ std::int8_t _index; // history index
+ std::uint8_t _remaining;
+ std::int8_t _pad[1];
+ History _history;
+ bool _pause;
+
+ static Stage _stages[Stage::MAX];
+};
+
+#endif
diff --git a/src/game_obj.cpp b/src/game_obj.cpp
new file mode 100644
index 0000000..e4450ad
--- /dev/null
+++ b/src/game_obj.cpp
@@ -0,0 +1,76 @@
+/*!
+ TinyDarius
+
+ @file game_obj.cpp
+ @brief Game object
+*/
+#include
+#include "game_obj.hpp"
+#include "app.hpp"
+#include "renderer.hpp"
+#include "constants.hpp"
+#include
+using goblib::lgfx::GSprite;
+
+#include
+
+bool GameObj::_showHitBox = false;
+
+//goblib::FixedVector GameObj::_collideObjects;
+GameObj::CollideObjects GameObj::_collideObjects;
+
+bool GameObj::onInitialize()
+{
+ TinyDarius::instance().insertRenderObj(this);
+ return true;
+}
+
+bool GameObj::onRelease()
+{
+ TinyDarius::instance().removeRenderObj(this);
+ return true;
+}
+
+// Render bounding
+void GameObj::render(void* arg)
+{
+ if(isShowHitBox())
+ {
+ _hbox.render(arg, hitable() ? CLR_MAGENTA : CLR_GREEN);
+ }
+}
+
+
+bool GameObj::checkHit(GameObj* obj)
+{
+ if(!obj->hitable()) { return false; }
+
+ return std::any_of(_collideObjects.begin(), _collideObjects.end(),
+ [&obj](GameObj* o)
+ {
+ if(obj != o && o->hitable() && (obj->category() & o->category()) == 0)
+ {
+ auto r = obj->hitBox() & o->hitBox();
+ if(r)
+ {
+ obj->onHit(o, r);
+ o->onHit(obj, r);
+ return true;
+ }
+ }
+ return false;
+ });
+}
+
+void GameObj::print()
+{
+ printf("GameObj: collide:%zu\n", _collideObjects.size());
+ for(auto& e: _collideObjects)
+ {
+ printf(" >[%16s]:%d:(%d,%d) - {(%d,%d) [%d,%d]}\n", e->tag(), e->hitable(),
+ static_cast(e->_hbox._rpos.x()), static_cast(e->_hbox._rpos.y()),
+ e->_hbox._box.left(), e->_hbox._box.top(),
+ e->_hbox._box.width(), e->_hbox._box.height());
+ }
+
+}
diff --git a/src/game_obj.hpp b/src/game_obj.hpp
new file mode 100644
index 0000000..a02b224
--- /dev/null
+++ b/src/game_obj.hpp
@@ -0,0 +1,118 @@
+/*!
+ TinyDarius
+
+ @file game_obj.hpp
+ @brief Game object
+*/
+#pragma once
+#ifndef TD_GAME_OBJ_HPP
+#define TD_GAME_OBJ_HPP
+
+#include "typedef.hpp"
+#include "hit_box.hpp"
+#include
+#include
+#include
+#include
+#include
+
+class GameObj : public goblib::Task, public goblib::graph::RenderObj2D
+{
+ public:
+ using Category = std::uint32_t;
+ static constexpr std::size_t COLLIDE_OBJECTS_MAX = 80;
+ using CollideObjects = goblib::FixedVector;
+
+ GameObj(goblib::Task::PriorityType pri, goblib::graph::RenderObj2D::OrderType order, Category category, const char* tag = "")
+ : goblib::Task(pri, tag), goblib::graph::RenderObj2D(order)
+ , _hbox{}, _pos() ,_category(category), _counter(0), _hitable(true)
+ {}
+ virtual ~GameObj(){}
+
+ virtual void render(void* arg) override;
+
+ GOBLIB_INLINE Pos2::pos_type x() const { return _pos.x(); }
+ GOBLIB_INLINE Pos2::pos_type y() const { return _pos.y(); }
+ GOBLIB_INLINE Pos2 pos() const { return _pos; }
+ GOBLIB_INLINE Category category() const { return _category; }
+
+ GOBLIB_INLINE const HitBox hitBox() const { return _hbox; }
+ GOBLIB_INLINE bool hitable() const{ return _hitable; }
+ GOBLIB_INLINE void virtual setHitable(bool b) { _hitable = b; }
+ GOBLIB_INLINE void enableHit() { setHitable(true); }
+ GOBLIB_INLINE void disableHit() { setHitable(false); }
+
+ GOBLIB_INLINE void move(const Pos2::pos_type& x, const Pos2::pos_type& y)
+ {
+ _pos.move(x, y);
+ _hbox.move(x, y);
+ }
+ GOBLIB_INLINE void move(const Pos2& to)
+ {
+ _pos = to;
+ _hbox.move(to.x(), to.y());
+ }
+ GOBLIB_INLINE void offset(const Pos2::pos_type& ox, const Pos2::pos_type& oy)
+ {
+ _pos.offset(ox, oy);
+ // _hbox.offset(ox, oy);
+ _hbox.move(_pos);
+ }
+ GOBLIB_INLINE void offset(const Pos2& off)
+ {
+ _pos += off;
+ // _hbox.offset(off);
+ _hbox.move(_pos);
+ }
+
+ static CollideObjects& collideObjects() { return _collideObjects; }
+ static bool checkHit(GameObj* obj);
+
+ static void showHitBox(bool b) { _showHitBox = b; }
+ static bool isShowHitBox() { return _showHitBox; }
+ static void print();
+
+ protected:
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ // virtual void onReceive (const TaskMessage& /*msg*/) override {}
+ virtual void onExecute(const float /*delta*/) override { ++_counter; }
+
+ virtual void onChain() override
+ {
+ if(category())
+ {
+ assert(_collideObjects.size() < _collideObjects.capacity() && "Too many objects");
+ _collideObjects.push_back(this);
+ }
+ }
+ virtual void onUnchain() override
+ {
+ _collideObjects.erase(std::remove_if(_collideObjects.begin(), _collideObjects.end(),
+ [this](GameObj* o) { return this == o; }),
+ _collideObjects.end());
+ }
+ virtual void onHit(GameObj* o, const Rect2& hit) {}
+
+ void resetCounter() { _counter = 0; }
+ std::uint32_t counter() const { return _counter; }
+
+ private:
+ GameObj() = delete;
+ GameObj(const GameObj&) = delete;
+ GameObj& operator=(const GameObj&) = delete;
+
+ protected:
+ HitBox _hbox;
+
+ private:
+ Pos2 _pos;
+ Category _category;
+ std::uint32_t _counter;
+ bool _hitable;
+
+ static CollideObjects _collideObjects;
+ static bool _showHitBox;
+};
+
+#endif
diff --git a/src/hit_box.cpp b/src/hit_box.cpp
new file mode 100644
index 0000000..8117a30
--- /dev/null
+++ b/src/hit_box.cpp
@@ -0,0 +1,26 @@
+/*!
+ TinyDarius
+
+ @file hit_box.cpp
+ @brief Collision
+*/
+#include
+#include "hit_box.hpp"
+#include "renderer.hpp"
+#include
+using goblib::lgfx::GSprite;
+
+
+void HitBox::render(void* arg, std::uint32_t clr)
+{
+#ifndef NDEBUG
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+
+ if(_box)
+ {
+ target->drawRect(_box.left(), _box.top() + rarg->yorigin,
+ _box.width(), _box.height(), clr);
+ }
+#endif
+}
diff --git a/src/hit_box.hpp b/src/hit_box.hpp
new file mode 100644
index 0000000..57cd740
--- /dev/null
+++ b/src/hit_box.hpp
@@ -0,0 +1,45 @@
+/*!
+ TinyDarius
+
+ @file hit_box.hpp
+ @brief Collision
+*/
+#pragma once
+#ifndef TD_HIT_BOX_HPP
+#define TD_HIT_BOX_HPP
+
+#include "typedef.hpp"
+#include
+
+struct HitBox
+{
+ // Pos2i
+ Pos2 _rpos; // relative
+ Rect2 _box; // absolute
+
+ constexpr HitBox() : HitBox(Pos2(), Rect2()) {}
+ constexpr HitBox(const Pos2& pos, const Rect2& r) : _rpos(pos), _box(r) {}
+
+ GOBLIB_INLINE void move(const Pos2& pos) { _box.move(_rpos + pos); }
+ GOBLIB_INLINE void move(const Pos2::pos_type x, const Pos2::pos_type y)
+ {
+ move(Pos2(x,y));
+ }
+
+ GOBLIB_INLINE void offset(const Pos2::pos_type x, const Pos2::pos_type y)
+ {
+ _box.offset(static_cast(x), static_cast(y));
+ }
+ GOBLIB_INLINE void offset(const Pos2& pos) { offset(pos.x(), pos.y()); }
+
+ GOBLIB_INLINE Rect2 isHit(const HitBox& x) const { return (_box & x._box); }
+
+ void render(void* arg, std::uint32_t clr);
+};
+
+GOBLIB_INLINE Rect2 operator&(const HitBox& a, const HitBox& b)
+{
+ return a.isHit(b);
+}
+
+#endif
diff --git a/src/info/information.cpp b/src/info/information.cpp
new file mode 100644
index 0000000..de0385c
--- /dev/null
+++ b/src/info/information.cpp
@@ -0,0 +1,109 @@
+/*!
+ TinyDarius
+
+ @file information.cpp
+ @brief Information on play
+*/
+#include
+
+#include "information.hpp"
+#include "../debug.hpp"
+#include "../app.hpp"
+#include "../game.hpp"
+#include "../typedef.hpp"
+#include "../renderer.hpp"
+#include "../utility.hpp"
+#include "../constants.hpp"
+
+#include
+using goblib::lgfx::GSprite;
+using goblib::lgfx::GSprite4;
+#include
+
+#include // std::log10
+#include
+
+namespace
+{
+constexpr char BITMAP_PATH[] = "/res/td/zone.bmp";
+
+constexpr Pos2i ZONE_POS(160 - 8*7, 0);
+constexpr Pos2i ZONE_NAME_POS(SCREEN_WIDTH/2 - 8, 20);
+constexpr Pos2i REMAINING_POS(SCREEN_WIDTH/2-56+16+4, 24);
+constexpr Pos2i SCORE_POS(8,16);
+constexpr Pos2i TIME_POS(SCREEN_WIDTH - (12*8), 16);
+}
+
+Information::Information()
+ : GameObj(PRIORITY_INFORMATION, ORDER_INFORMATION, CATEGORY_NONE, "information")
+ , _sprite(nullptr)
+{
+ _sprite = new GSprite4();
+ assert(_sprite);
+}
+
+Information::~Information()
+{
+ // TD_PRINT_FUNCTION();
+ goblib::safeDelete(_sprite);
+}
+
+void Information::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool Information::onInitialize()
+{
+ if(!createFromBitmap(*_sprite, BITMAP_PATH))
+ {
+ kill();
+ return false;
+ }
+ _sprite->makePaletteTable256();
+ return GameObj::onInitialize();
+}
+
+bool Information::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void Information::onExecute(const float delta)
+{
+}
+
+void Information::render(void* arg)
+{
+ static const char* score_tbl[] = {"", "0", "00","000", "0000", "00000", "000000", "0000000", "00000000" };
+ Game& game = TinyDarius::instance().game();
+
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ _sprite->pushSpriteTo16(target, ZONE_POS.x(), ZONE_POS.y() + rarg->yorigin);
+
+ target->setTextColor(CLR_WHITE, CLR_BLACK);
+ target->setTextSize(2);
+ target->setCursor(ZONE_NAME_POS.x(), ZONE_NAME_POS.y() + rarg->yorigin);
+ target->printf("%c", game.stage().toChar());
+ target->setTextSize(1);
+
+ target->setCursor(REMAINING_POS.x(), REMAINING_POS.y() + rarg->yorigin);
+ target->printf("%u", game.remaining());
+
+ auto pts = game.score();
+ auto column = (pts == 0) ? 7 : 8 - (static_cast(std::log10(static_cast(pts)) + 1));
+ target->setTextColor(CLR_GRAY, CLR_BLACK);
+ target->drawString(score_tbl[column], SCORE_POS.x(),SCORE_POS.y() + rarg->yorigin);
+ target->setTextColor(CLR_WHITE);
+ target->setCursor(SCORE_POS.x(),SCORE_POS.y() + rarg->yorigin);
+ target->printf("%8u", pts);
+
+ target->setCursor(TIME_POS.x(), TIME_POS.y() + rarg->yorigin);
+ auto tm = game.remainingTime();
+ if(tm <= 60_fsec) { target->setTextColor(CLR_RED, CLR_BLACK); }
+ target->printf("TIME:%02u:%02u", tm/(1_fmin), (tm/APP_FPS) % 60);
+ target->setTextColor(CLR_WHITE, CLR_BLACK);
+}
diff --git a/src/info/information.hpp b/src/info/information.hpp
new file mode 100644
index 0000000..1ddc743
--- /dev/null
+++ b/src/info/information.hpp
@@ -0,0 +1,37 @@
+/*!
+ TinyDarius
+
+ @file information.hpp
+ @brief Information on play
+*/
+#pragma once
+#ifndef TD_INFORMATION_HPP
+#define TD_INFORMATION_HPP
+
+#include "../game_obj.hpp"
+
+namespace goblib { namespace lgfx {
+class GSprite4;
+}}
+
+class Information : public GameObj
+{
+ public:
+ Information();
+ virtual ~Information();
+
+ virtual void render(void* arg) override;
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ //virtual void onReceive (const Task::Message& /*msg*/) override {}
+ virtual void onExecute(const float delta) override;
+
+ private:
+ goblib::lgfx::GSprite4* _sprite;
+
+};
+
+#endif
diff --git a/src/info/message.cpp b/src/info/message.cpp
new file mode 100644
index 0000000..3d364c0
--- /dev/null
+++ b/src/info/message.cpp
@@ -0,0 +1,236 @@
+/*!
+ TinyDarius
+
+ @file message.cpp
+ @brief Display message
+*/
+#include
+
+#include "message.hpp"
+#include "../debug.hpp"
+#include "../renderer.hpp"
+#include "../constants.hpp"
+#if __has_include("../df88.cpp")
+#include "../df88.hpp"
+#endif
+#include
+using goblib::lgfx::GSprite;
+#include
+using goblib::lgfx::GSprite4;
+#include
+#include
+#include
+#include
+
+CenteringMessage::Message::Message(const char* str, std::int16_t sy, std::uint32_t c, std::uint32_t w)
+ : source{0,}, dest{0}
+ , ps(source), pd(dest)
+ , x(0), y(sy), clr(c)
+ , counter(w), wait(w)
+{
+ std::strncpy(source, str, sizeof(source));
+ source[sizeof(source)-1] = '\0';
+ // Keep strlen(dest) equal strlen(source)
+ std::fill(std::begin(dest), std::begin(dest) + sizeof(dest), ' ');
+ dest[std::strlen(source)] = '\0';
+
+ auto width = std::strlen(str) * 8;
+ x = (SCREEN_WIDTH - width) / 2;
+}
+
+CenteringMessage::CenteringMessage()
+ : GameObj(PRIORITY_MESSAGE, ORDER_MESSAGE, CATEGORY_NONE, "message")
+ , _messages()
+{}
+
+
+CenteringMessage::CenteringMessage(const char* str, std::int32_t sy, std::uint32_t clr, std::uint32_t wait)
+ : GameObj(PRIORITY_MESSAGE, ORDER_MESSAGE, CATEGORY_NONE, "message")
+ , _messages()
+{
+ insert(str, sy, clr, wait);
+}
+
+CenteringMessage::~CenteringMessage()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void CenteringMessage::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool CenteringMessage::onInitialize()
+{
+ return GameObj::onInitialize();
+}
+
+bool CenteringMessage::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void CenteringMessage::onExecute(const float delta)
+{
+ for(auto& e : _messages)
+ {
+ if(!e.pump()) { break; }
+ }
+ GameObj::onExecute(delta);
+}
+
+void CenteringMessage::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+
+ auto old = target->getTextDatum();
+ // target->setTextDatum(textdatum_t::middle_center);
+ target->setTextDatum(textdatum_t::middle_left);
+
+ for(auto& e : _messages)
+ {
+ target->setTextColor(e.clr);
+ target->drawString(e.dest, e.x, e.y + rarg->yorigin);
+ }
+ target->setTextDatum(old);
+}
+
+//
+BlinkingMessage::BlinkingMessage(const char* source, std::int16_t sy, std::uint32_t clr, std::int32_t cycle)
+ : GameObj(PRIORITY_MESSAGE, ORDER_MESSAGE, CATEGORY_NONE, "blinkingmsg")
+ , _sprite(nullptr)
+ , _buffer{0}
+ , _clr(clr)
+ , _cycle(cycle)
+ , _show(false)
+{
+ std::strncpy(_buffer, source, sizeof(_buffer));
+ _buffer[sizeof(_buffer)-1] = '\0';
+
+ _sprite = new GSprite4(std::strlen(_buffer)*16, 16);
+ assert(_sprite);
+#if __has_include("../df88.cpp")
+ _sprite->setFont(&df88_gfx_font);
+#endif
+ _sprite->setPaletteColor(1, _clr); // 0:transparent 1;text color
+ _sprite->makePaletteTable256();
+ auto xx = (SCREEN_WIDTH - strlen(_buffer) * 16) / 2;
+
+ move(Pos2(xx, sy));
+}
+
+BlinkingMessage:: ~BlinkingMessage()
+{
+ goblib::safeDelete(_sprite);
+ // TD_PRINT_FUNCTION();
+}
+
+void BlinkingMessage::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool BlinkingMessage::onInitialize()
+{
+ _sprite->setTextSize(2); // double size
+ _sprite->setTextColor(_clr);
+ _sprite->drawString(_buffer, 0, 0);
+ return GameObj::onInitialize();
+}
+
+bool BlinkingMessage::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void BlinkingMessage::onExecute(const float delta)
+{
+ _show = (_cycle == 0) ? true : ((counter() % _cycle) == 0 ? !_show : _show);
+ show(_show);
+ GameObj::onExecute(delta);
+}
+
+void BlinkingMessage::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+
+ /*
+ auto old = target->getTextDatum();
+ target->setTextDatum(textdatum_t::middle_center);
+ target->setTextSize(2);
+ target->setTextColor(_clr);
+ target->drawString(_buffer, x(), y() + rarg->yorigin);
+ target->setTextSize(1);
+ target->setTextDatum(old);
+ */
+
+ _sprite->pushSpriteTo16(target, static_cast(x()) , static_cast(y()) - 8 + rarg->yorigin, 0);
+
+}
+
+
+//
+RemainBonusMessage::RemainBonusMessage(std::int32_t pts, std::int16_t sy, std::uint32_t clr)
+ : GameObj(PRIORITY_MESSAGE, ORDER_MESSAGE, CATEGORY_NONE, "bonusmessage")
+ , _buffer{0}
+ , _pts(pts)
+ ,_clr(clr)
+{
+ apply(_pts);
+
+ move(Pos2(SCREEN_WIDTH/2, sy));
+}
+
+RemainBonusMessage:: ~RemainBonusMessage()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void RemainBonusMessage::apply(std::int32_t pts)
+{
+ snprintf(_buffer, sizeof(_buffer), FORMAT_STR, pts);
+ _buffer[sizeof(_buffer)-1] = '\0';
+}
+
+void RemainBonusMessage::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool RemainBonusMessage::onInitialize()
+{
+ return GameObj::onInitialize();
+}
+
+bool RemainBonusMessage::onRelease()
+{
+ return GameObj::onRelease();
+}
+
+void RemainBonusMessage::onExecute(const float delta)
+{
+ GameObj::onExecute(delta);
+}
+
+void RemainBonusMessage::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+
+ auto old = target->getTextDatum();
+ target->setTextDatum(textdatum_t::middle_center);
+ target->setTextSize(2);
+ target->setTextColor(_clr);
+ target->drawString(_buffer, static_cast(x()), static_cast(y()) + rarg->yorigin);
+ target->setTextSize(1);
+ target->setTextDatum(old);
+}
diff --git a/src/info/message.hpp b/src/info/message.hpp
new file mode 100644
index 0000000..55aac46
--- /dev/null
+++ b/src/info/message.hpp
@@ -0,0 +1,146 @@
+/*!
+ TinyDarius
+
+ @file message.hpp
+ @brief Display message
+*/
+#pragma once
+#ifndef TD_MESSAGE_HPP
+#define TD_MESSAGE_HPP
+
+#include "../game_obj.hpp"
+#include "../constants.hpp"
+#include
+#include
+#include
+#include
+
+namespace goblib { namespace lgfx { class GSprite4; }}
+
+/*! @brief Centering messages to be displayed one character at a time */
+class CenteringMessage : public GameObj
+{
+ public:
+ constexpr static std::size_t MESSAGE_MAX = 3;
+
+ /*! @brief Message displayed one character at a time */
+ struct Message
+ {
+ public:
+ constexpr static std::size_t BUFFER_LENGTH = SCREEN_WIDTH/8 + 1;
+ char source[BUFFER_LENGTH];
+ char dest[BUFFER_LENGTH];
+ const char* ps;
+ char* pd; // display str.
+ std::int16_t x, y;
+ std::uint32_t clr;
+ std::int32_t counter;
+ const std::int32_t wait;
+
+ Message(const char* str, std::int16_t sy, std::uint32_t c, std::uint32_t w = 4);
+
+ GOBLIB_INLINE bool isFinish() const { return !ps || *ps == '\0'; }
+
+ bool pump()
+ {
+ if(!--counter)
+ {
+ counter = wait;
+ if(ps && *ps && pd) { *pd++ = *ps++; }
+ }
+ return isFinish();
+ }
+ };
+
+ CenteringMessage();
+ CenteringMessage(const char* source, std::int32_t sy, std::uint32_t clr, std::uint32_t wait = 4);
+ virtual ~CenteringMessage();
+
+ GOBLIB_INLINE void insert(const char* str, std::int32_t sy, std::uint32_t c, std::uint32_t w = 4)
+ {
+ assert(_messages.size() < MESSAGE_MAX);
+ _messages.emplace_back(str, sy, c, w);
+ }
+
+ GOBLIB_INLINE void clear() { _messages.clear(); }
+ GOBLIB_INLINE bool isFinish() const
+ {
+ return _messages.empty()
+ || std::all_of(_messages.begin(), _messages.end(), [](const Message& m) { return m.isFinish(); });
+ }
+
+ virtual void render(void* arg) override;
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ virtual void onExecute(const float delta) override;
+
+ private:
+ goblib::FixedVector _messages;
+};
+
+/*! @brief Blinking centering message */
+class BlinkingMessage : public GameObj
+{
+ public:
+ constexpr static std::size_t BUFFER_LENGTH = SCREEN_WIDTH/8 + 1;
+
+ /*!
+ @param source message string
+ @param sx center position
+ @param sy top position
+ @param cycle show/hide cycle.
+ */
+ BlinkingMessage(const char* source, std::int16_t sy, std::uint32_t clr, std::int32_t cycle = 16);
+ virtual ~BlinkingMessage();
+
+ virtual void render(void* arg) override;
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ virtual void onExecute(const float delta) override;
+
+ private:
+ goblib::lgfx::GSprite4* _sprite;
+
+ char _buffer[BUFFER_LENGTH];
+ const std::uint32_t _clr;
+ const std::int32_t _cycle;
+ bool _show;
+};
+
+/*! @brief Remain bonus message */
+class RemainBonusMessage : public GameObj
+{
+ public:
+ constexpr static std::size_t BUFFER_LENGTH = SCREEN_WIDTH/8 + 1;
+
+ RemainBonusMessage(std::int32_t pts, std::int16_t sy, std::uint32_t clr);
+ virtual ~RemainBonusMessage();
+
+ virtual void render(void* arg) override;
+
+ void setPts(std::int32_t pts) { _pts = pts; apply(_pts); }
+ void increasePts(std::int32_t pts) { _pts += pts; apply(_pts); }
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ virtual void onExecute(const float delta) override;
+
+ private:
+ void apply(std::int32_t pts);
+
+ private:
+ char _buffer[BUFFER_LENGTH];
+ std::int32_t _pts;
+ const std::uint32_t _clr;
+ constexpr static const char* FORMAT_STR = "%07d POINTS";
+};
+
+#endif
diff --git a/src/info/name_entry.cpp b/src/info/name_entry.cpp
new file mode 100644
index 0000000..2a835e3
--- /dev/null
+++ b/src/info/name_entry.cpp
@@ -0,0 +1,166 @@
+/*!
+ TinyDarius
+
+ @file name_entry.cpp
+ @brief Name entry
+*/
+#include
+#include "name_entry.hpp"
+#include "../debug.hpp"
+#include "../app.hpp"
+#include "../renderer.hpp"
+#include "../constants.hpp"
+#include "../typedef.hpp"
+
+#include
+#include
+using goblib::lgfx::GSprite;
+#include
+using goblib::m5s::FaceGB;
+
+#include
+
+namespace
+{
+constexpr const char VALID_CHAR[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ. ";
+constexpr const char* ENTER_MESSAGE[] =
+{
+ "ENTER YOUR INITIALS !",
+ "RANK SCORE NAME",
+};
+constexpr const char* ENTRY_FORMAT = "PROCO %4d %8d ";
+constexpr std::int32_t INPUT_OFFSET_X = 60;
+constexpr const char* DEFAULT_NAME = "M5S";
+constexpr std::int32_t INPUT_TIME = 60_fsec;
+}
+
+NameEntry::NameEntry(std::int32_t score, std::int32_t rank, std::int16_t sy)
+ : GameObj(PRIORITY_MESSAGE, ORDER_MESSAGE, CATEGORY_NONE, "inputranking")
+ , _score(score)
+ , _rank(rank)
+ , _timer(INPUT_TIME)
+ , _cursor(0)
+ , _name{0}
+ , _char(0)
+ , _saveRC{}
+{
+ move(Pos2(SCREEN_WIDTH/2, sy));
+}
+
+NameEntry::~NameEntry()
+{
+ // TD_PRINT_FUNCTION();
+}
+
+void NameEntry::onUnchain()
+{
+ // TD_PRINT_FUNCTION();
+ GameObj::onUnchain();
+ delete this;
+}
+
+bool NameEntry::onInitialize()
+{
+ FaceGB& input = TinyDarius::instance().input();
+ _saveRC[0] = input.getRepeatCycle(FaceGB::Button::Left);
+ _saveRC[1] = input.getRepeatCycle(FaceGB::Button::Right);
+ input.setRepeatCycle(FaceGB::Button::Up, 10);
+ input.setRepeatCycle(FaceGB::Button::Down, 10);
+
+ return GameObj::onInitialize();
+}
+
+bool NameEntry::onRelease()
+{
+ FaceGB& input = TinyDarius::instance().input();
+ input.setRepeatCycle(FaceGB::Button::Left, _saveRC[0]);
+ input.setRepeatCycle(FaceGB::Button::Right, _saveRC[1]);
+ return GameObj::onRelease();
+}
+
+void NameEntry::onExecute(const float delta)
+{
+ // Cut off when time is over.
+ if(_timer <= 0)
+ {
+ _name[_cursor] = '\0';
+ // Unentered?
+ if(_name[0] == '\0')
+ {
+ std::strncpy(_name, DEFAULT_NAME, sizeof(_name));
+ _name[sizeof(_name)-1] = '\0';
+ }
+ pause(true);
+ }
+
+ FaceGB& input = TinyDarius::instance().input();
+
+ // Change character
+ if(input.wasRepeated(FaceGB::Button::Left))
+ {
+ _char = (_char - 1 + goblib::size(VALID_CHAR)) % goblib::size(VALID_CHAR);
+ }
+ else if(input.wasRepeated(FaceGB::Button::Right))
+ {
+ _char = (_char + 1) % goblib::size(VALID_CHAR);
+ }
+ // Delete
+ if(input.wasPressed(FaceGB::Button::B))
+ {
+ if(_cursor > 0)
+ {
+ _name[--_cursor] = '\0';
+ _char = 0;
+ }
+ }
+ // Input
+ else if(input.wasPressed(FaceGB::Button::A))
+ {
+ _name[_cursor++] = VALID_CHAR[_char];
+ _char = 0;
+
+ // Done
+ if(_cursor >= NAME_LENGTH)
+ {
+ _name[_cursor] = '\0';
+ pause(true);
+ }
+ }
+
+ --_timer;
+ GameObj::onExecute(delta);
+}
+
+void NameEntry::render(void* arg)
+{
+ RenderArg* rarg = static_cast(arg);
+ GSprite* target = rarg->sprite;
+ std::int32_t xx = static_cast(x());
+ std::int32_t yy = static_cast(y());
+
+ auto old = target->getTextDatum();
+ target->setTextDatum(textdatum_t::middle_center);
+ target->setTextColor(CLR_WHITE);
+
+ target->drawString(ENTER_MESSAGE[0], xx, yy + rarg->yorigin);
+ target->drawString(ENTER_MESSAGE[1], xx, yy + 16 + rarg->yorigin);
+
+ char tmp[64];
+ snprintf(tmp, sizeof(tmp), ENTRY_FORMAT, _rank, _score);
+ tmp[sizeof(tmp)-1] = '\0';
+ target->drawString(tmp, xx, yy + 32 + rarg->yorigin);
+
+ target->setTextDatum(textdatum_t::middle_left);
+ target->drawString(_name, xx + INPUT_OFFSET_X, yy + 32 + rarg->yorigin);
+
+ if(_cursor < NAME_LENGTH)
+ {
+ snprintf(tmp, sizeof(tmp), "%c", VALID_CHAR[_char]);
+ tmp[sizeof(tmp)-1] = '\0';
+ target->setTextColor((counter() & 8) ? CLR_RED : CLR_WHITE);
+ target->drawString(tmp, xx + INPUT_OFFSET_X + 8 *_cursor, yy + 32 + rarg->yorigin);
+ }
+
+ target->setTextColor(CLR_WHITE);
+ target->setTextDatum(old);
+}
diff --git a/src/info/name_entry.hpp b/src/info/name_entry.hpp
new file mode 100644
index 0000000..2206caa
--- /dev/null
+++ b/src/info/name_entry.hpp
@@ -0,0 +1,47 @@
+/*!
+ TinyDarius
+
+ @file name_entry.hpp
+ @brief Name entry
+*/
+#pragma once
+#ifndef TD_NAME_ENTRY_HPP
+#define TD_NAME_ENTRY_HPP
+
+#include "../game_obj.hpp"
+#include "../constants.hpp"
+#include
+#include
+
+
+class NameEntry : public GameObj
+{
+ public:
+ constexpr static std::int32_t NAME_LENGTH = 3;
+
+ NameEntry(std::int32_t score, std::int32_t rank, std::int16_t sy);
+ virtual ~NameEntry();
+
+ GOBLIB_INLINE bool isFinish() const { return _timer <= 0 || _cursor >= NAME_LENGTH; }
+
+ GOBLIB_INLINE const char* name() const { return _name; }
+
+ virtual void render(void* arg) override;
+
+ protected:
+ virtual void onUnchain() override;
+ virtual bool onInitialize() override;
+ virtual bool onRelease() override;
+ virtual void onExecute(const float delta) override;
+
+ private:
+ std::int32_t _score;
+ std::int32_t _rank;
+ std::int32_t _timer;
+ std::int32_t _cursor;
+ char _name[NAME_LENGTH + 1]; // 3 char + '\0'
+ std::int32_t _char;
+ std::uint8_t _saveRC[2];
+};
+
+#endif
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..73ebfa8
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,78 @@
+/*!
+ TinyDarius
+
+ @file main.cpp
+ @brief Program entry
+*/
+
+#include
+#ifdef min
+#undef min
+#endif
+#include
+
+#include
+#include
+#include
+#if __has_include ()
+#include
+#else // esp_idf_version.h has been introduced in Arduino 1.0.5 (ESP-IDF3.3)
+#define ESP_IDF_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
+#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(3,2,0)
+#endif
+
+#include "app.hpp"
+#include "sound.hpp"
+#include
+#include
+#include
+#include
+
+static LGFX lcd;
+
+void setup()
+{
+ auto presetup_heap = esp_get_free_heap_size();
+
+ // Heap memory increases.
+ // We release the memory for BT as it is pre-allocated depending on the environment and configuration.
+ esp_bluedroid_disable();
+ esp_bluedroid_deinit();
+ esp_bt_controller_disable();
+ esp_bt_controller_deinit();
+ esp_bt_mem_release(ESP_BT_MODE_BTDM);
+ // esp_bt_controller_mem_release(ESP_BT_MODE_BTDM);
+
+ M5.begin(false /* LCD */, false /* SD */ , true /* Serial */);
+ Wire.begin();
+ while(!Serial){ delay(10); }
+
+ SdFat& sd = goblib::m5s::SD::instance().sd();
+ while(!sd.begin((unsigned)TFCARD_CS_PIN, SD_SCK_MHZ(25))) { delay(10); }
+
+ auto m5setup_heap = esp_get_free_heap_size();
+
+ TinyDarius::instance().setup(&lcd);
+ (void)SoundSystem::instance();
+ goblib::m5s::CpuUsage::setup();
+
+ printf("Free heap before M5.begin: %u\n", presetup_heap);
+ printf("Free heap after begin: %u\n", m5setup_heap);
+ printf("Free heap after my setup: %u\n", esp_get_free_heap_size());
+
+ printf("ESP-IDF Version %d.%d.%d\n",
+ (ESP_IDF_VERSION>>16) & 0xFF, (ESP_IDF_VERSION>>8)&0xFF, ESP_IDF_VERSION & 0xFF);
+#ifdef LGFX_USE_V1
+ const char* lgfx_v = "v1";
+#else
+ const char* lgfx_v = "v0";
+#endif
+ printf("LovyanGFX %s\n", lgfx_v);
+ printf("goblib %s:%08xH\n", GOBLIB_VERSION_STRING, GOBLIB_VERSION_VALUE);
+ printf("goblib_m5s %s:%08xH\n", GOBLIB_M5S_VERSION_STRING, GOBLIB_M5S_VERSION_VALUE);
+}
+
+void loop()
+{
+ TinyDarius::instance().pump();
+}
diff --git a/src/math_table.cpp b/src/math_table.cpp
new file mode 100644
index 0000000..594603e
--- /dev/null
+++ b/src/math_table.cpp
@@ -0,0 +1,41 @@
+/*!
+ TinyDarius
+
+ @file math_table.cpp
+ @brief Table based mathmatics.
+*/
+#include "math_table.hpp"
+#include