Path of Survival

CatΓ©gorie: Misc DifficultΓ©: hard Flag: HTB{i_h4v3_mY_w3ap0n_n0w_dIjKStr4!!!}

Challenge

Description


Far off in the distance, you hear a howl. Your blood runs cold as you realise the Kara'ka-ran have been released - vicious animals tortured beyond all recognition, starved to provide a natural savagery. They will chase you until the ends of the earth; your only chance of survival lies in a fight. Strong but not stupid, they will back off if they see you take down some of their number - briefly, anyway...

Analyse et objectif

Toutes les rΓ¨gles sont Γ©noncΓ©s sur le site et l’API est documentΓ©e. Il faut donc simplement les implΓ©menter en local afin de tester tous les chemins possibles jusqu’à en trouver un qui respecte toutes les rΓ¨gles.

Pour une question de temps, il faut Γ©galement optimiser un minimum cette recherche en Γ©vitant par exemple les chemins inutiles (comme faire des aller retour en 2 tuiles)

Script de rΓ©solution

import requests
from enum import Enum
from colorama import Fore, Style

class TileType(Enum):
    CLIFF = 'C'
    GEYSER = 'G'
    MOUNTAIN = 'M'
    PLAINS = 'P'
    RIVER = 'R'
    SAND = 'S'
    EMPTY = 'E'

class Tile:
    def __init__(self, raw):
        self.has_weapon = raw['has_weapon']
        self.type = TileType(raw['terrain'])

    def __str__(self):
        weapon = 'X' if self.has_weapon else ' '
        output = f"{self.type.value}[{weapon}]"
        return output

class Player:

    def __init__(self, x, y, time, game):
        self.x = x
        self.y = y
        self.time = time
        self.game: Game = game
        self.failed = False

    def copy(self):
        return Player(self.x, self.y, self.time, self.game)

    def move(self, direction):
        old_tile = self.game.get_tile(self.x, self.y)

        if direction == 'U':
            self.y -= 1
        elif direction == 'D':
            self.y += 1
        elif direction == 'L':
            self.x -= 1
        elif direction == 'R':
            self.x += 1

        # Illegal moves
        if not 0 <= self.x < self.game.width:
            raise ValueError('Illegal move: out of map')
        if not 0 <= self.y < self.game.height:
            raise ValueError('Illegal move: out of map')
        new_tile = self.game.get_tile(self.x, self.y)
        if new_tile.type == TileType.EMPTY:
            raise ValueError('Illegal move: fell off the world')
        if new_tile.type == TileType.CLIFF and direction in ['U', 'L']:
            raise ValueError('Illegal move: wrong side Cliff')
        if new_tile.type == TileType.GEYSER and direction in ['D', 'R']:
            raise ValueError('Illegal move: wrong side Geyser')

        # Time consumption
        if new_tile.type == old_tile.type:
            self.time -= 1

        if old_tile.type in [TileType.CLIFF, TileType.GEYSER]:
            self.time -= 1
        if new_tile.type in [TileType.CLIFF, TileType.GEYSER]:
            self.time -= 1

        if old_tile.type == TileType.PLAINS and new_tile.type == TileType.MOUNTAIN:
            self.time -= 5
        if old_tile.type == TileType.MOUNTAIN and new_tile.type == TileType.PLAINS:
            self.time -= 2

        if old_tile.type == TileType.PLAINS and new_tile.type == TileType.SAND:
            self.time -= 2
        if old_tile.type == TileType.SAND and new_tile.type == TileType.PLAINS:
            self.time -= 2

        if old_tile.type == TileType.PLAINS and new_tile.type == TileType.RIVER:
            self.time -= 5
        if old_tile.type == TileType.RIVER and new_tile.type == TileType.PLAINS:
            self.time -= 5

        if old_tile.type == TileType.MOUNTAIN and new_tile.type == TileType.SAND:
            self.time -= 5
        if old_tile.type == TileType.SAND and new_tile.type == TileType.MOUNTAIN:
            self.time -= 7

        if old_tile.type == TileType.MOUNTAIN and new_tile.type == TileType.RIVER:
            self.time -= 8
        if old_tile.type == TileType.RIVER and new_tile.type == TileType.MOUNTAIN:
            self.time -= 10

        if old_tile.type == TileType.SAND and new_tile.type == TileType.RIVER:
            self.time -= 8
        if old_tile.type == TileType.RIVER and new_tile.type == TileType.SAND:
            self.time -= 6

        if self.time < 0:
            raise ValueError('Too late')

        if self.time == 0 and not new_tile.has_weapon:
            raise ValueError('Too late')

        if new_tile.has_weapon:
            return True

        return False

    def __str__(self):
        output = f"[{self.x:0>2}, {self.y:0>2}] -> {self.time:0>3}"
        if self.failed:
            output = Style.DIM + Fore.RED + output + Style.RESET_ALL
        return output

class Game:
    def __init__(self, url):
        self.url = url
        data = requests.post(f"{url}/map").json()
        self.player = Player(data['player']['position'][0], data['player']['position'][1], data['player']['time'], self)
        self.width = data['width']
        self.height = data['height']
        self.map: list[list[Tile]] = []
        for y in range(self.height):
            self.map.append([])
            for x in range(self.width):
                self.map[y].append(Tile(data['tiles'][f"({x}, {y})"]))

    def get_tile(self, x, y) -> Tile:
        return self.map[y][x]

    @staticmethod
    def _solve_game(player: Player, path=None):
        path = path or []
        last_move = path[-1] if len(path) > 0 else None
        for move in ['U', 'R', 'D', 'L']:
            # Optimisation
            if move == 'U' and last_move == 'D':
                continue
            if move == 'D' and last_move == 'U':
                continue
            if move == 'R' and last_move == 'L':
                continue
            if move == 'L' and last_move == 'R':
                continue

            try:
                copy_player = player.copy()
                if copy_player.move(move):
                    return [*path, move]
                return Game._solve_game(copy_player, [*path, move])
            except ValueError:
                pass
        raise ValueError('No solution here')

    def solve(self):
        solution = Game._solve_game(self.player)
        for direction in solution:
            data = requests.post(f"{self.url}/update", json={"direction": direction}).json()
            if 'solved' in data:
                return data
        raise ValueError('Solve failed')

    def print_map(self):
        for y in range(self.height):
            items = []
            for x in range(self.width):
                tile = self.map[y][x]
                if self.player.x == x and self.player.y == y:
                    items.append(Style.DIM + Fore.CYAN + str(tile) + Style.RESET_ALL)
                elif tile.has_weapon:
                    items.append(Style.DIM + Fore.GREEN + str(tile) + Style.RESET_ALL)
                elif tile.type == TileType.EMPTY:
                    items.append(Style.DIM + Fore.RED + str(tile) + Style.RESET_ALL)
                else:
                    items.append(str(tile))
            print(' '.join(items))

def solve(url):
    assert requests.get(f"{url}/regenerate").status_code == 200
    for i in range(100):
        game = Game(url)
        print(f"Time left: {game.player.time}")
        game.print_map()
        print(game.solve())

if __name__ == '__main__':
    solve('http://83.136.250.103:30380')

Dernière mise à jour

Cet article vous a-t-il Γ©tΓ© utile ?