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.

Good travel games for two players, especially for playing on trains?
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.
