Projekt_Sztuczna_Inteligencja/json_generator.py

330 lines
13 KiB
Python

import json
import random
import project_constants as const
# import tile class
from tile import Tile
# import mine models
from mines.mine_models.standard_mine import StandardMine
from mines.mine_models.time_mine import TimeMine
from mines.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