--- /dev/null
+#include "collision.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof*(arr))
+
+void collision_displace(vec2d *desired_pos,
+ rectangle const *current,
+ rectangle const *obstacle,
+ vec2d *inertia,
+ bool *touching_ground) {
+ rectangle desired = *current;
+ rectangle_move_to(&desired, *desired_pos);
+
+ if(!rectangle_intersect(obstacle, &desired)) {
+ return;
+ }
+
+ fixed_point x = FIXED_POINT(1000, 0), y = FIXED_POINT(1000, 0);
+ fixed_point dx = desired_pos->x, dy = desired_pos->y;
+ bool bottom_collision = false;
+
+ if(fixed_point_le(rectangle_top ( obstacle), rectangle_top(&desired)) &&
+ fixed_point_gt(rectangle_bottom( obstacle), rectangle_top(&desired)) &&
+ fixed_point_lt(rectangle_top (&desired ), rectangle_top( current))) {
+
+ y = fixed_point_sub(rectangle_bottom(obstacle), rectangle_top(&desired));
+ dy = rectangle_bottom(obstacle);
+
+ } else if(fixed_point_gt(rectangle_bottom( obstacle), rectangle_bottom(&desired)) &&
+ fixed_point_le(rectangle_top ( obstacle), rectangle_bottom(&desired)) &&
+ fixed_point_gt(rectangle_top (&desired ), rectangle_top ( current))) {
+
+ y = fixed_point_sub(rectangle_bottom(&desired ), rectangle_top ( obstacle));
+ dy = fixed_point_sub(rectangle_top ( obstacle), rectangle_height(&desired ));
+ bottom_collision = true;
+
+ }
+
+
+ if(fixed_point_le(rectangle_left ( obstacle), rectangle_left(&desired)) &&
+ fixed_point_gt(rectangle_right( obstacle), rectangle_left(&desired)) &&
+ fixed_point_lt(rectangle_left (&desired ), rectangle_left( current))) {
+
+ x = fixed_point_sub(rectangle_right(obstacle), rectangle_left(&desired));
+ dx = rectangle_right(obstacle);
+
+ } else if(fixed_point_gt(rectangle_right( obstacle), rectangle_right(&desired)) &&
+ fixed_point_le(rectangle_left ( obstacle), rectangle_right(&desired)) &&
+ fixed_point_gt(rectangle_left (&desired ), rectangle_left ( current))) {
+
+ x = fixed_point_sub(rectangle_right(&desired ), rectangle_left ( obstacle));
+ dx = fixed_point_sub(rectangle_left ( obstacle), rectangle_width(&desired ));
+
+ }
+
+ if(fixed_point_eq(x, y)) {
+ desired_pos->x = dx;
+ desired_pos->y = dy;
+ } else if(fixed_point_gt(x, y)) {
+ desired_pos->y = dy;
+ inertia->y = FIXED_POINT(0, 0);
+
+ *touching_ground = bottom_collision;
+ } else {
+ desired_pos->x = dx;
+ inertia->x = FIXED_POINT(0, 0);
+ }
+
+ return;
+}
+
+void collisions_tiles_displace(vec2d *desired_position,
+ rectangle const *current,
+ jumpnrun_level const *lv,
+ jumpnrun_tile_range const *visible_tiles,
+ vec2d *inertia,
+ bool *touching_ground)
+{
+ int collision_tile[] = { -1, -1, -1,
+ -1, -1, -1,
+ -1, -1, -1
+ };
+ static int const collision_order[] = { 7, 1, 3, 5, 6, 8, 0, 2 };
+
+ vec2d midpoint = rectangle_mid(current);
+
+ jumpnrun_tile_position midtile_pos = {
+ fixed_point_cast_int(fixed_point_div(midpoint.x, FIXED_POINT(JUMPNRUN_TILE_PIXEL_WIDTH , 0))),
+ fixed_point_cast_int(fixed_point_div(midpoint.y, FIXED_POINT(JUMPNRUN_TILE_PIXEL_HEIGHT, 0)))
+ };
+
+ int tile;
+
+ for(tile = visible_tiles->first; (size_t) tile < visible_tiles->last && lv->tiles[tile].pos.x < midtile_pos.x - 1; ++tile)
+ ;
+ for(; (size_t) tile < visible_tiles->last && lv->tiles[tile].pos.x < midtile_pos.x + 2; ++tile) {
+ int xdiff = lv->tiles[tile].pos.x - midtile_pos.x;
+ int ydiff = lv->tiles[tile].pos.y - midtile_pos.y;
+
+ switch(xdiff) {
+ case -1:
+ {
+ switch(ydiff) {
+ case -1: collision_tile[0] = tile; break;
+ case 0: collision_tile[1] = tile; break;
+ case 1: collision_tile[2] = tile; break;
+ }
+ break;
+ }
+ case 0:
+ {
+ switch(ydiff) {
+ case -1: collision_tile[3] = tile; break;
+ case 0: collision_tile[4] = tile; break;
+ case 1: collision_tile[5] = tile; break;
+ }
+ break;
+ }
+ case 1:
+ {
+ switch(ydiff) {
+ case -1: collision_tile[6] = tile; break;
+ case 0: collision_tile[7] = tile; break;
+ case 1: collision_tile[8] = tile; break;
+ }
+ break;
+ }
+ }
+ }
+
+ /* collision: sort by priority (top/bottom, left/right, then diagonal) */
+ *touching_ground = false;
+
+ // printf("mid: %d, %d\n", midtile_pos.x, midtile_pos.y);
+ for(size_t collision_index = 0; collision_index < ARRAY_SIZE(collision_order); ++collision_index) {
+ if(collision_tile[collision_order[collision_index]] == -1) {
+ continue;
+ }
+
+ rectangle tile_rect = rect_from_tile(&lv->tiles[collision_tile[collision_order[collision_index]]]);
+
+ collision_displace(desired_position, current, &tile_rect, inertia, touching_ground);
+ }
+}
--- /dev/null
+#ifndef INCLUDED_COLLISION_H
+#define INCLUDED_COLLISION_H
+
+#include "jumpnrun.h"
+#include <badge/util/rectangle.h>
+#include <stdbool.h>
+
+void collision_displace(vec2d *desired_pos,
+ rectangle const *current,
+ rectangle const *obstacle,
+ vec2d *inertia,
+ bool *touching_ground);
+
+void collisions_tiles_displace(vec2d *desired_position,
+ rectangle const *current,
+ jumpnrun_level const *level,
+ jumpnrun_tile_range const *visible_tiles,
+ vec2d *inertia,
+ bool *touching_ground);
+
+#endif
--- /dev/null
+#include "enemies.h"
+
+#include "collision.h"
+#include "tiles.h"
+#include "jumpnrun.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof*(arr))
+
+static badge_sprite const anim_cat[] = {
+ { 8, 5, (uint8_t const *) "\xc7\x3f\xce\x38\x11" },
+ { 8, 5, (uint8_t const *) "\xd7\x7d\xc6\x19\x25" }
+};
+
+void jumpnrun_process_enemy(jumpnrun_enemy *self,
+ badge_framebuffer *fb,
+ struct jumpnrun_game_state *state,
+ struct jumpnrun_level *lv,
+ struct jumpnrun_tile_range const *visible_tiles) {
+ int const spawn_margin = 1 + self->type->animation_frames[self->current_frame].width;
+
+ if(self->flags & JUMPNRUN_ENEMY_SPAWNED) {
+ if(fixed_point_lt(self->current_pos.x, FIXED_POINT(state->left - spawn_margin, 0)) ||
+ fixed_point_gt(self->current_pos.x, FIXED_POINT(state->left + BADGE_DISPLAY_WIDTH + spawn_margin, 0)) ||
+ fixed_point_cast_int(self->current_pos.y) > BADGE_DISPLAY_HEIGHT) {
+ self->flags &= ~JUMPNRUN_ENEMY_SPAWNED;
+ } else {
+ self->type->game_tick(self, state, lv, visible_tiles);
+ badge_framebuffer_blt(fb,
+ fixed_point_cast_int(self->current_pos.x) - state->left,
+ fixed_point_cast_int(self->current_pos.y),
+ &self->type->animation_frames[self->current_frame],
+ fixed_point_lt(self->inertia.x, FIXED_POINT(0, 0)) ? 0 : BADGE_BLT_MIRRORED);
+ }
+ } else if(self->flags & JUMPNRUN_ENEMY_UNAVAILABLE) {
+ if(state->left > fixed_point_cast_int(self->spawn_pos.x) + spawn_margin ||
+ state->left + BADGE_DISPLAY_WIDTH + spawn_margin < fixed_point_cast_int(self->spawn_pos.x)) {
+ self->flags &= ~JUMPNRUN_ENEMY_UNAVAILABLE;
+ }
+ } else if((fixed_point_gt(self->spawn_pos.x, FIXED_POINT(state->left - spawn_margin, 0)) &&
+ fixed_point_lt(self->spawn_pos.x, FIXED_POINT(state->left - spawn_margin / 2, 0))) ||
+ (fixed_point_lt(self->spawn_pos.x, FIXED_POINT(state->left + BADGE_DISPLAY_WIDTH + spawn_margin, 0)) &&
+ fixed_point_gt(self->spawn_pos.x, FIXED_POINT(state->left + BADGE_DISPLAY_WIDTH, 0)))) {
+ // enemy unspawned, available and in spawn zone.
+ self->flags |= JUMPNRUN_ENEMY_SPAWNED | JUMPNRUN_ENEMY_UNAVAILABLE;
+ self->current_pos = self->spawn_pos;
+ self->inertia = self->type->spawn_inertia;
+ self->current_frame = 0;
+ self->tick_counter = 0;
+ }
+}
+
+void enemy_collision_tiles_bounce_horiz(jumpnrun_enemy *self,
+ vec2d *desired_position,
+ jumpnrun_level *lv,
+ jumpnrun_tile_range const *visible_tiles) {
+ rectangle rect_self = rect_from_enemy(self);
+ vec2d inertia_copy = self->inertia;
+ bool touching_ground = false;
+
+ collisions_tiles_displace(desired_position,
+ &rect_self,
+ lv,
+ visible_tiles,
+ &inertia_copy,
+ &touching_ground);
+
+ if(fixed_point_ne(inertia_copy.x, self->inertia.x)) {
+ self->inertia.x = fixed_point_neg(self->inertia.x);
+ }
+}
+
+void enemy_collision_player_kill(jumpnrun_enemy *self,
+ jumpnrun_game_state *state)
+{
+ rectangle rect_self = rect_from_enemy(self);
+ rectangle rect_hacker = hacker_rect_current(state);
+
+ if(rectangle_intersect(&rect_self, &rect_hacker)) {
+ state->status = JUMPNRUN_DEAD;
+ }
+}
+
+void enemy_tick_cat(jumpnrun_enemy *self,
+ jumpnrun_game_state *state,
+ jumpnrun_level *lv,
+ jumpnrun_tile_range const *visible_tiles) {
+ int screenpos = fixed_point_cast_int(self->current_pos.x);
+
+ if(screenpos + JUMPNRUN_MAX_SPAWN_MARGIN < state->left ||
+ screenpos >= state->left + BADGE_DISPLAY_WIDTH + JUMPNRUN_MAX_SPAWN_MARGIN) {
+ return;
+ }
+
+ jumpnrun_passive_movement(&self->inertia);
+
+ vec2d new_pos = vec2d_add(self->current_pos, self->inertia);
+ self->type->collision_tiles(self, &new_pos, lv, visible_tiles);
+ self->type->collision_player(self, state);
+ self->current_pos = new_pos;
+
+ self->tick_counter = (self->tick_counter + 1) % self->type->animation_ticks_per_frame;
+ if(self->tick_counter == 0) {
+ self->current_frame = (self->current_frame + 1) % self->type->animation_length;
+ }
+}
+
+jumpnrun_enemy_type const jumpnrun_enemy_type_data[JUMPNRUN_ENEMY_TYPE_COUNT] = {
+ { 2, ARRAY_SIZE(anim_cat), anim_cat,
+ { FIXED_POINT_I(0, -800), FIXED_POINT_I(0, 0) },
+ enemy_collision_tiles_bounce_horiz,
+ enemy_collision_player_kill,
+ enemy_tick_cat
+ }
+};
--- /dev/null
+#ifndef INCLUDED_JUMPNRUN_ENEMIES_H
+#define INCLUDED_JUMPNRUN_ENEMIES_H
+
+#include <badge/ui/sprite.h>
+#include <badge/util/rectangle.h>
+
+#include "tiles.h"
+
+struct jumpnrun_game_state;
+struct jumpnrun_level;
+struct jumpnrun_tile_range;
+
+struct jumpnrun_enemy;
+
+typedef struct jumpnrun_enemy_type {
+ unsigned animation_ticks_per_frame;
+ size_t animation_length;
+ badge_sprite const *animation_frames;
+
+ vec2d spawn_inertia;
+
+ void (*collision_tiles)(struct jumpnrun_enemy *self,
+ vec2d *desired_position,
+ struct jumpnrun_level *lv,
+ struct jumpnrun_tile_range const *visible_tiles);
+ void (*collision_player)(struct jumpnrun_enemy *self,
+ struct jumpnrun_game_state *state);
+ void (*game_tick)(struct jumpnrun_enemy *self,
+ struct jumpnrun_game_state *state,
+ struct jumpnrun_level *lv,
+ struct jumpnrun_tile_range const *visible_tiles);
+} jumpnrun_enemy_type;
+
+typedef struct jumpnrun_enemy {
+ vec2d spawn_pos;
+ vec2d current_pos;
+ vec2d inertia;
+ unsigned flags;
+ unsigned tick_counter;
+ unsigned current_frame;
+
+ jumpnrun_enemy_type const *type;
+} jumpnrun_enemy;
+
+static inline rectangle rect_from_enemy(jumpnrun_enemy const *enemy) {
+ badge_sprite const *cur_sprite = &enemy->type->animation_frames[(enemy->tick_counter / enemy->type->animation_ticks_per_frame) % enemy->type->animation_length];
+ rectangle r = { enemy->current_pos, { FIXED_POINT(cur_sprite->width, 0), FIXED_POINT(cur_sprite->height, 0) } };
+ return r;
+}
+
+enum {
+ JUMPNRUN_ENEMY_SPAWNED = 1,
+ JUMPNRUN_ENEMY_UNAVAILABLE = 2
+};
+
+enum {
+ JUMPNRUN_ENEMY_TYPE_CAT,
+
+ JUMPNRUN_ENEMY_TYPE_COUNT
+};
+
+extern jumpnrun_enemy_type const jumpnrun_enemy_type_data[JUMPNRUN_ENEMY_TYPE_COUNT];
+
+void jumpnrun_process_enemy(jumpnrun_enemy *self,
+ badge_framebuffer *fb,
+ struct jumpnrun_game_state *state,
+ struct jumpnrun_level *lv,
+ struct jumpnrun_tile_range const *visible_tiles);
+#endif
--- /dev/null
+#include "items.h"
+#include "jumpnrun.h"
+
+static void on_collect_win(jumpnrun_game_state *state) {
+ state->status = JUMPNRUN_WON;
+}
+
+jumpnrun_item_type const jumpnrun_item_type_data[JUMPNRUN_ITEM_TYPE_COUNT] = {
+ { { 6, 7, (uint8_t const *) "\x7c\x61\xb5\x1a\xfc\x03" }, on_collect_win }
+};
--- /dev/null
+#ifndef INCLUDED_JUMPNRUN_ITEMS_H
+#define INCLUDED_JUMPNRUN_ITEMS_H
+
+#include <badge/ui/sprite.h>
+#include <badge/util/rectangle.h>
+
+struct jumpnrun_game_state;
+
+typedef struct jumpnrun_item_type {
+ badge_sprite sprite;
+ void (*on_collect)(struct jumpnrun_game_state *state);
+} jumpnrun_item_type;
+
+typedef struct jumpnrun_item {
+ vec2d pos;
+ jumpnrun_item_type const *type;
+} jumpnrun_item;
+
+static inline rectangle rect_from_item(jumpnrun_item const *item) {
+ rectangle r = { item->pos, { FIXED_POINT(item->type->sprite.width, 0), FIXED_POINT(item->type->sprite.height, 0) } };
+ return r;
+}
+
+/************************************/
+
+enum {
+ JUMPNRUN_ITEM_TYPE_DOCUMENT,
+
+ JUMPNRUN_ITEM_TYPE_COUNT
+};
+
+extern jumpnrun_item_type const jumpnrun_item_type_data[JUMPNRUN_ITEM_TYPE_COUNT];
+
+#endif
--- /dev/null
+#include "jumpnrun.h"
+#include "collision.h"
+
+#include <badge/ui/display.h>
+#include <badge/ui/event.h>
+#include <badge/ui/sprite.h>
+
+#include <assert.h>
+#include <math.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof*(arr))
+
+static vec2d const gravity = { FIXED_POINT_I(0, 0), FIXED_POINT_I(0, 56) };
+static vec2d const move_max = { FIXED_POINT_I(1, 200), FIXED_POINT_I(1, 300) };
+static fixed_point const accel_horiz = FIXED_POINT_I(0, 100);
+static fixed_point const accel_vert = FIXED_POINT_I(0, 250);
+static fixed_point const drag_factor = FIXED_POINT_I(0, 854);
+static fixed_point const speed_jump_x = FIXED_POINT_I(1, 200);
+
+static badge_sprite const anim_hacker[] = {
+ { 5, 8, (uint8_t const *) "\x1c\xff\xfd\x04\x04" },
+ { 5, 8, (uint8_t const *) "\x1c\xff\x3d\xc4\x04" },
+ { 5, 8, (uint8_t const *) "\xdc\x3f\x1d\x24\xc4" },
+ { 5, 8, (uint8_t const *) "\x1c\xff\x3d\xc4\x04" }
+
+/*
+ { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x31" },
+ { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x52" },
+ { 5, 8, (uint8_t const *) "\x46\xfc\x73\x94\x8c" },
+ { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x52" }
+*/
+ /*
+ { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\xc3\x30" },
+ { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x43\x51" },
+ { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x35\x82" },
+ { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x43\x51" }
+ */
+ /*
+ { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
+ { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
+ { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
+ { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" }
+ */
+};
+
+
+static inline int imax(int x, int y) {
+ return x < y ? y : x;
+}
+
+static inline fixed_point hacker_left (vec2d const *pos, jumpnrun_game_state const *state) { (void) state; return pos->x; }
+static inline fixed_point hacker_top (vec2d const *pos, jumpnrun_game_state const *state) { (void) state; return pos->y; }
+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)); }
+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)); }
+
+static inline rectangle hacker_rect(vec2d const *pos,
+ jumpnrun_game_state const *state) {
+ 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) } };
+}
+
+rectangle hacker_rect_current(jumpnrun_game_state const *state) {
+ return hacker_rect(&state->current_pos, state);
+}
+
+int jumpnrun_level_assert_left_side(jumpnrun_game_state const *state) {
+ static int const lmargin = 20;
+ static int const rmargin = 50;
+
+ int pos_cur = fixed_point_cast_int(state->current_pos.x);
+ int pos_rel = pos_cur - state->left;
+
+ if(pos_rel < lmargin) {
+ return imax(0, pos_cur - lmargin);
+ } else if(pos_rel > BADGE_DISPLAY_WIDTH - rmargin) {
+ return pos_cur - (BADGE_DISPLAY_WIDTH - rmargin);
+ }
+
+ return state->left;
+}
+
+static int jumpnrun_bsearch_tile(jumpnrun_level const *lv, jumpnrun_game_state const *state) {
+ int front = 0;
+ int len = lv->header.tile_count;
+
+ while(len > 0) {
+ int mid = front + len / 2;
+
+ if(fixed_point_lt(tile_right(&lv->tiles[mid]), FIXED_POINT(state->left - JUMPNRUN_MAX_SPAWN_MARGIN, 0))) {
+ front = mid + 1;
+ len -= len / 2 + 1;
+ } else {
+ len /= 2;
+ }
+ }
+
+ return front;
+}
+
+jumpnrun_tile_range jumpnrun_visible_tiles(jumpnrun_level const *lv,
+ jumpnrun_game_state const *state) {
+ jumpnrun_tile_range r;
+
+ r.first = jumpnrun_bsearch_tile(lv, state);
+
+ for(r.last = r.first;
+ 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;
+ ++r.last)
+ ;
+
+ return r;
+}
+
+void jumpnrun_passive_movement(vec2d *inertia)
+{
+ *inertia = vec2d_add(*inertia, gravity);
+
+ inertia->x = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.x), inertia->x), move_max.x);
+ inertia->y = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.y), inertia->y), move_max.y);
+}
+
+static void jumpnrun_apply_movement(jumpnrun_level const *lv,
+ jumpnrun_tile_range const *tilerange,
+ jumpnrun_game_state *state) {
+ switch(badge_event_current_input_state() &
+ (BADGE_EVENT_KEY_LEFT |
+ BADGE_EVENT_KEY_RIGHT)) {
+ case BADGE_EVENT_KEY_LEFT:
+ // state->inertia.x = state->touching_ground ? fixed_point_sub(state->inertia.x, accel_horiz) : fixed_point_neg(speed_jump_x);
+ state->inertia.x = fixed_point_sub(state->inertia.x, accel_horiz);
+ state->anim_direction = BADGE_BLT_MIRRORED;
+ break;
+ case BADGE_EVENT_KEY_RIGHT:
+ // state->inertia.x = state->touching_ground ? fixed_point_add(state->inertia.x, accel_horiz) : speed_jump_x;
+ state->inertia.x = fixed_point_add(state->inertia.x, accel_horiz);
+ state->anim_direction = 0;
+ break;
+ default:
+ if(state->touching_ground) {
+ state->inertia.x = fixed_point_mul(state->inertia.x, drag_factor);
+ } //else {
+ //state->inertia.x = FIXED_POINT(0, 0);
+ //}
+
+ break;
+ }
+
+ if(state->jumpable_frames == 0) {
+ // intentionally left blank.
+ } else if(badge_event_current_input_state() & BADGE_EVENT_KEY_UP) {
+ state->inertia.y = fixed_point_sub(state->inertia.y, accel_vert);
+ // fixed_point_neg(move_max.y)
+ --state->jumpable_frames;
+ } else {
+ state->jumpable_frames = 0;
+ }
+
+ jumpnrun_passive_movement(&state->inertia);
+
+ state->inertia.x = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.x), state->inertia.x), move_max.x);
+ state->inertia.y = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.y), state->inertia.y), move_max.y);
+
+ vec2d new_pos = vec2d_add(state->current_pos, state->inertia);
+
+ if(fixed_point_lt(new_pos.x, FIXED_POINT(state->left, 0))) {
+ new_pos.x = FIXED_POINT(state->left, 0);
+ state->inertia.x = FIXED_POINT(0, 0);
+ }
+
+ rectangle hacker_rect_c = hacker_rect(&state->current_pos, state);
+ collisions_tiles_displace(&new_pos, &hacker_rect_c, lv, tilerange, &state->inertia, &state->touching_ground);
+
+ state->current_pos = new_pos;
+
+ if(fixed_point_gt(state->current_pos.y, FIXED_POINT(BADGE_DISPLAY_HEIGHT, 0))) {
+ state->status = JUMPNRUN_DEAD;
+ }
+}
+
+void jumpnrun_level_tick(jumpnrun_level *lv,
+ jumpnrun_game_state *state)
+{
+ jumpnrun_tile_range tilerange = jumpnrun_visible_tiles(lv, state);
+ jumpnrun_apply_movement(lv, &tilerange, state);
+
+ state->left = jumpnrun_level_assert_left_side(state);
+
+ if(state->tick_minor == 0) {
+ badge_framebuffer fb;
+ badge_framebuffer_clear(&fb);
+
+ for(size_t tile = tilerange.first; tile < tilerange.last; ++tile) {
+ badge_framebuffer_blt(&fb,
+ fixed_point_cast_int(tile_left(&lv->tiles[tile])) - state->left,
+ fixed_point_cast_int(tile_top (&lv->tiles[tile])),
+ &tile_type(&lv->tiles[tile])->sprite,
+ 0);
+ }
+
+ for(size_t item = 0; item < lv->header.item_count; ++item) {
+ int screenpos = fixed_point_cast_int(lv->items[item].pos.x) - state->left;
+ if(screenpos > -lv->items[item].type->sprite.width &&
+ screenpos < BADGE_DISPLAY_WIDTH) {
+ rectangle hack_rect = hacker_rect(&state->current_pos, state);
+ rectangle item_rect = rect_from_item(&lv->items[item]);
+
+ if(rectangle_intersect(&hack_rect, &item_rect)) {
+ lv->items[item].type->on_collect(state);
+ }
+
+ badge_framebuffer_blt(&fb,
+ screenpos,
+ fixed_point_cast_int(lv->items[item].pos.y),
+ &lv->items[item].type->sprite,
+ 0);
+ }
+ }
+
+ for(size_t enemy_ix = 0; enemy_ix < lv->header.enemy_count; ++enemy_ix) {
+ jumpnrun_enemy *enemy = &lv->enemies[enemy_ix];
+ jumpnrun_process_enemy(enemy, &fb, state, lv, &tilerange);
+ }
+
+ badge_framebuffer_blt(&fb,
+ fixed_point_cast_int(state->current_pos.x) - state->left,
+ fixed_point_cast_int(state->current_pos.y),
+ &anim_hacker[state->anim_frame],
+ state->anim_direction);
+
+ badge_framebuffer_flush(&fb);
+
+ if(!state->touching_ground) {
+ state->anim_frame = 2;
+ } else if(fixed_point_gt(fixed_point_abs(state->inertia.x), FIXED_POINT(0, 200))) {
+ state->anim_frame = (state->anim_frame + 1) % ARRAY_SIZE(anim_hacker);
+ } else {
+ state->anim_frame = 0;
+ }
+ }
+
+ state->tick_minor = (state->tick_minor + 1) % 2;
+}
+
+uint8_t jumpnrun_play(char const *lvname) {
+ jumpnrun_level lv;
+
+ memset(&lv, 0, sizeof(lv));
+
+ // This part looks ugly. The reason it's done this way is that we don't know how much memory
+ // we need for the level before parsing its header, and that the VLAs we use to store it have
+ // block scope.
+ // Still, better than opening the whole dynamic memory can of worms.
+ FIL fd;
+ int err;
+
+ if(FR_OK != f_open(&fd, lvname, FA_OPEN_EXISTING | FA_READ)) { return JUMPNRUN_ERROR; }
+
+ if(0 != jumpnrun_load_level_header_from_file(&lv, &fd)) {
+ f_close(&fd);
+ return JUMPNRUN_ERROR;
+ }
+
+ JUMPNRUN_LEVEL_MAKE_SPACE(lv);
+ err = jumpnrun_load_level_from_file(&lv, &fd);
+
+ f_close(&fd);
+ if(err != 0) {
+ return JUMPNRUN_ERROR;
+ }
+
+ jumpnrun_game_state gs;
+ memset(&gs, 0, sizeof(gs));
+
+ gs.current_pos = lv.start_pos;
+
+ while(gs.status == JUMPNRUN_PLAYING) {
+ badge_event_t ev = badge_event_wait();
+
+ switch(badge_event_type(ev)) {
+ case BADGE_EVENT_USER_INPUT:
+ {
+ uint8_t old_state = badge_event_old_input_state(ev);
+ uint8_t new_state = badge_event_new_input_state(ev);
+ uint8_t new_buttons = new_state & (old_state ^ new_state);
+
+ if((new_buttons & BADGE_EVENT_KEY_UP) && gs.touching_ground) {
+ gs.jumpable_frames = 8;
+ }
+
+ break;
+ }
+ case BADGE_EVENT_GAME_TICK:
+ {
+ jumpnrun_level_tick(&lv, &gs);
+ break;
+ }
+ }
+ }
+
+ return gs.status;
+}
--- /dev/null
+#ifndef INCLUDED_BADGE2013_JUMPNRUN_H
+#define INCLUDED_BADGE2013_JUMPNRUN_H
+
+#include "enemies.h"
+#include "items.h"
+#include "levels.h"
+#include "tiles.h"
+
+#include <badge/util/fixed_point.h>
+#include <badge/util/rectangle.h>
+#include <badge/ui/sprite.h>
+
+#include <stdbool.h>
+#include <stddef.h>
+
+enum {
+ JUMPNRUN_PLAYING,
+ JUMPNRUN_DEAD,
+ JUMPNRUN_WON,
+ JUMPNRUN_ERROR
+};
+
+enum {
+ JUMPNRUN_MAX_SPAWN_MARGIN = BADGE_SPRITE_MAX_WIDTH + 1,
+ JUMPNRUN_MAX_SPAWNED_ENEMIES = 10
+};
+
+typedef struct jumpnrun_game_state {
+ vec2d current_pos;
+ vec2d inertia;
+ uint8_t status;
+
+ uint8_t tick_minor;
+ uint8_t anim_frame;
+ uint8_t anim_direction;
+
+ int left;
+
+ bool touching_ground;
+ uint8_t jumpable_frames;
+
+ size_t spawned_enemies_counter;
+ size_t spawned_enemies[JUMPNRUN_MAX_SPAWNED_ENEMIES];
+} jumpnrun_game_state;
+
+rectangle hacker_rect_current(jumpnrun_game_state const *state);
+
+
+void jumpnrun_passive_movement(vec2d *inertia);
+
+uint8_t jumpnrun_play(char const *lvname);
+
+#endif
--- /dev/null
+#include "levels.h"
+#include "tiles.h"
+#include "items.h"
+#include "enemies.h"
+
+#include <stdio.h>
+
+typedef struct {
+ uint8_t x;
+ uint16_t y;
+ uint8_t type;
+} level_thing;
+
+static level_thing jumpnrun_level_parse_blob(unsigned char blob[3]) {
+ level_thing result;
+
+ result.y = blob[0] >> 4;
+ result.x = ((blob[0] & 0xf) << 8) | blob[1];
+ result.type = blob[2];
+
+ return result;
+}
+
+static void jumpnrun_level_make_tile(jumpnrun_tile *dest, level_thing thing) {
+ dest->type = thing.type;
+ dest->pos.x = thing.x;
+ dest->pos.y = thing.y;
+}
+
+static void jumpnrun_level_make_item(jumpnrun_item *dest, level_thing thing) {
+ dest->type = &jumpnrun_item_type_data[thing.type];
+ dest->pos.x = FIXED_POINT(thing.x * JUMPNRUN_TILE_PIXEL_WIDTH , 0);
+ dest->pos.y = FIXED_POINT(thing.y * JUMPNRUN_TILE_PIXEL_WIDTH , 0);
+}
+
+static void jumpnrun_level_make_enemy(jumpnrun_enemy *dest, level_thing thing) {
+ dest->type = &jumpnrun_enemy_type_data[thing.type];
+
+ dest->spawn_pos.x = FIXED_POINT(thing.x * JUMPNRUN_TILE_PIXEL_WIDTH , 0);
+ dest->spawn_pos.y = FIXED_POINT(thing.y * JUMPNRUN_TILE_PIXEL_HEIGHT, 0);
+ dest->current_pos = dest->spawn_pos;
+ dest->inertia = dest->type->spawn_inertia;
+ dest->flags = 0;
+ dest->tick_counter = 0;
+ dest->current_frame = 0;
+}
+
+int jumpnrun_load_level_header_from_file(jumpnrun_level *dest, FIL *fd) {
+ uint16_t head[3];
+ UINT count;
+
+ if(FR_OK != f_read(fd, head, sizeof(head), &count) || count != sizeof(head)) {
+ return JUMPNRUN_LEVEL_LOAD_ERROR;
+ }
+
+ dest->header. tile_count = head[0];
+ dest->header. item_count = head[1];
+ dest->header.enemy_count = head[2];
+
+ return JUMPNRUN_LEVEL_LOAD_OK;
+}
+
+int jumpnrun_load_level_from_file(jumpnrun_level *dest, FIL *fd) {
+ size_t i;
+ unsigned char buf[3];
+ uint16_t spos[2];
+ UINT count;
+
+ if(FR_OK != f_read(fd, spos, sizeof(spos), &count) || count != sizeof(spos)) {
+ return JUMPNRUN_LEVEL_LOAD_ERROR;
+ } else {
+ dest->start_pos.x = FIXED_POINT(spos[0] * JUMPNRUN_TILE_PIXEL_WIDTH , 0);
+ dest->start_pos.y = FIXED_POINT(spos[1] * JUMPNRUN_TILE_PIXEL_HEIGHT, 0);
+ }
+
+ for(i = 0; i < dest->header.tile_count; ++i) {
+ if(FR_OK != f_read(fd, buf, sizeof(buf), &count) || count != sizeof(buf)) {
+ return JUMPNRUN_LEVEL_LOAD_ERROR;
+ }
+
+ jumpnrun_level_make_tile(&dest->tiles[i], jumpnrun_level_parse_blob(buf));
+ if(i != 0 &&
+ ((dest->tiles[i - 1].pos.x > dest->tiles[i].pos.x) ||
+ (dest->tiles[i - 1].pos.x == dest->tiles[i].pos.x && dest->tiles[i - 1].pos.y >= dest->tiles[i].pos.y))) {
+ return JUMPNRUN_LEVEL_LOAD_ERROR;
+ }
+ }
+
+ for(i = 0; i < dest->header.item_count; ++i) {
+ if(FR_OK != f_read(fd, buf, sizeof(buf), &count) || count != sizeof(buf)) {
+ return JUMPNRUN_LEVEL_LOAD_ERROR;
+ }
+
+ jumpnrun_level_make_item(&dest->items[i], jumpnrun_level_parse_blob(buf));
+ if(i != 0 &&
+ (fixed_point_gt(dest->items[i - 1].pos.x, dest->items[i].pos.x) ||
+ (fixed_point_eq(dest->items[i - 1].pos.x, dest->items[i].pos.x) && fixed_point_ge(dest->items[i - 1].pos.y, dest->items[i].pos.y)))) {
+ return JUMPNRUN_LEVEL_LOAD_ERROR;
+ }
+ }
+
+ for(i = 0; i < dest->header.enemy_count; ++i) {
+ if(FR_OK != f_read(fd, buf, sizeof(buf), &count) || count != sizeof(buf)) {
+ return JUMPNRUN_LEVEL_LOAD_ERROR;
+ }
+
+ jumpnrun_level_make_enemy(&dest->enemies[i], jumpnrun_level_parse_blob(buf));
+ if(i != 0 &&
+ (fixed_point_gt(dest->enemies[i - 1].spawn_pos.x, dest->enemies[i].spawn_pos.x) ||
+ (fixed_point_eq(dest->enemies[i - 1].spawn_pos.x, dest->enemies[i].spawn_pos.x) && fixed_point_ge(dest->enemies[i - 1].spawn_pos.y, dest->enemies[i].spawn_pos.y)))) {
+ return JUMPNRUN_LEVEL_LOAD_ERROR;
+ }
+ }
+
+ return JUMPNRUN_LEVEL_LOAD_OK;
+}
--- /dev/null
+#ifndef INCLUDED_JUMPNRUN_LEVEL_H
+#define INCLUDED_JUMPNRUN_LEVEL_H
+
+#include "enemies.h"
+#include "items.h"
+#include "tiles.h"
+
+#include <badge/util/rectangle.h>
+
+#include <drivers/fatfs/ff.h>
+
+#include <stddef.h>
+#include <stdio.h>
+
+typedef struct jumpnrun_level_header {
+ size_t tile_count;
+ size_t item_count;
+ size_t enemy_count;
+} jumpnrun_level_header;
+
+typedef struct jumpnrun_level {
+ jumpnrun_level_header header;
+
+ vec2d start_pos;
+
+ jumpnrun_tile *tiles;
+ jumpnrun_item *items;
+ jumpnrun_enemy *enemies;
+} jumpnrun_level;
+
+enum {
+ JUMPNRUN_LEVEL_LOAD_OK,
+ JUMPNRUN_LEVEL_LOAD_ERROR
+};
+
+size_t jumpnrun_level_count(void);
+void jumpnrun_levels_dump(void);
+
+int jumpnrun_load_level_header_from_file(jumpnrun_level *dest, FIL *fd);
+int jumpnrun_load_level_from_file (jumpnrun_level *dest, FIL *fd);
+
+// Use stack-local VLAs to store dynamic content.
+#define JUMPNRUN_LEVEL_MAKE_SPACE(var) \
+ jumpnrun_tile var ## _tiles [var.header.tile_count]; \
+ jumpnrun_item var ## _items [var.header.item_count]; \
+ jumpnrun_enemy var ## _enemies[var.header.enemy_count]; \
+ \
+ var.tiles = var ## _tiles; \
+ var.items = var ## _items; \
+ var.enemies = var ## _enemies;
+
+#endif
--- /dev/null
+
+
+
+ #
+ # #
+ # #
+ #
+ # #
+ # # # #
+
+ # # D
+ P
+####### ######### # # # # ########## #
+
+[tiles]
+# brick
+
+[items]
+D doc
--- /dev/null
+
+
+ ##
+ C C #
+ ? ######## ###? ? ### #??# ## #
+ ### #
+ #### # #
+ ##### # ###
+ ? #?#?# 01 01 #?# # ## ? ? ? # ## # # ## # ##?# ###### # ###
+ 01 23 23 ## ## ### ## ####### # #####
+ P 01 23 23 23 ### ### #### ### 01 01 ######## D # #####
+ C 23 23C 23 C C 23 C C C C C C C C C #### #### ##### #### 23 C C 23######### # #####
+#################################################################### ############### ################################################################ ##################################################### #########
+
+[tiles]
+0 tube_top_left
+1 tube_top_right
+2 tube_left
+3 tube_right
+# brick
+? square
+
+[items]
+D doc
+
+[enemies]
+C cat
--- /dev/null
+#include "tiles.h"
+
+jumpnrun_tile_type const jumpnrun_tile_type_data[JUMPNRUN_TILE_TYPE_COUNT] = {
+ { 0, { 5, 5, (uint8_t const *) "\x3f\xf7\xfe\x01" } },
+ { 0, { 5, 5, (uint8_t const *) "\x3f\xc6\xf8\x01" } },
+ { 0, { 5, 5, (uint8_t const *) "\x2f\x87\x10\x00" } },
+ { 0, { 5, 5, (uint8_t const *) "\x21\x84\xfc\x00" } },
+ { 0, { 5, 5, (uint8_t const *) "\xe0\x03\x00\x00" } },
+ { 0, { 5, 5, (uint8_t const *) "\x00\x80\x0f\x00" } }
+};
--- /dev/null
+#ifndef INCLUDED_JUMPNRUN_TILES_H
+#define INCLUDED_JUMPNRUN_TILES_H
+
+#include <badge/util/fixed_point.h>
+#include <badge/util/rectangle.h>
+#include <badge/ui/sprite.h>
+
+#include <stdint.h>
+
+typedef enum {
+ JUMPNRUN_TILE_TOP = 1,
+ JUMPNRUN_TILE_BOTTOM = 2,
+ JUMPNRUN_TILE_LEFT = 4,
+ JUMPNRUN_TILE_RIGHT = 8
+} jumpnrun_tile_sides;
+
+enum {
+ JUMPNRUN_TILE_PIXEL_WIDTH = 5,
+ JUMPNRUN_TILE_PIXEL_HEIGHT = 5
+};
+
+typedef struct jumpnrun_tile_position {
+ uint16_t x;
+ uint8_t y;
+} jumpnrun_tile_position;
+
+typedef struct jumpnrun_tile_type {
+ uint8_t lethal_sides;
+ badge_sprite sprite;
+} jumpnrun_tile_type;
+
+typedef struct jumpnrun_tile {
+ jumpnrun_tile_position pos;
+ uint8_t type;
+} jumpnrun_tile;
+
+typedef struct jumpnrun_tile_range {
+ size_t first;
+ size_t last; // actually one past last.
+} jumpnrun_tile_range;
+
+static inline fixed_point tile_left (jumpnrun_tile const *tile) { return FIXED_POINT(tile->pos.x * JUMPNRUN_TILE_PIXEL_WIDTH , 0); }
+static inline fixed_point tile_top (jumpnrun_tile const *tile) { return FIXED_POINT(tile->pos.y * JUMPNRUN_TILE_PIXEL_HEIGHT, 0); }
+static inline fixed_point tile_right (jumpnrun_tile const *tile) { return fixed_point_add(tile_left(tile), FIXED_POINT(JUMPNRUN_TILE_PIXEL_WIDTH , 0)); }
+static inline fixed_point tile_bottom(jumpnrun_tile const *tile) { return fixed_point_add(tile_top (tile), FIXED_POINT(JUMPNRUN_TILE_PIXEL_HEIGHT, 0)); }
+
+/************************************/
+
+enum {
+ JUMPNRUN_TILE_TYPE_BRICK,
+ JUMPNRUN_TILE_TYPE_SQUARE,
+ JUMPNRUN_TILE_TYPE_TUBE_TOP_LEFT,
+ JUMPNRUN_TILE_TYPE_TUBE_TOP_RIGHT,
+ JUMPNRUN_TILE_TYPE_TUBE_LEFT,
+ JUMPNRUN_TILE_TYPE_TUBE_RIGHT,
+
+ JUMPNRUN_TILE_TYPE_COUNT
+};
+
+extern jumpnrun_tile_type const jumpnrun_tile_type_data[JUMPNRUN_TILE_TYPE_COUNT];
+
+static inline jumpnrun_tile_type const *tile_type(jumpnrun_tile const *tile) {
+ return &jumpnrun_tile_type_data[tile->type];
+}
+
+static inline rectangle rect_from_tile(jumpnrun_tile const *tile) {
+ rectangle r = {
+ {
+ tile_left(tile),
+ tile_top(tile)
+ }, {
+ FIXED_POINT(tile_type(tile)->sprite.width , 0),
+ FIXED_POINT(tile_type(tile)->sprite.height, 0)
+ }
+ };
+ return r;
+}
+
+#endif