コード例 #1
0
ファイル: object.py プロジェクト: jeamesbone/N-Arcs
class CircleUnionObject(PhysicsObject):
    def __init__(self,particle,color):
        '''
        Constructor
        '''
        PhysicsObject.__init__(self,particle)
        
        self.rect = Rect(0,0,0,0)
        for c in particle.collider.colliders:
            radius = int(c.r)
            rect = pygame.Rect(c.p.x-radius,c.p.y-radius,radius*2+1, radius*2+1)   
            self.rect.union_ip(rect) 
        
        self.original = pygame.surface.Surface([self.rect.width,self.rect.height],SRCALPHA)
                
        for c in particle.collider.colliders:
            radius = int(c.r)
            x = int(c.p.x)
            y = int(c.p.y)
            pygame.gfxdraw.aacircle(self.original, x-self.rect.left,y-self.rect.top,radius,color)
            pygame.gfxdraw.filled_circle(self.original,x-self.rect.left,y-self.rect.top,radius,color)
        
        self.view = self.original
           
        self.update()
コード例 #2
0
ファイル: menu.py プロジェクト: HampshireCS/cs143-Spring2012
    def build(self):
        # calculate the max dimensions
        max_w, max_h = 0, 0
        for item in self.items:
            width, height = self.font.size(item.text)
            max_w = max(width, max_w)
            max_h = max(height, max_h)

        rect = Rect(0,0,max_w,max_h).inflate(self.padding, self.padding)

        # place and initialize each menu item
        bounds = Rect(0, 0, 0, 0)
        top = 0
        for item in self.items:
            item.image = Surface(rect.size, SRCALPHA)
            item.rect = rect.copy()
            item.rect.top = top

            top = item.rect.bottom + self.margin
            
            bounds.union_ip(item.rect)

        # tmp, render each sprite initially
        for item in self.items:
            item.draw_normal()
コード例 #3
0
    def build(self):
        # calculate the max dimensions
        max_w, max_h = 0, 0
        for item in self.items:
            width, height = self.font.size(item.text)
            max_w = max(width, max_w)
            max_h = max(height, max_h)

        rect = Rect(0, 0, max_w, max_h).inflate(self.padding, self.padding)

        # place and initialize each menu item
        bounds = Rect(0, 0, 0, 0)
        top = 0
        for item in self.items:
            item.image = Surface(rect.size, SRCALPHA)
            item.rect = rect.copy()
            item.rect.top = top

            top = item.rect.bottom + self.margin

            bounds.union_ip(item.rect)

        # tmp, render each sprite initially
        for item in self.items:
            item.draw_normal()
コード例 #4
0
ファイル: main.py プロジェクト: grihabor/cascade
    def __init__(self):
        winstyle = 0
        bestdepth = pygame.display.mode_ok(SCREENRECT.size, winstyle, 32)
        self.screen = pygame.display.set_mode(SCREENRECT.size, winstyle,
                                              bestdepth)
        pygame.display.set_caption("Cascade")
        self.running = True

        self.balls_to_rotate = queue.LifoQueue()

        surfaces = Surfaces()
        balls = {}

        grid_size = 10
        grid = list(itertools.product(range(grid_size), range(grid_size)))
        for i, j in grid:
            balls[i, j] = Ball(
                surfaces=surfaces,
                rotation=random.randint(0, 3),
                center=(i, j),
                balls_to_rotate=self.balls_to_rotate,
                color=random.randint(0, 2),
            )

        grid_rect = Rect(0, 0, 0, 0)
        for ball in balls.values():
            grid_rect.union_ip(ball.rect())
        shift = (
            SCREENRECT.width / 2 - grid_rect.width / 2,
            SCREENRECT.height / 2 - grid_rect.height / 2,
        )

        for ball in balls.values():  # type: Ball
            ball.move_ip(*shift)

        def ok(i, j):
            return i >= 0 and j >= 0 and i < grid_size and j < grid_size

        for i, j in grid:
            if ok(i + 1, j):
                balls[i, j].set_left(balls[i + 1, j])
            if ok(i, j + 1):
                balls[i, j].set_down(balls[i, j + 1])
            if ok(i - 1, j):
                balls[i, j].set_right(balls[i - 1, j])
            if ok(i, j - 1):
                balls[i, j].set_up(balls[i, j - 1])

        self.balls = pygame.sprite.Group(*[b for b in balls.values()])
コード例 #5
0
ファイル: text.py プロジェクト: Grug16/Save-the-Robots-Game
    def render(self, lines, antialias, fg, bg=None):
        block = []
        bounds = Rect(0,0,self.margin,self.margin)
        for line in lines:

            if bg is not None:
                text = self.font.render(line, antialias, fg, bg)
            else:
                text = self.font.render(line, antialias, fg)

            rect = text.get_rect()
            if bounds.bottom > self.margin:
                rect.top = bounds.bottom + self.padding

            bounds.union_ip(rect)
            block.append((text,rect))
        
        bounds.height += self.margin


        surf = Surface(bounds.size, SRCALPHA)

        if bg is not None:
            surf.fill(bg)
        else:
            surf.fill((0,0,0,0))

        for line,rect in block:

            if self.justify == LEFT:
                rect.left = bounds.left + self.margin
            elif self.justify == RIGHT:
                rect.right = bounds.right - self.margin
            elif self.justify == CENTER:
                rect.centerx = bounds.centerx

            surf.blit(line, rect)

        return surf
コード例 #6
0
    def render(self, lines, antialias, fg, bg=None):
        block = []
        bounds = Rect(0, 0, self.margin, self.margin)
        for line in lines:

            if bg is not None:
                text = self.font.render(line, antialias, fg, bg)
            else:
                text = self.font.render(line, antialias, fg)

            rect = text.get_rect()
            if bounds.bottom > self.margin:
                rect.top = bounds.bottom + self.padding

            bounds.union_ip(rect)
            block.append((text, rect))

        bounds.height += self.margin

        surf = Surface(bounds.size, SRCALPHA)

        if bg is not None:
            surf.fill(bg)
        else:
            surf.fill((0, 0, 0, 0))

        for line, rect in block:

            if self.justify == LEFT:
                rect.left = bounds.left + self.margin
            elif self.justify == RIGHT:
                rect.right = bounds.right - self.margin
            elif self.justify == CENTER:
                rect.centerx = bounds.centerx

            surf.blit(line, rect)

        return surf
