6 #include "../ui/display.h"
7 #include "../ui/event.h"
8 #include "../ui/sprite.h"
9 #include "../util/util.h"
16 static vec2d
const gravity
= { FIXED_POINT_I(0, 0), FIXED_POINT_I(0, 56) };
17 static vec2d
const move_max
= { FIXED_POINT_I(0, 600), FIXED_POINT_I(1, 300) };
18 static fixed_point
const accel_horiz
= FIXED_POINT_I(0, 50);
19 static fixed_point
const accel_vert
= FIXED_POINT_I(0, 167);
20 static fixed_point
const drag_factor
= FIXED_POINT_I(0, 854);
21 static fixed_point
const speed_jump_x
= FIXED_POINT_I(0, 600);
23 vec2d
hacker_extents(void) { return (vec2d
) { FIXED_INT_I(5), FIXED_INT_I(8) }; }
24 static vec2d
const shot_spawn_inertia
= { FIXED_POINT_I(0, 800), FIXED_POINT_I(0, -800) };
26 static badge_sprite
const anim_hacker
[] = {
27 { 5, 8, (uint8_t const *) "\x1c\xff\xfd\x04\x04" },
28 { 5, 8, (uint8_t const *) "\x1c\xff\x3d\xc4\x04" },
29 { 5, 8, (uint8_t const *) "\xdc\x3f\x1d\x24\xc4" },
30 { 5, 8, (uint8_t const *) "\x1c\xff\x3d\xc4\x04" }
32 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x31" },
33 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x52" },
34 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x94\x8c" },
35 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x52" }
38 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\xc3\x30" },
39 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x43\x51" },
40 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x35\x82" },
41 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x43\x51" }
44 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
45 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
46 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
47 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" }
51 badge_sprite
const *jumpnrun_hacker_symbol(void) {
52 return &anim_hacker
[0];
55 static badge_sprite
const anim_sickle
[] = {
56 { 3, 3, (uint8_t const *) "\xab\x01" },
57 { 3, 3, (uint8_t const *) "\xee\x00" }
59 { 3, 3, (uint8_t const *) "\x8a\x01" },
60 { 3, 3, (uint8_t const *) "\x6a" },
61 { 3, 3, (uint8_t const *) "\xa3" },
62 { 3, 3, (uint8_t const *) "\xac" }
67 JUMPNRUN_SHOT_EXTENT
= 3,
68 JUMPNRUN_SHOT_TICKS_PER_FRAME
= 36
71 static void jumpnrun_shot_spawn(jumpnrun_shot
*shot
, jumpnrun_game_state
const *state
) {
73 shot
->inertia
= shot_spawn_inertia
;
75 if(state
->player
.anim_direction
== BADGE_BLT_MIRRORED
) {
76 shot
->current_box
= rectangle_new((vec2d
) { fixed_point_sub(rectangle_left(&state
->player
.current_box
), FIXED_INT(JUMPNRUN_SHOT_EXTENT
)), rectangle_top(&state
->player
.current_box
) },
77 (vec2d
) { FIXED_INT(JUMPNRUN_SHOT_EXTENT
), FIXED_INT(JUMPNRUN_SHOT_EXTENT
) });
78 shot
->inertia
.x
= fixed_point_neg(shot
->inertia
.x
);
80 shot
->current_box
= rectangle_new((vec2d
) { rectangle_right(&state
->player
.current_box
), rectangle_top(&state
->player
.current_box
) },
81 (vec2d
) { FIXED_INT(JUMPNRUN_SHOT_EXTENT
), FIXED_INT(JUMPNRUN_SHOT_EXTENT
) });
84 shot
->old_box
= shot
->current_box
;
85 shot
->inertia
= vec2d_add(shot
->inertia
, state
->player
.inertia
);
88 static inline int imax(int x
, int y
) {
92 static inline fixed_point
hacker_left (vec2d
const *pos
, jumpnrun_game_state
const *state
) { (void) state
; return pos
->x
; }
93 static inline fixed_point
hacker_top (vec2d
const *pos
, jumpnrun_game_state
const *state
) { (void) state
; return pos
->y
; }
94 static inline fixed_point
hacker_right (vec2d
const *pos
, jumpnrun_game_state
const *state
) { return fixed_point_add(hacker_left(pos
, state
), hacker_extents().x
); }
95 static inline fixed_point
hacker_bottom(vec2d
const *pos
, jumpnrun_game_state
const *state
) { return fixed_point_add(hacker_top (pos
, state
), hacker_extents().y
); }
97 int jumpnrun_level_assert_left_side(jumpnrun_game_state
const *state
) {
98 static int const lmargin
= 20;
99 static int const rmargin
= 50;
101 int pos_cur
= fixed_point_cast_int(state
->player
.current_box
.pos
.x
);
102 int pos_rel
= pos_cur
- state
->left
;
104 if(pos_rel
< lmargin
) {
105 return imax(0, pos_cur
- lmargin
);
106 } else if(pos_rel
> BADGE_DISPLAY_WIDTH
- rmargin
) {
107 return pos_cur
- (BADGE_DISPLAY_WIDTH
- rmargin
);
113 static int jumpnrun_bsearch_tile(jumpnrun_level
const *lv
, jumpnrun_game_state
const *state
) {
115 int len
= lv
->header
.tile_count
;
118 int mid
= front
+ len
/ 2;
120 if(fixed_point_lt(tile_right(&lv
->tiles
[mid
]), FIXED_INT(state
->left
- JUMPNRUN_MAX_SPAWN_MARGIN
))) {
131 jumpnrun_tile_range
jumpnrun_visible_tiles(jumpnrun_level
const *lv
,
132 jumpnrun_game_state
const *state
) {
133 jumpnrun_tile_range r
;
135 r
.first
= jumpnrun_bsearch_tile(lv
, state
);
137 for(r
.last
= r
.first
;
138 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
;
145 void jumpnrun_passive_movement(vec2d
*inertia
)
147 *inertia
= vec2d_add(*inertia
, gravity
);
149 inertia
->x
= fixed_point_min(fixed_point_max(fixed_point_neg(move_max
.x
), inertia
->x
), move_max
.x
);
150 inertia
->y
= fixed_point_min(fixed_point_max(fixed_point_neg(move_max
.y
), inertia
->y
), move_max
.y
);
153 static void jumpnrun_apply_movement(jumpnrun_level
const *lv
,
154 jumpnrun_tile_range
const *tilerange
,
155 jumpnrun_game_state
*state
,
156 vec2d
*inertia_mod
) {
157 switch(badge_event_current_input_state() &
158 (BADGE_EVENT_KEY_LEFT
|
159 BADGE_EVENT_KEY_RIGHT
)) {
160 case BADGE_EVENT_KEY_LEFT
:
161 // state->player.inertia.x = state->player.touching_ground ? fixed_point_sub(state->player.inertia.x, accel_horiz) : fixed_point_neg(speed_jump_x);
162 state
->player
.inertia
.x
= fixed_point_sub(state
->player
.inertia
.x
, accel_horiz
);
163 state
->player
.anim_direction
= BADGE_BLT_MIRRORED
;
165 case BADGE_EVENT_KEY_RIGHT
:
166 // state->player.inertia.x = state->player.touching_ground ? fixed_point_add(state->player.inertia.x, accel_horiz) : speed_jump_x;
167 state
->player
.inertia
.x
= fixed_point_add(state
->player
.inertia
.x
, accel_horiz
);
168 state
->player
.anim_direction
= 0;
171 if(state
->player
.touching_ground
) {
172 state
->player
.inertia
.x
= fixed_point_mul(state
->player
.inertia
.x
, drag_factor
);
174 //state->player.inertia.x = FIXED_INT(0);
180 if(state
->player
.jumpable_frames
== 0) {
181 // intentionally left blank.
182 } else if(badge_event_current_input_state() & BADGE_EVENT_KEY_BTN_A
) {
183 state
->player
.inertia
.y
= fixed_point_sub(state
->player
.inertia
.y
, accel_vert
);
184 --state
->player
.jumpable_frames
;
186 state
->player
.jumpable_frames
= 0;
189 jumpnrun_passive_movement(&state
->player
.inertia
);
191 vec2d new_pos
= vec2d_add(state
->player
.current_box
.pos
, state
->player
.inertia
);
193 if(fixed_point_lt(new_pos
.x
, FIXED_INT(state
->left
))) {
194 new_pos
.x
= FIXED_INT(state
->left
);
195 state
->player
.inertia
.x
= FIXED_INT(0);
198 *inertia_mod
= state
->player
.inertia
;
199 bool killed
= collisions_tiles_displace(&new_pos
, &state
->player
, lv
, tilerange
, inertia_mod
);
200 state
->player
.inertia
= *inertia_mod
;
202 if(killed
|| fixed_point_gt(state
->player
.current_box
.pos
.y
, FIXED_INT(BADGE_DISPLAY_HEIGHT
))) {
203 state
->status
= JUMPNRUN_DEAD
;
207 void jumpnrun_level_tick(jumpnrun_level
*lv
,
208 jumpnrun_game_state
*state
)
210 jumpnrun_tile_range tilerange
= jumpnrun_visible_tiles(lv
, state
);
211 vec2d inertia_mod
= state
->player
.inertia
;
213 jumpnrun_apply_movement(lv
, &tilerange
, state
, &inertia_mod
);
214 state
->left
= jumpnrun_level_assert_left_side(state
);
216 if(state
->player
.tick_minor
== 0) {
217 badge_framebuffer fb
;
218 badge_framebuffer_clear(&fb
);
220 for(size_t tile
= tilerange
.first
; tile
< tilerange
.last
; ++tile
) {
221 badge_framebuffer_blt(&fb
,
222 fixed_point_cast_int(tile_left(&lv
->tiles
[tile
])) - state
->left
,
223 fixed_point_cast_int(tile_top (&lv
->tiles
[tile
])),
224 &tile_type(&lv
->tiles
[tile
])->sprite
,
228 for(size_t item
= 0; item
< lv
->header
.item_count
; ++item
) {
229 jumpnrun_item
*item_obj
= &lv
->items
[item
];
231 if(item_obj
->flags
& JUMPNRUN_ITEM_COLLECTED
) {
235 int screenpos
= fixed_point_cast_int(item_obj
->pos
.x
) - state
->left
;
236 if(screenpos
> -item_obj
->type
->sprite
.width
&&
237 screenpos
< BADGE_DISPLAY_WIDTH
) {
238 rectangle item_rect
= rect_from_item(item_obj
);
240 if(rectangle_intersect(&state
->player
.current_box
, &item_rect
)) {
241 item_obj
->type
->on_collect(item_obj
, state
, lv
);
244 badge_framebuffer_blt(&fb
,
246 fixed_point_cast_int(item_obj
->pos
.y
),
247 &item_obj
->type
->sprite
,
252 for(size_t shot_ix
= 0; shot_ix
< JUMPNRUN_MAX_SHOTS
; ++shot_ix
) {
253 jumpnrun_shot
*shot
= &state
->shots
[shot_ix
];
255 if(jumpnrun_shot_spawned(shot
)) {
256 rectangle_move_rel(&shot
->current_box
, shot
->inertia
);
257 shot
->inertia
= vec2d_add(shot
->inertia
, gravity
);
259 if(fixed_point_gt(rectangle_top(&shot
->current_box
), FIXED_INT(BADGE_DISPLAY_HEIGHT
))) {
260 jumpnrun_shot_despawn(shot
);
263 /* show every position twice, because LCD switching time. This makes the shots more
264 * visible on the nokia lcds.
266 badge_framebuffer_blt(&fb
,
267 fixed_point_cast_int(shot
->old_box
.pos
.x
) - state
->left
,
268 fixed_point_cast_int(shot
->old_box
.pos
.y
),
269 &anim_sickle
[shot
->tick
/ JUMPNRUN_SHOT_TICKS_PER_FRAME
],
270 fixed_point_lt(shot
->inertia
.x
, FIXED_INT(0)) ? BADGE_BLT_MIRRORED
: 0);
271 badge_framebuffer_blt(&fb
,
272 fixed_point_cast_int(shot
->current_box
.pos
.x
) - state
->left
,
273 fixed_point_cast_int(shot
->current_box
.pos
.y
),
274 &anim_sickle
[shot
->tick
/ JUMPNRUN_SHOT_TICKS_PER_FRAME
],
275 fixed_point_lt(shot
->inertia
.x
, FIXED_INT(0)) ? BADGE_BLT_MIRRORED
: 0);
277 shot
->old_box
= shot
->current_box
;
280 if(shot
->tick
== ARRAY_SIZE(anim_sickle
) * JUMPNRUN_SHOT_TICKS_PER_FRAME
) {
286 for(size_t enemy_ix
= 0; enemy_ix
< lv
->header
.enemy_count
; ++enemy_ix
) {
287 jumpnrun_enemy
*enemy
= &lv
->enemies
[enemy_ix
];
288 jumpnrun_process_enemy(enemy
, &fb
, state
, lv
, &tilerange
, &inertia_mod
);
291 badge_framebuffer_blt(&fb
,
292 fixed_point_cast_int(state
->player
.current_box
.pos
.x
) - state
->left
,
293 fixed_point_cast_int(state
->player
.current_box
.pos
.y
),
294 &anim_hacker
[state
->player
.anim_frame
],
295 state
->player
.anim_direction
);
297 badge_framebuffer_flush(&fb
);
299 if(!state
->player
.touching_ground
) {
300 state
->player
.anim_frame
= 2;
301 } else if(fixed_point_gt(fixed_point_abs(state
->player
.inertia
.x
), FIXED_POINT(0, 200))) {
302 state
->player
.anim_frame
= (state
->player
.anim_frame
+ 1) % ARRAY_SIZE(anim_hacker
);
304 state
->player
.anim_frame
= 0;
307 for(size_t shot_ix
= 0; shot_ix
< JUMPNRUN_MAX_SHOTS
; ++shot_ix
) {
308 jumpnrun_shot
*shot
= &state
->shots
[shot_ix
];
310 if(jumpnrun_shot_spawned(shot
)) {
311 rectangle_move_rel(&shot
->current_box
, shot
->inertia
);
312 shot
->inertia
= vec2d_add(shot
->inertia
, gravity
);
314 if(fixed_point_gt(rectangle_top(&shot
->current_box
), FIXED_INT(BADGE_DISPLAY_HEIGHT
))) {
315 jumpnrun_shot_despawn(shot
);
319 if(shot
->tick
== ARRAY_SIZE(anim_sickle
) * JUMPNRUN_SHOT_TICKS_PER_FRAME
) {
325 for(size_t enemy_ix
= 0; enemy_ix
< lv
->header
.enemy_count
; ++enemy_ix
) {
326 jumpnrun_enemy
*enemy
= &lv
->enemies
[enemy_ix
];
327 jumpnrun_process_enemy(enemy
, NULL
, state
, lv
, &tilerange
, &inertia_mod
);
331 state
->player
.inertia
= inertia_mod
;
332 ++state
->player
.tick_minor
;
333 if(state
->player
.tick_minor
== 3) {
334 state
->player
.tick_minor
= 0;
338 uint8_t jumpnrun_play(char const *lvname
) {
341 JUMPNRUN_LEVEL_LOAD(lv
, lvname
);
343 jumpnrun_game_state gs
;
344 memset(&gs
, 0, sizeof(gs
));
346 for(gs
.lives
= 99; gs
.status
!= JUMPNRUN_WON
&& gs
.lives
!= 0; --gs
.lives
) {
347 jumpnrun_show_lives_screen(&gs
);
349 for(uint8_t i
= 0; i
< 75; ) {
350 badge_event_t ev
= badge_event_wait();
351 if(badge_event_type(ev
) == BADGE_EVENT_GAME_TICK
) {
354 uint8_t old_state
= badge_event_old_input_state(ev
);
355 uint8_t new_state
= badge_event_new_input_state(ev
);
356 uint8_t new_buttons
= new_state
& (old_state
^ new_state
);
358 if(new_buttons
!= 0) break;
362 gs
.status
= JUMPNRUN_PLAYING
;
364 memset(&gs
.player
, 0, sizeof(gs
.player
));
365 memset(&gs
.shots
, 0, sizeof(gs
.shots
));
366 gs
.player
.current_box
= rectangle_new(lv
.start_pos
,
369 for(size_t i
= 0; i
< lv
.header
.enemy_count
; ++i
) {
370 lv
.enemies
[i
].flags
= 0;
373 while(gs
.status
== JUMPNRUN_PLAYING
) {
374 badge_event_t ev
= badge_event_wait();
376 switch(badge_event_type(ev
)) {
377 case BADGE_EVENT_USER_INPUT
:
379 uint8_t old_state
= badge_event_old_input_state(ev
);
380 uint8_t new_state
= badge_event_new_input_state(ev
);
381 uint8_t new_buttons
= new_state
& (old_state
^ new_state
);
383 if((new_buttons
& BADGE_EVENT_KEY_BTN_A
) && gs
.player
.touching_ground
) {
384 gs
.player
.jumpable_frames
= 12;
387 if((new_buttons
& BADGE_EVENT_KEY_BTN_B
)) {
389 for(i
= 0; i
< JUMPNRUN_MAX_SHOTS
&& jumpnrun_shot_spawned(&gs
.shots
[i
]); ++i
)
392 if(i
< JUMPNRUN_MAX_SHOTS
) {
393 jumpnrun_shot_spawn(gs
.shots
+ i
, &gs
);
399 case BADGE_EVENT_GAME_TICK
:
401 jumpnrun_level_tick(&lv
, &gs
);