Beispiel #1
0
def random_player(state: None, log: List[NamedTuple], hands: List[List[Card]],
                  rules: Rules, tokens: Tokens, slots: List[int],
                  discard_pile: List[List[int]]) -> Tuple[None, Move, str]:
    """
    Tsvika and Ofer's random player

    Usage:
        h = Hanabi([random_player] * 3)
    """
    my_id = len(log) % len(hands)

    possible_actions = []
    if tokens.lives > 1:
        possible_actions.append(Play)

    if tokens.clues > 0:
        possible_actions.append(Clue)

    if tokens.clues < rules.max_tokens.clues:
        possible_actions.append(Discard)

    action = random.choice(possible_actions)

    if action == Play:
        return state, Play.create(random.choice(hands[my_id]).id), ''
    if action == Discard:
        return state, Discard.create(random.choice(hands[my_id]).id), ''
    if action == Clue:
        player = random.choice([i for i in range(len(hands)) if i != my_id])
        clue_type = random.choice(['suit', 'rank'])
        return state, Clue.create(
            player, clue_type,
            getattr(random.choice(hands[player]).data, clue_type)), ''
Beispiel #2
0
 def io_player(state: None, log: List[NamedTuple], hands: List[List[Card]],
               rules: Rules, tokens: Tokens, slots: List[int],
               discard_pile: List[List[int]]) -> Tuple[None, Move, str]:
     print(f"{name}'s turn")
     pprint(log[-len(hands):])
     pprint(hands)
     pprint(tokens)
     pprint(slots)
     pprint(discard_pile)
     print('What will it be?')
     print('[c]lue <player><type><n>\t\t[p]lay <card>\t\t[d]iscard <card>')
     move = input().split()
     try:
         if move[0] == 'c':
             _, (player, clue_type, param) = move
             return state, Clue.create(int(player), {
                 's': 'suit',
                 'n': 'rank'
             }[clue_type], {
                 's': Suit,
                 'n': Rank
             }[clue_type].from_str(param)), ''
         elif move[0] == 'p':
             _, card_id = move
             return state, Play.create(int(card_id)), ''
         elif move[0] == 'd':
             _, card_id = move
             return state, Discard.create(int(card_id)), ''
         else:
             raise ValueError("not a valid move type")
     except (ValueError, KeyError):
         print('illegal move')
         return io_player(state, log, hands, rules, tokens, slots,
                          discard_pile)
Beispiel #3
0
def naive2_player(state: None, log: List[NamedTuple], hands: List[List[Card]],
                  rules: Rules, tokens: Tokens, slots: List[int],
                  discard_pile: List[List[int]]) -> Tuple[None, NamedTuple]:
    """
    Zvika and Ofer's naive player
    """
    my_id = len(log) % len(hands)
    my_card_ids = [card.id for card in hands[my_id]]

    hinted_cards = set()
    for move in log[-len(hands):]:
        if isinstance(move, ResolvedClue):
            if move.player == my_id:
                hinted_cards = hinted_cards.union(card.id
                                                  for card in move.cards)

    # Its better to play than hint
    if hinted_cards:
        play_card = max(hinted_cards)
        return state, Play.create(play_card), ''

    # Its better to hint than discard
    if tokens.clues > 0:
        for i in range(len(hands) - 1):
            player = (my_id + i + 1) % len(hands)
            player_suits = set([card.data.suit for card in hands[player]])
            player_ranks = set([card.data.rank for card in hands[player]])
            playable_suits = set()
            playable_ranks = set()
            for suit in player_suits:
                card = max(
                    [card for card in hands[player] if card.data.suit == suit])
                if slots[card.data.suit] == card.data.rank:
                    playable_suits.add(card.data.suit)
            for rank in player_ranks:
                card = max(
                    [card for card in hands[player] if card.data.rank == rank])
                if slots[card.data.suit] == card.data.rank:
                    playable_ranks.add(card.data.rank)

            # its better to go up then sideways
            clue = None
            clue_rank = 0
            if playable_ranks:
                clue = Clue.create(player, 'rank', max(playable_ranks))
                clue_rank = max(player_ranks)
            for suit in playable_suits:
                if slots[suit] > clue_rank:
                    clue = Clue.create(player, 'suit', suit)
                    clue_rank = slots[suit]
            if clue:
                return state, clue, ''

    # Its better to discard then playing like an idiot
    if tokens.clues < rules.max_tokens.clues:
        return state, Discard.create(min(my_card_ids)), ''

    # If all else fails, play like an idiot
    return state, Play.create(max(my_card_ids)), 'idiot'
