Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Commit

Permalink
Documented level generation and map generation. Introducing a level s…
Browse files Browse the repository at this point in the history
…hould be easy now as long as the Django part has the right migrations.
  • Loading branch information
danalex97 committed Jul 25, 2017
1 parent 55bb341 commit c2b82ef
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 23 deletions.
71 changes: 61 additions & 10 deletions aimmo-game/simulation/custom_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,28 @@
from simulation.pickups import InvulnerabilityPickup
from simulation.pickups import DamagePickup

""" Custom level generation. TODO: document @ custom_map, map_generator """

class BaseGenerator(object):
"""
A map generator that exposes a game state and a check for level completion.
API:
- contructor(setting)
- a set of basic settings that the map uses at generation
- see DEFAULT_LEVEL_SETTINGS in simulation.world_map
- get_game_state(avatar_manager)
- exposes a game state used by the turn manager daemon
- for details see GameState
- check_complete(game_state)
- function to check if a map is "complete"
- the turn manager runs the action for each avatar, then runs check_complete afterwards
@abstract
- get_map
- returns the generated map
Important: for the moment level configurations are found directly by their name by looking into
the module map_generator - look in service.py for:
generator = getattr(map_generator, settings['GENERATOR'])(settings)
"""
__metaclass__ = abc.ABCMeta

def __init__(self, settings):
Expand All @@ -34,6 +53,11 @@ def get_map(self):
pass

class EmptyMapGenerator(BaseGenerator):
"""
Generates empty maps
- get_map_by_corners
- get_map - generates a map with center in (0, 0)
"""
def __init__(self, settings):
self.height = self.settings['START_HEIGHT']
self.width = self.settings['START_WIDTH']
Expand Down Expand Up @@ -68,22 +92,35 @@ def get_corners(height, width):
return EmptyMapGenerator.get_map_by_corners(self.settings, get_corners(self.height, self.width))

class BaseLevelGenerator(BaseGenerator):
"""
BaseGenerator with default settings.
"""
__metaclass__ = abc.ABCMeta

def __init__(self, *args, **kwargs):
super(BaseLevelGenerator, self).__init__(*args, **kwargs)
self.settings.update(DEFAULT_LEVEL_SETTINGS)

class TemplateLevelGenerator(BaseLevelGenerator):
__metaclass__ = abc.ABCMeta

def __init__(self, *args, **kwargs):
super(TemplateLevelGenerator, self).__init__(*args, **kwargs)
self.settings.update(DEFAULT_LEVEL_SETTINGS)

################################################################################

class Decoder():
"""
See @JsonLevelGenerator and @Levels first.
A Decorer is a class that receives a Json formatted as in levels/models
and decodes it, altering the state of the world_map.
Decoders are used to translate a Level from the JSON format to the internal
state of a map(i.e. a WorldMap).
An example of a JSon format is:
{
"code": "1",
"id" : "5",
"x" : "3",
"y" : "3"
}
"""
__metaclass__ = abc.ABCMeta

def __init__(self, code):
Expand Down Expand Up @@ -116,13 +153,27 @@ def decode(self, json, world_map):

################################################################################

class JsonLevelGenerator(TemplateLevelGenerator):
class JsonLevelGenerator(BaseLevelGenerator):
"""
Workflow:
- setup the metadata: map dimensions, etc.
- register the json that represents the map
- register the decoders that tranform the jsons into WorldMap objects
- decode the map applying the decoder to each of the jsons
All the levels can be found in json format in levels.LEVELS.
To register a level extend this class.
"""
def _setup_meta(self):
# Used so that the map dimension does not increase automatically
self.settings["TARGET_NUM_CELLS_PER_AVATAR"] = -1000

# Finds the json with metaiformation
for element in self.json_map:
if element["code"] == "meta":
self.meta = element

# Sets the empty map to the dimensions of the given level
self.world_map = EmptyMapGenerator.get_map_by_corners(
self.settings,
(0, self.meta["rows"] - 1, 0, self.meta["cols"] - 1))
Expand Down
6 changes: 6 additions & 0 deletions aimmo-game/simulation/levels/levels.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
from pprint import pprint

class RawLevelGenerator():
"""
Builder that is used to expose json formatted levels.
See @parsers for details on level generation.
To see the JSON format of the levels run this file.
"""
def __init__(self):
pass

Expand Down
36 changes: 36 additions & 0 deletions aimmo-game/simulation/levels/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,40 @@ def call_method(o, name):
return x

