<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function
from collections import Counter
import itertools
import math
import numpy as np
import random

# ------------------------------------------------------------
# Cards

# Cards are integers:

CARD_FACES = 'A234567890JQK'
CARD_SUITS = 'SHDC'
CARD_VALUES = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10]

def make_card(face, suit):
    return face + suit * 13

def split_card(vint):
    return vint % 13, vint // 13

def card_tostring(vint):
    v, s = split_card(vint)
    return '{}{}'.format(CARD_FACES[v], CARD_SUITS[s])

def make_deck():
    return range(52)

# ------------------------------------------------------------
# Cribbage scoring

def make_random_hand():
    return random.sample(make_deck(), 4)

def make_random_hand_and_draw():
    hd = random.sample(make_deck(), 5)
    return hd[:4], hd[4]

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return itertools.izip(a, b)

def score_hand(hand, draw=None, verbose=False):
    '''
    Scores a Cribbage hand.

    Arguments:
    - `hand`: a list of four card values
    - `draw`: a card value, or None
    '''
    assert len(hand) == 4
    score = 0
    # split card values
    split_values_hand = [split_card(c) for c in hand]
    split_values = split_values_hand[:]
    if draw is not None:
        draw_split = split_card(draw)
        _draw_face, draw_suit = draw_split
        split_values.append(draw_split)
    face_values = [f for (f,s) in split_values]
    # score runs
    #sorted_faces = sorted(face_values)
    face_counts = Counter(face_values)
    sorted_uniq_faces = sorted(face_counts.keys()) + [None]
    # a run is a contiguous
    run_begin = None
    for last_v, this_v in pairwise(sorted_uniq_faces):
        if this_v == last_v + 1:
            if run_begin is None:
                run_begin = last_v
            else:
                pass
        else:
            if run_begin is None:
                pass
            else:
                # finish a run
                run_length = last_v - run_begin + 1
                if run_length >= 3:
                    # interesting
                    run_score = np.product([face_counts[x] for x in
                                            range(run_begin, run_begin + run_length)]) * run_length
                    if verbose:
                        print('run', run_begin, last_v, run_score)
                    score += run_score
                run_begin = None
    # score pairs/triples/quads/etc.
    for _f, count in face_counts.items():
        # count = 2 -> 2
        # count = 3 -> 6
        # count = n -> 2 * (n CHOOSE 2)
        if count > 1:
            pair_score = math.factorial(count) / math.factorial(count - 2)
            if verbose:
                print('pair', _f, count, pair_score)
            score += pair_score
    # score 15s
    card_values = [CARD_VALUES[x] for x in face_values]
    for comb_len in [2,3,4,5]:
        for vlist in itertools.combinations(card_values, comb_len):
            if sum(vlist) == 15:
                if verbose:
                    print('fifteen')
                score += 2
    # score flush
    suit_values = set([s for (f,s) in split_values_hand])
    if len(suit_values) == 1:
        if draw is not None and list(suit_values)[0] == draw_suit:
            # draw 5
            if verbose:
                print('flush 5')
            score += len(hand) + 1
        else:
            if verbose:
                print('flush')
            score += len(hand)
    # score special jack
    if draw is not None and [f for (f,s) in split_values_hand if s == draw_suit and f == 10]:
        if verbose:
            print('special jack')
        score += 1
    if verbose:
        print('score', score)
    return score

def test_score():
    global hand, draw
    hand, draw = make_random_hand_and_draw()
    print(hand)
    print(draw)
    print('hand', ', '.join([card_tostring(v) for v in hand]))
    print('draw', card_tostring(draw))
    score_hand(hand, draw, verbose=True)

def n_choose_k(n,k):
    return math.factorial(n) // (math.factorial(k) * math.factorial(n-k))

# n_choose_k(5,2) + n_choose_k(5,3) + n_choose_k(5,4) + n_choose_k(5,5)
# 26
# n_choose_k(6,2)
# 15

# In [411]: test_score()
# [36, 3, 24, 23]
# 25
# hand JD, 4S, QH, JH
# draw KH
# pair 10 2 2
# special jack
# score 3


# ------------------------------------------------------------
# Cribbage Game

