#
# Code for enumerating coin and dice throws
#
# Copyright 2003 Mark Jason Dominus (mjd@plover.com)
# Copyright abandoned 2003 11 08
# This code is in the public domain
#

sub gen_table {
  my ($N, $C) = @_;
  my @table;

  my $total = $C**$N;
  my $check = 0;

  for my $pat (patterns($N)) {
    my $f = frequency($N, $C, $pat);
    next if $f == 0;
    push @table, [pattern_to_string($pat), $f];
    $check += $f;
  }

  if ($check != $total) {
    die "Total failed check\n";
  }
  @table;
}

sub patterns {
  my $N = shift;
  my $max = shift || $N;
  my @result;
  return if $N < 0;
  return [] if $N == 0;
  for my $i (reverse 1 .. $max) {
    push @result, map [$i, @$_], patterns($N-$i, $i);
  }
  @result;
}

sub pattern_to_string {
  my $pat = shift;
  my @pat = @$pat;
  my $l = 'A';
  my $s = "";
  while (@pat) {
    $s .= ($l x (shift @pat));
    $l++;
  }
  $s;
}

sub R {
  my $pat = shift;
  prod(map fact($_), @$pat);
}

sub B {
  my $pat = shift;
  my @count;
  $count[$_]++ for @$pat;
  prod(map fact($_), @count);
}

sub green {
  my ($C, $pat) = @_;
  my $T = 1;
  $T *= $C-- for @$pat;
  $T;
}

sub frequency {
  my ($N, $C, $pat) = @_;
  fact($N) / R($pat) / B($pat) * green($C, $pat);
}

sub prod {
  my $T = 1;
  for (@_) { $T *= $_ }
  $T;
}

BEGIN { my @fact = (1);
        sub fact {
          my $n = shift;
          return $fact[$n] if defined $fact[$n];
          $fact[$n] = $n * fact($n-1);
        }
      }

1;
