<commit_before># encoding: utf-8
"""This module provides 'sequence awareness'."""
# std imports
import functools
import textwrap
import math
import re

# local
from blessed._capabilities import CAPABILITIES_CAUSE_MOVEMENT

# 3rd party
import wcwidth
import six

__all__ = ('Sequence', 'SequenceTextWrapper', 'measure_length')

class Termcap(object):
    """Terminal capability of given variable name and pattern."""
    def __init__(self, name, pattern, attribute):
        """
        Class initializer.

        :arg str name: name describing capability.
        :arg str pattern: regular expression string.
        :arg str attribute: :class:`~.Terminal` attribute used to build
            this terminal capability.
        """
        self.name = name
        self.pattern = pattern
        self.attribute = attribute
        self._re_compiled = None

    def __repr__(self):
        # pylint: disable=redundant-keyword-arg
        return '<Termcap {self.name}:{self.pattern!r}>'.format(self=self)

    @property
    def named_pattern(self):
        # pylint: disable=redundant-keyword-arg
        return '(?P<{self.name}>{self.pattern})'.format(self=self)

    @property
    def re_compiled(self):
        if self._re_compiled is None:
            self._re_compiled = re.compile(self.pattern)
        return self._re_compiled

    @property
    def will_move(self):
        """Whether capability causes cursor movement."""
        return self.name in CAPABILITIES_CAUSE_MOVEMENT

    def horizontal_distance(self, text):
        """
        Horizontal carriage adjusted by capability, may be negative.

        :rtype: int
        :arg str text: for capabilities *parm_left_cursor*,
            *parm_right_cursor*, provide the matching sequence
            text, its interpreted distance is returned.

        :returns: 0 except for matching '
        """
        value = {
            'cursor_left': -1,
            'backspace': -1,
            'cursor_right': 1,
            'tab': 8,
            'ascii_tab': 8,
        }.get(self.name, None)
        if value is not None:
            return value

        unit = {
            'parm_left_cursor': -1,
            'parm_right_cursor': 1
        }.get(self.name, None)
        if unit is not None:
            value = int(self.re_compiled.match(text).group(1))
            return unit * value

        return 0

    # pylint: disable=too-many-arguments
    @classmethod
    def build(cls, name, capability, attribute, nparams=0,
              numeric=99, match_grouped=False, match_any=False,
              match_optional=False):
        r"""
        Class factory builder for given capability definition.

        :arg str name: Variable name given for this pattern.
        :arg str capability: A unicode string representing a terminal
            capability to build for. When ``nparams`` is non-zero, it
            must be a callable unicode string (such as the result from
            ``getattr(term, 'bold')``.
        :arg attribute: The terminfo(5) capability name by which this
            pattern is known.
        :arg int nparams: number of positional arguments for callable.
        :arg bool match_grouped: If the numeric pattern should be
            grouped, ``(\d+)`` when ``True``, ``\d+`` default.
        :arg bool match_any: When keyword argument ``nparams`` is given,
            *any* numeric found in output is suitable for building as
            pattern ``(\d+)``.  Otherwise, only the first matching value of
            range *(numeric - 1)* through *(numeric + 1)* will be replaced by
            pattern ``(\d+)`` in builder.
        :arg bool match_optional: When ``True``, building of numeric patterns
            containing ``(\d+)`` will be built as optional, ``(\d+)?``.
        """
        _numeric_regex = r'\d+'
        if match_grouped:
            _numeric_regex = r'(\d+)'
        if match_optional:
            _numeric_regex = r'(\d+)?'
        numeric = 99 if numeric is None else numeric

        # basic capability attribute, not used as a callable
        if nparams == 0:
            return cls(name, re.escape(capability), attribute)

        # a callable capability accepting numeric argument
        _outp = re.escape(capability(*(numeric,) * nparams))
        if not match_any:
            for num in range(numeric - 1, numeric + 2):
                if str(num) in _outp:
                    pattern = _outp.replace(str(num), _numeric_regex)
                    return cls(name, pattern, attribute)

        if match_grouped:
            pattern = re.sub(r'(\d+)', _numeric_regex, _outp)
        else:
            pattern = re.sub(r'\d+', _numeric_regex, _outp)
        return cls(name, pattern, attribute)

class SequenceTextWrapper(textwrap.TextWrapper):
    """This docstring overridden."""

    def __init__(self, width, term, **kwargs):
        """
        Class initializer.

        This class supports the :meth:`~.Terminal.wrap` method.
        """
        self.term = term
        textwrap.TextWrapper.__init__(self, width, **kwargs)

    def _wrap_chunks(self, chunks):
        """
        Sequence-aware variant of :meth:`textwrap.TextWrapper._wrap_chunks`.

        This simply ensures that word boundaries are not broken mid-sequence,
        as standard python textwrap would incorrectly determine the length
        of a string containing sequences, and may also break consider sequences
        part of a "word" that may be broken by hyphen (``-``), where this
        implementation corrects both.
        """
        lines = []
        if self.width <= 0 or not isinstance(self.width, int):
            raise ValueError(
                "invalid width {0!r}({1!r}) (must be integer > 0)"
                .format(self.width, type(self.width)))

        term = self.term
        drop_whitespace = not hasattr(self, 'drop_whitespace'
                                      ) or self.drop_whitespace
        chunks.reverse()
        while chunks:
            cur_line = []
            cur_len = 0
            if lines:
                indent = self.subsequent_indent
            else:
                indent = self.initial_indent
            width = self.width - len(indent)
            if drop_whitespace and (
                    Sequence(chunks[-1], term).strip() == '' and lines):
                del chunks[-1]
            while chunks:
                chunk_len = Sequence(chunks[-1], term).length()
                if cur_len + chunk_len <= width:
                    cur_line.append(chunks.pop())
                    cur_len += chunk_len
                else:
                    break
            if chunks and Sequence(chunks[-1], term).length() > width:
                self._handle_long_word(chunks, cur_line, cur_len, width)
            if drop_whitespace and (
                    cur_line and Sequence(cur_line[-1], term).strip() == ''):
                del cur_line[-1]
            if cur_line:
                lines.append(indent + u''.join(cur_line))
        return lines

    def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
        """Sequence-aware :meth:`textwrap.TextWrapper._handle_long_word`.

        This simply ensures that word boundaries are not broken mid-sequence,
        as standard python textwrap would incorrectly determine the length
        of a string containing sequences, and may also break consider sequences
        part of a "word" that may be broken by hyphen (``-``), where this
        implementation corrects both.
        """
        # Figure out when indent is larger than the specified width, and make
        # sure at least one character is stripped off on every pass
        if width < 1:
            space_left = 1
        else:
            space_left = width - cur_len

        # If we're allowed to break long words, then do so: put as much
        # of the next chunk onto the current line as will fit.

        if self.break_long_words:
            term = self.term
            chunk = reversed_chunks[-1]
            idx = nxt = 0
            for text, _ in iter_parse(term, chunk):
                nxt += len(text)
                if Sequence(chunk[:nxt], term).length() > space_left:
                    break
                idx = nxt
            cur_line.append(chunk[:idx])
            reversed_chunks[-1] = chunk[idx:]

        # Otherwise, we have to preserve the long word intact.  Only add
        # it to the current line if there's nothing already there --
        # that minimizes how much we violate the width constraint.
        elif not cur_line:
            cur_line.append(reversed_chunks.pop())

        # If we're not allowed to break long words, and there's already
        # text on the current line, do nothing.  Next time through the
        # main loop of _wrap_chunks(), we'll wind up here again, but
        # cur_len will be zero, so the next line will be entirely
        # devoted to the long word that we can't handle right now.

SequenceTextWrapper.__doc__ = textwrap.TextWrapper.__doc__

class Sequence(six.text_type):
    """
    A "sequence-aware" version of the base :class:`str` class.

    This unicode-derived class understands the effect of escape sequences
    of printable length, allowing a properly implemented :meth:`rjust`,
    :meth:`ljust`, :meth:`center`, and :meth:`length`.
    """

    def __new__(cls, sequence_text, term):
        """
        Class constructor.

        :arg sequence_text: A string that may contain sequences.
        :arg blessed.Terminal term: :class:`~.Terminal` instance.
        """
        new = six.text_type.__new__(cls, sequence_text)
        new._term = term
        return new

    def ljust(self, width, fillchar=u' '):
        """
        Return string containing sequences, left-adjusted.

        :arg int width: Total width given to right-adjust ``text``.  If
            unspecified, the width of the attached terminal is used (default).
        :arg str fillchar: String for padding right-of ``text``.
        :returns: String of ``text``, right-aligned by ``width``.
        :rtype: str
        """
        rightside = fillchar * int(
            (max(0.0, float(width - self.length()))) / float(len(fillchar)))
        return u''.join((self, rightside))

    def rjust(self, width, fillchar=u' '):
        """
        Return string containing sequences, right-adjusted.

        :arg int width: Total width given to right-adjust ``text``.  If
            unspecified, the width of the attached terminal is used (default).
        :arg str fillchar: String for padding left-of ``text``.
        :returns: String of ``text``, right-aligned by ``width``.
        :rtype: str
        """
        leftside = fillchar * int(
            (max(0.0, float(width - self.length()))) / float(len(fillchar)))
        return u''.join((leftside, self))

    def center(self, width, fillchar=u' '):
        """
        Return string containing sequences, centered.

        :arg int width: Total width given to center ``text``.  If
            unspecified, the width of the attached terminal is used (default).
        :arg str fillchar: String for padding left and right-of ``text``.
        :returns: String of ``text``, centered by ``width``.
        :rtype: str
        """
        split = max(0.0, float(width) - self.length()) / 2
        leftside = fillchar * int(
            (max(0.0, math.floor(split))) / float(len(fillchar)))
        rightside = fillchar * int(
            (max(0.0, math.ceil(split))) / float(len(fillchar)))
        return u''.join((leftside, self, rightside))

    def length(self):
        r"""
        Return the printable length of string containing sequences.

        Strings containing ``term.left`` or ``\b`` will cause "overstrike",
        but a length less than 0 is not ever returned. So ``_\b+`` is a
        length of 1 (displays as ``+``), but ``\b`` alone is simply a
        length of 0.

        Some characters may consume more than one cell, mainly those CJK
        Unified Ideographs (Chinese, Japanese, Korean) defined by Unicode
        as half or full-width characters.
        """
        # because control characters may return -1, "clip" their length to 0.
        clip = functools.partial(max, 0)
        return sum(clip(wcwidth.wcwidth(w_char))
                   for w_char in self.strip_seqs())

    # we require ur"" for the docstring, but it is not supported by pep257
    # tool: https://github.com/GreenSteam/pep257/issues/116
    length.__doc__ += (
        u"""For example:

            >>> from blessed import Terminal
            >>> from blessed.sequences import Sequence
            >>> term = Terminal()
            >>> Sequence(term.clear + term.red(u'コンニチハ'), term).length()
            10

        .. note:: Although accounted for, strings containing sequences such as
            ``term.clear`` will not give accurate returns, it is not
            considered lengthy (a length of 0).
        """)

    def strip(self, chars=None):
        """
        Return string of sequences, leading, and trailing whitespace removed.

        :arg str chars: Remove characters in chars instead of whitespace.
        :rtype: str
        """
        return self.strip_seqs().strip(chars)

    def lstrip(self, chars=None):
        """
        Return string of all sequences and leading whitespace removed.

        :arg str chars: Remove characters in chars instead of whitespace.
        :rtype: str
        """
        return self.strip_seqs().lstrip(chars)

    def rstrip(self, chars=None):
        """
        Return string of all sequences and trailing whitespace removed.

        :arg str chars: Remove characters in chars instead of whitespace.
        :rtype: str
        """
        return self.strip_seqs().rstrip(chars)

    def strip_seqs(self):
        """
        Return ``text`` stripped of only its terminal sequences.

        :rtype: str
        """
        gen = iter_parse(self._term, self.padd())
        return u''.join(text for text, cap in gen if not cap)

    def padd(self):
        """
        Return non-destructive horizontal movement as destructive spacing.

        :rtype: str
        """
        outp = ''
        for text, cap in iter_parse(self._term, self):
            if not cap:
                outp += text
                continue

            value = cap.horizontal_distance(text)
            if value > 0:
                outp += ' ' * value
            elif value < 0:
                outp = outp[:value]
            else:
                outp += text
        return outp

