Fallout 4 Hacking Mini Game Solver

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Fallout 4 hat ein meiner Meinung nach recht nerviges Hacking Mini Game, dass man spielen muss um Zugriff auf Terminals zu bekommen mit denen man z.b. Roboter kontrollieren kann. Ich hab mir ein Skript geschrieben um mir die gedankliche Arbeit dabei abzunehmen.

Ich dachte mir es könnte vielleicht noch andere geben die daran Bedarf haben :)

Code: Alles auswählen

#!/usr/bin/env python3.5
"""
    f4hackingsolver
    ~~~~~~~~~~~~~~~

    Solver for the Fallout 4 Hacking Mini Game.

    Within Fallout 4 you sometimes encounter terminals that need to be hacked.
    When hacking a terminal you are presented with a screen that contains a
    list of potential passwords all with the same length.

    You find the password by selecting a word from the list. If the word is the
    password you win, if it isn't you get a likeness score which is the number
    of characters in your selected word that match the password.

    This script assists you while playing this mini game. Simply enter the
    words you can choose from. The script will suggest a word to select, will
    ask you for the result and tell you which of the words remain potential
    candidates for the password.

    :copyright: 2015 Daniel Neuhäuser
    :license: Unlicense, see http://unlicense.org for more information
"""
from itertools import combinations
from collections import Counter


class Solver:
    """
    Implements the logic for the solver.

    Initialize this class with the list of available `words`. Figure out the
    password by calling :meth:`get_suggested_selection` and :meth:`select` in a
    loop. You can see which words remain after each attempt with the
    :attr:`words` instance attribute.
    """

    def __init__(self, words):
        #: The list of the words which remain candidates for the password.
        self.words = words

    def get_suggested_selection(self):
        """
        Returns a word with many characters matching many other of the
        remaining password candidates.

        If the suggestion doesn't match the password, it will hopefully
        eliminate as many candidates as possible from the pool.
        """
        char_counts = [Counter(column) for column in zip(*self.words)]
        return sorted(
            self.words,
            key=lambda w: sum(char_counts[i][c] for i, c in enumerate(w)),
            reverse=True
        )[0]

    def select(self, selected, likeness):
        """
        Eliminates words from :attr:`words` based on the `selected` word.
        """
        remaining_words = [word for word in self.words if word != selected]
        if likeness == 0:
            # Eliminate all words that have any matching characters.
            self.words = [
                word for word in remaining_words if
                not set(enumerate(word)) & set(enumerate(selected))
            ]
        else:
            # Find all characters in the selected word that could match any
            # other words. Keep all words that contain any of the `likeness`
            # number of combinations of these characters.
            alphabet = [set(column) for column in zip(*words)]
            potential_chars = [
                (i, char) for i, char in enumerate(selected)
                if char in alphabet[i]
            ]
            occurs_in = lambda c, w: all(w[i] == ch for i, ch in c)
            self.words = [
                word for word in remaining_words
                if any(
                    occurs_in(combination, word)
                    for combination in combinations(potential_chars, likeness)
                )
            ]


def prompt(question, allow_empty=False, type=None):
    """
    Prompts the user for input using `question`. Prompting repeatedly until
    an answer is given.

    You can prompt for things other than strings, by providing a function that
    converts the string into something else. That function may raise
    :exc:`ValueError` and the user will be prompted again, if that happens.

    If `allow_empty` is `True`, empty answers are allowed.
    """
    while True:
        raw_answer = input(question)
        if type is None:
            answer = raw_answer
        else:
            try:
                answer = type(raw_answer)
            except ValueError:
                continue
        if raw_answer or allow_empty:
            return answer


def ask_for_words():
    """
    Asks the user for a list of words.
    """
    return list(iter(lambda: prompt('WORD> ', allow_empty=True), ''))


def ask_for_selection():
    """
    Asks the user for a selection and likeness.
    """
    return prompt('SELECTED> '), prompt('LIKENESS> ', type=int)


def main():
    solver = Solver(ask_for_words())
    word_num = len(solver.words)
    while len(solver.words) > 1:
        print('SUGGESTION: {}'.format(solver.get_suggested_selection()))
        solver.select(*ask_for_selection())
        old_word_num = word_num
        word_num = len(solver.words)
        print('REMAINING WORDS (-{}):'.format(old_word_num - word_num))
        print('\n'.join(solver.words))


