#include #include #include #include #include //#include // these are the divisors in the auto-allocation equations if // blotches and tiles per blotch are on auto mode #define AUTO_TILES_MODIFIER 20 #define AUTO_BLOTCHES_MODIFIER 150 /* ==================================== Blotch Maker Pro 2.4 - class mode! ==================================== If you want to dink around with blotchmaker, you can change the vars in the User Vars section below and also change the sample blotchmaking perameters in the Main() function. The sample vars template is a way to pass a whole great glob of info to BlotchMaker proper. Unfortunately, BlotchMaker is only a little test bed and a way for me to learn C++. It does not have any command line perams and the only way to change the perams is to edit this file and recompile. */ // NEXT: // find a way to include template presets on calling BlotchMaker. // right now we have only custom // a new public chartographer function? --> GetTemplate // split to headers and code to make a module // TO DO: // ------------------------------------------------------- // edge attraction for running tile placement // random number of tiles per blotch? // preset user config variables into templates: // Curliqueuos (snakes) // Pipe Grid // Mountain Ranges // Swiss Cheese // Mold Colonies // Pepper Shot // Tunnels (clear-mode) // Custom (user messes with all variables) // edge turning option (instead of terminating blotch) // starting corner sizes mapped out // cut simple path to each homebase // waypoints and path cutting to each point. // bailout of FULL blotch_mode if number of attempts is > tile_count * some_number // (or get rid of FULL) - could cause infinite loop on maxed out maps // blotch path thickness // edge deterioration on path thickness (not all cells padding the core cell are popped in) // kinda like blank shooting for each pad tile. // Box-shaped blotching? // ------------------------------------------------------- // single tile position structure struct pos { short int x; short int y; }; // obstacle type - for use with maptiles enum obstacle {empty=0, block, water, pit, barrier}; // map tile structure struct maptile { enum obstacle obst; //obstacle type short int elev; // land elevation struct pos pos; // X,Y coords (as backup) }; //blotch modes for blotch tile placement enum blotch_modes {full=0, chaotic=1, x_or=2, clear=3} ; // full - places all tiles regardless // chaotic - counts all attempts to place tiles as tiles actually placed. // x_or - places blocks if tile is empty, otherwise empties it. // clear - empties a tile if it contains an obstacle. // a little data module to fit inside the blotch template struct blotch_type_perams { short int tiles_per_blotch; // zero for AUTO float mode_chances[4]; // Mode Persuasion (in parts): Full, Chaotic, X_OR, Clear float turn_chances[4]; // Turn Persuasion (in parts): Forward, Left, Right, Back short int blank_shot_chance; // Blank Shot (percentage chance of shooting a blank per tile) short int path_thickness; // 1 to 10 --- 0 = regular line. 1 = one tile on all sides of core tile. etc. short int edge_det; // percent chance of edges of path to "shoot a blank" short int edge_grav; // value of the edge attraction. usually 1-5. zero for none; enum obstacle blotch_type; // just to call it something later when placing tiles, determining which kind of obstacle to place }; // AKA a "blotch template" struct map_user_vars_package { int max_blotches; // max number of blotches per blotch_type float type_chances[5]; // empty, block, water, pit, barrier // one set of vars for each mode struct blotch_type_perams empty; struct blotch_type_perams block; struct blotch_type_perams water; struct blotch_type_perams pit; struct blotch_type_perams barrier; }; //FUNCTION PROTOTYPES //"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" // Non-linear functions: void ConvertToGoalMarkers (float list[], short int num_list_items); inline int RoundValue(const float ¶m); inline float AbsValue(float num); inline int AbsValue(int num); //"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" // USER VARIABLES //"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" const bool verbose = 0; // these need to be defined before making a chartographer class const short int rows_in_map = 40; const short int cols_in_map = 60; const enum obstacle map_initializer = empty; //"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" /* MAP INTERFACE (notes) */ class chartographer { public: chartographer(short int cols, short int rows); void InitializeMap(enum obstacle initializer); // you can reinitialize it here, although it's initalized to EMPTY by default constructor void BlotchMaker(struct map_user_vars_package the_user_vars); // start making blotches! // on above ^ : the "template", whether set or custom, must be passed in from outside. // In the future, we may want a simplified interface, in which case we would just // ask for a template name and not give CUSTOM as an option struct maptile map[cols_in_map][rows_in_map]; // the map short int map_cols; short int map_rows; private: // variables struct pos prev_tile; struct pos this_tile; struct pos next_tile; int tile_count; // tiles per blotch, running total short int blotch_count; // blotches running total short int turn_types[4]; // possibly add 45deg turns in the future short int num_turn_types; // simple number to pass to functions short int num_blotch_modes; // simple number to pass to functions short int num_blotch_types; // simple number to pass to functions enum blotch_modes blotch_mode; // current blotch mode in operation struct blotch_type_perams *blotch_type; // ptr - current blotch type in operation struct map_user_vars_package vars_template; // the template containing the user variables. determine which one to use later // Functions: Starting from the top: void MakeBlotch (); bool DoNextTile (); int AdjustForEdgeAttraction (char axis, short int coord); int ExtractVector (struct pos prev_tile, struct pos next_tile); int GetTurnAngle (); bool GetNextTileCoords (short int vector, struct pos &this_tile, struct pos &next_tile); bool ApplyTile (struct pos next_tile); int chartographer::IsValid (struct pos tile); }; /////// MAIN START /////////////////////////////////////////////////////////////////////////////////////////////////////// int main () { //Seed the random numbers: srand(time(NULL)); // make a sample template /* !!!!!! USE THIS TO SET VARIABLES !!!!!! number of blotches to make, {type chances in lottery balls} ---> empty, block, water, pit, barrier, foreach type after that: {tiles per blotch, {mode persuasion in lottery balls}, ---> Full: all tiles placed - can cause infinite loops if map is maxed out- carefull ---> Chaotic: not all tiles may be placed (overwrite mode) ---> X_OR (yes if no, no if yes) ---> Clear (clears whatever was there - good for cutting paths) {turn persuasion in lottery balls}, ---> forward ---> left ---> right ---> backward percentage chance to shoot a blank ( 100 < ), path thickness (unused), edge deterioration (unused), edge attraction for first tile placed (usually about 1-100. more = stronger gravity), obstacle type (a self reporting variable - don't worry about it) } also check the user vars further up the file */ map_user_vars_package sample = { 100, {0,10,0,0,0}, // type chances: empty, block, water, pit, barrier // one set of vars for each type // EMPTY { 1, {0,1,0,0}, {1,1,0,0}, 10, 0, 0, 8, obstacle(0) }, // BLOCK { 10, {0,1,0,0}, {2,4,2,0}, 0, 0, 0, 0, obstacle(1) }, // WATER { 50, {0,1,0,0}, {1,1,0,0}, 5, 0, 0, 0, obstacle(2) }, // PIT { 50, {0,1,0,0}, {1,1,0,0}, 10, 0, 0, 8, obstacle(3) }, // BARRIER { 50, {0,1,0,0}, {1,1,0,0}, 10, 0, 0, 8, obstacle(4) } }; // creat a chartographer object and start making blotches on it class chartographer chartographer(cols_in_map, rows_in_map); chartographer.BlotchMaker(sample); //print the results for (int counter = 0; counter < chartographer.map_cols + 2; counter++) {cout << '*';} cout << '\n'; for (int counter = 0; counter < chartographer.map_rows; counter++ ) { cout << '*'; for (int counterB = 0; counterB < chartographer.map_cols; counterB++ ) { if ( chartographer.map[counterB][counter].obst == empty ) {cout << ' ';} else if ( chartographer.map[counterB][counter].obst == block ) {cout << 'X';} else if ( chartographer.map[counterB][counter].obst == water ) {cout << '~';} else if ( chartographer.map[counterB][counter].obst == barrier ) {cout << '+';} else if ( chartographer.map[counterB][counter].obst == pit ) {cout << 'u';} } cout << "*\n"; } for (int counter = 0; counter < chartographer.map_cols + 2; counter++) {cout << '*';} cout << '\n'; return(0); } // END OF MAIN ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////// chartographer::chartographer(short int cols, short int rows) { // constructor // initialize variables: tile_count = 0; blotch_count = 0; map_cols = cols; map_rows = rows; turn_types[0] = 0; turn_types[1] = 270; turn_types[2] = 90; turn_types[3] = 180; num_turn_types = 4; num_blotch_modes = 4; num_blotch_types = 5; // fill in the map --> automatically starts with EMPTY for (int counter = 0; counter < map_rows; counter++ ) { for (int counterB = 0; counterB < map_cols; counterB++ ) { map[counterB][counter].obst = empty; } } } ////////////////////////////////////////////////////////////////////////////////////////////////////////// ////// DONE //////////////////////////////////////////////////////////////////////////////////////////////////// void chartographer::InitializeMap(enum obstacle initializer) { // fill in the map for (int counter = 0; counter < map_rows; counter++ ) { for (int counterB = 0; counterB < map_cols; counterB++ ) { map[counterB][counter].obst = initializer; } } } ////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////// void chartographer::BlotchMaker(struct map_user_vars_package the_user_vars) { vars_template = the_user_vars; // puts this in more permanant area as a member var. // "chances" lists -> send to converter functions (for each blotch_type in the vars template) ConvertToGoalMarkers( vars_template.type_chances, num_blotch_types ); ConvertToGoalMarkers( vars_template.block.mode_chances, num_blotch_modes ); ConvertToGoalMarkers( vars_template.block.turn_chances, num_turn_types ); ConvertToGoalMarkers( vars_template.empty.mode_chances, num_blotch_modes ); ConvertToGoalMarkers( vars_template.empty.turn_chances, num_turn_types ); ConvertToGoalMarkers( vars_template.barrier.mode_chances, num_blotch_modes ); ConvertToGoalMarkers( vars_template.barrier.turn_chances, num_turn_types ); ConvertToGoalMarkers( vars_template.water.mode_chances, num_blotch_modes ); ConvertToGoalMarkers( vars_template.water.turn_chances, num_turn_types ); ConvertToGoalMarkers( vars_template.pit.mode_chances, num_blotch_modes ); ConvertToGoalMarkers( vars_template.pit.turn_chances, num_turn_types ); // dynamically adjust number of tiles & blotches if they are on auto (zero) mode; if (vars_template.max_blotches == 0) { vars_template.max_blotches = int( (map_cols * map_rows) / AUTO_BLOTCHES_MODIFIER ); } if (vars_template.empty.tiles_per_blotch == 0) { vars_template.empty.tiles_per_blotch = int( (map_cols * map_rows) / AUTO_TILES_MODIFIER ); } if (vars_template.water.tiles_per_blotch == 0) { vars_template.water.tiles_per_blotch = int( (map_cols * map_rows) / AUTO_TILES_MODIFIER ); } if (vars_template.block.tiles_per_blotch == 0) { vars_template.block.tiles_per_blotch = int( (map_cols * map_rows) / AUTO_TILES_MODIFIER ); } if (vars_template.pit.tiles_per_blotch == 0) { vars_template.pit.tiles_per_blotch = int( (map_cols * map_rows) / AUTO_TILES_MODIFIER ); } if (vars_template.barrier.tiles_per_blotch == 0) { vars_template.barrier.tiles_per_blotch = int( (map_cols * map_rows) / AUTO_TILES_MODIFIER ); } // start making blotches while(blotch_count < vars_template.max_blotches) { // roll for blotch TYPE float blotch_type_roll = rand() % 100; for (short int counter = 0; counter < num_blotch_types; counter++) { // set the blotch_type pointer to the right type stats in the template if ( blotch_type_roll <= vars_template.type_chances[counter] ) { switch (counter) { case 0: blotch_type = &vars_template.empty; break; case 1: blotch_type = &vars_template.block; break; case 2: blotch_type = &vars_template.water; break; case 3: blotch_type = &vars_template.pit; break; case 4: blotch_type = &vars_template.barrier; break; } break; } } float blotch_mode_roll = rand() % 100; for (short int counter = 0; counter < num_blotch_modes; counter++) { if ( blotch_mode_roll <= blotch_type->mode_chances[counter] ) { blotch_mode = blotch_modes(counter); //typecasted into blotch_mode type break; } } MakeBlotch(); blotch_count++; } } /////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////// void chartographer::MakeBlotch () { // Starts making a blotch. // pick a place to start, if needed if (tile_count == 0) { struct pos first_tile; first_tile.x = int( rand() % map_cols ); first_tile.y = int( rand() % map_rows ); first_tile.x = AdjustForEdgeAttraction('x', int(rand()%map_cols)); first_tile.y = AdjustForEdgeAttraction('y', int(rand()%map_rows)); ApplyTile(first_tile); if (verbose) {cout << "STARTING AT: " << first_tile.x << ',' << first_tile.y << '\n';} // update stats prev_tile.x = first_tile.x; prev_tile.y = first_tile.y; tile_count++; } if (verbose) {cout << "Selected: " << blotch_mode << " - [0=Full, 1=Chaotic, 2=X-OR]\n";} // start placing tiles while(tile_count < blotch_type->tiles_per_blotch) { DoNextTile(); } tile_count = 0; } ////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////// bool chartographer::DoNextTile () { // Places a tile in a series inside of a blotch // Returns: 0 if good, 1 if terminated. // do a random second tile, if needed, to establish a vector if (tile_count == 1) { bool gotcha = 0; while (gotcha == 0) { short int roll = int(rand()%4); switch (roll) { case 0: this_tile.x = prev_tile.x +1; this_tile.y = prev_tile.y; break; case 1: this_tile.x = prev_tile.x -1; this_tile.y = prev_tile.y; break; case 2: this_tile.x = prev_tile.x; this_tile.y = prev_tile.y +1; break; case 3: this_tile.x = prev_tile.x; this_tile.y = prev_tile.y -1; break; } // if it checks out, go with it. if ( IsValid(this_tile) ) { ApplyTile(this_tile); // update stats tile_count++; gotcha = 1; } } } // end random second tile // get a vector short int vector = ExtractVector(prev_tile, next_tile); if (verbose) {cout << "From: " << prev_tile.x << ',' << prev_tile.y << "To: " << this_tile.x << ',' << this_tile.y << '\n';} if (verbose) {cout << "Vector: " << vector << '\n';} // do a roll to get a turn angle short int turn_angle = GetTurnAngle(); // find the absolute turn angle to calculate the next tiles position short int final_vector = (vector + turn_angle) % 360; if (verbose) {cout << "Final Vector: " << final_vector << '\n';} // calculate the next tiles position bool failed = GetNextTileCoords(final_vector, this_tile, next_tile); // termination signal if bad if (failed) { return(1); } if (verbose) {cout << "From: " << this_tile.x << ',' << this_tile.y << "To: " << next_tile.x << ',' << next_tile.y << "\n\n";} // apply the blotch mode and tile to map bool do_not_update = ApplyTile(next_tile); // update stats if ( do_not_update == false ) {tile_count++;} prev_tile.x = this_tile.x; prev_tile.y = this_tile.y; this_tile.x = next_tile.x; this_tile.y = next_tile.y; return 0; } ////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////// bool chartographer::ApplyTile (struct pos next_tile) { // Applies the tile to the map in a variety of ways // Takes: tile to place // returns1 if do_not_update signal, 0 if not // do_not_update will attempt a tile placement "do-over". // shoot a blank? if ( rand()%100 < blotch_type->blank_shot_chance ) {return 1;} else { switch(blotch_mode) { case full: if (map[next_tile.x][next_tile.y].obst == blotch_type->blotch_type) { return 1;} else {map[next_tile.x][next_tile.y].obst = blotch_type->blotch_type;} break; case chaotic: map[next_tile.x][next_tile.y].obst = blotch_type->blotch_type; break; case x_or: if (map[next_tile.x][next_tile.y].obst == blotch_type->blotch_type) { map[next_tile.x][next_tile.y].obst = empty; } else {map[next_tile.x][next_tile.y].obst = blotch_type->blotch_type;} break; case clear: map[next_tile.x][next_tile.y].obst = empty; break; default: // defaults to full if (map[next_tile.x][next_tile.y].obst == blotch_type->blotch_type) { return 1;} else {map[next_tile.x][next_tile.y].obst = blotch_type->blotch_type;} } } //cout << "tile applied at " << next_tile.x << ',' << next_tile.y << "which is a --> " << blotch_mode << '\n'; return(0); } ///////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////// int chartographer::IsValid (struct pos tile) { // Returns 1 if a valid map coord is passed, zero if not. // takes: a tile pos. struct (tile x,y coords) if ( (tile.x < 0) || (tile.x > (map_cols - 1)) ) {return 0;} else if ( (tile.y < 0) || (tile.y > (map_rows - 1)) ) {return 0;} return 1; } ////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////// int chartographer::ExtractVector (struct pos prev_tile, struct pos next_tile) { // Gets a vector based on the current tile and the last tile placed. // Takes: the previous tile position, the current tile position // 0 (F) // | // 270 (L) ---+--- 90 (R) // | // 180 (B) short int delta_x = prev_tile.x - this_tile.x; short int delta_y = prev_tile.y - this_tile.y; if (delta_x == -1) { return 90;} else if (delta_x == 1) { return 270;} else if (delta_y == -1) { return 0;} else if (delta_y == 1) { return 180;} return(0); } ///////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////// int chartographer::GetTurnAngle () { // Gets a randomly generated turn, based on turn persuasions. // float roll = rand()%100; for (short int x = 0; x < num_turn_types; x++) { if ( roll <= blotch_type->turn_chances[x] ) { if (verbose) {cout << "Turn angle: " << turn_types[x] << '\n';} return (turn_types[x]); } } return (0); } ///////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////// bool chartographer::GetNextTileCoords (short int vector, struct pos &this_tile, struct pos &next_tile) { // gets the coordinates of a tile using a final vector (abs turn angle) and the previous tile // modifies "next_tile" instead of returning it. // takes: // the vector (turn angle), // ref to the current tile // ref to the next_tile // returns: 0 if good, 1 if failed and terminate blotch switch (vector) { case 0: next_tile.x = this_tile.x; next_tile.y = this_tile.y + 1; break; case 90: next_tile.x = this_tile.x + 1; next_tile.y = this_tile.y; break; case 180: next_tile.x = this_tile.x; next_tile.y = this_tile.y - 1; break; case 270: next_tile.x = this_tile.x - 1; next_tile.y = this_tile.y; break; } // check for validity if ( IsValid(next_tile) ) {return 0;} // otherwise [pick a new direction? or] TERMINATE BLOTCH else { tile_count = blotch_type->tiles_per_blotch; // max out counter return(1); // termination signal } } ///////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////// void ConvertToGoalMarkers (float list[], short int num_list_items) { // converts lists of random-chance parts into a list of % goal markers for if statement decisions // takes: // a list ref // number of items in list // returns: nothing (just transforms list) short int counter = 0; float max_chances = 0; float running_total = 0; //get total for (counter = 0; counter < num_list_items; counter++) { max_chances += list[counter]; } //convert to percentages for (counter = 0; counter < num_list_items; counter++) { list[counter] = ( list[counter] / max_chances ) * 100; } //convert to goal markers for (counter = 0; counter < num_list_items; counter++) { float was = list[counter]; list[counter] += running_total; running_total += was; } } ////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////// int chartographer::AdjustForEdgeAttraction (char axis, short int coord) { // Adjusts the first tile placed by the gravity of the edge in that particular blotch type // Takes: // the axis: (char) X or Y // the current position on that axis (say, "32"...) // Returns: the coord of where you ought to place the tile. //find the center float center; if (axis == 'X' || axis == 'x') {center = (map_cols-1) / 2.0;} else {center = (map_rows-1) / 2.0;} // minus one because this is for the array index which starts at zero!!! //find the distance from the edge float edge_dist = center - AbsValue(coord - center); if (edge_dist == 0) {edge_dist = 1;} // fix for possible divide by zero //apply the formula float attr = (blotch_type->edge_grav * center * (1 / edge_dist ) ) - blotch_type->edge_grav; //get the new tile on the axis to move to short int ans; if (coord < center) ans = coord - RoundValue(attr); else ans = coord + RoundValue(attr); // check for out-of-bounds if (ans < 0) {ans = 0;} if (ans > (center*2)) {ans = int(center*2);} return(ans); } ////////////////////////////////////////////////////////////////////////////////////////////////////////// inline int RoundValue(const float ¶m) { if((param - floor(param)) < 0.5f) return int(floor(param)); else return int(ceil(param)); } inline float AbsValue(float num){ if (num >= 0) return(num); else return( num - num - num ); } inline int AbsValue(int num){ if (num >= 0) return(num); else return( num - num - num ); }