Source code for miniworldmaker.tokens.token

from __future__ import annotations

from typing import Union, List, Tuple, Optional, cast

import miniworldmaker.appearances.appearance as appearance
import miniworldmaker.appearances.costume as costume_mod
import miniworldmaker.appearances.costumes_manager as costumes_manager
import miniworldmaker.dialogs.ask as ask
import miniworldmaker.positions.direction as board_direction
import miniworldmaker.positions.position as board_position
import \
    miniworldmaker.tokens.managers.token_boardsensor as token_boardsensor  # - @todo not imported because of circular import
import miniworldmaker.tokens.managers.token_position_manager as token_position_manager
import miniworldmaker.tokens.token_base as token_base
import miniworldmaker.tools.token_inspection as token_inspection
import pygame.rect
from miniworldmaker.exceptions.miniworldmaker_exception import (
    MiniworldMakerError,
    NotImplementedOrRegisteredError,
    NoBoardError,
    RegisterError,
    NoValidBoardPositionError,
    MissingBoardSensor,
    MissingPositionManager
)


class Meta(type):
    def __call__(cls, *args, **kwargs):
        if len(args) >= 2 and type(args[0]) == int and type(args[1]) == int:
            first = (args[0], args[1])
            last_args = [args[n] for n in range(2, len(args))]
            args = [first] + last_args
        instance = type.__call__(cls, *args, **kwargs)  # create a new Token
        _token_connector = instance.board.get_token_connector(instance)
        _token_connector.add_token_to_board()
        return instance