if __name__ == '__main__':
    main()
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Hab das ganze noch ein klein wenig verbessert.

Code: Alles auswählen

#!/usr/bin/env python3.5
"""
    f4hackingsolver
    ~~~~~~~~~~~~~~~

    Solver for the Fallout 4 Hacking Mini Game.

    Within Fallout 4 you sometimes encounter terminals that need to be hacked.
    When hacking a terminal you are presented with a screen that contains a
    list of potential passwords all with the same length.

    You find the password by selecting a word from the list. If the word is the
    password you win, if it isn't you get a likeness score which is the number
    of characters in your selected word that match the password.

    This script assists you while playing this mini game. Simply enter the
    words you can choose from. The script will suggest a word to select, will
    ask you for the result and tell you which of the words remain potential
    candidates for the password.

    :copyright: 2015 Daniel Neuhäuser
    :license: Unlicense, see http://unlicense.org for more information
"""
import os
import pickle
from collections import Counter
from contextlib import contextmanager


def get_likeness(a, b):
    """
    Returns the likeness of two words.
    """
    return len(set(enumerate(a)) & set(enumerate(b)))


class Game:
    """
    Represents a hacking mini game.

    `words`
        List of all possible passwords. All words must have the same length.

    `password`
        Actual password among the `words`.

    `max_attempts`
        Maximum number of attempts a player is allowed to make.
    """

    def __init__(self, words, password, max_attempts=4):
        if len(set(len(word) for word in words)) > 1:
            raise ValueError('all words need to have the same length')

        if password not in words:
            raise ValueError('password not in words')

        if max_attempts < 1:
          raise ValueError('max_attempts must be greater than or equal to 1')

        self.words = words
        self.password = password
        self.max_attempts = max_attempts

        self.attempts = 0

    def select(self, word):
        """
        Selects a word for an attempt and returns the likeness.
        """
        if self.attempts >= self.max_attempts:
            raise ValueError('already reached maximum number of attempts')

        if word not in self.words:
            raise ValueError(
                'chosen word {!r} not in words {!r}'.format(word, self.words)
            )

        self.attempts += 1
        return get_likeness(word, self.password)


class Player:
    """
    Represents a player playing the mini game.

    `words` the words in the game.
    """
    def __init__(self, words):
        #: The words in the game that are currently candidates for being the
        #: password.
        self.candidates = words

    @property
    def knows_password(self):
        return len(self.candidates) == 1

    def find_most_likely_candidate(self):
        """
        Finds the word most likely to be the password.

        The approach is to choose the word with the highest overlap. This will
        either be the password or eliminate as many candidates as possible
        from the pool, if it isn't.
        """
        char_counts = [Counter(column) for column in zip(*self.candidates)]
        return sorted(
            self.candidates,
            key=lambda w: sum(char_counts[i][c] for i, c in enumerate(w)),
            reverse=True
        )[0]

    def exclude_candidates(self, word, likeness):
        """
        Excludes the given word and all words that have less than `likeness`
        characters in common with it from the candidates.
        """
        remaining_candidates = candidates = [
            candidate for candidate in self.candidates
            if candidate != word and get_likeness(candidate, word) >= likeness
        ]
        excluded_candidates = set(self.candidates) - set(remaining_candidates)
        self.candidates = remaining_candidates
        return excluded_candidates

    def play_round(self, game):
        """
        Plays a round of the game. Returns `True`, if the game was won.
        """
        candidate = self.find_most_likely_candidate()
        likeness = game.select(candidate)
        if len(candidate) == likeness:
            return True
        self.exclude_candidates(candidate, likeness)
        return False

    def play(self, game):
        """
        Plays the game to the end. Returns `True`, if the game was won.
        """
        return any(self.play_round(game) for _ in range(game.max_attempts))


def find_winnable_passwords(words):
    """
    Returns a list of all words within `words` that can be found by playing
    the game without luck.
    """
    return [
        word for word in words
        if Player(words[:]).play(Game(words[:], word))
    ]


