dice.py

'''\
Dice rolls and odds.

 D odds_%     winning_rolls   possible_rolls
--------------------------------------------
 1 16.667                 1                6
 2 30.556                11               36
 3 42.130                91              216
 4 51.775               671             1296
 5 59.812              4651             7776
 6 66.510             31031            46656
 7 72.092            201811           279936
 8 76.743           1288991          1679616
 9 80.619           8124571         10077696
10 83.849          50700551         60466176
11 86.541         313968931        362797056
12 88.784        1932641711       2176782336
13 90.654       11839990891      13060694016
14 92.211       72260648471      78364164096
15 93.509      439667406451     470184984576
16 94.591     2668522016831    2821109907456
17 95.493    16163719991611   16926659444736
18 96.244    97745259402791  101559956668416
19 96.870   590286253682371  609359740010496
20 97.392  3560791008422351 3656158440062976

commands:
    main            - Main function (normal use)
    demo            - Demo function (show off and test app)
    test            - Test function (development)

usage:
    module.dispatch({command}, args={arguments})    -- calling module from code

    $PYTHON module_name {command} {arguments}       -- executing module externally

Developer@Sonnack.com
April 2016
'''
####################################################################################################
from __future__ import print_function
from sys import argvexc_info
from traceback import extract_tb
from os import path
from random import random
from logger import loggerinfodebugtrace
####################################################################################################
Log = logger('dice')

BasePath = r'C:\CJS\prj\Python\ws'

BASE = 6

##================================================================================================##
def generate_all_possible_rolls (dice=6):
    rs = []
    r = [1] * dice
    while r:
        rs.append(tuple(r))
        r = generate_next_roll(r)
    return rs
##================================================================================================##
def generate_next_roll (roll):
    ix = len(roll) - 1
    while 0 <= ix:
        roll[ix] += 1
        if BASE < roll[ix]:
            roll[ix] = 1
            ix -= 1
        else:
            break
    return None if ix < 0 else roll


