I'm looking for bugs.  I tried to test all functionality in a variety of ways.

#by JB0x2D1

from decimal import Decimal
import math
import numbers
import operator
from fractions import Fraction

class Mixed(Fraction):
    """This class implements Fraction, which implements rational numbers."""
        # We're immutable, so use __new__ not __init__
    def __new__(cls, whole=0, numerator=None, denominator=None):
        """Constructs a Rational.

        Takes a string like '-1 2/3' or '1.5', another Rational instance, a
        numerator/denominator pair, a float, or a whole number/numerator/
        denominator set.  If one or more non-zero arguments is negative,
        all are treated as negative and the result is negative.

        General behavior:  whole number + (numerator / denominator)

        Examples
        --------

        &gt;&gt;&gt; Mixed(Mixed(-1,1,2), Mixed(0,1,2), Mixed(0,1,2))
        Mixed(-2, 1, 2)
        Note: The above call is similar to:
        &gt;&gt;&gt; Fraction(-3,2) + Fraction(Fraction(-1,2), Fraction(1,2))
        Fraction(-5, 2)
        &gt;&gt;&gt; Mixed('-1 2/3')
        Mixed(-1, 2, 3)
        &gt;&gt;&gt; Mixed(10,-8)
        Mixed(-1, 1, 4)
        &gt;&gt;&gt; Mixed(Fraction(1,7), 5)
        Mixed(0, 1, 35)
        &gt;&gt;&gt; Mixed(Mixed(1, 7), Fraction(2, 3))
        Mixed(0, 3, 14)
        &gt;&gt;&gt; Mixed(Mixed(0, 3, 2), Fraction(2, 3), 2)
        Mixed(1, 5, 6)
        &gt;&gt;&gt; Mixed('314')
        Mixed(314, 0, 1)
        &gt;&gt;&gt; Mixed('-35/4')
        Mixed(-8, 3, 4)
        &gt;&gt;&gt; Mixed('3.1415')
        Mixed(3, 283, 2000)
        &gt;&gt;&gt; Mixed('-47e-2')
        Mixed(0, -47, 100)
        &gt;&gt;&gt; Mixed(1.47)
        Mixed(1, 2116691824864133, 4503599627370496)
        &gt;&gt;&gt; Mixed(2.25)
        Mixed(2, 1, 4)
        &gt;&gt;&gt; Mixed(Decimal('1.47'))
        Mixed(1, 47, 100)

        """
        self = super(Fraction, cls).__new__(cls)

        if (numerator is None) and (denominator is None): #single argument
            if isinstance(whole, numbers.Rational) or \
               isinstance(whole, float) or \
               isinstance(whole, Decimal):
                if type(whole) == Mixed:
                    return whole
                f = Fraction(whole)
                whole = 0
            elif isinstance(whole, str):
                # Handle construction from strings.
                arg = whole
                fail = False
                try:
                    f = Fraction(whole)
                    whole = 0
                except ValueError:
                    n = whole.split()
                    if (len(n) == 2):
                        try:
                            whole = Fraction(n[0])
                            f = Fraction(n[1])
                        except ValueError:
                            fail = True
                    else:
                        fail = True
                if fail:
                    raise ValueError('Invalid literal for Mixed: %r' %
                                         arg)
            else:
                raise TypeError("argument should be a string "
                                "or a Rational instance")
        elif (isinstance(numerator, numbers.Rational) and #two arguments
            isinstance(whole, numbers.Rational) and (denominator is None)):
            #here whole is treated as numerator and numerator as denominator
            if numerator == 0:
                raise ZeroDivisionError('Mixed(%s, 0)' % whole)
            f = Fraction(whole, numerator)
            whole = 0
        elif (isinstance(whole, numbers.Rational) and #three arguments
              isinstance(numerator, numbers.Rational) and
              isinstance(denominator, numbers.Rational)):
            if denominator == 0:
                raise ZeroDivisionError('Mixed(%s, %s, 0)' % whole, numerator)
            whole = Fraction(whole)
            f = Fraction(numerator, denominator)
        else:
            raise TypeError("all three arguments should be "
                            "Rational instances")
        #handle negative values and convert improper to mixed number fraction
        if (whole &lt; 0) and (f &gt; 0):
            f = -f + whole
        elif (whole &gt; 0) and (f &lt; 0):
            f += -whole
        else:
            f += whole
        numerator = f.numerator
        denominator = f.denominator
        if numerator &lt; 0:
            whole = -(-numerator // denominator)
            numerator = -numerator % denominator
        else:
            whole = numerator // denominator
            numerator %= denominator
        self._whole = whole
        self._numerator = numerator
        self._denominator = denominator
        return self

    def __repr__(self):
        """repr(self)"""
        return ('Mixed(%s, %s, %s)' % (self._whole, self._numerator,
                                       self._denominator))

    def __str__(self):
        """str(self)"""
        if self._numerator == 0:
            return str(self._whole)
        elif self._whole != 0:
            return '%s %s/%s' % (self._whole, self._numerator,
                                 self._denominator)
        else:
            return '%s/%s' % (self._numerator, self._denominator)

    def to_fraction(self):
        n = self._numerator
        if self._whole != 0:
            if self._whole &lt; 0:
                n *= -1
            n += self._whole * self._denominator
        return Fraction(n, self._denominator)

    def limit_denominator(self, max_denominator=1000000):
        """Closest Fraction to self with denominator at most max_denominator.

        &gt;&gt;&gt; Mixed('3.141592653589793').limit_denominator(10)
        Mixed(3, 1, 7)
        &gt;&gt;&gt; Mixed('3.141592653589793').limit_denominator(100)
        Mixed(3, 14, 99)
        &gt;&gt;&gt; Mixed(4321, 8765).limit_denominator(10000)
        Mixed(0, 4321, 8765)
        """
        return Mixed(self.to_fraction().limit_denominator(max_denominator))

    @property
    def numerator(a):
        return a.to_fraction().numerator

    @property
    def denominator(a):
        return a._denominator

    @property
    def whole(a):
        """returns the whole number only (a % 1)

        &gt;&gt;&gt; Mixed(10,3).whole
        3
        """
        return a._whole

    @property
    def fnumerator(a):
        """ returns the fractional portion's numerator.

        &gt;&gt;&gt; Mixed('1 3/4').fnumerator
        3
        """
        return a._numerator

    def _add(a, b):
        """a + b"""
        return Mixed(a.numerator * b.denominator +
                     b.numerator * a.denominator,
                     a.denominator * b.denominator)
    __add__, __radd__ = Fraction._operator_fallbacks(_add, operator.add)

    def _sub(a, b):
        """a - b"""
        return Mixed(a.numerator * b.denominator -
                        b.numerator * a.denominator,
                        a.denominator * b.denominator)

    __sub__, __rsub__ = Fraction._operator_fallbacks(_sub, operator.sub)

    def _mul(a, b):
        """a * b"""
        return Mixed(a.numerator * b.numerator, a.denominator * b.denominator)

    __mul__, __rmul__ = Fraction._operator_fallbacks(_mul, operator.mul)


    def _div(a, b):
        """a / b"""
        return Mixed(a.numerator * b.denominator,
                        a.denominator * b.numerator)

    __truediv__, __rtruediv__ = Fraction._operator_fallbacks(_div, operator.truediv)

    def __pow__(a, b):
        """a ** b

        If b is not an integer, the result will be a float or complex
        since roots are generally irrational. If b is an integer, the
        result will be rational.

        """
        if isinstance(b, numbers.Rational):
            if b.denominator == 1:
                return Mixed(Fraction(a) ** b)
            else:
                # A fractional power will generally produce an
                # irrational number.
                return float(a) ** float(b)
        else:
            return float(a) ** b

    def __rpow__(b, a):
        """a ** b"""
        if b._denominator == 1 and b._numerator &gt;= 0:
            # If a is an int, keep it that way if possible.
            return a ** b.numerator

        if isinstance(a, numbers.Rational):
            return Mixed(a.numerator, a.denominator) ** b

        if b._denominator == 1:
            return a ** b.numerator

        return a ** float(b)

    def __pos__(a):
        """+a: Coerces a subclass instance to Fraction"""
        return Mixed(a.numerator, a.denominator)

    def __neg__(a):
        """-a"""
        return Mixed(-a.numerator, a.denominator)

    def __abs__(a):
        """abs(a)"""
        return Mixed(abs(a.numerator), a.denominator)

    def __trunc__(a):
        """trunc(a)"""
        if a.numerator &lt; 0:
            return -(-a.numerator // a.denominator)
        else:
            return a.numerator // a.denominator

    def __hash__(self):
        """hash(self)"""
        return self.to_fraction().__hash__()

    def __eq__(a, b):
        """a == b"""
        return Fraction(a) == b

    def _richcmp(self, other, op):
        """Helper for comparison operators, for internal use only.

        Implement comparison between a Rational instance `self`, and
        either another Rational instance or a float `other`.  If
        `other` is not a Rational instance or a float, return
        NotImplemented. `op` should be one of the six standard
        comparison operators.

        """
        return self.to_fraction()._richcmp(other, op)

    def __reduce__(self):
        return (self.__class__, (str(self),))

    def __copy__(self):
        if type(self) == Mixed:
            return self     # I'm immutable; therefore I am my own clone
        return self.__class__(self.numerator, self.denominator)

    def __deepcopy__(self, memo):
        if type(self) == Mixed:
            return self     # My components are also immutable
        return self.__class__(self.numerator, self.denominator)


Latest version download here.


Question: Good travel games for two players, especially for playing on trains?
Answer: The base version of Carcassonne can be played on a very compact space, if you get an alternative scoring track. 

I have a cloth scoring track, plus the draw bag that comes with B&amp;T (I think) that I can carry in a mini-expansion box.

There is also a travel version of Carcassonne.

The iPad version of Caracassonne and Small World are also very nice. You only need one iPad, and the battery lasts ages.