def process_help(
            command: str,
            ) -> None:
        """Process help commands.

        Keyword arguments:
        command -- A string containing a help command
        """
        # Remove "help " at the beginning and comments.
        help_command = command.split('#')[0][4:].strip()

        # Choose the correct help message.
        if help_command.lower().strip() == 'grid':
            str_help = 'helpGrid'
        elif help_command.lower().strip() == 'color':
            str_help = 'helpColor'
        elif help_command.lower().strip() == 'lang':
            str_help = 'helpLang'
        else:
            str_help = 'help'

        # Print the help message.
        print(wrap(
            lang[str_help] % {
                'lc': ansi.fx(fgc='c', fgl=1, on=settings['colorOn']),
                'br': ansi.fx(fgc='r', fxs='b', on=settings['colorOn']),
                'blg':
                    ansi.fx(fgc='g', fgl=1, fxs='b', on=settings['colorOn']),
                'lg': ansi.fx(fgc='g', fgl=1, on=settings['colorOn']),
                'b': ansi.fx(fxs='b', on=settings['colorOn']),
                'r': ansi.reset(on=settings['colorOn']),
                'prog': 'grid_drawer',
            }
        ))
Example #2
0
    def process_help(command: str, ) -> None:
        """Process help commands.

        Keyword arguments:
        command -- A string containing a help command
        """
        help_key = die[5:].lower().strip()
        if help_key == 'rolls':
            str_help = 'helpRolls'
        elif help_key == 'cond':
            str_help = 'helpCond'
        elif help_key == 'stack':
            str_help = 'helpStack'
        elif help_key == 'color':
            str_help = 'helpColor'
        elif help_key == 'lang':
            str_help = 'helpLang'
        elif help_key == 'log':
            str_help = 'helpLog'
        elif help_key == 'macro':
            str_help = 'helpMacro'
        elif help_key == 'profile':
            str_help = 'helpProfile'
        elif help_key == 'verbose':
            str_help = 'helpVerbose'
        else:
            str_help = 'help'

        print(
            wrap(
                lang[str_help] % {
                    'lc': ansi.fx(fgc='c', fgl=1, on=settings['colorOn']),
                    'br': ansi.fx(fgc='r', fxs='b', on=settings['colorOn']),
                    'blg': ansi.fx(
                        fgc='g', fgl=1, fxs='b', on=settings['colorOn']),
                    'lg': ansi.fx(fgc='g', fgl=1, on=settings['colorOn']),
                    'b': ansi.fx(fxs='b', on=settings['colorOn']),
                    'r': ansi.reset(on=settings['colorOn']),
                    'prog': 'dice_roller',
                }))