def iter_parse(term, text):
    """
    Generator yields (text, capability) for characters of ``text``.

    value for ``capability`` may be ``None``, where ``text`` is
    :class:`str` of length 1.  Otherwise, ``text`` is a full
    matching sequence of given capability.
    """
    for match in re.finditer(term._caps_compiled_any, text):
        name = match.lastgroup
        value = match.group(name)
        if name == 'MISMATCH':
            yield (value, None)
        else:
            yield value, term.caps[name]

def measure_length(text, term):
    """
    .. deprecated:: 1.12.0.

    :rtype: int
    """
    try:
        text, capability = next(iter_parse(term, text))
        if capability:
            return len(text)
    except StopIteration:
        return 0
    return 0
<commit_msg>allow 'iter_parse' from "import *" phrase<commit_after># encoding: utf-8
"""This module provides 'sequence awareness'."""
# std imports
import functools
import textwrap
import math
import re

# local
from blessed._capabilities import CAPABILITIES_CAUSE_MOVEMENT

# 3rd party
import wcwidth
import six

__all__ = ('Sequence', 'SequenceTextWrapper', 'iter_parse', 'measure_length')

class Termcap(object):
    """Terminal capability of given variable name and pattern."""
    def __init__(self, name, pattern, attribute):
        """
        Class initializer.

        :arg str name: name describing capability.
        :arg str pattern: regular expression string.
        :arg str attribute: :class:`~.Terminal` attribute used to build
            this terminal capability.
        """
        self.name = name
        self.pattern = pattern
        self.attribute = attribute
        self._re_compiled = None

    def __repr__(self):
        # pylint: disable=redundant-keyword-arg
        return '<Termcap {self.name}:{self.pattern!r}>'.format(self=self)

    @property
    def named_pattern(self):
        # pylint: disable=redundant-keyword-arg
        return '(?P<{self.name}>{self.pattern})'.format(self=self)

    @property
    def re_compiled(self):
        if self._re_compiled is None:
            self._re_compiled = re.compile(self.pattern)
        return self._re_compiled

    @property
    def will_move(self):
        """Whether capability causes cursor movement."""
        return self.name in CAPABILITIES_CAUSE_MOVEMENT

    def horizontal_distance(self, text):
        """
        Horizontal carriage adjusted by capability, may be negative.

        :rtype: int
        :arg str text: for capabilities *parm_left_cursor*,
            *parm_right_cursor*, provide the matching sequence
            text, its interpreted distance is returned.

        :returns: 0 except for matching '
        """
        value = {
            'cursor_left': -1,
            'backspace': -1,
            'cursor_right': 1,
            'tab': 8,
            'ascii_tab': 8,
        }.get(self.name, None)
        if value is not None:
            return value

        unit = {
            'parm_left_cursor': -1,
            'parm_right_cursor': 1
        }.get(self.name, None)
        if unit is not None:
            value = int(self.re_compiled.match(text).group(1))
            return unit * value

        return 0

    # pylint: disable=too-many-arguments
    @classmethod
    def build(cls, name, capability, attribute, nparams=0,
              numeric=99, match_grouped=False, match_any=False,
              match_optional=False):
        r"""
        Class factory builder for given capability definition.

        :arg str name: Variable name given for this pattern.
        :arg str capability: A unicode string representing a terminal
            capability to build for. When ``nparams`` is non-zero, it
            must be a callable unicode string (such as the result from
            ``getattr(term, 'bold')``.
        :arg attribute: The terminfo(5) capability name by which this
            pattern is known.
        :arg int nparams: number of positional arguments for callable.
        :arg bool match_grouped: If the numeric pattern should be
            grouped, ``(\d+)`` when ``True``, ``\d+`` default.
        :arg bool match_any: When keyword argument ``nparams`` is given,
            *any* numeric found in output is suitable for building as
            pattern ``(\d+)``.  Otherwise, only the first matching value of
            range *(numeric - 1)* through *(numeric + 1)* will be replaced by
            pattern ``(\d+)`` in builder.
        :arg bool match_optional: When ``True``, building of numeric patterns
            containing ``(\d+)`` will be built as optional, ``(\d+)?``.
        """
        _numeric_regex = r'\d+'
        if match_grouped:
            _numeric_regex = r'(\d+)'
        if match_optional:
            _numeric_regex = r'(\d+)?'
        numeric = 99 if numeric is None else numeric

        # basic capability attribute, not used as a callable
        if nparams == 0:
            return cls(name, re.escape(capability), attribute)

        # a callable capability accepting numeric argument
        _outp = re.escape(capability(*(numeric,) * nparams))
        if not match_any:
            for num in range(numeric - 1, numeric + 2):
                if str(num) in _outp:
                    pattern = _outp.replace(str(num), _numeric_regex)
                    return cls(name, pattern, attribute)

        if match_grouped:
            pattern = re.sub(r'(\d+)', _numeric_regex, _outp)
        else:
            pattern = re.sub(r'\d+', _numeric_regex, _outp)
        return cls(name, pattern, attribute)

class SequenceTextWrapper(textwrap.TextWrapper):
    """This docstring overridden."""

    def __init__(self, width, term, **kwargs):
        """
        Class initializer.

        This class supports the :meth:`~.Terminal.wrap` method.
        """
        self.term = term
        textwrap.TextWrapper.__init__(self, width, **kwargs)

    def _wrap_chunks(self, chunks):
        """
        Sequence-aware variant of :meth:`textwrap.TextWrapper._wrap_chunks`.

        This simply ensures that word boundaries are not broken mid-sequence,
        as standard python textwrap would incorrectly determine the length
        of a string containing sequences, and may also break consider sequences
        part of a "word" that may be broken by hyphen (``-``), where this
        implementation corrects both.
        """
        lines = []
        if self.width <= 0 or not isinstance(self.width, int):
            raise ValueError(
                "invalid width {0!r}({1!r}) (must be integer > 0)"
                .format(self.width, type(self.width)))

        term = self.term
        drop_whitespace = not hasattr(self, 'drop_whitespace'
                                      ) or self.drop_whitespace
        chunks.reverse()
        while chunks:
            cur_line = []
            cur_len = 0
            if lines:
                indent = self.subsequent_indent
            else:
                indent = self.initial_indent
            width = self.width - len(indent)
            if drop_whitespace and (
                    Sequence(chunks[-1], term).strip() == '' and lines):
                del chunks[-1]
            while chunks:
                chunk_len = Sequence(chunks[-1], term).length()
                if cur_len + chunk_len <= width:
                    cur_line.append(chunks.pop())
                    cur_len += chunk_len
                else:
                    break
            if chunks and Sequence(chunks[-1], term).length() > width:
                self._handle_long_word(chunks, cur_line, cur_len, width)
            if drop_whitespace and (
                    cur_line and Sequence(cur_line[-1], term).strip() == ''):
                del cur_line[-1]
            if cur_line:
                lines.append(indent + u''.join(cur_line))
        return lines

    def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
        """Sequence-aware :meth:`textwrap.TextWrapper._handle_long_word`.

        This simply ensures that word boundaries are not broken mid-sequence,
        as standard python textwrap would incorrectly determine the length
        of a string containing sequences, and may also break consider sequences
        part of a "word" that may be broken by hyphen (``-``), where this
        implementation corrects both.
        """
        # Figure out when indent is larger than the specified width, and make
        # sure at least one character is stripped off on every pass
        if width < 1:
            space_left = 1
        else:
            space_left = width - cur_len

        # If we're allowed to break long words, then do so: put as much
        # of the next chunk onto the current line as will fit.

        if self.break_long_words:
            term = self.term
            chunk = reversed_chunks[-1]
            idx = nxt = 0
            for text, _ in iter_parse(term, chunk):
                nxt += len(text)
                if Sequence(chunk[:nxt], term).length() > space_left:
                    break
                idx = nxt
            cur_line.append(chunk[:idx])
            reversed_chunks[-1] = chunk[idx:]

        # Otherwise, we have to preserve the long word intact.  Only add
        # it to the current line if there's nothing already there --
        # that minimizes how much we violate the width constraint.
        elif not cur_line:
            cur_line.append(reversed_chunks.pop())

        # If we're not allowed to break long words, and there's already
        # text on the current line, do nothing.  Next time through the
        # main loop of _wrap_chunks(), we'll wind up here again, but
        # cur_len will be zero, so the next line will be entirely
        # devoted to the long word that we can't handle right now.

SequenceTextWrapper.__doc__ = textwrap.TextWrapper.__doc__

