Ejemplo n.º 1
0
    def test_hash(self):
        stone = Block(blockstate="minecraft:stone")
        water = Block(blockstate="minecraft:water[level=1]")
        granite = Block(blockstate="minecraft:granite")
        dirt = Block(blockstate="minecraft:dirt")

        conglomerate_1 = stone + water + dirt
        conglomerate_2 = stone + dirt + water

        self.assertNotEqual(conglomerate_1, conglomerate_2)
        self.assertNotEqual(hash(conglomerate_1), hash(conglomerate_2))

        conglomerate_3 = dirt + water + dirt
        conglomerate_4 = dirt + dirt + water

        self.assertNotEqual(conglomerate_3, conglomerate_4)
        self.assertNotEqual(hash(conglomerate_3), hash(conglomerate_4))

        conglomerate_5 = conglomerate_1 + granite
        conglomerate_6 = conglomerate_3 + granite

        self.assertNotEqual(conglomerate_1, conglomerate_5)
        self.assertNotEqual(conglomerate_3, conglomerate_6)
        self.assertNotEqual(conglomerate_5, conglomerate_6)
        self.assertNotEqual(hash(conglomerate_5), hash(conglomerate_6))
Ejemplo n.º 2
0
    def test_get_from_blockstate(self):  # This is mostly just sanity checks
        air = Block(blockstate="minecraft:air")

        self.assertIsInstance(air, Block)
        self.assertEqual("minecraft", air.namespace)
        self.assertEqual("air", air.base_name)
        self.assertEqual({}, air.properties)
        self.assertEqual((), air.extra_blocks)
        self.assertEqual("minecraft:air", air.blockstate)

        stone = Block(blockstate="minecraft:stone")

        self.assertIsInstance(stone, Block)
        self.assertEqual("minecraft", stone.namespace)
        self.assertEqual("stone", stone.base_name)
        self.assertEqual({}, stone.properties)
        self.assertEqual((), stone.extra_blocks)
        self.assertEqual("minecraft:stone", stone.blockstate)

        self.assertNotEqual(air, stone)

        oak_leaves = Block(
            blockstate="minecraft:oak_leaves[distance=1,persistent=true]")

        self.assertIsInstance(oak_leaves, Block)
        self.assertEqual("minecraft", oak_leaves.namespace)
        self.assertEqual("oak_leaves", oak_leaves.base_name)
        self.assertEqual({
            "distance": "1",
            "persistent": "true"
        }, oak_leaves.properties)
        self.assertEqual((), oak_leaves.extra_blocks)
        self.assertEqual("minecraft:oak_leaves[distance=1,persistent=true]",
                         oak_leaves.blockstate)
Ejemplo n.º 3
0
    def get_blockstate(self, blockstate: str) -> Block:
        """
        Converts a version-specific blockstate string into a :class:`api.blocks.Block` object by parsing the blockstate
        and handling any addition logic that needs to be done (IE: Adding an extra block when `waterlogged=true` for
        Java edition). This method is replaced at runtime with the version specific handler.

        :param blockstate: The blockstate string to parse/convert
        :return: The resulting Block object
        """

        namespace, base_name, properties = Block.parse_blockstate_string(blockstate)
        return Block(namespace=namespace, base_name=base_name, properties=properties)
Ejemplo n.º 4
0
    def test_extra_blocks_immutable(self):
        stone = Block(blockstate="minecraft:stone")
        dirt = Block(blockstate="minecraft:dirt")

        stone2 = stone
        self.assertIs(stone, stone2)
        stone2 += dirt
        self.assertIsNot(stone, stone2)

        stone3 = stone2
        self.assertIs(stone2, stone3)
        stone3 -= dirt
        self.assertIsNot(stone, stone3)
