Python code bij Chomp

Je kunt de code ook hier vinden als Jupyter notebook.

 

 

 


"""Python programma bij "Een wiskundige doet de afwas 18 -- Chocolade eten: analyse"
    Pythagoras, 63ste jaargang, nummer 6, juni 2024

    Auteur: Tom Verhoeff, TU Eindhoven

    In Python >= 3.9 kan `@lru_cache(maxsize=None)` vervangen worden door `@cache`.

    in Python >= 3.9 kunnen de type hints in de functie aanhef opgenomen worden.
"""

from functools import lru_cache  # vanaf Python 3.2
# from functools import cache  # vereist Python >=3.9
from typing import Any, Union, Optional, Iterator
import doctest

# types
SpelToestand = tuple[int, ...]  # for blok: SpelToestand: all(row > 0 for row in blok)
Zet = Union[tuple[()], tuple[int, int]]

def is_blok_legaal(blok):
    # type: (SpelToestand) -> bool
    """Bepaal of blok een legale Chomp speltoestand is.

    >>> is_blok_legaal(())
    True
    >>> is_blok_legaal((1,))
    True
    >>> is_blok_legaal((0,))
    False
    >>> is_blok_legaal((1, 0))
    False
    >>> is_blok_legaal((1, 2))
    False
    """
    return (all(n > 0 for n in blok) and
            all(a >= b for a, b in zip(blok, blok[1:])))

def start_blok(m, n):
    # type: (int, int) -> SpelToestand
    """Construeer een vol m x n blok.

    >>> start_blok(0, 0)
    ()
    >>> start_blok(1, 1)
    (1,)
    >>> start_blok(2, 3)
    (3, 3)
    """
    return m * (n,)

def str_blok(blok):
    # type: (SpelToestand) -> str
    """Converteer blok naar een string.

    >>> print(str_blok(start_blok(1, 1)))
    ##
    >>> print(str_blok(start_blok(2, 3)))
    ##[][]
    [][][]
    >>> print(str_blok((5, 3, 1, 1)))
    ##[][][][]
    [][][]
    []
    []
    >>> print(str_blok(()))
    .
    """
    if blok:
        return '\n'.join(
            ('[]' if index else '##') + (n - 1) * '[]'
            for index, n in enumerate(blok)
            if n > 0
        )
    else:
        return '.'

def is_legale_zet(blok, zet):
    # type: (SpelToestand, Zet) -> bool
    """Bepaal of zet een legale zet is voor blok.

    >>> is_legale_zet(start_blok(1, 1), (-1, 0))
    False
    >>> is_legale_zet(start_blok(1, 1), (0, -1))
    False
    >>> is_legale_zet(start_blok(1, 1), (0, 1))
    False
    >>> is_legale_zet(start_blok(1, 1), (1, 0))
    False
    >>> is_legale_zet(start_blok(1, 1), (0, 0))
    True
    >>> is_legale_zet(start_blok(2, 3), (1, 2))
    True
    >>> is_legale_zet((), ())
    True
    >>> is_legale_zet(start_blok(1, 1), ())
    False
    """
    if not zet:  # zet == ()
        return not bool(blok)  # blok == ()

    m, n = zet
    return 0 <= m < len(blok) and 0 <= n < blok[m]

def alle_zetten(blok):
    # type: (SpelToestand) -> Iterator[Zet]
    """Lever 1-voor-1 alle zetten voor blok.

    >>> print(*alle_zetten(()))
    <BLANKLINE>
    >>> print(*alle_zetten(start_blok(1, 1)))
    (0, 0)
    >>> print(*alle_zetten(start_blok(2, 3)))
    (0, 0) (0, 1) (0, 2) (1, 0) (1, 1) (1, 2)
    >>> print(*alle_zetten((3, 2)))
    (0, 0) (0, 1) (0, 2) (1, 0) (1, 1)
    """
    for rij, n in enumerate(blok):  # n > 0
        for kolom in range(n):
            yield rij, kolom

