<commit_before>###########################################################################
# This object is used by peuchre to keep track of various stats of the
# hands.  We create one record item, and pass it to the player objects
# when it's created, and then the player object will call methods on the
# record object at various points.  At the moment, the Record object records
# these stats:
#  - remapped calling hand, hand result
#  - trick, % of hand can be used to follow
#  - number of hands
#  - number of tricks
#  - % of total possible calling hands
#  - some time stamps to compute hands/s

import os
import time
import sys
import curses

from card import Card
from collections import namedtuple

class Record: 

    ########################################################################### 
    # This initializes the object
    #
    def __init__(self,**kwargs):
        # records the time of the first submitted hand, in seconds
        self.start = time.time()

        # track total number of games, hands, and euchres
        self.counts = namedtuple('Counts',
            ['games','hands','orders','calls','euchres'])
        self.counts.games = 0
        self.counts.hands = 0
        self.counts.orders = 0
        self.counts.calls = 0
        self.counts.euchres = 0

        # a dict to track makers
        self.makers = namedtuple('Makers',
            ['team', 'player', 'team1pos', 'team2pos'])
        self.makers.team = [0,0]
        self.makers.player = [0,0,0,0]
        self.makers.team1pos = [0,0,0,0]
        self.makers.team2pos = [0,0,0,0]

        # a dict to track orderers
        self.orderers = namedtuple('Orderers',
            ['team', 'player', 'team1pos', 'team2pos'])
        self.orderers.team = [0,0]
        self.orderers.player = [0,0,0,0]
        self.orderers.team1pos = [0,0,0,0]
        self.orderers.team2pos = [0,0,0,0]

        # a dict to track callers
        self.callers = namedtuple('Callers',
            ['team', 'player', 'team1pos', 'team2pos'])
        self.callers.team = [0,0]
        self.callers.player = [0,0,0,0]
        self.callers.team1pos = [0,0,0,0]
        self.callers.team2pos = [0,0,0,0]

        # a dict to track euchres
        self.euchres=namedtuple('Euchres',
            ['team', 'player', 'team1pos', 'team2pos', 'team1hole','team2hole'])
        self.euchres.team = [0,0]
        self.euchres.player = [0,0,0,0]
        self.euchres.team1pos = [0,0,0,0]
        self.euchres.team2pos = [0,0,0,0]
        self.euchres.team1hole = [0,0,0,0,0,0]
        self.euchres.team2hole = [0,0,0,0,0,0]

        # set up the call hand stats dict
        self.chand  = {}
        self.ccount = 0
        self.cmax   = 0

        # set up the follow stats dict
        self.follow    = {}
        self.follow[1] = {}
        self.follow[2] = {}
        self.follow[3] = {}
        self.follow[4] = {}
        self.follow[5] = {}

        # initialize the lastwrite time to 0
        self.lastwrite = 0

        # if we were passed the name of the team1 and team2 algorithms,
        # store them, otherwise default to "unknown"
        self.team1 = "unknown"
        if "team1" in kwargs:
            self.team1 = kwargs['team1']
        self.team2 = "unknown"
        if "team2" in kwargs:
            self.team2 = kwargs['team2']

        # if we were passed stats, init the object value
        self.stats = False
        if "stats" in kwargs:
            self.stats = kwargs['stats']

        # if stats is enabled, initialize curses, so we can have a bit more
        # control of our output
        if self.stats:
            # initialize curses and get current screen size
            curses.initscr()
            width = curses.COLS
            height = curses.LINES

            # if the screen isn't at least 90x30, curses will abort, so check
            if width < 90 or height < 30:
                sys.stderr.write("\n")
                sys.stderr.write("Screen must be at least 90x30 to show stats")
                sys.stderr.write("\n")
                sys.stderr.write("\n")
                sys.exit(1)

            # these are some variables to make adjusting the col sizes easier
            headerx = width ; headery = 3
            col1x   = 25    ; col1y   = 24
            col2x   = 65    ; col2y   = 24
            footerx = width ; footery = 3

            # make the curses calls to create the windows
            self.header = curses.newwin(headery,headerx,0,0)
            self.col1   = curses.newwin(col1y,col1x,headery,0)
            self.col2   = curses.newwin(col2y,col2x,headery,col1x)
            self.footer = curses.newwin(footery,footerx,headery+col1y,0)


    ###########################################################################
    # This routine un-inits the curses interface (if enabled) so that we
    # don't leave the screen borked.
    #
    def __del__(self):
        if self.stats:
            curses.endwin()  


    ###########################################################################
    # This routine takes two numbers, x and y, and returns x as a percent
    # of y, accurate to 2 decimal points.  If y is not > 0, then it returns
    # 0.  The return value is a floating point number, so it can be used
    # in further mathematical operations, or directly printed.
    #
    def p(self, x, y):
        if y > 0:
            return int(10000*x/y)/100
        return 0.00


    ###########################################################################
    # This tracks overall game counts
    #
    def addGame(self):
        self.counts.games += 1

        # write if it's time to
        self.write()


    ########################################################################### 
    # This takes a list of cards, a trump card, a score, and the calling player
    # object.  It remaps the hand according to the trump suit, and then stores
    # the hand along with the relative score (which will be either -4, -2, 1,
    # 2, or 4, depending).  It then stores the result in a list, so we can
    # compute averages, histograms, sdev, etc.
    # 
    # We return the remapped string as a convenience: the player will print
    # this in the log, which will allow us to track back the details on a
    # specific result
    #
    def addHand(self, hand, trump, score, player):
        # track overall hand information
        self.counts.hands += 1

        # this computes the player position index: 0 is the first person
        # after the dealer, 1 is the dealer's partner, 2 is the next person,
        # 3 is the dealer; this allows aggregation of stats based on
        # position relative to the dealer
        pos = player.playerhandle - (player.state['dealer'] + 1)
        if pos < 0: pos += 4

        # track the player, team, and position that makes it
        self.makers.team[player.team - 1] += 1
        self.makers.player[player.playerhandle] += 1
        if player.team == 1:
            self.makers.team1pos[pos] += 1
        else:
            self.makers.team2pos[pos] += 1

        # if it was an order or a call, track that separately: the player
        # that calls addHand() is the maker, so we just need to check if
        # the orderer flag is set for this player (strictly, we could compute
        # the call stats by subtracting the orders from the makes, but it's
        # simpler and clearer this way)
        if player.state['orderer'] == player.playerhandle:
            self.counts.orders += 1
            self.orderers.team[player.team - 1] += 1
            self.orderers.player[player.playerhandle] += 1
            if player.team == 1:
                self.orderers.team1pos[pos] += 1
            else:
                self.orderers.team2pos[pos] += 1
        else:
            self.counts.calls += 1
            self.callers.team[player.team - 1] += 1
            self.callers.player[player.playerhandle] += 1
            if player.team == 1:
                self.callers.team1pos[pos] += 1
            else:
                self.callers.team2pos[pos] += 1

        # if the score is negative, then it was a euchre, so track those stats
        if score < 0:
            self.counts.euchres += 1
            self.euchres.team[player.team - 1] += 1
            self.euchres.player[player.playerhandle] += 1
            if player.team == 1:
                self.euchres.team1pos[pos] += 1
            else:
                self.euchres.team2pos[pos] += 1

            # if this was a euchre on an order (but not a self order, ie. the
            # ordered was not also the dealer), we track the %euchre by
            # team and hole card: map the hole card to an index and store
            # the count in a team-specific tuple
            if player.state['orderer'] == player.playerhandle and \
               player.state['dealer'] != player.playerhandle:
                # only one of 6 cards can be ordered: the 9, 10, J, Q, K, A
                # (the left can't be ordered since it would be the right)
                v = player.state['hole'].value
                if player.team == 1:
                    if v ==  9: self.euchres.team1hole[0] += 1  # 9
                    if v == 10: self.euchres.team1hole[1] += 1  # T
                    if v == 12: self.euchres.team1hole[2] += 1  # Q
                    if v == 13: self.euchres.team1hole[3] += 1  # K
                    if v == 14: self.euchres.team1hole[4] += 1  # A
                    if v == 11: self.euchres.team1hole[5] += 1  # J
                else:
                    if v ==  9: self.euchres.team2hole[0] += 1  # 9
                    if v == 10: self.euchres.team2hole[1] += 1  # T
                    if v == 12: self.euchres.team2hole[2] += 1  # Q
                    if v == 13: self.euchres.team2hole[3] += 1  # K
                    if v == 14: self.euchres.team2hole[4] += 1  # A
                    if v == 11: self.euchres.team2hole[5] += 1  # J

        # remap the hand by calling remap(hand,trump): this returns a string
        # representation of the hand which is independent of the specific
        # trump suit
        remap = self.remap(hand,trump)

        # now use the remap string to index into the hand dict, and store
        # the score result; if the index for this remap doesn't exist,
        # make
        if remap not in self.chand:
            self.chand[remap] = {}
            self.chand[remap]['count'] = 0
            self.chand[remap]['sum'] = 0
            self.chand[remap]['scores'] = list([])

        # store the passed in information
        self.chand[remap]['count'] += 1
        self.chand[remap]['sum']   += score
        self.chand[remap]['scores'].append(score)

        # track the maximum number of repeats we've seen
        if self.chand[remap]['count'] > self.cmax:
            self.cmax = self.chand[remap]['count']

        # keep track of the total hands processed
        self.ccount += 1

        # write if it's time to
        self.write()

        # return the remap string so the player can log it
        return remap


    ###########################################################################
    # This tracks follow stats: it takes the number of cards in the hand
    # and the number of cards that are playable, and computes and stores
    # the running % of playable cards in each trick
    #
    def addFollow(self,hand,playable):
        # the number of cards in the hand determines the trick:
        #  5 cards - trick 1
        #  4 cards - trick 2
        # etc...
        trick = 6 - hand
    
        # compute the % playable by playable/hand
        ratio = playable/hand

        # initialize some elements
        if 'sum' not in self.follow[trick]:
            self.follow[trick]['sum'] = 0
        if 'count' not in self.follow[trick]:
            self.follow[trick]['count'] = 0

        # and store the data
        self.follow[trick]['sum']   += ratio
        self.follow[trick]['count'] += 1

        # write if it's time to
        self.write()


    ########################################################################### 
    # This prints out a screen full of information related to the records
    # object
    #
    def printCDetails(self):
        # print the details of the chand dict
        print("hand          count scores")
        for remap in self.chand:
            print("%s: %8d " % (remap,self.chand[remap]['count'])
                + str(self.chand[remap]['scores']))


    ########################################################################### 
    # This prints some high level info for the record using curses
    #
    def print(self,**kwargs):
        # clear the screen to start
        if 'clear' not in kwargs \
           or ('clear' in kwargs and kwargs['clear'] != False ):
            #os.system('clear')
            #print("[H[2J")
            self.header.erase()
            self.col1.erase()
            self.col2.erase()
            self.footer.erase()

        # pre-calculate some data

        # compute the run time
        t = time.gmtime(time.time() - self.start)
        runtime = "%dd %02d:%02d:%02d" \
            % (t.tm_mday-1,t.tm_hour,t.tm_min,t.tm_sec)

        # compute hands per second and hands per game
        hps = 0
        if self.start != time.time():
            hps = self.counts.hands / (time.time() - self.start)
        hpg = 0
        if self.counts.games > 0:
            hpg = self.counts.hands / self.counts.games

        # compute the % coverage of remapped hands
        avg = 0
        numunique = len(self.chand)
        if numunique > 0:
            avg = self.ccount / numunique

        # compute the games per second
        gps = 0
        if self.start != time.time():
            gps = self.counts.games / (time.time() - self.start)

        # print the data

        # print the screen header
        self.header.addstr("\n")
        self.header.addstr("Peuchre Stats ( %s )   Team 1: %s   Team 2: %s"
            % (runtime,self.team1,self.team2))

        # print the game stats
        self.col1.addstr("Games\n")
        self.col1.addstr("  Total   : %5d\n" % (self.counts.games))
        self.col1.addstr("  Games/s :   %6.2f\n" % (gps))
        self.col1.addstr("\n")

        # print the basic hand data
        self.col1.addstr("Hands\n")
        self.col1.addstr("  Total   : %5d\n" % ( self.counts.hands, ) )
        self.col1.addstr("  Hands/s :   %6.2f\n" % ( hps, ) )
        self.col1.addstr("  Hands/g :   %6.2f\n" % ( hpg, ) )
        self.col1.addstr("  Unique  : %5d\n" % ( numunique, ) )
        self.col1.addstr("  %%cover  :   %6.2f\n" % ( 100*numunique/10422 ) )
        self.col1.addstr("  Max Reps: %5d\n" % ( self.cmax, ) )
        self.col1.addstr("  Avg Reps:   %6.2f\n" % ( avg, ) )

        # print the make stats
        self.col2.addstr("Makes\n")
        self.col2.addstr("  Order/Call: %5.2f / %5.2f\n"
            % ( self.p(self.counts.orders, self.counts.hands),
                self.p(self.counts.calls, self.counts.hands)
            ))

        # print the order stats
        self.col2.addstr("\n")
        self.col2.addstr("Orders\n")
        self.col2.addstr("  %%by team   : %5.2f / %5.2f\n"
            % ( self.p(self.orderers.team[0], self.counts.orders),
                self.p(self.orderers.team[1], self.counts.orders)
            ))

        self.col2.addstr("  %%by player : %5.2f /%6.2f /%6.2f /%6.2f\n"
            % ( self.p(self.orderers.player[0], self.counts.orders),
                self.p(self.orderers.player[1], self.counts.orders),
                self.p(self.orderers.player[2], self.counts.orders),
                self.p(self.orderers.player[3], self.counts.orders),
            ))

        self.col2.addstr("  %%by pos t1 : %5.2f / %5.2f / %5.2f / %5.2f\n"
            % ( self.p(self.orderers.team1pos[0],self.orderers.team[0]),
                self.p(self.orderers.team1pos[1],self.orderers.team[0]),
                self.p(self.orderers.team1pos[2],self.orderers.team[0]),
                self.p(self.orderers.team1pos[3],self.orderers.team[0]),
            ))

        self.col2.addstr("          t2 : %5.2f / %5.2f / %5.2f / %5.2f\n"
            % ( self.p(self.orderers.team2pos[0],self.orderers.team[1]),
                self.p(self.orderers.team2pos[1],self.orderers.team[1]),
                self.p(self.orderers.team2pos[2],self.orderers.team[1]),
                self.p(self.orderers.team2pos[3],self.orderers.team[1]),
            ))

        # print the call stats
        self.col2.addstr("\n")
        self.col2.addstr("Calls\n")
        self.col2.addstr("  %%by team   : %5.2f / %5.2f\n"
            % ( self.p(self.callers.team[0], self.counts.hands),
                self.p(self.callers.team[1], self.counts.hands)
            ))

        self.col2.addstr("  %%by player : %5.2f /%6.2f /%6.2f /%6.2f\n"
            % ( self.p(self.callers.player[0], self.counts.hands),
                self.p(self.callers.player[1], self.counts.hands),
                self.p(self.callers.player[2], self.counts.hands),
                self.p(self.callers.player[3], self.counts.hands),
            ))

        self.col2.addstr("  %%by pos t1 : %5.2f / %5.2f / %5.2f / %5.2f\n"
            % ( self.p(self.callers.team1pos[0],self.callers.team[0]),
                self.p(self.callers.team1pos[1],self.callers.team[0]),
                self.p(self.callers.team1pos[2],self.callers.team[0]),
                self.p(self.callers.team1pos[3],self.callers.team[0]),
            ))

        self.col2.addstr("          t2 : %5.2f / %5.2f / %5.2f / %5.2f\n"
            % ( self.p(self.callers.team2pos[0],self.callers.team[1]),
                self.p(self.callers.team2pos[1],self.callers.team[1]),
                self.p(self.callers.team2pos[2],self.callers.team[1]),
                self.p(self.callers.team2pos[3],self.callers.team[1]),
            ))

        # print the euchre stats
        self.col2.addstr("\n")
        self.col2.addstr("Euchres\n")

        self.col2.addstr("  %%euchred   : %5.2f\n"
            % ( self.p(self.counts.euchres,self.counts.hands)) )

        self.col2.addstr("  %%by team   : %5.2f / %5.2f\n"
            % ( self.p(self.euchres.team[0],self.makers.team[0]),
                self.p(self.euchres.team[1],self.makers.team[1]),<commit_msg>Minor adjustments to stats output<commit_after>###########################################################################
# This object is used by peuchre to keep track of various stats of the
# hands.  We create one record item, and pass it to the player objects
# when it's created, and then the player object will call methods on the
# record object at various points.  At the moment, the Record object records
# these stats:
#  - remapped calling hand, hand result
#  - trick, % of hand can be used to follow
#  - number of hands
#  - number of tricks
#  - % of total possible calling hands
#  - some time stamps to compute hands/s

import os
import time
import sys
import curses

from card import Card
from collections import namedtuple

class Record: 

    ########################################################################### 
    # This initializes the object
    #
    def __init__(self,**kwargs):
        # records the time of the first submitted hand, in seconds
        self.start = time.time()

        # track total number of games, hands, and euchres
        self.counts = namedtuple('Counts',
            ['games','hands','orders','calls','euchres'])
        self.counts.games = 0
        self.counts.hands = 0
        self.counts.orders = 0
        self.counts.calls = 0
        self.counts.euchres = 0

        # a dict to track makers
        self.makers = namedtuple('Makers',
            ['team', 'player', 'team1pos', 'team2pos'])
        self.makers.team = [0,0]
        self.makers.player = [0,0,0,0]
        self.makers.team1pos = [0,0,0,0]
        self.makers.team2pos = [0,0,0,0]

        # a dict to track orderers
        self.orderers = namedtuple('Orderers',
            ['team', 'player', 'team1pos', 'team2pos'])
        self.orderers.team = [0,0]
        self.orderers.player = [0,0,0,0]
        self.orderers.team1pos = [0,0,0,0]
        self.orderers.team2pos = [0,0,0,0]

        # a dict to track callers
        self.callers = namedtuple('Callers',
            ['team', 'player', 'team1pos', 'team2pos'])
        self.callers.team = [0,0]
        self.callers.player = [0,0,0,0]
        self.callers.team1pos = [0,0,0,0]
        self.callers.team2pos = [0,0,0,0]

        # a dict to track euchres
        self.euchres=namedtuple('Euchres',
            ['team', 'player', 'team1pos', 'team2pos', 'team1hole','team2hole'])
        self.euchres.team = [0,0]
        self.euchres.player = [0,0,0,0]
        self.euchres.team1pos = [0,0,0,0]
        self.euchres.team2pos = [0,0,0,0]
        self.euchres.team1hole = [0,0,0,0,0,0]
        self.euchres.team2hole = [0,0,0,0,0,0]

        # set up the call hand stats dict
        self.chand  = {}
        self.ccount = 0
        self.cmax   = 0

        # set up the follow stats dict
        self.follow    = {}
        self.follow[1] = {}
        self.follow[2] = {}
        self.follow[3] = {}
        self.follow[4] = {}
        self.follow[5] = {}

        # initialize the lastwrite time to 0
        self.lastwrite = 0

        # if we were passed the name of the team1 and team2 algorithms,
        # store them, otherwise default to "unknown"
        self.team1 = "unknown"
        if "team1" in kwargs:
            self.team1 = kwargs['team1']
        self.team2 = "unknown"
        if "team2" in kwargs:
            self.team2 = kwargs['team2']

        # if we were passed stats, init the object value
        self.stats = False
        if "stats" in kwargs:
            self.stats = kwargs['stats']

        # if stats is enabled, initialize curses, so we can have a bit more
        # control of our output
        if self.stats:
            # initialize curses and get current screen size
            curses.initscr()
            width = curses.COLS
            height = curses.LINES

            # if the screen isn't at least 90x30, curses will abort, so check
            if width < 90 or height < 30:
                sys.stderr.write("\n")
                sys.stderr.write("Screen must be at least 90x30 to show stats")
                sys.stderr.write("\n")
                sys.stderr.write("\n")
                sys.exit(1)

            # these are some variables to make adjusting the col sizes easier
            headerx = width ; headery = 3
            col1x   = 25    ; col1y   = 24
            col2x   = 65    ; col2y   = 24
            footerx = width ; footery = 3

            # make the curses calls to create the windows
            self.header = curses.newwin(headery,headerx,0,0)
            self.col1   = curses.newwin(col1y,col1x,headery,0)
            self.col2   = curses.newwin(col2y,col2x,headery,col1x)
            self.footer = curses.newwin(footery,footerx,headery+col1y,0)


    ###########################################################################
    # This routine un-inits the curses interface (if enabled) so that we
    # don't leave the screen borked.
    #
    def __del__(self):
        if self.stats:
            curses.endwin()  


    ###########################################################################
    # This routine takes two numbers, x and y, and returns x as a percent
    # of y, accurate to 2 decimal points.  If y is not > 0, then it returns
    # 0.  The return value is a floating point number, so it can be used
    # in further mathematical operations, or directly printed.
    #
    def p(self, x, y):
        if y > 0:
            return int(10000*x/y)/100
        return 0.00


    ###########################################################################
    # This tracks overall game counts
    #
    def addGame(self):
        self.counts.games += 1

        # write if it's time to
        self.write()


    ########################################################################### 
    # This takes a list of cards, a trump card, a score, and the calling player
    # object.  It remaps the hand according to the trump suit, and then stores
    # the hand along with the relative score (which will be either -4, -2, 1,
    # 2, or 4, depending).  It then stores the result in a list, so we can
    # compute averages, histograms, sdev, etc.
    # 
    # We return the remapped string as a convenience: the player will print
    # this in the log, which will allow us to track back the details on a
    # specific result
    #
    def addHand(self, hand, trump, score, player):
        # track overall hand information
        self.counts.hands += 1

        # this computes the player position index: 0 is the first person
        # after the dealer, 1 is the dealer's partner, 2 is the next person,
        # 3 is the dealer; this allows aggregation of stats based on
        # position relative to the dealer
        pos = player.playerhandle - (player.state['dealer'] + 1)
        if pos < 0: pos += 4

        # track the player, team, and position that makes it
        self.makers.team[player.team - 1] += 1
        self.makers.player[player.playerhandle] += 1
        if player.team == 1:
            self.makers.team1pos[pos] += 1
        else:
            self.makers.team2pos[pos] += 1

        # if it was an order or a call, track that separately: the player
        # that calls addHand() is the maker, so we just need to check if
        # the orderer flag is set for this player (strictly, we could compute
        # the call stats by subtracting the orders from the makes, but it's
        # simpler and clearer this way)
        if player.state['orderer'] == player.playerhandle:
            self.counts.orders += 1
            self.orderers.team[player.team - 1] += 1
            self.orderers.player[player.playerhandle] += 1
            if player.team == 1:
                self.orderers.team1pos[pos] += 1
            else:
                self.orderers.team2pos[pos] += 1
        else:
            self.counts.calls += 1
            self.callers.team[player.team - 1] += 1
            self.callers.player[player.playerhandle] += 1
            if player.team == 1:
                self.callers.team1pos[pos] += 1
            else:
                self.callers.team2pos[pos] += 1

        # if the score is negative, then it was a euchre, so track those stats
        if score < 0:
            self.counts.euchres += 1
            self.euchres.team[player.team - 1] += 1
            self.euchres.player[player.playerhandle] += 1
            if player.team == 1:
                self.euchres.team1pos[pos] += 1
            else:
                self.euchres.team2pos[pos] += 1

            # if this was a euchre on an order (but not a self order, ie. the
            # ordered was not also the dealer), we track the %euchre by
            # team and hole card: map the hole card to an index and store
            # the count in a team-specific tuple
            if player.state['orderer'] == player.playerhandle and \
               player.state['dealer'] != player.playerhandle:
                # only one of 6 cards can be ordered: the 9, 10, J, Q, K, A
                # (the left can't be ordered since it would be the right)
                v = player.state['hole'].value
                if player.team == 1:
                    if v ==  9: self.euchres.team1hole[0] += 1  # 9
                    if v == 10: self.euchres.team1hole[1] += 1  # T
                    if v == 12: self.euchres.team1hole[2] += 1  # Q
                    if v == 13: self.euchres.team1hole[3] += 1  # K
                    if v == 14: self.euchres.team1hole[4] += 1  # A
                    if v == 11: self.euchres.team1hole[5] += 1  # J
                else:
                    if v ==  9: self.euchres.team2hole[0] += 1  # 9
                    if v == 10: self.euchres.team2hole[1] += 1  # T
                    if v == 12: self.euchres.team2hole[2] += 1  # Q
                    if v == 13: self.euchres.team2hole[3] += 1  # K
                    if v == 14: self.euchres.team2hole[4] += 1  # A
                    if v == 11: self.euchres.team2hole[5] += 1  # J

        # remap the hand by calling remap(hand,trump): this returns a string
        # representation of the hand which is independent of the specific
        # trump suit
        remap = self.remap(hand,trump)

        # now use the remap string to index into the hand dict, and store
        # the score result; if the index for this remap doesn't exist,
        # make
        if remap not in self.chand:
            self.chand[remap] = {}
            self.chand[remap]['count'] = 0
            self.chand[remap]['sum'] = 0
            self.chand[remap]['scores'] = list([])

        # store the passed in information
        self.chand[remap]['count'] += 1
        self.chand[remap]['sum']   += score
        self.chand[remap]['scores'].append(score)

        # track the maximum number of repeats we've seen
        if self.chand[remap]['count'] > self.cmax:
            self.cmax = self.chand[remap]['count']

        # keep track of the total hands processed
        self.ccount += 1

        # write if it's time to
        self.write()

        # return the remap string so the player can log it
        return remap


    ###########################################################################
    # This tracks follow stats: it takes the number of cards in the hand
    # and the number of cards that are playable, and computes and stores
    # the running % of playable cards in each trick
    #
    def addFollow(self,hand,playable):
        # the number of cards in the hand determines the trick:
        #  5 cards - trick 1
        #  4 cards - trick 2
        # etc...
        trick = 6 - hand
    
        # compute the % playable by playable/hand
        ratio = playable/hand

        # initialize some elements
        if 'sum' not in self.follow[trick]:
            self.follow[trick]['sum'] = 0
        if 'count' not in self.follow[trick]:
            self.follow[trick]['count'] = 0

        # and store the data
        self.follow[trick]['sum']   += ratio
        self.follow[trick]['count'] += 1

        # write if it's time to
        self.write()


    ########################################################################### 
    # This prints out a screen full of information related to the records
    # object
    #
    def printCDetails(self):
        # print the details of the chand dict
        print("hand          count scores")
        for remap in self.chand:
            print("%s: %8d " % (remap,self.chand[remap]['count'])
                + str(self.chand[remap]['scores']))


    ########################################################################### 
    # This prints some high level info for the record using curses
    #
    def print(self,**kwargs):
        # clear the screen to start
        if 'clear' not in kwargs \
           or ('clear' in kwargs and kwargs['clear'] != False ):
            #os.system('clear')
            #print("[H[2J")
            self.header.erase()
            self.col1.erase()
            self.col2.erase()
            self.footer.erase()

        # pre-calculate some data

        # compute the run time
        t = time.gmtime(time.time() - self.start)
        runtime = "%dd %02d:%02d:%02d" \
            % (t.tm_mday-1,t.tm_hour,t.tm_min,t.tm_sec)

        # compute hands per second and hands per game
        hps = 0
        if self.start != time.time():
            hps = self.counts.hands / (time.time() - self.start)
        hpg = 0
        if self.counts.games > 0:
            hpg = self.counts.hands / self.counts.games

        # compute the % coverage of remapped hands
        avg = 0
        numunique = len(self.chand)
        if numunique > 0:
            avg = self.ccount / numunique

        # compute the games per second
        gps = 0
        if self.start != time.time():
            gps = self.counts.games / (time.time() - self.start)

        # print the data

        # print the screen header
        self.header.addstr("\n")
        self.header.addstr("Peuchre Stats ( %s )   Team 1: %s   Team 2: %s"
            % (runtime,self.team1,self.team2))

        # print the game stats
        self.col1.addstr("Games\n")
        self.col1.addstr("  Total   : %5d\n" % (self.counts.games))
        self.col1.addstr("  Games/s :   %6.2f\n" % (gps))
        self.col1.addstr("\n")

        # print the basic hand data
        self.col1.addstr("Hands\n")
        self.col1.addstr("  Total   : %5d\n" % ( self.counts.hands, ) )
        self.col1.addstr("  Hands/s :   %6.2f\n" % ( hps, ) )
        self.col1.addstr("  Hands/g :   %6.2f\n" % ( hpg, ) )
        self.col1.addstr("  Unique  : %5d\n" % ( numunique, ) )
        self.col1.addstr("  %%cover  :   %6.2f\n" % ( 100*numunique/10422 ) )
        self.col1.addstr("  Max Reps: %5d\n" % ( self.cmax, ) )
        self.col1.addstr("  Avg Reps:   %6.2f\n" % ( avg, ) )

        # print the make stats
        self.col2.addstr("Makes\n")
        self.col2.addstr("  Order/Call : %5.2f / %5.2f\n"
            % ( self.p(self.counts.orders, self.counts.hands),
                self.p(self.counts.calls, self.counts.hands)
            ))

        # print the order stats
        self.col2.addstr("\n")
        self.col2.addstr("Orders\n")
        self.col2.addstr("  %%by team   : %5.2f / %5.2f\n"
            % ( self.p(self.orderers.team[0], self.counts.orders),
                self.p(self.orderers.team[1], self.counts.orders)
            ))

        self.col2.addstr("  %%by player : %5.2f /%6.2f /%6.2f /%6.2f\n"
            % ( self.p(self.orderers.player[0], self.counts.orders),
                self.p(self.orderers.player[1], self.counts.orders),
                self.p(self.orderers.player[2], self.counts.orders),
                self.p(self.orderers.player[3], self.counts.orders),
            ))

        self.col2.addstr("  %%by pos t1 : %5.2f / %5.2f / %5.2f / %5.2f\n"
            % ( self.p(self.orderers.team1pos[0],self.counts.orders),
                self.p(self.orderers.team1pos[1],self.counts.orders),
                self.p(self.orderers.team1pos[2],self.counts.orders),
                self.p(self.orderers.team1pos[3],self.counts.orders),
            ))

        self.col2.addstr("          t2 : %5.2f / %5.2f / %5.2f / %5.2f\n"
            % ( self.p(self.orderers.team2pos[0],self.counts.orders),
                self.p(self.orderers.team2pos[1],self.counts.orders),
                self.p(self.orderers.team2pos[2],self.counts.orders),
                self.p(self.orderers.team2pos[3],self.counts.orders),
            ))

        # print the call stats
        self.col2.addstr("\n")
        self.col2.addstr("Calls\n")
        self.col2.addstr("  %%by team   : %5.2f / %5.2f\n"
            % ( self.p(self.callers.team[0], self.counts.calls),
                self.p(self.callers.team[1], self.counts.calls)
            ))

        self.col2.addstr("  %%by player : %5.2f /%6.2f /%6.2f /%6.2f\n"
            % ( self.p(self.callers.player[0], self.counts.calls),
                self.p(self.callers.player[1], self.counts.calls),
                self.p(self.callers.player[2], self.counts.calls),
                self.p(self.callers.player[3], self.counts.calls),
            ))

        self.col2.addstr("  %%by pos t1 : %5.2f / %5.2f / %5.2f / %5.2f\n"
            % ( self.p(self.callers.team1pos[0],self.counts.calls),
                self.p(self.callers.team1pos[1],self.counts.calls),
                self.p(self.callers.team1pos[2],self.counts.calls),
                self.p(self.callers.team1pos[3],self.counts.calls),
            ))

        self.col2.addstr("          t2 : %5.2f / %5.2f / %5.2f / %5.2f\n"
            % ( self.p(self.callers.team2pos[0],self.counts.calls),
                self.p(self.callers.team2pos[1],self.counts.calls),
                self.p(self.callers.team2pos[2],self.counts.calls),
                self.p(self.callers.team2pos[3],self.counts.calls),
            ))

        # print the euchre stats
        self.col2.addstr("\n")
        self.col2.addstr("Euchres\n")

        self.col2.addstr("  %%euchred   : %5.2f\n"
            % ( self.p(self.counts.euchres,self.counts.hands)) )

        self.col2.addstr("  %%by team   : %5.2f / %5.2f\n"
            % ( self.p(self.euchres.team[0],self.makers.team[0]),
                self.p(self.euchres.team[1],self.makers.team[1]),
{'fraction_non_alphanumeric': 0.15736665117423518, 'fraction_numerical': 0.02690215162533833, 'mean_word_length': 1.866614420062696, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6011467', 'n_tokens_mistral': 12067, 'n_tokens_neox': 11483, 'n_words': 3009}