Ejemplo n.º 5
0
    def setUp(self):
        self.manager = BlockManager()

        initial_dirt = Block(blockstate="minecraft:dirt")
        initial_stone = Block(blockstate="minecraft:stone")
        initial_granite = Block(blockstate="minecraft:granite")

        initial_dirt_water = initial_dirt + Block(blockstate="minecraft:water")

        # Partially populate the manager
        self.manager.add_block(initial_dirt)
        self.manager.add_block(initial_stone)
        self.manager.add_block(initial_granite)
        self.manager.add_block(initial_dirt_water)
Ejemplo n.º 6
0
def parse_blockstate(blockstate: str) -> Block:
    namespace, base_name, properties = Block.parse_blockstate_string(blockstate)

    if properties.pop("waterlogged", "false").lower() == "true":
        block = Block(
            namespace=namespace,
            base_name=base_name,
            properties=properties,
            extra_blocks=(_WATER_CONSTANT,),
        )
    else:
        block = Block(namespace=namespace, base_name=base_name, properties=properties)

    return block
Ejemplo n.º 7
0
    def test_get_block_from_manager(self):
        dirt = Block(blockstate="minecraft:dirt")
        stone = Block(blockstate="minecraft:stone")
        granite = Block(blockstate="minecraft:granite")
        water = Block(blockstate="minecraft:water")
        dirt_water = dirt + water

        self.assertEqual(dirt, self.manager[0])
        self.assertEqual(stone, self.manager[1])
        self.assertEqual(granite, self.manager[2])
        self.assertEqual(dirt_water, self.manager[3])

        with self.assertRaises(KeyError):
            brain_coral = Block(blockstate="minecraft:brain_coral")
            internal_id = self.manager[brain_coral]
Ejemplo n.º 8
0
    def test_extra_blocks(self):
        stone = Block(blockstate="minecraft:stone")
        water = Block(blockstate="minecraft:water[level=1]")
        granite = Block(blockstate="minecraft:granite")
        dirt = Block(blockstate="minecraft:dirt")

        conglomerate_1 = stone + water + dirt
        self.assertIsInstance(conglomerate_1, Block)
        self.assertEqual("minecraft", conglomerate_1.namespace)
        self.assertEqual("stone", conglomerate_1.base_name)
        self.assertEqual({}, conglomerate_1.properties)
        self.assertEqual(2, len(conglomerate_1.extra_blocks))
        for block_1, block_2 in zip(conglomerate_1.extra_blocks,
                                    (water, dirt)):
            self.assertEqual(block_1, block_2)
            self.assertEqual(0, len(block_1.extra_blocks))

        conglomerate_2 = conglomerate_1 + granite
        self.assertIsInstance(conglomerate_2, Block)
        self.assertEqual("minecraft", conglomerate_2.namespace)
        self.assertEqual("stone", conglomerate_2.base_name)
        self.assertEqual({}, conglomerate_2.properties)
        self.assertEqual(3, len(conglomerate_2.extra_blocks))
        for block_1, block_2 in zip(conglomerate_2.extra_blocks,
                                    (water, dirt, granite)):
            self.assertEqual(block_1, block_2)
            self.assertEqual(0, len(block_1.extra_blocks))

        self.assertNotEqual(conglomerate_1, conglomerate_2)

        conglomerate_3 = conglomerate_2 - dirt
        self.assertIsInstance(conglomerate_3, Block)
        self.assertEqual("minecraft", conglomerate_3.namespace)
        self.assertEqual("stone", conglomerate_3.base_name)
        self.assertEqual({}, conglomerate_3.properties)
        self.assertEqual(2, len(conglomerate_3.extra_blocks))
        for block_1, block_2 in zip(conglomerate_3.extra_blocks,
                                    (water, granite)):
            self.assertEqual(block_1, block_2)
            self.assertEqual(0, len(block_1.extra_blocks))

        self.assertRaises(TypeError, lambda: stone + 1)
        self.assertRaises(TypeError, lambda: stone - 1)