def prompt(question, allow_empty=False, type=None):
    """
    Prompts the user for input using `question`. Prompting repeatedly until
    an answer is given.

    You can prompt for things other than strings, by providing a function that
    converts the string into something else. That function may raise
    :exc:`ValueError` and the user will be prompted again, if that happens.

    If `allow_empty` is `True`, empty answers are allowed.
    """
    while True:
        raw_answer = input(question)
        if type is None:
            answer = raw_answer
        else:
            try:
                answer = type(raw_answer)
            except ValueError:
                continue
        if raw_answer or allow_empty:
            return answer


def prompt_boolean(question, default=True):
    """
    Prompts the user for a `y` or `n` answer to the given `question`. If no
    answer is given, the `default` will be assumed otherwise the question will
    be repeated.
    """
    while True:
        answer = input(question)
        if answer.lower() == 'y':
            return True
        elif answer.lower() == 'n':
            return False
        elif not answer:
            return default


def ask_for_words():
    """
    Asks the user for a list of words.
    """
    return list(iter(lambda: prompt('WORD> ', allow_empty=True), ''))


def print_list(iterable):
    """
    Prints an iterable of strings, one string per line each line prefixed with
    `- `.
    """
    print('- ' + '\n- '.join(iterable))


class State:
    """
    Represents the state of the application.
    """

    @classmethod
    def load(cls, filename):
        """
        Returns a state instance recovered from the file with `filename`.

        If no such file is found, an instance with no data is returned.
        """
        try:
            with open(filename, 'rb') as state_file:
                self = pickle.load(state_file)
                self.recovered = True
                return self
        except EnvironmentError:
            return cls()

    def __init__(self):
        #: The list of words given by the user.
        self.words = []

        #: The list of likenesses given by the user.
        self.likenesses = []

        #: True when the state was recovered from a file.
        self.recovered = False

    def dump(self, filename):
        """
        Dumps the state into a file with the given `filename`.
        """
        with open(filename, 'wb') as state_file:
            pickle.dump(self, state_file)

    def reset(self):
        """
        Resets the state.
        """
        self.words = []
        self.likenesses = []
        self.recovered = False


@contextmanager
def recovery():
    """
    A context manager that loads and yields a :class:`State` instance, dumps
    it, if an exception occurs and deletes it otherwise.
    """
    crash_filename = '.' + __file__ + '.crash'
    state = State.load(crash_filename)
    try:
        yield state
    except BaseException as exc:
        if not isinstance(exc, KeyboardInterrupt):
            state.dump(crash_filename)
        raise
    else:
        try:
            os.remove(crash_filename)
        except EnvironmentError:
            pass


def main():
    with recovery() as state:
        if state.recovered:
            print('RECOVERED FROM EARLIER CRASH')
            recover = prompt_boolean('CONTINUE WITH RECOVERED DATA [Yn]> ')
            if not recover:
                state.reset()
        else:
            state.words.extend(ask_for_words())

        winnable_words = find_winnable_passwords(state.words)
        unwinnable_words = set(state.words) - set(winnable_words)
        if unwinnable_words:
            print('EXCLUDED {} words as unwinnable:'.format(len(unwinnable_words)))
            print_list(unwinnable_words)

        player = Player(winnable_words)

        if state.recovered:
            for likeness in state.likenesses:
                candidate = player.find_most_likely_candidate()
                print('SELECT: {}'.format(candidate))
                print('LIKENESS> {}'.format(likeness))
                player.exclude_candidates(candidate, likeness)

        while not player.knows_password:
            candidate = player.find_most_likely_candidate()
            print('SELECT: {}'.format(candidate))
            likeness = prompt('LIKENESS> ', type=int)
            state.likenesses.append(likeness)
            excluded = player.exclude_candidates(candidate, likeness)
            if excluded:
                print('EXCLUDED {} WORDS:'.format(len(excluded)))
                print_list(excluded)
                print('{} WORDS REMAINING:'.format(len(player.candidates)))
                print_list(player.candidates)
        print('PASSWORD: {}'.format(player.candidates[0]))


if __name__ == '__main__':
    main()
Benutzeravatar
phytonStolzi
User
Beiträge: 3
Registriert: Montag 1. Mai 2017, 21:45
Wohnort: Kennst Du eh nicht. ;-)

Das ist cool danke :-)
:geek:
Antworten