コード例 #7
0
ファイル: rect_test.py プロジェクト: annie60/Xilarius
 def test_union_ip(self):
     r1 = Rect(1, 1, 1, 2)
     r2 = Rect(-2, -2, 1, 2)
     r1.union_ip(r2)
     self.assertEqual(Rect(-2, -2, 4, 5), r1)
コード例 #8
0
ファイル: rect_test.py プロジェクト: CTPUG/pygame_cffi
 def test_union_ip( self ):
     r1 = Rect( 1, 1, 1, 2 )
     r2 = Rect( -2, -2, 1, 2 )
     r1.union_ip(r2)
     self.assertEqual( Rect( -2, -2, 4, 5 ), r1 )
コード例 #9
0
ファイル: quadtree.py プロジェクト: bitcraft/pyweek17
    def __init__(self, items, depth=4, bounding_rect=None):
        """Creates a quad-tree.
 
        @param items:
            A sequence of items to store in the quad-tree. Note that these
            items must be a pygame.Rect or have a .rect attribute.
            
        @param depth:
            The maximum recursion depth.
            
        @param bounding_rect:
            The bounding rectangle of all of the items in the quad-tree. For
            internal use only.
        """
 
        # The sub-quadrants are empty to start with.
        self.nw = self.ne = self.se = self.sw = None
        
        # If we've reached the maximum depth then insert all items into this
        # quadrant.
        depth -= 1
        if depth == 0 or not items:
            self.items = items
            return
 
        # Find this quadrant's centre.
        if bounding_rect:
            bounding_rect = Rect( bounding_rect )
        else:
            # If there isn't a bounding rect, then calculate it from the items.
            bounding_rect = Rect( items[0] )
            for item in items[1:]:
                bounding_rect.union_ip( item )
        cx = self.cx = bounding_rect.centerx
        cy = self.cy = bounding_rect.centery
 
        self.items = []
        nw_items = []
        ne_items = []
        se_items = []
        sw_items = []
 
        for item in items:
            # Which of the sub-quadrants does the item overlap?
            in_nw = item.left <= cx and item.top <= cy
            in_sw = item.left <= cx and item.bottom >= cy
            in_ne = item.right >= cx and item.top <= cy
            in_se = item.right >= cx and item.bottom >= cy

            # If it overlaps all 4 quadrants then insert it at the current
            # depth, otherwise append it to a list to be inserted under every
            # quadrant that it overlaps.
            if in_nw and in_ne and in_se and in_sw:
                self.items.append(item)
            else:
                if in_nw: nw_items.append(item)
                if in_ne: ne_items.append(item)
                if in_se: se_items.append(item)
                if in_sw: sw_items.append(item)
           
        # Create the sub-quadrants, recursively.
        if nw_items:
            self.nw = FastQuadTree(nw_items, depth, \
                      (bounding_rect.left, bounding_rect.top, cx, cy))
 
        if ne_items:
            self.ne = FastQuadTree(ne_items, depth, \
                      (cx, bounding_rect.top, bounding_rect.right, cy))

        if se_items:
            self.se = FastQuadTree(se_items, depth, \
                      (cx, cy, bounding_rect.right, bounding_rect.bottom))
  
        if sw_items:
            self.sw = FastQuadTree(sw_items, depth, \
                      (bounding_rect.left, cy, cx, bounding_rect.bottom))
コード例 #10
0
    def __init__(self, items, depth=8, bounding_rect=None):
        """Creates a quad-tree.
 
        @param items:
            A sequence of items to store in the quad-tree. Note that these
            items must be a pygame.Rect or have a .rect attribute.
            
        @param depth:
            The maximum recursion depth.
            
        @param bounding_rect:
            The bounding rectangle of all of the items in the quad-tree. For
            internal use only.
        """

        # The sub-quadrants are empty to start with.
        self.nw = self.ne = self.se = self.sw = None

        # If we've reached the maximum depth then insert all items into this
        # quadrant.
        depth -= 1
        if depth == 0 or not items:
            self.items = items
            return

        # Find this quadrant's centre.
        if bounding_rect:
            bounding_rect = Rect(bounding_rect)
        else:
            # If there isn't a bounding rect, then calculate it from the items.
            bounding_rect = Rect(items[0])
            for item in items[1:]:
                bounding_rect.union_ip(item)
        cx = self.cx = bounding_rect.centerx
        cy = self.cy = bounding_rect.centery

        self.items = []
        nw_items = []
        ne_items = []
        se_items = []
        sw_items = []

        for item in items:
            # Which of the sub-quadrants does the item overlap?
            in_nw = item.left <= cx and item.top <= cy
            in_sw = item.left <= cx and item.bottom >= cy
            in_ne = item.right >= cx and item.top <= cy
            in_se = item.right >= cx and item.bottom >= cy

            # If it overlaps all 4 quadrants then insert it at the current
            # depth, otherwise append it to a list to be inserted under every
            # quadrant that it overlaps.
            if in_nw and in_ne and in_se and in_sw:
                self.items.append(item)
            else:
                if in_nw: nw_items.append(item)
                if in_ne: ne_items.append(item)
                if in_se: se_items.append(item)
                if in_sw: sw_items.append(item)

        # Create the sub-quadrants, recursively.
        if nw_items:
            self.nw = QuadTree(nw_items, depth,
                               (bounding_rect.left, bounding_rect.top, cx, cy))
        if ne_items:
            self.ne = QuadTree(
                ne_items, depth,
                (cx, bounding_rect.top, bounding_rect.right, cy))
        if se_items:
            self.se = QuadTree(
                se_items, depth,
                (cx, cy, bounding_rect.right, bounding_rect.bottom))
        if sw_items:
            self.sw = QuadTree(
                sw_items, depth,
                (bounding_rect.left, cy, cx, bounding_rect.bottom))