Ejemplo n.º 9
0
    def test_get_index_from_manager(self):
        dirt = Block(blockstate="minecraft:dirt")
        stone = Block(blockstate="minecraft:stone")
        granite = Block(blockstate="minecraft:granite")

        self.assertEqual(0, self.manager[dirt])
        self.assertEqual(1, self.manager[stone])
        self.assertEqual(2, self.manager[granite])

        water = Block(blockstate="minecraft:water")

        dirt_water = dirt + water

        self.assertNotEqual(dirt, dirt_water)
        self.assertIsNot(dirt, dirt_water)
        self.assertEqual(3, self.manager[dirt_water])

        with self.assertRaises(KeyError):
            random_block = self.manager[10000]
Ejemplo n.º 10
0
    def make_children(self, children, piece, held, next_can_hold, put_hold):

        maximum = float('-inf')
        ind = -1

        for i in range(len(children)):
            # unpack that child
            x, y, rot, moveset = children[i]

            # create new board and execute moves
            new_board = self.board.get_copy()

            new_piece = Block(piece)
            new_piece.execute_moves(moveset, self.board)
            new_piece.set(new_board)

            # calculate points
            new_lines = self.lines_sent
            combo_id = -1
            new_combos_so_far = self.combos_so_far + 1

            lines_cleared = new_board.clear_lines()

            if lines_cleared == 0:
                new_combos_so_far = 0
            else:
                is_tspin = tspin_detect(moveset)
                is_perf_clear = perf_clear_detect(new_board)
                combo_id = 1 if is_tspin else 0 if is_perf_clear else -1

                if is_tspin and is_perf_clear:
                    combo_id = 2

                new_lines += next_points(
                    self.combos_so_far - 1,
                    lines_cleared if lines_cleared != 4 else 0,
                    lines_cleared == 4, is_tspin, is_perf_clear,
                    combo_id == self.last_combo_id)

            # create the new node
            new_node = Tree_node(new_board, held, new_lines, new_combos_so_far,
                                 combo_id, next_can_hold)

            self.add_child(
                ([encode_move('hold')] if put_hold else []) + moveset,
                new_node)

            ranking = new_node.get_rank()
            if ranking > maximum:
                maximum = ranking
                ind = i

        return (maximum, ind)
Ejemplo n.º 11
0
        def test_fill_operation(self):
            with timeout(self, 0.25, show_completion_time=True):
                subbox_1 = SubBox((1, 70, 3), (5, 71, 5))
                box = SelectionBox((subbox_1,))

                # Start sanity check
                self.assertEqual(
                    "minecraft:stone", self.world.get_block(1, 70, 3).blockstate
                )
                self.assertEqual(
                    "minecraft:granite", self.world.get_block(1, 70, 5).blockstate
                )
                # End sanity check

                self.world.run_operation_from_operation_name(
                    "fill", box, Block("minecraft:stone")
                )

                for x, y, z in box:
                    self.assertEqual(
                        "minecraft:stone",
                        self.world.get_block(x, y, z).blockstate,
                        f"Failed at coordinate ({x},{y},{z})",
                    )

                self.world.undo()

                self.assertEqual(
                    "minecraft:stone", self.world.get_block(1, 70, 3).blockstate
                )

                self.assertEqual(
                    "minecraft:granite", self.world.get_block(1, 70, 5).blockstate
                )

                self.world.redo()

                for x, y, z in box:
                    self.assertEqual(
                        "minecraft:stone",
                        self.world.get_block(x, y, z).blockstate,
                        f"Failed at coordinate ({x},{y},{z})",
                    )
Ejemplo n.º 12
0
        def test_fill_operation(self):
            subbox_1 = SubBox((1, 70, 3), (5, 71, 5))
            box = SelectionBox((subbox_1, ))

            self.world.run_operation_from_operation_name(
                "fill", box, Block("minecraft:stone"))

            for x, y, z in box:
                self.assertEqual(
                    "minecraft:stone",
                    self.world.get_block(x, y, z).blockstate,
                    f"Failed at coordinate ({x},{y},{z})",
                )

            self.world.undo()

            self.assertEqual("minecraft:stone",
                             self.world.get_block(1, 70, 3).blockstate)
            self.assertEqual("minecraft:granite",
                             self.world.get_block(1, 70, 5).blockstate)
