Sprites in Repo geworfen (damit nichts verlorengeht)
[hackover2013-badge-firmware.git] / badge / jumpnrun / jumpnrun.c
1 #include "jumpnrun.h"
2 #include "collision.h"
3 #include "levels.h"
4
5 #include "../ui/display.h"
6 #include "../ui/event.h"
7 #include "../ui/sprite.h"
8 #include "../util/util.h"
9
10 #include <assert.h>
11 #include <math.h>
12 #include <stddef.h>
13 #include <stdio.h>
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(0, 600), FIXED_POINT_I(1, 300) };
17 static fixed_point const accel_horiz = FIXED_POINT_I(0, 50);
18 static fixed_point const accel_vert = FIXED_POINT_I(0, 167);
19 static fixed_point const drag_factor = FIXED_POINT_I(0, 854);
20 static fixed_point const speed_jump_x = FIXED_POINT_I(0, 600);
21
22 static vec2d const hacker_extent = { FIXED_INT_I(5), FIXED_INT_I(8) };
23 static vec2d const shot_spawn_inertia = { FIXED_POINT_I(0, 800), FIXED_POINT_I(0, -800) };
24
25 static badge_sprite const anim_hacker[] = {
26 { 5, 8, (uint8_t const *) "\x1c\xff\xfd\x04\x04" },
27 { 5, 8, (uint8_t const *) "\x1c\xff\x3d\xc4\x04" },
28 { 5, 8, (uint8_t const *) "\xdc\x3f\x1d\x24\xc4" },
29 { 5, 8, (uint8_t const *) "\x1c\xff\x3d\xc4\x04" }
30 /*
31 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x31" },
32 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x52" },
33 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x94\x8c" },
34 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x52" }
35 */
36 /*
37 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\xc3\x30" },
38 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x43\x51" },
39 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x35\x82" },
40 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x43\x51" }
41 */
42 /*
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 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
46 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" }
47 */
48 };
49
50 static badge_sprite const anim_sickle[] = {
51 { 3, 3, (uint8_t const *) "\xab\x01" },
52 { 3, 3, (uint8_t const *) "\xee\x00" }
53 /*
54 { 3, 3, (uint8_t const *) "\x8a\x01" },
55 { 3, 3, (uint8_t const *) "\x6a" },
56 { 3, 3, (uint8_t const *) "\xa3" },
57 { 3, 3, (uint8_t const *) "\xac" }
58 */
59 };
60
61 enum {
62 JUMPNRUN_SHOT_EXTENT = 3,
63 JUMPNRUN_SHOT_TICKS_PER_FRAME = 36
64 };
65
66 static void jumpnrun_shot_spawn(jumpnrun_shot *shot, jumpnrun_game_state const *state) {
67 shot->tick = 0;
68 shot->inertia = shot_spawn_inertia;
69
70 if(state->player.anim_direction == BADGE_BLT_MIRRORED) {
71 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) },
72 (vec2d) { FIXED_INT(JUMPNRUN_SHOT_EXTENT), FIXED_INT(JUMPNRUN_SHOT_EXTENT) });
73 shot->inertia.x = fixed_point_neg(shot->inertia.x);
74 } else {
75 shot->current_box = rectangle_new((vec2d) { rectangle_right(&state->player.current_box), rectangle_top(&state->player.current_box) },
76 (vec2d) { FIXED_INT(JUMPNRUN_SHOT_EXTENT), FIXED_INT(JUMPNRUN_SHOT_EXTENT) });
77 }
78
79 shot->old_box = shot->current_box;
80 shot->inertia = vec2d_add(shot->inertia, state->player.inertia);
81 }
82
83 static inline int imax(int x, int y) {
84 return x < y ? y : x;
85 }
86
87 static inline fixed_point hacker_left (vec2d const *pos, jumpnrun_game_state const *state) { (void) state; return pos->x; }
88 static inline fixed_point hacker_top (vec2d const *pos, jumpnrun_game_state const *state) { (void) state; return pos->y; }
89 static inline fixed_point hacker_right (vec2d const *pos, jumpnrun_game_state const *state) { return fixed_point_add(hacker_left(pos, state), hacker_extent.x); }
90 static inline fixed_point hacker_bottom(vec2d const *pos, jumpnrun_game_state const *state) { return fixed_point_add(hacker_top (pos, state), hacker_extent.y); }
91
92 int jumpnrun_level_assert_left_side(jumpnrun_game_state const *state) {
93 static int const lmargin = 20;
94 static int const rmargin = 50;
95
96 int pos_cur = fixed_point_cast_int(state->player.current_box.pos.x);
97 int pos_rel = pos_cur - state->left;
98
99 if(pos_rel < lmargin) {
100 return imax(0, pos_cur - lmargin);
101 } else if(pos_rel > BADGE_DISPLAY_WIDTH - rmargin) {
102 return pos_cur - (BADGE_DISPLAY_WIDTH - rmargin);
103 }
104
105 return state->left;
106 }
107
108 static int jumpnrun_bsearch_tile(jumpnrun_level const *lv, jumpnrun_game_state const *state) {
109 int front = 0;
110 int len = lv->header.tile_count;
111
112 while(len > 0) {
113 int mid = front + len / 2;
114
115 if(fixed_point_lt(tile_right(&lv->tiles[mid]), FIXED_INT(state->left - JUMPNRUN_MAX_SPAWN_MARGIN))) {
116 front = mid + 1;
117 len -= len / 2 + 1;
118 } else {
119 len /= 2;
120 }
121 }
122
123 return front;
124 }
125
126 jumpnrun_tile_range jumpnrun_visible_tiles(jumpnrun_level const *lv,
127 jumpnrun_game_state const *state) {
128 jumpnrun_tile_range r;
129
130 r.first = jumpnrun_bsearch_tile(lv, state);
131
132 for(r.last = r.first;
133 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;
134 ++r.last)
135 ;
136
137 return r;
138 }
139
140 void jumpnrun_passive_movement(vec2d *inertia)
141 {
142 *inertia = vec2d_add(*inertia, gravity);
143
144 inertia->x = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.x), inertia->x), move_max.x);
145 inertia->y = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.y), inertia->y), move_max.y);
146 }
147
148 static void jumpnrun_apply_movement(jumpnrun_level const *lv,
149 jumpnrun_tile_range const *tilerange,
150 jumpnrun_game_state *state,
151 vec2d *inertia_mod) {
152 switch(badge_event_current_input_state() &
153 (BADGE_EVENT_KEY_LEFT |
154 BADGE_EVENT_KEY_RIGHT)) {
155 case BADGE_EVENT_KEY_LEFT:
156 // state->player.inertia.x = state->player.touching_ground ? fixed_point_sub(state->player.inertia.x, accel_horiz) : fixed_point_neg(speed_jump_x);
157 state->player.inertia.x = fixed_point_sub(state->player.inertia.x, accel_horiz);
158 state->player.anim_direction = BADGE_BLT_MIRRORED;
159 break;
160 case BADGE_EVENT_KEY_RIGHT:
161 // state->player.inertia.x = state->player.touching_ground ? fixed_point_add(state->player.inertia.x, accel_horiz) : speed_jump_x;
162 state->player.inertia.x = fixed_point_add(state->player.inertia.x, accel_horiz);
163 state->player.anim_direction = 0;
164 break;
165 default:
166 if(state->player.touching_ground) {
167 state->player.inertia.x = fixed_point_mul(state->player.inertia.x, drag_factor);
168 } //else {
169 //state->player.inertia.x = FIXED_INT(0);
170 //}
171
172 break;
173 }
174
175 if(state->player.jumpable_frames == 0) {
176 // intentionally left blank.
177 } else if(badge_event_current_input_state() & BADGE_EVENT_KEY_BTN_A) {
178 state->player.inertia.y = fixed_point_sub(state->player.inertia.y, accel_vert);
179 --state->player.jumpable_frames;
180 } else {
181 state->player.jumpable_frames = 0;
182 }
183
184 jumpnrun_passive_movement(&state->player.inertia);
185
186 vec2d new_pos = vec2d_add(state->player.current_box.pos, state->player.inertia);
187
188 if(fixed_point_lt(new_pos.x, FIXED_INT(state->left))) {
189 new_pos.x = FIXED_INT(state->left);
190 state->player.inertia.x = FIXED_INT(0);
191 }
192
193 *inertia_mod = state->player.inertia;
194 bool killed = collisions_tiles_displace(&new_pos, &state->player, lv, tilerange, inertia_mod);
195 state->player.inertia = *inertia_mod;
196
197 if(killed || fixed_point_gt(state->player.current_box.pos.y, FIXED_INT(BADGE_DISPLAY_HEIGHT))) {
198 state->status = JUMPNRUN_DEAD;
199 }
200 }
201
202 void jumpnrun_level_tick(jumpnrun_level *lv,
203 jumpnrun_game_state *state)
204 {
205 jumpnrun_tile_range tilerange = jumpnrun_visible_tiles(lv, state);
206 vec2d inertia_mod = state->player.inertia;
207
208 jumpnrun_apply_movement(lv, &tilerange, state, &inertia_mod);
209 state->left = jumpnrun_level_assert_left_side(state);
210
211 if(state->player.tick_minor == 0) {
212 badge_framebuffer fb;
213 badge_framebuffer_clear(&fb);
214
215 for(size_t tile = tilerange.first; tile < tilerange.last; ++tile) {
216 badge_framebuffer_blt(&fb,
217 fixed_point_cast_int(tile_left(&lv->tiles[tile])) - state->left,
218 fixed_point_cast_int(tile_top (&lv->tiles[tile])),
219 &tile_type(&lv->tiles[tile])->sprite,
220 0);
221 }
222
223 for(size_t item = 0; item < lv->header.item_count; ++item) {
224 int screenpos = fixed_point_cast_int(lv->items[item].pos.x) - state->left;
225 if(screenpos > -lv->items[item].type->sprite.width &&
226 screenpos < BADGE_DISPLAY_WIDTH) {
227 rectangle item_rect = rect_from_item(&lv->items[item]);
228
229 if(rectangle_intersect(&state->player.current_box, &item_rect)) {
230 lv->items[item].type->on_collect(state);
231 }
232
233 badge_framebuffer_blt(&fb,
234 screenpos,
235 fixed_point_cast_int(lv->items[item].pos.y),
236 &lv->items[item].type->sprite,
237 0);
238 }
239 }
240
241 for(size_t shot_ix = 0; shot_ix < JUMPNRUN_MAX_SHOTS; ++shot_ix) {
242 jumpnrun_shot *shot = &state->shots[shot_ix];
243
244 if(jumpnrun_shot_spawned(shot)) {
245 rectangle_move_rel(&shot->current_box, shot->inertia);
246 shot->inertia = vec2d_add(shot->inertia, gravity);
247
248 if(fixed_point_gt(rectangle_top(&shot->current_box), FIXED_INT(BADGE_DISPLAY_HEIGHT))) {
249 jumpnrun_shot_despawn(shot);
250 }
251
252 /* show every position twice, because LCD switching time. This makes the shots more
253 * visible on the nokia lcds.
254 */
255 badge_framebuffer_blt(&fb,
256 fixed_point_cast_int(shot->old_box.pos.x) - state->left,
257 fixed_point_cast_int(shot->old_box.pos.y),
258 &anim_sickle[shot->tick / JUMPNRUN_SHOT_TICKS_PER_FRAME],
259 fixed_point_lt(shot->inertia.x, FIXED_INT(0)) ? BADGE_BLT_MIRRORED : 0);
260 badge_framebuffer_blt(&fb,
261 fixed_point_cast_int(shot->current_box.pos.x) - state->left,
262 fixed_point_cast_int(shot->current_box.pos.y),
263 &anim_sickle[shot->tick / JUMPNRUN_SHOT_TICKS_PER_FRAME],
264 fixed_point_lt(shot->inertia.x, FIXED_INT(0)) ? BADGE_BLT_MIRRORED : 0);
265
266 shot->old_box = shot->current_box;
267
268 ++shot->tick;
269 if(shot->tick == ARRAY_SIZE(anim_sickle) * JUMPNRUN_SHOT_TICKS_PER_FRAME) {
270 shot->tick = 0;
271 }
272 }
273 }
274
275 for(size_t enemy_ix = 0; enemy_ix < lv->header.enemy_count; ++enemy_ix) {
276 jumpnrun_enemy *enemy = &lv->enemies[enemy_ix];
277 jumpnrun_process_enemy(enemy, &fb, state, lv, &tilerange, &inertia_mod);
278 }
279
280 badge_framebuffer_blt(&fb,
281 fixed_point_cast_int(state->player.current_box.pos.x) - state->left,
282 fixed_point_cast_int(state->player.current_box.pos.y),
283 &anim_hacker[state->player.anim_frame],
284 state->player.anim_direction);
285
286 badge_framebuffer_flush(&fb);
287
288 if(!state->player.touching_ground) {
289 state->player.anim_frame = 2;
290 } else if(fixed_point_gt(fixed_point_abs(state->player.inertia.x), FIXED_POINT(0, 200))) {
291 state->player.anim_frame = (state->player.anim_frame + 1) % ARRAY_SIZE(anim_hacker);
292 } else {
293 state->player.anim_frame = 0;
294 }
295 } else {
296 for(size_t shot_ix = 0; shot_ix < JUMPNRUN_MAX_SHOTS; ++shot_ix) {
297 jumpnrun_shot *shot = &state->shots[shot_ix];
298
299 if(jumpnrun_shot_spawned(shot)) {
300 rectangle_move_rel(&shot->current_box, shot->inertia);
301 shot->inertia = vec2d_add(shot->inertia, gravity);
302
303 if(fixed_point_gt(rectangle_top(&shot->current_box), FIXED_INT(BADGE_DISPLAY_HEIGHT))) {
304 jumpnrun_shot_despawn(shot);
305 }
306
307 ++shot->tick;
308 if(shot->tick == ARRAY_SIZE(anim_sickle) * JUMPNRUN_SHOT_TICKS_PER_FRAME) {
309 shot->tick = 0;
310 }
311 }
312 }
313
314 for(size_t enemy_ix = 0; enemy_ix < lv->header.enemy_count; ++enemy_ix) {
315 jumpnrun_enemy *enemy = &lv->enemies[enemy_ix];
316 jumpnrun_process_enemy(enemy, NULL, state, lv, &tilerange, &inertia_mod);
317 }
318 }
319
320 state->player.inertia = inertia_mod;
321 ++state->player.tick_minor;
322 if(state->player.tick_minor == 3) {
323 state->player.tick_minor = 0;
324 }
325 }
326
327 uint8_t jumpnrun_play(char const *lvname) {
328 jumpnrun_level lv;
329
330 JUMPNRUN_LEVEL_LOAD(lv, lvname);
331
332 jumpnrun_game_state gs;
333 memset(&gs, 0, sizeof(gs));
334
335 gs.player.current_box = rectangle_new(lv.start_pos, hacker_extent);
336
337 while(gs.status == JUMPNRUN_PLAYING) {
338 badge_event_t ev = badge_event_wait();
339
340 switch(badge_event_type(ev)) {
341 case BADGE_EVENT_USER_INPUT:
342 {
343 uint8_t old_state = badge_event_old_input_state(ev);
344 uint8_t new_state = badge_event_new_input_state(ev);
345 uint8_t new_buttons = new_state & (old_state ^ new_state);
346
347 if((new_buttons & BADGE_EVENT_KEY_BTN_A) && gs.player.touching_ground) {
348 gs.player.jumpable_frames = 12;
349 }
350
351 if((new_buttons & BADGE_EVENT_KEY_BTN_B)) {
352 uint8_t i;
353 for(i = 0; i < JUMPNRUN_MAX_SHOTS && jumpnrun_shot_spawned(&gs.shots[i]); ++i)
354 ;
355
356 if(i < JUMPNRUN_MAX_SHOTS) {
357 jumpnrun_shot_spawn(gs.shots + i, &gs);
358 }
359 }
360
361 break;
362 }
363 case BADGE_EVENT_GAME_TICK:
364 {
365 jumpnrun_level_tick(&lv, &gs);
366 break;
367 }
368 }
369 }
370
371 return gs.status;
372 }
This page took 0.061011 seconds and 5 git commands to generate.