#ifndef BMD_STAGE_H #define BMD_STAGE_H #include #include #include "gameobjs.h" using namespace std; /* An attempt at movement can have different possible results. This would especially be used for sound effects with collision with various walls. At the moment all it is used for is: result_type = 1 <--- powerup collected 0 <--- simple allowed move -1 <--- collision with bomb -2 <--- collision with a bomber -3 <--- collision with unpathable wall example future applications: -4 <--- collision with bricks -5 <--- collision with player */ //enum MoveResultType { mrtWALL = -3, mrtBOMBER = -2, mrtBOMB = -1, mrtFREE = 0, mrtPUP = 1}; static const int mrtWALL = -3; static const int mrtBOMBER = -2; static const int mrtBOMB = -1; static const int mrtFREE = 0; static const int mrtPUP = 1; struct MoveResult { private: int result_type; int collidee_id; public: MoveResult(const int in_result_type){result_type = in_result_type; collidee_id = -1;} MoveResult(){result_type = -1; collidee_id = -1;} bool success() { return result_type >= 0; } //a positive result means success int get_type() { return result_type; } void set_collidee_id(int in_id) {collidee_id = in_id;} int get_collidee_id() {return collidee_id;} }; struct ExplosionResult { vector ef_bombers; vector ef_bombs; vector ef_powerups; vector created_powerups; ExplosionDat explosion_data; ExplosionResult() { } }; class TileType { public: int type_index; bool pathable() { if(type_index != tBRICK && type_index != tBLOCK) return true; return false; } }; class Tile { public: TileType type; //bool has_bomb; //bool has_bomber; int bomb_id; //bomb_id = -1 <=> no bomb int bomber_id; //bomber_id = -1 <=> no bomber int powerup_id; //... bool bomber_reserved;//if a bomber is approved to move to a tile, it becomes reserved bool bomb_reserved; //similar to bomber_reserved /* NOTE: the reason there are two types of reserved is in case there is ever a different result depending on who one colides with. A bomb detonation? Or just a different sound effect.*/ bool explosion_pathable() { return type.pathable() && bomb_id == -1; } bool clear_to_move() { return type.pathable() && bomb_id == -1 && bomber_id == -1 && !bomber_reserved && !bomb_reserved; } bool clear_to_bomb(const int owner_id) { return type.pathable() && bomb_id == -1 && bomber_id == owner_id && !bomber_reserved && !bomb_reserved; } inline bool has_bomber() { return (bomber_id >= 0);} inline bool has_powerup() { return (powerup_id >= 0);} inline bool has_bomb() { return (bomb_id >= 0);} //////////// // Test code void clear_bomb_residue() { bomb_id = -1; } void set_empty() { type.type_index = tEMPTY; bomb_id = bomber_id = powerup_id = -1; bomber_reserved = bomb_reserved = false; } PowerUp explode(const Coordinates & tile_coords) { PowerUp temppowerup; temppowerup.id = -1; //add destruction of bricks -> dropping of powerups, change in type essentially if(type.type_index == tBRICK) { type.type_index = tEXPLOSION_BRICK;//tBRICK_EXPLOSION; if(prob(POWERUP_ODDS))//if a power up is to be created { temppowerup.coords = tile_coords; temppowerup.power_type_index = rint(pOVER_MAX_INDEX); //first valid powerup index is 0 temppowerup.id = 0; //temporary ID indicating powerup creation } } else if(type.type_index == tEMPTY) type.type_index = tEXPLOSION; bomber_id = -1; return temppowerup; } // returns the ascii representation of a tile's contents char ascii_sym() { if(bomb_id >= 0) { if(bomber_id >= 0) return '%'; else return '+'; } else if(bomber_id >= 0) return 'B'; if(type.type_index == 0) return '.'; if(type.type_index == 1) return 'O'; if(type.type_index == 2) return '0'; return '?'; } }; class Stage { private: unsigned int H,W; Tile * tiles; bool tiles_declared; public: vector starting_loc; //vector of starting locations //to be used once coordinates are checked inline unsigned int unsafe_tile_index(const Coordinates & coords) const { return coords.x*H + coords.y; } inline unsigned int unsafe_tile_index(const unsigned int i, const unsigned int j) const { return i*H + j;} inline bool coord_valid(const Coordinates & coords) const { return (coords.x < W && coords.y < H); } inline bool coord_valid(const unsigned int i, const unsigned int j) const { return (i < W && j < H);} unsigned int tile_index(const Coordinates & coords) { if(coord_valid(coords)) return unsafe_tile_index(coords); raise_error(0); //*********** Temporary, improve. return 0; } //used only for testing at the moment unsigned int tile_index(unsigned int i, unsigned int j) { Coordinates temp_coords(i,j); return tile_index(temp_coords); } //////////////////////// /// Error handling //************** Temporary, improve. void raise_error(int error_type) { cout << "Error of type: " << error_type << endl; } public: // // implement map editing features // Stage() { H = W = 0; tiles_declared = false; } /* Stage(const char * map_file_name) { init(map_file_name); } */ //once map files are decided, implement /* void init(const char * map_file_name) { H = W = 0; tiles_declared = false;} */ /* the engine requests a bomber movement to: dest_coords the result of this movement is returned as a MoveResult(see). the possible outcomes are a collision, or if not, the bomber begins to move towards the destination tile, reserving it (making it unpathable) and limiting its movement options (during the transition the bomber can only move back towards its starting cell or keep proceeding). ****The bomber can still place bombs**** */ MoveResult bomber_CheckMove(const Coordinates & dest_coords) { unsigned int temp_index = tile_index(dest_coords); if(tiles[temp_index].clear_to_move()) { MoveResult move_result(mrtFREE); return move_result; } if(tiles[temp_index].bomb_id >= 0) { MoveResult move_result(mrtBOMB); move_result.set_collidee_id(tiles[temp_index].bomb_id); return move_result; } MoveResult move_result(mrtWALL); return move_result; } //must perform check_move_bomber before this to avoid errors void bomber_UnsafeCommenceMove(//const unsigned int bomber_id, const Coordinates & dest_coords) {tiles[unsafe_tile_index(dest_coords)].bomber_reserved = true;} /* Once a bomber has proceeded to an adjacent tile, the stage is signaled. Tile states are changed. */ void bomber_InformTileChange(const unsigned int in_bomber_id, const Coordinates & from_bomber_coords, const Coordinates & to_bomber_coords) { unsigned int temp_index = tile_index(from_bomber_coords); tiles[temp_index].bomber_id = -1; tiles[temp_index].bomber_reserved = true; temp_index = tile_index(to_bomber_coords); tiles[temp_index].bomber_id = in_bomber_id; tiles[temp_index].bomber_reserved = false; //the bomber is now in this tile, it is no longer just 'move_reserved' //a nice way to keep move_reserved up to date } //the following must be called to inform a bomber entirely leaving a tile - //it is then no longer considered reserved. void bomber_InformTileClear(const Coordinates & vacant_bomber_coords) { tiles[tile_index(vacant_bomber_coords)].bomber_reserved = false; } MoveResult bomb_CheckMove(const Coordinates & dest_coords) { unsigned int temp_index = tile_index(dest_coords); if(tiles[temp_index].clear_to_move()) { MoveResult move_result1(mrtFREE); return move_result1; } MoveResult move_result2(mrtWALL); return move_result2; } //must perform check_move_bomb before this to avoid errors void bomb_UnsafeCommenceMove(//const unsigned int bomb_id, const Coordinates & dest_coords) {tiles[unsafe_tile_index(dest_coords)].bomb_reserved = true;} /* Once a bomb has proceeded to an adjacent tile, the stage is signaled. Tile states are changed. */ void bomb_InformTileChange(const unsigned int in_bomb_id, const Coordinates & from_bomb_coords, const Coordinates & to_bomb_coords) { unsigned int temp_index = tile_index(from_bomb_coords); tiles[temp_index].bomb_id = -1; tiles[temp_index].bomb_reserved = true; temp_index = tile_index(to_bomb_coords); tiles[temp_index].bomb_id = in_bomb_id; tiles[temp_index].bomb_reserved = false; //the bomb is now in this tile, it is no longer just 'move_reserved' //a nice way to keep move_reserved up to date } //the following must be called to inform a bomb entirely leaving a tile - //it is then no longer considered reserved. void bomb_InformTileClear(const Coordinates & vacant_bomb_coords) { tiles[tile_index(vacant_bomb_coords)].bomb_reserved = false; } /* detonates a single bomb, returning a list of bombs, bombers and powerups effected */ ExplosionResult detonate_bomb(const Coordinates & coords, const unsigned int power) { ExplosionResult result; PowerUp temppowerup; Coordinates temp_coords_n; Coordinates temp_coords_p; unsigned int temp_index; bool n_pers, p_pers; //is the explosion persisting, or was it blocked? //n = negative //p = positive if(tiles[tile_index(coords)].has_bomber()) result.ef_bombers.push_back(tiles[tile_index(coords)].bomber_id); //techically i guess you know its the bomb's owner's id tiles[tile_index(coords)].bomb_id = -2; //DO NOT 'remove' the bomb from the tile system until later result.explosion_data.center = coords; n_pers = p_pers = true; temp_coords_n = coords; temp_coords_p = coords; for(unsigned int i = 0; i < power; i++) { if(n_pers) { temp_coords_n.x--; if(temp_coords_n.x >= 0) { temp_index = tile_index(temp_coords_n); if(tiles[temp_index].has_bomber()) result.ef_bombers.push_back(tiles[temp_index].bomber_id); if(tiles[temp_index].has_powerup()) result.ef_powerups.push_back(tiles[temp_index].powerup_id); if(tiles[temp_index].has_bomb()) { result.ef_bombs.push_back(tiles[temp_index].bomb_id); n_pers = false; result.explosion_data.dW = i;} else if(! tiles[temp_index].explosion_pathable()) //if its pathable, explosion persists - else is valid { n_pers = false; result.explosion_data.dW = i;} temppowerup = tiles[temp_index].explode(temp_coords_n); if(temppowerup.id >= 0) //ID used as sentinel for powerup creation { result.created_powerups.push_back(temppowerup); } } } if(p_pers) { temp_coords_p.x++; if(temp_coords_p.x < W) { temp_index = tile_index(temp_coords_p); if(tiles[temp_index].has_bomber()) result.ef_bombers.push_back(tiles[temp_index].bomber_id); if(tiles[temp_index].has_powerup()) result.ef_powerups.push_back(tiles[temp_index].bomber_id); if(tiles[temp_index].has_bomb()) { result.ef_bombs.push_back(tiles[temp_index].bomb_id); p_pers = false; result.explosion_data.dE = i;} else if(! tiles[temp_index].explosion_pathable()) //if its pathable, explosion persists - else is valid { p_pers = false; result.explosion_data.dE = i;} temppowerup = tiles[temp_index].explode(temp_coords_p); if(temppowerup.id >= 0) //ID used as sentinel for powerup creation { result.created_powerups.push_back(temppowerup); } } } } if(n_pers) result.explosion_data.dW = power; if(p_pers) result.explosion_data.dE = power; //the following code should be identical but use .y over .x, H over W, and dS over dW/dN over dE n_pers = p_pers = true; temp_coords_n = coords; temp_coords_p = coords; for(unsigned int i = 0; i < power; i++) { if(n_pers) { temp_coords_n.y--; if(temp_coords_n.y >= 0) { temp_index = tile_index(temp_coords_n); if(tiles[temp_index].has_bomber()) result.ef_bombers.push_back(tiles[temp_index].bomber_id); if(tiles[temp_index].has_powerup()) result.ef_powerups.push_back(tiles[temp_index].powerup_id); if(tiles[temp_index].has_bomb()) { result.ef_bombs.push_back(tiles[temp_index].bomb_id); n_pers = false; result.explosion_data.dN = i;} else if(! tiles[temp_index].explosion_pathable()) //if its pathable, explosion persists - else is valid { n_pers = false; result.explosion_data.dN = i;} temppowerup = tiles[temp_index].explode(temp_coords_n); if(temppowerup.id >= 0) //ID used as sentinel for powerup creation { result.created_powerups.push_back(temppowerup); } } } if(p_pers) { temp_coords_p.y++; if(temp_coords_p.y < H) { temp_index = tile_index(temp_coords_p); if(tiles[temp_index].has_bomber()) result.ef_bombers.push_back(tiles[temp_index].bomber_id); if(tiles[temp_index].has_powerup()) result.ef_powerups.push_back(tiles[temp_index].bomber_id); if(tiles[temp_index].has_bomb()) { result.ef_bombs.push_back(tiles[temp_index].bomb_id); p_pers = false; result.explosion_data.dS = i;} else if(! tiles[temp_index].explosion_pathable()) //if its pathable, explosion persists - else is valid { p_pers = false; result.explosion_data.dS = i;} temppowerup = tiles[temp_index].explode(temp_coords_p); if(temppowerup.id >= 0) //ID used as sentinel for powerup creation { result.created_powerups.push_back(temppowerup); } } } } if(n_pers) result.explosion_data.dN = power; if(p_pers) result.explosion_data.dS = power; return result; }// end detonate_bomb /* tells the stage to place a bomber with the given ID at the given location. returns true if the placement is allowed. otherwise returns false. */ bool place_bomber(unsigned int i, unsigned int j, unsigned int in_bomber_id) { if(coord_valid(i,j)) { unsigned int temp_tile_index = unsafe_tile_index(i,j); if(tiles[temp_tile_index].clear_to_move()) { tiles[temp_tile_index].bomber_id = in_bomber_id; return true; } } return false; } bool place_bomber(Bomber & bomber) { unsigned int temp_tile_index = unsafe_tile_index(bomber.coords); if(tiles[temp_tile_index].clear_to_move()) { tiles[temp_tile_index].bomber_id = bomber.get_id(); return true; } return false; } //the input is presumed to be safe - only called where powerups can be formed void place_powerup(PowerUp & powerup) { unsigned int temp_tile_index = unsafe_tile_index(powerup.coords); tiles[temp_tile_index].powerup_id = powerup.id; } bool place_bomb(Bomber & bomber, const unsigned int & in_bomb_id) { unsigned int temp_tile_index = unsafe_tile_index(bomber.coords); if(tiles[temp_tile_index].clear_to_bomb(bomber.get_id())) { tiles[temp_tile_index].bomb_id = in_bomb_id; return true; } return false; } bool place_bomb(const Coordinates & dest_coords, const unsigned int & in_bomb_id) { if(coord_valid(dest_coords)) { unsigned int temp_tile_index = unsafe_tile_index(dest_coords); if(tiles[temp_tile_index].clear_to_move()) { tiles[temp_tile_index].bomb_id = in_bomb_id; return true; } } return false; } bool place_bomb(unsigned int i, unsigned int j, unsigned int in_bomb_id) { Coordinates temp_coords(i,j); return place_bomb(temp_coords, in_bomb_id); } void clear_explosion(const Coordinates & coords) { if(coord_valid(coords)) {tiles[unsafe_tile_index(coords)].type.type_index = (unsigned int)tEMPTY;} } void clear_explosion_brick(const Coordinates & coords) { if(coord_valid(coords)) { unsigned int temp_index = unsafe_tile_index(coords); if(tiles[temp_index].type.type_index == tEXPLOSION_BRICK) tiles[temp_index].type.type_index = tEMPTY; } } void remove_bomb_residue(const Coordinates & coords) { if(coord_valid(coords)) {tiles[unsafe_tile_index(coords)].clear_bomb_residue();} } ~Stage() { if(tiles_declared) delete [] tiles; } int tile_tinfo(const Coordinates & coords) const { if(coord_valid(coords)) { return tiles[unsafe_tile_index(coords)].type.type_index; } return -1; } bool check_tinfo(const Coordinates & coords, int tindex) { if(coord_valid(coords)) { return tiles[unsafe_tile_index(coords)].type.type_index == tindex; } return false; } int acquire_powerup(const Coordinates & coords) { if(coord_valid(coords)) { unsigned int temp_index = unsafe_tile_index(coords); int ret_id = tiles[temp_index].powerup_id; tiles[temp_index].powerup_id = -1; //remove power up id, effectivelly "picking it up" return ret_id; } return -1; } unsigned int getH() const {return H;} unsigned int getW() const {return W;} //////////////////////// //////////////////////// //////////////////////// /// Testing Code follows //////////////////////// void ascii_dump() { for(unsigned int j = 0; j < H; j++) { cout << endl; for(unsigned int i = 0; i < W; i++) cout << tiles[unsafe_tile_index(i,j)].ascii_sym(); } cout << endl; cout << "-----------------------------"; } vector gen_random_map(unsigned int in_H, unsigned int in_W, unsigned int nP, //nP - number of players float brick_density) { H = in_H; W = in_W; if(H % 2 == 0) //if H is not even, map will be lopsided H--; if(W % 2 == 0) //similarly W--; tiles = new Tile[H*W]; tiles_declared = true; int sH, sW; //starting pointed related, modified H,W int rnd; int n_Hside; // int n_Vside; int n_remain; vector::iterator start_pos; for(unsigned int i = 0; i < W; i++) for(unsigned int j = 0; j < H; j++) tiles[tile_index(i,j)].set_empty(); //place 'field' blocks for(unsigned int i = 0; i < W; i+=2) for(unsigned int j = 0; j < H; j+=2) tiles[tile_index(i,j)].type.type_index = tBLOCK; //place bricks with ~density for(unsigned int i = 1; i < W - 1; i++) { if(i % 2 == 1) //free row { for(unsigned int j = 1; j < H - 1; j++) { if(prob(brick_density)) tiles[tile_index(i,j)].type.type_index = tBRICK; } } else //block row { for(unsigned int j = 1; j < H - 1; j+=2) { if(prob(brick_density)) tiles[tile_index(i,j)].type.type_index = tBRICK; } } } //place 'border' blocks for(unsigned int i = 0; i < W; i++) { tiles[tile_index(i,0)].type.type_index = 2; tiles[tile_index(i,H-1)].type.type_index = 2; } for(unsigned int j = 1; j < H -1; j++) { tiles[tile_index(0,j)].type.type_index = 2; tiles[tile_index(W-1,j)].type.type_index = 2; } //place player starting locations sH = H - 2; sW = W - 2; Coordinates locNW(1,1), locNE(sW,1), locSW(1,sH), locSE(sW,sH); rnd = rint(4); if(nP >= 4) { starting_loc.push_back(locNW); starting_loc.push_back(locNE); starting_loc.push_back(locSW); starting_loc.push_back(locSE); } else if(nP == 3) { if(rnd == 0) { starting_loc.push_back(locNE); starting_loc.push_back(locSW); starting_loc.push_back(locSE);} else if(rnd == 1) { starting_loc.push_back(locNW); starting_loc.push_back(locSW); starting_loc.push_back(locSE);} else if(rnd == 2) { starting_loc.push_back(locNW); starting_loc.push_back(locNE); starting_loc.push_back(locSE);} else { starting_loc.push_back(locNW); starting_loc.push_back(locNE); starting_loc.push_back(locSW);} } else if(nP == 2) { if(rnd == 0) { starting_loc.push_back(locNE); starting_loc.push_back(locNW);} else if(rnd == 1) { starting_loc.push_back(locNE); starting_loc.push_back(locSW);} else if(rnd == 2) { starting_loc.push_back(locNW); starting_loc.push_back(locSE);} else { starting_loc.push_back(locSE); starting_loc.push_back(locSW);} } else { if(rnd == 0) starting_loc.push_back(locNE); else if(rnd == 1) starting_loc.push_back(locNW); else if(rnd == 2) starting_loc.push_back(locSW); else starting_loc.push_back(locSE); } int tx, ty; for(start_pos = starting_loc.begin(); start_pos != starting_loc.end(); start_pos++) { tx = start_pos->x; ty = start_pos->y; tiles[tile_index(tx,ty)].type.type_index = tEMPTY; if(tx % 2 == 0) //horizontal is in line with a block { //however this implies fewer L drawing restrictions from border cases if(prob(0.5)) tx--; else tx++; tiles[tile_index(tx,ty)].type.type_index = tEMPTY; if(ty > 1) { if(ty < sH) //this case is unlikely, similar in below code { if(prob(0.5)) tiles[tile_index(tx,ty-1)].type.type_index = tEMPTY; else tiles[tile_index(tx,ty+1)].type.type_index = tEMPTY;} else tiles[tile_index(tx,ty-1)].type.type_index = tEMPTY; } else tiles[tile_index(tx,ty+1)].type.type_index = tEMPTY; } else if(ty % 2 == 0) { if(prob(0.5)) ty--; else ty++; tiles[tile_index(tx,ty)].type.type_index = tEMPTY; if(tx > 1) { if(tx < sW) //this case is unlikely, similar in below code { if(prob(0.5)) tiles[tile_index(tx-1,ty)].type.type_index = tEMPTY; else tiles[tile_index(tx+1,ty)].type.type_index = tEMPTY;} else tiles[tile_index(tx-1,ty)].type.type_index = tEMPTY; } else tiles[tile_index(tx+1,ty)].type.type_index = tEMPTY; } else //neither is aligned with a block { if(tx > 1) { if(tx < sW) { if(prob(0.5)) //50% chance tiles[tile_index(tx+1,ty)].type.type_index = tEMPTY; else tiles[tile_index(tx-1,ty)].type.type_index = tEMPTY;} else tiles[tile_index(tx-1,ty)].type.type_index = tEMPTY; } else tiles[tile_index(tx+1,ty)].type.type_index = tEMPTY; if(ty > 1) { if(ty < sH) { if(prob(0.5)) //50% chance tiles[tile_index(tx,ty+1)].type.type_index = tEMPTY; else tiles[tile_index(tx,ty-1)].type.type_index = tEMPTY;} else tiles[tile_index(tx,ty-1)].type.type_index = tEMPTY; } else tiles[tile_index(tx,ty+1)].type.type_index = tEMPTY; } } return starting_loc; } //end of map generator //empty init() call generates this preset stage for vector test_init() { return gen_random_map(12,16,4,DEFAULT_BRICK_DENSITY); } }; #endif