コード例 #11
0
class BoardGenerator(loading_saving.Savable, loading_saving.Loadable):
    """
    This class has 3 separate layers of generation:
    1. a broad layer that generates caves and environment far outside the visible surroudings. This is done to ensure
    that caves and structures are not forced to generate in a certain way because of how the player has explored
    2. a much smaller layer that generates special blocks that can span chunk borders. This layer goes one chunk
    further then the visible layer and ensures that things like ore clusters can be generated over chunk borders as well
    as bigger environment things.
    3. The smallest layer directly around the player that is generated as soon as a player enters a chunk that has no
    loaded chunk next to it.
    """
    # BIOME values
    # extend loading of biomes and structures by this much everytime terrain has to be loaded
    __EXTENDED_LOAD_AMOUNT: ClassVar[int] = 2000

    # the size of quadrants where a new biome is chosen
    BIOME_SIZES: ClassVar[Dict[str, util.Size]] = \
        {"tiny": util.Size(100, 100), "small": util.Size(200, 200), "normal": util.Size(500, 500),
         "big": util.Size(750, 7500), "massive": util.Size(1000, 1000), "huge": util.Size(1500, 1500),
         "just one pls": util.Size(3000, 3000)}  # in blocks

    # determines the standard deviation of the biomes, high values means very broad distributions
    BIOME_BLEND: ClassVar[Dict[str, int]] = \
        {"very low": 1, "low": 5, "normal": 15, "severe": 30, "extreme": 50, "what are biomes?": 100}

    # CAVE values
    MAX_CAVES: ClassVar[Dict[str, int]] = \
        {"one_caves": 1, "low": 5, "normal": 15, "lots": 30, "I WANT CAVES!!!": 50}  # per 100_000 blocks
    # the fraction of the distance between points based on the shortest side of the board
    MAX_POINT_DISTANCE: ClassVar[int] = 100
    # number of points a cave consists of
    CAVE_LENGTH: ClassVar[Dict[str, int]] = \
        {"short": 25, "normal": 50, "long": 100, "very long": 200, "when does it stop?": 500}
    # the chance for a cave to stop extending around its core. Do not go lower then 0.0005 --> takes a long time
    CAVE_STOP_SPREAD_CHANCE: ClassVar[Dict[str, int]] = \
        {"very thin": 0.1, "thin": 0.8, "normal": 0.05, "wide": 0.01, "very wide": 0.005}

    # BORDER values
    BORDER_SPREAD_LIKELYHOOD: ClassVar[util.Gaussian] = util.Gaussian(0, 2)
    MAX_BORDER_SPREAD_DISTANCE: ClassVar[int] = 4

    __environment_material_names: Set[str]
    __generated_chunks_matrix: List[List[int]]
    __predefined_blocks: "PredefinedBlocks"
    __minimum_generation_length: int
    __generation_rect: Rect

    __cave_length: int
    __cave_quadrant_size: util.Size
    __cave_stop_spread_chance: float

    __biome_definition: biome_classes.BiomeGenerationDefinition
    __biome_size: util.Size
    __biome_blend: int
    __biome_matrix: List[List[Union[biome_classes.Biome, None]]]

    def __init__(
        self,
        biome_generation_def: Union[biome_classes.BiomeGenerationDefinition,
                                    str] = None,
        biome_size: Union[str, util.Size] = "normal",
        biome_blend: Union[str, int] = "normal",
        nr_caves: Union[str, int] = "normal",
        cave_length: Union[str, int] = "normal",
        cave_broadness: Union[str, float] = "normal",
        progress_var: Union[None, List[str]] = None,
    ):
        self.__environment_material_names = {
            mat.name()
            for mat in block_util.environment_materials
        }

        # for tracking what chunks have been covered by generation 0 is not covered 1 is covered by ores and environment
        # 2 is covered with filler blocks
        self.__generated_chunks_matrix = [[
            0 for _ in range(
                ceil(con.ORIGINAL_BOARD_SIZE.width / con.CHUNK_SIZE.width))
        ] for _ in range(
            ceil(con.ORIGINAL_BOARD_SIZE.height / con.CHUNK_SIZE.height))]
        # structure for efficiently storing a variable number of blocks from a matrix that are not neccesairily
        # consecutive
        self.__predefined_blocks = PredefinedBlocks()

        # amount of cave points
        self.__cave_length = self.CAVE_LENGTH.get(cave_length, cave_length)
        # the square where one cave occurs
        self.__cave_quadrant_size = self.__determine_cave_quadrant_size(
            self.MAX_CAVES.get(nr_caves, nr_caves))
        self.__cave_stop_spread_chance = max(
            self.CAVE_STOP_SPREAD_CHANCE.get(cave_broadness, cave_broadness),
            0.0005)

        # minimum distance (x and y) between the closest generated chunk and the non-generated structures and biomes
        self.__minimum_generation_length = ceil(self.__cave_length *
                                                self.MAX_POINT_DISTANCE)
        # tracks what part of the board the structures and biomes have been generated for.
        self.__generation_rect = Rect(
            (max(
                0, con.START_LOAD_AREA[0][0] * con.CHUNK_SIZE.width -
                self.__minimum_generation_length),
             max(
                 0, con.START_LOAD_AREA[1][0] * con.CHUNK_SIZE.height -
                 self.__minimum_generation_length),
             min(
                 len(con.START_LOAD_AREA[0]) * con.CHUNK_SIZE.width +
                 2 * self.__minimum_generation_length,
                 con.ORIGINAL_BOARD_SIZE.width),
             min(
                 len(con.START_LOAD_AREA[1]) * con.CHUNK_SIZE.height +
                 2 * self.__minimum_generation_length,
                 con.ORIGINAL_BOARD_SIZE.height)))

        self.__biome_definition = biome_generation_def if biome_generation_def else\
            biome_classes.NormalBiomeGeneration()
        self.__biome_size = self.BIOME_SIZES.get(biome_size, biome_size)
        self.__biome_blend = self.BIOME_BLEND.get(biome_blend, biome_blend)
        # fill the biome matrix with empty values
        self.__biome_matrix = [[
            None for _ in range(
                ceil(con.ORIGINAL_BOARD_SIZE.width / self.__biome_size.width))
        ] for _ in range(
            ceil(con.ORIGINAL_BOARD_SIZE.height / self.__biome_size.height))]

        self.__generate_biomes(self.__generation_rect, progress_var)
        self.__generate_surroundings(self.__generation_rect, progress_var)

    def __init_load__(self,
                      generated_chunk_matrix=None,
                      predefined_blocks=None,
                      minimum_generation_length=None,
                      generation_rect=None,
                      cave_lenght=None,
                      cave_quadrant_size=None,
                      cave_stop_spread_chance=None,
                      biome_size=None,
                      biome_blend=None,
                      biome_matrix=None,
                      biome_definition=None):
        self.__environment_material_names = {
            mat.name()
            for mat in block_util.environment_materials
        }

        # for tracking what chunks have been covered by generation 0 is not covered 1 is covered by ores and environment
        # 2 is covered with filler blocks
        self.__generated_chunks_matrix = generated_chunk_matrix
        # structure for efficiently storing a variable number of blocks from a matrix that are not neccesairily
        # consecutive
        self.__predefined_blocks = predefined_blocks

        # amount of cave points
        self.__cave_length = cave_lenght
        # the square where one cave occurs
        self.__cave_quadrant_size = cave_quadrant_size
        self.__cave_stop_spread_chance = cave_stop_spread_chance

        # minimum distance (x and y) between the closest generated chunk and the non-generated structures and biomes
        self.__minimum_generation_length = minimum_generation_length
        # tracks what part of the board the structures and biomes have been generated for.
        self.__generation_rect = generation_rect

        self.__biome_definition = biome_definition
        self.__biome_size = self.BIOME_SIZES.get(biome_size, biome_size)
        self.__biome_blend = self.BIOME_BLEND.get(biome_blend, biome_blend)
        # fill the biome matrix with empty values
        self.__biome_matrix = biome_matrix

    def to_dict(self) -> Dict[str, Any]:
        return {
            "generated_chunk_matrix":
            self.__generated_chunks_matrix,
            "predefined_blocks":
            self.__predefined_blocks.to_dict(),
            "minimum_generation_length":
            self.__minimum_generation_length,
            "generation_rect":
            (self.__generation_rect.left, self.__generation_rect.top,
             self.__generation_rect.width, self.__generation_rect.height),
            "cave_lenght":
            self.__cave_length,
            "cave_quadrant_size":
            self.__cave_quadrant_size.to_dict(),
            "cave_stop_spread":
            self.__cave_stop_spread_chance,
            "biome_definition":
            self.__biome_definition.to_dict(),
            "biome_size":
            self.__biome_size.to_dict(),
            "biome_blend":
            self.__biome_blend,
            "biome_matrix":
            [[biome.to_dict() if biome is not None else None for biome in row]
             for row in self.__biome_matrix]
        }

    @classmethod
    def from_dict(cls, dct):
        predefined_blocks = PredefinedBlocks.from_dict(
            dct["predefined_blocks"])
        generation_rect = Rect(dct["generation_rect"])
        cave_quadrant_size = util.Size.from_dict(dct["cave_quadrant_size"])
        biome_definition = biome_classes.BiomeGenerationDefinition.from_dict(
            dct["biome_definition"])
        biome_size = util.Size.from_dict(dct["biome_size"])
        biome_matrix = [[
            biome_classes.Biome.from_dict(biome_d)
            if biome_d is not None else None for biome_d in row
        ] for row in dct["biome_matrix"]]

        return cls.load(
            generated_chunk_matrix=dct["generated_chunk_matrix"],
            predefined_blocks=predefined_blocks,
            minimum_generation_length=dct["minimum_generation_length"],
            generation_rect=generation_rect,
            cave_lenght=dct["cave_lenght"],
            cave_quadrant_size=cave_quadrant_size,
            cave_stop_spread_chance=dct["cave_stop_spread"],
            biome_size=biome_size,
            biome_blend=dct["biome_blend"],
            biome_matrix=biome_matrix,
            biome_definition=biome_definition)

    def generate_chunk(
        self, topleft: Union[Tuple[int, int], List[int]]
    ) -> Tuple[Union[None, List[List]], Union[None, List[List]]]:
        """Generate a chunk with the given topleft"""
        matrix = [[
            None for _ in range(interface_util.p_to_c(con.CHUNK_SIZE.width))
        ] for _ in range(interface_util.p_to_r(con.CHUNK_SIZE.height))]
        background_matrix = [[
            None for _ in range(interface_util.p_to_c(con.CHUNK_SIZE.width))
        ] for _ in range(interface_util.p_to_r(con.CHUNK_SIZE.height))]
        chunk_rect = Rect((topleft[0], topleft[1], con.CHUNK_SIZE.width,
                           con.CHUNK_SIZE.height))
        chunk_coord = interface_util.p_to_cp(topleft)

        # if the chunk was already generated do not do it again
        if self.__generated_chunks_matrix[chunk_coord[1]][chunk_coord[0]] == 2:
            return None, None
        # extend structures as caves and biomes when needed
        self.__extend_surrounding_generation(chunk_rect)
        # generate ores and environment one chunk out from the generated boundary to allow generation over chunks
        self.__add_special_blocks(chunk_coord)
        # add all blocks that have been pre-defined by surrounding generation and ore and environment
        self.__add_pre_defined_blocks(chunk_rect, matrix)
        self.__add_filler_blocks(chunk_rect, matrix, background_matrix)
        self.__add_border(matrix, topleft)

        # save that this chunk was covered by generation
        self.__generated_chunks_matrix[chunk_coord[1]][chunk_coord[0]] = 2
        return matrix, background_matrix