class CribbagePlayer(object):
    '''
    Abstract base class for an object that plays a game of cribbage.
    '''

    def __init__(self):
        '''Constructor.'''
        pass

    def discard(self, hand, is_dealer):
        '''
        Asks the player to select two cards from `hand` for discarding to
        the crib.

        Return is a list of two indices into the hand array.

        Arguments:
        - `hand`: an array of 6 card values
        - `is_dealer`: a flag to indicate whether the given player is
          currently the dealer or not
        '''
        raise NotImplementedError()

    def play_card(self, hand, is_dealer, own_pile, other_pile, linear_play, legal_moves):
        '''
        Asks the player to select one card from `hand` to play during a
        cribbage round.

        Return an index into the hand array.

        Arguments:
        - `hand`: an array of 1 to 4 card values
        - `is_dealer`: a flag to indicate whether the given player is
          currently the dealer or not
        - `own_pile`: an array of 0 to 3 card values that the given
          player has already played in this round
        - `other_pile`: an array of 0 to 4 card values that the
          player's opponent has played in this round
        - `linear_play`: the array of card values that have been
          played in this round by both players, zippered into a single
          list
        - `legal_moves`: a list of indices into `hand` indicating
          which cards from the hand may be played legally at this
          point in the game
        '''
        raise NotImplementedError()

class RandomCribbagePlayer(CribbagePlayer):
    '''A CribbagePlayer that always makes a random move.'''

    def __init__(self, ):
        '''Constructor.'''
        super(RandomCribbagePlayer, self).__init__()

    def discard(self, hand, is_dealer):
        '''
        Asks the player to select two cards from `hand` for discarding to
        the crib.

        Return is a list of two indices into the hand array.

        Arguments:
        - `hand`: an array of 6 card values
        - `is_dealer`: a flag to indicate whether the given player is
          currently the dealer or not
        '''
        return random.sample(range(6), 2)

    def play_card(self, hand, is_dealer, own_pile, other_pile, linear_play, legal_moves):
        '''
        Asks the player to select one card from `hand` to play during a
        cribbage round.

        Return an index into the hand array.

        Arguments:
        - `hand`: an array of 1 to 4 card values
        - `is_dealer`: a flag to indicate whether the given player is
          currently the dealer or not
        - `own_pile`: an array of 0 to 3 card values that the given
          player has already played in this round
        - `other_pile`: an array of 0 to 4 card values that the
          player's opponent has played in this round
        - `linear_play`: the array of card values that have been
          played in this round by both players, zippered into a single
          list
        - `legal_moves`: a list of indices into `hand` indicating
          which cards from the hand may be played legally at this
          point in the game
        '''
        return random.choice(legal_moves)

def card_score(card):
    '''
    Returns the number of points the given card value is worth.

    Arguments:
    - `card`:
    '''
    return CARD_VALUES[split_card(card)[0]]

def cards_score(cards):
    '''
    Calls `card_score` on every value in `cards` and returns the sum.

    Arguments:
    - `cards`:
    '''
    return sum(card_score(card) for card in cards)

def is_legal_play(card, linear_play):
    '''
    Tests whether the given card value is a legal play in a game of
    cribbage, assuming that the cards in `linear_play` have previously
    been played during the same round.

    Arguments:
    - `card`:
    - `linear_play`:
    '''
    lp_score = cards_score(linear_play)
    c_score = card_score(card)
    return lp_score + c_score <= 31

def score_play(linear_play, verbose=False):
    '''
    Scores the last play in a game.

    Arguments:
    - `linear_play`: a list containing the card values played, in
      order, during the round
    '''
    # the value we will return
    play_score = 0
    # we only need to know the face values thare played
    face_values = [split_card(c)[0] for c in linear_play]
    if verbose:
        print('Score', ' '.join(CARD_FACES[f] for f in face_values))
    if cards_score(linear_play) == 15:
        if verbose:
            print('fifteen')
        play_score +=2
    # look for pairs and tuples
    for backwards in range(len(linear_play), 1, -1):
        back_list = sorted(face_values[-backwards:])
        if len(set(back_list)) == 1:
            # pairlike
            pair_score = [2,6,12][len(back_list)-2]
            if verbose:
                print('pair {} points'.format(pair_score))
            play_score += pair_score
            break
        elif back_list == range(min(back_list), max(back_list) + 1):
            # run
            run_score = len(back_list)
            if run_score >= 3:
                if verbose:
                    print('run {} points'.format(run_score))
                play_score += run_score
                break
    return play_score

