HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Pretty print dice faces from multiple rolls of multi-sided dices

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
dicesfacesprettymultirollsprintdicemultiplefromsided

Problem

In this question related to rolling multiple dices, the OP pretty prints for a single case where number of dices is less than or equal to 6. This led to me finding this code golf on "Draw dice result in ASCII", but still only 6 sides.

I wanted to extend this to at least 20 sides, and possibly some day any number of sides. This resulted in the following code which I would like reviewed for improvements and efficiency and design of ascii art.

```
from random import randrange

def get_dice_rolls(dice_size, number_of_rolls):
"""Returns list with number_of_rolls from a dice_size-sided dice."""

return [randrange(1, dice_size+1) for _ in range(number_of_rolls)]

def get_single_dice_face(dice_size, dice_roll, zero_based=False, eye='o '):
"""Return the full face of the roll for a dice-size sided dice."""

# Shorten roll variable, and account for zero_basing rolls
r = dice_roll if zero_based else dice_roll - 1

# Build a proper dice_str according to dice_size and roll
if dice_size > 12:
dice_str ='---------\n|' \
+ eye[r 6:
dice_str = ' ------- \n|{} {} {} {}|\n|{} {}'.format(*(eye[r 20:
raise ValueError('Support only up to 20 sided dices')

if any(roll > dice_size for roll in dice_rolls):
raise ValueError('Roll is higher than dice size')

if len(eye) != 2:
raise ValueError('Excpected two choice for eye parameter')

# Set up some default values
dice_width = 7 if dice_size > 6 else 5
dice_lines = 7 if dice_size > 12 else 5

# Will try to collate output of multiple dice rolls into lines
# of up to max_width length
output_buffer = [''] * dice_lines

# Debug print for test purposes...
print('\n{}-sided dice{}: {}'.format(dice_size, ', zero-based' if zero_based else '', dice_rolls))

# Output the dice rolls using output_buffer
for roll in dice_rolls:

# Build a proper dice_str according to dice_size and roll
current_dice = get_single

Solution

Thought I wouldn't have much to say at first, but as improvements were added, code changed slightly.

Standard review

-
Grammar: 1 die, 2 dice, 2 choices, 2 eyes

It seems that 1 dice is accepted, but I learnt the former so I’ll stick with it.

  • Naming: having dice within most of variable names feels ugly to me, I’d rather simplify to face, faces, roll, rolls and so on. I have the same feeling about the use of get in functions names.



  • Random: randint(a,b) is an alias to randrange(a, b+1).



  • Containers: if len(output_buffer[0]) > 0 can be simplified to if output_buffer[0].



  • Output length: you hardcode 72 instead of using max_width.



  • Zero-based rolls: you check for it in get_single_dice_face but forgot to check it in print_dice_rolls. A 0-based roll equal to the number of faces should be invalid as well. Negative rolls too. Better convert your rolls to 0-based ones in print_dice_rolls and call get_single_dice_face with only 0-based rolls.



Pretty printing improvement

The first step before accounting for infinite-sized dice is to simplify handling of the various ascii-arts. The steps for a generic approach will be:

  • Build a template that can be used for up to limit-sized dice;



  • Fill this pattern with empty or full eyes based on the roll;



  • Mirror it and accound for the central eye.



The only form that can easily handle such task is the one using the generator expression:

def die_face(faces_count, roll, eyes='o '):
    """Return the full face of the roll for a faces_count-sided die."""

    # Build a proper die pattern according to faces_count
    if faces_count > 12:
        limit, pattern = 20, '---------\n|{} {} {} {}|\n|{} {} {} {}|\n|{} {}'

    elif faces_count > 6:
        limit, pattern = 12, ' ------- \n|{} {} {} {}|\n|{} {}'

    else:
        limit, pattern = 6, '+-----+\n| {} {} |\n| {}'

    # Fill the pattern with correct eye for current roll
    upper_die = pattern.format(*(eyes[roll<i] for i in range(1, limit, 2)))

    # Return mirrored pattern string with changing middle to get a full face
    return upper_die + eyes[roll&1] + upper_die[::-1]


This lead to slightly different output, but I find them more natural (compared to the outputs for your 6 and 7):

```
6-sided dice: [1, 2, 3, 4, 5, 6]
+-----+ +-----+ +-----+ +-----+ +-----+ +-----+
| | | o | | o | | o o | | o o | | o o |
| o | | | | o | | | | o | | o o |
| | | o | | o | | o o | | o o | | o o |
+-----+ +-----+ +-----+ +-----+ +-----+ +-----+