# LAYER1: cave biome and structure generation

    def __extend_surrounding_generation(self, rect: Rect) -> None:
        # check if surrounding generation needs to be extended
        if self.__generation_rect.top > 0 and \
                rect.top - self.__minimum_generation_length < self.__generation_rect.top:
            self.__generate_biome_structures("N")
        elif self.__generation_rect.right < con.ORIGINAL_BOARD_SIZE.width and \
                rect.right + self.__minimum_generation_length > self.__generation_rect.right:
            self.__generate_biome_structures("E")
        elif self.__generation_rect.bottom < con.ORIGINAL_BOARD_SIZE.height and \
                rect.bottom + self.__minimum_generation_length > self.__generation_rect.bottom:
            self.__generate_biome_structures("S")
        elif self.__generation_rect.left > 0 and \
                rect.left - self.__minimum_generation_length < self.__generation_rect.left:
            self.__generate_biome_structures("W")

    def __generate_biome_structures(self, direction: str) -> None:
        """Generate biomes structures and environment to a certain direction with __EXTENDED_LOAD_AMOUNT"""
        # create a rectangle that does not overlap with the __generation_rectangle and extends one of four directions
        if direction == "N":
            rect = Rect(
                (self.__generation_rect.left,
                 max(0,
                     self.__generation_rect.top - self.__EXTENDED_LOAD_AMOUNT),
                 self.__generation_rect.width,
                 min(self.__generation_rect.top, self.__EXTENDED_LOAD_AMOUNT)))
        elif direction == "E":
            rect = Rect((min(
                con.ORIGINAL_BOARD_SIZE.width,
                self.__generation_rect.right + self.__EXTENDED_LOAD_AMOUNT),
                         self.__generation_rect.top,
                         min(
                             con.ORIGINAL_BOARD_SIZE.width -
                             self.__generation_rect.right,
                             self.__EXTENDED_LOAD_AMOUNT),
                         self.__generation_rect.height))
        elif direction == "S":
            rect = Rect((self.__generation_rect.left,
                         min(
                             con.ORIGINAL_BOARD_SIZE.height,
                             self.__generation_rect.bottom +
                             self.__EXTENDED_LOAD_AMOUNT),
                         self.__generation_rect.width,
                         min(
                             con.ORIGINAL_BOARD_SIZE.height -
                             self.__generation_rect.bottom,
                             self.__EXTENDED_LOAD_AMOUNT)))
        elif direction == "W":
            rect = Rect(
                (max(0, self.__generation_rect.left -
                     self.__EXTENDED_LOAD_AMOUNT), self.__generation_rect.top,
                 min(self.__generation_rect.left, self.__EXTENDED_LOAD_AMOUNT),
                 self.__generation_rect.height))
        else:
            raise util.GameException(
                "Expected N, E, S or W not {}".format(direction))
        self.__generate_biomes(rect)
        self.__generate_surroundings(rect)
        self.__generation_rect.union_ip(rect)

    def __generate_biomes(self,
                          rect: Rect,
                          progress_var: Union[None, List[str]] = None) -> None:
        """Fill the biome matrix within within the given rect with biome classes based using the __biome_definition"""
        row_start = int(rect.top / self.__biome_size.height)
        col_start = int(rect.left / self.__biome_size.width)

        total_biomes = int(rect.height / self.__biome_size.height) * int(
            rect.width / self.__biome_size.width)
        for row_i in range(ceil(rect.height / self.__biome_size.height)):
            for col_i in range(ceil(rect.width / self.__biome_size.width)):
                if progress_var:
                    current_biome_nr = (row_i * int(
                        rect.width / self.__biome_size.width)) + col_i
                    progress_var[
                        0] = f"Generating biome {current_biome_nr} out of {total_biomes}..."
                row_i += row_start
                col_i += col_start
                # allow the shapes of the distributions to be a bit different (more oval)
                sd_x = self.__biome_size.width * self.__biome_blend * uniform(
                    0.6, 1.4)
                sd_y = self.__biome_size.height * self.__biome_blend * uniform(
                    0.6, 1.4)
                # allow the distribution to be tilted
                cov1 = uniform(-sd_x, sd_x)
                cov2 = uniform(-sd_y, sd_y)
                mean_x = col_i * self.__biome_size.width + 0.5 * self.__biome_size.width
                mean_y = row_i * self.__biome_size.height + 0.5 * self.__biome_size.height

                # check depths in blocks
                biome_type = self.__biome_definition.get_biome(
                    int(mean_y / con.BLOCK_SIZE.height))
                biome_instance = biome_type(util.Gaussian(mean_x, sd_x),
                                            util.Gaussian(mean_y, sd_y), cov1,
                                            cov2)
                self.__biome_matrix[row_i][col_i] = biome_instance

    def __generate_surroundings(
            self,
            rect: Rect,
            progress_var: Union[None, List[str]] = None) -> None:
        """Generate caves and structures within a given rectangle and save them in a PredefinedBlocks structure"""
        row_start = int(rect.top / self.__cave_quadrant_size.height)
        col_start = int(rect.left / self.__cave_quadrant_size.width)
        total_caves = int(rect.height / self.__cave_quadrant_size.height) * \
                      int(rect.width / self.__cave_quadrant_size.width) # noqa
        for row_i in range(int(rect.height /
                               self.__cave_quadrant_size.height)):
            for col_i in range(
                    int(rect.width / self.__cave_quadrant_size.width)):
                if progress_var:
                    current_cave_nr = (row_i * int(
                        rect.width / self.__cave_quadrant_size.width)) + col_i
                    progress_var[
                        0] = f"Carving out cave {current_cave_nr} out of {total_caves}..."
                row_i += row_start
                col_i += col_start
                x_coord = randint(
                    int(col_i * self.__cave_quadrant_size.height),
                    int((col_i + 1) * self.__cave_quadrant_size.height))
                y_coord = randint(
                    int(row_i * self.__cave_quadrant_size.width),
                    int((row_i + 1) * self.__cave_quadrant_size.width))
                self.__generate_cave([x_coord, y_coord])
                x_coord = randint(
                    int(col_i * self.__cave_quadrant_size.height),
                    int((col_i + 1) * self.__cave_quadrant_size.height))
                y_coord = randint(
                    int(row_i * self.__cave_quadrant_size.width),
                    int((row_i + 1) * self.__cave_quadrant_size.width))
                self.__generate_structure((x_coord, y_coord))

    def __generate_structure(self, coord: Tuple[int, int]):
        structure_class = self.__biome_definition.get_structure(coord[1])
        if structure_class is None:
            return
        structure_instance = structure_class()
        structure_matrix = structure_instance.get_structure_matrix()
        matrix_coord = int(coord[0] / con.BLOCK_SIZE.width), int(
            coord[1] / con.BLOCK_SIZE.height)
        for r_index, row in enumerate(structure_matrix):
            for c_index, material in enumerate(row):
                if material is None:
                    continue
                self.__predefined_blocks.add(
                    (matrix_coord[0] + c_index, matrix_coord[1] + r_index),
                    material)

    def __generate_cave(
            self, start_point: Union[Tuple[int, int], List[int]]) -> None:
        """Add air spaces to the PredefinedBlocks instance"""
        cave_points = self.__get_cave_points(start_point)
        # get the line between the points
        for index1 in range(1, len(cave_points)):
            point1 = cave_points[index1 - 1]
            point2 = cave_points[index1]
            a, b = util.line_from_points(point1, point2)
            if abs(a) < 1:
                number_of_breaks = int(abs(point1[0] - point2[0]) * abs(1 / a))
            else:
                number_of_breaks = int(abs(point1[0] - point2[0]) * abs(a))
            break_size = (point2[0] - point1[0]) / number_of_breaks
            x_values = [
                point1[0] + index * break_size
                for index in range(0, number_of_breaks)
            ]
            y_values = [a * x + b for x in x_values]
            # make all block_classes air on the direct line
            for index2 in range(len(x_values)):
                x = int(x_values[index2])
                matrix_x = min(
                    x,
                    int(con.ORIGINAL_BOARD_SIZE.width / con.BLOCK_SIZE.width) -
                    1)
                y = int(y_values[index2])
                matrix_y = min(
                    y,
                    int(con.ORIGINAL_BOARD_SIZE.height / con.BLOCK_SIZE.height)
                    - 1)
                self.__predefined_blocks.add((matrix_x, matrix_y), "Air")
                surrounding_coords = \
                    [coord for coord in self.__get_surrounding_block_coords(x, y) if coord is not None
                     and not self.__predefined_blocks.check(coord, ["Air"])]
                # extend the cave around the direct line at random.
                while len(surrounding_coords) > 0:
                    if uniform(0, 1) < self.__cave_stop_spread_chance:
                        break
                    remove_coord = choice(surrounding_coords)
                    surrounding_coords.remove(remove_coord)
                    self.__predefined_blocks.add(remove_coord, "Air")
                    additional_surrounding_coords = \
                        [coord for coord in self.__get_surrounding_block_coords(*remove_coord) if coord is not None
                         and not self.__predefined_blocks.check(coord, ["Air"])]
                    surrounding_coords.extend(additional_surrounding_coords)

    def __get_cave_points(
            self, start_point: Union[Tuple[int, int],
                                     List[int]]) -> List[List[int]]:
        """Get points from a start point within the con.BOARD_SIZE and shaped nicely"""
        cave_points = [start_point]
        prev_direction = uniform(0, 2 * pi)
        amnt_points = randint(int(max(self.__cave_length / 2, 1)),
                              self.__cave_length)
        while len(cave_points) < amnt_points:
            radius = randint(max(1, int(self.MAX_POINT_DISTANCE / 2)),
                             self.MAX_POINT_DISTANCE)
            prev_direction = uniform(prev_direction - 0.5 * pi,
                                     prev_direction + 0.5 * pi)
            new_x = min(
                max(int(cave_points[-1][0] + cos(prev_direction) * radius), 0),
                con.ORIGINAL_BOARD_SIZE.width)
            new_y = min(
                max(int(cave_points[-1][1] + sin(prev_direction) * radius), 0),
                con.ORIGINAL_BOARD_SIZE.height)
            # make sure no double points and no straight lines
            if [new_x, new_y] in cave_points or \
                    int(new_x / con.BLOCK_SIZE.width) == int(cave_points[-1][0] / con.BLOCK_SIZE.width) or \
                    int(new_y / con.BLOCK_SIZE.height) == int(cave_points[-1][1] / con.BLOCK_SIZE.height):
                continue
            cave_points.append([new_x, new_y])
        return [[
            int(x / con.BLOCK_SIZE.width),
            int(y / con.BLOCK_SIZE.height)
        ] for x, y in cave_points]

    def __add_pre_defined_blocks(self, rect: Rect, matrix: List[List]) -> None:
        """Get all predefined blocks in a rectangle and add them to matrix"""
        for row_i in range(int(rect.height / con.BLOCK_SIZE.height)):
            for col_i in range(int(rect.width / con.BLOCK_SIZE.width)):
                block_x_coord = int(rect.left / con.BLOCK_SIZE.width) + col_i
                block_y_coord = int(rect.top / con.BLOCK_SIZE.height) + row_i
                # add pre_defined_blocks
                if (block_x_coord, block_y_coord) in self.__predefined_blocks:
                    matrix[row_i][col_i] = self.__predefined_blocks.pop(
                        (block_x_coord, block_y_coord))