class Parser():
"""
The parser gets a level formatted as a 2D grid from numbers and transforms each number
into a json representing that particular object.
A *map* is a *.txt file composed out of numbers. Each numbers represent a cell in the
grid that will be eventually generated.
A *model* is an array of jsons. Each json has an associated code. By that associated code,
the numbers in the *map* get translated into an json. To see how the final exported version
of a map looks like, run levels.py.
A *transform* is an instance of a class that can be called inside a model. A function can be
called by prepending "class:" before the class name and function name.
(e.g. class:CellTransform.get_x)
API:
- parse_model
- gets the model name as a string and parsers the model from the folder models
- parse_map:
- changes the parser's associated map with map at the given path
- register model/s
- adds a new model to the model list
- register transform
- register an instance of a transform, so it can be used from *.json file
- map_apply_transfroms
- transforms a map formated as a list of numbers into a map formatted as list of jsons
@abstract
- register_transforms
- register all transforms that can be used by the parser
"""

__metaclass__ = abc.ABCMeta

def __init__(self):
Expand Down Expand Up @@ -50,6 +84,7 @@ def register_transform(self, transform):
def register_transforms():
pass

# helper function for map_apply_transforms
def feed_string(self, input_str):
if isinstance(input_str, unicode):
input_str = str(input_str)
Expand All @@ -64,6 +99,7 @@ def feed_string(self, input_str):
else:
return input_str

# helper function for map_apply_transforms
def feed_json(self, code):
def populate(json, model):
if isinstance(model, list):
Expand Down
29 changes: 20 additions & 9 deletions aimmo-game/simulation/levels/transforms.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import hashlib

class CellTransform():
def __init__(self, x, y):
self.x = x
self.y = y
"""
A transform is a function callable from objects.json. E.g.:
{
"code": "1",
"id" : "class:CellTransform.compute_id",
"x" : "class:CellTransform.get_x",
"y" : "class:CellTransform.get_y"
}
def compute_id(self):
return hash(str(self.x) + ":" + str(self.y))
A transform has to be registered to a parser.
"""
def __init__(self, x, y):
self.x = x
self.y = y

def get_x(self):
return self.x
def compute_id(self):
return hash(str(self.x) + ":" + str(self.y))

def get_y(self):
return self.y
def get_x(self):
return self.x

def get_y(self):
return self.y
31 changes: 28 additions & 3 deletions aimmo-game/simulation/map_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,18 @@

LOGGER = logging.getLogger(__name__)

""" Level generation. TODO: document @ custom_map, map_generator """

class Main(BaseGenerator):

"""
Main Level generator used by the map creation service from the Django server.
Custom level generators(see package levels) can be found in @custom_map.
To read more about map generators, read documentation in custom_map.
Obstacles are filled according to the obstacle ratio.
Once an obstacle is added we ensure that each habitable cell can reach each other,
thus the map will be connex and each generated avatar can reach others.
"""
def get_map(self):
height = self.settings['START_HEIGHT']
width = self.settings['START_WIDTH']
Expand Down Expand Up @@ -63,6 +72,10 @@ def pairwise(iterable):


def _all_habitable_neighbours_can_reach_each_other(cell, world_map):
"""
Helper function used by Main map generator. It ensures that each habitable cell can
reach each other.
"""
neighbours = get_adjacent_habitable_cells(cell, world_map)

assert len(neighbours) >= 1
Expand All @@ -73,7 +86,10 @@ def _all_habitable_neighbours_can_reach_each_other(cell, world_map):


def get_shortest_path_between(source_cell, destination_cell, world_map):

"""
Helper function. Uses A* to find the a shortest path between two cells.
The chosen admisible heuristic function is the manhattan function.
"""
def manhattan_distance_to_destination_cell(this_branch):
branch_tip_location = this_branch[-1].location
x_distance = abs(branch_tip_location.x - destination_cell.location.x)
Expand Down Expand Up @@ -103,6 +119,12 @@ def manhattan_distance_to_destination_cell(this_branch):


def get_random_edge_index(world_map, rng=random):
"""
Utility function used to get a tuple (x, y) on the edge of the map.
Note the function returns a tuple rather than a Location.
This function is also used by the tests.
"""
num_row_cells = world_map.num_rows - 2
num_col_cells = world_map.num_cols - 2
num_edge_cells = 2*num_row_cells + 2*num_col_cells
Expand Down Expand Up @@ -138,6 +160,9 @@ def get_adjacent_habitable_cells(cell, world_map):


class PriorityQueue(object):
"""
Class used in the A* implementation.
"""
def __init__(self, key, init_items=tuple()):
self.key = key
self.heap = [self._build_tuple(i) for i in init_items]
Expand Down
2 changes: 1 addition & 1 deletion aimmo-game/simulation/world_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def map_feature_dict(map_feature):
for cell in avatar_view.cells_to_reveal:
# There is an avatar.

if not cell.avatar is None:git
if not cell.avatar is None:
self.create_player(player_dict(cell.avatar))
# Cell is an obstacle.
if not cell.habitable:
Expand Down

0 comments on commit c2b82ef

Please sign in to comment.