4-sided dice: [1, 2, 3, 4]
+-----+ +-----+ +-----+ +-----+
| | | | | | | |
| | | | | | | |
| | | | | | | |
+-----+ +-----+ +-----+ +-----+

9-sided dice: [8, 6, 4, 6, 7]
------- ------- ------- ------- -------
|# # # #| |# # # .| |# # . .| |# # # .| |# # # .|
|. ... .| |. ... .| |. ... .| |. ... .| |. .#. .|
|# # # #| |. # # #| |. . # #| |. # # #| |. # # #|
------- ------- ------- ------- -------

12-sided dice, zero-based: range(0, 12)
------- ------- ------- ------- ------- -------
| | |o | |o | |o o | |o o | |o o o |
| o | | | | o | | | | o | | |
| | | o| | o| | o o| | o o| | o o o|
------- ------- ------- ------- ------- -------
------- ------- ------- ------- ------- -------
|o o o | |o o o o| |o o o o| |o o o o| |o o o o| |o o o o|
| o | | | | o | |o o| |o o o| |o o o o|
| o o o| |o o o o| |o o o o| |o o o o| |o o o o| |o o o o|
------- ------- ------- ------- ------- -------

15-sided dice: [4, 1, 8, 14, 14]
--------- --------- --------- --------- ---------
| . .| |. . . .| | | | | | |
|. . . .| |. . . .| |. . . .| | .| | .|
|. ... .| |. .*. .| |. ... .| |. ... .| |. ... .|
|. . . .| |. . . .| |. . . .| |. | |. |
|. . | |. . . .| | | | | | |
--------- --------- --------- --------- ---------

20-sided dice, zero-based: range(0, 20)
--------- --------- --------- --------- --------- ---------
| | |o | |o | |o o | |o o | |o o o |
| | | | | | | | | | | |
| o | | | | o | | | | o | | |
| | | | | | | | | | | |
| | | o| | o| | o o| | o o| | o o o|
--------- --------- --------- --------- --------- ---------
--------- --------- --------- --------- --------- ---------
|o o o | |o o o o| |o o o o| |o o o o| |o o o o| |o o o o|
| | | | | | |o | |o | |o o |
| o | | | | o | | | | o

Code Snippets

def die_face(faces_count, roll, eyes='o '):
    """Return the full face of the roll for a faces_count-sided die."""

    # Build a proper die pattern according to faces_count
    if faces_count > 12:
        limit, pattern = 20, '---------\n|{} {} {} {}|\n|{} {} {} {}|\n|{} {}'

    elif faces_count > 6:
        limit, pattern = 12, ' ------- \n|{} {} {} {}|\n|{} {}'

    else:
        limit, pattern = 6, '+-----+\n| {} {} |\n| {}'

    # Fill the pattern with correct eye for current roll
    upper_die = pattern.format(*(eyes[roll<i] for i in range(1, limit, 2)))

    # Return mirrored pattern string with changing middle to get a full face
    return upper_die + eyes[roll&1] + upper_die[::-1]
6-sided dice: [1, 2, 3, 4, 5, 6]
+-----+  +-----+  +-----+  +-----+  +-----+  +-----+  
|     |  | o   |  | o   |  | o o |  | o o |  | o o |  
|  o  |  |     |  |  o  |  |     |  |  o  |  | o o |  
|     |  |   o |  |   o |  | o o |  | o o |  | o o |  
+-----+  +-----+  +-----+  +-----+  +-----+  +-----+  

4-sided dice: [1, 2, 3, 4]
+-----+  +-----+  +-----+  +-----+  
|     |  | *   |  | *   |  | * * |  
|  *  |  |     |  |  *  |  |     |  
|     |  |   * |  |   * |  | * * |  
+-----+  +-----+  +-----+  +-----+  

9-sided dice: [8, 6, 4, 6, 7]
 -------    -------    -------    -------    -------   
|# # # #|  |# # # .|  |# # . .|  |# # # .|  |# # # .|  
|. ... .|  |. ... .|  |. ... .|  |. ... .|  |. .#. .|  
|# # # #|  |. # # #|  |. . # #|  |. # # #|  |. # # #|  
 -------    -------    -------    -------    -------   

12-sided dice, zero-based: range(0, 12)
 -------    -------    -------    -------    -------    -------   