# LAYER2: special block generation that can cross chunk borders

    def __add_special_blocks(
            self, chunk_coord: Union[Tuple[int, int], List[int]]) -> None:
        """Add blocks that span accros chunks but are not bigger then a chunk"""
        # check in a 3 * 3 around the chunk_coord if there is still special blocks to generate
        for chunk_row in \
                range(max(0, chunk_coord[1] - 1),
                      min(chunk_coord[1] + 2, ceil(con.ORIGINAL_BOARD_SIZE.height / con.CHUNK_SIZE.height) - 1)):
            for chunk_col in \
                    range(max(0, chunk_coord[0] - 1),
                          min(chunk_coord[0] + 2, ceil(con.ORIGINAL_BOARD_SIZE.width / con.CHUNK_SIZE.width) - 1)):
                if self.__generated_chunks_matrix[chunk_row][chunk_col] != 0:
                    continue
                rect = Rect((chunk_col * con.CHUNK_SIZE.width,
                             chunk_row * con.CHUNK_SIZE.height,
                             con.CHUNK_SIZE.width, con.CHUNK_SIZE.height))
                for row_i in range(int(rect.height / con.BLOCK_SIZE.height)):
                    for col_i in range(int(rect.width / con.BLOCK_SIZE.width)):
                        block_x_coord = int(
                            rect.left / con.BLOCK_SIZE.width) + col_i
                        block_y_coord = int(
                            rect.top / con.BLOCK_SIZE.height) + row_i
                        # determine biome based on coordinate

                        biome_liklyhoods = self.__biome_liklyhoods_from_point(
                            block_x_coord * con.BLOCK_SIZE.width,
                            block_y_coord * con.BLOCK_SIZE.height)
                        biome = choices(list(biome_liklyhoods.keys()),
                                        list(biome_liklyhoods.values()),
                                        k=1)[0]
                        # only add plants in caves
                        if self.__predefined_blocks.check((block_x_coord, block_y_coord), ["Air"]) \
                                and uniform(0, 1) < biome.FLORA_LIKELYHOOD:
                            flora_likelyhoods = biome.get_flora_lh_at_depth(
                                block_y_coord)
                            self.__add_environment(block_x_coord,
                                                   block_y_coord,
                                                   flora_likelyhoods)
                        elif uniform(0, 1) < biome.CLUSTER_LIKELYHOOD:
                            ore_likelyhoods = biome.get_ore_lh_at_depth(
                                block_y_coord)
                            self.__add_ore_cluster(block_x_coord,
                                                   block_y_coord,
                                                   ore_likelyhoods,
                                                   biome.MAX_CLUSTER_SIZE)
                self.__generated_chunks_matrix[chunk_row][chunk_col] = 1

    def __add_environment(self, block_x_coord: int, block_y_coord: int,
                          flora_likelyhoods: List[Dict[str, float]]) -> None:
        """Add environment blocks like plants"""
        s_coords = self.__get_surrounding_block_coords(block_x_coord,
                                                       block_y_coord)
        elligable_indexes = []
        for coord in s_coords:
            try:
                if coord is not None and \
                        not self.__predefined_blocks.check(coord, {"Air", *self.__environment_material_names}):
                    elligable_indexes.append(coord)
            except IndexError:
                # ignore coords outside the chunk to not cover them twice
                continue
        # if direction cant have a flora return
        if len(elligable_indexes) == 0:
            return
        chosen_index = s_coords.index(choice(elligable_indexes))
        # if the chosen index has no plant options return
        if len(flora_likelyhoods[chosen_index]) == 0:
            return
        flora = choices(list(flora_likelyhoods[chosen_index].keys()),
                        list(flora_likelyhoods[chosen_index].values()),
                        k=1)[0]
        self.__predefined_blocks.add((block_x_coord, block_y_coord), flora)

    def __add_ore_cluster(self, block_x_coord: int, block_y_coord: int,
                          ore_likelyhoods: Dict[str, float],
                          max_cluster_size: int) -> None:
        """Add ores in a cluster"""
        ore = choices(list(ore_likelyhoods.keys()),
                      list(ore_likelyhoods.values()),
                      k=1)[0]
        ore_locations = self.__create_ore_cluster(
            ore, (block_x_coord, block_y_coord), max_cluster_size)
        ore_locations.append([block_x_coord, block_y_coord])
        for loc in ore_locations:
            if self.__predefined_blocks.check((block_x_coord, block_y_coord),
                                              ["Air"]):
                continue
            self.__predefined_blocks.add(loc, ore, overwrite=False)

    def __create_ore_cluster(self, ore: str, center: Union[Tuple[int, int],
                                                           List[int]],
                             max_cluster_size: int) -> List[List[int]]:
        """Create a cluster of ores around a center up until a certain size is reached"""
        size = getattr(
            ground_materials,
            ore).get_cluster_size()  # -> this number is a random return
        ore_locations = []
        while len(ore_locations) <= size:
            location = [0, 0]
            for index in range(2):
                pos = choice([-1, 1])
                # assert index is bigger then 0
                location[index] = max(
                    0,
                    pos * randint(0, max_cluster_size) + center[index])
            if location not in ore_locations:
                ore_locations.append(location)
        return ore_locations

