4 #include <badge/ui/display.h>
5 #include <badge/ui/event.h>
6 #include <badge/ui/sprite.h>
13 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof*(arr))
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);
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" }
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" }
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" }
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" }
49 static inline int imax(int x
, int y
) {
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)); }
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) } };
63 rectangle
hacker_rect_current(jumpnrun_game_state
const *state
) {
64 return hacker_rect(&state
->current_pos
, state
);
67 int jumpnrun_level_assert_left_side(jumpnrun_game_state
const *state
) {
68 static int const lmargin
= 20;
69 static int const rmargin
= 50;
71 int pos_cur
= fixed_point_cast_int(state
->current_pos
.x
);
72 int pos_rel
= pos_cur
- state
->left
;
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
);
83 static int jumpnrun_bsearch_tile(jumpnrun_level
const *lv
, jumpnrun_game_state
const *state
) {
85 int len
= lv
->header
.tile_count
;
88 int mid
= front
+ len
/ 2;
90 if(fixed_point_lt(tile_right(&lv
->tiles
[mid
]), FIXED_POINT(state
->left
- JUMPNRUN_MAX_SPAWN_MARGIN
, 0))) {
101 jumpnrun_tile_range
jumpnrun_visible_tiles(jumpnrun_level
const *lv
,
102 jumpnrun_game_state
const *state
) {
103 jumpnrun_tile_range r
;
105 r
.first
= jumpnrun_bsearch_tile(lv
, state
);
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
;
115 void jumpnrun_passive_movement(vec2d
*inertia
)
117 *inertia
= vec2d_add(*inertia
, gravity
);
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
);
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
;
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;
140 if(state
->touching_ground
) {
141 state
->inertia
.x
= fixed_point_mul(state
->inertia
.x
, drag_factor
);
143 //state->inertia.x = FIXED_POINT(0, 0);
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
;
156 state
->jumpable_frames
= 0;
159 jumpnrun_passive_movement(&state
->inertia
);
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
);
164 vec2d new_pos
= vec2d_add(state
->current_pos
, state
->inertia
);
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);
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
);
174 state
->current_pos
= new_pos
;
176 if(fixed_point_gt(state
->current_pos
.y
, FIXED_POINT(BADGE_DISPLAY_HEIGHT
, 0))) {
177 state
->status
= JUMPNRUN_DEAD
;
181 void jumpnrun_level_tick(jumpnrun_level
*lv
,
182 jumpnrun_game_state
*state
)
184 jumpnrun_tile_range tilerange
= jumpnrun_visible_tiles(lv
, state
);
185 jumpnrun_apply_movement(lv
, &tilerange
, state
);
187 state
->left
= jumpnrun_level_assert_left_side(state
);
189 if(state
->tick_minor
== 0) {
190 badge_framebuffer fb
;
191 badge_framebuffer_clear(&fb
);
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
,
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
]);
208 if(rectangle_intersect(&hack_rect
, &item_rect
)) {
209 lv
->items
[item
].type
->on_collect(state
);
212 badge_framebuffer_blt(&fb
,
214 fixed_point_cast_int(lv
->items
[item
].pos
.y
),
215 &lv
->items
[item
].type
->sprite
,
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
);
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
);
231 badge_framebuffer_flush(&fb
);
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
);
238 state
->anim_frame
= 0;
242 state
->tick_minor
= (state
->tick_minor
+ 1) % 2;
245 uint8_t jumpnrun_play(char const *lvname
) {
248 memset(&lv
, 0, sizeof(lv
));
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
253 // Still, better than opening the whole dynamic memory can of worms.
257 if(FR_OK
!= f_open(&fd
, lvname
, FA_OPEN_EXISTING
| FA_READ
)) { return JUMPNRUN_ERROR
; }
259 if(0 != jumpnrun_load_level_header_from_file(&lv
, &fd
)) {
261 return JUMPNRUN_ERROR
;
264 JUMPNRUN_LEVEL_MAKE_SPACE(lv
);
265 err
= jumpnrun_load_level_from_file(&lv
, &fd
);
269 return JUMPNRUN_ERROR
;
272 jumpnrun_game_state gs
;
273 memset(&gs
, 0, sizeof(gs
));
275 gs
.current_pos
= lv
.start_pos
;
277 while(gs
.status
== JUMPNRUN_PLAYING
) {
278 badge_event_t ev
= badge_event_wait();
280 switch(badge_event_type(ev
)) {
281 case BADGE_EVENT_USER_INPUT
:
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
);
287 if((new_buttons
& BADGE_EVENT_KEY_BTN_B
) && gs
.touching_ground
) {
288 gs
.jumpable_frames
= 8;
293 case BADGE_EVENT_GAME_TICK
:
295 jumpnrun_level_tick(&lv
, &gs
);