def pre_process(
        grids: str,
        ) -> None:
    """Process commands.

    Keyword arguments:
    grids -- A string containing the user's input

    This function processes all the commands inserted by the user.
    """

    def process_language(
            command: str,
            ) -> None:
        """Process language commands.

        Keyword arguments:
        command -- A string containing a language command
        """

        # Remove "lang " at the beginning and comments.
        lang_command = command.split('#')[0][5:].strip()

        # List installed languages.
        if lang_command.lower().startswith('list'):
            lang_files = glob.glob(os.path.join(lang_path, '*.py'))
            lang_files.sort()
            for language in lang_files:
                if lang_path + lang_prefix in language:
                    lang_code = \
                        language[:-3].replace(lang_path + lang_prefix, '')
                    print(f' {lang_code}')
        # Change language.
        elif lang_command.lower().startswith('file'):
            language = lang_command[5:].strip()
            try:
                exec(f'from {lang_pack}{language} import lang', globals())
                settings['lang'] = language
                save_settings(settings)
                print(lang['langSwitch'])
            except ImportError:
                print(lang['langError'] % language)
        # Unrecognized command.
        else:
            error(command)
        return None

    def process_color(
            command: str,
            ) -> None:
        """Process color commands.

        Keyword arguments:
        command -- A string containing a color command
        """

        # Remove "color " at the beginning and comments.
        color_command = command.split('#')[0][6:].strip()

        # Activate colored text.
        if color_command[:2].lower() == 'on':
            settings['colorOn'] = True
            save_settings(settings)
            print(lang['colorOn'])
        # Deactivate colored text.
        elif color_command[:3].lower() == 'off':
            settings['colorOn'] = False
            save_settings(settings)
            print(lang['colorOff'])
        # Unrecognized command.
        else:
            error(command)
        return None

    def process_help(
            command: str,
            ) -> None:
        """Process help commands.

        Keyword arguments:
        command -- A string containing a help command
        """
        # Remove "help " at the beginning and comments.
        help_command = command.split('#')[0][4:].strip()

        # Choose the correct help message.
        if help_command.lower().strip() == 'grid':
            str_help = 'helpGrid'
        elif help_command.lower().strip() == 'color':
            str_help = 'helpColor'
        elif help_command.lower().strip() == 'lang':
            str_help = 'helpLang'
        else:
            str_help = 'help'

        # Print the help message.
        print(wrap(
            lang[str_help] % {
                'lc': ansi.fx(fgc='c', fgl=1, on=settings['colorOn']),
                'br': ansi.fx(fgc='r', fxs='b', on=settings['colorOn']),
                'blg':
                    ansi.fx(fgc='g', fgl=1, fxs='b', on=settings['colorOn']),
                'lg': ansi.fx(fgc='g', fgl=1, on=settings['colorOn']),
                'b': ansi.fx(fxs='b', on=settings['colorOn']),
                'r': ansi.reset(on=settings['colorOn']),
                'prog': 'grid_drawer',
            }
        ))

    def process_grid(
            command: str,
            ) -> None:
        """Process grid commands.

        Keyword arguments:
        command -- A string containing a grid command
        """
        def xml_line(
                x1: float,
                y1: float,
                x2: float,
                y2: float,
                ) -> str:
            """Create the text for an SVG line.

            Keyword arguments:
            x1, y1, x2, y2 -- Coordinates of the initial and final points of
                              the line segment

            This function returns the XML code for an SVG line.
            """
            return (
                f'\t\t\t<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" ' +
                'style="stroke:#777777;stroke-width:1px;" />\n'
            )

        def xml_label(
                x: float,
                y: float,
                font_size: int,
                label: Union[int, str],
                ) -> str:
            """Create the text for an SVG line.

            Keyword arguments:
            x, y -- Coordinates of the text label
            font_size -- A number of pixels
            label -- The actual text to be displayed

            This function returns the XML code for an SVG text label.
            """
            return (
                f'\t\t\t<text x="{x}" y="{y}" ' +
                f'style="font-size:{font_size}px;' +
                f'font-family:sans-serif;fill:#000000;">{label}</text>\n'
            )

        # Parsing the user input.
        letters = 'ABCDEFGHKLMNPQRSTUVWXYZ'
        if len(command.split(' ')) == 4:
            width, height, side, file_name = command.split(' ')
            side = float(side.strip())
        elif len(command.split(' ')) == 3:
            width, height, file_name = command.split(' ')
            side = 40
        else:
            error(command)
            return None
        if (
                int(width) != float(width) or
                int(width) > len(letters) + len(letters) ** 2
                ):
            error(
                command,
                lang['gridWidthError'] % str(len(letters) + len(letters) ** 2),
            )
            return None
        else:
            width = int(width.strip())
        if int(height) != float(height) or int(height) >= 100:
            error(command, lang['gridHeightError'] % '99')
            return None
        else:
            height = int(height.strip())
        file_name = file_name.strip()
        if file_name.split('.')[-1] != 'svg':
            file_name += '.svg'
        complete_file_name = grids_path + file_name.strip()

        overwrite = True
        if os.path.isfile(complete_file_name):
            while True:
                answer = input(lang['gridOver'])
                answer = answer.lower()
                if answer in lang['yes'] + ['']:
                    break
                elif answer in lang['no']:
                    overwrite = False
                    break
                else:
                    continue

        # File creation.
        if overwrite:
            with open(complete_file_name, 'w') as f:
                img_width = (width + 2) * side + 40
                img_height = (height + 2) * side + 40
                # XML declaration and svg tag.
                f.write(
                    '<?xml version="1.0" encoding="UTF-8"?>\n' +
                    f'<svg width="{img_width}" height="{img_height}">\n' +
                    '\t<g>\n' +
                    '\t\t<g>\n'
                )

                # Horizontal lines of the grid.
                for i in range(height + 1):
                    y = 20 + side * (i + 1)
                    f.write(xml_line(20, y, (width + 2) * side + 20, y))

                # Vertical lines of the grid.
                for i in range(width + 1):
                    x = 20 + side * (i + 1)
                    f.write(xml_line(x, 20, x, (height + 2) * side + 20))
                f.write('\t\t</g>\n\t\t<g>\n')

                # Numerical vertical labels, left and right of the grid.
                font_size = 2 * side // 3
                for i in range(height):
                    x1 = 25 - 2 * font_size * (len(str(i + 1)) - 1) // 5
                    x2 = \
                        img_width - 20 - font_size * (len(str(i + 1)) + 4) // 5
                    y = 13 + side * (i + 2)
                    f.write(xml_label(x1, y, font_size, i + 1))
                    f.write(xml_label(x2, y, font_size, i + 1))

                # Alphabetical horizontal label, above and below the grid.
                if width <= len(letters):
                    # Only single-letter labels.
                    for i in range(width):
                        x = 20 + side * (i + 1.25)
                        y1 = 10 + side
                        y2 = img_height - 20 - 3 * (side - 10) // 10
                        f.write(xml_label(x, y1, font_size, letters[i]))
                        f.write(xml_label(x, y2, font_size, letters[i]))
                else:
                    # Single- and double-letter labels.
                    for i in range(len(letters)):
                        x = 20 + side * (i + 1.25)
                        y1 = 10 + side
                        y2 = img_height - 20 - 3 * (side - 10) // 10
                        f.write(xml_label(x, y1, font_size, letters[i]))
                        f.write(xml_label(x, y2, font_size, letters[i]))
                    # A different cycle is needed because a double-letter
                    # label has to be positioned differently.
                    letters2 = list(product(letters, repeat=2))
                    for i in range(width - len(letters)):
                        x = 21 + side * (i + len(letters) + 1)
                        y1 = 10 + side
                        y2 = img_height - 20 - 3 * (font_size) // 10
                        label = ''.join(letters2[i])
                        f.write(xml_label(x, y1, font_size, label))
                        f.write(xml_label(x, y2, font_size, label))

                f.write('\t\t</g>\n\t</g>\n</svg>')
            print(lang['gridComplete'] % file_name)
        else:
            print(lang['gridAbort'] % file_name)
        return None

    for grid in grids.split(','):
        try:
            grid = grid.strip()
            # Language commands.
            if grid.lower().startswith('lang '):
                process_language(grid)
            # Color commands.
            elif grid.lower().startswith('color '):
                process_color(grid)
            # Help commands.
            elif grid.lower().startswith('help'):
                process_help(grid)
            # Settings commands.
            elif grid.lower() in ['settings', 's']:
                print(lang['langName'])
                get_settings()
            # Exit commands.
            elif grid.lower() in ['exit', 'q', 'quit']:
                print(lang['goodbye'] + '!\n')
                raise SystemExit
            # Copyright commands.
            elif grid.lower() in ['copyright', 'c']:
                print(wrap(lang['copyright'] % '2017'))
            # Version commands.
            elif grid.lower() in ['version', 'v']:
                print(wrap(lang['version'] % ('grid_drawer', __version__)))
            # Empty command (feeds new line).
            elif grid == '':
                continue
            # Draw grid.
            else:
                process_grid(grid)
        except KeyboardInterrupt:
            error(grid, lang['interrupted'])
        except SystemExit:
            sys.exit()
        except Exception:
            error(grid)
            if key.endswith('On'):
                value = bool(int(value))
            settings[key] = value
        if len(settings['lang']) > 3:
            settings['lang'] = settings['lang'][0:3]