Beispiel #4
0
def naive_player(log: List[NamedTuple], hands: List[List[Card]], rules: Rules,
                 tokens: Tokens, slots: List[int],
                 discard_pile: List[List[int]]) -> Tuple:
    """
    Zvika and Ofer's naive player
    """
    my_id = len(log) % len(hands)
    my_card_ids = [card.id for card in hands[my_id]]

    hinted_cards = set()
    for move in log[-len(hands):]:
        if isinstance(move, ResolvedClue) and move.player == my_id:
            hinted_cards = hinted_cards.union(card.id for card in move.cards)

    # Its better to play than hint
    if hinted_cards:
        play_card = max(hinted_cards)
        return Play.create(play_card), 'Play hinted card'

    # Its better to hint than discard
    if tokens.clues > 0:
        for i in range(len(hands) - 1):
            player = (my_id + i + 1) % len(hands)
            player_suits = set([card.data.suit for card in hands[player]])
            player_ranks = set([card.data.rank for card in hands[player]])
            for card in hands[player]:
                if slots[card.data.suit] != card.data.rank:
                    player_suits -= set([card.data.suit])
                    player_ranks -= set([card.data.rank])
            if player_ranks:
                # its better to go up then sideways
                return Clue.create(
                    player, 'rank',
                    max(player_ranks)), 'Given actionable rank clue'
            if player_suits:
                return Clue.create(
                    player, 'suit',
                    player_suits.pop()), 'Given actionable suit clue'

    # Its better to discard then playing like an idiot
    if tokens.clues < rules.max_tokens.clues:
        return Discard.create(min(my_card_ids)), 'Discard oldest card'

    # If all else fails, play like an idiot
    return Play.create(max(my_card_ids)), 'Play like an idiot'
