Day 22: Crab Combat

Classes:

Deck(cards)

Represent a deck of cards.

Split(deck1, deck2)

Represent a split of one big deck of cards into two sub-decks.

Functions:

play_a_round(split)

Play a round of the game given the current split.

parse_lines(lines)

Parse the input lines into two decks, as list of cards.

compute_score(deck)

Compute the score for the given deck based on its cards.

play(split)

Play the game starting with the split until one of the players wins.

class Deck(cards: Sequence[int])[source]

Represent a deck of cards.

Please make sure that you transfer the “ownership” immediately to Deck and don’t modify the original list of strings any more:

##
# OK
##

deck = Deck([1, 2, 3])

##
# Not OK
##

cards = [1, 2, 3]
deck = Deck(cards)
# ... do something assuming ``deck`` is immutable ...

cards[0] = 2
# ERROR! cards[0] now breaks the invariant!

Methods:

__init__(cards)

Initialize with the given values.

__getitem__()

Get the card(s) at the given index.

__len__()

Return the number of the cards in the deck.

__iter__()

Iterate through the cards in the deck.

__add__(other)

Join two decks together.

__repr__()

Represent the deck for easier debugging.

__eq__(other)

Compare with other by cards.

Attributes:

cards

Cards in the deck

__init__(cards: Sequence[int]) None[source]

Initialize with the given values.

Requires
  • len(set(cards)) == len(cards)

    (Unique cards)

  • all(card >= 0 for card in cards)

cards: Final[Sequence[int]]

Cards in the deck

__getitem__(index: int) int[source]
__getitem__(index: slice) Deck

Get the card(s) at the given index.

__len__() int[source]

Return the number of the cards in the deck.

__iter__() Iterator[int][source]

Iterate through the cards in the deck.

__add__(other: Deck) Deck[source]

Join two decks together.

Requires
  • (
            sum := list(self.cards) + other.cards,
            len(set(sum)) == len(sum)
    )[1]
    

    (Unique cards after the addition)

__repr__() str[source]

Represent the deck for easier debugging.

__eq__(other: object) bool[source]

Compare with other by cards.

If other is not a Deck or List:, propagate to generic ``__eq__`.

class Split(deck1: Deck, deck2: Deck)[source]

Represent a split of one big deck of cards into two sub-decks.

Methods:

__init__(deck1, deck2)

Initialize with the given values.

Attributes:

deck1

The deck for the player 1

deck2

The deck for the player 2

__init__(deck1: Deck, deck2: Deck) None[source]

Initialize with the given values.

Requires
  • not set(deck1).intersection(deck2)

    (No overlapping cards)

deck1: Final[Deck]

The deck for the player 1

deck2: Final[Deck]

The deck for the player 2

play_a_round(split: Split) Split[source]

Play a round of the game given the current split.

Returns

A new split after the round

Requires
  • len(split.deck2) > 0

    (Not game over for player 2)

  • len(split.deck1) > 0

    (Not game over for player 1)

Ensures
  • (
            len(split.deck1) == len(result.deck1) + 1
            and len(split.deck2) == len(result.deck2) - 1)
    or (
            len(split.deck1) == len(result.deck1) - 1
            and len(split.deck2) == len(result.deck2) + 1)
    

    (Either lost or won two cards)

  • split.deck2[1:] == result.deck2[0:len(split.deck2) - 1]

    (Only the prefix and the suffix of the deck 2 change)

  • split.deck1[1:] == result.deck1[0:len(split.deck1) - 1]

    (Only the prefix and the suffix of the deck 1 change)

  • set(split.deck1).union(split.deck2) == set(result.deck1).union(result.deck2)

    (No new cards)

parse_lines(lines: List[str]) Tuple[List[int], List[int]][source]

Parse the input lines into two decks, as list of cards.

Requires
  • len(lines) > 3

  • lines[0] == 'Player 1:'

  • 'Player 2:' in lines[1:]

  • all(
        re.match(r'^(Player 1:|Player 2:|0|[1-9][0-9]*|)\Z', line)
        for line in lines
    )
    
compute_score(deck: Deck) int[source]

Compute the score for the given deck based on its cards.

Ensures
  • result >= 0

play(split: Split) Split[source]

Play the game starting with the split until one of the players wins.

Requires
  • len(split.deck2) > 0

    (Not game over for player 2)

  • len(split.deck1) > 0

    (Not game over for player 1)

Ensures
  • set(split.deck1).union(split.deck2) == set(result.deck1).union(result.deck2)

  • (
            len(split.deck1) + len(split.deck2) == len(result.deck1)
            and len(result.deck2) == 0
    )
    or (
            len(result.deck1) == 0
            and len(split.deck1) + len(split.deck2) == len(result.deck2)
    )