Source code for opend6_tools.spell_measure

r"""
Creates the Spell Measures table from first principles.

Two cases:

-   Very first range has steps: 1, 1.5, 2.5, 3.5, 5.

-   Remaining ranges and bands have steps:  1, 1.5, 2, 2.5, 4, 6.

Both are *essentially* this.

..  math::

    v = \lceil 5 \times \log_{10}(m) \rceil

However, the very first range and all the remaining ranges have slightly
different decimal rounding rules.

-   The first five are ``ROUND_CEILING``.

-   All the rest are  ``ROUND_HALF_UP``.

The whole table has 3 ranges: :math:`\times 10^0`, :math:`\times 10^1`, and :math:`\times 10^2`,
in 6 bands: "", ",000", " million", " billion", " quadrillion", " quintillion".


Implementation
==============

..  autofunction:: value_measure

..  autofunction:: main
"""

from collections.abc import Iterator
from decimal import Decimal, localcontext, ROUND_CEILING, ROUND_HALF_UP
import csv
import sys

import typer


[docs] def value_measure() -> Iterator[tuple[Decimal, str]]: """Emits the sequence of game values and the source measures.""" first = [1, 1.5, 2.5, 3.5, 5] rest = [1, 1.5, 2.5, 4, 6] bands = [ (group, scale, format) for group, format in [ (1, lambda measure, scale: f"{int(measure * scale)}"), (10**3, lambda measure, scale: f"{int(measure * scale * 1000):,}"), (10**6, lambda measure, scale: f"{measure * scale:.1f} million"), (10**9, lambda measure, scale: f"{measure * scale:.1f} billion"), (10**12, lambda measure, scale: f"{measure * scale:.1f} trillion"), (10**15, lambda measure, scale: f"{measure * scale:.1f} quadrillion"), (10**18, lambda measure, scale: f"{measure * scale:.1f} quintillion"), ] for scale in [10**0, 10**1, 10**2] ] # First 5 -- measure < 10. for measure in first: m_10 = Decimal(str(measure)) with localcontext(rounding=ROUND_CEILING): v = (m_10.log10() * 5).quantize(Decimal("1")) yield v, str(measure) # yield round(5 * math.log(measure, 10) + 0.25), measure # Remainder -- measure >= 10 up to 100 quintillion which has a value of 100. for group, scale, format_expr in bands: for measure in rest: if 10 <= (m := measure * scale * group) <= 100 * 10**18: m_10 = Decimal(m) with localcontext(rounding=ROUND_HALF_UP): v = (m_10.log10() * 5).quantize(Decimal("1")) yield v, format_expr(measure, scale)
# yield round(5 * math.log(m, 10)), format_expr(measure, scale)
[docs] def main(rows: int = 35) -> None: """Produce CSV-formatted table of Spell Measures. The number of rows defines the format. - For 100 rows, the table is in a single pair of columns. - For less than 100 rows, the table is split into columns to meet the row constraint. The published table has 35 rows, therefore, this is the default. """ table = list(value_measure()) target = csv.writer(sys.stdout) if rows >= len(table): target.writerow(("Value", "Measure")) target.writerows(table) else: columns = (len(table) + rows) // rows target.writerow(("Val.", "Measure") * columns) for row in range(rows): source_row = (row + col * rows for col in range(columns)) display_row = [ table[src][a] if src < len(table) else None for src in source_row for a in (0, 1) ] target.writerow(display_row)
app = typer.Typer() app.command()(main) if __name__ == "__main__": # pragma: no cover app()