class Game(object):
    '''
    An object representing a game of cribbage between two
    CribbagePlayers.
    '''

    def __init__(self, players):
        '''
        Constructor.

        Arguments:
        - `players`: a list of two CribbagePlayer objects
        '''
        self.players = players
        # scores start at zero
        self.scores = [0, 0]
        # The players cut for first deal, and the person who cuts the
        # lowest card deals.
        # Randomly pick one player to start.
        self.dealer_idx = random.randrange(2)
        self.target_score = 121
        # a flag to cache the game state
        self.over = False
        # the deck of the current game round
        self.deck = None
        # the hands of the two players during the current game round
        self.hands = None
        # copy of self.hands
        self.orig_hands = None
        # the crib of the current game round
        self.crib = None
        # the card value of the "starter" or "cut" card for the
        # current game round
        self.starter_card = None
        # the player whose turn it currently is to play a card
        self.turn_idx = None
        # the array of cards that have been played already during the
        # current game round by the two players
        self.faceups = None
        # the same cards recorded in self.faceups, but linearised into
        # a single list, based on the time that the cards were played
        self.linear_play = None
        # during a round, we keep track of the last_player
        self.last_player = None
        # the cards played by both players during a round
        self.round_played = []

    def do_round(self, verbose=False):
        '''
        Executes a single round of cribbage.

            Play proceeds through a succession of "hands", each hand
            consisting of a "deal", "the play" and "the show." At any
            time during any of these stages, if a player reaches the
            target score (usually 121), play ends immediately with
            that player being the winner of the game. This can even
            happen during the deal, since the dealer can score if a
            Jack is cut as the starter.

        Returns True if the game is not over after the round; False if
        the game is over (or was already over before the round).

        Arguments:
        - `verbose`:
        '''
        if verbose:
            print('Starting new round')
        if self.deal_round(verbose=verbose):
            if self.play_round(verbose=verbose):
                return self.show_round(verbose=verbose)
        return False

    def deal_round(self, verbose=False):
        '''
        Shuffles and deals the cards for a single round of cribbage.
<commit_msg>cribbage.py: pylint mention more Game attrs in __init__<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function
from collections import Counter
import itertools
import math
import numpy as np
import random

# ------------------------------------------------------------
# Cards

# Cards are integers:

CARD_FACES = 'A234567890JQK'
CARD_SUITS = 'SHDC'
CARD_VALUES = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10]

def make_card(face, suit):
    return face + suit * 13

def split_card(vint):
    return vint % 13, vint // 13

def card_tostring(vint):
    v, s = split_card(vint)
    return '{}{}'.format(CARD_FACES[v], CARD_SUITS[s])

def make_deck():
    return range(52)

# ------------------------------------------------------------
# Cribbage scoring

def make_random_hand():
    return random.sample(make_deck(), 4)

def make_random_hand_and_draw():
    hd = random.sample(make_deck(), 5)
    return hd[:4], hd[4]

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return itertools.izip(a, b)

def score_hand(hand, draw=None, verbose=False):
    '''
    Scores a Cribbage hand.

    Arguments:
    - `hand`: a list of four card values
    - `draw`: a card value, or None
    '''
    assert len(hand) == 4
    score = 0
    # split card values
    split_values_hand = [split_card(c) for c in hand]
    split_values = split_values_hand[:]
    if draw is not None:
        draw_split = split_card(draw)
        _draw_face, draw_suit = draw_split
        split_values.append(draw_split)
    face_values = [f for (f,s) in split_values]
    # score runs
    #sorted_faces = sorted(face_values)
    face_counts = Counter(face_values)
    sorted_uniq_faces = sorted(face_counts.keys()) + [None]
    # a run is a contiguous
    run_begin = None
    for last_v, this_v in pairwise(sorted_uniq_faces):
        if this_v == last_v + 1:
            if run_begin is None:
                run_begin = last_v
            else:
                pass
        else:
            if run_begin is None:
                pass
            else:
                # finish a run
                run_length = last_v - run_begin + 1
                if run_length >= 3:
                    # interesting
                    run_score = np.product([face_counts[x] for x in
                                            range(run_begin, run_begin + run_length)]) * run_length
                    if verbose:
                        print('run', run_begin, last_v, run_score)
                    score += run_score
                run_begin = None
    # score pairs/triples/quads/etc.
    for _f, count in face_counts.items():
        # count = 2 -> 2
        # count = 3 -> 6
        # count = n -> 2 * (n CHOOSE 2)
        if count > 1:
            pair_score = math.factorial(count) / math.factorial(count - 2)
            if verbose:
                print('pair', _f, count, pair_score)
            score += pair_score
    # score 15s
    card_values = [CARD_VALUES[x] for x in face_values]
    for comb_len in [2,3,4,5]:
        for vlist in itertools.combinations(card_values, comb_len):
            if sum(vlist) == 15:
                if verbose:
                    print('fifteen')
                score += 2
    # score flush
    suit_values = set([s for (f,s) in split_values_hand])
    if len(suit_values) == 1:
        if draw is not None and list(suit_values)[0] == draw_suit:
            # draw 5
            if verbose:
                print('flush 5')
            score += len(hand) + 1
        else:
            if verbose:
                print('flush')
            score += len(hand)
    # score special jack
    if draw is not None and [f for (f,s) in split_values_hand if s == draw_suit and f == 10]:
        if verbose:
            print('special jack')
        score += 1
    if verbose:
        print('score', score)
    return score

def test_score():
    global hand, draw
    hand, draw = make_random_hand_and_draw()
    print(hand)
    print(draw)
    print('hand', ', '.join([card_tostring(v) for v in hand]))
    print('draw', card_tostring(draw))
    score_hand(hand, draw, verbose=True)

def n_choose_k(n,k):
    return math.factorial(n) // (math.factorial(k) * math.factorial(n-k))

# n_choose_k(5,2) + n_choose_k(5,3) + n_choose_k(5,4) + n_choose_k(5,5)
# 26
# n_choose_k(6,2)
# 15

# In [411]: test_score()
# [36, 3, 24, 23]
# 25
# hand JD, 4S, QH, JH
# draw KH
# pair 10 2 2
# special jack
# score 3


# ------------------------------------------------------------
# Cribbage Game

class CribbagePlayer(object):
    '''
    Abstract base class for an object that plays a game of cribbage.
    '''

    def __init__(self):
        '''Constructor.'''
        pass

    def discard(self, hand, is_dealer):
        '''
        Asks the player to select two cards from `hand` for discarding to
        the crib.

        Return is a list of two indices into the hand array.

        Arguments:
        - `hand`: an array of 6 card values
        - `is_dealer`: a flag to indicate whether the given player is
          currently the dealer or not
        '''
        raise NotImplementedError()

    def play_card(self, hand, is_dealer, own_pile, other_pile, linear_play, legal_moves):
        '''
        Asks the player to select one card from `hand` to play during a
        cribbage round.

        Return an index into the hand array.

        Arguments:
        - `hand`: an array of 1 to 4 card values
        - `is_dealer`: a flag to indicate whether the given player is
          currently the dealer or not
        - `own_pile`: an array of 0 to 3 card values that the given
          player has already played in this round
        - `other_pile`: an array of 0 to 4 card values that the
          player's opponent has played in this round
        - `linear_play`: the array of card values that have been
          played in this round by both players, zippered into a single
          list
        - `legal_moves`: a list of indices into `hand` indicating
          which cards from the hand may be played legally at this
          point in the game
        '''
        raise NotImplementedError()

class RandomCribbagePlayer(CribbagePlayer):
    '''A CribbagePlayer that always makes a random move.'''

    def __init__(self, ):
        '''Constructor.'''
        super(RandomCribbagePlayer, self).__init__()

    def discard(self, hand, is_dealer):
        '''
        Asks the player to select two cards from `hand` for discarding to
        the crib.

        Return is a list of two indices into the hand array.

        Arguments:
        - `hand`: an array of 6 card values
        - `is_dealer`: a flag to indicate whether the given player is
          currently the dealer or not
        '''
        return random.sample(range(6), 2)

    def play_card(self, hand, is_dealer, own_pile, other_pile, linear_play, legal_moves):
        '''
        Asks the player to select one card from `hand` to play during a
        cribbage round.

        Return an index into the hand array.

        Arguments:
        - `hand`: an array of 1 to 4 card values
        - `is_dealer`: a flag to indicate whether the given player is
          currently the dealer or not
        - `own_pile`: an array of 0 to 3 card values that the given
          player has already played in this round
        - `other_pile`: an array of 0 to 4 card values that the
          player's opponent has played in this round
        - `linear_play`: the array of card values that have been
          played in this round by both players, zippered into a single
          list
        - `legal_moves`: a list of indices into `hand` indicating
          which cards from the hand may be played legally at this
          point in the game
        '''
        return random.choice(legal_moves)

def card_score(card):
    '''
    Returns the number of points the given card value is worth.

    Arguments:
    - `card`:
    '''
    return CARD_VALUES[split_card(card)[0]]

def cards_score(cards):
    '''
    Calls `card_score` on every value in `cards` and returns the sum.

    Arguments:
    - `cards`:
    '''
    return sum(card_score(card) for card in cards)

def is_legal_play(card, linear_play):
    '''
    Tests whether the given card value is a legal play in a game of
    cribbage, assuming that the cards in `linear_play` have previously
    been played during the same round.

    Arguments:
    - `card`:
    - `linear_play`:
    '''
    lp_score = cards_score(linear_play)
    c_score = card_score(card)
    return lp_score + c_score <= 31

def score_play(linear_play, verbose=False):
    '''
    Scores the last play in a game.

    Arguments:
    - `linear_play`: a list containing the card values played, in
      order, during the round
    '''
    # the value we will return
    play_score = 0
    # we only need to know the face values thare played
    face_values = [split_card(c)[0] for c in linear_play]
    if verbose:
        print('Score', ' '.join(CARD_FACES[f] for f in face_values))
    if cards_score(linear_play) == 15:
        if verbose:
            print('fifteen')
        play_score +=2
    # look for pairs and tuples
    for backwards in range(len(linear_play), 1, -1):
        back_list = sorted(face_values[-backwards:])
        if len(set(back_list)) == 1:
            # pairlike
            pair_score = [2,6,12][len(back_list)-2]
            if verbose:
                print('pair {} points'.format(pair_score))
            play_score += pair_score
            break
        elif back_list == range(min(back_list), max(back_list) + 1):
            # run
            run_score = len(back_list)
            if run_score >= 3:
                if verbose:
                    print('run {} points'.format(run_score))
                play_score += run_score
                break
    return play_score

class Game(object):
    '''
    An object representing a game of cribbage between two
    CribbagePlayers.
    '''

    def __init__(self, players):
        '''
        Constructor.

        Arguments:
        - `players`: a list of two CribbagePlayer objects
        '''
        self.players = players
        # scores start at zero
        self.scores = [0, 0]
        # The players cut for first deal, and the person who cuts the
        # lowest card deals.
        # Randomly pick one player to start.
        self.dealer_idx = random.randrange(2)
        self.target_score = 121
        # a flag to cache the game state
        self.over = False
        # the deck of the current game round
        self.deck = None
        # the hands of the two players during the current game round
        self.hands = None
        # copy of self.hands
        self.orig_hands = None
        # the crib of the current game round
        self.crib = None
        # the card value of the "starter" or "cut" card for the
        # current game round
        self.starter_card = None
        # the player whose turn it currently is to play a card
        self.turn_idx = None
        # the array of cards that have been played already during the
        # current game round by the two players
        self.faceups = None
        # the same cards recorded in self.faceups, but linearised into
        # a single list, based on the time that the cards were played
        self.linear_play = None
        # during a round, we keep track of the last_player
        self.last_player = None
        # the cards played by both players during a round
        self.round_played = []
        # a flag that indicates whether the current sequence of the
        # current game round has gone to "Go" or not
        self.is_go = False
        # a flag that indicates whether the last player hit 31 or not
        # during the current sequence of the current game round
        self.flag_31 = False

    def do_round(self, verbose=False):
        '''
        Executes a single round of cribbage.

            Play proceeds through a succession of "hands", each hand
            consisting of a "deal", "the play" and "the show." At any
            time during any of these stages, if a player reaches the
            target score (usually 121), play ends immediately with
            that player being the winner of the game. This can even
            happen during the deal, since the dealer can score if a
            Jack is cut as the starter.

        Returns True if the game is not over after the round; False if
        the game is over (or was already over before the round).

        Arguments:
        - `verbose`:
        '''
        if verbose:
            print('Starting new round')
        if self.deal_round(verbose=verbose):
            if self.play_round(verbose=verbose):
                return self.show_round(verbose=verbose)
        return False

    def deal_round(self, verbose=False):
        '''
        Shuffles and deals the cards for a single round of cribbage.

{'fraction_non_alphanumeric': 0.09835171258231695, 'fraction_numerical': 0.011845848108171297, 'mean_word_length': 1.9461600275513717, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '558205', 'n_tokens_mistral': 7842, 'n_tokens_neox': 7313, 'n_words': 2735}