#!/usr/bin/env python
# -*- coding: utf-8 -*-
# $Id: sudoku.py 600 2015-02-24 20:21:02Z goodger $

# Author: David Goodger <goodger@python.org>
# Copyright: (C) 1998-2015 by David J. Goodger
# License: GPL 2 (see __init__.py)

"""
Sudoku solver with support for front end applications.
"""

import sys
import math
import optparse
from datetime import datetime
import puzzler


usage = '%prog [options] [<puzzle-file>]'

description = """\
9x9 Sudoku puzzle solver.  Supply a Sudoku starting position: either provide
the name of the file containing the position (as <puzzle-file> above), or type
in the starting position at the prompt.  Use periods (".") or zeros ("0") to
represent empty squares in starting positions.  Starting positions must be
either 9 lines of 9 columns, or all on one line, with or without spaces
between digits.  See the README.txt file for details
(http://puzzler.sourceforge.net/README.html#sudoku).
"""

cmdline_problem_text = """\
%s takes one optional command-line argument, the file name of
the starting position.  Omit the command-line argument or use "-" to read
the starting position from standard input."""

stdin_prompt = """
Enter a 9x9 Sudoku starting position: either 9 lines of 9 columns
or 1 big line, "." or "0" for empty squares, spaces optional.
Ctrl-D (on Linux/Mac), Ctrl-Z + Enter (on Windows) to end:
"""


def run(puzzle_class, start_position=None, output_stream=sys.stdout,
        settings=None):
    """Given a `Puzzle` subclass instance, find all solutions."""
    if settings is None:
        settings = Settings(start_position=start_position)
    elif start_position:
        settings.start_position = start_position
    solve(puzzle_class, output_stream, settings)

def run_from_command_line(puzzle_class, output_stream=sys.stdout,
                          settings=None):
    """
    Given a `Puzzle` subclass instance, process the command line and find all
    solutions.
    """
    if settings is None:
        settings = process_command_line()
    solve(puzzle_class, output_stream, settings)

def process_command_line_options():
    """Process command-line options & return a settings object & args."""
    parser = optparse.OptionParser(
        formatter=optparse.TitledHelpFormatter(width=78),
        add_help_option=None, description=description, usage=usage)
    choices = ('x2', 'dlx',)
    parser.add_option(
        '-a', '--algorithm', metavar='NAME', choices=choices,
        default=choices[0],
        help=('Choice of exact cover algorithm.  Choices: %s.'
              % ('"%s" (default), "%s"'
                 % (choices[0], '", "'.join(choices[1:])))))
    parser.add_option(
        '-n', '--stop-after', type='int', metavar='N',
        help='Stop processing after generating N solutions.')
    parser.add_option(
        '-h', '--help', help='Show this help message and exit.', action='help')
    settings, args = parser.parse_args()
    return settings, args

def process_command_line():
    settings, args = process_command_line_options()
    if not args:
        settings.start_position = read_start_position_from_stdin()
    elif len(args) == 1:
        if args[0] == '-':
            settings.start_position = read_start_position_from_stdin()
        else:
            settings.start_position = read_start_position_from_file(args[0])
    else:
        print >>sys.stderr, cmdline_problem_text % (sys.argv[0])
        sys.exit(1)
    return settings

def read_start_position_from_stdin():
    print >>sys.stderr, stdin_prompt
    data = sys.stdin.read()
    return data

def read_start_position_from_file(name):
    try:
        f = open(name)
    except IOError:
        print >>sys.stderr, 'Unable to open file "%s".' % name
        sys.exit(1)
    try:
        try:
            data = f.read()
        except Exception, error:
            print >>sys.stderr, 'Problem reading data from file "%s":' % name
            print >>sys.stderr, '%s: %s' % (error.__class__.__name__, error)
            sys.exit(1)
    finally:
        f.close()
    return data

def solve(puzzle_class, output_stream, settings):
    """Find and record all solutions to a puzzle.  Report on `output_stream`."""
    start = datetime.now()
    puzzle = puzzle_class(settings.start_position)
    solver = puzzler.exact_cover_modules[settings.algorithm].ExactCover(
        puzzle.matrix)
    try:
        try:
            print >>output_stream, ('solving %s:\n'
                                    % puzzle.__class__.__name__)
            print >>output_stream, puzzle.start_position, '\n'
            for solution in solver.solve():
                puzzle.record_solution(
                    solution, solver, stream=output_stream)
                if ( settings.stop_after
                     and solver.num_solutions == settings.stop_after):
                    break
        except KeyboardInterrupt:
            print >>output_stream, 'Session interrupted by user.'
            sys.exit(1)
    finally:
        end = datetime.now()
        duration = end - start
        print >>output_stream, (
            '%s solution%s, %s searches, duration %s'
            % (solver.num_solutions, ('s', '')[solver.num_solutions == 1],
               solver.num_searches, duration))


