def error(
        command: str,
        reason: str = '',
        ) -> None:
    """Print an error message.

    Keyword arguments:
    command -- A string containing the user's input
    reason -- An explanation of what went wrong
    """
    if reason:
        print(
            f'    {command}: ' +
            ansi.fx(bgc='r', bgl=1, on=settings['colorOn']) +
            f'{reason}!' +
            ansi.reset(on=settings['colorOn'])
        )
    else:
        print(
            f'    {command}: ' +
            ansi.fx(bgc='r', bgl=1, on=settings['colorOn']) +
            f'{lang["error"]}!' +
            ansi.reset(on=settings['colorOn'])
        )
    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',
            }
        ))
Exemplo n.º 3
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',
                }))
        settings = {}
        for line in settings_file.readlines():
            key, value = line.strip().split('=')
            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']),
        ))
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
    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
Exemplo n.º 7
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()