Beispiel #5
0
def humanlike_player(state, log, hands, rules, tokens, slots, discard_pile):
    """
    Ofer's humanlike player
    """

    def add_card_to_state(given_state, card_id):
        if card_id not in given_state:
            given_state[card_id] = CardInfo(
                Info(None, None),
                Info([True for _ in range(rules.suits)], [True for _ in rules.ranks])
            )

    def update_cards(cards, player_id=None, clue=None):
        if player_id is None:
            player_id = my_id

        hinted_cards = set()
        _log = log[-len(hands):]
        if clue is not None:
            _log.append(clue)

        for move in _log:
            if isinstance(move, ResolvedClue):
                if move.player == player_id:
                    for card in move.cards:
                        hinted_cards.add(card.id)

                card_ids_in_hint = set()
                for card in move.cards:
                    add_card_to_state(cards, card.id)
                    card_ids_in_hint.add(card.id)
                    cards[card.id] = cards[card.id]._replace(positive=cards[card.id].positive._replace(**{move.type: move.param}))
                for card in hands[player_id]:
                    if card.id not in card_ids_in_hint:
                        add_card_to_state(cards, card.id)
                        new_negative = getattr(cards[card.id].negative, move.type)
                        new_negative[move.param] = False
                        cards[card.id] = cards[card.id]._replace(negative=cards[card.id].negative._replace(**{move.type: new_negative}))

        # Consolidate negatives in hand
        for card_id in hinted_cards:
            if cards[card_id].negative.suit.count(True) == 1:
                cards[card.id]._replace(positive=cards[card.id].positive._replace(
                    suit=[i for i, v in enumerate(cards[card_id].negative.suit) if v]))
            if cards[card_id].negative.rank.count(True) == 1:
                cards[card.id]._replace(positive=cards[card.id].positive._replace(
                    rank=[i for i, v in enumerate(cards[card_id].negative.rank) if v]))

        return cards, hinted_cards

    def get_max_rank_in_suit(suit, _slots, _discard_pile):
        max_rank_in_suit = None
        for rank in range(len(rules.ranks)):
            left_in_rank = rules.ranks[rank] - _discard_pile[suit][rank]
            if rank >= _slots[suit] and left_in_rank == 0:
                max_rank_in_suit = rank
                break

        return max_rank_in_suit

    def is_playable_suit(suit, _slots, _discard_pile):
        if _slots[suit] > len(rules.ranks):
            return False

        max_rank_in_suit = get_max_rank_in_suit(suit, _slots, _discard_pile)
        if max_rank_in_suit is not None and max_rank_in_suit < _slots[suit]:
            return False

        return True

    def should_play_card(cards, cards_in_hand, hinted_cards, _slots=None, _discard_pile=None):
        if _slots is None:
            _slots = slots
        if _discard_pile is None:
            _discard_pile = discard_pile

        hinted_cards = hinted_cards.intersection(cards_in_hand)
        definate_cards_to_play = set()
        cards_to_play = set()
        for card_id in cards_in_hand:
            add_card_to_state(cards, card_id)
            if cards[card_id].positive.suit is not None and cards[card_id].positive.rank is not None:
                if is_play_legal(cards[card_id].positive.suit, cards[card_id].positive.rank, _slots):
                    definate_cards_to_play.add(card_id)

            if card_id in sorted(hinted_cards):
                if cards[card_id].positive.rank is not None and cards[card_id].positive.suit is None and any(
                        [is_playable_suit(suit, _slots, _discard_pile) and cards[card_id].positive.rank == _slots[suit]
                         for suit in range(len(_slots))]):
                    cards_to_play.add(card_id)
                if cards[card_id].positive.suit is not None and cards[card_id].positive.rank is None \
                        and is_playable_suit(cards[card_id].positive.suit, _slots, _discard_pile):
                    cards_to_play.add(card_id)

        if definate_cards_to_play:  # its better to go up than go sideways!
            highest_rank = 0
            cards_in_highest_rank = set()
            for card_id in definate_cards_to_play:
                if cards[card_id].positive.rank > highest_rank:
                    highest_rank = cards[card_id].positive.rank
                    cards_in_highest_rank = set()
                if cards[card_id].positive.rank == highest_rank:
                    cards_in_highest_rank.add(card_id)
            return sorted(cards_in_highest_rank)[-1]  # play newest card

        if cards_to_play:
            return sorted(cards_to_play)[-1]  # play newest card

        return None

    def what_will_player_play(cards, hand, player_id, clue, _slots, _discard_pile):
        cards, _hinted_cards = update_cards(cards, player_id, clue)
        card_id = should_play_card(cards, [card.id for card in hand], _hinted_cards, _slots, _discard_pile)
        if card_id is not None:
            card = [card for card in hand if card.id == card_id][0]
            legal = is_play_legal(card.data.suit, card.data.rank, _slots)
            return cards, legal, card_id
        else:
            return cards, None, None

    def is_play_legal(suit, rank, _slots):
        return _slots[suit] == rank

    def create_clue(my_id, _player, type, param):
        cards = [card for card in hands[_player] if getattr(card.data, type) == param]
        cards_neg = [card for card in hands[_player] if getattr(card.data, type) != param]
        return ResolvedClue.create(my_id, _player, type, param, cards, cards_neg)

    # Start

    if state is None:
        state = {}

    my_id = len(log) % len(hands)
    state, state_actions = update_cards(state)

    my_card_ids = [card.id for card in hands[my_id]]

    card_to_play = should_play_card(state, my_card_ids, state_actions, slots, discard_pile)

    if card_to_play is not None:   # Its better to play than hint
        return state, Play.create(card_to_play), 'Played card'

    if tokens.clues > 0:  # Its better to hint than discard
        foreseen_slots = list(slots)
        foreseen_state = dict(state)

        for i in range(len(hands) - 1):
            player = (my_id + i + 1) % len(hands)
            foreseen_state, is_legal, play = what_will_player_play(
                foreseen_state, hands[player], player, None, foreseen_slots, discard_pile)
            player_state, player_hinted = update_cards(foreseen_state, player)
            player_play = should_play_card(state, [card.id for card in hands[player]], player_hinted)
            if player_play is not None:
                card = [card for card in hands[player] if card.id == player_play][0]

                if is_play_legal(card.data.suit, card.data.rank, slots):
                    foreseen_slots[card.data.suit] = card.data.rank
                    continue
                else:  # try and rectify stupidity
                    for card in hands[player]:
                        suit_clue = create_clue(my_id, player, 'suit', card.data.suit)
                        _, is_legal, play = what_will_player_play(
                            dict(foreseen_state), hands[player], player, suit_clue, foreseen_slots, discard_pile)
                        if is_legal or play is None:
                            return state, Clue.create(player, 'suit', card.data.suit), 'Gave hint against stupid play'

                        rank_clue = create_clue(my_id, player, 'rank', card.data.rank)
                        _, is_legal, play = what_will_player_play(
                            dict(foreseen_state), hands[player], player, rank_clue, foreseen_slots, discard_pile)
                        if is_legal or play is None:
                            return state, Clue.create(player, 'rank', card.data.rank), 'Gave hint against stupid play'

            good_clues = set()
            for card in hands[player]:
                if slots[card.data.suit] == card.data.rank:
                    suit_clue = create_clue(my_id, player, 'suit', card.data.suit)
                    _, is_legal, play = what_will_player_play(
                        dict(foreseen_state), hands[player], player, suit_clue, foreseen_slots, discard_pile)
                    if is_legal and play == card.id:
                        good_clues.add(PossibleClue(player=player, card=card, type='suit'))

                    rank_clue = create_clue(my_id, player, 'rank', card.data.rank)
                    _, is_legal, play = what_will_player_play(
                        dict(foreseen_state), hands[player], player, rank_clue, foreseen_slots, discard_pile)
                    if is_legal and play == card.id:
                        good_clues.add(PossibleClue(player=player, card=card, type='rank'))

            if good_clues:
                # make sure highest card possible is played
                highest_rank = 0
                given_clue = None
                for clue in good_clues:
                    if given_clue is None:
                        given_clue = clue
                    if clue.card.data.rank > highest_rank:
                        highest_rank = clue.card.data.rank
                        given_clue = clue
                return state, Clue.create(given_clue.player, given_clue.type, getattr(given_clue.card.data, given_clue.type)), 'Gave actionable clue'

    if tokens.clues < rules.max_tokens.clues:  # Its better to discard then playing like an idiot
        protected_cards = set()
        for card_id in my_card_ids:
            # Throw away useless cards
            if state[card_id].positive.suit is not None and not is_playable_suit(state[card_id].positive.suit, slots, discard_pile):
                return state, Discard.create(card_id), 'Discarded unplayable suit'

            if state[card_id].positive.rank is not None and all([slot<state[card_id].positive.rank for slot in slots]):
                return state, Discard.create(card_id), 'Discarded Unplayable rank'

            if state[card_id].positive.suit is not None and state[card_id].positive.rank is not None:
                if slots[state[card_id].positive.suit] < state[card_id].positive.rank:
                    return state, Discard.create(card_id), 'Discarded unplayable known card'

                # Don't throw away lone copies
                avaiable_copies = rules.ranks[state[card_id].positive.rank]
                discarded_copies = discard_pile[state[card_id].positive.suit][state[card_id].positive.rank]
                if avaiable_copies - discarded_copies == 1:
                    protected_cards.add(card_id)

            # Don't throw away 5s
            if state[card_id].positive.rank is not None:
                avaiable_copies = rules.ranks[state[card_id].positive.rank]
                if avaiable_copies == 1:
                    protected_cards.add(card_id)

        throwaways = set(my_card_ids) - protected_cards
        if throwaways:
            return state, Discard.create(min(throwaways)), 'Discarded unprotected card'

        return state, Discard.create(min(my_card_ids)), 'Discarded oldest card'

    if tokens.clues > 0:
        # give random clue to the player playing before you so the other players may fix it
        player = (my_id -1) % len(hands)
        if hands[player]:
            highest_rank_in_hand = sorted([card.data.rank for card in hands[player]])[-1]
            return state, Clue.create(player, 'rank', highest_rank_in_hand), 'Gave random clue'

    # If all else fails, play like an idiot
    return state, Play.create(max(my_card_ids)), 'Played random card'