def chomp(blok, zet):
    # type: (SpelToestand, Zet) -> SpelToestand
    """Neem hap uit blok.

    Aanname: is_legale_zet(blok, zet)

    >>> chomp(start_blok(1, 1), (0, 0))
    ()
    >>> chomp(start_blok(2, 2), (1, 1))
    (2, 1)
    >>> chomp(start_blok(2, 2), (1, 0))
    (2,)
    """
    rij, kolom = zet
    return tuple(n if r < rij or n < kolom else kolom
                 for r, n in enumerate(blok)
                 if r < rij or 0 < kolom
                 )

@lru_cache(maxsize=None)  # onthoud vorige resultaten; gaat sneller maar kost meer geheugen
def winnende_zet(blok):
    # type: (SpelToestand) -> Optional[Zet]
    """Bepaal een winnende zet, indien mogelijk, en anders retourneer None.

    >>> print(winnende_zet(()))
    ()
    >>> print(winnende_zet(start_blok(1, 1)))
    None
    >>> print(winnende_zet(start_blok(1, 2)))
    (0, 1)
    >>> print(winnende_zet(start_blok(2, 3)))
    (1, 2)
    >>> print(winnende_zet((2, 1)))
    None
    """
    #     print(sum(blok) * ' ', repr(str_blok(blok)))
    if not blok:  # leeg blok
        return ()  # reeds gewonnen door niets te doen

    for zet in alle_zetten(blok):
        nieuw_blok = chomp(blok, zet)
        #         print(sum(blok) * ' ', 'beschouw', zet, '->', repr(str_blok(nieuw_blok)))
        #         print(winnende_zet(nieuw_blok))
        if winnende_zet(nieuw_blok) is None:
            return zet

    return None

def alle_winnende_zetten(blok):
    # type: (SpelToestand) -> Iterator[Zet]
    """Lever 1-voor-1 alle winnende zetten.

    >>> print(*alle_winnende_zetten(start_blok(1, 1)))
    <BLANKLINE>
    >>> print(*alle_winnende_zetten(start_blok(2, 2)))
    (1, 1)
    >>> print(*alle_winnende_zetten(start_blok(8, 10)))
    (3, 8) (4, 5)
    """
    if not blok:
        yield ()

    for zet in alle_zetten(blok):
        nieuw_blok = chomp(blok, zet)
        if winnende_zet(nieuw_blok) is None:
            yield zet

def main():
    # Kies via "uitcommentariëren" welke functie je wilt aanroepen

    # 1
    print("Voer alle doctests in de docstrings uit.")
    doctest.testmod(verbose=True)
    print()

    # 2
    print("Analyseer wat kleine blokken, met visuele uitvoer.")
    for m in range(1, 5 + 1):
        for n in range(m, 6 + 1):
            blok = start_blok(m, n)
            winner = winnende_zet(blok)
            print()
            print(m, 'x', n, winner)
            if winner:
                print(str_blok(chomp(blok, winner)))
    print()

    # 3
    print("Bepaal alle winnende zetten voor blokken tot 12x12.")
    print("(Dit duurt wat langer.)")
    for m in range(1, 12 + 1):
        for n in range(m, 12 + 1):
            print(f"{m:2} x {n:2}", *alle_winnende_zetten(start_blok(m, n)))
    print("Bij de blokken 8x10 en 9x10 is er meer dan één winnende zet.")
    print()

    # 4
    print("Bepaal alle winnende zetten voor blokken 3 x n t/m n = 30.")
    for m in range(3, 3 + 1):
        for n in range(1, 30 + 1):
            print(f"{m:2} x {n:2}", *alle_winnende_zetten(start_blok(m, n)))
    print("Ze zijn allemaal uniek.")
    print("Kun je hier een patroon in vinden?")
    print()

    # 5
    print("Bepaal afmeting van winnende zet voor blokken 3 x n t/m n = 30.")
    for m in range(3, 3+1):
        for n in range(1, 30+1):
            blok = start_blok(m, n)
            winner = winnende_zet(blok)
            print(m, 'x', n, winner)
            print(f"afmeting van winnende zet: {m - winner[0]}x{n - winner[1]}")
            # print(str_blok(chomp(blok, winner)))

if __name__ == '__main__':
    main()