|       |  |o      |  |o      |  |o o    |  |o o    |  |o o o  |  
|   o   |  |       |  |   o   |  |       |  |   o   |  |       |  
|       |  |      o|  |      o|  |    o o|  |    o o|  |  o o o|  
 -------    -------    -------    -------    -------    -------   
 -------    -------    -------    -------    -------    -------   
|o o o  |  |o o o o|  |o o o o|  |o o o o|  |o o o o|  |o o o o|  
|   o   |  |       |  |   o   |  |o     o|  |o  o  o|  |o o o o|  
|  o o o|  |o o o o|  |o o o o|  |o o o o|  |o o o o|  |o o o o|  
 -------    -------    -------    -------    -------    -------   

15-sided dice: [4, 1, 8, 14, 14]
---------  ---------  ---------  ---------  ---------  
|* * . .|  |. . . .|  |* * * *|  |* * * *|  |* * * *|  
|. . . .|  |. . . .|  |. . . .|  |* * * .|  |* * * .|  
|. ... .|  |. .*. .|  |. ... .|  |. ... .|  |. ... .|  
|. . . .|  |. . . .|  |. . . .|  |. * * *|  |. * * *|  
|. . * *|  |. . . .|  |* * * *|  |* * * *|  |* * * *|  
---------  ---------  ---------  ---------  ---------  

20-sided dice, zero-based: range(0, 20)
---------  ---------  ---------  ---------  ---------  ---------  
|       |  |o      |  |o      |  |o o    |  |o o    |  |o o o  |  
|       |  |       |  |       |  |       |  |       |  |       |  
|   o   |  |       |  |   o   |  |       |  |   o   |  |       |  
|       |  |       |  |       |  |       |  |       |  |       |  
|       |  |      o|  |      o|  |    o o|  |    o o|  |  o o o|  
---------  ---------  ---------  ---------  ---------  ---------  
---------  ---------  ---------  ---------  ---------  ---------  
|o o o  |  |o o o o|  |o o o o|  |o o o o|  |o o o o|  |o o o o|  
|       |  |       |  |       |  |o      |  |o      |  |o o    |  
|   o   |  |       |  |   o   |  |       |  |   o   |  |       |  
|       |  |       |  |       |  |      o|  |      o|  |    o o|  
|  o o o|  |o o o o|  |o o o o|  |o o o o|  |o o o o|  |o o o o|  
---------  ---------  ---------  ---------  ---------  ---------  
---------  ---------  ---------  ---------  --------
from random import randint
from math import sqrt, ceil