Beispiel #6
0
def oracle_player(log: List[NamedTuple], hands: List[List[Card]],
                  rules: Rules, tokens: Tokens, slots: List[int],
                  discard_pile: List[List[int]]) -> Tuple:
    """
    Zvika and Ofer's oracle player
    """
    my_id = len(log) % len(hands)
    my_hand = hands[my_id]
    if my_hand[0].data is None:
        raise RuntimeError("I need to be omniscient")

    # play something playable
    playable_card = None
    for card in my_hand:
        if slots[card.data.suit] == card.data.rank:
            if playable_card is None or playable_card.data.rank < card.data.rank:
                playable_card = card
    if playable_card is not None:
        return Play.create(playable_card.id), 'playable'

    def get_card_to_discard():
        # discard already played
        for card in my_hand:
            if slots[card.data.suit] > card.data.rank:
                return card.id, 'low'
        # discard unreachable
        for suit in range(rules.suits):
            max_rank_in_suit = None
            for rank in range(len(rules.ranks)):
                left_in_rank = rules.ranks[rank] - discard_pile[suit][rank]
                if rank >= slots[suit] and left_in_rank == 0:
                    max_rank_in_suit = rank
                    break
            if max_rank_in_suit:
                for card in my_hand:
                    if card.data.suit == suit and card.data.rank > max_rank_in_suit:
                        return card.id, 'high'
        # discard duplicates in own hand
        knowns = [card.data for card in my_hand]
        if len(set(knowns)) < len(knowns):
            for i, known in enumerate(knowns):
                for known2 in knowns[i+1:]:
                    if known == known2:
                        return my_hand[i].id, 'dup'
        # discard duplicates with others
        knowns = [card.data for card in my_hand]
        for hand in hands[:my_id]+hands[my_id+1:]:
            knowns2 = [card.data for card in hand]
            if len(set(knowns+knowns2)) < len(knowns)+len(set(knowns2)):
                for i, known in enumerate(knowns):
                    for known2 in knowns2:
                        if known == known2:
                            return my_hand[i].id, 'dup2'
        return None, ''

    # discard something discardable
    if tokens.clues < rules.max_tokens.clues:
        card, note = get_card_to_discard()
        if card is not None:
            return Discard.create(card), 'pass/d/' + note

    # nothing useful to do
    # try to pass with useless clue
    if tokens.clues > 0:
        player = (my_id + 1) % len(hands)
        if hands[player]:
            return Clue.create(player, 'suit', hands[player][0].data.suit), 'pass/c'

    # try to pass with false play
    if tokens.lives > 1:
        card, note = get_card_to_discard()
        if card is not None:
            return Play.create(card), 'pass/p/' + note

    # you have to throw something useful. try the farthest from the suit
    # look for an expandable card
    diff = None
    throw = None
    for card in my_hand:
        card_diff = card.data.rank - slots[card.data.suit]
        if diff is None or card_diff > diff:
            if rules.ranks[card.data.rank] - discard_pile[card.data.suit][card.data.rank] > 1:
                diff = card_diff
                throw = card
                note = ''
    # look for a non expandable card, if you must (BOO!)
    if diff is None:
        note = '/bad'
        for card in my_hand:
            card_diff = card.data.rank - slots[card.data.suit]
            if diff is None or card_diff > diff:
                diff = card_diff
                throw = card

    # throw by discard
    if tokens.clues < rules.max_tokens.clues:
        return Discard.create(throw.id), 'throw/d' + note

    # throw by false play
    return Play.create(throw.id), 'throw/p' + note