[docs]class Token(token_base.BaseToken): """Tokens are objects on your board. Tokens can move around the board and have sensors to detect other tokens. The appearance of a token is determined by its costume. Examples: Create a token: .. code-block:: python from miniworldmaker import * board = Board() board.size = (100,60) Token(position=(10, 10)) board.run() Output: .. image:: ../_images/token1.png :width: 100px :alt: Create a token Create a token with an image: .. code-block:: python from miniworldmaker import * board = Board(100,60) token = Token((10, 10)) token.add_costume("images/player.png") board.run() Output: .. image:: ../_images/token2.png :width: 100px :alt: Create a Token with image .. code-block:: python import miniworldmaker class MyToken(miniworldmaker.Token): def on_setup(self): self.add_costume("images/player.png") board = Board(100,60) my_token = MyToken(position = (40,130)) board.run() Output: .. image:: ../_images/token1.png :width: 100px :alt: Create a token Create a Token at current mouse position: .. code-block:: python from miniworldmaker import * board = Board() @board.register def act(self): Token(self.get_mouse_position()) board.run() .. image:: ../_images/token3.png :width: 100px :alt: Create a token at mouse position See Also: * See: :doc:`Token <../api/token>` * See: :doc:`Shapes <../api/token.shape>` * See: :doc:`TextTokens and NumberTokens <../api/token.texttoken>` """ token_count: int = 0 class_image: str = "" def __init__(self, position: Optional[Union[Tuple, "board_position.Position"]] = (0, 0), board=None): self._board_sensor: "token_boardsensor.Boardsensor" = None self._position_manager: "token_position_manager.TokenPositionManager" = None self._costume_manager: "costumes_manager.CostumesManager" = None super().__init__(board) if position is None: self._position = (0, 0) position = (0, 0) elif type(position) in [int, float]: raise NoValidBoardPositionError(position) else: self._position = position self._collision_type: str = "mask" self._dirty = 0 self._layer: int = 0 self._inner = 0 self._size = (0, 0) self._static = False self.children = [] # self._position: Union["board_position.Position", "board_position.PositionBase"] = position self.token_id: int = Token.token_count + 1 self._has_position_manager = False self._has_board_sensor = False self._has_costume_manager = False self._is_acting: bool = True # is act method called? self._is_deleted = False self.is_focusable = False self.has_focus = False self._parent = None # For tokens in container self.children: List["Token"] = [] try: self.board.get_token_connector( self).init_managers(position) except AttributeError: raise AttributeError("Token could not be created on a Board - Did you created a board instance before?") try: self._position = board_position.Position.create(position) except NoValidBoardPositionError: raise NoValidBoardPositionError(position) if not self.board: raise NoBoardError() pygame.sprite.DirtySprite.__init__(self) Token.token_count += 1 self.speed: int = 1 self.ask: "ask.Ask" = ask.Ask(self.board) self._dirty = 1
[docs] @classmethod def create_on_board(cls, board): """Creates a token to a specific board overwritten in subclasses """ return cls((0, 0), board)
@property def collision_type(self) -> str: """collision_type specifies how collisions should be checked: * `default`: tile for TiledBoards, 'mask' for PixelBoards * `tile`: Are tokens on the same tile? (only TiledBoard) * `rect`: Are tokens colliding when checking their bounding - boxes? (Only PixelBoard) * `static-rect`: Are tokens colliding when checking circle with radius = bounding-box-radius.(Only PixelBoard) * `circle`: Are tokens colliding when checking circle with radius = bounding-box-radius.(Only PixelBoard) * `mask`: Are tokens colliding when checking if their image masks are overlapping. """ if self._collision_type == "default": return "mask" else: return self._collision_type @property def sticky(self): return self.position_manager.sticky @sticky.setter def sticky(self, value): self.position_manager.sticky = value @collision_type.setter def collision_type(self, value: str): self._collision_type = value @property def layer(self) -> int: """defines layer the token is drawn, if multiple tokens overlap.""" return self._layer @layer.setter def layer(self, value: int): self._layer = value self.board._tokens.change_layer(self, value) # changes layer in DirtySpriteGroup. @property def last_position(self) -> "board_position.Position": """Token position in last frame Can be used to track changes. """ return self.position_manager.last_position @property def last_direction(self) -> int: return self.position_manager.last_direction
[docs] @classmethod def from_center(cls, center_position: "board_position.Position"): """ Creates a token with center at center_position Args: center_position: Center of token """ obj = cls(position=(0, 0)) # temp position obj.center = center_position # pos set to center return obj
@property def costume_count(self) -> int: """Returns number of costumes of token, 0 if token has no costume Examples: Add costume and count costumes .. code-block:: python from miniworldmaker import * board = Board() token = Token() assert token.costume_count == 0 token.add_costume((255,0,0,0)) assert token.costume_count == 1 board.run() Returns: int: _description_ """ return self.costume_manager.length() @property def is_flipped(self) -> bool: """ If a token is flipped, it is mirrored via the y-axis. You can use this property in 2d-plattformers to change the direction of token. .. note:: It may be necessary to set ``is_rotatable = True`` Examples: Flip a costume after 100 frames. .. code-block:: from miniworldmaker import * board = Board(100,100) token = Token() token.add_costume("images/alien1.png") token.height= 400 token.width = 100 token.is_rotatable = False @token.register def act(self): if self.board.frame % 100 == 0: if self.is_flipped: self.is_flipped = False else: self.is_flipped = True board.run() Output: .. raw:: html <video loop autoplay muted width=200> <source src="../_static/flipalien.webm" type="video/webm"> Your browser does not support the video tag. </video> Returns: True, if token is flipped """ return self.costume.is_flipped @is_flipped.setter def is_flipped(self, value: bool): self.costume.is_flipped = value
[docs] def flip_x(self) -> int: """Flips the actor by 180° degrees. The costume is flipped and the token's direction changed by 180 degrees. .. image:: ../_images/flip_x.png Examples: Flip a token in Example flipthefish.py .. code-block:: python from miniworldmaker import * board=TiledBoard() board.columns = 4 board.rows = 1 board.add_background("images/water.png") fish = Token() fish.border = 1 fish.add_costume("images/fish.png") fish.direction = "right" fish.orientation = -90 @fish.register def act(self): self.move() @fish.register def on_not_detecting_board(self): self.move_back() self.flip_x() board.run() Output: .. raw:: html <video loop autoplay muted width=200> <source src="../_static/flipthefish.webm" type="video/webm"> Your browser does not support the video tag. </video> """ return self.position_manager.flip_x()
[docs] def add_costume(self, source: Union[None, Tuple, str, List] = None) -> "costume_mod.Costume": """Adds a new costume to token. The costume can be switched with self.switch_costume(index) Args: source: Path to the first image of new costume or Tuple with color-value Examples: Add first costume from image: .. code-block:: python from miniworldmaker import * board = Board((100,60)) token = Token((10,10)) costume = token.add_costume("images/player.png") board.run() Output: .. image:: ../_images/add_costume1.png :width: 100px :alt: Create Token with image as costume Add first costume from color: .. code-block:: python from miniworldmaker import * board = Board((100,60)) token = Token((10,10)) costume = token.add_costume((255,255,0)) board.run() Output: .. image:: ../_images/add_costume2.png :width: 100px :alt: Create Token with image as costume Create two costumes and switch between costumes .. code-block:: python from miniworldmaker import * board = Board((100,60)) token = Token((10,10)) board.speed = 30 costume1 = token.add_costume((255,255,0)) costume2 = token.add_costume((255,0,255)) @token.register def act(self): if self.costume == costume1: self.switch_costume(costume2) else: self.switch_costume(costume1) board.run() Output: .. image:: ../_images/add_costume3.png :width: 100% :alt: Create multiple costumes and switch between costumes Returns: The new costume. """ if not source or type(source) in [str, tuple]: return self.costume_manager.add_new_appearance(source) elif type(source) == list: return cast("costume.Costume", self.costume_manager.add_new_appearance_from_list(source)) else: raise MiniworldMakerError(f"Wrong type for appearance. Expected: list, tuple or str, got {type(source)}")
[docs] def add_costumes(self, sources: list) -> "costume_mod.Costume": """Adds multiple costumes """ return self.costume_manager.add_new_appearances(sources)
[docs] def remove_costume(self, source: Union[int, "costume_mod.Costume"] = None): """Removes a costume from token Args: source: The index of the new costume or costume-object. Defaults to actual costume """ if source is None: source = self.costume return self.costume_manager.remove_appearance(source)
[docs] def switch_costume(self, source: Union[int, "appearance.Appearance"]) -> "costume_mod.Costume": """Switches the costume of token Args: source: Number of costume or Costume object Examples: Switch a costume: .. code-block:: python from miniworldmaker import * board = Board(100,60) t = Token() costume =t1.add_costume("images/1.png") t.add_costume("images/2.png") t.switch_costume(1) @timer(frames = 40) def switch(): t1.switch_costume(0) board.run() Returns: The new costume """ return self.costume_manager.switch_costume(source)
[docs] def set_costume(self, costume: Union[str, tuple, int, "appearance.Appearance"]): if type(costume) == int or isinstance(costume, appearance.Appearance): self.switch_costume(costume) elif type(costume) in [str, tuple]: costume = self.add_costume(costume) self.switch_costume(costume)
[docs] def reset_costumes(self): self.costume_manager.reset()
[docs] def set_background_color(self, color: tuple): self.set_costume(color)
[docs] def next_costume(self): """Switches to the next costume of token Returns: The new costume """ self.costume_manager.next_costume()
@property def costume(self) -> costume_mod.Costume: """Gets the costume of token """ if hasattr(self, "costume_manager") and self.costume_manager is not None: return self.costume_manager.get_actual_appearance() @costume.setter def costume(self, value): self.costume_manager.appearance = value @property def costumes(self) -> "costumes_manager.CostumesManager": """Gets the costume manager The costume manager can be iterated to get all costumes """ return self.costume_manager @property def orientation(self) -> int: return self.costume.orientation @orientation.setter def orientation(self, value: int): self.costume.orientation = value @property def direction(self) -> int: """Directions are handled exactly as in the Scratch programming language, see: `Scratch Wiki <https://en.scratch-wiki.info/wiki/Direction_(value)>`_ The default direction is ``0°``. All tokens are looking ``"up"`` .. image:: /_images/movement.jpg :width: 100% :alt: Move on board **Values for Direction** * ``0°`` or ``"up"``: up * ``90°`` or ``"right"``: Move right * ``-90°`` or ``"left"``: Move left * ``180°`` or ``"down"``: Move down * ``"forward"``: Current direction Sets direction of the token. You can use an integer or a string to describe the direction Options * ``0``, ``"up"`` - Look up * ``90``, ``"right"``, - Look right * ``-90``, ``"left"``, - Look left * ``-180``, ``180``, ``"down"`` - Look down .. image:: ../_images/direction.png Examples: Move in a direction with WASD-Keys .. code-block:: python def on_key_down(self, keys): if "W" in keys: self.direction = "up" elif "S" in keys: self.direction = "down" elif "A" in keys: self.direction = "left" elif "D" in keys: self.direction = "right" self.move() Move 45°: .. code-block:: python from miniworldmaker import * board = Board(100, 100) c = Circle ((50,50), 10) @c.register def act(self): c.direction = 45 c.move() board.run() .. raw:: html <video loop autoplay muted width=400> <source src="../_static/move45.webm" type="video/webm"> Your browser does not support the video tag. </video> Move -45°: .. code-block:: python from miniworldmaker import * board = Board(100, 100) c = Circle ((50,50), 10) @c.register def act(self): c.direction = -45 c.move() board.run() .. raw:: html <video loop autoplay muted width=400> <source src="../_static/moveminus45.webm" type="video/webm"> Your browser does not support the video tag. </video> """ return self.position_manager.direction @direction.setter def direction(self, value: int): self.position_manager.direction = value @property def direction_at_unit_circle(self) -> int: """Gets the direction as value in unit circle (0° right, 90° top, 180° left...) """ return self.position_manager.dir_to_unit_circle(self.direction) @direction_at_unit_circle.setter def direction_at_unit_circle(self, value: int): """Sets the direction from unit circle Args: value: An angle in the unit circle, e.g. 0°: right, 90° top, ... """ self.direction = self.position_manager.unit_circle_to_dir(value)
[docs] def turn_left(self, degrees: int = 90) -> int: """Turns actor by *degrees* degrees left .. image:: ../_images/turn_left.png Options: * You can set the value token.is_rotatable = False if you don't want the token to be rotated. Examples: .. code-block:: python from miniworldmaker import * board = Board(100, 100) t = Token() t.add_costume("images/arrow.png") t.size = (100,100) @t.register def act(self): t.turn_left(1) board.run() Output: .. raw:: html <video loop autoplay muted width=400> <source src="../_static/turnleft.webm" type="video/webm"> Your browser does not support the video tag. </video> Args: degrees: degrees in left direction Returns: New direction """ return self.position_manager.turn_left(degrees)
[docs] def turn_right(self, degrees: Union[int, float] = 90): """Turns token by *degrees* degrees right .. image:: ../_images/turn_right.png Examples: .. code-block:: python from miniworldmaker import * board = Board(100, 100) t = Token() t.add_costume("images/arrow.png") t.size = (100,100) @t.register def act(self): t.turn_left(1) board.run() Output: .. raw:: html <video loop autoplay muted width=400> <source src="../_static/turnright.webm" type="video/webm"> Your browser does not support the video tag. </video> Options: * You can set the value token.is_rotatable = False if you don't want the token to be rotated. Args: degrees: degrees in left direction Returns: New direction """ return self.position_manager.turn_right(degrees)
[docs] def point_in_direction(self, direction: Union[str, int, float]) -> "board_direction.Direction": """Token points in given direction. You can use a integer or a string to describe the direction Args: The direction as integer or string (see options) Options * ``0``, ``"up"`` - Look up * ``90``, ``"right"``, - Look right * ``-90``, ``"left"``, - Look left * ``-180``, ``180``, ``"down"`` - Look down .. image:: ../_images/direction.png Examples: Move in a direction with WASD-Keys .. code-block:: python def on_key_down(self, keys): if "W" in keys: self.direction = "up" elif "S" in keys: self.direction = "down" elif "A" in keys: self.direction = "left" elif "D" in keys: self.direction = "right" self.move() """ return self.position_manager.point_in_direction(direction)
[docs] def point_towards_position(self, destination: Union[tuple, "board_position.BoardPosition"]) -> Union[int, float]: """ Token points towards a given position Args: destination: The position to which the actor should pointing Returns: The new direction Examples: Point towards mouse_position: .. code-block:: python def act(self): mouse = self.board.get_mouse_position() if mouse: self.point_towards_position(mouse) self.move() """ return self.position_manager.point_towards_position(destination)
[docs] def point_towards_token(self, other: "Token") -> int: """Token points towards another token. Args: other: The other token Returns: The new direction """ pos = other.get_global_rect().center return self.point_towards_position(pos)
@property def size(self) -> tuple: """Size of the token""" return self.position_manager.size @size.setter def size(self, value: tuple): self.set_size(value)
[docs] def set_size(self, value: tuple): self.position_manager.set_size(value)
@property def width(self): """The width of the token in pixels. When the width of a token is changed, the height is scaled proportionally. Examples: Create a token and scale width/height proportionally: .. code-block:: python from miniworldmaker import * board = Board(800,400) def create_token(x, y): t = Token() t.position = (x, y) t.add_costume("images/alien1.png") t.border = 1 return t t0 = create_token(0,0) t1 = create_token(50,0) t1.height = 400 t2 = create_token(300,0) t2.width = 180 board.run() .. image:: ../_images/widthheight.png :alt: Textured image """ return self.position_manager.get_size()[0] @width.setter def width(self, value): self.position_manager.set_width(value) self.on_shape_change()
[docs] def scale_width(self, value): old_width = self.size[0] old_height = self.size[1] scale_factor = value / old_width self.size = (value, old_height * scale_factor)
@property def height(self): """The height of the token in pixels. When the height of a token is changed, the width is scaled proportionally. Examples: Create a token and scale width/height proportionally: .. code-block:: python from miniworldmaker import * board = Board(800,400) def create_token(x, y): t = Token() t.position = (x, y) t.add_costume("images/alien1.png") t.border = 1 return t t0 = create_token(0,0) t1 = create_token(50,0) t1.height = 400 t2 = create_token(300,0) t2.width = 180 board.run() .. image:: ../_images/widthheight.png :alt: Textured image """ return self.position_manager.get_size()[1] @height.setter def height(self, value): self.position_manager.set_height(value) self.on_shape_change()
[docs] def scale_height(self, value): old_width = self.size[0] old_height = self.size[1] scale_factor = value / old_height self.size = (old_width * scale_factor, value)
@property def x(self) -> float: """The x-value of a token""" return self.position_manager.get_position()[0] @x.setter def x(self, value: float): self.set_position((value, self.y)) @property def y(self) -> float: """The y-value of a token""" return self.position_manager.get_position()[1] @y.setter def y(self, value: float): self.set_position((self.x, value)) @property def class_name(self) -> str: return self.__class__.__name__ @property def center_x(self): """x-value of token center-position""" return self.position_manager.center_x @property def topleft_x(self): """x-value of token topleft-position""" return self.get_global_rect().topleft[0] @property def topleft_y(self): """x-value of token topleft-position""" return self.get_global_rect().topleft[1] @property def topleft(self) -> "board_position.Position": return self.position_manager.topleft @topleft.setter def topleft(self, value: Union[Tuple, "board_position.Position"]): self.position_manager.topleft = value @property def center_y(self): """y-value of token center-position""" return self.position_manager.center_y @property def center(self) -> "board_position.Position": return self.position_manager.center @property def local_center(self): """x-value of token center-position""" return self.position_manager.local_center @center_x.setter def center_x(self, value: float): self.position_manager.center_x = value @center_y.setter def center_y(self, value: float): self.position_manager.center_y = value @center.setter def center(self, value: Union[Tuple, "board_position.Position"]): self.position_manager.center = value
[docs] def move(self, distance: int = 0): """Moves actor *distance* steps in current direction .. image:: ../_images/move.png Args: distance: Number of steps to move. If distance = 0, the actor speed will be used. Returns: The moved token Examples: if token is on the board, move forward: .. code-block:: python class Robot(Token): def act(self): if self.detecting_board(): self.move() """ return self.position_manager.move(distance)
[docs] def move_vector(self, vector): """Moves actor in direction defined by the vector Returns: The moved token """ return self.position_manager.move_vector(vector)
[docs] def move_up(self, distance: int = 1): return self.position_manager.move_in_direction("up", distance)
[docs] def move_down(self, distance: int = 1): return self.position_manager.move_in_direction("down", distance)
[docs] def move_left(self, distance: int = 1): return self.position_manager.move_in_direction("left", distance)
[docs] def move_right(self, distance: int = 1): return self.position_manager.move_in_direction("right", distance)
[docs] def move_back(self): """deprecated - use: undo_move() In next versions, this functions will move the token backwards """ return self.position_manager.undo_move()
[docs] def undo_move(self): """Undo the last move. Moves the actor to the last position and resets direction. .. image:: ../_images/move_back.png Returns: The moved token Examples: move_back when field is blocked: .. code-block:: python def on_sensing_wall(self, wall): self.undo_move() """ return self.position_manager.undo_move()
[docs] def move_towards(self, target: Union["board_position.Position", "Token"]): if isinstance (target, Token): target = target.position return self.position_manager.move_towards_position(target)
[docs] def move_in_direction(self, direction: Union[int, str, tuple, "board_direction.Direction", "board_position.Position"], distance=1): """Moves token *distance* steps into a *direction* or towards a position .. image:: ../_images/move_in_direction.png Options * 0, "up" - Look up * 90, "right", - Look right * -90, "left", - Look left * -180, 180, "down" - Look down .. image:: ../_images/direction.png Args: direction: Direction as angle distance: Senses obj "distance" steps in front of current token. Returns: The token itself """ if type(direction) in [int, str, board_direction.Direction]: return self.position_manager.move_in_direction(direction, distance) elif type(direction) == tuple or isinstance(direction, board_position.Position): return self.position_manager.move_towards_position(direction, distance) else: raise MiniworldMakerError(f"Expected direction or position, got f{type(direction)}, ({direction})")
[docs] def move_to(self, position: "board_position.Position"): """Moves token *distance* to a specific board_posiition Args: position: The position to which the actor should move. The position can be a 2-tuple (x, y) which will be converted to a board_position .. image:: ../_images/move_to.png Returns: The token itself Examples: move to (3, 2) on mouse_click .. code-block:: python def on_clicked_left(self, position): self.move_to((3,2)) """ return self.position_manager.move_to(position)
[docs] def remove(self, kill=True): """ Removes this token from board Examples: Removes robots in thecrash.py : .. code-block:: python def act(self): self.move() other = self.sensing_token(distance = 0, token_type=Robot) if other: explosion = Explosion(position=self.position) self.remove() other.remove() """ if kill is True: if hasattr(self, "board") and self.board: self.board.get_token_connector(self).delete_token() else: if hasattr(self, "board") and self.board: self.board.get_token_connector(self).remove_token_from_board()
@property def is_rotatable(self) -> bool: """Defines if the costume of a token should be rotatable. The token can still be rotated with the ``direction`` property, but its costume won't be changed .. note:: You can also use ``token.costume.is_rotatable`` Examples: Create a rotatable and a not rotatable token .. code-block:: from miniworldmaker import * board = Board() t1 = Token((100,100)) t1.add_costume("images/alien1.png") t2 = Token((200,200)) t2.add_costume("images/alien1.png") t2.is_rotatable = False @t1.register def act(self): self.move() self.direction += 1 @t2.register def act(self): self.move() self.direction += 1 board.run() Output: .. raw:: html <video loop autoplay muted width=400> <source src="../_static/rotatable.webm" type="video/webm"> Your browser does not support the video tag. </video> """ return self.costume.is_rotatable @is_rotatable.setter def is_rotatable(self, value: bool): self.costume.is_rotatable = value
[docs] def bounce_from_border(self, borders: List[str]) -> Token: """The actor "bounces" from a border. The direction is set according to the principle input angle = output angle. .. note:: You must check for borders first! Args: borders: A list of borders as strings e.g. ["left", "right"] Examples: .. code-block:: python from miniworldmaker import * import random board = Board(150, 150) token = Token((50,50)) token.add_costume("images/ball.png") token.direction = 10 @token.register def act(self): self.move() borders = self.sensing_borders() if borders: self.bounce_from_border(borders) board.run() Output: .. raw:: html <video loop autoplay muted width=240> <source src="../_static/bouncing_ball.webm" type="video/webm"> Your browser does not support the video tag. </video> Returns: The token """ return self.position_manager.bounce_from_border(borders)
[docs] def on_not_detecting_board(self): """`on_not_detecting_board` is called, when token is not on the board. Examples: Register on_not_detecting_board method: .. code-block:: @player.register def on_not_detecting_board(self): print("Warning: I'm not on the board!!!") Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_not_detecting_board())
[docs] def detect_all(self, token_filter: str = None, direction: int = 0, distance: int = 0) -> List["Token"]: """Detects if tokens are on token position. Returns a list of tokens. .. image:: ../_images/sensing_tokens.png Args: token_filter: filter by token type. Enter a class_name of tokens to look for here direction: The direction in which tokens should be detected. distance: The distance in which tokens should be detected (Start-Point is token.center) Returns: All tokens found by Sensor """ if distance == 0: return self.board_sensor.detect_tokens(token_filter) else: return self.board_sensor.detect_tokens_at(token_filter, direction, distance)
detect_tokens = detect_all #: Alias of :meth:`Token.detect_all` sensing_tokens = detect_tokens #: Alias of :meth:`Token.sensing_tokens`
[docs] def detect(self, token_filter: Union[str, type] = None, direction: int = 0, distance: int = 0) -> Union[ "Token", None]: """Senses if tokens are on token position. Returns the first found token. .. image:: ../_images/sensing_token.png Args: token_filter: filter by token type. Enter a class_name of tokens to look for heredirection: int = 0, distance: int = 0 direction: The direction in which tokens should be detected. distance: The distance in which tokens should be detected (Start-Point is token.center) Returns: First token found by Sensor Examples: The green robot pushes the yellow robot: .. code-block:: python from miniworldmaker import * board = TiledBoard(8,3) token = Token((1,1)) token.add_costume("images/robo_green.png") token.orientation = -90 token.direction = 90 token2 = Token((4,1)) token2.add_costume("images/robo_yellow.png") token2.orientation = -90 token2.direction = -90 @token.register def act(self): self.move() token = self.sensing_token() if token: token.move_right() board.run() Output: .. raw:: html <video loop autoplay muted width=240> <source src="../_static/pushing.webm" type="video/webm"> Your browser does not support the video tag. </video> """ if distance == 0: return self.board_sensor.detect_token(token_filter) else: return self.board_sensor.detect_tokens_at(token_filter, direction, distance)
detect_token = detect sensing_token = detect #: Alias of :meth:`Token.detect`
[docs] def detect_borders(self, distance: int = 0, ) -> List: """ Senses borders .. image:: ../_images/sensing_borders.png Args: distance: Specifies the distance in front of the actuator to which the sensors reacts. Returns: True if border was found. """ return self.board_sensor.detect_borders(distance)
sensing_borders = detect_borders #: Alias of :meth:`Token.sensing_borders`
[docs] def detect_left_border(self) -> bool: """Does the token touch the left border? Returns: True if border was found. """ return "left" in self.board_sensor.detect_borders(0)
sensing_left_border = detect_left_border is_sensing_left_border = sensing_left_border #: Alias of :meth:`Token.sensing_left_border`
[docs] def detect_right_border(self) -> bool: """Does the token touch the right border? Returns: True if border was found. """ return "right" in self.board_sensor.detect_borders(0)
sensing_right_border = detect_right_border is_sensing_right_border = sensing_right_border #: Alias of :meth:`Token.sensing_right_border`
[docs] def detect_top_border(self) -> bool: """Does the token touch the lower border? Returns: True if border was found. """ return "top" in self.board_sensor.detect_borders(0)
sensing_top_border = detect_top_border is_sensing_top_border = sensing_top_border #: Alias of :meth:`Token.sensing_top_border`
[docs] def sensing_bottom_border(self) -> bool: """Does the token touch the lower border? Returns: True if border was found. """ return "bottom" in self.board_sensor.detect_borders(0)
is_sensing_bottom_border = sensing_bottom_border #: Alias of :meth:`Token.sensing_bottom_border` is_touching_bottom_border = sensing_bottom_border #: Alias of :meth:`Token.sensing_bottom_border`
[docs] def detect_color(self, color: Tuple = None) -> bool: """Senses colors in board-background at token center-position Args: color: color as tuple Returns: True, if color was found """ color = self.board_sensor.detect_color(color, ) return color
sensing_color = detect_color
[docs] def detect_colosr(self, color: List = None) -> bool: """Senses colors in board-background at token center-position Args: color: A list of colors Returns: True, if any color was found """ color = self.board_sensor.detect_colors(color, ) return color
[docs] def detect_color_at(self, direction: int = None, distance: int = 0) -> Union[Tuple, List]: """Detects colors in board-background at token-position Args: direction: Specifies the direction where the sensors is searching. distance: Specifies the distance in front of the actuator to which the sensors reacts. Returns: All colors found by Sensor """ color = self.board_sensor.detect_color_at(direction, distance) return color
sensing_color_at = detect_color_at sense_color_at = detect_color_at
[docs] def detect_tokens_at(self, direction=None, distance=0, token_filter=None) -> list: """Detects a token in given direction and distance. Examples: .. code-block:: python from miniworldmaker import * board = Board() wall=Rectangle((200,0)) wall.size = (20, 400) for i in range(7): token = Circle((10,i*60 + 20)) token.range = i * 10 @token.register def act(self): if not self.detect_tokens_at(self.direction, self.range): self.direction = "right" self.move() board.run() :param direction: The direction in which tokens should be detected. :param distance: The distance in which tokens should be detected (Start-Point is token.center) :return: A list of tokens """ return self.board_sensor.detect_tokens_at(token_filter, direction, distance)
sensing_tokens_at = detect_tokens #: Alias of :meth:`Token.sensing_tokens_at`
[docs] def detect_token_at(self, direction=None, distance=0, token_filter=None) -> "Token": found_tokens = self.board_sensor.detect_tokens_at(token_filter, direction, distance) if found_tokens: return found_tokens[0]
[docs] def detect_tokens_in_front(self, token_filter=None, distance=1, ) -> list: return self.board_sensor.detect_tokens_at(token_filter, self.direction, distance)
[docs] def detect_token_in_front(self, token_filter=None, distance=1, ) -> "Token": found_tokens = self.board_sensor.detect_tokens_at(token_filter, self.direction, distance) if found_tokens: return found_tokens[0]
[docs] def detect_point(self, position: Union["board_position.Position", Tuple]) -> bool: """Is the token colliding with a specific (global) point? Returns: True if point is below token """ return self.board_sensor.detect_point(position)
sensing_point = detect_point
[docs] def detect_rect(self, rect: Union[Tuple, pygame.rect.Rect]): """Is the token colliding with a static rect?""" return self.board_sensor.detect_rect(rect)
is_touching_rect = detect_rect
[docs] def bounce_from_token(self, other: "Token"): self.position_manager.bounce_from_token(other)
[docs] def animate(self, speed: int = 10): self.costume_manager.animate(speed)
[docs] def animate_costume(self, costume: "costume_mod.Costume", speed: int = 10): self.costume_manager.animate_costume(costume, speed)
[docs] def animate_loop(self, speed: int = 10): """Animates a costume with a looping animation Switches through all costume-images every ``speed``-frame. Examples: .. code-block:: python from miniworldmaker import * board = Board(columns=280, rows=100) robo = Token(position=(0, 0)) robo.costume.add_images(["images/1.png", "images/2.png","images/3.png","images/4.png"]) robo.size = (99, 99) robo.animate_loop() board.run() Args: speed (int, optional): Every ``speed`` frame, the image is switched. Defaults to 10. """ self.costume.loop = True self.costume_manager.animate(speed)
[docs] def stop_animation(self): """Stops current animation. Costume ``is_animated`` is set to False Examples: .. code-block:: python from miniworldmaker import * board = Board(columns=280, rows=100) robo = Token(position=(0, 0)) robo.costume.add_images(["images/1.png", "images/2.png","images/3.png","images/4.png"]) robo.size = (99, 99) robo.animate_loop() @timer(frames = 100) def stop(): robo.stop_animation() board.run() """ self.costume.is_animated = False
[docs] def send_message(self, message: str): """Sends a message to board. The message can be received with the ``on_message``-event Examples: Send and receive messages: .. code-block:: python from miniworldmaker import * board = Board() token1 = Token((2, 2)) token1.add_costume((100,0,100,100)) @token1.register def on_message(self, message): print("Received message:" + message) token2 = Token((100,100)) token2.send_message("Hello from token2") @token2.register def on_key_down_s(self): self.send_message("Hello") board.run() Args: message (str): A string containing the message. """ self.board.app.event_manager.to_event_queue("message", message)
[docs] def on_key_down(self, key: list): """**on_key_down** is called one time when a key is pressed down. .. note:: Instead of **on_key_down** you can use **on_key_down_letter**, e.g. **on_key_down_a** or **on_key_down_w** , if you want to handle an on_key_down event for a specific letter. Examples: Register a key_down event: .. code-block:: token1 = miniworldmaker.Token(position = (2, 2) ) token1.add_costume((100,0,100,100)) @token1.register def on_key_down(self, key): print(key) Register on_key_down_a event .. code-block:: token1 = miniworldmaker.Token(position = (2, 2) ) token1.add_costume((100,0,100,100)) @token1.register def on_key_down_a(self): print("a") Args: key (list): The typed key as list (e.g. ['A', 'a']) containing both uppercase and lowercase of typed letter. Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_key_down)
[docs] def on_key_pressed(self, key: list): """**on_key_pressed** is called when while key is pressed. If you hold the key, on_key_pressed is repeatedly called again and again until the key is released. .. note:: Like `on_key_down` the method can be called in the variant `on_key_pressed_[letter]` (e.g. `on_key_pressed_w(self)`). Examples: Register on_key_pressed event: .. code-block:: token1 = miniworldmaker.Token(position = (2, 2) ) token1.add_costume((100,0,100,100)) @token1.register def on_key_pressed(self, key): print("pressed", key) @token1.register def on_key_pressed_s(self): print("pressed s") Args: key (list): The typed key as list (e.g. ['C', 'c', 'D', 'd']) containing both uppercase and lowercase of typed letter. Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_key_pressed)
[docs] def on_key_up(self, key): raise NotImplementedOrRegisteredError(self.on_key_up)
[docs] def on_mouse_over(self, position): """on_mouse_over is called, when mouse is moved over token :param position: The mouse position """ raise NotImplementedOrRegisteredError(self.on_mouse_over)
[docs] def on_mouse_leave(self, position): """on_mouse_over is called, when mouse is moved over token :param position: The mouse position """ raise NotImplementedOrRegisteredError(self.on_mouse_over)
[docs] def on_mouse_left(self, position: tuple): """on_mouse_left is called when left mouse button was pressed. You must *register* or *implement* this method as an event. .. note:: The event is triggered, when mouse-left was clicked, even when the current mouse position is not related to token position. You can use :py:meth:`Token.sensing_point` to check, if the mouse_position is *inside* the token. Examples: A circle will be moved, if you click on circle. .. code-block:: from miniworldmaker import * board = Board(120,40) circle = Circle((20, 20)) circle.direction = 90 @circle.register def on_mouse_left(self, mouse_pos): if self.sensing_point(mouse_pos): self.move() board.run() Args: position (tuple): Actual mouse position as tuple (x,y) Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_mouse_left)
[docs] def on_mouse_right(self, position: tuple): """Method is called when right mouse button was pressed. You must *register* or *implement* this method as an event. .. note:: The event is triggered, when mouse was clicked,even when the current mouse position is not related to token position. You can use :py:meth:`Token.sensing_point` to check, if the mouse_position is *inside* the token. Examples: See: :py:meth:`Token.on_mouse_left`. Args: position (tuple): Actual mouse position as tuple (x,y) Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_mouse_right)
[docs] def on_mouse_motion(self, position: tuple): """Method is called when mouse moves. You must *register* or *implement* this method as an event. .. note:: The event is triggered, when mouse is moved, even when the current mouse position is not related to token position. You can use :py:meth:`Token.sensing_point` to check, if the mouse_position is *inside* the token. Examples: A circle will be moved, if you click on circle. .. code-block:: from miniworldmaker import * board = Board(120,40) circle = Circle((20, 20)) circle.direction = 90 @circle.register def on_mouse_motion(self, mouse_pos): if self.sensing_point(mouse_pos): self.move() board.run() Args: position (tuple): Actual mouse position as tuple (x,y) Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_mouse_motion)
[docs] def on_mouse_left_released(self, position: tuple): """Method is called when left mouse key is released. Examples: You can use on_mouse_left_release to implement a drag_and_drop event .. code-block:: from miniworldmaker import * board = Board(200, 200) circle = Circle((30, 30), 60) circle.direction = 90 circle.dragged = False @circle.register def on_mouse_left(self, mouse_pos): if self.sensing_point(mouse_pos): self.dragged = True @circle.register def on_mouse_left_released(self, mouse_pos): if not board.is_mouse_pressed(): self.dragged = False self.center = mouse_pos board.run() Output: .. raw:: html <video loop autoplay muted width=200> <source src="../_static/draganddrop.webm" type="video/webm"> Your browser does not support the video tag. </video> Args: position (tuple): Actual mouse position as tuple (x,y) Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_mouse_left_released)
[docs] def on_mouse_right_released(self, position: tuple): """Method is called when right mouse key is released. See :py:meth:`Token.on_mouse_left_released`. Args: position (tuple): Actual mouse position as tuple (x,y) Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_mouse_right_released)
[docs] def on_message(self, message: str): """Messages are used to allow objects to communicate with each other. Send a message: * A token and the board can send a message to all tokens and the board with the command: `self.send_message(“message_string”)` Process a message: * If your board or your token should react to messages you can use the event on_message: Examples: Receive a message: .. code-block:: python @player.register def on_message(self, message): if message == "Example message": do_something() Args: message (str): The message as string Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_message)
[docs] def on_clicked_left(self, position: tuple): """The mouse is on top of a token and mouse was clicked. Examples: Registering a on_click event: .. code-block:: token = miniworldmaker.Token((2,2)) @token.register def on_clicked_left(self, position): print("clicked" + str(position)) Args: position (tuple): Actual mouse position as tuple (x,y) Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_clicked_left)
[docs] def on_clicked_right(self, position): """The mouse is on top of a token and mouse was clicked. Examples: Registering a on_click event: .. code-block:: token = miniworldmaker.Token((2,2)) @token.register def on_clicked_right(self, position): print("clicked" + str(position)) Args: position (tuple): Actual mouse position as tuple (x,y) Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_clicked_right)
[docs] def on_detecting_board(self): """`on_detecting_board` is called, when token is on the board Examples: Register on_detecting_board method: .. code-block:: @player.register def on_detecting_board(self): print("Player 3: I'm on the board:") Raises: NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered. """ raise NotImplementedOrRegisteredError(self.on_detecting_board)
@property def static(self): """Should token react to events? You can turn this option off for additional performance boost. """ return self._static @static.setter def static(self, value): _token_connector = self.board.get_token_connector(self) _token_connector.set_static(value) @property def fill_color(self): """The fill color of token as rgba value, e.g. (255, 0, 0) for red. When ``fill_color`` is set to a color, the attribute ``is_filled`` of costume (See: :py:attr:.appearances.appearance.Appearance.is_filled`) is set to ``True``. .. note:: Aliases: :py:attr:`Token.color` .. warning:: If you fill a costume with an image, the image will be completely overwritten, even if `fill_color` is transparent. This behaviour may change in later releases! Examples: .. code-block:: python from miniworldmaker import * board = Board(200,80) board.default_fill_color = (0,0, 255) t = Token() t2 = Token((40,0)) t2.is_filled = (0, 255, 0) t3 = Token((80, 0)) t3.fill_colorimport miniworldmaker.tokens.token as token = (255, 0, 0) t4 = Token((120, 0)) t4.add_costume((0,0,0)) t4.fill_color = (255, 255, 0) t5 = Token((160, 0)) t5.add_costume("images/player.png") t5.fill_color = (255, 255, 0, 100) # image is overwritten t6 = Circle((0, 40), 20) t6.position = t6.center t6.fill_color = (255, 255, 255) t7 = Ellipse((40, 40), 40, 40) t7.fill_color = (255, 0, 255) board.run() Output: .. image:: ../_images/fill_color.png :width: 200px :alt: Set borders """ return self.costume.fill_color @fill_color.setter def fill_color(self, value): self.costume.fill(value) # Alias color = fill_color
[docs] def fill(self, value): """Set fill color for borders and lines""" self.costume.fill(value)
@property def is_filled(self): """Is token filled with color?""" return self.costume.is_filled @is_filled.setter def is_filled(self, value): self.costume.fill(value) @property def border_color(self): """border color of token. The border-color is a rgba value, for example (255, 0, 0) for red, (0, 255, 0) for green and (255, 0, 0, 100). If the color-value has 4 values, the last value defines the transparency: * 0: Full transparent, * 255: No transparency .. note:: You must also set :py:attr:`Token.border` to a value > 0 Aliases: :py:attr:`Token.stroke_color` Examples: See :py:attr:`Token.border` """ return self.costume.border_color @border_color.setter def border_color(self, value): self.costume.border_color = value # Alias stroke_color = border_color @property def border(self): """The border-size of token. The value is 0, if token has no border. .. note:: You can also set border with ``costume.border`` or you can set the border with ``board.default_border`` Examples: Set border of token: .. code-block:: from miniworldmaker import * board = Board(210,80) board.default_border_color = (0,0, 255) board.default_border = 1 t = Token((10,10)) # default-border and color from bord t.add_costume("images/player.png") t2 = Token ((60, 10)) # overwrites default border values t2.add_costume("images/player.png") t2.border_color = (0,255, 0) t2.border = 5 t3 = Token ((110, 10)) # removes border t3.add_costume("images/player.png") t3.border = None board.run() Output: .. image:: ../_images/borders.png :width: 200px :alt: Set borders """ return self.costume.border @border.setter def border(self, value): self.costume.border = value
[docs] def hide(self): """Hides a token (the token will be invisible) """ self.visible = False
[docs] def show(self): """Displays a token ( an invisible token will be visible) """ self.visible = True
[docs] def register(self, method: callable, force=False, name=None): """This method is used for the @register decorator. It adds a method to an object Args: method (callable): The method which should be added to the token force: Should register forced, even if method is not handling a valid event? name: Registers method with specific name """ if not force and method.__name__ not in self.board.event_manager.token_class_events_set: raise RegisterError(method.__name__, self) bound_method = token_inspection.TokenInspection(self).bind_method(method, name) if method.__name__ == "on_setup": self.on_setup() self.board.event_manager.register_event(method.__name__, self) return bound_method
[docs] def get_local_rect(self) -> pygame.Rect: local_rect = self.position_manager.get_local_rect() return local_rect
[docs] def get_global_rect(self) -> pygame.Rect: if self.position_manager: return self.position_manager.get_global_rect() return pygame.Rect(-1, -1, 0, 0)
@property def rect(self) -> pygame.Rect: """The surrounding Rectangle as pygame.Rect. Warning: If the token is rotated, the rect vertices are not the vertices of the token image. """ local_rect = self.position_manager.get_local_rect() # local_rect.topleft = self.board.container_top_left_x + local_rect.topleft[0], self.board.container_top_left_y + \ # local_rect.topleft[1] return local_rect
[docs] def get_rect(self) -> pygame.Rect: """Gets the rect of the token. If a camera is used, the local rect is written. Returns: pygame.Rect: A Rectangle with local position. """ return self.position_manager.get_local_rect()
def __str__(self): if self.board and hasattr(self, "position_manager") and self.position_manager: return "**Token: ID: {1} at pos {2} with size {3}**".format( self.__class__.__name__, self.token_id, self.position, self.size ) else: return "**Token: {0} ; ID: {1}**".format(self.__class__.__name__, self.token_id) @property def image(self) -> pygame.Surface: """ The image of the token: .. warning:: Warning: You should not directly draw on the image as the image will be reloaded during animations """ return self.costume_manager.image @property def position_manager(self): # if not hasattr(self, "_position_manager") or not self._position_manager: # return None try: return self._position_manager except AttributeError: raise MissingPositionManager(self) def _remove_position_manager(self): if hasattr(self, "_board_sensor"): self._position_manager.remove_from_board() del self._position_manager self._position_manager = None self.has_position_manager = False @property def board_sensor(self): try: return self._board_sensor except AttributeError: raise MissingBoardSensor(self) def _remove_board_sensor(self): if hasattr(self, "_board_sensor"): self.board_sensor.remove_from_board() del self._board_sensor self._board_sensor = None self.has_board_sensor = False @property def costume_manager(self): return self._costume_manager @property def position(self) -> "board_position.Position": """The position of the token as Position(x, y) """ return self.position_manager.position @position.setter def position(self, value: Union["board_position.Position", tuple]): self.set_position(value)
[docs] def set_position(self, value: Union["board_position.Position", tuple]): self.position_manager.position = value
[docs] def get_distance_to(self, obj: Union["Token", "board_position.Position", tuple]) -> float: """Gets the distance to another token or a position Args: obj: Token or Position Returns: float: The distance between token (measured from token.center) to token or position. """ return self.board_sensor.get_distance_to(obj)
[docs] def on_shape_change(self): pass