class DataError(RuntimeError): pass


class Settings(object):

    algorithm = 'x2'
    stop_after = 0
    start_position = ''

    def __init__(self, **keywordargs):
        self.__dict__.update(keywordargs)


class Puzzle(object):

    empties = set(['.', '0'])

    def __init__(self, start_position, init_puzzle=True):
        self.start_position = start_position
        """Text specification of the Sudoku puzzle start position, a
        multi-line string.  For example, in a 4x4 puzzle, the start position
        might look like this::

            1 . . 4
            . 4 . 2
            2 3 . 1
            . . 2 .

        The number of lines must equal the order of the puzzle, including
        blank lines.  The number of space-separated text columns must also
        equal the order of the puzzle.  Empty cells must contain a period (".")
        or a zero ("0").
        """

        self.coordinate_blocks = {}
        """Mapping of (x,y) coordinates to puzzle block."""

        self.matrix = []
        """A list of lists; see ExactCover.load_matrix()."""

        self.matrix_columns = {}
        """Mapping of `self.matrix` column names to indices."""

        if init_puzzle:
            self.init_puzzle()

    def init_puzzle(self):
        """Initialize the puzzle matrix and data attributes."""
        self.build_matrix_header()
        self.init_coordinate_blocks()
        try:
            self.build_matrix()
        except DataError, error:
            print 'Problem with start position: %s.' % (error,)
            sys.exit(1)
        except ValueError, error:
            print 'Non-integer found in start position: %s.' % (error,)
            sys.exit(1)

    def build_matrix_header(self):
        """
        Create and populate the first row of `self.matrix`, a list of column
        names.  Also populate the `self.matrix_columns` mapping.
        """
        headers = ['%ib%i' % (i, b) for b in range(self.order)
                   for i in range(1, self.order + 1)]
        headers.extend('%ic%i' % (i, x) for x in range(self.order)
                       for i in range(1, self.order + 1))
        headers.extend('%ir%i' % (i, y) for y in range(self.order)
                       for i in range(1, self.order + 1))
        headers.extend('+%i,%i' % (x, y) for x in range(self.order)
                       for y in range(self.order))
        if len(headers) != self.order ** 2 * 4:
            raise RuntimeError(
                'Problem with matrix columns: expecting %s columns, %s created'
                % (self.order ** 2 * 4, len(headers)))
        self.matrix_columns = dict((name, i)
                                   for (i, name) in enumerate(headers))
        self.matrix.append(headers)

    def init_coordinate_blocks(self):
        """Populate the `self.coordinate_blocks` mapping."""
        block_size = math.sqrt(self.order)
        # reimplement in subclasses if not a whole square:
        if int(block_size) != block_size:
            raise RuntimeError('self.order (== %i) must be a whole square'
                               % self.order)
        block_size = int(block_size)
        for x in range(self.order):
            for y in range(self.order):
                block_id = x / block_size + block_size * (y / block_size)
                self.coordinate_blocks[(x,y)] = block_id

    def build_matrix(self):
        """
        Create and populate the data rows of `self.matrix`, lists of 0's and
        1's (or other true values).
        """
        filled = self.build_matrix_rows_for_givens()
        self.build_matrix_rows_for_unknowns(filled)

    def build_matrix_rows_for_givens(self):
        """
        Append rows to `self.matrix` from cells in starting position.

        Return a set of IDs for the givens.
        """
        num_cols_in_matrix = len(self.matrix[0])
        filled = set()
        self.normalize_start_position()
        lines = self.start_position.splitlines()
        for y, line in enumerate(lines):
            cells = line.split()
            for x, cell in enumerate(cells):
                if cell in self.empties:
                    continue
                row = [0] * num_cols_in_matrix
                value = int(cell)
                block_id = '%ib%i' % (value, self.coordinate_blocks[(x,y)])
                filled.add(block_id)
                row[self.matrix_columns[block_id]] = 1
                column_id = '%ic%i' % (value, x)
                filled.add(column_id)
                row[self.matrix_columns[column_id]] = 1
                row_id = '%ir%i' % (value, y)
                filled.add(row_id)
                row[self.matrix_columns[row_id]] = 1
                coord_id = '+%i,%i' % (x, y)
                row[self.matrix_columns[coord_id]] = 1
                filled.add(coord_id)
                self.matrix.append(row)
        return filled

    def build_matrix_rows_for_unknowns(self, filled):
        """
        Append rows to `self.matrix` for cells not in starting position.

        `filled` is a set of IDs for the givens.
        """
        num_cols_in_matrix = len(self.matrix[0])
        for block in range(self.order):
            for x in range(self.order):
                for y in range(self.order):
                    if self.coordinate_blocks[(x,y)] != block:
                        continue
                    coord_id = '+%i,%i' % (x, y)
                    if coord_id in filled:
                        continue
                    for value in range(1, self.order + 1):
                        block_id = '%ib%i' % (value,
                                              self.coordinate_blocks[(x,y)])
                        if block_id in filled:
                            continue
                        column_id = '%ic%i' % (value, x)
                        if column_id in filled:
                            continue
                        row_id = '%ir%i' % (value, y)
                        if row_id in filled:
                            continue
                        row = [0] * num_cols_in_matrix
                        row[self.matrix_columns[block_id]] = 1
                        row[self.matrix_columns[column_id]] = 1
                        row[self.matrix_columns[row_id]] = 1
                        row[self.matrix_columns[coord_id]] = 1
                        self.matrix.append(row)

    def normalize_start_position(self):
        pos = self.start_position
        pos = pos.replace('-', '').replace('|', '').replace('+', '')
        pos = pos.replace('0', '.')
        lines = [line for line in pos.splitlines() if line]
        if len(lines) == 1:
            line = lines[0].strip()
            cells = line.split()
            cells = list(''.join(cells))
            if len(cells) != self.order ** 2:
                raise DataError(
                    '1-line starting position: %i numbers found, %i expected'
                    % (len(lines), self.order ** 2))
            self.start_position = (
                '\n'.join(' '.join(cells[n:n+self.order])
                          for n in range(0, self.order ** 2, self.order)))
        elif len(lines) == self.order:
            new_lines = []
            for i, line in enumerate(lines):
                cells = line.split()
                if len(cells) != self.order:
                    cells = list(''.join(cells))
                if len(cells) == self.order:
                    new_lines.append(' '.join(cells))
                else:
                    raise DataError('%i columns found (in row %i), %i expected'
                                    % (len(cells), i + 1, self.order))
            self.start_position = '\n'.join(new_lines)
        else:
            raise DataError('%i rows found, 1 or %i expected'
                            % (len(lines), self.order))

    def record_solution(self, solution, solver, stream=sys.stdout, dated=False):
        formatted = self.format_solution(solution)
        solver.num_solutions += 1
        if dated:
            print >>stream, 'at %s,' % datetime.datetime.now(),
        print >>stream, formatted
        print >>stream

    def format_solution(self, solution):
        matrix = [[0] * self.order for i in range(self.order)]
        for row in solution:
            coord_id, block_id, column_id, row_id = row
            x, y = [int(part) for part in coord_id[1:].split(',')]
            value = int(block_id.split('b')[0])
            matrix[y][x] = value
        return '\n'.join([' '.join([str(cell) for cell in row])
                          for row in matrix])