Beispiel #7
0
def naive2_with_2nd_degree_player(log: List[NamedTuple],
                                  hands: List[List[Card]], rules: Rules,
                                  tokens: Tokens, slots: List[int],
                                  discard_pile: List[List[int]]) -> Tuple:
    """
    Zvika and Ofer's naive player
    Improved with 2nd degree clues by Ofer
    This player only gives 2nd degree clues to the next player
    """
    def relative_position(player_id):
        return (player_id - my_id) % len(hands)

    def is_playable(card):
        return slots[card.data.suit] == card.data.rank

    my_id = len(log) % len(hands)
    my_card_ids = [card.id for card in hands[my_id]]

    hinted_cards = set()
    for move in log[-len(hands):]:
        if isinstance(move, ResolvedClue):
            if move.player == my_id:
                hinted_cards = hinted_cards.union(card.id
                                                  for card in move.cards)

    # Its better to play than hint
    if hinted_cards:
        play_card = max(hinted_cards)
        return Play.create(play_card), 'Play hinted card'

    # Play 2nd degree clues
    for move in log[-len(hands) + 1:]:
        if isinstance(move, ResolvedClue):
            if relative_position(move.cur_player) > relative_position(
                    move.player):
                hinted_card = max([card.id for card in move.cards])
                known_card = [
                    card for card in hands[move.player]
                    if card.id == hinted_card
                ][0]
                if slots[known_card.data.suit] == known_card.data.rank - 1:
                    return Play.create(
                        max(my_card_ids)), 'play 2nd degree clue'

    # Its better to hint than discard
    if tokens.clues > 0:
        # If possible give 2nd degree clue
        hintable_card = hands[(my_id + 1) % len(hands)][-1]
        if is_playable(hintable_card):
            player_id = (my_id + 2) % len(hands)
            for card in hands[player_id]:
                if card.data.suit == hintable_card.data.suit and card.data.rank == hintable_card.data.rank + 1:
                    # suit clue
                    if max([card.id for card in hands[player_id] if card.data.suit == hintable_card.data.suit])\
                            == card.id:
                        return Clue.create(
                            player_id, 'suit',
                            card.data.suit), 'give 2nd degree suit clue'
                    # rank clue
                    if max([card.id for card in hands[player_id] if card.data.rank == hintable_card.data.rank+1])\
                            == card.id:
                        return Clue.create(
                            player_id, 'rank',
                            card.data.rank), 'give 2nd degree rank clue'

        # Give regular clue
        for i in range(len(hands) - 1):
            player = (my_id + i + 1) % len(hands)
            player_suits = set([card.data.suit for card in hands[player]])
            player_ranks = set([card.data.rank for card in hands[player]])
            playable_suits = set()
            playable_ranks = set()
            for suit in player_suits:
                card = max(
                    [card for card in hands[player] if card.data.suit == suit])
                if slots[card.data.suit] == card.data.rank:
                    playable_suits.add(card.data.suit)
            for rank in player_ranks:
                card = max(
                    [card for card in hands[player] if card.data.rank == rank])
                if slots[card.data.suit] == card.data.rank:
                    playable_ranks.add(card.data.rank)

            # its better to go up then sideways
            clue = None
            clue_rank = 0
            if playable_ranks:
                clue = Clue.create(player, 'rank', max(playable_ranks))
                clue_rank = max(player_ranks)
            for suit in playable_suits:
                if slots[suit] > clue_rank:
                    clue = Clue.create(player, 'suit', suit)
                    clue_rank = slots[suit] + 1
            if clue:
                return clue, 'Given actionable clue'

    # Its better to discard then playing like an idiot
    if tokens.clues < rules.max_tokens.clues:
        return Discard.create(min(my_card_ids)), 'Discard oldest card'

    # If all else fails, play like an idiot
    return Play.create(max(my_card_ids)), 'Play like an idiot'