Ejemplo n.º 13
0
	def make_nodes(self, children, piece_type, held_piece, q, is_held):

		for child in children:

			x, y, rot, moveset = child

			new_board = self.board.get_copy()

			new_piece = Block(piece_type)
			new_piece.execute_moves(moveset, self.board)
			new_piece.set(new_board)

			new_lines = self.lines_sent
			combo_id = -1
			new_combos_so_far = self.combos_so_far + 1

			lines_cleared = new_board.clear_lines()
			if lines_cleared == 0:
				new_combos_so_far = 0
			else:
				is_tspin = tspin_detect(moveset)
				is_perf_clear = perf_clear_detect(new_board)
				combo_id = 1 if is_tspin else 0 if is_perf_clear else -1

				if is_tspin and is_perf_clear:
					combo_id = 2

				new_lines += next_points(self.combos_so_far - 1,
										 lines_cleared if lines_cleared != 4 else 0,
										 lines_cleared == 4,
										 is_tspin,
										 is_perf_clear,
										 combo_id == self.last_combo_id)


			nq = [] if len(q) < 2 else deepcopy(q[1:])

			new_node = Tree_node(new_board,
								-1 if len(q) == 0 else q[0],
								held_piece,
								nq,
								new_lines,
								new_combos_so_far,
								combo_id,
								self.ranker,
								is_held)

			self.add_child(([encode_move('hold')] if is_held else []) + moveset, new_node)
Ejemplo n.º 14
0
def generate_successor_states(board, piece_type):
	# number of unique rotations per piece
	r = valid_rotations(piece_type)

	# store all possible positions in a queue
	pos = Queue()
	# 3D memo for later ;)
	memo = [[[0 for z in range(r)] for y in range(board.get_height())] for x in range(board.get_width())]

	# for each unique rotation
	for rotation in range(r):
		# construct a temporary piece
		temp_piece = Block(piece_type)
		temp_piece.set_rotation(board, rotation)


		# get the next offset after rotating
		sx = temp_piece.get_offset()[0]

		# for each horizontal position
		for x in range(board.get_width() - temp_piece.get_width() + 1):

			# shift the piece horizontally
			temp_piece.set_offset(x, 0)
			if temp_piece.collides(board):
				continue

			# drop
			temp_piece.drop(board)

			# get final position
			tx, ty = temp_piece.get_offset()

			# memoize
			memo[tx][ty][rotation] = 1

			#print(str(tx) + ", " + str(ty) + ", " + str(rotation))

			# encode moves
			moves = [encode_move('crot') for i in range(rotation)] + [encode_move('left') if x - sx < 0 else encode_move('right') for i in range(abs(x-sx))] + [encode_move('drop')]

			# enqueue
			pos.put((tx, ty, rotation, moves))

	# the final set to return
	children = []

	# while the queue still contains positions
	while not pos.empty():

		child = pos.get()

		# add to final bag
		children.append(child)

		x, y, rot, moves = child

		# make a block and put it into the correct place
		test_piece = Block(piece_type)
		test_piece.execute_moves(moves, board)

		o_off_x, o_off_y = test_piece.get_offset()

		# generate partial movements from this position, i.e. left, right, and all rotations
		# stored in tuples like so (dx, dy, nr)
		next_positions = [(1, 0, rot), (-1, 0, rot)] + [(0,0,i) for i in range(r)]

		# for each partial movement
		for npos in next_positions:
			# quick access variables
			dx, dy, nr = npos

			# rotate the piece for the new rotation, if possibe, else its invalid so skip
			if not test_piece.set_rotation(board, nr):
				continue

			offset = test_piece.get_offset()

			# translate the piece right or left or skip if invalid
			if (dx > 0 and not test_piece.r_translate(board)) or (dx < 0 and not test_piece.l_translate(board)):
				continue

			# apply gravity
			down = test_piece.drop(board)

			# get updated locations
			nx, ny = test_piece.get_offset()

			# check that the move was not already encoded
			if memo[nx][ny][nr] == 1:
				test_piece.dirty_reset_position(o_off_x, o_off_y, rot)
				continue

			# now encode additional movements
			# copy moves and convert drops to down movements because this is more meticulous

			nmoves = deepcopy(moves)

			# convert drops to down moves
			l = len(moves) - 1
			if moves[l] == encode_move('drop'):
				nmoves = nmoves[:l] + [encode_move('down') for i in range(y)]

			# generate additional horizontal movements
			if dx != 0:
				nmoves.append(encode_move('left') if dx == -1 else encode_move('right'))

			# generate rotation movements
			dr = nr - rot
			#print("rotations:",dr)
			if rot == 3 and nr == 0:
				nmoves += [encode_move('crot')]
			elif dr != 0:
				nmoves += [encode_move('crot') if dr > 0 else encode_move('ccrot') for i in range(abs(dr))]

			# generate additional down movements
			nmoves += [encode_move('down') for i in range(down)]

			# enqueue
			pos.put((nx, ny, nr, nmoves))

			# mark this new space as visited, too
			memo[nx][ny][nr] = 1

			# undo moves
			test_piece.dirty_reset_position(o_off_x, o_off_y, rot)

	return children
