#include #include #include #include #include #include #include #include #include "level.h" #include "items.h" #include "main.h" #include "draw.h" #define bool_t uint8_t struct level level; struct progress progress; struct entity entities[LEVEL_ENTITIES_MAX]; uint8_t level_data[LEVEL_SIZE_MAX]; #define player entities[0] static bool_t level_player_alive; static uint8_t level_frame_step; static uint8_t level_revealed; static uint8_t playerlastX; static uint8_t playerlastY; enum directions { DIRECTION_DOWN = 0, DIRECTION_LEFT = 1, DIRECTION_UP = 2, DIRECTION_RIGHT = 3, DIRECTION_MASK = /*11*/0x3, DIRECTION_RESET = 4, DIRECTION_NONE = 8, }; static uint8_t level_keys_test(void) { kb_Scan(); return (kb_Data[6] & (kb_Enter | kb_Clear)); } void level_info(void) { static char text_Diamonds[] = "Diamonds"; static char text_Lives[] = "Lives"; os_ClrHome(); print_text(3, 1, "LEVEL"); print_number(12 + 6, 1, progress.level_index); { uint8_t entities_count_backup = level.entities_count; level.entities_count = '\0'; print_text(3 + 5, 4, &level.name[0]); level.entities_count = entities_count_backup; } print_number(16 -7, 8, level.diamonds_required); text_Diamonds[8-1] = level.diamonds_required != 1 ? 's' : '\0'; print_text(16+6 -7,8, &text_Diamonds[0]); if (progress.player_lives > 0) { print_number(16 -7, 7, progress.player_lives); text_Lives[5-1] = progress.player_lives != 1 ? 's' : '\0'; print_text(16+6 -7,7, &text_Lives[0]); } while (level_keys_test()); // waits for a previously pressed key to be released while (!level_keys_test()); // waits for the player to press a key while (level_keys_test()); // waits for the player to release the key } uint8_t* level_move_direction(uint8_t direction, uint8_t* X, uint8_t* Y) { switch (direction) { case DIRECTION_DOWN : ++*Y; break; case DIRECTION_LEFT : --*X; break; case DIRECTION_UP : --*Y; break; case DIRECTION_RIGHT: ++*X; break; } return &level_data[(*Y) * level.sizeX + (*X)]; } void level_entity_explode(uint8_t* cell, uint8_t X, uint8_t Y) { uint8_t item = (*cell & ITEM_MASK) == ITEM_BUTTERFLY ? ITEM_DIAMOND : ITEM_AIR; int dx, dy; for (dy = -1; dy <= 1; ++dy) for (dx = -1; dx <= 1; ++dx) { cell = &level_data[(Y+dy) * level.sizeX + (X+dx)]; if (item_properties(*cell) & PROPERTY_BREAKABLE) { *cell = item; } } } void level_entity_remove(int i) { int j; --level.entities_count; for (j = i; j < level.entities_count; j++) { int k = j+1; entities[j] = entities[k]; } } void level_step_ennemies(void) { struct entity* ennemy = &entities[level.entities_count - 1]; int i; for (i = level.entities_count - 1; i > 0; --i, --ennemy) { uint8_t X = ennemy->posX; uint8_t Y = ennemy->posY; uint8_t* cell = &level_data[Y * level.sizeX + X]; uint8_t direction; uint8_t* cell_next; if (!(item_properties(*cell) & PROPERTY_EXPLODES_IF_CRUSHED)) { goto ennemy_remove; } else if (((ennemy->posY == playerlastY) && ((uint8_t)(ennemy->posX - playerlastX + 1) <= 2)) || ((ennemy->posX == playerlastX) && ((uint8_t)(ennemy->posY - playerlastY + 1) <= 2))) { level_player_alive = false; ennemy_explode: level_entity_explode(cell, X, Y); ennemy_remove: level_entity_remove(i); } else if (*cell & PROPERTY_CRUSHED) { goto ennemy_explode; } else { direction = ennemy->direction; cell_next = level_move_direction(direction, &X, &Y); if (!(*cell_next)) { *cell_next = *cell; *cell = 0; ennemy->posX = X; ennemy->posY = Y; ++direction; } else { --direction; } ennemy->direction = direction & DIRECTION_MASK; } } } enum level_play_result level_keys_check(void) { static uint8_t palette_delay = 0; kb_key_t key; uint8_t count; kb_Scan(); key = kb_Data[7]; count = 0; if ((key & kb_Up ) | (kb_Data[4] & kb_8)) { player.direction = DIRECTION_UP ; ++count; } if ((key & kb_Down ) | (kb_Data[4] & kb_2)) { player.direction = DIRECTION_DOWN ; ++count; } if ((key & kb_Left ) | (kb_Data[3] & kb_4)) { player.direction = DIRECTION_LEFT ; ++count; } if ((key & kb_Right) | (kb_Data[5] & kb_6)) { player.direction = DIRECTION_RIGHT; ++count; } if (count != 1) player.direction = DIRECTION_NONE; if (palette_delay) { --palette_delay; } else if (kb_Data[2] & kb_Alpha) { draw_toggle_palette(); palette_delay = 5; } key = kb_Data[6]; if (key & kb_Enter) player.direction = DIRECTION_RESET; return (key & kb_Clear) ? QUIT : PLAYING; } void level_adjust_view(void) { // keeps the view always perfectly centered on the player /* level.viewX = player.posX - (SCREEN_SPRITES_X / 2); */ /* level.viewY = player.posY - (SCREEN_SPRITES_Y / 2); */ // if the player isn't near the center of the screen, and the view can stay within the level boundaries, it moves towards the player if (player.posX - level.viewX < (SCREEN_SPRITES_X*1/3) && ((uint8_t)(level.viewX - 1)) < 256 - SCREEN_SPRITES_X) level.viewX--; if (player.posX - level.viewX > (SCREEN_SPRITES_X*2/3) && ((uint8_t)(level.viewX + SCREEN_SPRITES_X)) < level.sizeX) level.viewX++; if (player.posY - level.viewY < (SCREEN_SPRITES_Y*1/3) && ((uint8_t)(level.viewY - 1)) < 256 - SCREEN_SPRITES_Y) level.viewY--; if (player.posY - level.viewY > (SCREEN_SPRITES_Y*2/3) && ((uint8_t)(level.viewY + SCREEN_SPRITES_Y)) < level.sizeY) level.viewY++; } enum level_play_result level_step_player(void) { uint8_t X = player.posX; uint8_t Y = player.posY; uint8_t* cell = &level_data[Y * level.sizeX + X]; uint8_t* cell_next; uint8_t item_next; if (*cell & PROPERTY_CRUSHED) { level_entity_explode(cell, X, Y); goto dead; } else if (player.direction <= DIRECTION_MASK) { cell_next = level_move_direction(player.direction, &X, &Y); if ((*cell & ITEM_MASK) == ITEM_PLAYER) { item_next = *cell_next; if (item_properties(item_next) & PROPERTY_PLAYER_CAN_GOTO) { if ((item_next & ITEM_MASK) == ITEM_DIAMOND) { ++progress.diamonds_collected; if (level.diamonds_required) { if (!--level.diamonds_required) { level_revealed |= HIDDEN_EXIT; } } } else if ((item_next & ITEM_MASK) == ITEM_BOULDER) { if (level_frame_step == 0) { if ((player.direction == DIRECTION_LEFT) && (*(cell_next-1) == ITEM_AIR)) { *(cell_next-1) = item_next; goto move; } else if ((player.direction == DIRECTION_RIGHT) && (*(cell_next+1) == ITEM_AIR)) { *(cell_next+1) = item_next; goto move; } } return PLAYING; } else if (item_next == ITEM_EXIT) { return DONE; } move: *cell_next = *cell; *cell = 0; move_view: player.posX = X; player.posY = Y; level_adjust_view(); } } else if ((X < level.sizeX && Y < level.sizeY)) { goto move_view; } } else if (player.direction == DIRECTION_RESET) { *cell = 0; dead: if (level_player_alive) { level_player_alive = false; } else { return DIED; } } return PLAYING; } void level_step_cells(void) { uint8_t* cell = &level_data[(level.sizeY-1)*level.sizeX]; uint8_t* cell_under = cell + level.sizeX; uint8_t* cell_under_under = cell_under + level.sizeX; uint8_t y; for (y = level.sizeY - 2; (uint8_t) (y+1) != 0; --y) { uint8_t x; for (x = level.sizeX - 1; (uint8_t) (x+1) != 0; --x) { uint8_t item = *--cell; uint8_t item_under = *--cell_under; uint8_t item_under_under = *--cell_under_under; if (item & level_revealed) { *cell = (item & level_revealed) ^ (item ^ 1); } else if (item_properties(item) & PROPERTY_AFFECTED_BY_GRAVITY) { if (item_under == ITEM_AIR) { *cell_under = item | PROPERTY_FALLING; fell: *cell = ITEM_AIR; } else if (item_under == (ITEM_WALL | HIDDEN_MAGIC_WALL) && (item_under_under == ITEM_AIR)) { level_revealed |= HIDDEN_MAGIC_WALL; } else if (item_under == ITEM_MAGIC_WALL && (item_under_under == ITEM_AIR)) { *cell_under_under = (item ^ 1); goto fell; } else if (item_properties(item_under) & PROPERTY_SOAPY) { if (*(cell-1) == ITEM_AIR && *(cell_under-1) == ITEM_AIR) { *(cell_under-1) = item | PROPERTY_FALLING; goto fell; } else if (*(cell+1) == ITEM_AIR && *(cell_under+1) == ITEM_AIR) { *(cell_under+1) = item | PROPERTY_FALLING; goto fell; } goto land; } else if ((item & PROPERTY_FALLING) && (item_properties(item_under) & PROPERTY_EXPLODES_IF_CRUSHED)) { *cell_under = item_under | PROPERTY_CRUSHED; goto fell; } else { land: *cell &= ~PROPERTY_FALLING; } } } } } enum level_play_result level_play(void) { enum level_play_result result/* = PLAYING*/; level_info(); draw_init(); level_player_alive = true; level_frame_step = 0; level_revealed = 0; playerlastX = 0; playerlastY = 0; for (;;) { draw_tileset = level_frame_step; draw_view(level_data, level.sizeX, level.sizeY, level.viewX, level.viewY); delay(70); // so that the game runs at the same speed as the TI-83 Plus version level_step_ennemies(); if ((result = level_keys_check()) != PLAYING) break; if ((result = level_step_player()) != PLAYING) break; level_step_cells(); level_frame_step = (level_frame_step + 1) % 3; if (level_player_alive) { playerlastX = player.posX; playerlastY = player.posY; } } draw_exit(); return result; }