# LAYER3: filler blocks that fill in the rest of the chunk

    def __add_filler_blocks(self, rect: Rect, matrix: List[List],
                            background_matrix: List[List]) -> None:
        """Add blocks to all unfilled places in the matrix and to the background matrix"""
        for row_i in range(int(rect.height / con.BLOCK_SIZE.height)):
            for col_i in range(int(rect.width / con.BLOCK_SIZE.width)):
                block_x_coord = int(rect.left / con.BLOCK_SIZE.width) + col_i
                block_y_coord = int(rect.top / con.BLOCK_SIZE.height) + row_i
                block = matrix[row_i][col_i]
                # determine biome based on coordinate
                biome_liklyhoods = self.__biome_liklyhoods_from_point(
                    block_x_coord * con.BLOCK_SIZE.width,
                    block_y_coord * con.BLOCK_SIZE.height)
                biome = choices(list(biome_liklyhoods.keys()),
                                list(biome_liklyhoods.values()),
                                k=1)[0]
                if block is None:
                    filler_likelyhoods = biome.get_filler_lh_at_depth(
                        block_y_coord)
                    filler = choices(list(filler_likelyhoods.keys()),
                                     list(filler_likelyhoods.values()),
                                     k=1)[0]
                    matrix[row_i][col_i] = block_util.MCD(filler)
                # reget the biome to get slightly different front and backgrounds
                biome = choices(list(biome_liklyhoods.keys()),
                                list(biome_liklyhoods.values()),
                                k=1)[0]
                background_likelyhoods = biome.get_background_lh_at_depth(
                    block_y_coord)
                background_mat = choices(list(background_likelyhoods.keys()),
                                         list(background_likelyhoods.values()),
                                         k=1)[0]
                background_matrix[row_i][col_i] = block_util.MCD(
                    background_mat)

    def __add_border(self, matrix: List[List],
                     topleft: Union[Tuple[int, int], List[int]]) -> None:
        """add a border if neccesairy and determine the direction of set border"""
        if topleft[1] <= 0:
            self.__add_directional_border(matrix, "north")
        elif topleft[
                1] + con.CHUNK_SIZE.height >= con.ORIGINAL_BOARD_SIZE.height:
            self.__add_directional_border(matrix, "south")
        if topleft[0] <= 0:
            self.__add_directional_border(matrix, "west")
        elif topleft[0] + con.CHUNK_SIZE.width >= con.ORIGINAL_BOARD_SIZE.width:
            self.__add_directional_border(matrix, "east")

    def __add_directional_border(self, matrix: List[List],
                                 direction: str) -> None:
        """Add a border for a certain direction"""
        if direction == "north":
            rows = matrix[0:self.MAX_BORDER_SPREAD_DISTANCE]
            for row_i in range(len(rows)):
                border_block_chance = self.BORDER_SPREAD_LIKELYHOOD.cumulative_probability(
                    row_i)
                for col_i in range(len(matrix[row_i])):
                    if uniform(0, 1) < border_block_chance:
                        matrix[row_i][col_i] = block_util.MCD("BorderMaterial")
        elif direction == "south":
            rows = matrix[-(self.MAX_BORDER_SPREAD_DISTANCE + 1):-1]
            for row_i in range(len(rows)):
                border_block_chance = self.BORDER_SPREAD_LIKELYHOOD.cumulative_probability(
                    row_i)
                for col_i in range(len(matrix[row_i])):
                    if uniform(0, 1) < border_block_chance:
                        matrix[-(row_i + 1)][-(col_i + 1)] = block_util.MCD(
                            "BorderMaterial")
        elif direction == "west":
            for row_i in range(len(matrix)):
                for col_i in range(
                        len(matrix[row_i][0:self.MAX_BORDER_SPREAD_DISTANCE])):
                    border_block_chance = self.BORDER_SPREAD_LIKELYHOOD.cumulative_probability(
                        col_i)
                    if uniform(0, 1) < border_block_chance:
                        matrix[row_i][col_i] = block_util.MCD("BorderMaterial")
        elif direction == "east":
            for row_i in range(len(matrix)):
                for col_i in range(
                        len(matrix[row_i][-(self.MAX_BORDER_SPREAD_DISTANCE +
                                            1):-1])):
                    border_block_chance = self.BORDER_SPREAD_LIKELYHOOD.cumulative_probability(
                        col_i)
                    if uniform(0, 1) < border_block_chance:
                        matrix[-(row_i + 1)][-(col_i + 1)] = block_util.MCD(
                            "BorderMaterial")
        else:
            raise util.GameException(
                "Unrecognized direction for border: {}".format(direction))