Ejemplo n.º 15
0
    def get_blocks(self, cx: int,
                   cz: int) -> Union[numpy.ndarray, NotImplementedError]:
        chunk_sections, _, _ = self._region_manager.load_chunk(cx, cz)
        if len(chunk_sections) == 0:
            return NotImplementedError(
                "We don't support reading chunks that never been edited in Minecraft before"
            )

        blocks = numpy.zeros((256, 16, 16), dtype=int)
        block_data = numpy.zeros((256, 16, 16), dtype=numpy.uint8)
        for section in chunk_sections:
            lower = section["Y"].value << 4
            upper = (section["Y"].value + 1) << 4

            section_blocks = numpy.frombuffer(section["Blocks"].value,
                                              dtype=numpy.uint8)
            section_data = numpy.frombuffer(section["Data"].value,
                                            dtype=numpy.uint8)
            section_blocks = section_blocks.reshape((16, 16, 16))
            section_blocks.astype(numpy.uint16, copy=False)

            section_data = section_data.reshape(
                (16, 16, 8)
            )  # The Byte array is actually just Nibbles, so the size is off

            section_data = world_utils.from_nibble_array(section_data)

            if "Add" in section:
                add_blocks = numpy.frombuffer(section["Add"].value,
                                              dtype=numpy.uint8)
                add_blocks = add_blocks.reshape((16, 16, 8))
                add_blocks = world_utils.from_nibble_array(add_blocks)

                section_blocks |= add_blocks.astype(numpy.uint16) << 8

            blocks[lower:upper, :, :] = section_blocks
            block_data[lower:upper, :, :] = section_data

        blocks = numpy.swapaxes(blocks.swapaxes(0, 1), 0, 2)
        block_data_array = numpy.swapaxes(block_data.swapaxes(0, 1), 0, 2)

        unique_block_ids = numpy.unique(
            blocks
        )  # Flatten the 3D array into 1D and remove all duplicate entries
        # unique_block_ids = unique_block_ids[
        #    unique_block_ids != 0
        # ]  # Remove all air entries

        unique_blocks = set()
        for (block_id) in (
                unique_block_ids
        ):  # Find all instances of the base ID and find any occurrences of data values
            indices = numpy.where(blocks == block_id)

            for block_data in numpy.unique(block_data_array[indices]):
                unique_blocks.add((block_id, block_data))

        block_test = numpy.zeros_like(blocks, dtype=int)
        for block in unique_blocks:
            internal = self._materials.get_block_from_definition(
                block, default="minecraft:unknown_{}")
            block_object: Block = self.get_blockstate(internal)
            if (
                    internal == "minecraft:unknown_{}"
            ):  # If we don't have the block in our definitions, call it an unknown block
                try:
                    internal_id = list(
                        self.unknown_blocks.values()).index(block)
                except ValueError:
                    block_object: Block = Block(
                        blockstate=
                        f"minecraft:unknown_{len(self.unknown_blocks)}")
                    internal_id = self.block_manager.add_block(block_object)
                    self.unknown_blocks[internal_id] = block
            else:
                internal_id = self.block_manager.add_block(
                    block_object
                )  # Find the index of the block in mapping_handler

            block_mask = blocks == block[0]
            data_mask = block_data_array == block[1]

            mask = (
                block_mask & data_mask
            )  # Combine the mask from the base ID array and the data value array

            block_test[
                mask] = internal_id  # Mask all occurrences and set them to the internal ID

        block_test = block_test.astype(f"uint{get_smallest_dtype(block_test)}"
                                       )  # Shrink the array's dtype as needed
        return block_test