except OSError:
    # Settings file does not exist.  Write a new default settings file.
    with open(settings_path, 'w') as settings_file:
        settings_file.write('lang=eng\ncolorOn=1')
        settings = {'lang': 'eng', 'colorOn': True}

exec(f'from {lang_pack}{settings["lang"]} import lang', globals())
bg_green = ansi.fx(bgc='g', fgl=1, on=settings['colorOn'])

print(wrap(
    ansi.clear(on=settings['colorOn']) +
    lang['welcome'] % ('grid_drawer', __version__, '"help" or "copyright"')
))
get_settings()
print('')

# Main loop.
while True:
    try:
        grids = input(lang['prompt'] % (
            ansi.fx(fgc='c', fgl=1, esc=1, on=settings['colorOn']),
            ansi.reset(esc=1, on=settings['colorOn']),
        ))
        pre_process(grids)
        print('')  # Add an empty line after a grid generation.
    except EOFError:
        print('\n' + lang['goodbye'] + '!\n')
Example #5
0
def pre_process(dice: str, ) -> None:
    """Process commands.

    Keyword arguments:
    dice -- A string containing the user's input

    This function processes all the commands inserted by the user.
    """
    def process_conditional(command: str, ) -> None:
        """Process conditional commands.

        Keyword arguments:
        command -- A string containing a conditional command
        """
        die, conditions = command.split('??', 1)
        conditions = \
            [condition.split('::') for condition in conditions.split('&&')]
        # Prepare repeated conditional rolls.
        if len(die.split('#')[0].split('x')) == 2:
            die, max_counter = die.split('#')[0].split('x')
            multiplier = 'x' + max_counter
            max_counter = int(max_counter)
        else:
            multiplier = ''
            max_counter = 1
        counter = 0
        # Repeat conditional rolls.
        while counter < max_counter:
            pre_process(die.replace(multiplier, '', 1))
            for condition in conditions:
                cond_test = test(stack[-1], condition[0].strip())
                if cond_test == -1:
                    break
                elif cond_test:
                    pre_process(condition[1].strip())
                    break
            if max_counter - counter > 1:
                print('')
            counter += 1
        return None

    def process_log(command: str, ) -> None:
        """Process log commands.

        Keyword arguments:
        command -- A string containing a log command
        """
        die = command.split('#')[0][4:].strip()
        # Activate log.
        if die.lower() == 'on':
            settings['logOn'] = True
            save_settings(settings)
            print(lang['logOn'] % (bg_green + settings['logFile'][:-4] +
                                   ansi.reset(on=settings['colorOn'])))
        # Deactivate log.
        elif die.lower() == 'off':
            settings['logOn'] = False
            save_settings(settings)
            print(lang['logOff'])
        # Set a new log.
        elif die[:4].lower() == 'file':
            settings['logFile'] = die[5:].strip()
            # Add .log extension if necessary.
            if (len(settings['logFile']) < 5
                    or settings['logFile'][-4:] != '.log'):
                settings['logFile'] += '.log'
            save_settings(settings)
            print(lang['logSwitch'] % (bg_green + settings['logFile'][:-4] +
                                       ansi.reset(on=settings['colorOn'])))
        # Wrong log command.
        else:
            error(command)
        return None

    def process_macro(command: str, ) -> None:
        """Process macro commands.

        Keyword arguments:
        command -- A string containing a macro command
        """
        global macro

        die = command.split('#')[0][6:].strip()
        # New macro.
        if die.lower().startswith('set'):
            macro_name, macro_dice = \
                command[6:].strip()[4:].strip().split(' ', 1)
            macro_dice = macro_dice.split(';')
            for i in range(len(macro_dice)):
                macro_dice[i] = macro_dice[i].split('#', 1)
                # Delete useless spaces.
                if len(macro_dice[i]) == 1:
                    macro_dice[i] = macro_dice[i][0].replace(' ', '')
                else:
                    macro_dice[i][0] = macro_dice[i][0].replace(' ', '')
                    macro_dice[i] = \
                        macro_dice[i][0] + ' #' + macro_dice[i][1].strip()
            macro_dice = ', '.join(macro_dice)
            # Overwrite existing macro.
            if macro_name in macro.keys():
                while True:
                    answer = input(lang['macroOver'])
                    answer = answer.lower()
                    if answer in lang['yes'] + ['']:
                        macro[macro_name] = macro_dice
                        save_macro(macro, macro_path + settings['macroFile'])
                        print(lang['macroAdded'] %
                              (ansi.fx(fgc='g', fgl=1, on=settings['colorOn']),
                               macro_name, ansi.reset(on=settings['colorOn'])))
                        break
                    elif answer in lang['no']:
                        print(lang['macroNotAdded'] %
                              (ansi.fx(fgc='r', fgl=1, on=settings['colorOn']),
                               macro_name, ansi.reset(on=settings['colorOn'])))
                        break
                    else:
                        continue
            # Save new macro.
            else:
                macro[macro_name] = macro_dice
                save_macro(macro, macro_path + settings['macroFile'])
                print(lang['macroAdded'] %
                      (ansi.fx(fgc='g', fgl=1, on=settings['colorOn']),
                       macro_name, ansi.reset(on=settings['colorOn'])))
        # Delete macro, if it exists.
        elif die.lower().startswith('del'):
            macro_name = die[4:].strip()
            if macro_name in macro.keys():
                macro.pop(macro_name)
                save_macro(macro, macro_path + settings['macroFile'])
                print(lang['macroRemoved'] %
                      (ansi.fx(fgc='g', fgl=1, on=settings['colorOn']),
                       macro_name, ansi.reset(on=settings['colorOn'])))
            else:
                print(lang['macroNotExisting'] %
                      (ansi.fx(fgc='r', fgl=1, on=settings['colorOn']),
                       macro_name, ansi.reset(on=settings['colorOn'])))
        # List macro.
        elif die.lower().startswith('list'):
            macro_names = list(macro.keys())
            macro_names.sort()
            if len(macro_names) == 0:
                print(lang['macroEmpty'] %
                      (ansi.fx(fgc='r', fgl=1, on=settings['colorOn']),
                       ansi.reset(on=settings['colorOn'])))
            else:
                for name in macro_names:
                    print(
                        ansi.fx(fgc='b', on=settings['colorOn']) + '  ' +
                        name + ansi.reset(on=settings['colorOn']) + ': ' +
                        macro[name])
        # Change macro file.
        elif die.lower().startswith('file'):
            settings['macroFile'] = die[5:].strip()
            # Add .macro extension, if necessary.
            if (len(settings['macroFile']) < 7
                    or settings['macroFile'][-6] != '.macro'):
                settings['macroFile'] += '.macro'
            macro = open_macro(macro_path + settings['macroFile'])
            save_settings(settings)
            print(lang['macroSwitch'] %
                  (bg_green + settings['macroFile'][:-6] +
                   ansi.reset(on=settings['colorOn']), len(macro)))
        else:
            error(command)
        return None

    def process_profile(command: str, ) -> None:
        """Process profile commands.

        Keyword arguments:
        command -- A string containing a profile command
        """
        global macro

        profile = command.split('#')[0][8:].strip()
        if profile.lower() == 'list':
            prof_files = glob.glob(os.path.join(log_path, '*.log'))
            prof_files.sort()
            for prof in prof_files:
                print(' ' + prof[:-4].replace(log_path, ''))
        else:
            settings['logFile'] = profile + '.log'
            settings['macroFile'] = profile + '.macro'
            macro = open_macro(macro_path + settings['macroFile'])
            save_settings(settings)
            print(lang['logSwitch'] % (bg_green + settings['logFile'][:-4] +
                                       ansi.reset(on=settings['colorOn'])))
            print(lang['macroSwitch'] %
                  (bg_green + settings['macroFile'][:-6] +
                   ansi.reset(on=settings['colorOn']), len(macro)))
        return None

    def process_language(command: str, ) -> None:
        """Process language commands.

        Keyword arguments:
        command -- A string containing a language command
        """
        die = command.split('#')[0][5:].strip()
        if die.lower().startswith('list'):
            lang_files = glob.glob(os.path.join(lang_path, '*.py'))
            lang_files.sort()
            for language in lang_files:
                if lang_path + lang_prefix in language:
                    print(' ' +
                          language[:-3].replace(lang_path + lang_prefix, ''))
        elif die.lower().startswith('file'):
            language = die[5:].strip()
            try:
                exec(f'from {lang_pack}{language} import lang', globals())
                settings['lang'] = language
                save_settings(settings)
                print(lang['langSwitch'])
            except Exception:
                print(lang['langError'] % language)
        else:
            error(command)
        return None

    def process_verbose(command: str, ) -> None:
        """Process verbose commands.

        Keyword arguments:
        command -- A string containing a verbose command
        """
        die = command.split('#')[0][8:].strip()
        if die.lower().startswith('on'):
            settings['verboseOn'] = True
            save_settings(settings)
            print(lang['verboseOn'])
        elif die.lower().startswith('off'):
            settings['verboseOn'] = False
            save_settings(settings)
            print(lang['verboseOff'])
        else:
            error(command)
        return None

    def process_color(command: str, ) -> None:
        """Process color commands.

        Keyword arguments:
        command -- A string containing a color command
        """
        die = command.split('#')[0][6:].strip()
        if die.lower().startswith('on'):
            settings['colorOn'] = True
            save_settings(settings)
            print(lang['colorOn'])
        elif die.lower().startswith('off'):
            settings['colorOn'] = False
            save_settings(settings)
            print(lang['colorOff'])
        else:
            error(command)
        return None

    def process_help(command: str, ) -> None:
        """Process help commands.

        Keyword arguments:
        command -- A string containing a help command
        """
        help_key = die[5:].lower().strip()
        if help_key == 'rolls':
            str_help = 'helpRolls'
        elif help_key == 'cond':
            str_help = 'helpCond'
        elif help_key == 'stack':
            str_help = 'helpStack'
        elif help_key == 'color':
            str_help = 'helpColor'
        elif help_key == 'lang':
            str_help = 'helpLang'
        elif help_key == 'log':
            str_help = 'helpLog'
        elif help_key == 'macro':
            str_help = 'helpMacro'
        elif help_key == 'profile':
            str_help = 'helpProfile'
        elif help_key == 'verbose':
            str_help = 'helpVerbose'
        else:
            str_help = 'help'

        print(
            wrap(
                lang[str_help] % {
                    'lc': ansi.fx(fgc='c', fgl=1, on=settings['colorOn']),
                    'br': ansi.fx(fgc='r', fxs='b', on=settings['colorOn']),
                    'blg': ansi.fx(
                        fgc='g', fgl=1, fxs='b', on=settings['colorOn']),
                    'lg': ansi.fx(fgc='g', fgl=1, on=settings['colorOn']),
                    'b': ansi.fx(fxs='b', on=settings['colorOn']),
                    'r': ansi.reset(on=settings['colorOn']),
                    'prog': 'dice_roller',
                }))

    def process_test(command: str, ) -> None:
        """Process test commands.

        Keyword arguments:
        command -- A string containing a test command
        """
        repetitions = 10
        command = command[1:].strip()
        # Compose plot title.
        if '#' in command:
            die, comment = command.split('#', 1)
            orig_die = die.split('x')[0]
            die = die \
                .replace(' ', '') \
                .replace('%', '100') \
                .replace('**', '^') \
                .replace('//', '|') \
                .split('x')[0]
            title = f'{comment.strip()} [{orig_die}]'
        else:
            title = command.split('x')[0]
            die = command \
                .replace(' ', '') \
                .replace('%', '100') \
                .replace('**', '^') \
                .replace('//', '|') \
                .split('x')[0]
        counter = 0
        values = []
        tot_results = []
        min_dice, max_dice = get_extremes(die)
        throws = min([(max_dice - min_dice + 1) * 5000, 100000])
        bins = [min_dice + x for x in range(max_dice - min_dice + 2)]
        # Set of throws to study variance between sets.
        while counter < repetitions:
            result = []
            # Repeatedly throws dice to test their distribution.
            for i in range(throws):
                result.append(roll(die)[3])
            tot_results += result
            hist, bins2 = histogram(result, bins=bins, density=True)
            values.append(hist * 100)
            counter += 1
        results = [[] for x in range(len(values[0]))]
        for i in range(repetitions):
            for j in range(len(values[i])):
                results[j].append(values[i][j])
        hist = []
        sigma_b = []
        # Calculate mean value and standard deviation for every possible value.
        for result in results:
            hist.append(mean(result))
            sigma_b.append(std(result))
        mu_x = mean(tot_results)
        sigma_x = std(tot_results)
        mu_y = mean(hist)
        sigma_y = std(hist)
        plt.figure()
        plt.title(title)
        plt.xlabel(
            lang['testValue'] +
            fr'  ($\mu$ = {round(mu_x, 2)}, $\sigma$ = {round(sigma_x, 2)})')
        plt.ylabel(lang['testFreq'] + fr' [%]  ($\mu$ = {round(mu_y, 2)}, ' +
                   fr'$\sigma$ = {round(sigma_y, 2)})')
        plt.bar(bins[:-1],
                hist,
                1,
                color=['#33CC33', '#55FF55'],
                yerr=sigma_b,
                ecolor='k')
        plt.axis(xmin=min(bins) - 0.5, xmax=max(bins) - 0.5, ymin=0)
        # Add value labels if there are not too many bars.
        if len(bins) <= 30:
            for i in range(len(hist)):
                x = (bins[i] + bins[i + 1]) // 2
                percent = round(hist[i], 2)
                text = fr'{int(x)}: ({percent}$\pm${round(sigma_b[i], 2)})%'
                if percent < 0.35 * max(hist):
                    y = percent + 0.021 * max(hist)
                else:
                    y = 0.015 * max(hist)
                plt.text(
                    x + 0.2,
                    y,
                    text,
                    rotation='vertical',
                    rotation_mode='anchor',
                )
        return None

    def process_roll(command: str, ) -> None:
        """Process roll commands.

        Keyword arguments:
        command -- A string containing a roll command
        """
        if '#' in command:
            die, comment = command.split('#', 1)
            comment.strip()
        else:
            die = command
            comment = ''
        # Shouldn't find a macro at this point.
        if len(die) > 0 and die[0] == '@':
            error(die[1:], lang['macroError'])
            return
        orig_die = die
        die = die \
            .replace(' ', '') \
            .replace('%', '100') \
            .replace('**', '^') \
            .replace('//', '|')
        # Formats output string.
        if comment:
            title = f'{comment} ({orig_die.split("x")[0].strip()})'
        else:
            title = orig_die.split('x')[0].strip()
        # Prepare repeated rolls.
        if len(die.split('x')) == 2:
            die, max_counter = die.split('x')
            max_counter = int(max_counter)
        else:
            max_counter = 1
        counter = 0
        while counter < max_counter:
            temp_die = die
            while '{' in temp_die and '}' in temp_die:
                curly = temp_die[temp_die.index('{'):temp_die.index('}') + 1]
                pos = int(curly[1:-1])
                if pos > 0:
                    pos -= 1
                try:
                    temp_die = temp_die.replace(curly, str(stack[pos]))
                except IndexError:
                    error(temp_die, lang['stackError'])
                    return
            dice_results = roll(temp_die)
            if dice_results:
                stack.append(dice_results[3])
                output_num = \
                    ' ' + \
                    ansi.fx(fgc='g', fgl=1, on=settings['colorOn']) + \
                    f'{{{len(stack)}}}' + \
                    ansi.reset(on=settings['colorOn']) + \
                    ' '
                output = title + ': '
                if (dice_results[0] == dice_results[1]
                        and format_dice(dice_results[0])
                        or not settings['verboseOn']):
                    output += format_dice(dice_results[1])
                else:
                    output += \
                        format_dice(dice_results[0]) + \
                        ' --> ' + \
                        format_dice(dice_results[1])
                if (dice_results[2] == dice_results[3]
                        or not settings['verboseOn']):
                    output += f' --> {dice_results[3]}'
                else:
                    output += f' --> {dice_results[2]} --> {dice_results[3]}'
                print(output_num + output)
                if settings['logOn']:
                    log_file = open(log_path + settings['logFile'], 'a')
                    log_file.write(
                        time.strftime('%d/%m/%Y %H:%M:%S') + '\t' +
                        output.strip() + '\n')
                    log_file.flush()
                    log_file.close()
            else:
                error(die)
            counter += 1
        return None

    # Replace macros with the actual rolls.
    for m in macro.keys():
        position = 0
        for i in range(dice.count('@' + m)):
            macro_name = dice[dice[position:].find('@' + m):] \
                .split(',')[0] \
                .split('#')[0]
            # Replace parameters in macro.
            params = macro_name.replace('@' + m, '').strip()[1:-1].split('][')
            position = dice[position:].find('@' + m) + 1
            subst = macro[m]
            for param in params:
                if param != '':
                    param_name, param_value = param.split('=', 1)
                    subst = subst.replace('[' + param_name + ']', param_value)
                    while '[' + param_name + '=' in subst:
                        pos1 = subst.find('[' + param_name + '=')
                        pos2 = subst[pos1:].find(']') + pos1 + 1
                        subst = subst.replace(subst[pos1:pos2], param_value)
            while '[' in subst:
                pos1 = subst.find('[')
                pos2 = subst[pos1:].find(']') + pos1 + 1
                if '=' in subst[pos1:pos2]:
                    subst = subst.replace(
                        subst[pos1:pos2],
                        subst[pos1 + 1:pos2 - 1].split('=', 1)[1],
                    )
                else:
                    subst = subst.replace(subst[pos1:pos2], '0')
            dice = dice.replace(macro_name, subst)

    # Parse every roll.
    plots = False
    for die in dice.split(','):
        try:
            die = die.strip()
            # Conditional roll.
            if (die.count('??') > 0 and die.count('::') > 0
                    and not die.lower().startswith('macro')):
                process_conditional(die)
            # Log commands.
            elif die.lower().startswith('log'):
                process_log(die)
            # Macro commands.
            elif die.lower().startswith('macro'):
                process_macro(die)
            # Profile commands.
            elif die.lower().startswith('profile'):
                process_profile(die)
            # Language commands.
            elif die.lower().startswith('lang'):
                process_language(die)
            # Verbose commands.
            elif die.lower().startswith('verbose'):
                process_verbose(die)
            # Color commands.
            elif die.lower().startswith('color'):
                process_color(die)
            # Help commands.
            elif die.lower().startswith('help'):
                process_help(die)
            # Settings commands.
            elif die.lower() in ['settings', 's']:
                print(lang['langName'])
                get_settings()
            # Exit commands.
            elif die.lower() in ['exit', 'q', 'quit']:
                print(lang['goodbye'] + '!\n')
                raise SystemExit
            # Copyright commands.
            elif die.lower() in ['c', 'copyright']:
                print(wrap(lang['copyright'] % '2015'))
            # Changelog commands.
            elif die.lower() == 'changelog':
                print(
                    wrap(
                        lang['changelog'] % {
                            'bb': ansi.fx(
                                fgc='b', fxs='b', on=settings['colorOn']),
                            'r': ansi.reset(on=settings['colorOn'])
                        }))
            # Version commands.
            elif die.lower() in ['v', 'version']:
                print(wrap(lang['version'] % ('dice_roller', __version__)))
            # Empty commands (feed new line).
            elif die == '':
                continue
            # Comment line.
            elif die[0] == '#':
                print(f'    {die[1:].strip()}')
            # Probability distribution tester.
            elif die[0] == 't':
                process_test(die)
                plots = True
            # Roll dice.
            else:
                process_roll(die)
        except KeyboardInterrupt:
            error(die, lang['interrupted'])
        except SystemExit:
            sys.exit()
        except Exception:
            error(die)
    # Show plots all together.
    if plots:
        plt.show()
Example #6
0
            'lang': 'eng',
            'logOn': False,
            'verboseOn': True,
            'colorOn': True,
            'logFile': 'dice.log',
            'macroFile': 'dice.macro',
        }

exec(f'from {lang_pack}{settings["lang"]} import lang', globals())
macro = open_macro(macro_path + settings['macroFile'])
bg_green = ansi.fx(bgc='g', fgl=1, on=settings['colorOn'])

print(
    wrap(
        ansi.clear(on=settings['colorOn']) + lang['welcome'] % (
            'dice_roller',
            __version__,
            '"help", "copyright" or "changelog"',
        )))
get_settings()
print('')

while True:
    try:
        dice = input(lang['prompt'] %
                     (ansi.fx(fgc='c', fgl=1, esc=1, on=settings['colorOn']),
                      ansi.reset(esc=1, on=settings['colorOn'])))
        pre_process(dice)
        print('')  # Add an empty line after a list of rolls.
    except EOFError:
        print('\n' + lang['goodbye'] + '!\n')
        sys.exit()