├── .gitignore
├── assets
├── icon.png
├── splash.png
├── sprites.act
├── sprites.png
├── sprites.tsx
├── level00.tmx
├── level01.tmx
├── level03.tmx
└── level02.tmx
├── screenshot.png
├── README.md
├── metadata.yml
├── rocks-and-diamonds.hpp
├── ci
└── install_sdl_vs.sh
├── assets.yml
├── splash.tmx
├── CMakeLists.txt
├── LICENSE
├── .github
└── workflows
│ └── build.yml
└── rocks-and-diamonds.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | build.em/
3 | build.stm32/
4 |
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/32blit/rocks-and-diamonds/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/32blit/rocks-and-diamonds/HEAD/screenshot.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/32blit/rocks-and-diamonds/HEAD/assets/splash.png
--------------------------------------------------------------------------------
/assets/sprites.act:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/32blit/rocks-and-diamonds/HEAD/assets/sprites.act
--------------------------------------------------------------------------------
/assets/sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/32blit/rocks-and-diamonds/HEAD/assets/sprites.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rocks and Diamonds - 32blit
2 |
3 | A 32blit remake of the classic rock-dodging, diamond-grabbing, boulder-dashing game we all know and love.
4 |
5 | 
--------------------------------------------------------------------------------
/assets/sprites.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/metadata.yml:
--------------------------------------------------------------------------------
1 | title: Rocks And Diamonds
2 | description: A classic game of rock-dodging, boulder-dashing action.
3 | author: Gadgetoid
4 | splash:
5 | file: assets/splash.png
6 | icon:
7 | file: assets/icon.png
8 | url: https://github.com/32blit/rocks-and-diamonds
9 | version: v0.0.5
10 |
11 |
--------------------------------------------------------------------------------
/rocks-and-diamonds.hpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "32blit.hpp"
4 | #include "assets.hpp"
5 |
6 | void init();
7 | void update(uint32_t time);
8 | void render(uint32_t time);
9 |
10 | void update_camera(uint32_t time);
11 |
12 | void update_level(blit::Timer &timer);
13 | void animate_level(blit::Timer &timer);
--------------------------------------------------------------------------------
/ci/install_sdl_vs.sh:
--------------------------------------------------------------------------------
1 | if [ ! -f 32blit/vs/sdl/include/SDL_image.h ]; then
2 | curl https://libsdl.org/release/SDL2-devel-2.0.10-VC.zip -o SDL2.zip
3 | curl https://www.libsdl.org/projects/SDL_image/release/SDL2_image-devel-2.0.5-VC.zip -o SDL2_image.zip
4 |
5 | unzip SDL2.zip -d 32blit/vs/sdl
6 | unzip SDL2_image.zip -d 32blit/vs/sdl
7 |
8 | # move dirs up
9 | mv 32blit/vs/sdl/SDL2-2.0.10/* 32blit/vs/sdl
10 | cp -r 32blit/vs/sdl/SDL2_image-2.0.5/* 32blit/vs/sdl
11 | fi
12 |
--------------------------------------------------------------------------------
/assets/level00.tmx:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/assets.yml:
--------------------------------------------------------------------------------
1 | # Define an output target for the asset builder
2 | # in this case we want a CSource (and implicitly also a header file)
3 | # type auto-detection will notice the ".cpp" and act accordingly
4 | assets.cpp:
5 | prefix: asset_
6 | # Include assets/sprites.png
7 | # and place it in a variable named "sprites"
8 | # Since it ends in ".png" the builder will run "sprites_packed" to convert our source file
9 | assets/sprites.png:
10 | name: sprites
11 | palette: assets/sprites.act
12 | strict: true # Fail if a colour does not exist in the palette
13 | transparent: 255,0,255
14 |
15 | # Include assets/level.tmx
16 | # and place it in a variable named "level"
17 | # Since it ends in ".tmx" the builder will run "map_tiled" to convert our source file
18 | assets/level*.tmx:
19 | name:
20 | empty_tile: 0
21 | output_struct: true
22 |
--------------------------------------------------------------------------------
/splash.tmx:
--------------------------------------------------------------------------------
1 |
2 |
21 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Basic parameters; check that these match your project / environment
2 | cmake_minimum_required(VERSION 3.8)
3 |
4 | project(rocks-and-diamonds)
5 | set(PROJECT_SOURCE rocks-and-diamonds.cpp rocks-and-diamonds.hpp)
6 | set(PROJECT_DISTRIBS LICENSE README.md)
7 |
8 | # Build configuration; approach this with caution!
9 | if(MSVC)
10 | add_compile_options("/W4" "/wd4244" "/wd4324")
11 | else()
12 | add_compile_options("-Wall" "-Wextra" "-Wdouble-promotion")
13 | endif()
14 | find_package(32BLIT CONFIG REQUIRED PATHS ../ /opt)
15 |
16 | blit_executable (${PROJECT_NAME} ${PROJECT_SOURCE})
17 | blit_assets_yaml (${PROJECT_NAME} assets.yml)
18 | blit_metadata (${PROJECT_NAME} metadata.yml)
19 | add_custom_target (flash DEPENDS ${PROJECT_NAME}.flash)
20 |
21 | # setup release packages
22 | install (FILES ${PROJECT_DISTRIBS} DESTINATION .)
23 | set (CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)
24 | set (CPACK_GENERATOR "ZIP" "TGZ")
25 | include (CPack)
26 |
--------------------------------------------------------------------------------
/assets/level01.tmx:
--------------------------------------------------------------------------------
1 |
2 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Phil Howard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/assets/level03.tmx:
--------------------------------------------------------------------------------
1 |
2 |
55 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # Build Github Action, to run a test build on all targets
2 | # (Linux, Blit, MacOS, Visual Studio) when the project is checked in.
3 | #
4 | # Thanks in large part to the phenomenal examples of DaftFreak.
5 |
6 | name: Build
7 |
8 | on:
9 | push:
10 | branches:
11 | - '**' # only run on branches
12 | pull_request:
13 | release:
14 | types: [created]
15 |
16 | env:
17 | BUILD_TYPE: Release
18 | EM_VERSION: 2.0.18 # Emscripten version
19 | EM_CACHE_FOLDER: 'emsdk-cache' # Cache for Emscripten libs
20 |
21 | jobs:
22 |
23 | build:
24 |
25 | name: ${{matrix.name}}
26 | strategy:
27 | matrix:
28 | include:
29 | - os: ubuntu-20.04
30 | name: Linux
31 | release-suffix: LIN64
32 | cmake-args: -D32BLIT_DIR=$GITHUB_WORKSPACE/32blit-sdk
33 | apt-packages: libsdl2-dev libsdl2-image-dev libsdl2-net-dev python3-setuptools
34 |
35 | - os: ubuntu-20.04
36 | name: STM32
37 | release-suffix: STM32
38 | cmake-args: -DCMAKE_TOOLCHAIN_FILE=$GITHUB_WORKSPACE/32blit-sdk/32blit.toolchain
39 | apt-packages: gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib python3-setuptools
40 |
41 | - os: ubuntu-20.04
42 | pico-sdk: true
43 | name: PicoSystem
44 | cache-key: picosystem
45 | release-suffix: PicoSystem
46 | cmake-args: -DCMAKE_TOOLCHAIN_FILE=$GITHUB_WORKSPACE/32blit-sdk/pico.toolchain -DPICO_BOARD=pimoroni_picosystem
47 | apt-packages: gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib python3-setuptools
48 |
49 | - os: ubuntu-20.04
50 | name: Emscripten
51 | release-suffix: WEB
52 | cmake-args: -D32BLIT_DIR=$GITHUB_WORKSPACE/32blit-sdk
53 | cmake-prefix: emcmake
54 | apt-packages: python3-setuptools
55 |
56 | - os: macos-latest
57 | name: macOS
58 | release-suffix: MACOS
59 | cmake-args: -D32BLIT_DIR=$GITHUB_WORKSPACE/32blit-sdk
60 | brew-packages: sdl2 sdl2_image sdl2_net
61 |
62 | - os: windows-latest
63 | name: Visual Studio
64 | release-suffix: WIN64
65 | cmake-args: -D32BLIT_DIR=$GITHUB_WORKSPACE/32blit-sdk -DSDL2_DIR=$GITHUB_WORKSPACE/32blit-sdk/vs/sdl
66 |
67 | runs-on: ${{matrix.os}}
68 |
69 | env:
70 | RELEASE_FILE: ${{github.event.repository.name}}-${{github.event.release.tag_name}}-${{matrix.release-suffix}}
71 |
72 | steps:
73 | # Check out the main repo
74 | - name: Checkout
75 | uses: actions/checkout@v2
76 | with:
77 | path: main
78 |
79 | # Check out the 32Blit API we build against
80 | - name: Checkout 32Blit API
81 | uses: actions/checkout@v2
82 | with:
83 | repository: 32blit/32blit-sdk
84 | path: 32blit-sdk
85 |
86 | # pico sdk/extras for some builds
87 | - name: Checkout Pico SDK
88 | if: matrix.pico-sdk
89 | uses: actions/checkout@v2
90 | with:
91 | repository: raspberrypi/pico-sdk
92 | path: pico-sdk
93 | submodules: true
94 |
95 | - name: Checkout Pico Extras
96 | if: matrix.pico-sdk
97 | uses: actions/checkout@v2
98 | with:
99 | repository: raspberrypi/pico-extras
100 | path: pico-extras
101 |
102 | # Linux dependencies
103 | - name: Install Linux deps
104 | if: runner.os == 'Linux'
105 | run: |
106 | sudo apt update && sudo apt install ${{matrix.apt-packages}}
107 | pip3 install 32blit
108 |
109 | # MacOS dependencies
110 | - name: Install macOS deps
111 | if: runner.os == 'macOS'
112 | run: |
113 | brew install ${{matrix.brew-packages}}
114 | python3 -m pip install 32blit
115 |
116 | # Windows dependencies
117 | - name: Install Windows deps
118 | if: runner.os == 'Windows'
119 | shell: bash
120 | run: |
121 | python -m pip install 32blit
122 |
123 | # Emscripten SDK setup
124 | - name: Setup Emscripten cache
125 | if: matrix.name == 'Emscripten'
126 | id: cache-system-libraries
127 | uses: actions/cache@v2
128 | with:
129 | path: ${{env.EM_CACHE_FOLDER}}
130 | key: ${{env.EM_VERSION}}-${{runner.os}}
131 |
132 | - name: Setup Emscripten
133 | if: matrix.name == 'Emscripten'
134 | uses: mymindstorm/setup-emsdk@v7
135 | with:
136 | version: ${{env.EM_VERSION}}
137 | actions-cache-folder: ${{env.EM_CACHE_FOLDER}}
138 |
139 | - name: Pre-build Emscripten ports
140 | if: matrix.name == 'Emscripten'
141 | run: embuilder.py build sdl2 sdl2-image-jpg sdl2-net
142 |
143 | # Set up the cmake build environment
144 | - name: Create Build Environment
145 | run: cmake -E make_directory ${{runner.workspace}}/main/build
146 |
147 | # Ask cmake to build the makefiles
148 | - name: Configure CMake
149 | shell: bash
150 | working-directory: ${{runner.workspace}}/main/build
151 | run: ${{matrix.cmake-prefix}} cmake $GITHUB_WORKSPACE/main -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCPACK_PACKAGE_FILE_NAME=${{env.RELEASE_FILE}} ${{matrix.cmake-args}}
152 |
153 | # And then run the build itself
154 | - name: Build
155 | working-directory: ${{runner.workspace}}/main/build
156 | shell: bash
157 | run: |
158 | cmake --build . --config $BUILD_TYPE -j 2
159 |
160 | # When it's a release, generate tar/zip files of the build
161 | - name: Package Release
162 | if: github.event_name == 'release' && matrix.release-suffix != ''
163 | shell: bash
164 | working-directory: ${{runner.workspace}}/main/build
165 | run: |
166 | cmake --build . --config $BUILD_TYPE --target package
167 |
168 | # Push the tar file to the release
169 | - name: Upload tar
170 | if: github.event_name == 'release' && matrix.release-suffix != ''
171 | uses: actions/upload-release-asset@v1
172 | env:
173 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
174 | with:
175 | asset_path: ${{runner.workspace}}/main/build/${{env.RELEASE_FILE}}.tar.gz
176 | upload_url: ${{github.event.release.upload_url}}
177 | asset_name: ${{env.RELEASE_FILE}}.tar.gz
178 | asset_content_type: application/octet-stream
179 |
180 | # Push the zip file to the release
181 | - name: Upload zip
182 | if: github.event_name == 'release' && matrix.release-suffix != ''
183 | uses: actions/upload-release-asset@v1
184 | env:
185 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
186 | with:
187 | asset_path: ${{runner.workspace}}/main/build/${{env.RELEASE_FILE}}.zip
188 | upload_url: ${{github.event.release.upload_url}}
189 | asset_name: ${{env.RELEASE_FILE}}.zip
190 | asset_content_type: application/zip
191 |
--------------------------------------------------------------------------------
/assets/level02.tmx:
--------------------------------------------------------------------------------
1 |
2 |
73 |
--------------------------------------------------------------------------------
/rocks-and-diamonds.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | #include "rocks-and-diamonds.hpp"
7 |
8 | using namespace blit;
9 |
10 | #define PLAYER_TOP (player.position.y)
11 | #define PLAYER_BOTTOM (player.position.y + player.size.y)
12 | #define PLAYER_RIGHT (player.position.x + player.size.x)
13 | #define PLAYER_LEFT (player.position.x)
14 |
15 | const Point P_LEFT(-1, 0);
16 | const Point P_RIGHT(1, 0);
17 | const Point P_BELOW(0, 1);
18 | const Point P_ABOVE(0, -1);
19 |
20 | const Point P_ALEFT = P_LEFT + P_ABOVE;
21 | const Point P_ARIGHT = P_RIGHT + P_ABOVE;
22 | const Point P_BLEFT = P_LEFT + P_BELOW;
23 | const Point P_BRIGHT = P_RIGHT + P_BELOW;
24 |
25 | const Size LEVEL_SIZE(64, 64);
26 |
27 | const void* levels[] = {
28 | &asset_assets_level00_tmx,
29 | &asset_assets_level01_tmx,
30 | &asset_assets_level02_tmx,
31 | &asset_assets_level03_tmx
32 | };
33 |
34 | uint8_t *level_data;
35 |
36 | Timer timer_level_update;
37 | Timer timer_level_animate;
38 | TileMap* level;
39 |
40 | struct Feedback {
41 | bool rock_thunk;
42 | };
43 |
44 | struct Player {
45 | Point start;
46 | Point position;
47 | Point screen_location;
48 | uint32_t score;
49 | Vec2 camera;
50 | Point size = Point(1, 1);
51 | bool facing = true;
52 | bool has_key;
53 | uint32_t level;
54 | bool dead;
55 | };
56 |
57 | Player player;
58 |
59 | Feedback feedback;
60 |
61 | Mat3 camera;
62 |
63 | enum entityType {
64 | // Terrain
65 | NOTHING = 0x00,
66 | DIRT = 0x01,
67 | WALL = 0x02,
68 | STAIRS = 0x03,
69 | LOCKED_STAIRS = 0x04,
70 |
71 | // Rocks and diamonds
72 | ROCK = 0x10,
73 | DIAMOND = 0x11,
74 |
75 | // Player animations... or lack thereof
76 | PLAYER = 0x30,
77 | PLAYER_FL = 0x31,
78 | PLAYER_SQUASHED = 0x3e,
79 | PLAYER_DEAD = 0x3f,
80 |
81 | // Collectable entities that aren't diamonds
82 | KEY_SILVER = 0x20,
83 | KEY_GOLD = 0x21,
84 |
85 | // Animations, ho boy this is ugly!
86 | DIRT_ANIM_1 = 0x50,
87 | DIRT_ANIM_2 = 0x51,
88 | DIRT_ANIM_3 = 0x52,
89 | DIRT_ANIM_4 = 0x53,
90 |
91 | // So ugly, oh boy!
92 | BOMB_ANIM_1 = 0x60,
93 | BOMB_ANIM_2 = 0x61,
94 | BOMB_ANIM_3 = 0x62,
95 | BOMB_ANIM_4 = 0x63,
96 | BOMB_ANIM_5 = 0x64,
97 | BOMB_ANIM_6 = 0x65,
98 | };
99 |
100 | // Line-interrupt callback for level->draw that applies our camera transformation
101 | // This can be expanded to add effects like camera shake, wavy dream-like stuff, all the fun!
102 | Mat3 level_line_interrupt_callback(uint8_t y) {
103 | (void)y; // Camera is updated elsewhere and is scanline-independent
104 | return camera;
105 | };
106 |
107 | void update_camera(uint32_t time) {
108 | (void)time;
109 |
110 | static uint32_t thunk_a_bunch = 0;
111 | // Create a camera transform that centers around the player's position
112 | if(player.camera.x < player.position.x) {
113 | player.camera.x += 0.1f;
114 | }
115 | if(player.camera.x > player.position.x) {
116 | player.camera.x -= 0.1f;
117 | }
118 | if(player.camera.y < player.position.y) {
119 | player.camera.y += 0.1f;
120 | }
121 | if(player.camera.y > player.position.y) {
122 | player.camera.y -= 0.1f;
123 | }
124 |
125 | if(feedback.rock_thunk) {
126 | thunk_a_bunch = 10;
127 | feedback.rock_thunk = 0;
128 | }
129 |
130 | camera = Mat3::identity();
131 | camera *= Mat3::translation(Vec2(player.camera.x * 8.0f, player.camera.y * 8.0f)); // offset to middle of world
132 | camera *= Mat3::translation(Vec2(-screen.bounds.w / 2, -screen.bounds.h / 2)); // transform to centre of framebuffer
133 |
134 | if(thunk_a_bunch){
135 | camera *= Mat3::translation(Vec2(
136 | float(blit::random() & 0x03) - 1.5f,
137 | float(blit::random() & 0x03) - 1.5f
138 | ));
139 | thunk_a_bunch--;
140 | vibration = thunk_a_bunch / 10.0f;
141 | }
142 |
143 |
144 | }
145 |
146 | Point level_first(entityType entity) {
147 | for(auto x = 0; x < LEVEL_SIZE.w; x++) {
148 | for(auto y = 0; y < LEVEL_SIZE.h; y++) {
149 | if (level_data[y * LEVEL_SIZE.w + x] == entity) {
150 | return Point(x, y);
151 | }
152 | }
153 | }
154 | return Point(-1, -1);
155 | }
156 |
157 | void level_set(Point location, entityType entity) {
158 | level_data[location.y * LEVEL_SIZE.w + location.x] = entity;
159 | }
160 |
161 | bool player_at(Point location) {
162 | return (player.position.x == location.x && player.position.y == location.y);
163 | }
164 |
165 | entityType level_get(Point location) {
166 | static Rect level_bounds(Point(0, 0), LEVEL_SIZE);
167 | if(!level_bounds.contains(location)) {
168 | return WALL;
169 | }
170 | entityType entity = (entityType)level_data[location.y * LEVEL_SIZE.w + location.x];
171 | if(entity == NOTHING && player_at(location)) {
172 | entity = PLAYER;
173 | }
174 | return entity;
175 | }
176 |
177 | void level_set(Point location, entityType entity, bool not_nothing) {
178 | if(not_nothing) {
179 | if(level_get(location) != NOTHING) {
180 | level_set(location, entity);
181 | }
182 | } else {
183 | level_set(location, entity);
184 | }
185 | }
186 |
187 | void animate_level(Timer &timer) {
188 | (void)timer;
189 |
190 | Point location = Point(0, 0);
191 | for(location.y = LEVEL_SIZE.h - 1; location.y > -1; location.y--) {
192 | for(location.x = 0; location.x < LEVEL_SIZE.w + 1; location.x++) {
193 | entityType current = level_get(location);
194 |
195 | if(current == DIRT_ANIM_4) {
196 | level_set(location, NOTHING);
197 | } else if(current == DIRT_ANIM_3) {
198 | level_set(location, DIRT_ANIM_4);
199 | } else if(current == DIRT_ANIM_2) {
200 | level_set(location, DIRT_ANIM_3);
201 | } else if(current == DIRT_ANIM_1) {
202 | level_set(location, DIRT_ANIM_2);
203 | }
204 |
205 | if(current == BOMB_ANIM_1) {
206 | level_set(location, BOMB_ANIM_2);
207 | } else if(current == BOMB_ANIM_2) {
208 | level_set(location, BOMB_ANIM_3);
209 | } else if(current == BOMB_ANIM_3) {
210 | level_set(location, BOMB_ANIM_4);
211 | } else if(current == BOMB_ANIM_4) {
212 | level_set(location, BOMB_ANIM_5);
213 | } else if(current == BOMB_ANIM_5) {
214 | level_set(location, BOMB_ANIM_6);
215 | } else if(current == BOMB_ANIM_6) {
216 | level_set(location, NOTHING);
217 | level_set(location + P_BELOW, DIRT_ANIM_1, true);
218 | level_set(location + P_ABOVE, DIRT_ANIM_1, true);
219 | level_set(location + P_RIGHT, DIRT_ANIM_1, true);
220 | level_set(location + P_LEFT, DIRT_ANIM_1, true);
221 | level_set(location + P_BRIGHT, DIRT_ANIM_1, true);
222 | level_set(location + P_ARIGHT, DIRT_ANIM_1, true);
223 | level_set(location + P_BLEFT, DIRT_ANIM_1, true);
224 | level_set(location + P_ALEFT, DIRT_ANIM_1, true);
225 | }
226 | }
227 | }
228 | }
229 |
230 | void update_level(Timer &timer) {
231 | (void)timer;
232 |
233 | Point location = Point(0, 0);
234 | for(location.y = LEVEL_SIZE.h - 1; location.y > 0; location.y--) {
235 | for(location.x = 0; location.x < LEVEL_SIZE.w; location.x++) {
236 | Point location_below = location + P_BELOW;
237 | entityType current = level_get(location);
238 | entityType below = level_get(location_below);
239 |
240 | for(entityType check_entity : {ROCK, DIAMOND}) {
241 | if(current == check_entity) {
242 | if (below == NOTHING) {
243 | // If the space underneath is empty, fall down
244 | level_set(location, NOTHING);
245 | level_set(location_below, check_entity);
246 |
247 | if(check_entity == ROCK) {
248 | // Add a little *THUNK* effect for rocks falling directly down
249 | Point location_land = location_below + P_BELOW;
250 | switch (level_get(location_land)) {
251 | case WALL:
252 | feedback.rock_thunk = true;
253 | break;
254 | case PLAYER:
255 | player.dead = true;
256 | feedback.rock_thunk = true;
257 | level_set(location_land, PLAYER_SQUASHED);
258 | default:
259 | break;
260 | }
261 | }
262 | } else if (below == PLAYER_SQUASHED && check_entity == ROCK) {
263 | level_set(location, NOTHING);
264 | level_set(location_below, PLAYER_DEAD);
265 | } else if (below == ROCK || below == DIAMOND) {
266 | // If the space below is a rock or a diamond, check to the left/right
267 | // and "roll" down the stack
268 | entityType left = level_get(location + P_LEFT);
269 | entityType below_left = level_get(location + P_BLEFT);
270 | entityType right = level_get(location + P_RIGHT);
271 | entityType below_right = level_get(location + P_BRIGHT);
272 |
273 | if(left == NOTHING && below_left == NOTHING){
274 | level_set(location, NOTHING);
275 | level_set(location + Point(-1, 1), check_entity);
276 | } else if(right == NOTHING && below_right == NOTHING){
277 | level_set(location, NOTHING);
278 | level_set(location + Point(1, 1), check_entity);
279 | }
280 | }
281 | }
282 | }
283 |
284 | }
285 | }
286 | }
287 |
288 | void new_game(uint32_t level) {
289 | // Get a pointer to the map header
290 | TMX *tmx = (TMX *)levels[level];
291 |
292 | // Bail if the map is oversized
293 | if(tmx->width > LEVEL_SIZE.w) return;
294 | if(tmx->height > LEVEL_SIZE.h) return;
295 |
296 | // Clear the level data
297 | memset(level_data, 0, LEVEL_SIZE.area());
298 |
299 | // Load the level data from the map memory
300 | for(auto x = 0u; x < tmx->width; x++) {
301 | for(auto y = 0u; y < tmx->height; y++) {
302 | auto src = y * tmx->width + x;
303 | auto dst = y * LEVEL_SIZE.w + x;
304 | level_data[dst] = tmx->data[src];
305 | }
306 | }
307 |
308 | player.start = level_first(PLAYER);
309 | level_set(player.start, NOTHING);
310 | player.position = player.start;
311 | player.camera = Vec2(player.position);
312 | player.has_key = false;
313 | player.score = 0;
314 | player.dead = false;
315 |
316 | player.screen_location = Point(screen.bounds.w / 2, screen.bounds.h / 2);
317 | player.screen_location += Point(1, 1);
318 | }
319 |
320 | void init() {
321 | set_screen_mode(ScreenMode::lores);
322 |
323 | // Load the spritesheet from the linked binary blob
324 | screen.sprites = Surface::load(asset_sprites);
325 |
326 | // Allocate memory for the writeable copy of the level
327 | level_data = (uint8_t *)malloc(LEVEL_SIZE.area());
328 |
329 | // Load our level data into the TileMap
330 | level = new TileMap((uint8_t *)level_data, nullptr, Size(LEVEL_SIZE.w, LEVEL_SIZE.h), screen.sprites);
331 |
332 | timer_level_update.init(update_level, 250, -1);
333 | timer_level_update.start();
334 |
335 | timer_level_animate.init(animate_level, 100, -1);
336 | timer_level_animate.start();
337 |
338 | new_game(0);
339 | }
340 |
341 | void render(uint32_t time) {
342 | (void)time;
343 |
344 | screen.pen = Pen(0, 0, 0);
345 | screen.clear();
346 |
347 | // Draw our level
348 | level->draw(&screen, Rect(0, 0, screen.bounds.w, screen.bounds.h), level_line_interrupt_callback);
349 |
350 | // Draw our character sprite
351 | if(!player.dead) {
352 | screen.sprite(player.facing ? entityType::PLAYER : entityType::PLAYER_FL, player.screen_location);
353 | }
354 |
355 | // Draw the header bar
356 | screen.pen = Pen(255, 255, 255);
357 | screen.rectangle(Rect(0, 0, screen.bounds.w, 10));
358 | screen.pen = Pen(0, 0, 0);
359 | screen.text("Level: " + std::to_string(player.level) + " Score: " + std::to_string(player.score), minimal_font, Point(2, 2));
360 |
361 | if(player.has_key) {
362 | screen.sprite(entityType::KEY_SILVER, Point(screen.bounds.w - 10, 1));
363 | }
364 | // screen.text(std::to_string(player.position.x), minimal_font, Point(0, 0));
365 | // screen.text(std::to_string(player.position.y), minimal_font, Point(0, 10));
366 | }
367 |
368 | void update(uint32_t time) {
369 | (void)time;
370 |
371 | Point movement = Point(0, 0);
372 |
373 | if(buttons.pressed & Button::B) {
374 | new_game(player.level);
375 | }
376 |
377 | if(buttons.pressed & Button::A) {
378 | if(level_get(player.position + Point(0, 1)) == NOTHING) {
379 | level_set(player.position + Point(0, 1), BOMB_ANIM_1);
380 | }
381 | }
382 |
383 | if(!player.dead) {
384 | if(buttons.pressed & Button::DPAD_UP) {
385 | movement.y = -1;
386 | }
387 | if(buttons.pressed & Button::DPAD_DOWN) {
388 | movement.y = 1;
389 | }
390 | if(buttons.pressed & Button::DPAD_LEFT) {
391 | player.facing = false;
392 | movement.x = -1;
393 | }
394 | if(buttons.pressed & Button::DPAD_RIGHT) {
395 | player.facing = true;
396 | movement.x = 1;
397 | }
398 |
399 | player.position += movement;
400 |
401 | entityType standing_on = level_get(player.position);
402 |
403 | switch(standing_on) {
404 | case WALL:
405 | player.position -= movement;
406 | break;
407 | case ROCK:
408 | // Push rocks if there's an empty space the other side of them
409 | if(movement.x > 0 && level_get(player.position + Point(1, 0)) == NOTHING){
410 | level_set(player.position + Point(1, 0), ROCK);
411 | level_set(player.position, NOTHING);
412 | }
413 | else if(movement.x < 0 && level_get(player.position + Point(-1, 0)) == NOTHING){
414 | level_set(player.position + Point(-1, 0), ROCK);
415 | level_set(player.position, NOTHING);
416 | }
417 | else {
418 | player.position -= movement;
419 | }
420 | break;
421 | case DIAMOND:
422 | // Collect diamonds!
423 | player.score += 1;
424 | level_set(player.position, NOTHING);
425 | break;
426 | case DIRT:
427 | // Dig dirt!
428 | level_set(player.position, DIRT_ANIM_1);
429 | break;
430 | case KEY_SILVER:
431 | player.has_key = true;
432 | level_set(player.position, NOTHING);
433 | break;
434 | case LOCKED_STAIRS:
435 | if(player.has_key) {
436 | level_set(player.position, STAIRS);
437 | }
438 | player.position -= movement;
439 | break;
440 | case STAIRS:
441 | player.level++;
442 | new_game(player.level);
443 | break;
444 | default:
445 | break;
446 | }
447 | }
448 |
449 | update_camera(time);
450 | }
--------------------------------------------------------------------------------