class Die:
    def __init__(self, faces_count, eyes='o ', corner='+'):
        """Compute statistics about places of eyes on a die face"""

        if len(eyes) != 2:
            raise ValueError('Excpected two choices for eyes parameter')

        self.eyes = eyes
        width = int(sqrt(faces_count))
        height = ceil(faces_count/width)

        # Fix lengthes for nearly square ascii-art
        while height > width + 1:
            width += 1
            height = ceil(faces_count/width)

        # Account for python 2 ceil returning float
        height = int(height)
        if not height % 2:
            # Fix height to have a middle point
            height += 1

        # Values to generate a specific face
        self.limit = width * height
        self.faces = faces_count

        # Template of the face
        pattern = ' '.join('{}' for _ in range(width))
        pattern = '| {} |'.format(pattern)
        top = corner + ('-' * (2 * width + 1)) + corner
        middle = ' '.join('{}' for _ in range(width//2))
        middle = '| ' + middle + ' ' * (width % 2)
        self.pattern = '\n'.join([top] + [pattern for _ in range(height//2)] + [middle])

        # Size of the ascii-art
        self.width = len(top)
        self.height = self.pattern.count('\n') * 2 + 1


    def face(self, roll):
        """Return the full face of the roll for this die.

        roll is accounted in a 0-base fashion.
        """

        if not (0 <= roll < self.faces):
            raise ValueError('Roll is higher than die size or negative')

        eye_full, eye_empty = self.eyes

        # Fill the pattern with correct eye for current roll
        upper_face = self.pattern.format(*(eye_empty if roll < i else eye_full
            for i in range(1, self.limit, 2)))

        # Return mirrored pattern string with changing middle to get a full face
        return upper_face + self.eyes[roll&1] + upper_face[::-1]


def dice_rolls(faces_count, number_of_rolls):
    """Returns list with number_of_rolls from a faces_count-sided die"""

    return [randint(1, faces_count) for _ in range(number_of_rolls)]


def print_dice_rolls(faces_count, rolls, zero_based=False,  max_width=72, eyes='o '):
    """Pretty print all rolls using faces_count-sided di(c)e."""

    # Debug print for test purposes...
    print('\n{}-sided dice{}: {}'.format(faces_count, ', zero-based' if zero_based else '', rolls))

    # Set up some default values
    die = Die(faces_count, eyes)
    face_width = die.width

    # Will try to collate output of multiple dice rolls into lines
    # of up to max_width length
    output_buffer = ['' for _ in range(die.height)] 

    # Output the dice rolls using output_buffer
    # Make sure to use 0-based rolls
    for roll in (r + zero_based - 1 for r in rolls):

        # Flush buffer if too wide
        if len(output_buffer[0]) + face_width >= max_width:
            for idx, line in enumerate(outp
6-sided dice: [1, 2, 3, 4, 5, 6]
+-----+  +-----+  +-----+  +-----+  +-----+  +-----+  
|     |  | o   |  | o   |  | o o |  | o o |  | o o |  
|  o  |  |     |  |  o  |  |     |  |  o  |  | o o |  
|     |  |   o |  |   o |  | o o |  | o o |  | o o |  
+-----+  +-----+  +-----+  +-----+  +-----+  +-----+  

4-sided dice: [1, 2, 3, 4]
+-----+  +-----+  +-----+  +-----+  
|     |  | *   |  | *   |  | * * |  
|  *  |  |     |  |  *  |  |     |  
|     |  |   * |  |   * |  | * * |  
+-----+  +-----+  +-----+  +-----+  

9-sided dice: [3, 4, 4, 9, 6]
+-------+  +-------+  +-------+  +-------+  +-------+  
| # . . |  | # # . |  | # # . |  | # # # |  | # # # |  
| . # . |  | . . . |  | . . . |  | # # # |  | . . . |  
| . . # |  | . # # |  | . # # |  | # # # |  | # # # |  
+-------+  +-------+  +-------+  +-------+  +-------+  

12-sided dice, zero-based: range(0, 12)
+-------+  +-------+  +-------+  +-------+  +-------+  +-------+  
|       |  | o     |  | o     |  | o o   |  | o o   |  | o o o |  
|       |  |       |  |       |  |       |  |       |  |       |  
|   o   |  |       |  |   o   |  |       |  |   o   |  |       |  
|       |  |       |  |       |  |       |  |       |  |       |  
|       |  |     o |  |     o |  |   o o |  |   o o |  | o o o |  
+-------+  +-------+  +-------+  +-------+  +-------+  +-------+  
+-------+  +-------+  +-------+  +-------+  +-------+  +-------+  
| o o o |  | o o o |  | o o o |  | o o o |  | o o o |  | o o o |  
|       |  | o     |  | o     |  | o o   |  | o o   |  | o o o |  
|   o   |  |       |  |   o   |  |       |  |   o   |  |       |  
|       |  |     o |  |     o |  |   o o |  |   o o |  | o o o |  
| o o o |  | o o o |  | o o o |  | o o o |  | o o o |  | o o o |  
+-------+  +-------+  +-------+  +-------+  +-------+  +-------+  

15-sided dice: [15, 6, 7, 2, 15]
+---------+  +---------+  +---------+  +---------+  +---------+  
| * * * * |  | * * * . |  | * * * . |  | * . . . |  | * * * * |  
| * * * . |  | . . . . |  | . . . . |  | . . . . |  | * * * . |  
| . .*. . |  | . ... . |  | . .*. . |  | . ... . |  | . .*. . |  
| . * * * |  | . . . . |  | . . . . |  | . . . . |  | . * * * |  
| * * * * |  | . * * * |  | . * * * |  | . . . * |  | * * * * |  
+---------+  +---------+  +---------+  +---------+  +---------+  

20-sided dice, zero-based: range(0, 20)
+---------+  +---------+  +---------+  +---------+  +---------+  
|         |  | o       |  | o       |  | o o     |  | o o     |  
|         |  |         |  |         |  |         |  |         |  
|    o    |  |         |  |    o    |  |         |  |    o    |  
|         |  |         |  |         |  |         |  |         |  
|         |  |       o |  |       o |  |     o o |  |     o o |  
+---------+  +---------+  +---------+  +---------+  +---------+  
+---------+  +---------+  +---------+  +---------+  +---------+  
| o o o   |  | o o o   |  | o o o o |  | o o o o |  | o o o o |  
|         |  |         |  |         |  |         |  | o   

Context

StackExchange Code Review Q#111337, answer score: 3

Revisions (0)

No revisions yet.