Source code for miniworlds.worlds.tiled_world.tiled_world

from collections import defaultdict
from typing import Union, Dict, Tuple, Optional, cast, List
import pygame

import miniworlds.worlds.manager.camera_manager as world_camera_manager

import miniworlds.worlds.world as world
import miniworlds.appearances.background as background_mod
import miniworlds.worlds.tiled_world.tiled_world_camera_manager as tiled_camera_manager
import miniworlds.worlds.tiled_world.corner as corner_mod
import miniworlds.worlds.tiled_world.edge as edge_mod
import miniworlds.worlds.tiled_world.tile as tile_mod
import miniworlds.worlds.tiled_world.tile_factory as tile_factory
import miniworlds.worlds.tiled_world.tiled_world_connector as tiled_world_connector
import miniworlds.actors.actor as actor_mod

import miniworlds.base.exceptions as miniworlds_exception
from miniworlds.base.exceptions import TiledWorldTooBigError


[docs] class TiledWorld(world.World): """from typing A TiledWorld is a World where each Actor is placed in one Tile. With Tiled World, you can realize RPGs and Boardgames. .. image:: /_images/rpg.jpg :alt: TiledWorld Each Actor on a TiledWorld can be placed on a Tile, on a Corner between Tiles or on an Edge between Tiles. Examples: Create Actor on Tile, Corner and Edge: .. code-block:: from miniworlds import * world = TiledWorld(6, 3) world.grid = True last_corner = None tile = Tile((1,1)) t1 = Actor() t1.center = tile.position t1.fill_color = (255,255,255) corner = Corner((3,1), "nw") t2 = Actor() t2.center = corner.position t2.fill_color = (255,0,0) edge = Edge((5,1), "w") t3 = Actor() t3.center = edge.position t3.fill_color = (0,0,255) t3.size = (0.2,1) t3.direction = edge.angle world.run() .. image:: /_images/tile_corner_edge.png :alt: Placing Actors on a Tile, on a Corner or in a Edge """
[docs] def __init__(self, x: int = 20, y: int = 16, tile_size: int = 40, empty=False): """Initializes the TiledWorld Args: view_x: The number of columns view_y: The number of rows empty: The world has no tiles, edges, and corners. They must be created manually """ self._tile_size: int = tile_size "TiledWorld.tile_size Defines the size of a single tile (All Tiles are square)" self.default_actor_speed: int = 1 self.empty = empty self.tile_factory = self._get_tile_factory() self.tiles: defaultdict = defaultdict() self.corners: defaultdict = defaultdict() self.edges: defaultdict = defaultdict() if x > 1000 or y > 1000: raise TiledWorldTooBigError(x, y, 40) super().__init__(x=x, y=y) self._tile_size = 40 self.step = 20 self.dynamic_actors_dict: defaultdict = defaultdict( list ) # the dict is regularly updated self.dynamic_actors: "pygame.sprite.Group" = ( pygame.sprite.Group() ) # Set with all dynamic actors self.static_actors_dict: defaultdict = defaultdict(list) self.actors_fixed_size = True self.rotatable_actors = True self.setup_world() self.is_tiled = True
def _get_tile_factory(self): return tile_factory.TileFactory()
[docs] def clear_tiles(self): """Removes all tiles, corners and edges from World Instead of clearing the world, you can add the parameter empty to World to create a new World from scratch. Examples: Clear and re-create world: .. code-block:: python from miniworlds import * world = HexWorld(8, 8) @world.register def on_setup(self): self.clear_tiles() center = HexTile((4, 4)) for x in range(self.columns): for y in range(self.rows): if center.position.distance((x, y)) < 2: tile = self.add_tile_to_world((x, y)) tt = Actor() t.center = tile.position world.run() Create a new world from scratch .. note:: This variant is faster, because Tiles are not created twice .. code-block:: python from miniworlds import * world = HexWorld(8, 8, empty=True) @world.register def on_setup(self): center = HexTile((4, 4)) for x in range(self.columns): for y in range(self.rows): if center.position.distance((x, y)) < 2: tile = self.add_tile_to_world((x, y)) tile.create_actor() world.run() """ self.tiles.clear() self.corners.clear() self.edges.clear()
@staticmethod def _get_camera_manager_class(): return tiled_camera_manager.TiledCameraManager
[docs] def setup_world(self): """In this method, corners and edges are created.""" if not self.empty: self._setup_tiles() self._setup_corners() self._setup_edges()
def _templates(self): """Returns Classes for Tile, Edge and Corner""" return tile_mod.Tile, edge_mod.Edge, corner_mod.Corner
[docs] def add_tile_to_world(self, position): tile_cls, edge_cls, corner_cls = self._templates() tile_pos = position tile = tile_cls(tile_pos, self) self.tiles[tile.position] = tile return tile
[docs] def add_corner_to_world(self, position, direction): tile_cls, edge_cls, corner_cls = self._templates() corner = corner_cls(position, direction, self) corner_pos = corner.position if corner_pos not in self.corners: self.corners[corner_pos] = corner else: self.corners[corner_pos].merge(corner) return self.corners[corner_pos]
[docs] def add_edge_to_world(self, position, direction): edge_cls = self.tile_factory.edge_cls edge = edge_cls(position, direction, self) edge_pos = edge.position if edge_pos not in self.edges: self.edges[edge_pos] = edge else: self.edges[edge_pos].merge(edge) return self.edges[edge_pos]
def _setup_tiles(self): """Adds Tile to World for each WorldPosition""" for x in range(self.world_size_x): for y in range(self.world_size_y): self.add_tile_to_world((x, y)) def _setup_corners(self): """Add all Corner to World for each Tile. Merges identical corners for different Tiles """ tile_cls = self.tile_factory.tile_cls for position, tile in self.tiles.items(): for direction in tile_cls.corner_vectors: self.add_corner_to_world(tile.position, direction) def _setup_edges(self): """Add all Edges to World for each Tile Merges identical edges for different tiles """ tile_cls = self.tile_factory.tile_cls for position, tile in self.tiles.items(): for direction in tile_cls.edge_vectors: self.add_edge_to_world(tile.position, direction)
[docs] def get_tile(self, position: Tuple[float, float]): """Gets Tile at Position. Raises TileNotFoundError, if Tile does not exists. Examples: Get tile from actor: .. code-block:: python tile = world.get_tile(actor.position) Full example: .. code-block:: python from miniworlds import * world = TiledWorld(6, 3) world.grid = True last_corner = None tile = Tile((1,1)) t1 = Actor() t1.center = tile.position t1.fill_color = (255,255,255) tile=world.get_tile((1,1)) assert(tile.get_actors()[0] == t1) world.run() :param position: Position on World :return: Tile on Position, if position exists """ if self.is_tile(position): position = position return self.tiles[position] else: raise miniworlds_exception.TileNotFoundError(position)
[docs] def detect_actors( self, position: Union[Tuple[float, float], Tuple[float, float]] ) -> List["actor_mod.Actor"]: return cast( List["actor_mod.Actor"], [actor for actor in self.actors if actor.position == position], )
[docs] def get_actors_from_pixel( self, position: Union[Tuple[float, float], Tuple[float, float]] ) -> List["actor_mod.Actor"]: tile = tile_mod.Tile.from_pixel(position) return self.detect_actors(tile.position)
[docs] def get_corner( self, position: Tuple[float, float], direction: Optional[str] = None ): """Gets Corner at Position. Raises CornerNotFoundError, if Tile does not exists. Examples: Get corner from actor: .. code-block:: python corner = world.get_corner(actor.position) Get corner from world-position and direction .. code-block:: python from miniworlds import * from miniworlds import * world = TiledWorld(6, 3) world.grid = True last_corner = None corner = Corner((3,1), "nw") t2 = Actor() t1.center = corner.position t2.fill_color = (255,0,0) corner=world.get_corner((3,1),"nw") assert(corner.get_actors()[0] == t2) world.run() Args: position: Position on World direction: if direction is not None, position is interpreted as tile-world-position Returns next corner, if position exists """ corner_cls = self.tile_factory.corner_cls if direction is not None: position = corner_cls(position, direction).position if self.is_corner(position): return self.corners[(position[0], position[1])] else: raise miniworlds_exception.CornerNotFoundError(position)
[docs] def get_edge(self, position, direction: Optional[str] = None): """Gets Edge at Position. Raises EdgeNotFoundError, if Tile does not exists. Examples: Get edge from actor: .. code-block:: python tile = world.get_edge(actor.position) Get edge from world-position and direction .. code-block:: python from miniworlds import * world = TiledWorld(6, 3) world.grid = True last_corner = None edge=world.get_edge((5,1),"w") assert(edge.get_actors()[0] == t3) world.run() :param position: Position on World :return: Edge on Position, if position exists """ edge_cls = self.tile_factory.edge_cls if direction is not None: position = edge_cls(position, direction).position if self.is_edge(position): return self.edges[(position[0], position[1])] else: raise miniworlds_exception.TileNotFoundError(position)
@staticmethod def _get_world_connector_class(): return tiled_world_connector.TiledWorldConnector
[docs] def detect_position(self, position: Tuple[float, float]) -> bool: """Returns True if a position is on the world.""" return super().detect_position(position)
[docs] def borders(self, value: Union[tuple, Tuple[float, float], pygame.Rect]) -> list: """ Returns the World's borders, if actor is near a Border. """ position = value return self.get_borders_from_position(position)
def _update_actor_positions(self): """Updates the dynamic_actors_dict. All positions of dynamic_actors_dict are updated by reading the dynamic_actors list. This method is called very often in self.sensing_actors - The dynamic_actors list should therefore be as small as possible. Other actors should be defined as static. """ self.dynamic_actors_dict.clear() for actor in self.dynamic_actors: x, y = actor.position[0], actor.position[1] self.dynamic_actors_dict[(x, y)].append(actor)
[docs] def detect_actors_at_position(self, position): """Sensing actors at same position""" self._update_actor_positions() # This method can be a bottleneck! actor_list = [] if self.dynamic_actors_dict[position[0], position[1]]: actor_list.extend(self.dynamic_actors_dict[(position[0], position[1])]) if self.static_actors_dict[position[1], position[1]]: actor_list.extend(self.static_actors_dict[(position[0], position[1])]) actor_list = [actor for actor in actor_list] return actor_list
[docs] def detect_actor_at_position(self, position): """Sensing single actor at same position Faster than sensing_actors, but only the first found actor is recognized. """ actor_list = self.detect_actors_at_position(position) if actor_list is None or actor_list == []: return None else: return actor_list[0]
@property def grid(self): """Displays grid overlay on background.""" return self.background.grid @grid.setter def grid(self, value): self.background.grid = value
[docs] def draw_on_image(self, image, position): position = self.to_pixel(position) self.background.draw_on_image(image, position, self.tile_size, self.tile_size)
[docs] def get_from_pixel(self, position): """Gets world position from pixel coordinates""" if position[0] > self.camera.height or position[1] > self.camera.height: return None else: return self.get_tile_from_pixel(position).position
[docs] def get_tile_from_pixel(self, position): """Gets nearest Tile from pixel""" tile_cls = self.tile_factory.tile_cls return tile_cls.from_pixel(position, self)
[docs] def get_edge_points(self) -> Dict[Tuple, Tuple[float, float]]: edge_points = dict() for position, edge in self.edges.items(): edge_points[position] = edge.to_pixel() return edge_points
[docs] def get_corner_points(self) -> Dict[Tuple, Tuple[float, float]]: corner_points = dict() for position, corner in self.corners.items(): corner_points[position] = corner.to_pixel() return corner_points
[docs] def is_edge(self, position): """Returns True, if position is a edge.""" if position in self.edges: return True else: return False
[docs] def is_corner(self, position): """Returns True, if position is a corner.""" if position in self.corners: return True else: return False
[docs] def is_tile(self, position): """Returns True, if position is a tile.""" if position in self.tiles: return True else: return False
[docs] def to_pixel(self, position, size=(0, 0), origin=(0, 0)): """Converts WorldPosition to pixel coordinates""" x = position[0] * self.tile_size + origin[0] y = position[1] * self.tile_size + origin[1] return x, y
[docs] def set_columns(self, value: int): self._columns = value self.camera.width = value #* self.tile_size self.world_size_x = value self.setup_world()
[docs] def set_rows(self, value: int): self._rows = value self.camera.height = value #* self.tile_size self.world_size_y = value self.setup_world()
@property def columns(self) -> int: return self.camera.world_size_x @columns.setter def columns(self, value: int): self.set_columns(value) @property def rows(self) -> int: return self.camera.world_size_y @rows.setter def rows(self, value: int): self.set_rows(value) @property def tile_size(self) -> int: """Tile size of each tile, if world has tiles Returns: The tile-size in pixels. """ return self._tile_size @tile_size.setter def tile_size(self, value: int): self.set_tile_size(value)
[docs] def set_tile_size(self, value): self._tile_size = value self.camera.reload_camera() self.background.set_dirty("all", background_mod.Background.RELOAD_ACTUAL_IMAGE)