<commit_before>from itertools import islice
import logging

import urwid

from flax.component import IRender
from flax.geometry import Rectangle
from flax.geometry import Size
from .util import LogWidget
from .util import ToggleableOverlay


log = logging.getLogger(__name__)


PALETTE = [
    # (name, other)
    # (name, fg, bg, mono, fg_high, bg_high)
    # high colors: 0 6 8 a d f, plus g0 .. g100 (24 of them)

    # UI
    ('ui-dividers', 'dark gray', 'default', None, 'g30', 'default'),
    ('message-old', 'dark gray', 'default', None, '#666', 'default'),
    ('message-fresh', 'white', 'default', None, '#fff', 'default'),
    ('inventory-default', 'default', 'default', None, 'default', 'default'),
    ('inventory-selected', 'default', 'dark gray', None, 'default', 'g15'),
    ('health-full-fill', 'white', 'dark green', None, '#fff', '#080'),
    ('health-full-empty', 'dark green', 'default', None, '#060', 'default'),

    # Architecture
    ('wall', 'light gray', 'default', None, 'g70', 'g70'),
    ('rock', 'dark gray', 'default', None, '#860', 'default'),
    ('floor', 'black', 'default', None, 'g20', 'default'),
    ('water', 'light blue', 'dark blue', None, '#06f', '#008'),
    ('bridge', 'dark blue', 'dark gray', None, 'g50', 'g30'),
    ('stairs', 'white', 'dark gray', None, '#aaa', 'g19'),
    ('grass', 'dark green', 'default', None, '#060', 'default'),
    ('tree', 'dark green', 'default', None, '#080', 'default'),
    ('dirt', 'brown', 'default', None, '#660', 'default'),
    ('decay0', 'white', 'default', None, 'g85', 'default'),
    ('decay1', 'light gray', 'default', None, 'g65', 'default'),
    ('decay2', 'dark gray', 'default', None, 'g45', 'default'),
    ('decay3', 'black', 'default', None, 'g25', 'default'),

    # Special
    ('gate', 'light magenta', 'default', None, '#a0f', 'default'),

    # Creatures
    ('player', 'yellow', 'default', None, '#ff6', 'default'),
    ('salamango', 'brown', 'default', None, '#fa0', 'default'),

    # Items
    ('potion', 'light magenta', 'default', None, '#f6f', 'default'),
    ('wood', 'brown', 'default', None, '#960', 'default'),

    # Debug
    ('stdout', 'light gray', 'default', None, '#aaa', 'default'),
    ('stderr', 'light red', 'default', None, '#d66', 'default'),
]


class CellCanvas(urwid.Canvas):
    def __init__(self, map):
        self.map = map

        super().__init__()

    def rows(self):
        return self.map.rect.height

    def cols(self):
        return self.map.rect.width

    def translated_coords(self, dx, dy):
        return None

    def content(self, trim_left=0, trim_top=0, cols=None, rows=None, attr=None):
        for row in islice(self.map.rows, trim_top, trim_top + rows):
            ret = []
            current_attr = None
            current_glyphs = []
            for tile in islice(row, trim_left, trim_left + cols):
                obj = next(tile.entities)
                render = IRender(obj)
                glyph, attr = render.sprite, render.color
                if current_attr != attr:
                    if current_glyphs:
                        ret.append((current_attr, None, ''.join(current_glyphs).encode('utf8')))
                        current_glyphs = []
                    current_attr = attr
                current_glyphs.append(glyph.value)
            if current_glyphs:
                ret.append((current_attr, None, ''.join(current_glyphs).encode('utf8')))

            yield ret

    def content_delta(self):
        return self.content()