Ejemplo n.º 16
0
    leveldat_root = load_leveldat(directory)

    if "FML" in leveldat_root:
        return False

    # 1444 is the version for 17w43a snapshot (first 1.13 snapshot)
    # 1519 is the version for the 1.13 release version
    # 1628 is the version for the 1.13.1 release version
    # if not check_version_leveldat(leveldat_root, _min=1444, _max=1628):
    if not check_version_leveldat(leveldat_root, _min=1444):
        return False

    return True


_WATER_CONSTANT = Block(blockstate="minecraft:water")


def parse_blockstate(blockstate: str) -> Block:
    namespace, base_name, properties = Block.parse_blockstate_string(
        blockstate)

    if properties.pop("waterlogged", "false").lower() == "true":
        block = Block(
            namespace=namespace,
            base_name=base_name,
            properties=properties,
            extra_blocks=(_WATER_CONSTANT, ),
        )
    else:
        block = Block(namespace=namespace,
Ejemplo n.º 17
0
    def render(self):
        matrix = self.matrix
        w = self.width
        h = self.height

        #draw held piece
        for x in range(w):
            for y in range(1, int(h / 2)):
                pygame.draw.rect(
                    self.screen, (200, 200, 200),
                    pygame.Rect((TOTAL_PIECE_WIDTH) * x,
                                (TOTAL_PIECE_HEIGHT) * y, PIECE_W, PIECE_H))

        #locate held_piece
        held_piece = self.board.get_held()
        if held_piece != None:
            color = block_color(held_piece)
            piece = Block(held_piece)
            hx = piece.get_spawn(w)
            hy = int(h / 4)

            for x in range(piece.get_width()):
                for y in range(piece.get_height()):
                    if piece.loc_mat[x][y] == 1:
                        pygame.draw.rect(
                            self.screen, color,
                            pygame.Rect((TOTAL_PIECE_WIDTH) * (hx + x),
                                        (TOTAL_PIECE_HEIGHT) * (hy + y),
                                        PIECE_W, PIECE_H))

        #draw game board
        cp = self.board.get_current()

        for x in range(w):
            for y in range(1, h):
                piece = matrix.lookup(x, y)

                if cp.intersects(x, y):
                    color = cp.get_color()
                else:
                    color = block_color(piece) if piece != 0 else GRAY

                pygame.draw.rect(
                    self.screen, color,
                    pygame.Rect((TOTAL_PIECE_WIDTH) * (w + x),
                                (TOTAL_PIECE_HEIGHT) * y, PIECE_W, PIECE_H))

        #draw next pieces
        for x in range(2 * w, 3 * w):
            for y in range(1, h):
                pygame.draw.rect(
                    self.screen, (200, 200, 200),
                    pygame.Rect((TOTAL_PIECE_WIDTH) * x,
                                (TOTAL_PIECE_HEIGHT) * y, PIECE_W, PIECE_H))

        q = list(self.board.next_queue)

        ny = 2
        for next_piece in q[:5]:
            color = block_color(next_piece + 1)
            piece = Block(next_piece)
            nx = piece.get_spawn(w)

            for x in range(piece.get_width()):
                for y in range(piece.get_height()):
                    if piece.loc_mat[x][y] == 1:
                        pygame.draw.rect(
                            self.screen, color,
                            pygame.Rect((TOTAL_PIECE_WIDTH) * (2 * w + nx + x),
                                        (TOTAL_PIECE_HEIGHT) * (ny + y),
                                        PIECE_W, PIECE_H))
            ny += 4
Ejemplo n.º 18
0
    def test_remove_layer(self):
        stone = Block(blockstate="minecraft:stone")
        water = Block(blockstate="minecraft:water[level=1]")
        granite = Block(blockstate="minecraft:granite")
        dirt = Block(blockstate="minecraft:dirt")
        oak_log_axis_x = Block(blockstate="minecraft:oak_log[axis=x]")

        conglomerate_1 = stone + water + dirt + dirt + granite
        self.assertIsInstance(conglomerate_1, Block)
        self.assertEqual("minecraft", conglomerate_1.namespace)
        self.assertEqual("stone", conglomerate_1.base_name)
        self.assertEqual({}, conglomerate_1.properties)
        self.assertEqual(4, len(conglomerate_1.extra_blocks))
        for block_1, block_2 in zip(conglomerate_1.extra_blocks,
                                    (water, dirt, dirt, granite)):
            self.assertEqual(block_1, block_2)
            self.assertEqual(0, len(block_1.extra_blocks))

        new_conglomerate = conglomerate_1.remove_layer(2)
        for block_1, block_2 in zip(new_conglomerate.extra_blocks,
                                    (water, dirt, granite)):
            self.assertEqual(block_1, block_2)
            self.assertEqual(0, len(block_1.extra_blocks))

        conglomerate_2 = granite + water + stone + dirt + oak_log_axis_x
        self.assertIsInstance(conglomerate_2, Block)
        self.assertEqual("minecraft", conglomerate_2.namespace)
        self.assertEqual("granite", conglomerate_2.base_name)
        self.assertEqual({}, conglomerate_2.properties)
        self.assertEqual(4, len(conglomerate_2.extra_blocks))
        for block_1, block_2 in zip(conglomerate_2.extra_blocks,
                                    (water, stone, dirt, oak_log_axis_x)):
            self.assertEqual(block_1, block_2)
            self.assertEqual(0, len(block_1.extra_blocks))

        new_conglomerate = conglomerate_2.remove_layer(3)
        self.assertEqual(3, len(new_conglomerate.extra_blocks))
        for block_1, block_2 in zip(new_conglomerate.extra_blocks,
                                    (water, stone, oak_log_axis_x)):
            self.assertEqual(block_1, block_2)
            self.assertEqual(0, len(block_1.extra_blocks))

        new_base = conglomerate_2.remove_layer(0)
        self.assertEqual(3, len(new_base.extra_blocks))
        self.assertEqual("minecraft", new_base.namespace)
        self.assertEqual("water", new_base.base_name)
        self.assertEqual({"level": "1"}, new_base.properties)
        for block_1, block_2 in zip(new_base.extra_blocks,
                                    (stone, dirt, oak_log_axis_x)):
            self.assertEqual(block_1, block_2)
            self.assertEqual(0, len(block_1.extra_blocks))

        self.assertNotEqual(new_base, new_base.remove_layer(1))

        with self.assertRaises(InvalidBlockException):
            no_block = granite.remove_layer(0)

        with self.assertRaises(InvalidBlockException):
            non_present_layer = granite.remove_layer(7)
            non_present_layer = conglomerate_2.remove_layer(5)

        conglomerate_2.remove_layer(
            4)  # Check if last layer can still be removed