Lebensscreen.
[hackover2013-badge-firmware.git] / badge / jumpnrun / jumpnrun.c
1 #include "jumpnrun.h"
2 #include "collision.h"
3 #include "levels.h"
4 #include "stats.h"
5
6 #include "../ui/display.h"
7 #include "../ui/event.h"
8 #include "../ui/sprite.h"
9 #include "../util/util.h"
10
11 #include <assert.h>
12 #include <math.h>
13 #include <stddef.h>
14 #include <stdio.h>
15
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);
22
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) };
25
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" }
31 /*
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" }
36 */
37 /*
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" }
42 */
43 /*
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" }
48 */
49 };
50
51 badge_sprite const *jumpnrun_hacker_symbol(void) {
52 return &anim_hacker[0];
53 }
54
55 static badge_sprite const anim_sickle[] = {
56 { 3, 3, (uint8_t const *) "\xab\x01" },
57 { 3, 3, (uint8_t const *) "\xee\x00" }
58 /*
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" }
63 */
64 };
65
66 enum {
67 JUMPNRUN_SHOT_EXTENT = 3,
68 JUMPNRUN_SHOT_TICKS_PER_FRAME = 36
69 };
70
71 static void jumpnrun_shot_spawn(jumpnrun_shot *shot, jumpnrun_game_state const *state) {
72 shot->tick = 0;
73 shot->inertia = shot_spawn_inertia;
74
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);
79 } else {
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) });
82 }
83
84 shot->old_box = shot->current_box;
85 shot->inertia = vec2d_add(shot->inertia, state->player.inertia);
86 }
87
88 static inline int imax(int x, int y) {
89 return x < y ? y : x;
90 }
91
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); }
96
97 int jumpnrun_level_assert_left_side(jumpnrun_game_state const *state) {
98 static int const lmargin = 20;
99 static int const rmargin = 50;
100
101 int pos_cur = fixed_point_cast_int(state->player.current_box.pos.x);
102 int pos_rel = pos_cur - state->left;
103
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);
108 }
109
110 return state->left;
111 }
112
113 static int jumpnrun_bsearch_tile(jumpnrun_level const *lv, jumpnrun_game_state const *state) {
114 int front = 0;
115 int len = lv->header.tile_count;
116
117 while(len > 0) {
118 int mid = front + len / 2;
119
120 if(fixed_point_lt(tile_right(&lv->tiles[mid]), FIXED_INT(state->left - JUMPNRUN_MAX_SPAWN_MARGIN))) {
121 front = mid + 1;
122 len -= len / 2 + 1;
123 } else {
124 len /= 2;
125 }
126 }
127
128 return front;
129 }
130
131 jumpnrun_tile_range jumpnrun_visible_tiles(jumpnrun_level const *lv,
132 jumpnrun_game_state const *state) {
133 jumpnrun_tile_range r;
134
135 r.first = jumpnrun_bsearch_tile(lv, state);
136
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;
139 ++r.last)
140 ;
141
142 return r;
143 }
144
145 void jumpnrun_passive_movement(vec2d *inertia)
146 {
147 *inertia = vec2d_add(*inertia, gravity);
148
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);
151 }
152
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;
164 break;
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;
169 break;
170 default:
171 if(state->player.touching_ground) {
172 state->player.inertia.x = fixed_point_mul(state->player.inertia.x, drag_factor);
173 } //else {
174 //state->player.inertia.x = FIXED_INT(0);
175 //}
176
177 break;
178 }
179
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;
185 } else {
186 state->player.jumpable_frames = 0;
187 }
188
189 jumpnrun_passive_movement(&state->player.inertia);
190
191 vec2d new_pos = vec2d_add(state->player.current_box.pos, state->player.inertia);
192
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);
196 }
197
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;
201
202 if(killed || fixed_point_gt(state->player.current_box.pos.y, FIXED_INT(BADGE_DISPLAY_HEIGHT))) {
203 state->status = JUMPNRUN_DEAD;
204 }
205 }
206
207 void jumpnrun_level_tick(jumpnrun_level *lv,
208 jumpnrun_game_state *state)
209 {
210 jumpnrun_tile_range tilerange = jumpnrun_visible_tiles(lv, state);
211 vec2d inertia_mod = state->player.inertia;
212
213 jumpnrun_apply_movement(lv, &tilerange, state, &inertia_mod);
214 state->left = jumpnrun_level_assert_left_side(state);
215
216 if(state->player.tick_minor == 0) {
217 badge_framebuffer fb;
218 badge_framebuffer_clear(&fb);
219
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,
225 0);
226 }
227
228 for(size_t item = 0; item < lv->header.item_count; ++item) {
229 jumpnrun_item *item_obj = &lv->items[item];
230
231 if(item_obj->flags & JUMPNRUN_ITEM_COLLECTED) {
232 continue;
233 }
234
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);
239
240 if(rectangle_intersect(&state->player.current_box, &item_rect)) {
241 item_obj->type->on_collect(item_obj, state, lv);
242 }
243
244 badge_framebuffer_blt(&fb,
245 screenpos,
246 fixed_point_cast_int(item_obj->pos.y),
247 &item_obj->type->sprite,
248 0);
249 }
250 }
251
252 for(size_t shot_ix = 0; shot_ix < JUMPNRUN_MAX_SHOTS; ++shot_ix) {
253 jumpnrun_shot *shot = &state->shots[shot_ix];
254
255 if(jumpnrun_shot_spawned(shot)) {
256 rectangle_move_rel(&shot->current_box, shot->inertia);
257 shot->inertia = vec2d_add(shot->inertia, gravity);
258
259 if(fixed_point_gt(rectangle_top(&shot->current_box), FIXED_INT(BADGE_DISPLAY_HEIGHT))) {
260 jumpnrun_shot_despawn(shot);
261 }
262
263 /* show every position twice, because LCD switching time. This makes the shots more
264 * visible on the nokia lcds.
265 */
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);
276
277 shot->old_box = shot->current_box;
278
279 ++shot->tick;
280 if(shot->tick == ARRAY_SIZE(anim_sickle) * JUMPNRUN_SHOT_TICKS_PER_FRAME) {
281 shot->tick = 0;
282 }
283 }
284 }
285
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);
289 }
290
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);
296
297 badge_framebuffer_flush(&fb);
298
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);
303 } else {
304 state->player.anim_frame = 0;
305 }
306 } else {
307 for(size_t shot_ix = 0; shot_ix < JUMPNRUN_MAX_SHOTS; ++shot_ix) {
308 jumpnrun_shot *shot = &state->shots[shot_ix];
309
310 if(jumpnrun_shot_spawned(shot)) {
311 rectangle_move_rel(&shot->current_box, shot->inertia);
312 shot->inertia = vec2d_add(shot->inertia, gravity);
313
314 if(fixed_point_gt(rectangle_top(&shot->current_box), FIXED_INT(BADGE_DISPLAY_HEIGHT))) {
315 jumpnrun_shot_despawn(shot);
316 }
317
318 ++shot->tick;
319 if(shot->tick == ARRAY_SIZE(anim_sickle) * JUMPNRUN_SHOT_TICKS_PER_FRAME) {
320 shot->tick = 0;
321 }
322 }
323 }
324
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);
328 }
329 }
330
331 state->player.inertia = inertia_mod;
332 ++state->player.tick_minor;
333 if(state->player.tick_minor == 3) {
334 state->player.tick_minor = 0;
335 }
336 }
337
338 uint8_t jumpnrun_play(char const *lvname) {
339 jumpnrun_level lv;
340
341 JUMPNRUN_LEVEL_LOAD(lv, lvname);
342
343 jumpnrun_game_state gs;
344 memset(&gs, 0, sizeof(gs));
345
346 for(gs.lives = 99; gs.status != JUMPNRUN_WON && gs.lives != 0; --gs.lives) {
347 jumpnrun_show_lives_screen(&gs);
348
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) {
352 ++i;
353 } else if(i > 25) {
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);
357
358 if(new_buttons != 0) break;
359 }
360 }
361
362 gs.status = JUMPNRUN_PLAYING;
363 gs.left = 0;
364 memset(&gs.player, 0, sizeof(gs.player));
365 gs.player.current_box = rectangle_new(lv.start_pos,
366 hacker_extents());
367
368 for(size_t i = 0; i < lv.header.enemy_count; ++i) {
369 lv.enemies[i].flags = 0;
370 }
371
372 while(gs.status == JUMPNRUN_PLAYING) {
373 badge_event_t ev = badge_event_wait();
374
375 switch(badge_event_type(ev)) {
376 case BADGE_EVENT_USER_INPUT:
377 {
378 uint8_t old_state = badge_event_old_input_state(ev);
379 uint8_t new_state = badge_event_new_input_state(ev);
380 uint8_t new_buttons = new_state & (old_state ^ new_state);
381
382 if((new_buttons & BADGE_EVENT_KEY_BTN_A) && gs.player.touching_ground) {
383 gs.player.jumpable_frames = 12;
384 }
385
386 if((new_buttons & BADGE_EVENT_KEY_BTN_B)) {
387 uint8_t i;
388 for(i = 0; i < JUMPNRUN_MAX_SHOTS && jumpnrun_shot_spawned(&gs.shots[i]); ++i)
389 ;
390
391 if(i < JUMPNRUN_MAX_SHOTS) {
392 jumpnrun_shot_spawn(gs.shots + i, &gs);
393 }
394 }
395
396 break;
397 }
398 case BADGE_EVENT_GAME_TICK:
399 {
400 jumpnrun_level_tick(&lv, &gs);
401 break;
402 }
403 }
404 }
405 }
406
407 return gs.status;
408 }
This page took 0.086151 seconds and 5 git commands to generate.