Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Sudoku generator

by Adam (Vicar)
on Aug 04, 2005 at 21:01 UTC ( #481022=snippet: print w/ replies, xml ) Need Help??

Description: I just discovered Sudoku last weekend, and I thought... I can generate these; but when I looked around online for code, I didn't find any. I found plenty of solvers, and I recognized that solving a blank grid would result in a puzzle if you had a random element in your solution method. So I set out to write my own solver/generator that did just that. This code can create and solve Sudoku puzzles of varying sizes, although not very quickly. One problem I'd like to solve is that the puzzles generated are either too easy or too hard. A puzzle tends to be too hard when there are more than one solution, and too easy when there are too many givens. This code tries to find a middle ground by testing to see if randomly solving the puzzle gets the expected solution, and requiring a minimum number of open spots. Enjoy. And yes, I know that Brovnik already posted some code (SuDoKu solver) to solve these, but my method is different (that is, more brutish). Optimizations and corrections are welcome and encouraged.
#!perl -w
use strict;

# Sudoku
#
# This set of functions allows generation of various dimensioned Sudok
+u puzzles
# The ResolveGrid() function can also be used to resolve partially con
+structed grids,
# which is to say that it can be used to solve puzzles you find elsewh
+ere.
# Also, due to the computational complexity (NP) of prooving that an i
+ncomplete grid
# leads to only one possible solution, not all generated puzzles will 
+have a unique
# solution.  This also means that it may take awhile to generate a lar
+ge puzzle.
#
use constant Symetry  => 1;              # 1 => Symetrical Puzzle
use constant Dim      => 4;              # 3 = Traditional
use constant Side     => Dim * Dim;      # One side of the grid
use constant Squared  => Side * Side;    # Total spaces in grid
use constant MinDots  => int( Squared / 2 );  # Specifying the max giv
+ens.
use constant Options  => (1..9,0,'A'..'Z','a'..'z')[0..Side()-1];
use constant Form     => "| ". join(' | ',(join(' ',('@')x Dim))x Dim)
+ ." |\n";
use constant Line     => "+-". join('-+-',(join('-',('-')x Dim))x Dim)
+ ."-+\n";

# For convenience, functions which return an array of results
# return a reference to that array in scalar context.

# Fisher-Yates
sub Shuffle
{
    my @array = @_;
    if ( not defined $_[1] and UNIVERSAL::isa( $_[0], 'ARRAY' ) )
    {
        @array = @{$_[0]};
    }
    my $i=@array; 
    while($i--)
    {
        my $j=int rand(1+$i); 
        @array[$i, $j]=@array[$j, $i]
    }
    return wantarray ? @array : \@array;
}

# Coordinates are zero based.
# Index = Crd2Index( Row, Col )
sub Crd2Index($$) { $_[0] * Side + $_[1] }
sub Index2Crd($)
{
    my $i = $_[0];
    my $r = int( $i / Side );
    my $c = $i % Side;
    return wantarray ? ( $r, $c ) : [ $r, $c ];
}

# I know, I know... still, it is easier to read this way.
sub DotCount($) { return $_[0] =~ s/\./\./g }

# Put the grid into a ... grid.
sub Format($)
{
    die "ASSERT" unless $_[0] and length $_[0] == Squared;

    $^A = "";
    for ( 0 .. Side - 1 )
    {
        $^A .= Line if $_ % Dim == 0;
        formline Form, split //, substr( $_[0], Side * $_, Side );
    }
    return $^A ? $^A . Line : undef;
}

# Determine available options for a given spot in a given grid
# Avail( Grid, Index )
# Avail( Grid, Row, Col )
sub Avail($$;$)
{
    my ( $grid, $row, $col ) = @_;
    ( $row, $col ) = Index2Crd( $row ) if not defined $col;
    my %used = map { $_ => 0 } Options;

    # A little error checking
    die "ASSERT( $row, $col )" unless Crd2Index( $row, $col ) < Square
+d;
    die "ASSERT( '$grid' )" unless length( $grid ) == Squared;

    # Row and Col - Could also do Row via a split, but whatever.
    for ( 0 .. Side - 1 )
    {
        ++$used{substr( $grid, Crd2Index( $_, $col ), 1 )};
        ++$used{substr( $grid, Crd2Index( $row, $_ ), 1 )};
    }

    # Now determine which square we are in
    my ( $x, $y ) = map { int( $_ / Dim ) * Dim } ( $col, $row );
    for my $r ( $y .. $y + Dim - 1 )
    {
        for my $c ( $x .. $x + Dim - 1 )
        {
            ++$used{substr( $grid, Crd2Index( $r, $c ), 1 )};
        }
    }

    my @result = grep { not $used{$_} } Options;
    return wantarray ? @result : \@result;
}

# Making and Solving a grid are basically the exact same thing
# You don't need to shuffle the available array if you are just
# solving, but it doesn't hurt anything - and it allows you to
# find multiple solutions, if they exist.
sub ResolveGrid(;$)
{
    my $grid = $_[0];
    $grid = '.' x Squared if not defined $grid;

    my @stack = ( );
    my $next = 0;

    while ( 0 <= $next and $next < Squared )
    {
        if ( substr( $grid, $next, 1 ) ne '.' )
        {
            ++$next;
            next;
        }

        my $avail = Shuffle Avail( $grid, $next );

        if ( not @{$avail} )
        {
            die "INVALID GRID\n" if not @stack;
            my $prev = pop @stack;
            $grid   = $prev->[0];
            $avail  = $prev->[1];
            $next   = $prev->[2];
        }

        my $choice = shift @{$avail};
        push @stack, [ $grid, $avail, $next ] if @{$avail};
        substr( $grid, $next, 1 ) = $choice;
        ++$next;
    }

    return $grid;
}

# Could also resolve a grid recursivly, but you hit Perl's limit with 
# grids larger then (3x3)^2  --- that is, Dim => 3.
sub ResolveGridRecursive
{
    my $grid = $_[0];
    my $next = $_[1] || 0;

    die "ASSERT!\nGRID = '$grid'\nNEXT='$next'\n\t" if $next < 0;

    $grid = '.' x Squared if not defined $grid;
    return $grid if $next >= length $grid;
    substr( $grid, $next, 1 ) = '.';

    for ( Shuffle Avail( $grid, $next ) )
    {
        substr( $grid, $next, 1 ) = $_;
        my $testgrid = ResolveGridRecursive( $grid, $next + 1 );
        return $testgrid if defined $testgrid;
    }

    substr( $grid, $next, 1 ) = '.';
    return undef;
}

# Generate a puzzle
# 
sub MakePuzzle(;$)
{
    my $soln = ResolveGrid( $_[0] ); # Resolving empty grid creates a 
+random solution.

    my $puz = $soln;
    my @location = Shuffle( 0 .. Squared - 1 );
    my $i;

    while ( @location )
    {
        $i = pop @location;
        substr( $puz, $i, 1 ) = '.';
        last if ResolveGrid( $puz ) ne $soln;
    }

    substr( $puz, $i, 1 ) = substr( $soln, $i, 1 ) if defined $i;
    return $puz;
}

# Some people prefer symetrical puzzles.
sub MakePuzzleSym(;$)
{
    my $soln = $_[0] || ResolveGrid; # Resolving empty grid creates a 
+random solution.

    my $puz = $soln;
    my @location;
    for ( 0 .. Side - 1 ) { push @location, $_ * Side .. $_ * Side + S
+ide - ( Side - $_ ) }

    @location = Shuffle( @location );
    my ( $i, $j );

    while ( @location )
    {
        $i = pop @location;
        $j = Crd2Index( Index2Crd( $i )->[1], Index2Crd( $i )->[0] );
        die "ASSERT( $i, $j )" if $j > Squared;
        substr( $puz, $i, 1 ) = '.';
        substr( $puz, $j, 1 ) = '.';
        last if ResolveGrid( $puz ) ne $soln;
    }

    substr( $puz, $i, 1 ) = substr( $soln, $i, 1 ) if defined $i;
    substr( $puz, $j, 1 ) = substr( $soln, $j, 1 ) if defined $j;
    return $puz;
}

########
# Main #
########

# Resolving and empty grid gives you a legitimate Sudoku square
# The MakePuzzle functions will do this for you, if you don't want the
+ solution handy.
my $solution = ResolveGrid;

# Now find a sufficiently 'hard' set of givens, where 'hard' is merely
+ a function
# of how many givens you have. MakePuzzle attempts to verify that the 
+puzzle you 
# get has only the desired solution, but of course you can't really ve
+rify that
# without checking every possible solution.
my $puzzle   = $solution;
while ( DotCount( $puzzle ) < MinDots )
{ print "Attempt\n";
    $puzzle = Symetry ? MakePuzzleSym( $solution ) : MakePuzzle( $solu
+tion );
}

# Now you can display the puzzle in a lovely grid
print Format( $puzzle ), "\n";

# Or just dump out a simple string form, suitable for testing other so
+lvers
print "Puzzle  : $puzzle\n";

# And, of course, the solution... just in case you want it.
print "Solution: $solution\n";


__END__

Example Output for a (4x4)^2 symetrical puzzle:
+---------+---------+---------+---------+
| . . C 7 | 4 . . 9 | . . E 3 | 1 . . . |
| . . 5 2 | F . . . | A 6 . 1 | 9 B . . |
| 9 D 8 . | . 5 B . | . 2 0 . | . . . . |
| 1 0 . . | E 3 . A | . . C 8 | . 2 . . |
+---------+---------+---------+---------+
| C B . A | 8 . E 2 | 1 . . 0 | . D 7 5 |
| . . 7 1 | . . 3 4 | 5 9 B . | C . 2 A |
| . . F . | 1 C D 6 | . . 4 . | . 9 B . |
| 2 . . 3 | 7 B A 5 | 8 C D . | . 1 4 . |
+---------+---------+---------+---------+
| . 4 . . | 2 1 . C | E . . . | . . . 7 |
| . 9 2 . | . A . B | . . F 5 | . 4 . 1 |
| 3 . 1 F | . D 4 E | . B . A | . C . . |
| 5 A . C | 9 . . . | . 1 2 . | 3 . . . |
+---------+---------+---------+---------+
| 7 C . . | . E . . | . . . 2 | . . 9 . |
| . E . 9 | D . C 3 | . 0 5 . | . . . . |
| . . . . | A 7 1 F | . . . . | 5 . 3 C |
| . . . . | 5 2 . . | 3 4 . . | . . E 0 |
+---------+---------+---------+---------+

Puzzle  : ..C74..9..E31.....52F...A6.19B..9D8..5B..20.....10..E3.A..C8
+.2..CB.A8.
E21..0.D75..71..3459B.C.2A..F.1CD6..4..9B.2..37BA58CD..14..4..21.CE...
+...7.92..A
.B..F5.4.13.1F.D4E.B.A.C..5A.C9....12.3...7C...E.....2..9..E.9D.C3.05.
+........A7
1F....5.3C....52..34....E0
Solution: AFC74629BDE31508E352F08DA6719BC49D86C5B1420F73AE10B4E37A95C8
+D2F6CB4A89
E21F306D75D8710F3459B6CE2A05FE1CD62A4789B326937BA58CDE014FB4D0215CE369
+AF8769283A
0BC7F5E4D1371F6D4E0B8A2C595AEC98F7D124306B7C35BE60F8124A9D8EA9D4C3705B
+F612420BA7
1F6E9D583CF16D529834ACB7E0
Comment on Sudoku generator
Download Code
Re: Sudoku generator
by polettix (Vicar) on Aug 05, 2005 at 01:58 UTC
    Strictly speaking, a correct SuDoKu puzzle should have exactly one solution. You can find some info about generating puzzles here and some code here (ANSI C) or here (Java, look at the Composer). Anyway, my favourite sudoku-generation method so far is LWP::Simple and this site ;)

    Flavio
    perl -ple'$_=reverse' <<<ti.xittelop@oivalf

    Don't fool yourself.
Re: Sudoku generator
by Arunbear (Parson) on Aug 05, 2005 at 09:28 UTC

Back to Snippets Section

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: snippet [id://481022]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others chilling in the Monastery: (6)
As of 2014-10-24 08:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    For retirement, I am banking on:










    Results (131 votes), past polls