class Sequence(six.text_type):
    """
    A "sequence-aware" version of the base :class:`str` class.

    This unicode-derived class understands the effect of escape sequences
    of printable length, allowing a properly implemented :meth:`rjust`,
    :meth:`ljust`, :meth:`center`, and :meth:`length`.
    """

    def __new__(cls, sequence_text, term):
        """
        Class constructor.

        :arg sequence_text: A string that may contain sequences.
        :arg blessed.Terminal term: :class:`~.Terminal` instance.
        """
        new = six.text_type.__new__(cls, sequence_text)
        new._term = term
        return new

    def ljust(self, width, fillchar=u' '):
        """
        Return string containing sequences, left-adjusted.

        :arg int width: Total width given to right-adjust ``text``.  If
            unspecified, the width of the attached terminal is used (default).
        :arg str fillchar: String for padding right-of ``text``.
        :returns: String of ``text``, right-aligned by ``width``.
        :rtype: str
        """
        rightside = fillchar * int(
            (max(0.0, float(width - self.length()))) / float(len(fillchar)))
        return u''.join((self, rightside))

    def rjust(self, width, fillchar=u' '):
        """
        Return string containing sequences, right-adjusted.

        :arg int width: Total width given to right-adjust ``text``.  If
            unspecified, the width of the attached terminal is used (default).
        :arg str fillchar: String for padding left-of ``text``.
        :returns: String of ``text``, right-aligned by ``width``.
        :rtype: str
        """
        leftside = fillchar * int(
            (max(0.0, float(width - self.length()))) / float(len(fillchar)))
        return u''.join((leftside, self))

    def center(self, width, fillchar=u' '):
        """
        Return string containing sequences, centered.

        :arg int width: Total width given to center ``text``.  If
            unspecified, the width of the attached terminal is used (default).
        :arg str fillchar: String for padding left and right-of ``text``.
        :returns: String of ``text``, centered by ``width``.
        :rtype: str
        """
        split = max(0.0, float(width) - self.length()) / 2
        leftside = fillchar * int(
            (max(0.0, math.floor(split))) / float(len(fillchar)))
        rightside = fillchar * int(
            (max(0.0, math.ceil(split))) / float(len(fillchar)))
        return u''.join((leftside, self, rightside))

    def length(self):
        r"""
        Return the printable length of string containing sequences.

        Strings containing ``term.left`` or ``\b`` will cause "overstrike",
        but a length less than 0 is not ever returned. So ``_\b+`` is a
        length of 1 (displays as ``+``), but ``\b`` alone is simply a
        length of 0.

        Some characters may consume more than one cell, mainly those CJK
        Unified Ideographs (Chinese, Japanese, Korean) defined by Unicode
        as half or full-width characters.
        """
        # because control characters may return -1, "clip" their length to 0.
        clip = functools.partial(max, 0)
        return sum(clip(wcwidth.wcwidth(w_char))
                   for w_char in self.strip_seqs())

    # we require ur"" for the docstring, but it is not supported by pep257
    # tool: https://github.com/GreenSteam/pep257/issues/116
    length.__doc__ += (
        u"""For example:

            >>> from blessed import Terminal
            >>> from blessed.sequences import Sequence
            >>> term = Terminal()
            >>> Sequence(term.clear + term.red(u'コンニチハ'), term).length()
            10

        .. note:: Although accounted for, strings containing sequences such as
            ``term.clear`` will not give accurate returns, it is not
            considered lengthy (a length of 0).
        """)

    def strip(self, chars=None):
        """
        Return string of sequences, leading, and trailing whitespace removed.

        :arg str chars: Remove characters in chars instead of whitespace.
        :rtype: str
        """
        return self.strip_seqs().strip(chars)

    def lstrip(self, chars=None):
        """
        Return string of all sequences and leading whitespace removed.

        :arg str chars: Remove characters in chars instead of whitespace.
        :rtype: str
        """
        return self.strip_seqs().lstrip(chars)

    def rstrip(self, chars=None):
        """
        Return string of all sequences and trailing whitespace removed.

        :arg str chars: Remove characters in chars instead of whitespace.
        :rtype: str
        """
        return self.strip_seqs().rstrip(chars)

    def strip_seqs(self):
        """
        Return ``text`` stripped of only its terminal sequences.

        :rtype: str
        """
        gen = iter_parse(self._term, self.padd())
        return u''.join(text for text, cap in gen if not cap)

    def padd(self):
        """
        Return non-destructive horizontal movement as destructive spacing.

        :rtype: str
        """
        outp = ''
        for text, cap in iter_parse(self._term, self):
            if not cap:
                outp += text
                continue

            value = cap.horizontal_distance(text)
            if value > 0:
                outp += ' ' * value
            elif value < 0:
                outp = outp[:value]
            else:
                outp += text
        return outp

def iter_parse(term, text):
    """
    Generator yields (text, capability) for characters of ``text``.

    value for ``capability`` may be ``None``, where ``text`` is
    :class:`str` of length 1.  Otherwise, ``text`` is a full
    matching sequence of given capability.
    """
    for match in re.finditer(term._caps_compiled_any, text):
        name = match.lastgroup
        value = match.group(name)
        if name == 'MISMATCH':
            yield (value, None)
        else:
            yield value, term.caps[name]

