import json import random import project_constants as const # import tile class from objects.tile import Tile # import mine models from objects.mine_models.standard_mine import StandardMine from objects.mine_models.time_mine import TimeMine from objects.mine_models.chained_mine import ChainedMine class JsonGenerator: grid = dict() # constructor that can be used to set agent's initial state def __init__(self, agents_initial_position=(0, 0), agents_initial_direction=const.Direction.UP.value): # saving agent's initial state (position & direction) self.agents_initial_position = format_position_to_tuple(agents_initial_position) self.agents_initial_direction = agents_initial_direction # saving data to the grid dictionary self.grid["agents_initial_state"] = { "position": format_position_to_str(agents_initial_position), "direction": agents_initial_direction } # sets agent's initial state def set_agents_initial_state(self, position=(0, 0), direction=const.Direction.UP.value): # setting fields in the instance self.agents_initial_position = format_position_to_tuple(position) self.agents_initial_direction = direction # setting new agent's initial state self.grid["agents_initial_state"] = { "position": format_position_to_str(position), "direction": direction } # overwrites grid field with a new grid with randomized colors and mines def generate_randomized_grid(self, dimensions, mine_appearance_chance=0.15, predecessor_chance_decrease=0.25): # clearing grid field self.clear_grid() # formatting dimensions to tuple dimensions = format_position_to_tuple(dimensions) # getting grid dimensions num_of_rows, num_of_columns = dimensions tile_pool = [] for i in range(num_of_rows): for j in range(num_of_columns): # picking random values for tiles random_tile_color = random.choice(const.STRUCT_TILE_TERRAINS) # adding added tile's indexes to a pool tile_pool.append((i, j)) # creating random tile self.add_tile((i, j), random_tile_color) # deleting agent's starting tile from the pool deleted_row, deleted_column = self.agents_initial_position tile_pool.remove((int(deleted_row), int(deleted_column))) for i in range(num_of_rows): for j in range(num_of_columns): # checking if a mine will appear if random.random() < mine_appearance_chance and len(tile_pool) > 0 and tile_pool.__contains__((i, j)): # removing current tile from the pool tile_pool.remove((i, j)) # choosing random mine parameters random_mine_type = random.choice(const.STRUCT_MINE_TYPES) random_attribute_values = [] for attr_type in const.STRUCT_MINE_ATTRIBUTE_TYPES[random_mine_type]: random_attribute_values.append(_get_random_attribute_values(attr_type, dimensions)) # adding the mine self.set_a_mine((i, j), random_mine_type, random_attribute_values) # if is ChainedMine create predecessors if random_mine_type == "chained": predecessor_appearance_chance = 1.0 current_tile = format_position_to_str((i, j)) # create chained predecessors while random.random() < predecessor_appearance_chance and len(tile_pool) > 0: predecessor_appearance_chance -= predecessor_chance_decrease predecessor_position = random.choice(tile_pool) predecessor = format_position_to_str(predecessor_position) tile_pool.remove(predecessor_position) self.set_a_mine(predecessor_position, "chained", []) self.grid[current_tile]["mine"]["predecessor"] = predecessor self.grid[predecessor]["mine"]["predecessor"] = None current_tile = predecessor # adds a new tile or edits an existing one in the grid field def add_tile(self, position, color): # creating new tile without a mine self.grid[format_position_to_str(position)] = { "color": color, "mine": None } # adds a new tile with a mine or edits an existing one in the grid field def add_tile_with_a_mine(self, position, color, mine_type, attribute_values): # setting mine data using attribute_values mine_values = const.STRUCT_MINE_ATTRIBUTES[mine_type] for key in mine_values.keys(): if key not in const.HARDCODED_VALUES and len(attribute_values) > 0: mine_values[key] = attribute_values.pop(0) # creating a new tile self.grid[format_position_to_str(position)] = { "color": color } # updating the tile with a mine field self.grid[format_position_to_str(position)]["mine"] = {} for key in mine_values.keys(): self.grid[format_position_to_str(position)]["mine"][key] = mine_values[key] # deletes a mine with a given position from the grid field def delete_a_tile(self, position): # deleting a tile with given key self.grid.pop(format_position_to_str(position)) # adds a mine to a tile stored in the grid field def set_a_mine(self, position, mine_type, attribute_values): # setting mine data using attribute_values mine_values = const.STRUCT_MINE_ATTRIBUTES[mine_type] for key in mine_values.keys(): if key not in const.HARDCODED_VALUES and len(attribute_values) > 0: mine_values[key] = attribute_values.pop(0) # adding a mine to the edited tile self.grid[format_position_to_str(position)]["mine"] = {} for key in mine_values.keys(): self.grid[format_position_to_str(position)]["mine"][key] = mine_values[key] # deletes a mine from a tile stored in the grid field def delete_a_mine(self, position): # removing mine from the edited tile self.grid[format_position_to_str(position)]["mine"] = None # returns chosen tiles data def get_tile(self, position): return self.grid[format_position_to_str(position)] # returns chosen mines data def get_mine(self, position): return self.grid[format_position_to_str(position)]["mine"] # returns the grid field def get_grid(self): return self.grid # clears the grid field def clear_grid(self): # clearing grid dict self.grid.clear() # re-setting the agent's initial state self.grid["agents_initial_state"] = { "position": format_position_to_str(self.agents_initial_position), "direction": self.agents_initial_direction } # loads a grid from a file and overwrites the grid field def load_from_a_file(self, file_path): # opening a file for reading with open(file_path, 'r') as input_file: # overwriting the grid field with the grid stored in a file self.grid = json.load(input_file) # saves the current grid field to a file def save_to_a_file(self, file_path, access_mode): # opening a file with a given access mode (w - write / a - append) with open(file_path, access_mode) as output_file: # saving the grid to the file json.dump(self.grid, output_file, indent=2, sort_keys=True) # edits a grid in a file. doesn't delete data, only overwrites and adds new entries def edit_a_file(self, file_path): # opening a file for reading with open(file_path, "r") as input_file: # loading data that was stored in the file previously previous_data = json.load(input_file) # creating and updating a new grid using it's own grid field new_grid = previous_data new_grid.update(self.grid) # opening the file for writing with open(file_path, "w") as output_file: # saving the newly created grid json.dump(new_grid, output_file, indent=2, sort_keys=True) # STATIC "PUBLIC" FUNCTIONS # creates a Mine (Standard or Time or Chained) instance from mine dict data # doesn't link chained mines, since it has no actual access to a grid def create_a_mine(mine_dict, position): # initializing a mine with no value mine = None # if mine doesn't exist - returning None if mine_dict is None: return mine # formatting position to right format - in case it's not position = format_position_to_tuple(position) # if mine's type is "standard" - creating standard mine if mine_dict["mine_type"] == "standard": mine = StandardMine(position) # if mine's type is "time" - creating time mine elif mine_dict["mine_type"] == "time": timer_value = mine_dict["timer"] mine = TimeMine(position, timer_value) # if mine's type is "chained" - creating chained mine (no successors assigned yet) elif mine_dict["mine_type"] == "chained": mine = ChainedMine(position) return mine # creates a Tile instance with a mine (if a mine exists) from tile dict data def create_a_tile(tile_dict, position): # formatting position to right format - in case it's not position = format_position_to_tuple(position) # getting tile's parameters color = tile_dict["color"] mine = create_a_mine(tile_dict["mine"], position) # creating and returning a tile with the parameters set above return Tile(position, color, mine) # returns a list of tuples containing chained mine's position and it's predecessors position # data is in format [(chained_mine_position, its_predecessors_position), ...] def get_chained_mine_and_its_predecessor_pairs(minefield_dictionary): predecessors = list() # iterate for each key in the grid field for key in minefield_dictionary.keys(): # if a chained mine with predecessor exists - adding it's and it's predecessors positions as a tuple to a list if key != "agents_initial_state" and minefield_dictionary[key]["mine"] is not None\ and minefield_dictionary[key]["mine"]["mine_type"] == "chained"\ and minefield_dictionary[key]["mine"]["predecessor"] is not None: # getting the chained mines and it's predecessors positions this_mines_position = tuple(int(i) for i in key.split(',')) its_predecessors_position = \ tuple(int(i) for i in minefield_dictionary[key]["mine"]["predecessor"].split(',')) # adding the positions to the list as a tuple predecessors.append((this_mines_position, its_predecessors_position)) return predecessors # changes position from str or tuple format to str format def format_position_to_str(position): # if mine's position is in "row,column" format - that means position parameter is already in good format if isinstance(position, str): pass # if mine's position is in (row:int, column:int) - creating string from tuple elif isinstance(position, tuple) and list(map(type, position)) == [int, int]: row, column = position return str(row) + ',' + str(column) # if mine's position is not in any of 2 formats above - returning None - unsupported format else: return None return position # changes position from str or tuple format to tuple format def format_position_to_tuple(position): # if mine's position is in "row,column" format - getting parameters from string if isinstance(position, str): position = tuple(int(i) for i in position.split(',')) # if mine's position is in (row:int, column:int) - that means position parameter is already in good format elif isinstance(position, tuple) and list(map(type, position)) == [int, int]: pass # if mine's position is not in any of 2 formats above - returning None - unsupported format else: return None return position # AUXILIARY "PRIVATE" FUNCTIONS # auxiliary function that returns random attribute values based on their type def _get_random_attribute_values(attr_type, grid_dimensions): num_of_rows, num_of_columns = grid_dimensions if attr_type == int: # temporary solution return random.randint(num_of_rows + num_of_columns, (2 * num_of_rows + num_of_columns)) else: return None