##================================================================================================##
def roll_six_stats (rolldice=6):
    '''Returns rolls stats on a roll of six dice. {roll: list of six dice}'''
    zs = map(lambda x: x+1range(dice))
    stats = {'roll':roll'zs':zs}
    stats['1s'] = sum(map(lambda x: 1 if x==1 else 0roll))
    stats['5s'] = sum(map(lambda x: 1 if x==5 else 0roll))
    stats['str8'] = (0 == sum(map(lambda x,y: abs(x-y), sorted(roll), zs)))
    of_a_kind = [0] * BASE
    for d in roll:
        of_a_kind[d-1] += 1
    stats['oak'] = of_a_kind
    stats['2oak'] = sum(map(lambda x: 1 if x==2 else 0of_a_kind))
    stats['3oak'] = sum(map(lambda x: 1 if x==3 else 0of_a_kind))
    stats['4oak'] = sum(map(lambda x: 1 if x==4 else 0of_a_kind))
    stats['5oak'] = sum(map(lambda x: 1 if x==5 else 0of_a_kind))
    stats['6oak'] = sum(map(lambda x: 1 if x==6 else 0of_a_kind))
    stats['4+2'] = ((stats['4oak'] == 1and (stats['2oak'] == 1))
    stats['3+3'] = (stats['3oak'] == 2)
    stats['3P'] = (stats['2oak'] == 3)
    return stats

##================================================================================================##
def print_roll_stats (stats):
    print()
    print('Roll: %s' % str(stats['roll']))
    print('1s:%d, 5s:%d' % (stats['1s'], stats['5s']))
    print('2oak:%d, 3oak:%d, 4oak:%d, 5oak:%d, 6oak:%d' % (stats['2oak'],stats['3oak'],stats['4oak'],stats['5oak'],stats['6oak']))
    print('3+3:%s, 4+2:%s, 3P:%s' % (stats['3+3'], stats['4+2'], stats['3P']))
    print('straight: %s' % str(stats['str8']))
    print('OAK: %s' % str(stats['oak']))
    print()


##================================================================================================##
def odds_of_a_number (dice=6):
    '''Implements: SUM:dice-1:i=0:[(B^i)*((B-1)^((dice-1)-i))] where: B=6; dice=#-of-dice.'''
    m = BASE ** dice
    ix = range(dice)
    ls = [BASE ** n for n in ix]
    rs = [(BASE-1) ** ((dice-1)-nfor n in ix]
    ss = map(lambda a,b: a*blsrs)
    n = sum(ss)
    return [100.0 * (float(n)/float(m)), nmssrsls]



##================================================================================================##
def do_odds (*args):
    Log.info('odds/parameters: %d' % len(args))
    # Generate a list of odds...
    odds = []
    print()
    for n in range(20):
        g = odds_of_a_number(n+1)
        print('%8.3f  %16d %16d' % tuple(g[:3]))
        odds.append(g)
    return 'Done!'

##================================================================================================##
def do_list (*args):
    Log.info('list/parameters: %d' % len(args))
    rolls = generate_all_possible_rolls()
    fn = path.join(BasePath'dice.lst')
    fp = open(fn'w')
    try:
        print('Possible Rolls (%d):' % len(rolls), file=fp)
        for ix,r in enumerate(rolls):
            g = roll_six_stats(r)
            t = (g['1s'],g['5s'], g['2oak'],g['3oak'],g['4oak'],g['5oak'],g['6oak'], g['3+3'],g['4+2'],g['3P'],g['str8'])
            s = '%d %d [%d %d %d %d %d] %5s %5s %5s %5s'
            print('%5d: %s  %s' % (ixr, (s % t)), file=fp)
        print(file=fp)
    except:
        raise
    finally:
        fp.close()
    return 'Done!'

##================================================================================================##
def do_demo (*args):
    Log.info('demo/parameters: %d' % len(args))
    d1 = int(args[0]) if 0 < len(argselse int((random()*BASE)+1)
    d2 = int(args[1]) if 1 < len(argselse int((random()*BASE)+1)
    d3 = int(args[2]) if 2 < len(argselse int((random()*BASE)+1)
    d4 = int(args[3]) if 3 < len(argselse int((random()*BASE)+1)
    d5 = int(args[4]) if 4 < len(argselse int((random()*BASE)+1)
    d6 = int(args[5]) if 5 < len(argselse int((random()*BASE)+1)
    roll = [d1d2d3d4d5d6]
    g = roll_six_stats(roll)
    print_roll_stats(g)
    return 'Done!'

##================================================================================================##
def do_test (*args):
    Log.info('test/parameters: %d' % len(args))
    rolls = generate_all_possible_rolls()
    stats = map(lambda x: roll_six_stats(x), rolls)
    possible_rolls = float(len(rolls))
    ones = 0
    fives = 0
    one_or_five = 0
    straights = 0
    six_oak = 0
    five_oak = 0
    four_oak = 0
    three_oak = 0
    four_two = 0
    two_triples = 0
    three_pairs = 0
    for stat in stats:
        if stat['1s']: ones += 1
        if stat['5s']: fives += 1
        if stat['1s'or stat['5s']: one_or_five += 1
        if stat['str8']: straights += 1
        if stat['6oak']: six_oak += 1
        if stat['5oak']: five_oak += 1
        if stat['4oak']: four_oak += 1
        if stat['3oak']: three_oak += 1
        if stat['4+2']: four_two += 1
        if stat['3+3']: two_triples += 1
        if stat['3P']: three_pairs += 1
    # Write results...
    fn = path.join(BasePath'dice.out')
    fp = open(fn'w')
    try:
        print('Roll Stats:'file=fp)
        print('==========='file=fp)
        print('Dice: 6'file=fp)
        print('Sides: %d' % BASEfile=fp)
        print('Possible rolls: %d' % possible_rollsfile=fp)
        print(file=fp)
        print('Rolls with a 1: %d  (%.2f%%)' % (ones100.0*(ones/possible_rolls)), file=fp)
        print('Rolls with a 5: %d  (%.2f%%)' % (fives100.0*(fives/possible_rolls)), file=fp)
        print('Rolls with a 1 or 5: %d  (%.2f%%)' % (one_or_five100.0*(one_or_five/possible_rolls)), file=fp)
        print(file=fp)
        print('Straights: %d  (%.2f%%)' % (straights100.0*(straights/possible_rolls)), file=fp)
        print(file=fp)
        print('Three Pairs: %d  (%.2f%%)' % (three_pairs100.0*(three_pairs/possible_rolls)), file=fp)
        print(file=fp)
        print('4+2: %d  (%.2f%%)' % (four_two100.0*(four_two/possible_rolls)), file=fp)
        print('3+3: %d  (%.2f%%)' % (two_triples100.0*(two_triples/possible_rolls)), file=fp)
        print(file=fp)
        print('6-of-a-kind: %5d  (%7.4f%%)' % (six_oak100.0*(six_oak/possible_rolls)), file=fp)
        print('5-of-a-kind: %5d  (%6.3f%%)' % (five_oak100.0*(five_oak/possible_rolls)), file=fp)
        n = four_oak - four_two
        print('4-of-a-kind: %5d  (%5.2f%%)  [raw: %d]' % (n100.0*(n/possible_rolls), four_oak), file=fp)
        n = three_oak - two_triples
        print('3-of-a-kind: %5d  (%5.2f%%)  [raw: %d]' % (n100.0*(n/possible_rolls), three_oak), file=fp)
        print(file=fp)
        n = 0
        print(file=fp)
        for stat in stats:
            oak = stat['oak']
            if (oak[0]==0and (oak[4]==0and (max(oak) < 3and (not stat['3P']):
                n += 1
        print('Farkle: %d  (%.2f%%)' % (n100.0*(n/possible_rolls)), file=fp)
        print(file=fp)
    except:
        raise
    finally:
        fp.close()
    return 'Done!'
##================================================================================================##
def do_main (*args):
    Log.info('main/parameters: %d' % len(args))
    return 'Done!'
####################################################################################################
def dispatch (cmd, *args):
    Log.info('command: %s' % cmd)
    Log.info('arguments: %d' % len(args))
    if cmd == 'odds': return do_odds(*args)
    if cmd == 'list': return do_list(*args)
    if cmd == 'demo': return do_demo(*args)
    if cmd == 'test': return do_test(*args)
    if cmd == 'main': return do_main(*args)
    return 'Nothing to do!'
####################################################################################################
if __name__ == '__main__':
    print('autorun: %s' % argv[0])
    Log.start(path.join(BasePath,'dice.log'))
    Log.level(info())
    cmd = argv[1if 1 < len(argvelse ''
    etc = argv[2:if 2 < len(argvelse []
    try:
        obj = dispatch(cmd, *etc)
        print(obj)
        Log.info(obj)
    except:
        etypeevaluetb = exc_info()
        ts = extract_tb(tb)
        Log.error('%s: %s' % (etype.__name__,str(evalue)))
        for t in ts[-3:]:
            Log.error('[%d] %s  (%s)' % (t[1], t[2], t[0]))
            Log.error('    %s' % t[3])
        raise
    finally:
        Log.end()
####################################################################################################
'''eof'''