# utility methods

    def __determine_cave_quadrant_size(self, caves_nr: int) -> util.Size:
        total_blocks = (con.ORIGINAL_BOARD_SIZE.width / con.BLOCK_SIZE.width) *\
                       (con.ORIGINAL_BOARD_SIZE.height / con.BLOCK_SIZE.height)
        total_caves = (total_blocks / 100_000) * caves_nr
        # Me brother : )
        cave_quadrant_side = int(
            sqrt((con.ORIGINAL_BOARD_SIZE.width *
                  con.ORIGINAL_BOARD_SIZE.height) / total_caves))
        return util.Size(cave_quadrant_side, cave_quadrant_side)

    def __biome_liklyhoods_from_point(
            self, x: int, y: int) -> Dict[biome_classes.Biome, float]:
        biome_matrix_col = int(x / self.__biome_size.width)
        biome_matrix_row = int(y / self.__biome_size.height)
        main_biome = self.__biome_matrix[biome_matrix_row][biome_matrix_col]
        surrounding_biomes = [main_biome]
        # collect all surrounding biomes
        for new_position in [(0, -1), (1, 0), (0, 1), (-1, 0)]:
            surrounding_coord = [
                biome_matrix_col + new_position[0],
                biome_matrix_row + new_position[1]
            ]
            if surrounding_coord[0] >= len(self.__biome_matrix[0]) - 1 or surrounding_coord[0] < 0 \
                    or surrounding_coord[1] >= len(self.__biome_matrix) - 1 or surrounding_coord[1] < 0:
                continue
            surrounding_biomes.append(self.__biome_matrix[surrounding_coord[1]]
                                      [surrounding_coord[0]])
        surrounding_biomes = [b for b in surrounding_biomes if b is not None]
        wheights = util.normalize(
            [b.get_likelyhood_at_coord(x, y) for b in surrounding_biomes])
        biome_likelyhoods = {
            biome: wheights[index]
            for index, biome in enumerate(surrounding_biomes)
        }
        return biome_likelyhoods

    def __get_surrounding_block_coords(self, x: int,
                                       y: int) -> List[Union[List[int], None]]:
        surrounding_coords = [None for _ in range(4)]
        for index, new_position in enumerate([(0, -1), (1, 0), (0, 1),
                                              (-1, 0)]):
            surrounding_coord = [x + new_position[0], y + new_position[1]]
            # Make sure within range
            if surrounding_coord[0] >= con.ORIGINAL_BOARD_SIZE.width / con.BLOCK_SIZE.width or \
                    surrounding_coord[0] < 0 or \
                    surrounding_coord[1] >= con.ORIGINAL_BOARD_SIZE.height / con.BLOCK_SIZE.height or \
                    surrounding_coord[1] < 0:
                continue
            surrounding_coords[
                index] = surrounding_coord  # noqa --> because there seems no reason for complaint here
        return surrounding_coords