Lebenszahl levelweise einstellbar.
[hackover2013-badge-firmware.git] / badge / jumpnrun / jumpnrun.c
1 #include "game_state.h"
2 #include "jumpnrun.h"
3 #include "collision.h"
4 #include "levels.h"
5 #include "player.h"
6 #include "render.h"
7 #include "stats.h"
8
9 #include "../ui/display.h"
10 #include "../ui/event.h"
11 #include "../ui/sprite.h"
12 #include "../util/util.h"
13
14 #include <assert.h>
15 #include <math.h>
16 #include <stddef.h>
17 #include <stdio.h>
18
19 static vec2d gravity () { return (vec2d) { FIXED_POINT(0, 0), FIXED_POINT(0, 56) }; }
20 static vec2d move_max () { return (vec2d) { FIXED_POINT(0, 600), FIXED_POINT(1, 300) }; }
21 static fixed_point accel_horiz () { return FIXED_POINT(0, 50); }
22 static fixed_point accel_vert () { return FIXED_POINT(0, 167); }
23 static fixed_point drag_factor () { return FIXED_POINT(0, 854); }
24
25 static inline int imax(int x, int y) {
26 return x < y ? y : x;
27 }
28
29 static inline fixed_point hacker_left (vec2d const *pos, jumpnrun_game_state const *state) { (void) state; return pos->x; }
30 static inline fixed_point hacker_top (vec2d const *pos, jumpnrun_game_state const *state) { (void) state; return pos->y; }
31 static inline fixed_point hacker_right (vec2d const *pos, jumpnrun_game_state const *state) { return fixed_point_add(hacker_left(pos, state), jumpnrun_player_extents().x); }
32 static inline fixed_point hacker_bottom(vec2d const *pos, jumpnrun_game_state const *state) { return fixed_point_add(hacker_top (pos, state), jumpnrun_player_extents().y); }
33
34 int jumpnrun_level_assert_left_side(jumpnrun_game_state const *state) {
35 static int const lmargin = 20;
36 static int const rmargin = 50;
37
38 int pos_cur = fixed_point_cast_int(state->player.base.hitbox.pos.x);
39 int pos_rel = pos_cur - state->screen_left;
40
41 if(pos_rel < lmargin) {
42 return imax(0, pos_cur - lmargin);
43 } else if(pos_rel > BADGE_DISPLAY_WIDTH - rmargin) {
44 return pos_cur - (BADGE_DISPLAY_WIDTH - rmargin);
45 }
46
47 return state->screen_left;
48 }
49
50 static int jumpnrun_bsearch_tile(jumpnrun_level const *lv, jumpnrun_game_state const *state) {
51 int front = 0;
52 int len = lv->header.tile_count;
53
54 while(len > 0) {
55 int mid = front + len / 2;
56
57 if(fixed_point_lt(tile_right(&lv->tiles[mid]), FIXED_INT(state->screen_left - JUMPNRUN_MAX_SPAWN_MARGIN))) {
58 front = mid + 1;
59 len -= len / 2 + 1;
60 } else {
61 len /= 2;
62 }
63 }
64
65 return front;
66 }
67
68 jumpnrun_tile_range jumpnrun_visible_tiles(jumpnrun_level const *lv,
69 jumpnrun_game_state const *state) {
70 jumpnrun_tile_range r;
71
72 r.first = jumpnrun_bsearch_tile(lv, state);
73
74 for(r.last = r.first;
75 r.last < lv->header.tile_count && lv->tiles[r.last].pos.x * JUMPNRUN_TILE_PIXEL_WIDTH < state->screen_left + BADGE_DISPLAY_WIDTH + JUMPNRUN_MAX_SPAWN_MARGIN;
76 ++r.last)
77 ;
78
79 return r;
80 }
81
82 void jumpnrun_apply_gravity(vec2d *inertia) {
83 *inertia = vec2d_add(*inertia, gravity());
84 }
85
86 void jumpnrun_passive_movement(vec2d *inertia)
87 {
88 jumpnrun_apply_gravity(inertia);
89
90 inertia->x = fixed_point_min(fixed_point_max(fixed_point_neg(move_max().x), inertia->x), move_max().x);
91 inertia->y = fixed_point_min(fixed_point_max(fixed_point_neg(move_max().y), inertia->y), move_max().y);
92 }
93
94 static void jumpnrun_apply_movement(jumpnrun_level const *lv,
95 jumpnrun_tile_range const *tilerange,
96 jumpnrun_game_state *state,
97 vec2d *inertia_mod) {
98 switch(badge_event_current_input_state() &
99 (BADGE_EVENT_KEY_LEFT |
100 BADGE_EVENT_KEY_RIGHT)) {
101 case BADGE_EVENT_KEY_LEFT:
102 // state->player.base.inertia.x = state->player.touching_ground ? fixed_point_sub(state->player.base.inertia.x, accel_horiz()) : fixed_point_neg(speed_jump_x());
103 state->player.base.inertia.x = fixed_point_sub(state->player.base.inertia.x, accel_horiz());
104 state->player.base.flags |= JUMPNRUN_MOVEABLE_MIRRORED;
105 break;
106 case BADGE_EVENT_KEY_RIGHT:
107 // state->player.base.inertia.x = state->player.touching_ground ? fixed_point_add(state->player.base.inertia.x, accel_horiz()) : speed_jump_x();
108 state->player.base.inertia.x = fixed_point_add(state->player.base.inertia.x, accel_horiz());
109 state->player.base.flags &= ~JUMPNRUN_MOVEABLE_MIRRORED;
110 break;
111 default:
112 if(jumpnrun_moveable_touching_ground(&state->player.base)) {
113 state->player.base.inertia.x = fixed_point_mul(state->player.base.inertia.x, drag_factor());
114 } //else {
115 //state->player.base.inertia.x = FIXED_INT(0);
116 //}
117
118 break;
119 }
120
121 if(state->player.base.jumpable_frames == 0) {
122 // intentionally left blank.
123 } else if(badge_event_current_input_state() & BADGE_EVENT_KEY_BTN_A) {
124 state->player.base.inertia.y = fixed_point_sub(state->player.base.inertia.y, accel_vert());
125 --state->player.base.jumpable_frames;
126 } else {
127 state->player.base.jumpable_frames = 0;
128 }
129
130 jumpnrun_passive_movement(&state->player.base.inertia);
131
132 vec2d new_pos = vec2d_add(state->player.base.hitbox.pos, state->player.base.inertia);
133
134 if(fixed_point_lt(new_pos.x, FIXED_INT(state->screen_left))) {
135 new_pos.x = FIXED_INT(state->screen_left);
136 state->player.base.inertia.x = FIXED_INT(0);
137 }
138
139 *inertia_mod = state->player.base.inertia;
140 bool killed = collisions_tiles_displace(&new_pos, &state->player.base, lv, tilerange, inertia_mod);
141 state->player.base.inertia = *inertia_mod;
142
143 if(fixed_point_gt(rectangle_top(&state->player.base.hitbox), FIXED_INT(BADGE_DISPLAY_HEIGHT))) {
144 jumpnrun_player_despawn(&state->player);
145 } else if(killed) {
146 jumpnrun_player_kill (&state->player);
147 }
148 }
149
150 void jumpnrun_level_tick(jumpnrun_level *lv,
151 jumpnrun_game_state *state)
152 {
153 jumpnrun_tile_range tilerange = jumpnrun_visible_tiles(lv, state);
154 vec2d inertia_mod = state->player.base.inertia;
155
156 if(jumpnrun_player_alive(&state->player)) {
157 jumpnrun_apply_movement(lv, &tilerange, state, &inertia_mod);
158 }
159
160 state->screen_left = jumpnrun_level_assert_left_side(state);
161
162 if(state->tick == 0) {
163 badge_framebuffer fb = { { { 0 } } };
164
165 for(uint16_t tile = tilerange.first; tile < tilerange.last; ++tile) {
166 jumpnrun_render_tile(&fb, state, &lv->tiles[tile]);
167 }
168
169 for(uint16_t item = 0; item < lv->header.item_count; ++item) {
170 jumpnrun_item *item_obj = &lv->items[item];
171
172 if(item_obj->flags & JUMPNRUN_ITEM_COLLECTED) {
173 continue;
174 }
175
176 int screenpos = fixed_point_cast_int(item_obj->pos.x) - state->screen_left;
177 if(screenpos > -item_obj->type->sprite.width &&
178 screenpos < BADGE_DISPLAY_WIDTH) {
179 rectangle item_rect = rect_from_item(item_obj);
180
181 if(rectangle_intersect(&state->player.base.hitbox, &item_rect)) {
182 item_obj->type->on_collect(item_obj, state, lv);
183 }
184
185 jumpnrun_render_item(&fb, state, item_obj);
186 }
187 }
188
189 for(uint16_t shot_ix = 0; shot_ix < JUMPNRUN_MAX_SHOTS; ++shot_ix) {
190 jumpnrun_shot *shot = &state->shots[shot_ix];
191 jumpnrun_shot_process(shot);
192 if(jumpnrun_shot_spawned(shot)) {
193 jumpnrun_render_shot(&fb, state, shot);
194 }
195 }
196
197 for(uint16_t enemy_ix = 0; enemy_ix < lv->header.enemy_count; ++enemy_ix) {
198 jumpnrun_enemy *enemy = &lv->enemies[enemy_ix];
199 jumpnrun_process_enemy(enemy, &fb, state, lv, &tilerange, &inertia_mod);
200 }
201
202 if(jumpnrun_player_alive(&state->player)) {
203 jumpnrun_render_player(&fb, state);
204 jumpnrun_player_advance_animation(&state->player);
205 } else if(jumpnrun_moveable_finished_dying(&state->player.base)) {
206 jumpnrun_player_despawn(&state->player);
207 } else if(jumpnrun_moveable_dying(&state->player.base)) {
208 jumpnrun_render_splosion(&fb, state, &state->player.base);
209 state->player.base.tick_minor += JUMPNRUN_STATE_TICKS_PER_FRAME;
210 }
211
212 badge_framebuffer_flush(&fb);
213
214 if(!jumpnrun_moveable_touching_ground(&state->player.base)) {
215 state->player.base.anim_frame = 2;
216 } else if(fixed_point_gt(fixed_point_abs(state->player.base.inertia.x), FIXED_POINT(0, 200))) {
217 state->player.base.anim_frame = (state->player.base.anim_frame + 1) % JUMPNRUN_PLAYER_FRAMES;
218 } else {
219 state->player.base.anim_frame = 0;
220 }
221 } else {
222 for(uint16_t shot_ix = 0; shot_ix < JUMPNRUN_MAX_SHOTS; ++shot_ix) {
223 jumpnrun_shot_process(&state->shots[shot_ix]);
224 }
225
226 for(uint16_t enemy_ix = 0; enemy_ix < lv->header.enemy_count; ++enemy_ix) {
227 jumpnrun_enemy *enemy = &lv->enemies[enemy_ix];
228 jumpnrun_process_enemy(enemy, NULL, state, lv, &tilerange, &inertia_mod);
229 }
230 }
231
232 state->player.base.inertia = inertia_mod;
233 ++state->tick;
234 if(state->tick == JUMPNRUN_STATE_TICKS_PER_FRAME) {
235 state->tick = 0;
236 }
237 }
238
239 uint8_t jumpnrun_play_level(char const *lvname) {
240 jumpnrun_level lv;
241
242 JUMPNRUN_LEVEL_LOAD(lv, lvname);
243
244 jumpnrun_game_state gs;
245
246 jumpnrun_game_state_init(&gs, &lv);
247
248 do {
249 jumpnrun_show_lives_screen(&gs);
250 jumpnrun_game_state_respawn(&gs, &lv);
251
252 while((gs.player.base.flags & JUMPNRUN_PLAYER_DEAD) == 0 &&
253 (gs. flags & JUMPNRUN_STATE_WON ) == 0) {
254 badge_event_t ev = badge_event_wait();
255
256 switch(badge_event_type(ev)) {
257 case BADGE_EVENT_USER_INPUT:
258 {
259 uint8_t old_state = badge_event_old_input_state(ev);
260 uint8_t new_state = badge_event_new_input_state(ev);
261 uint8_t new_buttons = new_state & (old_state ^ new_state);
262
263 if((new_buttons & BADGE_EVENT_KEY_BTN_A) && jumpnrun_moveable_touching_ground(&gs.player.base)) {
264 gs.player.base.jumpable_frames = 12;
265 }
266
267 if((new_buttons & BADGE_EVENT_KEY_BTN_B)) {
268 uint8_t i;
269 for(i = 0; i < JUMPNRUN_MAX_SHOTS && jumpnrun_shot_spawned(&gs.shots[i]); ++i)
270 ;
271
272 if(i < JUMPNRUN_MAX_SHOTS) {
273 jumpnrun_shot_spawn(gs.shots + i, &gs);
274 }
275 }
276
277 break;
278 }
279 case BADGE_EVENT_GAME_TICK:
280 {
281 jumpnrun_level_tick(&lv, &gs);
282 break;
283 }
284 }
285 }
286 } while((gs.flags & JUMPNRUN_STATE_WON) == 0 && gs.player.lives-- != 0);
287
288 if(gs.flags & JUMPNRUN_STATE_WON) {
289 jumpnrun_show_you_rock();
290 return JUMPNRUN_WON;
291 }
292
293 if(++gs.player.lives == 0) {
294 jumpnrun_show_game_over();
295 return JUMPNRUN_LOST;
296 }
297
298 return JUMPNRUN_ERROR;
299 }
This page took 0.06903 seconds and 5 git commands to generate.