#include #include #include #include "engine.h" #define CELL_X(x) (-2 + 10 * (x)) #define CELL_Y(y) (-3 + 10 * (y)) //--- // Utilities //--- /* Determine the cycle pattern for a certain door */ static char const *map_door_cycle(struct map const *map, int x, int y) { int door = map->tiles[y * map->w + x]; int cycle_id = -1; if(door >= TILE_VDOOR && door < TILE_VDOOR + 8) cycle_id = door - TILE_VDOOR; else if(door >= TILE_HDOOR && door < TILE_HDOOR + 8) cycle_id = door - TILE_HDOOR + 8; else return NULL; return map->door_cycle + map->door_cycle_index[cycle_id]; } /* Check whether a cell of the map is walkable */ static bool map_walkable(struct game const *game, int x, int y) { /* Refuse to walk on walls */ int tile = game->map->tiles[y * game->map->w + x]; if(tile == TILE_WALL) return false; /* Refuse to walk on closed doors */ for(int t = 0; t < game->dynamic_tile_count; t++) { struct dynamic_tile const *dt = &game->dynamic_tiles[t]; if(dt->x != x || dt->y != y) continue; char const *cycle = map_door_cycle(game->map, x, y); if(!dt->state2 && cycle[dt->state] == '#') return false; } return true; } //--- // Map loading //--- void engine_load(struct game *game, struct map const *map) { game->map = map; game->time = 0; for(int p = 0; p < PLAYER_COUNT + 1; p++) game->players[p] = NULL; memset(&game->dynamic_tiles, 0, MAP_DYNAMIC_TILES * sizeof(struct dynamic_tile)); /* Create dynamic tiles for suitable tiles in the map */ int t = 0; for(int y = 0; y < map->h && t < MAP_DYNAMIC_TILES; y++) for(int x = 0; x < map->w && t < MAP_DYNAMIC_TILES; x++) { int tile = map->tiles[y*map->w + x]; struct dynamic_tile *dt = &game->dynamic_tiles[t]; dt->y = y; dt->x = x; dt->state = 0; int is_dynamic = 1; if(tile == TILE_START) { dt->idle = !anim_tile_start(&dt->anim, 1); } else if(tile == TILE_END) { dt->idle = !anim_tile_end(&dt->anim, 1); } else if(tile >= TILE_KEY && tile < TILE_KEY + 8) { /* TODO: Initial keys' dynamic tile information */ } else if((tile >= TILE_VDOOR && tile < TILE_VDOOR + 8) || (tile >= TILE_HDOOR && tile < TILE_HDOOR + 8)) { dt->anim.dir = (tile < TILE_HDOOR) ? DIR_UP : DIR_LEFT; char const *cycle = map_door_cycle(map, x, y); if(cycle[dt->state] == '#') dt->idle = !anim_door_closed(&dt->anim, 1); else dt->idle = !anim_door_open(&dt->anim, 1); } else is_dynamic = 0; t += is_dynamic; } game->dynamic_tile_count = t; } void engine_spawn(struct game *game) { struct map const *map = game->map; int p = 0; for(int y = 0; y < map->h && game->players[p]; y++) for(int x = 0; x < map->w && game->players[p]; x++) { /* Try to spawn a player at (x,y) */ if(map->tiles[y*map->w + x] == TILE_START) { struct player *player = game->players[p]; player->y = y; player->x = x; player->dir = DIR_DOWN; player->anim.dir = DIR_DOWN; player->idle = !anim_player_idle(&player->anim, 1); p++; } } } bool engine_all_players_idle(struct game *game) { for(int p = 0; game->players[p]; p++) { if(!game->players[p]->idle) return false; } return true; } bool engine_all_dynamic_tiles_idle(struct game *game) { for(int t = 0; t < game->dynamic_tile_count; t++) { if(!game->dynamic_tiles[t].idle) return false; } return true; } bool engine_wins(struct game *game, struct player *player) { /* First check that the player is participating in this game */ bool found = false; for(int p = 0; game->players[p] && !found; p++) { if(game->players[p] == player) found = true; } if(!found) return false; int tile = game->map->tiles[player->y * game->map->w + player->x]; return (tile == TILE_END); } //--- // Animations //--- void engine_tick(struct game *game, int delta_time) { game->time += delta_time; /* Update the animations for every player */ for(int p = 0; game->players[p]; p++) { struct player *player = game->players[p]; player->anim.duration -= delta_time; if(player->anim.duration > 0) continue; /* Call the animation function to generate the next frame */ player->idle = !player->anim.function(&player->anim, 0); } /* Update animations for dynamic tiles */ for(int t = 0; t < game->dynamic_tile_count; t++) { struct dynamic_tile *dt = &game->dynamic_tiles[t]; dt->anim.duration -= delta_time; if(dt->anim.duration > 0) continue; dt->idle = !dt->anim.function(&dt->anim, 0); } } //--- // Rendering //--- static void engine_draw_player(struct player const *player) { dframe(CELL_X(player->x) - 1 + player->anim.dx, CELL_Y(player->y) - 5 + player->anim.dy, player->anim.img); } static void engine_draw_dynamic_tile(struct dynamic_tile const *dt) { /* The -10 is hardcoded to suit the format of the tilesheet */ dframe(CELL_X(dt->x) + dt->anim.dx, CELL_Y(dt->y) + dt->anim.dy - 10, dt->anim.img); } void engine_draw(struct game const *game) { dclear(C_WHITE); dimage(0, 0, game->map->img); for(int t = 0; t < game->dynamic_tile_count; t++) { engine_draw_dynamic_tile(&game->dynamic_tiles[t]); } for(int p = 0; game->players[p]; p++) { engine_draw_player(game->players[p]); } } //--- // Physics //--- int engine_move(struct game *game, struct player *player, int dir) { int dx = (dir == DIR_RIGHT) - (dir == DIR_LEFT); int dy = (dir == DIR_DOWN) - (dir == DIR_UP); int olddir = player->dir; /* Don't move players that are already moving */ if(!player->idle) return 0; /* Update the direction */ player->dir = dir; player->anim.dir = dir; /* Only move the player if the destination is walkable */ if(!map_walkable(game, player->x + dx, player->y + dy)) { /* If not, set the new idle animation */ if(dir != olddir) { player->idle = !anim_player_idle(&player->anim,1); } return 0; } player->x += dx; player->y += dy; /* Set the walking animation */ player->idle = !anim_player_walking(&player->anim, 1); return 1; } void engine_finish_turn(struct game *game) { /* TODO: Have players pick up keys and open corresponding doors */ /* Update door cycles */ for(int t = 0; t < game->dynamic_tile_count; t++) { struct dynamic_tile *dt = &game->dynamic_tiles[t]; char const *cycle = map_door_cycle(game->map, dt->x, dt->y); if(!cycle) continue; /* Determine current open/closed status */ int current_status = dt->state2 ? '.' : cycle[dt->state]; /* Move to next cycle state */ dt->state++; if(cycle[dt->state] == ' ' || cycle[dt->state] == 0) dt->state = 0; dt->state2 = 0; /* Determine next open/closed status (this might be different from the cycle state if a player standing in the door prevents if from closing) */ int new_status = cycle[dt->state]; for(int p = 0; game->players[p]; p++) { struct player *player = game->players[p]; if(player->x == dt->x && player->y == dt->y) { new_status = '.'; dt->state2 = 1; } } if(new_status == current_status) continue; /* Set animations when changing status */ if(new_status == '#') dt->idle = !anim_door_closing(&dt->anim, 1); else if(new_status == '.') dt->idle = !anim_door_opening(&dt->anim, 1); } }