class Sudoku4x4(Puzzle):

    order = 4


class Sudoku9x9(Puzzle):

    order = 9


class SudokuTest(object):

    start_positions_9x9 = [
"""\
5 3 . . 7 . . . .
6 . . 1 9 5 . . .
. 9 8 . . . . 6 .
8 . . . 6 . . . 3
4 . . 8 . 3 . . 1
7 . . . 2 . . . 6
. 6 . . . . 2 8 .
. . . 4 1 9 . . 5
. . . . 8 . . 7 9
""",
"""\
. . . . . . . . .
. . . . . 3 . 8 5
. . 1 . 2 . . . .
. . . 5 . 7 . . .
. . 4 . . . 1 . .
. 9 . . . . . . .
5 . . . . . . 7 3
. . 2 . 1 . . . .
. . . . 4 . . . 9
""",
# http://en.wikipedia.org/wiki/Algorithmics_of_Sudoku
# "Exceptionally difficult Sudokus (Hardest Sudokus)":
"""\
1.. ... ..2
.9. 4.. .5.
..6 ... 7..

.5. 9.3 ...
... .7. ...
... 85. .4.

7.. ... 6..
.3. ..9 .8.
..2 ... ..1
""",
"""\
. . 1 | . . 4 | . . .
. . . | . 6 . | 3 . 5
. . . | 9 . . | . . .
------+-------+------
8 . . | . . . | 7 . 3
. . . | . . . | . 2 8
5 . . | . 7 . | 6 . .
------+-------+------
3 . . | . 8 . | . . 6
. . 9 | 2 . . | . . .
. 4 . | . . 1 | . . .
""",
"""\
. . . . . . . 3 9
. . . . . 1 . . 5
. . 3 . 5 . 8 . .
. . 8 . 9 . . . 6
. 7 . . . 2 . . .
1 . . 4 . . . . .
. . 9 . 8 . . 5 .
. 2 . . . . 6 . .
4 . . 7 . . . . .
""",
"""\
. 2 . 4 . 3 7 . .
. . . . . . . 3 2
. . . . . . . . 4
. 4 . 2 . . . 7 .
8 . . . 5 . . . .
. . . . . 1 . . .
5 . . . . . 9 . .
. 3 . 9 . . . . 7
. . 1 . . 8 6 . .
""",
# http://en.wikipedia.org/wiki/Algorithmics_of_Sudoku
# "Algorithmic Search for Symmetrical Sudokus with Few Givens" (18 clues):
"""\
. . . . 2 5 . . .
. . . . . 7 3 . .
. . . . . . 4 8 .
. . . . . . . 5 9
7 . . . . . . . 2
3 8 . . . . . . .
. 9 5 . . . . . .
. . 1 6 . . . . .
. . . 8 3 . . . .
""",
# http://magictour.free.fr/top95, first & last puzzles:
"""\
4 . . . . . 8 . 5
. 3 . . . . . . .
. . . 7 . . . . .
. 2 . . . . . 6 .
. . . . 8 . 4 . .
. . . . 1 . . . .
. . . 6 . 3 . 7 .
5 . . 2 . . . . .
1 . 4 . . . . . .
""",
"""\
3 . . . 8 . . . .
. . . 7 . . . . 5
1 . . . . . . . .
. . . . . . 3 6 .
. . 2 . . 4 . . .
. 7 . . . . . . .
. . . . 6 . 1 3 .
. 4 5 2 . . . . .
. . . . . . 8 . .
""",
# http://www.usatoday.com/news/offbeat/2006-11-06-sudoku_x.htm
# "Mathematician claims to have penned hardest sudoku" (22 clues):
"""\
8 5 . . . 2 4 . .
7 2 . . . . . . 9
. . 4 . . . . . .
. . . 1 . 7 . . 2
3 . 5 . . . 9 . .
. 4 . . . . . . .
. . . . 8 . . 7 .
. 1 7 . . . . . .
. . . . 3 6 . 4 .
""",
# World's hardest sudoku: can you crack it? (by Arto Inkala)
# http://www.telegraph.co.uk/science/science-news/9359579/Worlds-hardest-sudoku-can-you-crack-it.html
# 21 clues
"""\
8 . . . . . . . .
. . 3 6 . . . . .
. 7 . . 9 . 2 . .
. 5 . . . 7 . . .
. . . . 4 5 7 . .
. . . 1 . . . 3 .
. . 1 . . . . 6 8
. . 8 5 . . . 1 .
. 9 . . . . 4 . . 
""",
]

    # http://magictour.free.fr/top95
    magictour = """\
4.....8.5 .3....... ...7..... .2.....6. ....8.4.. ....1.... ...6.3.7. 5..2..... 1.4......
52...6.........7.13...........4..8..6......5...........418.........3..2...87.....
6.....8.3.4.7.................5.4.7.3..2.....1.6.......2.....5.....8.6......1....
48.3............71.2.......7.5....6....2..8.............1.76...3.....4......5....
....14....3....2...7..........9...3.6.1.............8.2.....1.4....5.6.....7.8...
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
6.2.5.........3.4..........43...8....1....2........7..5..27...........81...6.....
.524.........7.1..............8.2...3.....6...9.5.....1.6.3...........897........
6.2.5.........4.3..........43...8....1....2........7..5..27...........81...6.....
.923.........8.1...........1.7.4...........658.........6.5.2...4.....7.....9.....
6..3.2....5.....1..........7.26............543.........8.15........4.2........7..
.6.5.1.9.1...9..539....7....4.8...7.......5.8.817.5.3.....5.2............76..8...
..5...987.4..5...1..7......2...48....9.1.....6..2.....3..6..2.......9.7.......5..
3.6.7...........518.........1.4.5...7.....6.....2......2.....4.....8.3.....5.....
1.....3.8.7.4..............2.3.1...........958.........5.6...7.....8.2...4.......
6..3.2....4.....1..........7.26............543.........8.15........4.2........7..
....3..9....2....1.5.9..............1.2.8.4.6.8.5...2..75......4.1..6..3.....4.6.
45.....3....8.1....9...........5..9.2..7.....8.........1..4..........7.2...6..8..
.237....68...6.59.9.....7......4.97.3.7.96..2.........5..47.........2....8.......
..84...3....3.....9....157479...8........7..514.....2...9.6...2.5....4......9..56
.98.1....2......6.............3.2.5..84.........6.........4.8.93..5...........1..
..247..58..............1.4.....2...9528.9.4....9...1.........3.3....75..685..2...
4.....8.5.3..........7......2.....6.....5.4......1.......6.3.7.5..2.....1.9......
.2.3......63.....58.......15....9.3....7........1....8.879..26......6.7...6..7..4
1.....7.9.4...72..8.........7..1..6.3.......5.6..4..2.........8..53...7.7.2....46
4.....3.....8.2......7........1...8734.......6........5...6........1.4...82......
.......71.2.8........4.3...7...6..5....2..3..9........6...7.....8....4......5....
6..3.2....4.....8..........7.26............543.........8.15........8.2........7..
.47.8...1............6..7..6....357......5....1..6....28..4.....9.1...4.....2.69.
......8.17..2........5.6......7...5..1....3...8.......5......2..4..8....6...3....
38.6.......9.......2..3.51......5....3..1..6....4......17.5..8.......9.......7.32
...5...........5.697.....2...48.2...25.1...3..8..3.........4.7..13.5..9..2...31..
.2.......3.5.62..9.68...3...5..........64.8.2..47..9....3.....1.....6...17.43....
.8..4....3......1........2...5...4.69..1..8..2...........3.9....6....5.....2.....
..8.9.1...6.5...2......6....3.1.7.5.........9..4...3...5....2...7...3.8.2..7....4
4.....5.8.3..........7......2.....6.....5.8......1.......6.3.7.5..2.....1.8......
1.....3.8.6.4..............2.3.1...........958.........5.6...7.....8.2...4.......
1....6.8..64..........4...7....9.6...7.4..5..5...7.1...5....32.3....8...4........
249.6...3.3....2..8.......5.....6......2......1..4.82..9.5..7....4.....1.7...3...
...8....9.873...4.6..7.......85..97...........43..75.......3....3...145.4....2..1
...5.1....9....8...6.......4.1..........7..9........3.8.....1.5...2..4.....36....
......8.16..2........7.5......6...2..1....3...8.......2......7..3..8....5...4....
.476...5.8.3.....2.....9......8.5..6...1.....6.24......78...51...6....4..9...4..7
.....7.95.....1...86..2.....2..73..85......6...3..49..3.5...41724................
.4.5.....8...9..3..76.2.....146..........9..7.....36....1..4.5..6......3..71..2..
.834.........7..5...........4.1.8..........27...3.....2.6.5....5.....8........1..
..9.....3.....9...7.....5.6..65..4.....3......28......3..75.6..6...........12.3.8
.26.39......6....19.....7.......4..9.5....2....85.....3..2..9..4....762.........4
2.3.8....8..7...........1...6.5.7...4......3....1............82.5....6...1.......
6..3.2....1.....5..........7.26............843.........8.15........8.2........7..
1.....9...64..1.7..7..4.......3.....3.89..5....7....2.....6.7.9.....4.1....129.3.
.........9......84.623...5....6...453...1...6...9...7....1.....4.5..2....3.8....9
.2....5938..5..46.94..6...8..2.3.....6..8.73.7..2.........4.38..7....6..........5
9.4..5...25.6..1..31......8.7...9...4..26......147....7.......2...3..8.6.4.....9.
...52.....9...3..4......7...1.....4..8..453..6...1...87.2........8....32.4..8..1.
53..2.9...24.3..5...9..........1.827...7.........981.............64....91.2.5.43.
1....786...7..8.1.8..2....9........24...1......9..5...6.8..........5.9.......93.4
....5...11......7..6.....8......4.....9.1.3.....596.2..8..62..7..7......3.5.7.2..
.47.2....8....1....3....9.2.....5...6..81..5.....4.....7....3.4...9...1.4..27.8..
......94.....9...53....5.7..8.4..1..463...........7.8.8..7.....7......28.5.26....
.2......6....41.....78....1......7....37.....6..412....1..74..5..8.5..7......39..
1.....3.8.6.4..............2.3.1...........758.........7.5...6.....8.2...4.......
2....1.9..1..3.7..9..8...2.......85..6.4.........7...3.2.3...6....5.....1.9...2.5
..7..8.....6.2.3...3......9.1..5..6.....1.....7.9....2........4.83..4...26....51.
...36....85.......9.4..8........68.........17..9..45...1.5...6.4....9..2.....3...
34.6.......7.......2..8.57......5....7..1..2....4......36.2..1.......9.......7.82
......4.18..2........6.7......8...6..4....3...1.......6......2..5..1....7...3....
.4..5..67...1...4....2.....1..8..3........2...6...........4..5.3.....8..2........
.......4...2..4..1.7..5..9...3..7....4..6....6..1..8...2....1..85.9...6.....8...3
8..7....4.5....6............3.97...8....43..5....2.9....6......2...6...7.71..83.2
.8...4.5....7..3............1..85...6.....2......4....3.26............417........
....7..8...6...5...2...3.61.1...7..2..8..534.2..9.......2......58...6.3.4...1....
......8.16..2........7.5......6...2..1....3...8.......2......7..4..8....5...3....
.2..........6....3.74.8.........3..2.8..4..1.6..5.........1.78.5....9..........4.
.52..68.......7.2.......6....48..9..2..41......1.....8..61..38.....9...63..6..1.9
....1.78.5....9..........4..2..........6....3.74.8.........3..2.8..4..1.6..5.....
1.......3.6.3..7...7...5..121.7...9...7........8.1..2....8.64....9.2..6....4.....
4...7.1....19.46.5.....1......7....2..2.3....847..6....14...8.6.2....3..6...9....
......8.17..2........5.6......7...5..1....3...8.......5......2..3..8....6...4....
963......1....8......2.5....4.8......1....7......3..257......3...9.2.4.7......9..
15.3......7..4.2....4.72.....8.........9..1.8.1..8.79......38...........6....7423
..........5724...98....947...9..3...5..9..12...3.1.9...6....25....56.....7......6
....75....1..2.....4...3...5.....3.2...8...1.......6.....1..48.2........7........
6.....7.3.4.8.................5.4.8.7..2.....1.3.......2.....5.....7.9......1....
....6...4..6.3....1..4..5.77.....8.5...8.....6.8....9...2.9....4....32....97..1..
.32.....58..3.....9.428...1...4...39...6...5.....1.....2...67.8.....4....95....6.
...5.3.......6.7..5.8....1636..2.......4.1.......3...567....2.8..4.7.......2..5..
.5.3.7.4.1.........3.......5.8.3.61....8..5.9.6..1........4...6...6927....2...9..
..5..8..18......9.......78....4.....64....9......53..2.6.........138..5....9.714.
..........72.6.1....51...82.8...13..4.........37.9..1.....238..5.4..9.........79.
...658.....4......12............96.7...3..5....2.8...3..19..8..3.6.....4....473..
.2.3.......6..8.9.83.5........2...8.7.9..5........6..4.......1...1...4.22..7..8.9
.5..9....1.....6.....3.8.....8.4...9514.......3....2..........4.8...6..77..15..6.
.....2.......7...17..3...9.8..7......2.89.6...13..6....9..5.824.....891..........
3...8.......7....51..............36...2..4....7...........6.13..452...........8..
"""

    blank_start = """\
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
"""

    start4x4 = """\
1 . . 4
. 4 . 2
2 3 . 1
. . 2 .
"""

    @classmethod
    def run9x9(cls, settings):
        for i, pos in enumerate(cls.start_positions_9x9):
            print 'SudokuTest.start_positions_9x9[%i]:\n' % i
            run(Sudoku9x9, start_position=pos, settings=settings)
            print

    @classmethod
    def run_magictour(cls, settings):
        for i, pos in enumerate(cls.magictour.splitlines()):
            print 'SudokuTest.magictour line %i:\n' % (i + 1)
            run(Sudoku9x9, start_position=pos, settings=settings)
            print


if __name__ == '__main__':
    settings, args = process_command_line_options()
    if args:
        print 'Command line arguments ignored: %r' % args
    SudokuTest.run9x9(settings)
    SudokuTest.run_magictour(settings)