ef3c4711e0e2b2722845ea2a18135684776b8c0e
[hackover2013-badge-firmware.git] / badge / jumpnrun / jumpnrun.c
1 #include "jumpnrun.h"
2 #include "collision.h"
3
4 #include <badge/ui/display.h>
5 #include <badge/ui/event.h>
6 #include <badge/ui/sprite.h>
7
8 #include <assert.h>
9 #include <math.h>
10 #include <stddef.h>
11 #include <stdio.h>
12
13 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof*(arr))
14
15 static vec2d const gravity = { FIXED_POINT_I(0, 0), FIXED_POINT_I(0, 56) };
16 static vec2d const move_max = { FIXED_POINT_I(1, 200), FIXED_POINT_I(1, 300) };
17 static fixed_point const accel_horiz = FIXED_POINT_I(0, 100);
18 static fixed_point const accel_vert = FIXED_POINT_I(0, 250);
19 static fixed_point const drag_factor = FIXED_POINT_I(0, 854);
20 static fixed_point const speed_jump_x = FIXED_POINT_I(1, 200);
21
22 static badge_sprite const anim_hacker[] = {
23 { 5, 8, (uint8_t const *) "\x1c\xff\xfd\x04\x04" },
24 { 5, 8, (uint8_t const *) "\x1c\xff\x3d\xc4\x04" },
25 { 5, 8, (uint8_t const *) "\xdc\x3f\x1d\x24\xc4" },
26 { 5, 8, (uint8_t const *) "\x1c\xff\x3d\xc4\x04" }
27
28 /*
29 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x31" },
30 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x52" },
31 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x94\x8c" },
32 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x52" }
33 */
34 /*
35 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\xc3\x30" },
36 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x43\x51" },
37 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x35\x82" },
38 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x43\x51" }
39 */
40 /*
41 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
42 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
43 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
44 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" }
45 */
46 };
47
48
49 static inline int imax(int x, int y) {
50 return x < y ? y : x;
51 }
52
53 static inline fixed_point hacker_left (vec2d const *pos, jumpnrun_game_state const *state) { (void) state; return pos->x; }
54 static inline fixed_point hacker_top (vec2d const *pos, jumpnrun_game_state const *state) { (void) state; return pos->y; }
55 static inline fixed_point hacker_right (vec2d const *pos, jumpnrun_game_state const *state) { return fixed_point_add(hacker_left(pos, state), FIXED_POINT(anim_hacker[state->anim_frame].width , 0)); }
56 static inline fixed_point hacker_bottom(vec2d const *pos, jumpnrun_game_state const *state) { return fixed_point_add(hacker_top (pos, state), FIXED_POINT(anim_hacker[state->anim_frame].height, 0)); }
57
58 static inline rectangle hacker_rect(vec2d const *pos,
59 jumpnrun_game_state const *state) {
60 return (rectangle) { { hacker_left(pos, state), hacker_top(pos, state) }, { FIXED_POINT(anim_hacker[state->anim_frame].width, 0), FIXED_POINT(anim_hacker[state->anim_frame].height, 0) } };
61 }
62
63 rectangle hacker_rect_current(jumpnrun_game_state const *state) {
64 return hacker_rect(&state->current_pos, state);
65 }
66
67 int jumpnrun_level_assert_left_side(jumpnrun_game_state const *state) {
68 static int const lmargin = 20;
69 static int const rmargin = 50;
70
71 int pos_cur = fixed_point_cast_int(state->current_pos.x);
72 int pos_rel = pos_cur - state->left;
73
74 if(pos_rel < lmargin) {
75 return imax(0, pos_cur - lmargin);
76 } else if(pos_rel > BADGE_DISPLAY_WIDTH - rmargin) {
77 return pos_cur - (BADGE_DISPLAY_WIDTH - rmargin);
78 }
79
80 return state->left;
81 }
82
83 static int jumpnrun_bsearch_tile(jumpnrun_level const *lv, jumpnrun_game_state const *state) {
84 int front = 0;
85 int len = lv->header.tile_count;
86
87 while(len > 0) {
88 int mid = front + len / 2;
89
90 if(fixed_point_lt(tile_right(&lv->tiles[mid]), FIXED_POINT(state->left - JUMPNRUN_MAX_SPAWN_MARGIN, 0))) {
91 front = mid + 1;
92 len -= len / 2 + 1;
93 } else {
94 len /= 2;
95 }
96 }
97
98 return front;
99 }
100
101 jumpnrun_tile_range jumpnrun_visible_tiles(jumpnrun_level const *lv,
102 jumpnrun_game_state const *state) {
103 jumpnrun_tile_range r;
104
105 r.first = jumpnrun_bsearch_tile(lv, state);
106
107 for(r.last = r.first;
108 r.last < lv->header.tile_count && lv->tiles[r.last].pos.x * JUMPNRUN_TILE_PIXEL_WIDTH < state->left + BADGE_DISPLAY_WIDTH + JUMPNRUN_MAX_SPAWN_MARGIN;
109 ++r.last)
110 ;
111
112 return r;
113 }
114
115 void jumpnrun_passive_movement(vec2d *inertia)
116 {
117 *inertia = vec2d_add(*inertia, gravity);
118
119 inertia->x = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.x), inertia->x), move_max.x);
120 inertia->y = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.y), inertia->y), move_max.y);
121 }
122
123 static void jumpnrun_apply_movement(jumpnrun_level const *lv,
124 jumpnrun_tile_range const *tilerange,
125 jumpnrun_game_state *state) {
126 switch(badge_event_current_input_state() &
127 (BADGE_EVENT_KEY_LEFT |
128 BADGE_EVENT_KEY_RIGHT)) {
129 case BADGE_EVENT_KEY_LEFT:
130 // state->inertia.x = state->touching_ground ? fixed_point_sub(state->inertia.x, accel_horiz) : fixed_point_neg(speed_jump_x);
131 state->inertia.x = fixed_point_sub(state->inertia.x, accel_horiz);
132 state->anim_direction = BADGE_BLT_MIRRORED;
133 break;
134 case BADGE_EVENT_KEY_RIGHT:
135 // state->inertia.x = state->touching_ground ? fixed_point_add(state->inertia.x, accel_horiz) : speed_jump_x;
136 state->inertia.x = fixed_point_add(state->inertia.x, accel_horiz);
137 state->anim_direction = 0;
138 break;
139 default:
140 if(state->touching_ground) {
141 state->inertia.x = fixed_point_mul(state->inertia.x, drag_factor);
142 } //else {
143 //state->inertia.x = FIXED_POINT(0, 0);
144 //}
145
146 break;
147 }
148
149 if(state->jumpable_frames == 0) {
150 // intentionally left blank.
151 } else if(badge_event_current_input_state() & BADGE_EVENT_KEY_BTN_A) {
152 state->inertia.y = fixed_point_sub(state->inertia.y, accel_vert);
153 // fixed_point_neg(move_max.y)
154 --state->jumpable_frames;
155 } else {
156 state->jumpable_frames = 0;
157 }
158
159 jumpnrun_passive_movement(&state->inertia);
160
161 state->inertia.x = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.x), state->inertia.x), move_max.x);
162 state->inertia.y = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.y), state->inertia.y), move_max.y);
163
164 vec2d new_pos = vec2d_add(state->current_pos, state->inertia);
165
166 if(fixed_point_lt(new_pos.x, FIXED_POINT(state->left, 0))) {
167 new_pos.x = FIXED_POINT(state->left, 0);
168 state->inertia.x = FIXED_POINT(0, 0);
169 }
170
171 rectangle hacker_rect_c = hacker_rect(&state->current_pos, state);
172 collisions_tiles_displace(&new_pos, &hacker_rect_c, lv, tilerange, &state->inertia, &state->touching_ground);
173
174 state->current_pos = new_pos;
175
176 if(fixed_point_gt(state->current_pos.y, FIXED_POINT(BADGE_DISPLAY_HEIGHT, 0))) {
177 state->status = JUMPNRUN_DEAD;
178 }
179 }
180
181 void jumpnrun_level_tick(jumpnrun_level *lv,
182 jumpnrun_game_state *state)
183 {
184 jumpnrun_tile_range tilerange = jumpnrun_visible_tiles(lv, state);
185 jumpnrun_apply_movement(lv, &tilerange, state);
186
187 state->left = jumpnrun_level_assert_left_side(state);
188
189 if(state->tick_minor == 0) {
190 badge_framebuffer fb;
191 badge_framebuffer_clear(&fb);
192
193 for(size_t tile = tilerange.first; tile < tilerange.last; ++tile) {
194 badge_framebuffer_blt(&fb,
195 fixed_point_cast_int(tile_left(&lv->tiles[tile])) - state->left,
196 fixed_point_cast_int(tile_top (&lv->tiles[tile])),
197 &tile_type(&lv->tiles[tile])->sprite,
198 0);
199 }
200
201 for(size_t item = 0; item < lv->header.item_count; ++item) {
202 int screenpos = fixed_point_cast_int(lv->items[item].pos.x) - state->left;
203 if(screenpos > -lv->items[item].type->sprite.width &&
204 screenpos < BADGE_DISPLAY_WIDTH) {
205 rectangle hack_rect = hacker_rect(&state->current_pos, state);
206 rectangle item_rect = rect_from_item(&lv->items[item]);
207
208 if(rectangle_intersect(&hack_rect, &item_rect)) {
209 lv->items[item].type->on_collect(state);
210 }
211
212 badge_framebuffer_blt(&fb,
213 screenpos,
214 fixed_point_cast_int(lv->items[item].pos.y),
215 &lv->items[item].type->sprite,
216 0);
217 }
218 }
219
220 for(size_t enemy_ix = 0; enemy_ix < lv->header.enemy_count; ++enemy_ix) {
221 jumpnrun_enemy *enemy = &lv->enemies[enemy_ix];
222 jumpnrun_process_enemy(enemy, &fb, state, lv, &tilerange);
223 }
224
225 badge_framebuffer_blt(&fb,
226 fixed_point_cast_int(state->current_pos.x) - state->left,
227 fixed_point_cast_int(state->current_pos.y),
228 &anim_hacker[state->anim_frame],
229 state->anim_direction);
230
231 badge_framebuffer_flush(&fb);
232
233 if(!state->touching_ground) {
234 state->anim_frame = 2;
235 } else if(fixed_point_gt(fixed_point_abs(state->inertia.x), FIXED_POINT(0, 200))) {
236 state->anim_frame = (state->anim_frame + 1) % ARRAY_SIZE(anim_hacker);
237 } else {
238 state->anim_frame = 0;
239 }
240 }
241
242 state->tick_minor = (state->tick_minor + 1) % 2;
243 }
244
245 uint8_t jumpnrun_play(char const *lvname) {
246 jumpnrun_level lv;
247
248 memset(&lv, 0, sizeof(lv));
249
250 // This part looks ugly. The reason it's done this way is that we don't know how much memory
251 // we need for the level before parsing its header, and that the VLAs we use to store it have
252 // block scope.
253 // Still, better than opening the whole dynamic memory can of worms.
254 FIL fd;
255 int err;
256
257 if(FR_OK != f_open(&fd, lvname, FA_OPEN_EXISTING | FA_READ)) { return JUMPNRUN_ERROR; }
258
259 if(0 != jumpnrun_load_level_header_from_file(&lv, &fd)) {
260 f_close(&fd);
261 return JUMPNRUN_ERROR;
262 }
263
264 JUMPNRUN_LEVEL_MAKE_SPACE(lv);
265 err = jumpnrun_load_level_from_file(&lv, &fd);
266
267 f_close(&fd);
268 if(err != 0) {
269 return JUMPNRUN_ERROR;
270 }
271
272 jumpnrun_game_state gs;
273 memset(&gs, 0, sizeof(gs));
274
275 gs.current_pos = lv.start_pos;
276
277 while(gs.status == JUMPNRUN_PLAYING) {
278 badge_event_t ev = badge_event_wait();
279
280 switch(badge_event_type(ev)) {
281 case BADGE_EVENT_USER_INPUT:
282 {
283 uint8_t old_state = badge_event_old_input_state(ev);
284 uint8_t new_state = badge_event_new_input_state(ev);
285 uint8_t new_buttons = new_state & (old_state ^ new_state);
286
287 if((new_buttons & BADGE_EVENT_KEY_BTN_B) && gs.touching_ground) {
288 gs.jumpable_frames = 8;
289 }
290
291 break;
292 }
293 case BADGE_EVENT_GAME_TICK:
294 {
295 jumpnrun_level_tick(&lv, &gs);
296 break;
297 }
298 }
299 }
300
301 return gs.status;
302 }
This page took 0.057074 seconds and 3 git commands to generate.