def measure_length(text, term):
    """
    .. deprecated:: 1.12.0.

    :rtype: int
    """
    try:
        text, capability = next(iter_parse(term, text))
        if capability:
            return len(text)
    except StopIteration:
        return 0
    return 0
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    This module copyright (C) 2015 Therp BV (<http://therp.nl>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.tests.common import TransactionCase

class TestAuditlog(TransactionCase):
    def test_LogCreation(self):
        auditlog_log = self.env['auditlog.log']
        user_model_id = self.env.ref('base.model_res_users').id
        self.env['auditlog.rule'].create({
            'name': 'testrule for users',
            'model_id': user_model_id,
            'log_create': True,
            'log_write': True,
            'log_unlink': True,
            'state': 'subscribed',
        })
        user = self.env['res.users'].create({
            'login': 'testuser',
            'name': 'testuser',
        })
        self.assertTrue(auditlog_log.search([
            ('model_id', '=', user_model_id),
            ('method', '=', 'create'),
            ('res_id', '=', user.id),
        ]))
        user.write({'name': 'Test User'})
        self.assertTrue(auditlog_log.search([
            ('model_id', '=', user_model_id),
            ('method', '=', 'write'),
            ('res_id', '=', user.id),
        ]))
        user.unlink()
        self.assertTrue(auditlog_log.search([
            ('model_id', '=', user_model_id),
            ('method', '=', 'unlink'),
            ('res_id', '=', user.id),
        ]))
<commit_msg>[IMP] use a model for test that is not influenced by the mail module<commit_after># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    This module copyright (C) 2015 Therp BV (<http://therp.nl>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.tests.common import TransactionCase

class TestAuditlog(TransactionCase):
    def test_LogCreation(self):
        auditlog_log = self.env['auditlog.log']
        groups_model_id = self.env.ref('base.model_res_groups').id
        self.env['auditlog.rule'].create({
            'name': 'testrule for groups',
            'model_id': groups_model_id,
            'log_create': True,
            'log_write': True,
            'log_unlink': True,
            'state': 'subscribed',
        })
        group = self.env['res.groups'].create({
            'name': 'testgroup',
        })
        self.assertTrue(auditlog_log.search([
            ('model_id', '=', groups_model_id),
            ('method', '=', 'create'),
            ('res_id', '=', group.id),
        ]))
        group.write({'name': 'Testgroup'})
        self.assertTrue(auditlog_log.search([
            ('model_id', '=', groups_model_id),
            ('method', '=', 'write'),
            ('res_id', '=', group.id),
        ]))
        group.unlink()
        self.assertTrue(auditlog_log.search([
            ('model_id', '=', groups_model_id),
            ('method', '=', 'unlink'),
            ('res_id', '=', group.id),
        ]))
<|endoftext|>


<commit_before>#---------------------------------------------------------------------------
# Poker Hand Evaluation scripts
#---------------------------------------------------------------------------

def PokerHand(rank,suit,type = shootout, result = 'normal'): # Evaluates 5 cards to find which poker hand they create.
   jokers = 0 # A counter to remember how many jokers we have in our hand
   for value in suit:
      if value == 'Joker': jokers += 1 # Go through the hand and count the jokers available.
   numranks = [numrank(rank[0]), numrank(rank[1]), numrank(rank[2]), numrank(rank[3]), numrank(rank[4])] # Convert the hand into strict integers
   srank = sorted(numranks) # Sort the integers ascending. This is useful for checking for straights
   chkpairs = pairschk(rank,jokers,type) # So that we don't run the procedure 6 times unnecessarily.
# Look for Dead Man's Hand
   if checkDMH(rank,suit,jokers,type) == 1:
      if result == 'comparison': return 11
      else: return "11: Dead Man's Hand!"
# Look for five of a kind
   if chkpairs == 5:
      if result == 'comparison': return 10
      else: return "10: Five of a Kind"
# Look for four of a kind
   elif chkpairs == 4:
      if result == 'comparison': return 8
      else: return "8: Four of a Kind"
# Look for Full House
   elif chkpairs == 32:
      if result == 'comparison': return 7
      else: return '7: Full House'
# Look for a Flush
   elif flushchk(suit,jokers,type) == 1:
      if straightchk(srank,jokers,type) == 1:
         if result == 'comparison': return 9
         else: return "9: Straight Flush"
      else:
         if result == 'comparison': return 6
         else: return "6: Flush"
# Look for a straight
   elif straightchk(srank,jokers,type) == 1:
      if result == 'comparison': return 5
      else: return "5: Straight"
# Look for Three of a Kind
   elif chkpairs == 3:
      if result == 'comparison': return 4
      else: return '4: Three of a kind'
# Look for Two Pairs
   elif chkpairs == 2:
      if result == 'comparison': return 3
      else: return '3: Two Pairs'
# Look for One Pair
   elif chkpairs == 1:
      if result == 'comparison': return 2
      else: return '2: One Pair'
# If none of the above is true, return a high card result.
   else:
      if result == 'comparison': return 1
      else: return '1: High Card'

def numrank(rank, ask = False): # Convert card ranks into pure integers for comparison
   if rank == '*':
      if ask:
         failsafe = 0
         joker = askInteger("Please type a rank for the Joker (1-13)", 13)
         while joker > 13 or joker < 1:
            joker = askInteger("Please type a rank for the Joker (1-13)", 13)
            failsafe += 1
            if failsafe == 4: break
         if joker == 1 or joker > 10: notify(":>> {} sets the rank of the joker as {}".format(me,{1:'Ace',11:'Jack',12:'Queen',13:'King'}.get(joker)))
         else: notify(":>> {} sets the rank of the joker as {}".format(me,joker))
         return joker
      else: return 13
   elif rank == 'A': return 1
   elif rank == 'J': return 11
   elif rank == 'Q': return 12
   elif rank == 'K': return 13
   else: return num(rank)

def pairschk(rank,jokers = 0,type = shootout):
# This function checks the hand for similar ranks and depending on how many it finds, it returns the appropriate hand code.
   i = 0
   j = 1
   pairs = [0,0,0,0,0]
# The above variable is where we'll be storing the matches we find for each hand.
# The first pair we find will be labeled 1 and the second 2 as long as it's not the same rank as 1.
# This is for prevent us from counting the same pairing more than once.
# In the end, we'll have a list where each digit is either 0, 1 or 2. 0 means this card's rank matches no other card in the hand
# 1 and 2 means that this card's rank is the same as all the others which are labeled with the same number
# So for example [1,0,1,2,2] is two pairs and [1,1,2,1,2] is full house.
   match1 = 0 # These will count in the end how many matches per rank we have.
   match2 = 0
   op1 = 5 # These are used to point which was the first card where we found a match of this number.
   op2 = 5
   while i < 4:
      while j < 5: # We start a nested while. First we check the first card with all the others.
                   # Then the second one with the 3rd, 4th and 5th.
                   # Then 3rd with 4th and 5th and finally 4th and 5th.
         if rank[i] == rank[j] and rank[i] != 'Joker': # If we find a match (but we don't care about matching jokers)
#            notify("comparing rank[{}] = {} with rank[{}] = {}".format(i,rank[i],j,rank[j])) # Used for testing
            if op1 == 5: # If our first pointer has not been set
               op1 = i # Put our pointer on the first card of the match
               pairs[i] = 1 # Set the position of those cards to belong to the first matching.
               pairs[j] = 1
            elif rank[op1] == rank[j]: #If the pointer shows that the match belongs to the first group.
               pairs[j] = 1 # set the currently checked card's position to belong to the first group.
            elif op2 == 5: # If there is no match with the first pointer, then use the second one
               op2 = i
               pairs[i] = 2
               pairs[j] = 2
            elif rank[op2] == rank[j]: # Same process
               pairs[j]=2
#            notify("pairs[{},{},{},{},{}]".format(pairs[0],pairs[1],pairs[2],pairs[3],pairs[4])) # Used for testing
         j += 1
      i += 1 # here we increment the parent loop by one
      j = i+1 # And we set the second loop's iterator to start at one position further.
   for pair in pairs: # Now we count how many matching cards we have for each group
      if pair == 1: match1 += 1
      elif pair == 2: match2 += 1
#   notify("match1 = {}, match2 = {}, type == {}".format(match1, match2, type)) # Used for testing
   if match1 == 5 or (match1 + jokers == 5 and type == shootout): return 5 # Finally we check for hand ranks. 5 sames are Five of a Kind
                                                                           # (Jokers count only in non-lowball hands)
   if match1 == 4 or (match1 + jokers == 4 and type == shootout): return 4 # 4 Sames are Four of a Kind.
   if ((match1 == 3 and match2 == 2) or
         (match1 == 2 and match2 == 3) or
         (match1 + jokers == 3 and match2 == 2 and type == shootout) or
         (match1 == 2 and match2 + jokers == 3 and type == shootout)): return 32 # Thee of a kind plus one pair is a Full House
   if (match1 == 3 or # 3 sames is a Thee of a Kind
      (match1 + jokers == 3 and type == shootout) or # 1 pair and one joker is also a Three of a Kind
      (jokers == 2 and type == shootout)): return 3 # Also 2 jokers and nothing else is always at the least a Three of a Kind
   if match1 == 2 and match2 == 2: return 2 # 2 of each is Two Pairs
   if match1 == 2 or (jokers == 1 and type == shootout): return 1 # 2 matching cards is One Pair
                                                                    # And one joker with nothing else is always at least a pair.
   else: return 0

def checkDMH(rank,suit,jokers,type = shootout): # This function checks whether the player has a Dead Man's Hand
# A DMH is 2 black Aces (clubs & spades), 2 black eights (clubs & spades) and one Diamond Jack.
   i = 0
   count = 0
   DMH = [0,0,0,0,0] # Create a list. Each digit will become 1 if the corresponding card has been found for DMH.
   while i < 5:
      if rank[i] == '8' and suit[i] == 'S': DMH[0] = 1
      if rank[i] == '8' and suit[i] == 'C': DMH[1] = 1
      if rank[i] == 'A' and suit[i] == 'S': DMH[2] = 1
      if rank[i] == 'A' and suit[i] == 'C': DMH[3] = 1
      if rank[i] == 'J' and suit[i] == 'D': DMH[4] = 1
      i += 1
   for card in DMH:
      if card == 1: count += 1
   if count == 5 or (count + jokers == 5 and type == shootout): return 1
   else: return 0

def flushchk(suit,jokers,type = shootout): # Check if the player's hand is a flush.
   i = 0
   sames = 0 # A variable to count how many pairs of matching suits we find.
   match = '' # A Variable to note which is the first matching suit we've found
   while i < 4:
      #if suit[i] == '': suit[i] = 'Joker' # Jokers have empty suits, so lets give them something to make this easier to read.
      if suit[i] == suit[i+1]: # Check if the suit of cards adjacent to each other is the same
         if match == '' and suit != 'Joker':  # If we haven't found matching suits yet and and the cards are not jokers...
            match = suit[i] # Mark which is the first matching suit we found (otherwise 2 pairs of different suits will also increase our count.)
            sames += 1 # Increase our count.
         elif suit[i] == match and suit != 'Joker': sames+=1 # If we have already found a matching suit, check that what we matched now is the same.
      elif jokers >= 1 and i < 3:  # If the cards do not match, but we have at least joker, skip one card and compare with the next as above
         if suit[i] == suit[i+2]:
            if match == '' and suit != 'Joker':
               match = suit[i]
               sames += 1
            elif suit[i] == match and suit != 'Joker': sames+=1
         elif jokers == 2 and i < 2: # If the cards do not match, but we have two jokers, skip two cards and compare with the next as above
            if suit[i] == suit[i+3]:
               if match == '' and suit != 'Joker':
                  match = suit[i]
                  sames += 1
               elif suit[i] == match and suit != 'Joker': sames+=1
      i += 1
   if sames == 4 or (sames + jokers == 4 and type == shootout): return 1 # Four pairs of matching adjacent cards means all cards are of the same suit
                                                                         # If we have less pairs and enough jokers, it can also be a flush.
   else: return 0

def straightchk(rank,jokers,type = shootout): # Check if the player's hand is a straight.
   i = 0
   straight = 0 # A counter to see how many serial numbers the player has
   while i < 4:
      if (rank[i] + 1 == rank[i+1] or # We increment our counter if the pair is serial or...
         (rank[i] + 2 == rank[i+1] and jokers >= 1 and type == shootout) or # If the pair is two numbers away
                                                                            # and the player has at least 1 joker to cover the gap
                                                                            # and this is a non-lowball hand or...
                                                                            # (because in lowball you don't want the jokers to be helping you get a higher hand)
         (rank[i] + 3 == rank[i+1] and jokers == 2 and type == shootout)):  # If the pair is three numbers away
                                                                            # and the player has 2 jokers to cover the gap
                                                                            # and this is not a lowball hand...
         straight +=1 # Then increase the counter marking how many straight pairs we have by 1.
      i += 1
   if straight == 4 or (straight + jokers == 4 and type == shootout): return 1 # If we have 4 straight pairs (including the jokers), then it's a straight.
   else: return 0

def cheatinchk(rank,suit): # Check if the player is cheating (i.e. has 2 or more cards of the same suit & rank)
   i = 0
   j = 1
   while i < 4:
      while j < 5:
         if rank[i] == rank[j] and suit[i] == suit[j] and suit[i] != 'Joker': return " (Cheatin'!)"
         j += 1
      i += 1
      j = i + 1
   return ''

def fullsuit(suit): # This function simply returns the full suit of the various cards so that notifications read easily.
   if suit == "C": return "Clubs"
   elif suit == "S": return "Spades"
   elif suit == "D": return "Diamonds"
   elif suit == "H": return "Hearts"
   else: return suit

def fullrank(rank): # This function simply returns the full rank of non-numeral cards so that notifications read easily
   if rank == "Q": return "Queen of"
   elif rank == "J": return "Jack of"
   elif rank == "K": return "King of"
   elif rank == "A": return "Ace of"
   elif rank == "*": return "" # The Joker is announced from the Suit
   else: return rank

def clearHandRanks(): # Cleas player hand ranks so that comparisons can start anew
   debugNotify(">>> clearHandRanks().")
   for player in getActivePlayers():
      #player.HandRank = 0 # Make sure that all shootout handrank counters are cleared.
      if player != me: remoteCall(player,'clearMyHandRank',[])
      else: clearMyHandRank()
   debugNotify("<<< clearHandRanks().")

def clearMyHandRank():
   mute()
   #notify("====> clearing my handrank") # Debug
   me.setGlobalVariable('Hand Rank','N/A')<commit_msg>Update poker.py<commit_after>#---------------------------------------------------------------------------
# Poker Hand Evaluation scripts
#---------------------------------------------------------------------------

def PokerHand(rank,suit,type = shootout, result = 'normal'): # Evaluates 5 cards to find which poker hand they create.
   jokers = 0 # A counter to remember how many jokers we have in our hand
   for value in suit:
      if value == 'Joker': jokers += 1 # Go through the hand and count the jokers available.
   numranks = [numrank(rank[0]), numrank(rank[1]), numrank(rank[2]), numrank(rank[3]), numrank(rank[4])] # Convert the hand into strict integers
   srank = sorted(numranks) # Sort the integers ascending. This is useful for checking for straights
   chkpairs = pairschk(rank,jokers,type) # So that we don't run the procedure 6 times unnecessarily.
# Look for Dead Man's Hand
   if checkDMH(rank,suit,jokers,type) == 1:
      if result == 'comparison': return 11
      else: return "11: Dead Man's Hand!"
# Look for five of a kind
   if chkpairs == 5:
      if result == 'comparison': return 10
      else: return "10: Five of a Kind"
# Look for four of a kind
   elif chkpairs == 4:
      if result == 'comparison': return 8
      else: return "8: Four of a Kind"
# Look for Full House
   elif chkpairs == 32:
      if result == 'comparison': return 7
      else: return '7: Full House'
# Look for a Flush
   elif flushchk(suit,jokers,type) == 1:
      if straightchk(srank,jokers,type) == 1:
         if result == 'comparison': return 9
         else: return "9: Straight Flush"
      else:
         if result == 'comparison': return 6
         else: return "6: Flush"
# Look for a straight
   elif straightchk(srank,jokers,type) == 1:
      if result == 'comparison': return 5
      else: return "5: Straight"
# Look for Three of a Kind
   elif chkpairs == 3:
      if result == 'comparison': return 4
      else: return '4: Three of a kind'
# Look for Two Pairs
   elif chkpairs == 2:
      if result == 'comparison': return 3
      else: return '3: Two Pairs'
# Look for One Pair
   elif chkpairs == 1:
      if result == 'comparison': return 2
      else: return '2: One Pair'
# If none of the above is true, return a high card result.
   else:
      if result == 'comparison': return 1
      else: return '1: High Card'

def numrank(rank, ask = False): # Convert card ranks into pure integers for comparison
   if rank == '*':
      if ask:
         failsafe = 0
         joker = askInteger("Please type a rank for the Joker (1-13)", 13)
         while joker > 13 or joker < 1:
            joker = askInteger("Please type a rank for the Joker (1-13)", 13)
            failsafe += 1
            if failsafe == 4: break
         if joker == 1 or joker > 10: notify(":>> {} sets the rank of the joker as {}".format(me,{1:'Ace',11:'Jack',12:'Queen',13:'King'}.get(joker)))
         else: notify(":>> {} sets the rank of the joker as {}".format(me,joker))
         return joker
      else: return 13
   elif rank == 'A': return 1
   elif rank == 'J': return 11
   elif rank == 'Q': return 12
   elif rank == 'K': return 13
   else: return num(rank)

def pairschk(rank,jokers = 0,type = shootout):
# This function checks the hand for similar ranks and depending on how many it finds, it returns the appropriate hand code.
   i = 0
   j = 1
   pairs = [0,0,0,0,0]
# The above variable is where we'll be storing the matches we find for each hand.
# The first pair we find will be labeled 1 and the second 2 as long as it's not the same rank as 1.
# This is for prevent us from counting the same pairing more than once.
# In the end, we'll have a list where each digit is either 0, 1 or 2. 0 means this card's rank matches no other card in the hand
# 1 and 2 means that this card's rank is the same as all the others which are labeled with the same number
# So for example [1,0,1,2,2] is two pairs and [1,1,2,1,2] is full house.
   match1 = 0 # These will count in the end how many matches per rank we have.
   match2 = 0
   op1 = 5 # These are used to point which was the first card where we found a match of this number.
   op2 = 5
   while i < 4:
      while j < 5: # We start a nested while. First we check the first card with all the others.
                   # Then the second one with the 3rd, 4th and 5th.
                   # Then 3rd with 4th and 5th and finally 4th and 5th.
         if rank[i] == rank[j] and rank[i] != 'Joker': # If we find a match (but we don't care about matching jokers)
#            notify("comparing rank[{}] = {} with rank[{}] = {}".format(i,rank[i],j,rank[j])) # Used for testing
            if op1 == 5: # If our first pointer has not been set
               op1 = i # Put our pointer on the first card of the match
               pairs[i] = 1 # Set the position of those cards to belong to the first matching.
               pairs[j] = 1
            elif rank[op1] == rank[j]: #If the pointer shows that the match belongs to the first group.
               pairs[j] = 1 # set the currently checked card's position to belong to the first group.
            elif op2 == 5: # If there is no match with the first pointer, then use the second one
               op2 = i
               pairs[i] = 2
               pairs[j] = 2
            elif rank[op2] == rank[j]: # Same process
               pairs[j]=2
#            notify("pairs[{},{},{},{},{}]".format(pairs[0],pairs[1],pairs[2],pairs[3],pairs[4])) # Used for testing
         j += 1
      i += 1 # here we increment the parent loop by one
      j = i+1 # And we set the second loop's iterator to start at one position further.
   for pair in pairs: # Now we count how many matching cards we have for each group
      if pair == 1: match1 += 1
      elif pair == 2: match2 += 1
#   notify("match1 = {}, match2 = {}, type == {}".format(match1, match2, type)) # Used for testing
   if match1 == 5 or (match1 + jokers == 5 and type == shootout): return 5 # Finally we check for hand ranks. 5 sames are Five of a Kind
                                                                           # (Jokers count only in non-lowball hands)
   if match1 == 4 or (match1 + jokers == 4 and type == shootout): return 4 # 4 Sames are Four of a Kind.
   if ((match1 == 3 and match2 == 2) or
         (match1 == 2 and match2 == 3) or
         (match1 + jokers == 3 and match2 == 2 and type == shootout) or
         (match1 == 2 and match2 + jokers == 3 and type == shootout)): return 32 # Thee of a kind plus one pair is a Full House
   if (match1 == 3 or # 3 sames is a Thee of a Kind
      (match1 + jokers == 3 and type == shootout) or # 1 pair and one joker is also a Three of a Kind
      (jokers == 2 and type == shootout)): return 3 # Also 2 jokers and nothing else is always at the least a Three of a Kind
   if match1 == 2 and match2 == 2: return 2 # 2 of each is Two Pairs
   if match1 == 2 or (jokers == 1 and type == shootout): return 1 # 2 matching cards is One Pair
                                                                    # And one joker with nothing else is always at least a pair.
   else: return 0

def checkDMH(rank,suit,jokers,type = shootout): # This function checks whether the player has a Dead Man's Hand
# A DMH is 2 black Aces (clubs & spades), 2 black eights (clubs & spades) and one Diamond Jack.
   i = 0
   count = 0
   DMH = [0,0,0,0,0] # Create a list. Each digit will become 1 if the corresponding card has been found for DMH.
   while i < 5:
      if rank[i] == '8' and suit[i] == 'Spades': DMH[0] = 1
      if rank[i] == '8' and suit[i] == 'Clubs': DMH[1] = 1
      if rank[i] == 'A' and suit[i] == 'Spades': DMH[2] = 1
      if rank[i] == 'A' and suit[i] == 'Clubs': DMH[3] = 1
      if rank[i] == 'J' and suit[i] == 'Diamonds': DMH[4] = 1
      i += 1
   for card in DMH:
      if card == 1: count += 1
   if count == 5 or (count + jokers == 5 and type == shootout): return 1
   else: return 0

def flushchk(suit,jokers,type = shootout): # Check if the player's hand is a flush.
   i = 0
   sames = 0 # A variable to count how many pairs of matching suits we find.
   match = '' # A Variable to note which is the first matching suit we've found
   while i < 4:
      #if suit[i] == '': suit[i] = 'Joker' # Jokers have empty suits, so lets give them something to make this easier to read.
      if suit[i] == suit[i+1]: # Check if the suit of cards adjacent to each other is the same
         if match == '' and suit != 'Joker':  # If we haven't found matching suits yet and and the cards are not jokers...
            match = suit[i] # Mark which is the first matching suit we found (otherwise 2 pairs of different suits will also increase our count.)
            sames += 1 # Increase our count.
         elif suit[i] == match and suit != 'Joker': sames+=1 # If we have already found a matching suit, check that what we matched now is the same.
      elif jokers >= 1 and i < 3:  # If the cards do not match, but we have at least joker, skip one card and compare with the next as above
         if suit[i] == suit[i+2]:
            if match == '' and suit != 'Joker':
               match = suit[i]
               sames += 1
            elif suit[i] == match and suit != 'Joker': sames+=1
         elif jokers == 2 and i < 2: # If the cards do not match, but we have two jokers, skip two cards and compare with the next as above
            if suit[i] == suit[i+3]:
               if match == '' and suit != 'Joker':
                  match = suit[i]
                  sames += 1
               elif suit[i] == match and suit != 'Joker': sames+=1
      i += 1
   if sames == 4 or (sames + jokers == 4 and type == shootout): return 1 # Four pairs of matching adjacent cards means all cards are of the same suit
                                                                         # If we have less pairs and enough jokers, it can also be a flush.
   else: return 0

def straightchk(rank,jokers,type = shootout): # Check if the player's hand is a straight.
   i = 0
   straight = 0 # A counter to see how many serial numbers the player has
   while i < 4 - jokers:
      if (rank[i] + 1 == rank[i+1] or # We increment our counter if the pair is serial or...
         (rank[i] + 2 == rank[i+1] and jokers >= 1 and type == shootout) or # If the pair is two numbers away
                                                                            # and the player has at least 1 joker to cover the gap
                                                                            # and this is a non-lowball hand or...
                                                                            # (because in lowball you don't want the jokers to be helping you get a higher hand)
         (rank[i] + 3 == rank[i+1] and jokers == 2 and type == shootout)):  # If the pair is three numbers away
                                                                            # and the player has 2 jokers to cover the gap
                                                                            # and this is not a lowball hand...
         straight +=1 # Then increase the counter marking how many straight pairs we have by 1.
      i += 1
   if straight == 4 or (straight + jokers == 4 and type == shootout): return 1 # If we have 4 straight pairs (including the jokers), then it's a straight.
   else: return 0

def cheatinchk(rank,suit): # Check if the player is cheating (i.e. has 2 or more cards of the same suit & rank)
   i = 0
   j = 1
   while i < 4:
      while j < 5:
         if rank[i] == rank[j] and suit[i] == suit[j] and suit[i] != 'Joker': return " (Cheatin'!)"
         j += 1
      i += 1
      j = i + 1
   return ''

def fullsuit(suit): # This function simply returns the full suit of the various cards so that notifications read easily.
   if suit == "C": return "Clubs"
   elif suit == "S": return "Spades"
   elif suit == "D": return "Diamonds"
   elif suit == "H": return "Hearts"
   else: return suit

def fullrank(rank): # This function simply returns the full rank of non-numeral cards so that notifications read easily
   if rank == "Q": return "Queen of"
   elif rank == "J": return "Jack of"
   elif rank == "K": return "King of"
   elif rank == "A": return "Ace of"
   elif rank == "*": return "" # The Joker is announced from the Suit
   else: return rank." of"

def clearHandRanks(): # Cleas player hand ranks so that comparisons can start anew
   debugNotify(">>> clearHandRanks().")
   for player in getActivePlayers():
      #player.HandRank = 0 # Make sure that all shootout handrank counters are cleared.
      if player != me: remoteCall(player,'clearMyHandRank',[])
      else: clearMyHandRank()
   debugNotify("<<< clearHandRanks().")

def clearMyHandRank():
   mute()
   #notify("====> clearing my handrank") # Debug
   me.setGlobalVariable('Hand Rank','N/A')
<|endoftext|>


<commit_before>#!/usr/bin/python

# Brutally primitive reference taxonomy browser.
# Basically just a simple shim on the taxomachine 'taxon' method.
# Intended to be run as a CGI command, but it can be tested by running it
# directly from the shell; just set the environment QUERY_STRING to be
# the name or id of the taxon to browse to.

# Apache (or other server) must be configured to be able to run CGI scripts,
# and this program must be in the directory where it looks for such scripts.
# The file name there should be simply 'browse' (not browse.py).

# NOT YET IMPLEMENTED: percent-escaping and -unescaping

# If this were to be written using peyotl, it might do something similar to the following:
# from peyotl.api import APIWrapper
# taxo = APIWrapper().taxomachine
# print taxo.taxon(12345)

default_api_base_url = 'https://devapi.opentreeoflife.org/'

import os
import sys
import cgi, cgitb, StringIO

import requests
import simplejson

headers = {
    'content-type' : 'application/json',
    'accept' : 'application/json',
}

# Main entry point.  Returns HTML as a string.

def browse(id=None, name=None, limit=None, api_base=None):
    if id != None: id = int(id)
    if limit != None: limit = int(limit)
    if api_base == None: api_base = default_api_base_url

    output = StringIO.StringIO()
    #output.write('
\n')
    if id != None:
        browse_by_id(id, limit, api_base, output)
    elif name != None:
        browse_by_name(name, limit, api_base, output)
    else:
        output.write('bogus invocation\n')
    #output.write('
\n')

    return output.getvalue()

def browse_by_name(name, limit, api_base, output):
    result = look_up_name(name, api_base)
    matches = result[u'matches']
    if len(matches) == 0:
        output.write('no TNRS matches for %s\n' % cgi.escape(name))
        return None
    elif len(matches) > 1:
        output.write('Matches for %s: \n' % cgi.escape(name))
        for match in matches:
            output.write("  %s\n" % link_to_taxon(match[u'ot:ottId'], match[u'unique_name']))
    else:
        id = matches[0][u'ot:ottId']
        browse_by_id(id, limit, api_base, output)

# Map taxon name to taxonomy id using match_names service

def look_up_name(name, api_base):
    response = requests.post(api_base + 'v2/tnrs/match_names',
                             headers=headers,
                             data=simplejson.dumps({'names':[name]}))
    response.raise_for_status()
    answer = response.json()
    results = answer[u'results']
    if len(results) == 0: return None
    if len(results) > 1:
        output.write('multiple results - should not happen\n')
        return None # shouldn't happen
    return results[0]

def browse_by_id(id, limit, api_base, output):
    info = get_taxon_info(id, api_base)
    #print simplejson.dumps(info, sort_keys=True, indent=4)
    display_taxon_info(info, limit, output)

def get_taxon_info(ottid, api_base):
    d=simplejson.dumps({'ott_id': ottid, 'include_children': True, 'include_lineage': True})
    response = requests.post(api_base + 'v2/taxonomy/taxon',
                             headers=headers,
                             data=d)
    response.raise_for_status()
    return response.json()

def display_taxon_info(info, limit, output):
    if u'ot:ottId' in info:
        id = info[u'ot:ottId']
        start_el(output, 'p', 'taxon')
        output.write('Taxon: ')
        display_basic_info(info, output)
        output.write(' (OTT id %s)' % id)
        end_el(output, 'p')

        if u'synonyms' in info:
            synonyms = info[u'synonyms']
            name = info[u'ot:ottTaxonName']
            if name in synonyms:
                synonyms.remove(name)
            if len(synonyms) > 0:
                start_el(output, 'p', 'synonyms')
                output.write("Synonym(s): %s\n" % ', '.join(map(link_to_name, synonyms)))
                end_el(output, 'p')
        if u'taxonomic_lineage' in info:
            first = True
            start_el(output, 'p', 'lineage')
            output.write('Lineage: ')
            for ancestor in info[u'taxonomic_lineage']:
                if not first:
                    output.write(' < ')
                output.write(link_to_taxon(ancestor[u'ot:ottId'], ancestor[u'ot:ottTaxonName']))
                first = False
            output.write('\n')
            end_el(output, 'p')
        else:
            output.write('missing lineage field %s\n', info.keys())
        any_suppressed = False
        if u'children' in info:
            children = sorted(info[u'children'], key=priority)
            if len(children) > 0:
                start_el(output, 'p', 'children')
                output.write('Children:\n')
                if limit == None: limit = 200
                start_el(output, 'ul', 'children')
                for child in children[:limit]:
                    if ishidden(child):
                        start_el(output, 'li', 'child suppressed')
                        write_suppressed(output)
                        any_suppressed = True
                    else:
                        start_el(output, 'li', 'child exposed')
                        start_el(output, 'span', 'exposedmarker')
                        output.write("  ")
                        end_el(output, 'span')
                    output.write(' ')
                    display_basic_info(child, output)
                    end_el(output, 'li')
                if len(children) > limit:
                    start_el(output, 'li', 'more_children')
                    output.write('... %s' % link_to_taxon(id,
                                                          ('%s more children' %
                                                           (len(children)-limit)),
                                                          limit=100000))
                    end_el(output, 'li')
                end_el(output, 'ul')
                end_el(output, 'p')
        output.write("\n")
        if any_suppressed:
            start_el(output, 'p', 'footer suppressed')
            output.write("'")
            write_suppressed(output)
            output.write("' = suppressed from synthetic tree\n")
            end_el(output, 'p')
        start_el(output, 'p', 'footer flags')
        output.write('explanation of flags\n')
        end_el(output, 'p')
    else:
        output.write('? losing')
        output.write(cgi.escape(simplejson.dumps(info, sort_keys=True, indent=4)))

def write_suppressed(output):
    start_el(output, 'span', 'suppressedmarker')
    output.write("*")
    end_el(output, 'span')

def display_basic_info(info, output):
    # Might be better to put rank as a separate column in a table.  That way the
    # names will line up
    if not info[u'rank'].startswith('no rank'):
        output.write(info[u'rank'] + ' ')

    # Taxon name
    if u'unique_name' in info and len(info[u'unique_name']) > 0:
        text = info[u'unique_name']
    elif u'ot:ottTaxonName' in info:
        text = info[u'ot:ottTaxonName']
    output.write(link_to_taxon(info[u'ot:ottId'], text))

    # Sources
    start_el(output, 'span', 'sources')
    if u'tax_sources' in info:
        output.write(' %s ' % ', '.join(map(source_link, info[u'tax_sources'])))
    end_el(output, 'span')

    # Flags
    start_el(output, 'span', 'flags')
    output.write('%s ' % ', '.join(map(lambda f:f.lower(), info[u'flags'])))
    end_el(output, 'span')
    output.write('\n')

def source_link(source_id):
    if source_id.startswith('http:') or source_id.startswith('https:'):
        url = source_id
    else:
        parts = source_id.split(':')
        url = None
        if len(parts) == 2:
            if parts[0] == 'ncbi':
                url = 'http://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id=%s' % parts[1]
            elif parts[0] == 'gbif':
                url = 'http://www.gbif.org/species/%s/' % parts[1]
            elif parts[0] == 'irmng':
                url = 'http://www.marine.csiro.au/mirrorsearch/ir_search.taxon_info?id=%s' % parts[1]
            elif parts[0] == 'if':
                url = 'http://www.indexfungorum.org/names/NamesRecord.asp?RecordID=%s' % parts[1]
            elif parts[0] == 'worms':
                url = 'http://www.marinespecies.org/aphia.php?p=taxdetails&id=%s' % parts[1]
            elif parts[0] == 'silva':
                url = 'http://www.arb-silva.de/browser/ssu/silva/%s' % parts[1]
    if url != None:
        return '%s' % (cgi.escape(url), cgi.escape(source_id))
    else:
        return source_id

def start_el(output, tag, clas=''):
    output.write('<%s class="%s">' % (tag, clas))
def end_el(output, tag):
    output.write('</%s>' % tag)

def link_to_taxon(id, text, limit=None):
    if limit == None:
        option = ''
    else:
        option = '&limit=%s' % limit
    return '%s' % (id, option, style_name(cgi.escape(text)))

def link_to_name(name):
    name = cgi.escape(name)
    return '%s' % (name, style_name(name))

def style_name(ename):
    return '%s' % ename

def priority(child):
    if ishidden(child):
        return 1
    else:
        return 0

def ishidden(info):
    for flag in info[u'flags']:
        if flag in ott29_exclude_flags:
            return True
    return False

# From treemachine/src/main/java/opentree/GraphInitializer.java

ott29_exclude_flags_list = ["major_rank_conflict", "major_rank_conflict_inherited", "environmental",
		"unclassified_inherited", "unclassified", "viral", "barren", "not_otu", "incertae_sedis",
		"incertae_sedis_inherited", "extinct_inherited", "extinct", "hidden", "unplaced", "unplaced_inherited",
		"was_container", "inconsistent", "inconsistent", "hybrid", "merged", "inconsistent"]
ott29_exclude_flags = {}
for flag in ott29_exclude_flags_list:
    ott29_exclude_flags[flag.upper()] = True

if __name__ == '__main__':
    form = cgi.FieldStorage()
    id = name = limit = api_base = None
    if "id" in form: id = form["id"].value
    if "name" in form: name = form["name"].value
    if "limit" in form: limit = form["limit"].value
    if "api_base" in form: api_base = form["api_base"].value
    sys.stdout.write('Content-type: text/html\r\n')
    sys.stdout.write('\r\n')
    output = sys.stdout
    start_el(output, 'html')
    start_el(output, 'head', '')
    output.write('<link rel="stylesheet" href="http://opentreeoflife.github.io/css/main.css" />')
    end_el(output, 'head')
    start_el(output, 'body')
    print browse(id, name, limit, api_base)
    end_el(output, 'body')
    end_el(output, 'html')
<commit_msg>Scheme-relative stylesheet. Test of local styles.<commit_after>#!/usr/bin/python

# Brutally primitive reference taxonomy browser.
# Basically just a simple shim on the taxomachine 'taxon' method.
# Intended to be run as a CGI command, but it can be tested by running it
# directly from the shell; just set the environment QUERY_STRING to be
# the name or id of the taxon to browse to.

# Apache (or other server) must be configured to be able to run CGI scripts,
# and this program must be in the directory where it looks for such scripts.
# The file name there should be simply 'browse' (not browse.py).

# NOT YET IMPLEMENTED: percent-escaping and -unescaping

# If this were to be written using peyotl, it might do something similar to the following:
# from peyotl.api import APIWrapper
# taxo = APIWrapper().taxomachine
# print taxo.taxon(12345)

default_api_base_url = 'https://devapi.opentreeoflife.org/'

import os
import sys
import cgi, cgitb, StringIO

import requests
import simplejson

headers = {
    'content-type' : 'application/json',
    'accept' : 'application/json',
}

# Main entry point.  Returns HTML as a string.

def browse(id=None, name=None, limit=None, api_base=None):
    if id != None: id = int(id)
    if limit != None: limit = int(limit)
    if api_base == None: api_base = default_api_base_url

    output = StringIO.StringIO()
    #output.write('
\n')
    if id != None:
        browse_by_id(id, limit, api_base, output)
    elif name != None:
        browse_by_name(name, limit, api_base, output)
    else:
        output.write('bogus invocation\n')
    #output.write('
\n')

    return output.getvalue()

def browse_by_name(name, limit, api_base, output):
    result = look_up_name(name, api_base)
    matches = result[u'matches']
    if len(matches) == 0:
        output.write('no TNRS matches for %s\n' % cgi.escape(name))
        return None
    elif len(matches) > 1:
        output.write('Matches for %s: \n' % cgi.escape(name))
        for match in matches:
            output.write("  %s\n" % link_to_taxon(match[u'ot:ottId'], match[u'unique_name']))
    else:
        id = matches[0][u'ot:ottId']
        browse_by_id(id, limit, api_base, output)

# Map taxon name to taxonomy id using match_names service

def look_up_name(name, api_base):
    response = requests.post(api_base + 'v2/tnrs/match_names',
                             headers=headers,
                             data=simplejson.dumps({'names':[name]}))
    response.raise_for_status()
    answer = response.json()
    results = answer[u'results']
    if len(results) == 0: return None
    if len(results) > 1:
        output.write('multiple results - should not happen\n')
        return None # shouldn't happen
    return results[0]

def browse_by_id(id, limit, api_base, output):
    info = get_taxon_info(id, api_base)
    #print simplejson.dumps(info, sort_keys=True, indent=4)
    display_taxon_info(info, limit, output)

def get_taxon_info(ottid, api_base):
    d=simplejson.dumps({'ott_id': ottid, 'include_children': True, 'include_lineage': True})
    response = requests.post(api_base + 'v2/taxonomy/taxon',
                             headers=headers,
                             data=d)
    response.raise_for_status()
    return response.json()

def display_taxon_info(info, limit, output):
    if u'ot:ottId' in info:
        id = info[u'ot:ottId']
        start_el(output, 'p', 'taxon')
        output.write('Taxon: ')
        display_basic_info(info, output)
        output.write(' (OTT id %s)' % id)
        end_el(output, 'p')

        if u'synonyms' in info:
            synonyms = info[u'synonyms']
            name = info[u'ot:ottTaxonName']
            if name in synonyms:
                synonyms.remove(name)
            if len(synonyms) > 0:
                start_el(output, 'p', 'synonyms')
                output.write("Synonym(s): %s\n" % ', '.join(map(link_to_name, synonyms)))
                end_el(output, 'p')
        if u'taxonomic_lineage' in info:
            first = True
            start_el(output, 'p', 'lineage')
            output.write('Lineage: ')
            for ancestor in info[u'taxonomic_lineage']:
                if not first:
                    output.write(' < ')
                output.write(link_to_taxon(ancestor[u'ot:ottId'], ancestor[u'ot:ottTaxonName']))
                first = False
            output.write('\n')
            end_el(output, 'p')
        else:
            output.write('missing lineage field %s\n', info.keys())
        any_suppressed = False
        if u'children' in info:
            children = sorted(info[u'children'], key=priority)
            if len(children) > 0:
                start_el(output, 'p', 'children')
                output.write('Children:\n')
                if limit == None: limit = 200
                start_el(output, 'ul', 'children')
                for child in children[:limit]:
                    if ishidden(child):
                        start_el(output, 'li', 'child suppressed')
                        write_suppressed(output)
                        any_suppressed = True
                    else:
                        start_el(output, 'li', 'child exposed')
                        start_el(output, 'span', 'exposedmarker')
                        output.write("  ")
                        end_el(output, 'span')
                    output.write(' ')
                    display_basic_info(child, output)
                    end_el(output, 'li')
                if len(children) > limit:
                    start_el(output, 'li', 'more_children')
                    output.write('... %s' % link_to_taxon(id,
                                                          ('%s more children' %
                                                           (len(children)-limit)),
                                                          limit=100000))
                    end_el(output, 'li')
                end_el(output, 'ul')
                end_el(output, 'p')
        output.write("\n")
        if any_suppressed:
            start_el(output, 'p', 'footer suppressed')
            output.write("'")
            write_suppressed(output)
            output.write("' = suppressed from synthetic tree\n")
            end_el(output, 'p')
        start_el(output, 'p', 'footer flags')
        output.write('explanation of flags\n')
        end_el(output, 'p')
    else:
        output.write('? losing')
        output.write(cgi.escape(simplejson.dumps(info, sort_keys=True, indent=4)))

def write_suppressed(output):
    start_el(output, 'span', 'suppressedmarker')
    output.write("*")
    end_el(output, 'span')

def display_basic_info(info, output):
    # Might be better to put rank as a separate column in a table.  That way the
    # names will line up
    if not info[u'rank'].startswith('no rank'):
        output.write(info[u'rank'] + ' ')

    # Taxon name
    if u'unique_name' in info and len(info[u'unique_name']) > 0:
        text = info[u'unique_name']
    elif u'ot:ottTaxonName' in info:
        text = info[u'ot:ottTaxonName']
    output.write(link_to_taxon(info[u'ot:ottId'], text))

    # Sources
    start_el(output, 'span', 'sources')
    if u'tax_sources' in info:
        output.write(' %s ' % ', '.join(map(source_link, info[u'tax_sources'])))
    end_el(output, 'span')

    # Flags
    start_el(output, 'span', 'flags')
    output.write('%s ' % ', '.join(map(lambda f:f.lower(), info[u'flags'])))
    end_el(output, 'span')
    output.write('\n')

def source_link(source_id):
    if source_id.startswith('http:') or source_id.startswith('https:'):
        url = source_id
    else:
        parts = source_id.split(':')
        url = None
        if len(parts) == 2:
            if parts[0] == 'ncbi':
                url = 'http://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id=%s' % parts[1]
            elif parts[0] == 'gbif':
                url = 'http://www.gbif.org/species/%s/' % parts[1]
            elif parts[0] == 'irmng':
                url = 'http://www.marine.csiro.au/mirrorsearch/ir_search.taxon_info?id=%s' % parts[1]
            elif parts[0] == 'if':
                url = 'http://www.indexfungorum.org/names/NamesRecord.asp?RecordID=%s' % parts[1]
            elif parts[0] == 'worms':
                url = 'http://www.marinespecies.org/aphia.php?p=taxdetails&id=%s' % parts[1]
            elif parts[0] == 'silva':
                url = 'http://www.arb-silva.de/browser/ssu/silva/%s' % parts[1]
    if url != None:
        return '%s' % (cgi.escape(url), cgi.escape(source_id))
    else:
        return source_id

def start_el(output, tag, clas=''):
    output.write('<%s class="%s">' % (tag, clas))
def end_el(output, tag):
    output.write('</%s>' % tag)

def link_to_taxon(id, text, limit=None):
    if limit == None:
        option = ''
    else:
        option = '&limit=%s' % limit
    return '%s' % (id, option, style_name(cgi.escape(text)))

def link_to_name(name):
    name = cgi.escape(name)
    return '%s' % (name, style_name(name))

def style_name(ename):
    return '%s' % ename

def priority(child):
    if ishidden(child):
        return 1
    else:
        return 0

def ishidden(info):
    for flag in info[u'flags']:
        if flag in ott29_exclude_flags:
            return True
    return False

# From treemachine/src/main/java/opentree/GraphInitializer.java

ott29_exclude_flags_list = ["major_rank_conflict", "major_rank_conflict_inherited", "environmental",
		"unclassified_inherited", "unclassified", "viral", "barren", "not_otu", "incertae_sedis",
		"incertae_sedis_inherited", "extinct_inherited", "extinct", "hidden", "unplaced", "unplaced_inherited",
		"was_container", "inconsistent", "inconsistent", "hybrid", "merged", "inconsistent"]
ott29_exclude_flags = {}
for flag in ott29_exclude_flags_list:
    ott29_exclude_flags[flag.upper()] = True

local_stylesheet = """

"""

if __name__ == '__main__':
    form = cgi.FieldStorage()
    id = name = limit = api_base = None
    if "id" in form: id = form["id"].value
    if "name" in form: name = form["name"].value
    if "limit" in form: limit = form["limit"].value
    if "api_base" in form: api_base = form["api_base"].value
    sys.stdout.write('Content-type: text/html\r\n')
    sys.stdout.write('\r\n')
    output = sys.stdout
    start_el(output, 'html')
    start_el(output, 'head', '')
    output.write('<link rel="stylesheet" href="//opentreeoflife.github.io/css/main.css" />')
    output.write(local_stylesheet)
    end_el(output, 'head')
    start_el(output, 'body')
    print browse(id, name, limit, api_base)
    end_el(output, 'body')
    end_el(output, 'html')
<|endoftext|>


<commit_before>"""
FinalProject.py
Author: Avery Wallis
Sources: Hayden Hatfield, Mr. Donnelly,
Marcelo Almaguer for the Chell character model used in his own version of Portal called "Remember the Game"
maxiesnax for the Orange Portal Image:0 "http://orig05.deviantart.net/58ec/f/2012/361/2/5/portal___orange_portal_by_maxiesnax-d5pcfmj.png"
Blue Portal Image "http://vignette4.wikia.nocookie.net/kirby-bulborb/images/1/12/Blue_Portal.png/revision/latest?cb=20151004085207"
"""
from ggame import App, Color, LineStyle, Sprite, RectangleAsset, CircleAsset, EllipseAsset, LineAsset
from ggame import ImageAsset, PolygonAsset, Frame

red = Color(0xff0000, 1.0)
green = Color(0x00ff00, 1.0)
blue = Color(0x0000ff, 1.0)
black = Color(0x000000, 1.0)
orange = Color(0xffa500, 1.0)
skin =Color(0xFCD15B, 1.0)
wall=Color(0xE8E8E8, 1.0)
orange=Color(0xFFa500,1.0)
plat=Color(0xB9BDBB,1.0)
gooy=Color(0xCDF238,1.0)
white=Color(0xFFFFFF,1.0)
darkblue=Color(0x052099,1.0)

thinline= LineStyle(1, black)
thickline= LineStyle(5, black)
thickishline= LineStyle(2.5, black)
noline=LineStyle(0, black)
portalline=LineStyle(1, blue)
portalline2=LineStyle(1, orange)

wall=RectangleAsset(500,500, noline, wall)
blueportal=EllipseAsset(27, 60, noline, blue)
orangeportal=EllipseAsset(27, 60, noline, orange)
innerportal=EllipseAsset(24, 57, noline, white)
exit=CircleAsset(70, thinline, plat)
exit2=CircleAsset(20, thinline, plat)
plat=RectangleAsset(250, 50, noline, plat)
doorline=LineAsset(0, 120, thinline)
goo=PolygonAsset([(0,500),(800,500),(800,600,),(0,600)],noline,gooy)

class Chell(Sprite):
    asset = ImageAsset("images/ChellSpriteSheet.png", Frame(0,0,205,361), 2, 'horizontal')
    def __init__(self, position):
        super().__init__(Chell.asset, position)
        self.visible = True
        self.x = 250
        self.y = 250
        self.click = 0
        self.mright = 0
        self.mleft = 0
        self.alt = 0
        self.image = 0
        self.scale = .25
        self.op = 0
        PortalGame.listenKeyEvent("keydown", "d", self.rightOn)
        PortalGame.listenKeyEvent("keyup", "d", self.rightOff)
        PortalGame.listenKeyEvent("keydown", "a", self.leftOn)
        PortalGame.listenKeyEvent("keyup", "a", self.leftOff)
        PortalGame.listenMouseEvent("click", self.ClickOn)
        PortalGame.listenKeyEvent("keydown", "alt", self.altOn)
        PortalGame.listenKeyEvent("keyup", "alt", self.altOff)
        self.oportal = None
        self.bportal = None
        self.cbx = 0
        self.cby = 0
        self.cox = 0
        self.coy = 0
        self.cpx = 0
        self.cpy = 0

    def step(self):
        if self.click == 1 and self.alt != 1:
            if self.oportal:
                self.oportal.destroy()
                self.oportal = OrangePortal((self.cpx-50,self.cpy-70))
                self.cox = self.cpx
                self.coy = self.cpy
                self.click = 0
            else:
                self.oportal = OrangePortal((self.cpx-50,self.cpy-70))
                self.cox = self.cpx
                self.coy = self.cpy
                self.click = 0

        if self.click == 1 and self.alt == 1:
            if self.bportal:
                self.bportal.destroy()
                self.bportal = BluePortal((self.cpx-50,self.cpy-70))
                self.cbx = self.cpx
                sefl.cby = self.cpy
                self.click = 0
            else:
                self.bportal = BluePortal((self.cpx-50,self.cpy-70))
                self.bportal
                self.cbx = self.cpx
                self.cby = self.cpy
                self.click = 0

        if self.mright == 1:
            self.setImage(0)
            self.x += .75
            self.y = self.y
        if self.mright == 0:
            self.x =self.x
            self.y=self.y
        if self.mleft == 1:
            self.setImage(1)
            self.x += -.75
            self.y = self.y
        if self.mleft == 0:
            self.x = self.x
            self.y = self.y

        col = self.collidingWithSprites(OrangePortal)
        if col:
            if self.bportal:
                self.x = self.cbx
                self.y = self.cby

    def ClickOn(self,event):
        self.click = 1
        self.cpx = event.x
        self.cpy = event.y
    def rightOn(self,event):
        self.mright = 1
    def rightOff(self,event):
        self.mright = 0
    def leftOn(self,event):
        self.mleft = 1
    def leftOff(self,event):
        self.mleft = 0
    def altOn(self,event):
        self.alt = 1
    def altOff(self,event):
        self.alt = 0

class BluePortal(Sprite):
    asset = ImageAsset("images/Blue_Portal.png", Frame(25,10,350,475), 1, 'vertical')
    def __init__(self, position):
        super().__init__(BluePortal.asset, position)
        self.scale = .25
        self.center = (0,0)

    def step(self):
        self.x = self.x
        self.y = self.y

class OrangePortal(Sprite):
    asset = ImageAsset("images/portal___orange_portal_by_maxiesnax-d5pcfmj.png", Frame(25,10,350,475), 1, 'vertical')
    def __init__(self, position):
        super().__init__(OrangePortal.asset, position)
        self.scale = .25
        self.center = (0,0)

    def step(self):
        self.x = self.x
        self.y = self.y

class PortalGame(App):
    def __init__(self, width, height):
        super().__init__(width, height)
        Sprite(wall, (400,20))
        Sprite(wall, (100,20))
        Sprite(exit, (800,100))
        Sprite(exit2, (800, 100))
        Sprite(doorline, (800, 30))
        Sprite(plat, (100,400))
        Sprite(plat, (650, 150))
        Sprite(goo, (100,0))
        Chell((0,0))

    def step(self):
        for chell in self.getSpritesbyClass(Chell):
            chell.step()

myapp = PortalGame(1000,750)
myapp.run()<commit_msg>Updated from Brython Server: 4/26/2016 2:29:09 PM<commit_after>"""
FinalProject.py
Author: Avery Wallis
Sources: Hayden Hatfield, Mr. Donnelly,
Marcelo Almaguer for the Chell character model used in his own version of Portal called "Remember the Game"
maxiesnax for the Orange Portal Image:0 "http://orig05.deviantart.net/58ec/f/2012/361/2/5/portal___orange_portal_by_maxiesnax-d5pcfmj.png"
Blue Portal Image "http://vignette4.wikia.nocookie.net/kirby-bulborb/images/1/12/Blue_Portal.png/revision/latest?cb=20151004085207"
"""
from ggame import App, Color, LineStyle, Sprite, RectangleAsset, CircleAsset, EllipseAsset, LineAsset
from ggame import ImageAsset, PolygonAsset, Frame

red = Color(0xff0000, 1.0)
green = Color(0x00ff00, 1.0)
blue = Color(0x0000ff, 1.0)
black = Color(0x000000, 1.0)
orange = Color(0xffa500, 1.0)
skin =Color(0xFCD15B, 1.0)
wall=Color(0xE8E8E8, 1.0)
orange=Color(0xFFa500,1.0)
plat=Color(0xB9BDBB,1.0)
gooy=Color(0xCDF238,1.0)
white=Color(0xFFFFFF,1.0)
darkblue=Color(0x052099,1.0)

thinline= LineStyle(1, black)
thickline= LineStyle(5, black)
thickishline= LineStyle(2.5, black)
noline=LineStyle(0, black)
portalline=LineStyle(1, blue)
portalline2=LineStyle(1, orange)

wall=RectangleAsset(500,500, noline, wall)
blueportal=EllipseAsset(27, 60, noline, blue)
orangeportal=EllipseAsset(27, 60, noline, orange)
innerportal=EllipseAsset(24, 57, noline, white)
exit=CircleAsset(70, thinline, plat)
exit2=CircleAsset(20, thinline, plat)
plat=RectangleAsset(250, 50, noline, plat)
doorline=LineAsset(0, 120, thinline)
goo=PolygonAsset([(0,500),(800,500),(800,600,),(0,600)],noline,gooy)

class Chell(Sprite):
    asset = ImageAsset("images/ChellSpriteSheet.png", Frame(0,0,205,361), 2, 'horizontal')
    def __init__(self, position):
        super().__init__(Chell.asset, position)
        self.visible = True
        self.x = 250
        self.y = 250
        self.click = 0
        self.mright = 0
        self.mleft = 0
        self.alt = 0
        self.image = 0
        self.scale = .25
        self.op = 0
        PortalGame.listenKeyEvent("keydown", "d", self.rightOn)
        PortalGame.listenKeyEvent("keyup", "d", self.rightOff)
        PortalGame.listenKeyEvent("keydown", "a", self.leftOn)
        PortalGame.listenKeyEvent("keyup", "a", self.leftOff)
        PortalGame.listenMouseEvent("click", self.ClickOn)
        PortalGame.listenKeyEvent("keydown", "alt", self.altOn)
        PortalGame.listenKeyEvent("keyup", "alt", self.altOff)
        self.oportal = None
        self.bportal = None
        self.cbx = 0
        self.cby = 0
        self.cox = 0
        self.coy = 0
        self.cpx = 0
        self.cpy = 0

    def step(self):
        if self.click == 1 and self.alt != 1:
            if self.oportal:
                self.oportal.destroy()
                self.oportal = OrangePortal((self.cpx-50,self.cpy-70))
                self.cox = self.cpx
                self.coy = self.cpy
                self.click = 0
            else:
                self.oportal = OrangePortal((self.cpx-50,self.cpy-70))
                self.cox = self.cpx
                self.coy = self.cpy
                self.click = 0

        if self.click == 1 and self.alt == 1:
            if self.bportal:
                self.bportal.destroy()
                self.bportal = BluePortal((self.cpx-50,self.cpy-70))
                self.cbx = self.cpx
                sefl.cby = self.cpy
                self.click = 0
            else:
                self.bportal = BluePortal((self.cpx-50,self.cpy-70))
                self.cbx = self.cpx
                self.cby = self.cpy
                self.click = 0

        if self.mright == 1:
            self.setImage(0)
            self.x += .75
            self.y = self.y
        if self.mright == 0:
            self.x =self.x
            self.y=self.y
        if self.mleft == 1:
            self.setImage(1)
            self.x += -.75
            self.y = self.y
        if self.mleft == 0:
            self.x = self.x
            self.y = self.y

        col = self.collidingWithSprites(OrangePortal)
        if col:
            if self.bportal:
                self.x = self.cbx
                self.y = self.cby

    def ClickOn(self,event):
        self.click = 1
        self.cpx = event.x
        self.cpy = event.y
    def rightOn(self,event):
        self.mright = 1
    def rightOff(self,event):
        self.mright = 0
    def leftOn(self,event):
        self.mleft = 1
    def leftOff(self,event):
        self.mleft = 0
    def altOn(self,event):
        self.alt = 1
    def altOff(self,event):
        self.alt = 0

class BluePortal(Sprite):
    asset = ImageAsset("images/Blue_Portal.png", Frame(25,10,350,475), 1, 'vertical')
    def __init__(self, position):
        super().__init__(BluePortal.asset, position)
        self.scale = .25
        self.center = (0,0)

    def step(self):
        self.x = self.x
        self.y = self.y

class OrangePortal(Sprite):
    asset = ImageAsset("images/portal___orange_portal_by_maxiesnax-d5pcfmj.png", Frame(25,10,350,475), 1, 'vertical')
    def __init__(self, position):
        super().__init__(OrangePortal.asset, position)
        self.scale = .25
        self.center = (0,0)

    def step(self):
        self.x = self.x
        self.y = self.y

class PortalGame(App):
    def __init__(self, width, height):
        super().__init__(width, height)
        Sprite(wall, (400,20))
        Sprite(wall, (100,20))
        Sprite(exit, (800,100))
        Sprite(exit2, (800, 100))
        Sprite(doorline, (800, 30))
        Sprite(plat, (100,400))
        Sprite(plat, (650, 150))
        Sprite(goo, (100,0))
        Chell((0,0))

    def step(self):
        for chell in self.getSpritesbyClass(Chell):
            chell.step()

myapp = PortalGame(1000,750)
myapp.run()<|endoftext|>