class CellWidget(urwid.Widget):
    _sizing = {'box'}
    _selectable = True

    # Number of rows/columns that must exist between the player and the edge of
    # the map
    MAP_MARGIN = 4

    def __init__(self, world):
        super().__init__()

        self.world = world

        self.viewport = None

    def _adjust_viewport(self, viewport, width, pos, bounds):
        """Adjust the given `viewport` span so that it's the given `width`,
        contains the point `pos`, and doesn't unnecessarily exceed `bounds`
        (the span of the map).

        Returns a new `Span`.
        """
        # The goal here is to scroll the map /as little as possible/, to reduce
        # the changes we'll have to write to the terminal.  Reduces flicker,
        # helps with lag when played over a network, and lets the player move
        # around without feeling like it's just the map scrolling instead.

        # Make sure the player position is actually visible to start with
        viewport = viewport.shift_into_view(pos)

        # Two steps here.
        # 1. If the size changed, position the new size so the player is still
        # roughly the same relative distance across the screen.
        viewport = viewport.scale(width, pivot=pos)

        # 2. If the player is no longer within the map view (excluding the
        # border of MAP_MARGIN), shift the view towards the player.
        # BUT!  Avoid having needless space on the bottom and right, and avoid
        # having ANY space on the top and left.

        # Shrink the margin so that it's less than half the viewport.  So if
        # MAP_MARGIN is 4, but the viewport is only 6 wide, cut it down to 2.
        margin = min(self.MAP_MARGIN, (width - 1) // 2)

        # Need to move the viewport by the distance from the left margin to the
        # player, if that distance is < 0, and reverse for the right margin
        viewport = viewport.shift_into_view(pos, margin=margin)

        # We never want empty space on the leading side, so start cannot go
        # below map.start (0).  We want to /avoid/ empty space on the trailing
        # side, so end cannot go above map.end...  unless the map is smaller
        # than the viewport, in which case it can go until map.start + width.
        move = max(0, bounds.start - viewport.start)
        move = min(move, max(bounds.end, bounds.start + width) - viewport.end)

        return viewport + move

    def render(self, size, focus=False):
        size = Size(*size)
        map = self.world.current_map
        map_rect = map.rect
        player_position = map.find(self.world.player).position

        if not self.viewport:
            # Let's pretend the map itself is the viewport, and the below logic
            # can adjust it as necessary.<commit_msg>Center the map when its smaller than the available space.<commit_after>from itertools import islice
import logging

import urwid

from flax.component import IRender
from flax.geometry import Rectangle
from flax.geometry import Size
from .util import LogWidget
from .util import ToggleableOverlay


log = logging.getLogger(__name__)


PALETTE = [
    # (name, other)
    # (name, fg, bg, mono, fg_high, bg_high)
    # high colors: 0 6 8 a d f, plus g0 .. g100 (24 of them)

    # UI
    ('ui-dividers', 'dark gray', 'default', None, 'g30', 'default'),
    ('message-old', 'dark gray', 'default', None, '#666', 'default'),
    ('message-fresh', 'white', 'default', None, '#fff', 'default'),
    ('inventory-default', 'default', 'default', None, 'default', 'default'),
    ('inventory-selected', 'default', 'dark gray', None, 'default', 'g15'),
    ('health-full-fill', 'white', 'dark green', None, '#fff', '#080'),
    ('health-full-empty', 'dark green', 'default', None, '#060', 'default'),

    # Architecture
    ('wall', 'light gray', 'default', None, 'g70', 'g70'),
    ('rock', 'dark gray', 'default', None, '#860', 'default'),
    ('floor', 'black', 'default', None, 'g20', 'default'),
    ('water', 'light blue', 'dark blue', None, '#06f', '#008'),
    ('bridge', 'dark blue', 'dark gray', None, 'g50', 'g30'),
    ('stairs', 'white', 'dark gray', None, '#aaa', 'g19'),
    ('grass', 'dark green', 'default', None, '#060', 'default'),
    ('tree', 'dark green', 'default', None, '#080', 'default'),
    ('dirt', 'brown', 'default', None, '#660', 'default'),
    ('decay0', 'white', 'default', None, 'g85', 'default'),
    ('decay1', 'light gray', 'default', None, 'g65', 'default'),
    ('decay2', 'dark gray', 'default', None, 'g45', 'default'),
    ('decay3', 'black', 'default', None, 'g25', 'default'),

    # Special
    ('gate', 'light magenta', 'default', None, '#a0f', 'default'),

    # Creatures
    ('player', 'yellow', 'default', None, '#ff6', 'default'),
    ('salamango', 'brown', 'default', None, '#fa0', 'default'),

    # Items
    ('potion', 'light magenta', 'default', None, '#f6f', 'default'),
    ('wood', 'brown', 'default', None, '#960', 'default'),

    # Debug
    ('stdout', 'light gray', 'default', None, '#aaa', 'default'),
    ('stderr', 'light red', 'default', None, '#d66', 'default'),
]


class CellCanvas(urwid.Canvas):
    def __init__(self, map):
        self.map = map

        super().__init__()

    def rows(self):
        return self.map.rect.height

    def cols(self):
        return self.map.rect.width

    def translated_coords(self, dx, dy):
        return None

    def content(self, trim_left=0, trim_top=0, cols=None, rows=None, attr=None):
        for row in islice(self.map.rows, trim_top, trim_top + rows):
            ret = []
            current_attr = None
            current_glyphs = []
            for tile in islice(row, trim_left, trim_left + cols):
                obj = next(tile.entities)
                render = IRender(obj)
                glyph, attr = render.sprite, render.color
                if current_attr != attr:
                    if current_glyphs:
                        ret.append((current_attr, None, ''.join(current_glyphs).encode('utf8')))
                        current_glyphs = []
                    current_attr = attr
                current_glyphs.append(glyph.value)
            if current_glyphs:
                ret.append((current_attr, None, ''.join(current_glyphs).encode('utf8')))

            yield ret

    def content_delta(self):
        return self.content()


class CellWidget(urwid.Widget):
    _sizing = {'box'}
    _selectable = True

    # Proportion of the map that must be visible in all directions from the
    # player.  Should probably be smaller than 0.5.
    MAP_MARGIN = 0.2

    def __init__(self, world):
        super().__init__()

        self.world = world

        self.viewport = None

    def _adjust_viewport(self, viewport, width, pos, bounds):
        """Adjust the given `viewport` span so that it's the given `width`,
        contains the point `pos`, and doesn't unnecessarily exceed `bounds`
        (the span of the map).

        Returns a new `Span`.
        """
        # If the entire map fits within the viewport, just show the whole
        # thing, centered within the available space.
        if len(bounds) <= width:
            return bounds.scale(width)

        # The goal here is to scroll the map /as little as possible/, to reduce
        # the changes we'll have to write to the terminal.  Reduces flicker,
        # helps with lag when played over a network, and lets the player move
        # around without feeling like it's just the map scrolling instead.

        # Make sure the player position is actually visible to start with
        viewport = viewport.shift_into_view(pos)

        # Two steps here.
        # 1. If the size changed, position the new size so the player is still
        # roughly the same relative distance across the screen.
        viewport = viewport.scale(width, pivot=pos)

        # 2. If the player is no longer within the map view (excluding the
        # border of MAP_MARGIN), shift the view towards the player.
        # Need to move the viewport by the distance from the left margin to the
        # player, if that distance is < 0, and reverse for the right margin
        margin = int(self.MAP_MARGIN * width + 0.99)
        viewport = viewport.shift_into_view(pos, margin=margin)

        # If there's any empty space left, shift the map to fill it.  (This can
        # never produce space on the other side, because we already checked
        # for a map smaller than the viewport above.)
        leading = viewport.end - bounds.end
        trailing = bounds.start - viewport.start
        if leading > 0:
            move = - leading
        elif trailing > 0:
            move = trailing
        else:
            move = 0

        return viewport + move

    def render(self, size, focus=False):
        size = Size(*size)
        map = self.world.current_map
        map_rect = map.rect
        player_position = map.find(self.world.player).position

        if not self.viewport:
            # Let's pretend the map itself is the viewport, and the below logic
            # can adjust it as necessary.
{'fraction_non_alphanumeric': 0.13921040114158872, 'fraction_numerical': 0.013952750911685428, 'mean_word_length': 2.596066134549601, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6682900', 'n_tokens_mistral': 3906, 'n_tokens_neox': 3661, 'n_words': 1326}