예제 #1
0
파일: bundle.py 프로젝트: mseyne/bansoko
def load_bundle(metadata_filename: str) -> Bundle:
    """Load game resources into bundle using metadata file.

    :param metadata_filename: name of the metadata file
    :return: bundle with game resources
    """
    try:
        with open(metadata_filename) as metadata_file:
            metadata = load(metadata_file)
            validate(metadata, METADATA_JSON_SCHEMA)
            sprites = create_sprites(metadata["sprites"])
            sprite_packs = create_sprite_packs(metadata["sprite_packs"],
                                               sprites)
            screens = create_screens(metadata["screens"], sprites)
            gui_consts = create_gui_consts(metadata["gui_consts"], sprites)
            sha1 = bytearray(
                metadata["levels"]["sha1"],
                "utf-8").zfill(SHA1_SIZE_IN_BYTES)[-SHA1_SIZE_IN_BYTES:]
            level_templates = create_level_templates(
                metadata["levels"]["level_templates"], sprite_packs)
            return Bundle(sha1, sprites, sprite_packs, screens, gui_consts,
                          level_templates)
    except JSONDecodeError as decode_error:
        raise GameError(
            "Incorrect format of resource metadata file") from decode_error
    except ValidationError as validation_error:
        raise GameError(
            "Incorrect format of resource metadata file") from validation_error
예제 #2
0
def _load_profile_file(profile_file_path: Path, bundle: Bundle) -> PlayerProfile:
    try:
        logging.info("Loading existing player profile file '%s'", profile_file_path)
        with open(profile_file_path, "r+b") as profile_file:
            header = profile_file.read(len(FILE_HEADER))
            if header != FILE_HEADER:
                raise GameError("File is not a valid player profile file")

            while True:
                sha1 = profile_file.read(SHA1_SIZE_IN_BYTES)
                if not sha1:
                    break

                section_size = _read_int(profile_file)

                if sha1 == bundle.sha1:
                    return _read_player_profile(profile_file_path, profile_file, bundle)

                profile_file.seek(section_size, 1)

            return _init_player_profile(profile_file_path, profile_file, bundle)
    except IOError as io_error:
        raise GameError("Unable to open player profile file") from io_error
    except EOFError as eof_error:
        raise GameError("Unexpected end of player profile file") from eof_error
예제 #3
0
    def complete_level(self, level_score: LevelScore) -> LevelScore:
        """Save information about level completion to profile file. Additionally, as a reward,
        unlock next level.

        :param level_score: score of level completion
        :return: the previous score for the completed level
        """
        logging.info("Updating player profile file with game progress")

        if not self.is_level_completed(level_score.level_num):
            level_to_be_unlocked = self._last_unlocked_level + 1
            if self._can_unlock_level(level_to_be_unlocked):
                self._last_unlocked_level = level_to_be_unlocked

        prev_level_score = self.levels_scores[level_score.level_num]
        new_level_score = prev_level_score.merge_with(level_score)
        self.levels_scores[level_score.level_num] = new_level_score

        try:
            with open(self._profile_file_path, "r+b") as profile_file:
                profile_file.seek(self._file_offset)
                _write_int(profile_file, self._last_unlocked_level)
                profile_file.seek(new_level_score.level_num * LEVEL_SCORE_SIZE_IN_BYTES, 1)
                _write_int(profile_file, 1 if new_level_score.completed else 0)
                _write_int(profile_file, new_level_score.steps)
                _write_int(profile_file, new_level_score.pushes)
                _write_int(profile_file, new_level_score.time_in_ms)
        except IOError as io_error:
            raise GameError(
                f"Unable to update player profile file '{self._profile_file_path}'. "
                "Progress lost :-(") from io_error

        return prev_level_score
예제 #4
0
def _create_profile_file(profile_file_path: Path, bundle: Bundle) -> PlayerProfile:
    try:
        logging.info("Creating new player profile file '%s'", profile_file_path)
        with open(profile_file_path, "wb") as profile_file:
            profile_file.write(FILE_HEADER)
            return _init_player_profile(profile_file_path, profile_file, bundle)
    except IOError as io_error:
        raise GameError("Unable to create player profile file") from io_error
예제 #5
0
파일: __main__.py 프로젝트: mseyne/bansoko
def load_game_resources(filenames: FileNames) -> Bundle:
    """Load Pyxel's resource file containing bundle."""
    logging.info("Loading Pyxel resources file '%s'", filenames.resource_file)
    if not os.path.isfile(filenames.resource_file):
        # This is the only way we can pre-check whether pyxel.load() will fail or not
        # In current version of Pyxel it's not possible to react to error or capture the error
        # reason
        raise GameError(
            f"Unable to find Pyxel resource file '{filenames.resource_file}'")
    pyxel.load(filenames.resource_file)

    logging.info("Loading resources metadata file '%s'",
                 filenames.metadata_file)
    if not os.path.isfile(filenames.metadata_file):
        raise GameError(
            f"Unable to find resources metadata file '{filenames.metadata_file}'"
        )

    return load_bundle(filenames.metadata_file)
예제 #6
0
    def create_robot(self) -> Robot:
        """Create robot instance based on information from tilemap about its start position."""
        start = None
        for tile_position in self.tilemap.tiles_positions():
            if self.tile_at(tile_position).is_start:
                start = tile_position
        if not start:
            raise GameError(
                f"Level {self.level_num} does not have player start tile")

        face_direction = Direction.UP
        for direction in list(Direction):
            if self.tile_at(start.move(direction)).is_walkable:
                face_direction = direction
                break

        return Robot(start, face_direction, self.sprite_packs.robot_animations)
예제 #7
0
    def create_crates(self) -> Tuple[Crate, ...]:
        """Create a collection of crates based on information from tilemap about their initial
        positions.

        :return: collection of crates created from level template
        """
        crates_positions = []
        for tile_position in self.tilemap.tiles_positions():
            if self.tile_at(tile_position).is_crate_spawn_point:
                crates_positions.append(tile_position)

        crates = []
        for crate_position in crates_positions:
            is_initially_placed = self.tile_at(
                crate_position).is_crate_initially_placed
            crates.append(
                Crate(crate_position, is_initially_placed,
                      self.sprite_packs.crate_sprites))
        if not crates:
            raise GameError(f"Level {self.level_num} does not have any crates")

        return tuple(crates)
예제 #8
0
    def merge_with(self, level_score: "LevelScore") -> "LevelScore":
        """Merge this level score with given score.

        In case of differences in field values prefer better score
        (smaller number of steps, pushes, quicker completion time)

        :param level_score: level score to merge with
        :return: newly created level score which is a result of merge
        """
        if self == level_score:
            return level_score
        if self.level_num != level_score.level_num:
            raise GameError("Cannot merge scores from different levels")

        if not self.completed:
            return level_score

        return LevelScore(
            level_num=self.level_num,
            completed=self.completed,
            pushes=min(self.pushes, level_score.pushes),
            steps=min(self.steps, level_score.steps),
            time_in_ms=min(self.time_in_ms, level_score.time_in_ms))