The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Games::Sudoku::Component::Table;
{
  use strict;
  use warnings;
  use Carp;

  our $VERSION = '0.02';

  use base qw/Games::Sudoku::Component::Base/;
  use Games::Sudoku::Component::Table::Cell;
  use Games::Sudoku::Component::Table::Item;
  use Games::Sudoku::Component::Table::Permission;

  sub _initialize {
    my ($this, %options) = @_;

    my $size         = $this->{size};
    my $block_width  = $this->{block_width};
    my $block_height = $this->{block_height};

    my $perm = Games::Sudoku::Component::Table::Permission->new(
      block_width  => $block_width,
      block_height => $block_height,
    );

    foreach my $row (1..$size) {
      foreach my $col (1..$size) {
        my $cell_id = $this->_cell_id($row, $col);
        $this->{cells}->[$cell_id] =
          Games::Sudoku::Component::Table::Cell->new(
            row          => $row,
            col          => $col,
            block_width  => $block_width,
            block_height => $block_height,
            perm         => $perm,
          );
      }
    }
  }

  sub cells {
    my $this = shift;

    my @cells = @{ $this->{cells} };
  }

  sub cell {
    my ($this, $row, $col) = @_;

    my $cell_id = $this->_cell_id($row, $col);

    $this->{cells}->[$cell_id];
  }

  sub _cell_id {
    my ($this, $row, $col) = @_;

    croak "Invalid row: $row" unless $this->_check($row);
    croak "Invalid col: $col" unless $this->_check($col);

    ($row - 1) * $this->{size} + ($col - 1);
  }

  sub clear {
    my $this = shift;

    foreach my $cell ($this->cells) {
      $cell->unlock;
      $cell->value(0);
    }
  }

  sub find_next {
    my $this = shift;

    my $size = $this->{size};
    my $min  = $size + 1;
    my $next = undef;
    foreach my $cell ($this->cells) {
      my @allowed = $cell->allowed or next;

      if (scalar @allowed < $min) {
        $min  = scalar @allowed;
        $next = {
          row     => $cell->row,
          col     => $cell->col,
          allowed => \@allowed,
        };
      }
      last if $min == 1;
    }
    $next ? Games::Sudoku::Component::Table::Item->new($next) : undef;
  }

  sub find_all {
    my $this = shift;

    my $size = $this->{size};
    my $min  = $size + 1;
    my @all  = ();
    foreach my $cell ($this->cells) {
      my @allowed = $cell->allowed or next;
      if (scalar @allowed < $min) {
        @all = ();
        $min  = scalar @allowed;
      }
      if (scalar @allowed == $min) {
        push @all, Games::Sudoku::Component::Table::Item->new(
          row     => $cell->row,
          col     => $cell->col,
          allowed => \@allowed,
        );
      }
    }
    @all;
  }

  sub lock_all {
    my $this = shift;

    foreach my $cell ($this->cells) {
      $cell->lock if $cell->realvalue;
    }
  }

  sub unlock_all {
    my $this = shift;

    foreach my $cell ($this->cells) {
      $cell->unlock;
    }
  }

  sub check_tmpvalue {
    my $this = shift;

    my $size = $this->{size};

    foreach my $cell ($this->cells) {
      my $tmpvalue = $cell->tmpvalue or next;
      if ($cell->is_allowed($tmpvalue)) {
        $cell->value($tmpvalue);
      }
    }
  }

  sub num_of_finished {
    my $this = shift;

    my $sum = 0;
    foreach my $cell ($this->cells) {
      $sum++ if $cell->realvalue;
    }
    $sum;
  }

  sub is_finished {
    my $this = shift;

    foreach my $cell ($this->cells) {
      return 0 unless $cell->realvalue;
    }
    return 1;
  }

  sub as_string {
    my $this = shift;

    my %options = ref $_[0] ? %{ $_[0] } : @_;

    my $separator = $options{separator} || ' ';
    my $break     = $options{linebreak} || "\n";

    my $size   = $this->{size};
    my $digit  = int(log($size) / log(10)) + 1;
    my $numfmt = '%'.$digit.'d';

    my @lines;
    foreach my $row (1..$size) {
      my @cells;
      foreach my $col (1..$size) {
        my $value = $this->cell($row, $col)->value;
        push @cells, sprintf($numfmt,$value);
      }
      push @lines, join $separator, @cells;
    }
    join $break, @lines;
  }

  sub as_HTML {
    my $this = shift;

    my %options = ref $_[0] ? %{ $_[0] } : @_;

    my $border = $options{border}    || 1;
    my $break  = $options{linebreak} || "\n";

    my $size = $this->{size};

    my @lines;
    push @lines, qq{<table class="sudoku" border="$border">};
    foreach my $row (1..$size) {
      my @cells;
      foreach my $col (1..$size) {
        my $value = $this->cell($row, $col)->value;
        my $class;
        if ($options{color_by_block}) {
          $class =
            $this->_block_id($row, $col) % 2 ? 'odd' : 'even';
        }
        if ($options{color_by_cell}) {
          $class =
            $this->_cell_id($row, $col) % 2 ? 'odd' : 'even';
        }
        push @cells, 
          $class ? qq{<td class="$class">} : qq{<td>},
          qq{$value</td>};
      }
      my $tds = join '', @cells;
      push @lines, qq{<tr>$tds</tr>};
    }
    push @lines, qq{</table>};
    join $break, @lines;
  }
}

1;
__END__

=head1 NAME

Games::Sudoku::Component::Table

=head1 SYNOPSIS

  use Games::Sudoku::Component::Table;

  # Let's create a default (9x9) table.

  my $t = Games::Sudoku::Component::Table->new;

  $t->

=head1 DESCRIPTION

=head1 METHODS

=head2 new (I<hash> or I<hashref>)

Creates an object. Options are:

=over 4

=item size

Specifies the size of a puzzle board (table). The default is 9.
Actually this value is assumed to be a square of another integer.

=item block_width

=item block_height

Specify the width/height of internal blocks, respectively.
(C<block_width> x C<block_height> = C<size>)

=back

=head2 cell (I<row>, I<column>)

Returns the specified cell object (L<Games::Sudoku::Component::Table::Cell>).

=head2 cells

Returns all the cell objects. Mainly used in C<foreach> loops.

=head2 check_tmpvalue

Checks permissions and if there are any values stored temporarily
but allowed now, store them again as real values.

=head2 clear

Clears all the values.

=head2 find_all

Finds all the cells that have least value possibilities.

=head2 find_next

Finds a cell that have least value possibilities.

=head2 is_finished

Returns true if all the cells have a real value.

=head2 num_of_finished

Returns a number of the cells that have a real value.

=head2 lock_all

Locks all the cells that have a real value, mainly to protect
the generated or loaded puzzle, so that players cannot, 
intentionally or unintentionally, change it.

=head2 unlock_all

Unlocks the cells. Use before creating or loading a new
puzzle.

=head2 as_string

Returns a loadable set of strings for the puzzle.

=head2 as_HTML

Returns an HTML table for the puzzle.

=head1 SEE ALSO

=over 4

=item L<Games::Sudoku::Component>

=item L<Games::Sudoku::Component::Table::Cell>

=item L<Games::Sudoku::Component::Table::Item>

=item L<Games::Sudoku::Component::Table::Permission>

=back

=head1 AUTHOR

Kenichi Ishigaki, E<lt>ishigaki@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2006 by Kenichi Ishigaki

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut