patternpythonMinor
Pretty print dice faces from multiple rolls of multi-sided dices
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
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.
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:
The only form that can easily handle such task is the one using the generator expression:
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
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
dicewithin most of variable names feels ugly to me, I’d rather simplify toface,faces,roll,rollsand so on. I have the same feeling about the use ofgetin functions names.
- Random:
randint(a,b)is an alias torandrange(a, b+1).
- Containers:
if len(output_buffer[0]) > 0can be simplified toif output_buffer[0].
- Output length: you hardcode
72instead of usingmax_width.
- Zero-based rolls: you check for it in
get_single_dice_facebut forgot to check it inprint_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 inprint_dice_rollsand callget_single_dice_facewith 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(outp6-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.