#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (C) 2021 Rod Engerstad

import random
from random import randint
from random import shuffle
import Tkinter as tk
#import sys
import math

from pzl_lib import PzlG
from pzl_lib import SettingsB
from pzl_lib import Preferences
from pzl_lib import MenuItem
from pzl_lib import Flasher
from pzl_lib import ViewB
from shakashaka_data import ModelData


class Sgg(object):
    """
    Globals
    """
    width = 10
    height = 10
    GIVE_HINT = 0
    APPLY_HINT = 1
    SOLVE = 0
    HINT = 1
    GUI_ACTIVE = True
    bugs = []
    algorithm_counts = [0 for i in range(18)]
    algorithm_scores = [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 2, 2, 3, 3, 1, 4]


class Settings(SettingsB):
    """
    General configuration and settings
    """
    def __init__(self):
        SettingsB.__init__(self)
        self.original_square_size = 54
        self.original_square_size = 500
        self.square_size = self.original_square_size
        self.dic = {}
        self.dic['text_colour'] = '#000000'
        self.dic['cause_colour'] = '#00d341'
        self.dic['effect_colour'] = '#ff0000'
        self.dic['button_colour'] = '#82c026'
        self.dic['error_colour'] = '#ff0000'
        self.dic['font_size'] = '2'
        self.menu_items = []
        self.preferences = Preferences(self.dic)
        self.preferences.load()
        self.load_menus()
        self.count = len(self.menu_items)
        self.set_fonts()
        self.make_help()
        self.make_tooltip()
        self.make_alg_help()

    def make_tooltip(self):
        self.tooltip_text = [
            _('Left-mouse random puzzle, right-mouse next puzzle'),
            _('Left mouse: give hint'),
            _('Check for errors, correct errors'),
            _('Show solution'),
            ]

    def make_alg_help(self):
        self.alg_text = [_("Alg_0, The environment of some clues forces particular triangles to be the only possibe answer."), _("Alg_1Each edge of the grid is restricted to two particular triangle types: top: types 1 and 2; bottom: types 3 and 4; left: types 2 and 3; right: types 1 and 4. If an edge cell has a triangle it determines which of the two possibilities must be occupied by its neigbouring edge cell."), _("Alg_2 If a clue is satisfied all of its remaining exposed faces must touch empty cells."), _("Alg_3 Find pairs of cells forming a 135 degree angle. Add the appropriate triangle to change the angle to 90 degrees."), _("Alg_4 Find pairs of cells on the edge of the grid which form a 135 degree angle. Add the appropriate triangle to change the angle to 90 degrees."), _("Alg_5 Find an unsatisfied clue which is touching exactly the number of blank cells it requires. If only one triangle type is compatible with one of the blank cells, it must be its solution."), _("Alg_6 A single blank cell on the edge of the grid with adjacent flat faces can only be empty."), _("Alg_7 An empty cell on the edge and blocked inwards can be set empty."), _("Alg_8 A 3 clue on the edge must have compatible triangles on each side."), _("Alg_9 A clue on the edge of the grid which requires only one more triangle to be satisfied and which has only one adjacent blank cell must have a particulat triangle type."), _("Alg_10 A pair of diagonal 4 clues or a diagonal 4 and a 3 clue must share a compatible triangle."), _("Alg_11 Find unsatisfied clues requiring one more triangle and having two adjacent blank cells. One of those must be set empty. If an empty cell is incompatible with either blank cell, the other one must be empty."), _("Alg_12 Once the dimensions of a rectangle are clear we can fill in the sides."), _("Alg_13 Once the dimensions of a rectangle start to emerge we can fill in the corners."), _("Alg_14 Shorter sides of emerging rectangles can be filled in."), _("Alg_15 Diagonal empty cells must either be in rectangles or have an orthogonal black diagonal."), _("Alg_16 A blank cell completely surrounded by filled cells or on an edge can be set empty."), _("Alg_17 This algorithm uses the data contained in Possible Patterns on the website and finds unsolved cells for which either only one triangle type or an empty cell is compatible with the surrounding cells.")]

    def make_help(self):
        self.help_text = _("The program starts with a randomly selected puzzle displayed. It will consist of white cells, black cells and black cells containing numbers with values 0 to 4. These are clue cells. When the puzzle is finished every white cell will be part of a rectangle delineated by black triangles and every clue cell of face value N will share an edge with N black triangles. Left-clicking in a quadrant of a white cell will add the corresponding triangle, middle-clicking will turn a white cell yellow, right-clicking will remove a triangle or turn a yellow cell white. In the toolbar the jigsaw symbol contains a menu, the next symbol starts a new game; the wand requests a hint, a second click on the wand will perform the hint operation; the thumb checks if the puzzle contains any errors and a second click will clear the errors; the sad smiley shows the solution; the entrybox shows the current puzzle number and can be used to select a new puzzle number; the number next to the puzzle number is the puzzle difficulty score. The clock ticks every 5 seconds but will be replaced by the algorithm code if a hint is requested. A left click on the hint button will request a hint. While the hint is active (shown by the wand icon leaning leftwards) clicking on the wand with the middle mouse button will cause a pop-up to appear containing a short explanation of the hint. Hints are shown by an orange question mark highlighting a cell whose solution can be deduced by the current state of the puzzle. A further left click on the wand will set that solution. The program contains 1000 puzzles roughly sorted in order of difficulty.")

    def load_menus(self):
        self.menu_items.append(MenuItem(
            _('Font Size'),
            [
                '2',
                '3',
                '5',
                '7',
                '8',
                '9',
                '10',
                '11',
                '12',
                '13'],
            '',
            [
                '2',
                '3',
                '5',
                '7',
                '8',
                '9',
                '10',
                '11',
                '12',
                '13'],
            'font_size'
        ))
        self.menu_items.append(MenuItem(
            _('Save Settings'),
            '',
            '',
            '',
            'save_settings'
        ))
        self.menu_items.append(MenuItem(
            _('Help'),
            '',
            '',
            '',
            'help'
        ))
        self.menu_items.append(MenuItem(
            _('About'),
            '',
            '',
            '',
            'about'
        ))

    def do_scaling(self):
        inc = 0.1
        mult = int(self.dic['font_size'])
        factor = 0.8 + mult * inc
        self.square_size = 40
        self.square_size = int(self.square_size * factor)


class Board(tk.Canvas):
    def __init__(self, parent, settings, width, height):
        self.parent = parent
        self.settings = settings
        self.datum_dic = {}
        tk.Canvas.__init__(self, self.parent, width=width,
                           height=height,
                           bg='white')
        self.grid(row=0, column=0)
        self.hint_id = None
        self.update_idletasks()

    def add_callbacks(self, func):
        self.bind("<ButtonRelease-1>", func)
        self.bind("<ButtonRelease-3>", func)
        self.bind("<ButtonRelease-2>", func)

    def my_destroy(self):
        self.destroy()

    def do_scale(self, value):
        return self.settings.square_size * value

    def scale_xy(self, value):
        return (self.settings.square_size * value[0],
                self.settings.square_size * value[1])

    def scale_xya(self, value, plus):
        return (self.settings.square_size * (value[0] + plus[0]),
                self.settings.square_size * (value[1] + plus[1]))

    def draw_grid(self):
        """
        Draw grid lines
        """
        fill = 'black'
        width = 1
        scale_x = self.do_scale(Sgg.width)
        for y in range(Sgg.height):
            scale_y = self.do_scale(y)
            jd = self.create_line(0, scale_y, scale_x, scale_y,
                                  width=width,
                                  fill=fill)
        scale_y = self.do_scale(Sgg.height)
        for x in range(Sgg.width):
            scale_x = self.do_scale(x)
            jd = self.create_line(scale_x, 0, scale_x, scale_y,
                                  width=width,
                                  fill=fill)

    def draw_it(self):
        """
        Draw the clue cells using the single 4-sided triangle
        and create hidden widgets for all the possible triangles
        in the other cells.
        """
        corners = [
            [(0, 0), (1, 0), (1, 1)],
            [(0, 0), (1, 0), (0, 1)],
            [(0, 0), (1, 1), (0, 1)],
            [(1, 0), (1, 1), (0, 1)]]

        fill = 'black'
        font = self.settings.value_font
        for key, datum in list(self.datum_dic.items()):
            if not datum.is_clue():
                if datum.corner == 5:
                    # Black cells
                    xys = [self.scale_xy(i) for i in datum.triangle]
                    jd = self.create_polygon(xys,
                                             fill=fill)
                else:
                    # triangles and Whites
                    for cid, corner in enumerate(corners):
                        xys = [self.scale_xya(i, key) for i in corner]
                        jd = self.create_polygon(xys,
                                                 fill=fill, state='hidden')
                        datum.add_corner(xys, jd)
                    # add centre
                    xys = [self.scale_xy(i) for i in datum.square]
                    jd = self.create_polygon(xys,
                                             fill='yellow', state='hidden')
                    datum.add_centre(xys, jd)
            else:
                # clue cells
                xys = [self.scale_xy(i) for i in datum.triangle]
                jd = self.create_polygon(xys,
                                         fill=fill)
                text = datum.clue
                x0 = datum.triangle[0][0]
                x1 = datum.triangle[1][0]
                y0 = datum.triangle[0][1]
                y1 = datum.triangle[2][1]
                dx = (x1 - x0) / 2.0
                dy = (y1 - y0) / 2.0
                xys = self.scale_xy((x0 + dx, y0 + dy))
                jd = self.create_text(xys, text=text, font=font, fill='white')
        self.draw_grid()
        self.update_idletasks()

    def show_hint(self, key):
        font = self.settings.value_font
        text = '?'
        x = key[0]
        y = key[1]
        triangle = [(x, y), (x + 1, y), (x + 1, y + 1),
                    (x, y + 1)]
        x0 = triangle[0][0]
        x1 = triangle[1][0]
        y0 = triangle[0][1]
        y1 = triangle[2][1]
        dx = (x1 - x0) / 2.0
        dy = (y1 - y0) / 2.0
        xys = self.scale_xy((x0 + dx, y0 + dy))
        jd = self.create_text(xys, text=text, font=font, fill='orange')
        self.hint_id = jd
        self.update_idletasks()

    def destroy_hint(self):
        if self.hint_id is not None:
            self.delete(self.hint_id)
            self.hint_id = None
        self.update_idletasks()


class View(ViewB):
    """
    Stick blobs on the screen and respond to callbacks
    """

    def __init__(self, master, settings):
        ViewB.__init__(self, master, settings)
        self.datum_dic = {}
        self.image_dic = {}
        self.make_image_dic()
        self.image_dic['start'] = [self.images.start_shakashaka, 0, True]
        self.do_layout(self)
        self.entry_box.activate()

    def make_board(self):
        """
        draw the squares
        """
        if self.board is not None:
            self.board.my_destroy()
        width = self.do_scale(Sgg.width)
        height = self.do_scale(Sgg.height)
        self.board = Board(self.top_frame, self.settings, width, height)
        self.board.draw_grid()

    def do_scale(self, value):
        return self.settings.square_size * value

    def update_clock_box(self, algorithm):
        value = '  '
        if algorithm is not None:
            value = algorithm
        self.clock.box.set_text(value)


class Datum(object):
    """
    Create unscaled x,y for the 4 triangles in a cell.
    numbered for the quadrants in wikipedia I, II, III, IV
          II  I
          III IV

           2  1
           3  4

    If the cell has no triangles value and guess = 0
    ie cells in boxes have corner == 0 and if they are set
    by hint or user guess == 0. If they are not set their
    guess will remain as None and should be counted as finished.

    Clues: -1 = 0
           -2 = 1
           -3 = 2
           -4 = 3
           -5 = 4

            5 means black cell but not clue

    For clue cells we create a single 4-sided triangle
    and we keep count of triangles added in done

    corner: 1, 2, 3, 4 for solveable cells, 0 for cells in boxes
            5 black cell, 6 clue cell
    """
    def __init__(self, key, value):
        self.key = key
        self.top_left = [99999, 99999]
        self.bottom_right = [-99999, -99999]
        x = key[0]
        y = key[1]
        self.square = [(x, y), (x + 1, y), (x + 1, y + 1),
                       (x, y + 1)]
        self.centre_jd = None
        self.guess = None
        self.start = ()
        self.end = ()
        self.corners = [((), ())]
        self.triangle = []
        self.extensions = []
        self.nbr_clues = []
        self.neighbours = []
        self.done = 0
        self.widget = None
        if value < 0:
            # clues
            self.clue = abs(value) - 1
            self.corner = 6
            triangle = [(x, y), (x + 1, y), (x + 1, y + 1),
                        (x, y + 1)]
        elif value == 5:
            # black cells
            self.clue = None
            self.corner = value
            self.guess = value
            triangle = [(x, y), (x + 1, y), (x + 1, y + 1),
                        (x, y + 1)]
        else:
            # has a triangle or is in box (<5)
            self.clue = None
            self.corner = value
            triangle = []
            # x, y is top left of square
            if self.corner == 1:
                triangle = [(x, y), (x + 1, y), (x + 1, y + 1)]
            elif self.corner == 2:
                triangle = [(x, y), (x + 1, y), (x, y + 1)]
            elif self.corner == 3:
                triangle = [(x, y), (x + 1, y + 1), (x, y + 1)]
            elif self.corner == 4:
                triangle = [(x + 1, y), (x + 1, y + 1), (x, y + 1)]
            elif self.corner == 0:
                triangle = [(x, y), (x + 1, y), (x + 1, y + 1),
                            (x, y + 1)]
        self.triangle = triangle

    def is_needed_corner(self, corner):
        """
        Return True if guess set to corner
        """
        return self.guess == corner

    def is_black(self, triangle_types):
        """
        Return True if cell is black ie clue or simply black
        or is a triangle of the right type
        """
        return self.is_clue() or self.guess == 5 or self.guess in\
            triangle_types

    def is_flat_edge(self, triangle_types):
        """
        Return True if given edge of this cell is black.
        ie has a triangle or a square
        edge (including squares which must be empty):
            4
           1 3
            2
        """
        ret = False
        if self.is_black(triangle_types) or self.guess == 0:
            ret = True
        return ret

    def get_type(self):
        """
        return
        """
        ret = None
        if self.is_clue():
            ret = 5
        elif self.guess is not None:
            ret = self.guess
        return ret

    def is_clue(self):
        return self.clue is not None

    def is_set_empty(self):
        return self.guess == 0

    def add_corner(self, xys, jd):
        """
        Given the scaled x,y of a corner's triangle and its widget jid
        add its cog and jid to its corners list.
        Also add the cell's scaled top left xy and bottom right xy
        to top_left and bottom right (we do this for each triangle
        even though they are all the same!).
        """
        cog_x = (xys[0][0] + xys[1][0] + xys[2][0]) / 3.0
        cog_y = (xys[0][1] + xys[1][1] + xys[2][1]) / 3.0
        self.corners.append(((cog_x, cog_y), jd))
        best_x = self.top_left[0]
        best_y = self.top_left[1]
        for i in xys:
            best_x = min(best_x, i[0])
            best_y = min(best_y, i[1])
        self.top_left[0] = best_x
        self.top_left[1] = best_y

        best_x = self.top_left[0]
        best_y = self.top_left[1]
        for i in xys:
            best_x = max(best_x, i[0])
            best_y = max(best_y, i[1])
        self.bottom_right[0] = best_x
        self.bottom_right[1] = best_y

    def add_centre(self, xys, jd):
        self.top_left[0] = xys[0][0]
        self.top_left[1] = xys[0][1]
        self.bottom_right[0] = xys[2][0]
        self.bottom_right[1] = xys[2][1]
        self.centre_jd = jd

    def centre_id(self, pos):
        """
        Given an x,y from the board check if the cursor is in this cell
        """
        ret = -1
        if pos[0] > self.top_left[0] and\
           pos[0] < self.bottom_right[0] and\
           pos[1] > self.top_left[1] and\
           pos[1] < self.bottom_right[1]:
            # the right cell
            ret = self.centre_jd
        return ret

    def triangle_id(self, pos):
        """
        Given an x,y from the board check if the cursor is in this cell
        and if so, return its corner id
        """
        ret = -1
        if pos[0] > self.top_left[0] and\
           pos[0] < self.bottom_right[0] and\
           pos[1] > self.top_left[1] and\
           pos[1] < self.bottom_right[1]:
            # the right cell
            nearest = 9999
            for corner_id, corner in enumerate(self.corners[1:]):
                cogs = corner[0]
                dist_x = abs(pos[0] - cogs[0])
                dist_y = abs(pos[1] - cogs[1])
                dist = math.sqrt(dist_x * dist_x + dist_y * dist_y)
                if dist < nearest:
                    nearest = dist
                    ret = corner_id + 1
        return ret

    def set_empty(self):
        """
        Set guess to 0 and widget to centre_widget if
        correct cell type and guess is None
        """

        if self.corner < 5 and self.guess is None:
            self.guess = 0
            if Sgg.GUI_ACTIVE:
                self.widget = self.centre_jd

    def set_guess(self, guess):
        """
        Set guess to guess and widget to centre_widget or corner if
        correct cell type and guess is None
        """

        if self.corner < 5 and self.guess is None:
            self.guess = guess
            if Sgg.GUI_ACTIVE:
                if guess == 0:
                    self.widget = self.centre_jd
                else:
                    self.widget = self.corners[guess][1]

    def unset_guess(self):
        """
        Set guess to None and widget to centre_widget or corner if
        correct cell type and guess is currently set.
        """
        # fixme: any reason why guess 0 treated differently???

        if self.corner < 5 and self.guess is not None:
            if Sgg.GUI_ACTIVE:
                if self.guess == 0:
                    self.widget = self.centre_jd
                else:
                    self.widget = self.corners[self.guess][1]
            self.guess = None

    def set_answer(self):
        self.guess = self.corner

    def is_correct(self):
        """
        """
        ret = True
        if not self.is_clue():
            if self.guess is not None and self.guess != self.corner:
                ret = False
        return ret

    def is_solveable(self):
        ret = True
        if self.is_clue() or self.corner == 5:
            ret = False
        return ret

    def requires_solving(self):
        ret = False
        if self.is_solveable() and not self.is_solved():
            ret = True
        return ret

    def is_solved(self):
        """
        is_solved applies to cells which are not clues or not simply black
        Note it does not require the solution to be correct!
        But is only used in algorithms, and they only operate when no erros
        are present.
        """
        ret = False
        if self.is_solveable() and\
           self.guess is not None:
            ret = True
        return ret

    def is_finished(self):
        """
        is_finished applies to cells which are not clues or not simply black
        but is complicated by cells in boxes which may not require solving
        and simply get left empty with corner == 0 and guess == None
        """

        ret = False
        if (self.corner in [1, 2, 3, 4] and self.guess == self.corner) or\
           (self.corner == 0 and self.guess not in [1, 2, 3, 4]) or\
           self.corner in [5, 6]:
            ret = True
        return ret

    def is_satisfied(self):
        ret = False
        if self.is_clue() and self.clue == self.done:
            ret = True
        return ret

    def has_triangle(self):
        return self.guess in [1, 2, 3, 4]

    def is_empty(self):
        """
        if cell unsolved or set to be empty?
        """
        ret = False
        if self.is_solveable() and not self.is_solved():
            ret = True
        return ret

    def is_virgin(self):
        ret = False
        if self.is_solveable() and not self.is_solved():
            ret = True
        return ret

    def get_widget(self):
        return self.widget

    def unset_widget(self):
        """
        There are 2 types of widget: those for triangles and
        those for yellow squares. Their ids are saved in self.widget.
        We set the widget when the cell is changed so that the graphics
        routines know which to use, and then we set it to None immediately
        after use with this function.
        """
        self.widget = None

    def set_done(self, done):
        self.done = done

    def get_done(self):
        return self.done


class Edge(object):
    """
    Edges make boxes and are numbered after their edge triangles.
    Start and end identify corners
    """
    def __init__(self, key, triangle):
        self.triangle = triangle
        self.keys = []
        if key:
            self.keys.append(key)
        self.start = ()
        self.end = ()
        self.start_corner = ()
        self.end_corner = ()
        self.next_start = ()
        self.in_box = False

    def add_key(self, new_key):
        self.keys.append(new_key)

    def put_in_box(self):
        self.in_box = True

    def is_in_box(self):
        return self.in_box

    def edge_sort(self):
        if self.triangle in [3, 4]:
            self.keys.sort(key=lambda x: x[0])
        else:
            self.keys.sort(key=lambda x: x[0], reverse=True)

    def get_length(self):
        """
        Can only get length if both corners are set
        """
        ret = None
        if self.start_corner and self.end_corner:
            ret = len(self.keys)
        return ret

    def get_length_f(self):
        """
        """
        ret = None
        if self.start_corner:
            ret = len(self.keys)
        return ret

    def get_length_r(self):
        """
        """
        ret = None
        if self.end_corner:
            ret = len(self.keys)
        return ret

    """
    def print_me(self):
        print
        print self.triangle, self.keys
        print 'start', self.start, 'end', self.end
        print 'start_corner', self.start_corner, 'end_corner', self.end_corner
        print 'in_box', self.in_box
    """


class VBox(object):
    def __init__(self, key):
        self.keys = [key]
        self.width = 0
        self.height = 0
        self.finished = False

    def add_key(self, key):
        self.keys.append(key)

    def get_sizes(self):
        min_x = 9999
        max_x = -9999
        min_y = 9999
        max_y = -9999
        for key in self.keys:
            min_x = min(min_x, key[0])
            max_x = max(max_x, key[0])
            min_y = min(min_y, key[1])
            max_y = max(max_y, key[1])
        self.width = max_x - min_x + 1
        self.height = max_y - min_y + 1

    def set_finished(self):
        self.finished = True

    def is_finished(self):
        return self.finished

    #def print_me(self):
    #    print 'VBox', self.width, self.height, self.finished, self.keys


class Box(object):
    def __init__(self, edges):
        self.edges = edges

    def add_edge(self, edge):
        edge.put_in_box()
        self.edges.append(edge)

    def is_finished(self):
        ret = False
        if len(self.edges) == 4:
            ret = True
            for edge in self.edges:
                if not edge.start_corner or not edge.end_corner:
                    ret = False
        return ret

    def get_edge(self, triangle):
        ret = None
        for edge in self.edges:
            if edge.triangle == triangle:
                ret = edge
                break
        return ret

    def get_edges(self, triangle):
        ret = []
        for edge in self.edges:
            if edge.triangle == triangle:
                ret.append(edge)
        return ret

    def insert_edge(self, new_edge):
        """
        Replace edge (may be more than one to replace)
        """
        triangle = new_edge.triangle
        new_edges = []
        done = False
        for edge in self.edges:
            if edge.triangle < triangle:
                new_edges.append(edge)
            else:
                new_edges.append(new_edge)
                done = True
            if edge.triangle > triangle:
                new_edges.append(edge)
        if not done:
            new_edges.append(new_edge)
        self.edges = new_edges

    """
    def print_me(self):
        print 'box'
        for edge in self.edges:
            edge.print_me()
    """


class LineKeys(object):
    """
    Given the keys for the ends of an edge in a graph
    return all the keys it intersects.
    """
    def __init__(self, ends, inc):

        keys = []
        key = ends[0]
        while key != ends[1]:
            keys.append(key)
            key = Model.add_keys(key, inc)
        keys.append(key)
        self.keys = keys


class Model(object):

    cell_neighbours = [(-1, 0), (0, 1), (1, 0), (0, -1)]
    cell_neighbours_2 = [(-1, -1), (1, -1), (1, 0), (-1, 1)]
    all_neighbours = [(1, -1), (0, -1), (-1, -1), (-1, 0),
                      (-1, 1), (0, 1), (1, 1), (1, 0)]
    """
    bad_dic {
             triangle:[bad triangles for 8 positions anticlockwise from 1, -1]
            }
    """

    bad_dic = {
        1: [[1], [1, 2], [2], [1, 3, 4, 5], [1, 2, 4, 5], [1, 2, 3, 5],
            [4], [1, 4]],
        2: [[1], [1, 2], [2], [2, 3], [3], [1, 2, 4, 5], [1, 2, 3, 5],
            [2, 3, 4, 5]],
        3: [[2, 3, 4, 5], [1, 3, 4, 5], [2], [2, 3], [3], [3, 4], [4],
            [1, 2, 3, 5]],
        4: [[1], [2, 3, 4, 5], [1, 3, 4, 5], [1, 2, 4, 5], [3], [3, 4],
            [4], [1, 4]],
        5: [[1], [1, 2], [2], [2, 3], [3], [3, 4], [4], [1, 4]],
        0: [[1], [1, 2], [2], [2, 3], [3], [3, 4], [4], [1, 4]],
        None: [[], [], [], [], [], [], [], []],
    }

    def __init__(self, model_data):
        self.model_data = model_data
        self.difficulty = None
        self.width = Sgg.width
        self.height = Sgg.height
        self.status = None
        self.errors = []
        self.datum_dic = {}
        self.hint_status = Sgg.GIVE_HINT
        self.boxes = []
        self.graph_dic = {}
        self.left_1 = []
        self.left_2 = []
        self.right_1 = []
        self.right_2 = []
        self.vboxes = []
        self.oobs = []
        self.edge_dic = {}
        self.hint = []
        self.algorithm = None
        self.algorithms = [
            (self.alg_0),
            (self.alg_1),
            (self.alg_2),
            (self.alg_3),
            (self.alg_4),
            (self.alg_5),
            (self.alg_6),
            (self.alg_7),
            (self.alg_8),
            (self.alg_9),
            (self.alg_10),
            (self.alg_11),
            (self.alg_12),
            (self.alg_13),
            (self.alg_14),
            (self.alg_15),
            (self.alg_16),
            (self.alg_17),
            ]

        self.zero_algorithm_counts()

    @staticmethod
    def add_keys(key_0, key_1):
        return key_0[0] + key_1[0], key_0[1] + key_1[1]

    def get_vbox(self, key):
        ret = None
        for box in self.vboxes:
            if key in box.keys:
                ret = box
                break
        return ret

    def get_vboxes(self):
        """
        Get vertical boxes.
        The first key will be the top left, the last bottom right
        """
        self.vboxes = []
        for y in range(self.height):
            for x in range(self.width):
                key = x, y
                if self.datum_dic[key].guess == 0:
                    done = False
                    # got a set_empty cell
                    key_up = key[0], key[1] - 1
                    if self.key_in_range(key_up):
                        box = self.get_vbox(key_up)
                        if box is not None:
                            box.add_key(key)
                            done = True
                    key_left = key[0] - 1, key[1]
                    if self.key_in_range(key_left):
                        box = self.get_vbox(key_left)
                        if box is not None:
                            box.add_key(key)
                            done = True
                    if not done:
                        # new box
                        self.vboxes.append(VBox(key))
        self.get_oobs()

    def get_oobs(self):
        """
        Examine box dimensions.
        One Black cell touching a side is sufficient to establish
        a dimension as we can only have rectangles.
        """
        self.oobs = []

        for box in self.vboxes:
            box.get_sizes()
            # sizes set max extent of the box cells
            # but we need to touch a Black cell to be sure.
            left = box.keys[0][0]
            right = left + box.width - 1
            top = box.keys[0][1]
            bot = top + box.height - 1
            vleft = False
            vright = False
            vtop = False
            vbot = False
            for key in box.keys:
                if not vleft:
                    if key[0] == left:
                        oob = left - 1, key[1]
                        if self.key_in_range(oob):
                            if self.datum_dic[oob].is_black([1, 2, 3, 4]):
                                vleft = True
                        else:
                            vleft = True
                if not vright:
                    if key[0] == right:
                        oob = right + 1, key[1]
                        if self.key_in_range(oob):
                            if self.datum_dic[oob].is_black([1, 2, 3, 4]):
                                vright = True
                        else:
                            vright = True
                if not vtop:
                    if key[1] == top:
                        oob = key[0], top - 1
                        if self.key_in_range(oob):
                            if self.datum_dic[oob].is_black([1, 2, 3, 4]):
                                vtop = True
                        else:
                            vtop = True
                if not vbot:
                    if key[1] == bot:
                        oob = key[0], bot + 1
                        if self.key_in_range(oob):
                            if self.datum_dic[oob].is_black([1, 2, 3, 4]):
                                vbot = True
                        else:
                            vbot = True
            if not vleft or not vright:
                box.width = 0
            if not vtop or not vbot:
                box.height = 0
            if box.width and box.height:
                box.set_finished()

        for box in self.vboxes:
            left = box.keys[0][0]
            right = left + box.width - 1
            top = box.keys[0][1]
            bot = top + box.height - 1
            for key in box.keys:
                if box.width != 0 and key[0] == left:
                    oob = left - 1, key[1]
                    if self.key_in_range(oob) and\
                       self.datum_dic[oob].is_virgin():
                        self.oobs.append(oob)
                if box.width != 0 and key[0] == right:
                    oob = right + 1, key[1]
                    if self.key_in_range(oob) and\
                       self.datum_dic[oob].is_virgin():
                        self.oobs.append(oob)
                if box.height != 0 and key[1] == top:
                    oob = key[0], top - 1
                    if self.key_in_range(oob) and\
                       self.datum_dic[oob].is_virgin():
                        self.oobs.append(oob)
                if box.height != 0 and key[1] == bot:
                    oob = key[0], bot + 1
                    if self.key_in_range(oob) and\
                       self.datum_dic[oob].is_virgin():
                        self.oobs.append(oob)

    def get_edges(self):
        """
        edge_dic { key: edge,...}
        incs_dic {triangle: back_inc, top_corner_inc, ct, bottom_corner_inc, ct}
        ct is code of the corner triangle
        """
        incs_dic = {1: [[-1, -1], [0, 1, 4], [-1, 0, 2]],
                    2: [[1, -1], [1, 0, 1], [0, 1, 3]],
                    3: [[-1, -1], [0, -1, 2], [1, 0, 4]],
                    4: [[1, -1], [-1, 0, 3], [0, -1, 1]]}

        self.edge_dic = {}
        done = []
        for y in range(self.height):
            for x in range(self.width):
                key = x, y
                datum = self.datum_dic[key]
                guess = datum.guess
                if guess in [1, 2, 3, 4]:
                    placed = False
                    #
                    # look along this edge
                    #
                    inc = incs_dic[guess][0]
                    back_key = Model.add_keys(key, inc)
                    if self.key_in_range(back_key):
                        if back_key in self.edge_dic:
                            back_datum = self.datum_dic[back_key]
                            #
                            # same slope?
                            #
                            if back_datum.guess == datum.guess:
                                this_edge = self.edge_dic[back_key]
                                this_edge.add_key(key)
                                self.edge_dic[key] = this_edge
                                placed = True
                                del self.edge_dic[back_key]
                            else:
                                this_edge = Edge(key, guess)
                                self.edge_dic[key] = this_edge
                                placed = True
                        elif not placed:
                            this_edge = Edge(key, guess)
                            self.edge_dic[key] = this_edge
                            placed = True
                    elif not placed:
                        this_edge = Edge(key, guess)
                        self.edge_dic[key] = this_edge

        #
        # Find corners (if they exist)
        #
        done = []
        for ekey, edge in list(self.edge_dic.items()):
            if edge not in done:
                # sort edges anticlockwise
                edge.edge_sort()
                key = edge.keys[0]
                datum = self.datum_dic[key]
                guess = datum.guess
                inc = incs_dic[guess][1]
                back_key = Model.add_keys(key, inc)
                back_datum = self.datum_dic[back_key]
                if back_datum.guess == inc[2]:
                    edge.start = key
                    edge.start_corner = back_key

                key = edge.keys[-1]
                datum = self.datum_dic[key]
                guess = datum.guess
                inc = incs_dic[guess][2]
                back_key = Model.add_keys(key, inc)
                back_datum = self.datum_dic[back_key]
                if back_datum.guess == inc[2]:
                    edge.end = key
                    edge.end_corner = back_key

                edge.next_start = back_key
                done.append(done)

        self.get_boxes()

    def box_find(self, job, box_in):
        """
        job == 1
          start
        job == 2
          end
        """
        ret = None
        if job == 1:
            key = box_in.edges[-1].end_corner
            for box in self.boxes:
                if box is not box_in:
                    for edge in box.edges:
                        if edge.start == key:
                            ret = box
        elif job == 2:
            key = box_in.edges[0].start_corner
            for box in self.boxes:
                if box is not box_in:
                    for edge in box.edges:
                        if edge.end == key:
                            ret = box
        return ret

    def compare_ends(self):
        res = None
        for box in self.boxes:
            if not box.is_finished():
                edge = box.edges[-1]
                if edge.end_corner:
                    res = self.box_find(1, box)
                    if res is not None:
                        break
                edge = box.edges[0]
                if edge.start_corner:
                    res = self.box_find(2, box)
                    if res is not None:
                        break
        if res is not None:
            return box, res

        return None, None

    def join_boxes_1(self, box_0, box_1):
        for edge in box_1.edges:
            box_0.edges.append(edge)
        self.boxes.remove(box_1)

    def join_boxes(self):
        while True:
            box_0, box_1 = self.compare_ends()
            if box_0 is not None:
                if box_0.edges[0].triangle < box_1.edges[0].triangle:
                    self.join_boxes_1(box_0, box_1)
                else:
                    self.join_boxes_1(box_1, box_0)
            else:
                return

    def fill_edges(self):
        """
        Check to see if edges are complete

        """
        incs_dic = {1: [-1, -1],
                    2: [-1, 1],
                    3: [1, 1],
                    4: [1, -1]}
        for box in self.boxes:
            for edge in box.edges:
                inc = incs_dic[edge.triangle]
                for i, key in enumerate(edge.keys):
                    next_key = Model.add_keys(key, inc)
                    if self.key_in_range(next_key):
                        if i + 1 < len(edge.keys):
                            if edge.keys[i + 1] != next_key:
                                self.hint = [next_key, edge.triangle]
                                return

    def tidy_boxes_1(self):
        ret = False
        for box in self.boxes:
            if not box.is_finished():
                # do any boxes have multiple edges for one side ?
                counts_dic = {1: 0, 2: 0, 3: 0, 4: 0}
                for edge in box.edges:
                    counts_dic[edge.triangle] += 1
                for triangle, count in list(counts_dic.items()):
                    if count > 1:
                        # multiple edges for one side
                        # need to create a single replacement edge
                        new_keys = []
                        new_edge = Edge((), triangle)
                        # join the keys, if possible set corners
                        for edge in box.edges:
                            if edge.triangle == triangle:
                                new_keys += edge.keys
                                if edge.start_corner:
                                    new_edge.start_corner = edge.start_corner
                                if edge.end_corner:
                                    new_edge.end_corner = edge.end_corner
                        new_edge.keys = new_keys
                        new_edge.edge_sort()
                        # remove old edges by creating new list
                        remove = box.get_edges(triangle)
                        new_edges = []
                        for edge in box.edges:
                            if edge not in remove:
                                new_edges.append(edge)
                        box.edges = new_edges
                        # insert the new edge
                        new_edge.in_box = True
                        box.insert_edge(new_edge)
                        ret = True
                        # if we've changed self.boxes we must leave loop
                        break
            if ret:
                break
        return ret

    def tidy_boxes(self):
        while True:
            ret = self.tidy_boxes_1()
            if not ret:
                return
            else:
                self.fill_edges()
                if self.hint:
                    return

    def get_boxes(self):
        """
        1. Initialise boxes using edges of type 1
        2. for each edge of type 1 try to find the next 2,3,4
        """
        self.hint = []
        self.boxes = []
        for triangle in range(1, 5):
            for box in self.boxes:
                last_edge = box.edges[-1]
                if last_edge.triangle == triangle - 1:
                    # look for extension of this box
                    for ekey, edge in list(self.edge_dic.items()):
                        if edge.triangle == triangle:
                            # got needed triangle type
                            # its first cell should match edge.next_start
                            if edge.keys[0] == last_edge.next_start:
                                box.add_edge(edge)
                                edge.put_in_box()
                                break

            # deal with those that did not get put in a box
            # including all triangle 1s
            for ekey, edge in list(self.edge_dic.items()):
                if not edge.is_in_box():
                    if edge.triangle == triangle:
                        # got needed triangle type
                        box = Box([edge])
                        self.boxes.append(box)
                        edge.put_in_box()

    def extend_edge(self, edge, triangle):
        inc_dic = {1: (-1, -1), 2: (-1, 1), 3: (1, 1), 4: (1, -1)}
        inc = inc_dic[triangle]
        last_key = edge.keys[-1]
        next_key = Model.add_keys(last_key, inc)
        self.hint = [next_key, triangle]

    def prepend_edge(self, edge, triangle):
        inc_dic = {1: (1, 1), 2: (1, -1), 3: (-1, -1), 4: (1, -1)}
        inc = inc_dic[triangle]
        last_key = edge.keys[0]
        next_key = Model.add_keys(last_key, inc)
        self.hint = [next_key, triangle]

    def fix_lengths_1(self, box, length_dic, data):
        pair = data[0]
        if length_dic[pair[0]] is None and length_dic[pair[1]] is not None:
            triangle = pair[0]
            oppos = pair[1]
            length = length_dic[oppos] - 1
            edge = box.get_edge(triangle)
            if edge is not None:
                # is start done?
                if edge.start_corner:
                    # can extend end
                    inc = data[1]
                    end = edge.start
                    new = end[0] + inc[0] * length, end[1] + inc[1] * length
                    if self.datum_dic[new].is_virgin():
                        self.hint = [new, triangle]
                else:
                    # can extend start
                    inc = data[2]
                    end = edge.end
                    new = end[0] + inc[0] * length, end[1] + inc[1] * length
                    if self.datum_dic[new].is_virgin():
                        self.hint = [new, triangle]

    def fix_lengths(self, box):
        """
        Try to work out lengths from opposite sides.
        """
        length_dic = {1: None, 2: None, 3: None, 4: None}
        self.hint = []
        for edge in box.edges:
            length_dic[edge.triangle] = edge.get_length()
        self.fix_lengths_1(box, length_dic,
                           [[1, 3], (-1, -1), (1, 1)])
        if not self.hint:
            self.fix_lengths_1(box, length_dic,
                               [[3, 1], (1, 1), (-1, -1)])
        if not self.hint:
            self.fix_lengths_1(box, length_dic,
                               [[2, 4], (-1, 1), (1, -1)])
        if not self.hint:
            self.fix_lengths_1(box, length_dic,
                               [[4, 2], (1, -1), (-1, 1)])

    def extend_edges(self, box):
        """
        Try to work out lengths from opposite sides.
        """
        self.hint = []
        length_f_dic = {1: None, 2: None, 3: None, 4: None}
        length_r_dic = {1: None, 2: None, 3: None, 4: None}
        for edge in box.edges:
            length_f_dic[edge.triangle] = edge.get_length_f()
            length_r_dic[edge.triangle] = edge.get_length_r()
        if length_f_dic[3] is None and length_r_dic[3] is not None and\
           length_r_dic[1] is None and length_f_dic[1] is not None:
            if length_r_dic[3] < length_f_dic[1]:
                triangle = 3
                edge = box.get_edge(triangle)
                self.prepend_edge(edge, triangle)

            elif length_r_dic[3] > length_f_dic[1]:
                triangle = 1
                edge = box.get_edge(triangle)
                self.extend_edge(edge, triangle)

        if not self.hint and\
           length_f_dic[2] is None and length_r_dic[2] is not None and\
           length_r_dic[4] is None and length_f_dic[4] is not None:
            if length_r_dic[2] < length_f_dic[4]:
                triangle = 2
                edge = box.get_edge(triangle)
                self.prepend_edge(edge, triangle)

            elif length_r_dic[2] > length_f_dic[4]:
                triangle = 4
                edge = box.get_edge(triangle)
                self.extend_edge(edge, triangle)

    def make_puzzle(self, puzzle):
        self.make_puzzle_1(puzzle)
        self.make_puzzle_2()

    def make_puzzle_1(self, puzzle):
        self.difficulty = puzzle[0]
        self.width = puzzle[1]
        self.height = puzzle[2]

        Sgg.width = self.width
        Sgg.height = self.height
        item = 3
        for y in range(self.height):
            for x in range(self.width):
                key = (x, y)
                self.datum_dic[key] = Datum(key, puzzle[item])
                item += 1

    def make_puzzle_2(self):
        self.do_clue_extensions()
        self.do_nbr_clues()
        self.add_neighbours()

    def make_empty_puzzle(self):
        triangle = 5
        for y in range(self.height):
            for x in range(self.width):
                key = (x, y)
                self.datum_dic[key] = Datum(key, triangle)

    def make_g_puzzle(self):
        triangle = 0
        puzzle_dic = {}
        for y in range(self.height):
            for x in range(self.width):
                key = (x, y)
                triangle = self.datum_dic[key].corner
                puzzle_dic[key] = Datum(key, triangle)
        self.datum_dic = puzzle_dic
        self.add_neighbours()

    def get_neighbours(self, key):
        """
        Order: up screen (towards origin!) , then clockwise
        """
        neighbours = []
        x = key[0]
        y = key[1]

        neighbour_key = (x, y - 1)
        if neighbour_key in self.datum_dic:
            neighbours.append(neighbour_key)
        neighbour_key = (x - 1, y)
        if neighbour_key in self.datum_dic:
            neighbours.append(neighbour_key)
        neighbour_key = (x, y + 1)
        if neighbour_key in self.datum_dic:
            neighbours.append(neighbour_key)
        neighbour_key = (x + 1, y)
        if neighbour_key in self.datum_dic:
            neighbours.append(neighbour_key)
        return neighbours

    def add_neighbours(self):
        for key, datum in list(self.datum_dic.items()):
            datum.neighbours = self.get_neighbours(key)

    def do_nbr_clues(self):
        """
        Need to know if neighbouring clues are satisfied
        so here we find them.
        """
        neighbours = Model.cell_neighbours
        neighbours_2 = Model.cell_neighbours_2
        for key, datum in list(self.datum_dic.items()):
            nbr_clues = []
            if not datum.is_clue():
                for nbr in neighbours:
                    nbr_key = Model.add_keys(key, nbr)
                    if self.in_grid_1(nbr_key):
                        nbr_datum = self.datum_dic[nbr_key]
                        if nbr_datum.is_clue():
                            nbr_clues.append(nbr_datum)
                for nbr in neighbours_2:
                    nbr_key = Model.add_keys(key, nbr)
                    if self.in_grid_1(nbr_key):
                        nbr_datum = self.datum_dic[nbr_key]
                        if nbr_datum.is_clue():
                            nbr_clues.append(nbr_datum)
            datum.nbr_clues = nbr_clues

    def nearest_cell(self, pos):
        for key, datum in list(self.datum_dic.items()):
            if not datum.is_clue():
                triangle_id = datum.centre_id(pos)
                if triangle_id != -1:
                    break
        return key, triangle_id

    def nearest_triangle(self, pos):
        for key, datum in list(self.datum_dic.items()):
            if not datum.is_clue():
                triangle_id = datum.triangle_id(pos)
                if triangle_id != -1:
                    break
        return key, triangle_id

    def set_empty(self, board_x, board_y):
        """
        Set empty and return widget_id
        """
        key, triangle_id = self.nearest_cell((board_x, board_y))
        self.datum_dic[key].set_empty()
        widget_id = self.datum_dic[key].get_widget()

        if widget_id is not None:
            text = 'Board_event: add user triangle to ' +\
                   str(key[0]) + ',' + str(key[1]) + ' ' + str(triangle_id)
            PzlG.logger.logger.info(text)
        return widget_id

    def set_guess(self, board_x, board_y):
        """
        Set guess and return widget_id
        """

        key, triangle_id = self.nearest_triangle((board_x, board_y))
        self.datum_dic[key].set_guess(triangle_id)
        widget_id = self.datum_dic[key].get_widget()
        self.datum_dic[key].unset_widget()
        if widget_id is not None:
            text = 'Board_event: add user triangle to ' +\
                   str(key[0]) + ',' + str(key[1]) + ' ' + str(triangle_id)
            PzlG.logger.logger.info(text)

        return widget_id

    def unset_guess(self, board_x, board_y):
        """
        Unset guess and return widget_id
        """

        key, triangle_id = self.nearest_triangle((board_x, board_y))
        self.datum_dic[key].unset_guess()
        widget_id = self.datum_dic[key].get_widget()
        self.datum_dic[key].unset_widget()
        if widget_id is not None:
            text = 'Board_event: remove user triangle at ' +\
                   str(key[0]) + ',' + str(key[1]) + ' ' + str(triangle_id)
            PzlG.logger.logger.info(text)

        if self.errors and key in self.errors:
            self.errors.remove(key)
        return widget_id

    def unset_errors(self):
        for error in self.errors:
            self.datum_dic[error].unset_guess()

    def get_done_1(self):
        """
        For each clue count the number of set shapes and set datum.done
        Return a list of unfinished clues.
        """
        neighbours = Model.cell_neighbours
        unfinished = []
        for key, datum in list(self.datum_dic.items()):
            if datum.is_clue():
                done = 0
                undone = []
                for nbr in neighbours:
                    nbr_key = Model.add_keys(key, nbr)
                    if self.in_grid(nbr_key):
                        nbr_datum = self.datum_dic[nbr_key]
                        if nbr_datum.has_triangle():
                            done += 1
                        else:
                            if not nbr_datum.is_set_empty() and\
                               nbr_datum.guess != 5:
                                undone.append(nbr_key)
                datum.set_done(done)
                if datum.get_done() != datum.clue:
                    unfinished.append([key, undone])
        return unfinished

    def get_done_2(self):
        """
        For each set_empty cell return its unsolved neighbours.
        """
        neighbours = Model.cell_neighbours
        unfinished = []
        for key, datum in list(self.datum_dic.items()):
            if datum.is_set_empty():
                done = 0
                undone = []
                for nbr in neighbours:
                    nbr_key = Model.add_keys(key, nbr)
                    if self.in_grid(nbr_key):
                        nbr_datum = self.datum_dic[nbr_key]
                        if nbr_datum.has_triangle():
                            done += 1
                        else:
                            if not nbr_datum.is_set_empty() and\
                               nbr_datum.guess != 5:
                                undone.append(nbr_key)
                datum.set_done(done)
                if datum.get_done() != datum.clue:
                    unfinished.append([key, undone])
        return unfinished

    def in_grid(self, key):
        ret = False
        if key[0] > -1 and key[0] < Sgg.width and\
           key[1] > -1 and key[1] < Sgg.height and\
           not self.datum_dic[key].is_clue():
            ret = True
        return ret

    def in_grid_1(self, key):
        ret = False
        if key[0] > -1 and key[0] < Sgg.width and\
           key[1] > -1 and key[1] < Sgg.height:
            ret = True
        return ret

    def key_in_graph(self, key):
        ret = False
        if key[0] > -1 and key[0] < Sgg.width and\
           key[1] > -1 and key[1] < Sgg.height:
            ret = True
        return ret

    def triangle_ok(self, key, triangle_id):
        """
        Is this cell vacant and does this triangle face space?
        """

        faces = [(), (-1, 1), (1, 1), (1, -1), (-1, -1)]
        face = faces[triangle_id]
        face_key = Model.add_keys(key, face)
        return self.in_grid(face_key)

    def do_clue_extensions(self):
        all_extensions = [((0, -1), (3, 4)), ((0, 1), (1, 2)),
                          ((-1, 0), (1, 4)), ((1, 0), (2, 3))]
        for key, datum in list(self.datum_dic.items()):
            if datum.is_clue():
                my_extensions = []
                for extension in all_extensions:
                    pos = extension[0]
                    e_pos = Model.add_keys(key, pos)
                    if self.in_grid(e_pos):
                        for triangle_id in extension[1]:
                            if self.triangle_ok(e_pos, triangle_id):
                                my_extensions.append([e_pos, triangle_id])
                datum.extensions = my_extensions

    def alg_0(self):
        """
        Are there clues with unavoidable choices?
        """
        self.hint = []
        for key, datum in list(self.datum_dic.items()):
            if datum.is_clue():
                if datum.clue == len(datum.extensions):
                    for extension in datum.extensions:
                        hint_key = extension[0]
                        hint_datum = self.datum_dic[hint_key]
                        if hint_datum.requires_solving():
                            hint_triangle = extension[1]
                            self.hint = [hint_key, hint_triangle]
                            break
            if self.hint:
                break

    def alg_1(self):
        """
        Which triangles are compatible with those on the edge of the board?
        Only types 1 and 2 can occur along the top edge.
        Only types 3 and 4 can occur along the bottom edge.
        Only types 2 and 3 can occur along the left edge.
        Only types 1 and 4 can occur along the right edge.
        Any adjacent edge triangle will determine which type site next to
        it on the edge.
        """
        across = [[[Sgg.width, 0], [2, 1]],
                  [[Sgg.width, Sgg.height - 1], [3, 4]]]
        down = [[[0, Sgg.height], [2, 3]],
                [[Sgg.width - 1, Sgg.height], [1, 4]]]
        for edge in across:
            y = edge[0][1]
            xss = edge[0][0]
            pair = edge[1]
            for x in range(xss):
                key = (x, y)
                datum = self.datum_dic[key]
                if datum.is_solved():
                    kp1 = (x + 1, y)
                    if self.in_grid(kp1):
                        datum_1 = self.datum_dic[kp1]
                        if datum_1.requires_solving():
                            if datum.guess == pair[0]:
                                self.hint = [kp1, pair[1]]
                        if self.hint:
                            break
            if self.hint:
                return
        for edge in down:
            x = edge[0][0]
            yss = edge[0][1]
            pair = edge[1]
            for y in range(yss):
                key = (x, y)
                datum = self.datum_dic[key]
                if datum.is_solved():
                    kp1 = (x, y + 1)
                    if self.in_grid(kp1):
                        datum_1 = self.datum_dic[kp1]
                        if datum_1.requires_solving():
                            if datum.guess == pair[0]:
                                self.hint = [kp1, pair[1]]
                        if self.hint:
                            break
            if self.hint:
                break

    def alg_2(self):
        """
        Find and mark cells which must be empty because they
        are adjacent to satisfied clues.
        """
        neighbours = Model.cell_neighbours
        #
        # for each clue datum set the of guessed adjacent cells
        #
        self.get_done_1()
        for key, datum in list(self.datum_dic.items()):
            if datum.is_clue() and datum.get_done() == datum.clue or\
               datum.is_clue() and datum.clue == 0:
                #
                # the clue is satisfied
                #
                for nbr in neighbours:
                    nbr_key = Model.add_keys(key, nbr)
                    if self.in_grid(nbr_key):
                        nbr_datum = self.datum_dic[nbr_key]
                        if nbr_datum.requires_solving():
                            #
                            # this neighbour requires solving
                            # but is next to a satisfied clue
                            # so it must be empty
                            #

                            self.hint = [nbr_key, 0]
                            break
            if self.hint:
                break

    def alg_3(self):
        """
        Find pairs of cells forming a 135 degree angle.
        _^ or ^_ in all 4 orientations
        a must be flat_edge
        b must be correct corner
        triples are offsets to a,b, and corner, kosher corners, new corners
        kosher corners are triangle types
        """

        triples = [[[1, 0], [0, -1], 1, [2, 3], 4],
                   [[0, -1], [-1, 0], 2, [3, 4], 1],
                   [[-1, 0], [0, 1], 3, [1, 4], 2],
                   [[0, 1], [1, 0], 4, [1, 2], 3],
                   [[-1, 0], [0, -1], 2, [1, 4], 3],
                   [[1, 0], [0, 1], 4, [2, 3], 1],
                   [[0, -1], [1, 0], 1, [3, 4], 2],
                   [[0, 1], [-1, 0], 3, [1, 2], 4]]

        for key, datum in list(self.datum_dic.items()):
            if datum.requires_solving():
                # try 8 orientations
                for triple in triples:
                    a_off = triple[0]
                    b_off = triple[1]
                    corner = triple[2]
                    edges = triple[3]
                    new_corner = triple[4]
                    a_key = Model.add_keys(key, a_off)
                    b_key = Model.add_keys(key, b_off)
                    if self.key_in_range(a_key) and\
                       self.key_in_range(b_key):
                        a_datum = self.datum_dic[a_key]
                        b_datum = self.datum_dic[b_key]
                        if a_datum.is_flat_edge(edges) and\
                           b_datum.is_needed_corner(corner):
                            self.hint = [key, new_corner]
                            break

    def alg_4(self):
        """
        Find pairs of cells forming a 135 degree angle.
        _^ or ^_ in all 4 orientations
        a must be flat_edge
        b must be correct corner
        triples are offsets to a,b, and corner, kosher corners, new corners
        """
        triples = [[[1, 0], [0, -1], 1, [2, 3], 4],
                   [[0, -1], [-1, 0], 2, [3, 4], 1],
                   [[-1, 0], [0, 1], 3, [1, 4], 2],
                   [[0, 1], [1, 0], 4, [1, 2], 3],
                   [[-1, 0], [0, -1], 2, [1, 4], 3],
                   [[1, 0], [0, 1], 4, [2, 3], 1],
                   [[0, -1], [1, 0], 1, [3, 4], 2],
                   [[0, 1], [-1, 0], 3, [1, 2], 4]]

        for key, datum in list(self.datum_dic.items()):
            if datum.requires_solving():
                # try 8 orientations
                for triple in triples:
                    a_off = triple[0]
                    b_off = triple[1]
                    corner = triple[2]
                    new_corner = triple[4]
                    a_key = Model.add_keys(key, a_off)
                    b_key = Model.add_keys(key, b_off)
                    if not self.key_in_range(a_key) and\
                       self.key_in_range(b_key):
                        #a_datum = self.datum_dic[a_key]
                        b_datum = self.datum_dic[b_key]
                        if b_datum.is_needed_corner(corner):
                            self.hint = [key, new_corner]
                            break

    def alg_5(self):
        """
        Find unsatisfied clues which touch exactly that number of
        unsolved cells.
        Then for each of those cells check to see if there is
        only 1 compatible triangle type. If so we have a hint.
        Unsolved cells are named "holes".
        """

        def get_good(clue_key, hole):
            if hole[0] > clue_key[0]:
                good = [2, 3]
            elif hole[0] < clue_key[0]:
                good = [1, 4]
            elif hole[1] < clue_key[1]:
                good = [3, 4]
            else:
                good = [1, 2]
            return set(good)

        neighbours = Model.all_neighbours
        faces = [4, 5, 6, 7, 0, 1, 2, 3]
        bad_dic = Model.bad_dic
        unfinished = self.get_done_1()
        for unfin in unfinished:
            clue_key = unfin[0]
            datum = self.datum_dic[clue_key]
            clue = datum.clue
            done = datum.get_done()
            holes = unfin[1]
            left = clue - done
            if left - len(holes) == 0:
                #
                # the number of unsolved cells matches the unsatisfied clues
                #
                for hole_key in holes:
                    good_set = get_good(clue_key, hole_key)
                    #
                    # which triangles are compatible with hole_key?
                    # look at 8 surrounding cells
                    #
                    for offset_i, offset in enumerate(neighbours):
                        key = Model.add_keys(hole_key, offset)
                        if self.key_in_range(key):
                            face = faces[offset_i]
                            f_datum = self.datum_dic[key]
                            triangle = f_datum.guess
                            bad = bad_dic[triangle][face]
                            for item in bad:
                                good_set.discard(item)
                            if len(good_set) == 1:
                                self.hint = [hole_key, good_set.pop()]
                                break
                    if self.hint:
                        break
                if self.hint:
                    break

    def alg_6(self):
        """
        An empty cell on the edge and blocked in both directions
        along the edge can be set empty
        """
        for key, datum in list(self.datum_dic.items()):
            if datum.requires_solving():
                edge = self.get_edge(key)
                if edge is not None:
                    if edge == 1 or edge == 3:
                        nbr_0_key = key[0] - 1, key[1]
                        if self.key_in_range(nbr_0_key):
                            nbr_1_key = key[0] + 1, key[1]
                            if self.key_in_range(nbr_1_key):
                                nbr_0_datum = self.datum_dic[nbr_0_key]
                                nbr_1_datum = self.datum_dic[nbr_1_key]
                                if nbr_0_datum.is_black([0, 1, 4]) and\
                                   nbr_1_datum.is_black([0, 2, 3]):
                                    self.hint = [key, 0]
                                    break
                    else:
                        nbr_0_key = key[0], key[1] - 1
                        if self.key_in_range(nbr_0_key):
                            nbr_1_key = key[0], key[1] + 1
                            if self.key_in_range(nbr_1_key):
                                nbr_0_datum = self.datum_dic[nbr_0_key]
                                nbr_1_datum = self.datum_dic[nbr_1_key]
                                if nbr_0_datum.is_black([0, 3, 4]) and\
                                   nbr_1_datum.is_black([0, 1, 2]):
                                    self.hint = [key, 0]
                                    break

    def alg_7(self):
        """
        An empty cell on the edge and blocked inwards
        can be set empty
        """
        for key, datum in list(self.datum_dic.items()):
            if datum.requires_solving():
                edge = self.get_edge(key)
                if edge is not None:
                    if edge == 1:
                        nbr_0_key = key[0], key[1] + 1
                        nbr_0_datum = self.datum_dic[nbr_0_key]
                        if nbr_0_datum.is_black([0, 1, 2]):
                            self.hint = [key, 0]
                            break

                    elif edge == 3:
                        nbr_0_key = key[0], key[1] - 1
                        nbr_0_datum = self.datum_dic[nbr_0_key]
                        if nbr_0_datum.is_black([0, 3, 4]):
                            self.hint = [key, 0]
                            break

                    elif edge == 2:
                        nbr_0_key = key[0] + 1, key[1]
                        nbr_0_datum = self.datum_dic[nbr_0_key]
                        if nbr_0_datum.is_black([0, 2, 3]):
                            self.hint = [key, 0]
                            break

                    elif edge == 4:
                        nbr_0_key = key[0] - 1, key[1]
                        nbr_0_datum = self.datum_dic[nbr_0_key]
                        if nbr_0_datum.is_black([0, 1, 4]):
                            self.hint = [key, 0]
                            break

    def alg_8(self):
        """
        A 3 clue on the edge must have compatible triangles on each side
        """
        for key, datum in list(self.datum_dic.items()):
            if datum.is_clue() and not datum.is_satisfied() and datum.clue == 3:
                edge = self.get_edge(key)
                if edge is not None:
                    if edge == 1 or edge == 3:
                        hole_key = key[0] - 1, key[1]
                        hole_datum = self.datum_dic[hole_key]
                        if hole_datum.guess is None:
                            if edge == 1:
                                self.hint = [hole_key, 1]
                                break
                            else:
                                self.hint = [hole_key, 4]
                                break
                        else:
                            hole_key = key[0] + 1, key[1]
                            hole_datum = self.datum_dic[hole_key]
                            if hole_datum.guess is None:
                                if edge == 1:
                                    self.hint = [hole_key, 2]
                                    break
                                else:
                                    self.hint = [hole_key, 3]
                                    break

                    elif edge == 2 or edge == 4:
                        hole_key = key[0], key[1] - 1
                        hole_datum = self.datum_dic[hole_key]
                        if hole_datum.guess is None:
                            if edge == 2:
                                self.hint = [hole_key, 3]
                                break
                            else:
                                self.hint = [hole_key, 4]
                                break

                        else:
                            hole_key = key[0], key[1] + 1
                            hole_datum = self.datum_dic[hole_key]
                            if hole_datum.guess is None:
                                if edge == 2:
                                    self.hint = [hole_key, 2]
                                    break
                                else:
                                    self.hint = [hole_key, 1]
                                    break

    def alg_9(self):
        """
        """
        unfinished = self.get_done_1()
        for unfin in unfinished:
            clue_key = unfin[0]
            datum = self.datum_dic[clue_key]
            clue = datum.clue
            done = datum.get_done()
            holes = unfin[1]
            left = clue - done
            if left - len(holes) == 0 and len(holes) == 1:
                #
                # the number of unsolved cells matches the unsatisfied clues
                # and there is only one hole.
                # Here we are interested only in those holes on the edge
                hole_key = holes[0]
                edge = self.get_edge(hole_key)
                triangle = None
                if edge is not None:

                    if edge == 1:
                        if clue_key[0] < hole_key[0]:
                            triangle = 2
                        else:
                            triangle = 1
                    elif edge == 2:
                        if clue_key[1] < hole_key[1]:
                            triangle = 2
                        else:
                            triangle = 3
                    elif edge == 3:
                        if clue_key[0] < hole_key[0]:
                            triangle = 3
                        else:
                            triangle = 4
                    elif edge == 4:
                        if clue_key[1] < hole_key[1]:
                            triangle = 1
                        else:
                            triangle = 4
                    self.hint = [hole_key, triangle]
                    break

    def alg_10(self):
        """
        A pair of diagonal 4 clues or a diagonal 4 and a 3 clue
        must share a compatible triangle.
        4
         3

        43
        13

         4
        3

        44
        32
        """

        for key, datum in list(self.datum_dic.items()):
            pair = []
            if datum.is_clue() and datum.clue in [3, 4]:
                pair.append(datum.clue)
                nbr_key = key[0] + 1, key[1] + 1
                if self.key_in_range(nbr_key):
                    nbr_datum = self.datum_dic[nbr_key]
                    if nbr_datum.is_clue() and nbr_datum.clue in [3, 4]:
                        pair.append(nbr_datum.clue)
                        nbr_key = key[0] + 1, key[1]
                        nbr_datum = self.datum_dic[nbr_key]
                        # must have at least one 4
                        if nbr_datum.requires_solving() and\
                           4 in pair:
                            self.hint = [nbr_key, 3]
                            break
                        nbr_key = key[0], key[1] + 1
                        nbr_datum = self.datum_dic[nbr_key]
                        if nbr_datum.requires_solving() and\
                           4 in pair:
                            self.hint = [nbr_key, 1]
                            break
                nbr_key = key[0] - 1, key[1] + 1
                if self.key_in_range(nbr_key):
                    nbr_datum = self.datum_dic[nbr_key]
                    if nbr_datum.is_clue() and nbr_datum.clue in [3, 4]:
                        pair.append(nbr_datum.clue)
                        nbr_key = key[0] - 1, key[1]
                        nbr_datum = self.datum_dic[nbr_key]
                        if nbr_datum.requires_solving() and\
                           4 in pair:
                            self.hint = [nbr_key, 4]
                            break
                        nbr_key = key[0], key[1] + 1
                        nbr_datum = self.datum_dic[nbr_key]
                        if nbr_datum.requires_solving() and\
                           4 in pair:
                            self.hint = [nbr_key, 2]
                            break

    def alg_11(self):
        """
        Find unsatisfied clues requiring 1 more triangle and having
        2 adjacent empty cells. One of those must become set_empty.
        If an empty (square) cell is incompatible with either empty
        cell, we have a hint.
        Unsolved cells are named "holes".
        neighbour order: up, left, down, right
        """

        def get_good(clue_key, hole):
            if hole[0] > clue_key[0]:
                good = [2, 3, 5]
            elif hole[0] < clue_key[0]:
                good = [1, 4, 5]
            elif hole[1] < clue_key[1]:
                good = [3, 4, 5]
            else:
                good = [1, 2, 5]
            return set(good)

        faces = [2, 3, 0, 1, None]
        neighbours = Model.all_neighbours
        faces = [4, 5, 6, 7, 0, 1, 2, 3]
        bad_dic = Model.bad_dic
        unfinished = self.get_done_1()
        for unfin in unfinished:
            clue_key = unfin[0]
            datum = self.datum_dic[clue_key]
            clue = datum.clue
            done = datum.get_done()
            holes = unfin[1]
            if clue - done == 1 and len(holes) == 2:
                #
                # we require one of 2 empty cells to set_empty
                # find which one can take an empty cell
                #
                keep = []
                for hole_key in holes:
                    good_set = get_good(clue_key, hole_key)
                    #
                    # which triangles are compatible with hole_key?
                    # look at 8 surrounding cells
                    #
                    for offset_i, offset in enumerate(neighbours):
                        key = Model.add_keys(hole_key, offset)
                        if self.key_in_range(key):
                            face = faces[offset_i]
                            f_datum = self.datum_dic[key]
                            triangle = f_datum.guess
                            bad = bad_dic[triangle][face]
                            for item in bad:
                                good_set.discard(item)
                    if 5 in good_set:
                        keep.append(hole_key)
                if len(keep) == 1:
                    # only one hole_key can take an empty cell
                    self.hint = [keep.pop(), 0]
                    break
                if self.hint:
                    break

    def alg_12(self):
        """
        Once the dimensions of a rectangle are clear we can fill in the sides.
        """
        self.join_boxes()
        self.tidy_boxes()

    def alg_13(self):
        """
        Once the dimensions of a rectangle are clear we can fill in the sides.
        """
        for box in self.boxes:
            if not box.is_finished():

                self.fix_lengths(box)
                if self.hint:
                    return

    def alg_14(self):
        """
        Use opposite sides to extend edges.
        """
        for box in self.boxes:
            if not box.is_finished():

                self.extend_edges(box)
                if self.hint:
                    return

    def alg_15(self):
        """
        Diagonal zeroes must either be in squares or have an
        orthogonal black diagonal
                AB
                C0
        0 current cell
        A a diagonal neighbour
        B a cell we might set
        C a cell we might set
        diags: diagonal_nbr, set_cell, set_cell, triangle codes
        one diag for each of 4 diagonal directions
        """
        diags = [
            [[-1, -1], [0, -1], [-1, 0], [3, 1]],
            [[1, -1], [0, -1], [1, 0], [4, 2]],
            [[1, 1], [1, 0], [0, 1], [3, 1]],
            [[-1, 1], [-1, 0], [0, 1], [4, 2]]]

        for key, datum in list(self.datum_dic.items()):
            if datum.is_set_empty():
                for diag_i, diag in enumerate(diags):
                    diag_0 = diag[0]
                    diag_1 = diag[1]
                    diag_2 = diag[2]
                    black = diag[3]
                    #
                    # black are the triangle codes to place
                    #
                    d_key = Model.add_keys(key, diag_0)
                    if self.key_in_range(d_key):
                        if self.datum_dic[d_key].is_set_empty():
                            #
                            # Set_empty diagonal pair.
                            #
                            # p_key and q_key are diagonal neighbours
                            #
                            p_key = Model.add_keys(key, diag_1)
                            q_key = Model.add_keys(key, diag_2)
                            if self.key_in_range(p_key) and\
                               self.key_in_range(q_key):
                                p_datum = self.datum_dic[p_key]
                                q_datum = self.datum_dic[q_key]
                                if p_datum.is_black([1, 2, 3, 4]) and\
                                   q_datum.is_empty():
                                    self.hint = [q_key, black[1]]
                                    break
                                elif q_datum.is_black([1, 2, 3, 4]) and\
                                     p_datum.is_empty():
                                    self.hint = [p_key, black[0]]
                                    break
                                elif p_datum.is_set_empty() and\
                                     q_datum.is_empty():
                                    self.hint = [q_key, 0]
                                    break
                                elif q_datum.is_set_empty() and\
                                     p_datum.is_empty():
                                    self.hint = [p_key, 0]
                                    break
                                elif p_datum.is_empty() and\
                                     q_datum.is_empty():
                                    self.hint = [p_key, 0]
                                    break
                if self.hint:
                    break

    def alg_16(self):
        """
        An empty cell completely surrounded by filled cells or an
        edge must be set_white.
        """
        triangle_dic = {1: (3, 4),
                        2: (2, 3),
                        3: (1, 2),
                        4: (1, 4)}

        for key, datum in list(self.datum_dic.items()):
            if not datum.is_solved():
                done = 0
                for direction in range(1, 5):
                    triangles = triangle_dic[direction]
                    if self.good_16(key, direction, triangles):
                        done += 1
                    if done == 4:
                        self.hint = [key, 0]
                        break
            if self.hint:
                break

    def alg_17(self):
        """
        Find unsatisfied clues and check the neighbouring unsolved cells
        to find those with no choice.
        Unsolved cells are named "holes".
        """

        unfinished = self.get_done_1()
        self.get_vboxes()

        for unfin in unfinished:
            clue_key = unfin[0]
            datum = self.datum_dic[clue_key]
            holes = unfin[1]

            for hole in holes:
                good = self.good_triangles(hole)
                if hole in self.oobs and 5 in good:
                    good.remove(5)
                if len(good) == 1:
                    triangle = good[0]
                    if triangle == 5:
                        triangle = 0
                    self.hint = [hole, triangle]
                    break
            if self.hint:
                break
        unfinished = self.get_done_2()
        for unfin in unfinished:
            clue_key = unfin[0]
            holes = unfin[1]
            for hole in holes:
                good = self.good_triangles(hole)

                if len(good) == 1:
                    triangle = good[0]
                    if triangle == 5:
                        triangle = 0
                    self.hint = [hole, triangle]
                    break
            if self.hint:
                break

        if not self.hint:
            for oob in self.oobs:
                good = self.good_triangles(oob)
                if 5 in good:
                    good.remove(5)
                if len(good) == 1:
                    triangle = good[0]
                    self.hint = [oob, triangle]
                    break

    def is_flat_edge_1(self, key, direction, triangle_types):
        incs_dic = {1: (0, -1),
                    2: (1, 0),
                    3: (0, 1),
                    4: (-1, 0)}
        ret = False
        inc = incs_dic[direction]
        nbr_key = Model.add_keys(key, inc)
        if self.key_in_range(nbr_key):
            if self.datum_dic[nbr_key].is_black(triangle_types):
                ret = True
        else:
            ret = True
        return ret

    def good_16(self, key, direction, triangles):
        datum = self.datum_dic[key]
        ret = False
        if datum.is_empty() and\
           self.is_flat_edge_1(key, direction, triangles):
            ret = True
        return ret

    def in_range(self, x, y):
        ret = True
        if x < 0 or x >= self.width or y < 0 or y >= self.height:
            ret = False
        return ret

    def key_in_range(self, key):
        x = key[0]
        y = key[1]
        return self.in_range(x, y)

    def good_triangles(self, key_in):
        """
        Which triangles are compatible with cell key_in?
        They must be compatible with all its neighbours.
        Start allowing all and remove those which are incompatible
        with each neighbour in turn.
        """
        bad_dic = Model.bad_dic
        neighbours = Model.all_neighbours
        revs = [4, 5, 6, 7, 0, 1, 2, 3]
        good_set = set([1, 2, 3, 4, 5])
        bad_set = set([])
        for i, inc in enumerate(neighbours):
            key = Model.add_keys(key_in, inc)
            if self.key_in_range(key):
                my_type = self.datum_dic[key].get_type()
            else:
                my_type = 5
            if my_type is not None:
                rev = revs[i]
                bad = bad_dic[my_type][rev]
                bad_set = bad_set | set(bad)
        good_set = good_set - bad_set
        return list(good_set)

    def get_edge(self, key):
        """
        BUG: corners are on 2 edges so should return list.
        Guess we get the missed one next time!
        """
        edge = None
        if key[1] == 0:
            edge = 1
        elif key[0] == 0:
            edge = 2
        elif key[1] == self.height - 1:
            edge = 3
        elif key[0] == self.width - 1:
            edge = 4
        return edge

    def zero_algorithm_counts(self):
        self.algorithm_counts = [0 for i in self.algorithms]

    def update_algorithm_counts(self, algorithm, count):
        self.algorithm_counts[algorithm] += count
        Sgg.algorithm_counts[algorithm] += count

    def set_hint_status(self, status):
        self.hint_status = status

    def get_hint_status(self):
        return self.hint_status

    def get_puzzle(self, puzzle_index):
        puzzle = self.model_data.get_puzzle(puzzle_index)
        if puzzle:
            self.make_puzzle(puzzle)

    def load_puzzle(self, puzzle):
        if puzzle:
            self.make_puzzle(puzzle)

    """
    def print_puzzle(self, job):
        print
        for y in range(self.height):
            for x in range(self.width):
                key = x, y
                datum = self.datum_dic[key]
                print datum.corner,
            print
        print
    """

    def check_errors(self):
        self.status = True
        self.errors = []
        for key, datum in list(self.datum_dic.items()):
            if not datum.is_correct():
                self.errors.append(key)
                Sgg.bugs.append(key)
                Sgg.bugs.append(self.algorithm)
        if self.errors:
            #print 'errs', self.errors
            #print 'alg', self.algorithm
            #Sgg.bugs.append(self.algorithm)
            self.status = False

    def is_finished(self):
        ret = True
        for key, datum in list(self.datum_dic.items()):
            if not datum.is_finished():
                ret = False
                break
        return ret

    def apply_algorithms(self):
        """
        Try all algorithms. If one succeeds, return.
        """
        self.hint = []
        self.get_edges()
        for self.algorithm, algorithm in enumerate(self.algorithms):
            algorithm()
            if self.hint:
                self.update_algorithm_counts(self.algorithm, 1)
                break

    def rapply_algorithms(self):
        """
        Try all algorithms in random order. If one succeeds, return.
        """
        self.hint = []
        self.get_edges()

        while True:
            i = randint(0, len(self.algorithms) - 1)
            algorithm = self.algorithms[i]
            self.algorithm = i
            algorithm()
            if self.hint:
                self.update_algorithm_counts(self.algorithm, 1)
                #print 'counts', self.algorithm_counts
                #print 'counts', Sgg.algorithm_counts
                break
        #print 'a_algs', self.hint

    def apply_hint(self):
        widget_id = 0
        key = self.hint[0]
        triangle = self.hint[1]
        if self.datum_dic[key].guess == triangle:
            #print self.algorithm
            exit(1)
        if triangle != 0:
            self.datum_dic[key].set_guess(triangle)
        else:
            self.datum_dic[key].set_empty()
        widget_id = self.datum_dic[key].get_widget()
        self.datum_dic[key].unset_widget()
        return widget_id

    def solve_puzzle(self):
        progress = True
        while progress and not self.is_finished():
            progress = False
            self.apply_algorithms()
            if self.hint:
                progress = True
                self.apply_hint()
                self.check_errors()
                if not self.status:
                    break

    def get_difficulty(self):
        score = 0
        for x, y in enumerate(Sgg.algorithm_scores):
            score += y * self.algorithm_counts[x]
        self.difficulty = score

    def do_hint(self):
        self.apply_algorithms()

    def graph_to_grid(self, key):
        x = key[0] - 1
        y = self.height - key[1] - 1
        return x, y

    def grid_to_graph(self, key):
        x = key[0] + 1
        y = self.height - key[1]
        return x, y

    def make_graph(self):
        self.graph_dic = {}
        for y in range(Sgg.height):
            for x in range(Sgg.width):
                self.graph_dic[x, y] = 0

    """
    def print_graph(self):
        z = Sgg.height - 1
        for y in range(Sgg.height):
            for x in range(Sgg.width):
                if self.graph_dic[x, z] != 0:
                    print 'x',
                else:
                    print '0',
            print
            z -= 1
    """

    def add_foursome(self):
        """
        Add box of 4 triangles.
        """
        new_keys = []
        self.left_1 = []
        self.left_2 = []
        self.right_1 = []
        self.right_2 = []
        for y in range(Sgg.height - 1):
            for x in range(Sgg.width - 1):
                if self.graph_dic[x, y] == 0 and\
                   self.graph_dic[x + 1, y] == 0 and\
                   self.graph_dic[x, y + 1] == 0 and\
                   self.graph_dic[x + 1, y + 1] == 0:
                    xy = x, y
                    x1y = x + 1, y
                    xy1 = x, y + 1
                    x1y1 = x + 1, y + 1
                    new_keys = [xy, x1y, xy1, x1y1]
                    self.left_1 = [xy]
                    self.left_2 = [xy1]
                    self.right_1 = [x1y1]
                    self.right_2 = [x1y]
                    break
        return new_keys

    def scan_diag(self, key_in, inc, max_len):
        """
        scan along the diagonal in direction inc from key_in
        returning the last empty cells' key.
        """
        ret = key_in
        key = key_in
        count = 0
        while True:
            key = Model.add_keys(key, inc)
            count += 1
            if self.key_in_graph(key):
                if self.graph_dic[key] != 0:
                    break
            else:
                break
            ret = key
            if count == max_len:
                break
        return ret

    def get_corners(self, key_in):
        """
        Find the corners of the biggest diagonal box for which there is room
        """
        left_keys = []
        right_keys = []
        sides = [None, None, None, None]
        x = key_in[0]
        y = key_in[1]
        max_len = min(x, self.width - x, y - 1)

        #side 1
        inc = 1, -1
        side_2_end = key_in
        side_1_start = Model.add_keys(key_in, (1, 0))
        x = side_1_start[0]
        y = side_1_start[1]
        last_key = self.scan_diag(side_1_start, inc, max_len)
        side_1_end = last_key
        sides[0] = [side_1_start, side_1_end]

        # side 4
        inc = -1, -1
        side_4_start = Model.add_keys(side_1_end, (0, -1))
        x = side_4_start[0]
        y = side_4_start[1]
        last_key = self.scan_diag(side_4_start, inc, max_len)
        side_4_end = last_key
        sides[1] = [side_4_start, side_4_end]

        # side 3
        side_3_start = Model.add_keys(side_4_end, (-1, 0))
        x = side_3_start[0]
        y = side_3_start[1]
        length = side_1_end[0] - side_1_start[0]
        side_3_end = x - length, y + length
        sides[2] = [side_3_start, side_3_end]

        # side 2
        side_2_start = Model.add_keys(side_3_end, (0, 1))
        sides[3] = [side_2_start, side_2_end]

        right_keys = LineKeys(sides[0], (1, -1)).keys
        right_keys += LineKeys(sides[1], (-1, -1)).keys
        left_keys = LineKeys(sides[2], (-1, 1)).keys
        left_keys += LineKeys(sides[3], (1, 1)).keys

        self.right_1 = LineKeys(sides[0], (1, -1)).keys
        self.right_2 = LineKeys(sides[1], (-1, -1)).keys
        self.left_1 = LineKeys(sides[2], (-1, 1)).keys
        self.left_2 = LineKeys(sides[3], (1, 1)).keys

        return left_keys, right_keys

    def graph_to_grid(self, key):
        x = key[0]
        y = self.height - key[1] - 1
        return x, y

    def transfer_grid(self):
        triangle = 3
        for key in self.left_1:
            grid_key = self.graph_to_grid(key)
            if self.key_in_range(grid_key):
                self.datum_dic[grid_key].corner = triangle
        triangle = 2
        for key in self.left_2:
            grid_key = self.graph_to_grid(key)
            if self.key_in_range(grid_key):
                self.datum_dic[grid_key].corner = triangle
        triangle = 1
        for key in self.right_1:
            grid_key = self.graph_to_grid(key)
            if self.key_in_range(grid_key):
                self.datum_dic[grid_key].corner = triangle
        triangle = 4
        for key in self.right_2:
            grid_key = self.graph_to_grid(key)
            if self.key_in_range(grid_key):
                self.datum_dic[grid_key].corner = triangle

    def check_rows(self, left_keys, right_keys):
        new_keys = []
        bad = None
        # do a row
        inc = 1, 0
        for left_key in left_keys:
            key = left_key
            while key not in right_keys:
                if self.graph_dic[key] != 0:
                    # hit another box
                    bad = key
                    new_keys = []
                    break
                else:
                    new_keys.append(key)
                key = Model.add_keys(key, inc)
            if bad is not None:
                break
            # is right key bad?
            if self.graph_dic[key] != 0:
                # hit another box
                bad = key
                new_keys = []
                break
            new_keys.append(key)

        return new_keys

    def do_halo(self, left_keys):
        new_keys = []

        inc = -1, 0
        for left_key in left_keys:
            key = left_key
            new_key = Model.add_keys(key, inc)
            if self.in_grid(new_key):
                new_keys.append(new_key)
        inc = 1, 0
        for right_key in left_keys:
            key = right_key
            new_key = Model.add_keys(key, inc)
            if self.in_grid(new_key):
                new_keys.append(new_key)

        return new_keys

    def halo_ok(self, halo):
        """
        if this halo has > 1 overlaps with another box
        we assume it is touching along an edge and hence
        would form a non-rectangular shape.
        """
        ret = True
        counts_dic = {}
        for key in halo:
            if self.graph_dic[key] != 0:
                counts_dic.setdefault(self.graph_dic[key], 0)
                counts_dic[self.graph_dic[key]] += 1

        for key, count in list(counts_dic.items()):
            if count > 1:
                ret = False
                break
        return ret

    def do_middles(self, new_keys):
        triangle = 0
        all_edges = self.left_1 + self.left_2 + self.right_1 + self.right_2

        for key in new_keys:
            if key not in all_edges:
                grid_key = self.graph_to_grid(key)
                self.datum_dic[grid_key].corner = triangle

    def generate_2(self, done_dic, box_number):
        all_keys = []
        for y in range(self.height):
            for x in range(self.width):
                key = (x, y)
                if key not in done_dic:
                    all_keys.append(key)

        shuffle(all_keys)
        while all_keys:
            key = all_keys.pop()
            if key[0] >= 1 and key[0] <= self.width - 2 and key[1] >= 2 and\
               key[1] <= self.height:
                if key not in done_dic:
                    left_keys, right_keys = self.get_corners(key)
                    if left_keys and right_keys:
                        new_keys = self.check_rows(left_keys, right_keys)
                        if new_keys:
                            # no internal overlaps, check inappropriate touching
                            halo = self.do_halo(left_keys)
                            if self.halo_ok(halo):
                                # no inappropriate touching so save keys
                                box_number += 1
                                for new_key in new_keys:
                                    self.graph_dic[new_key] = box_number
                                    done_dic[new_key] = 1
                                self.transfer_grid()
                                self.do_middles(new_keys)

        return box_number

    def generate_1(self):
        """
        Make a puzzle for testing
        """
        self.datum_dic = {}
        self.make_graph()
        self.make_empty_puzzle()

        done_dic = {}
        box_number = 0

        # try to add diagonal boxes

        for i in range(5):
            box_number = self.generate_2(done_dic, box_number)

        # try to add small diagonal boxes

        while True:
            new_keys = self.add_foursome()
            if new_keys:
                box_number += 1
                for new_key in new_keys:
                    self.graph_dic[new_key] = box_number
                    done_dic[new_key] = 1
                self.transfer_grid()
            else:
                break
        self.make_g_puzzle()
        return self.gmake_puzzle()

    def gmake_puzzle(self):
        """
        make_puzzle
        Finalise the new puzzle

        Clues: -1 = 0
               -2 = 1
               -3 = 2
               -4 = 3
               -5 = 4
        """
        clue_keys = []
        self.get_difficulty()
        puzzle = [self.difficulty, self.width, self.height]
        for y in range(self.height):
            for x in range(self.width):
                if self.datum_dic[x, y].corner == 5:
                    clue = 0
                    neighbours = self.datum_dic[x, y].neighbours
                    for nbr in neighbours:
                        if self.datum_dic[nbr].corner in [1, 2, 3, 4]:
                            clue += 1
                    if clue == 0:
                        clue_value = 5
                        puzzle.append(clue_value)
                    else:
                        clue_value = -clue - 1
                        puzzle.append(clue_value)
                        clue_keys.append((x, y))
                else:
                    puzzle.append(self.datum_dic[x, y].corner)
        return puzzle, clue_keys


class Generate(object):
    """
    Generate puzzles and write them to a file
    """
    def __init__(self):
        self.model_data = ModelData()
        self.model = Model(self.model_data)
        # prevent use of widget_ids
        Sgg.GUI_ACTIVE = False
        seed = 1
        random.seed(seed)
        bad = 0
        Sgg.width = 10
        Sgg.height = 10
        puzzles = []
        max_puzzles = 500
        puzzle_count = 0
        failed = 0
        while puzzle_count < max_puzzles:
            puzzle, all_clues = self.model.generate_1()
            self.model = Model(self.model_data)
            self.model.load_puzzle(puzzle)
            self.model.solve_puzzle()
            if self.model.is_finished():
                # this puzzle can be solved with all possible clues
                # try to remove some
                puzzle = self.select_clues(puzzle, all_clues)
                if puzzle:
                    puzzles.append(puzzle)
                    puzzle_count += 1
                    #print 'puzzle_count', puzzle_count
                    #print puzzle
                else:
                    failed += 1
            else:
                bad += 1
        #print 'bugs', Sgg.bugs
        #print 'fails', bad, failed
        Generate.save_puzzles(puzzles)
        exit(1)

    @staticmethod
    def save_puzzles(puzzles):
        puzzles.sort(key=lambda x: x[0])
        filename = '24_puzzles.txt'
        with open(filename, "w") as fo:
            rec = ''
            for puzzle in puzzles:
                for item in puzzle:
                    rec += str(item)
                    rec += ','
            fo.write(rec)

    def is_isolated(self, key_in):
        """
        Return False if key touching an empty cell
        """
        ret = True
        keys = [(key_in[0], key_in[1] + 1),
                (key_in[0] + 1, key_in[1]),
                (key_in[0], key_in[1] - 1),
                (key_in[0] - 1, key_in[1])]
        for key in keys:
            if self.model.key_in_graph(key) and\
               self.model.datum_dic[key].corner == 0:
                ret = False
                break

        return ret

    def is_extensible(self, key_in):
        """
        Can this cell be extended to make a larger vertical box?
        """
        incs = [[(1, 0), (1, -1), (1, 1)],
                [(-1, 0), (-1, -1), (-1, 1)],
                [(0, 1), (-1, 1), (1, 1)],
                [(0, -1), (-1, -1), (1, -1)]]
        for inc in incs:
            ret = ()
            key_i = Model.add_keys(key_in, inc[0])
            key_j = Model.add_keys(key_in, inc[1])
            key_k = Model.add_keys(key_in, inc[2])
            if self.model.key_in_graph(key_i) and\
               self.model.datum_dic[key_i].corner == 5:

                if self.model.key_in_graph(key_j):
                    if self.model.datum_dic[key_j].corner != 0:
                        ret = key_i
                else:
                    ret = key_i
                if ret:
                    if self.model.key_in_graph(key_k):
                        if self.model.datum_dic[key_k].corner != 0:
                            ret = key_i
                    else:
                        ret = key_i
            if ret:
                break

        return ret

    def is_extensible(self, key_in):
        """
        Can this cell be extended to make a larger vertical box?
        """
        incs = [[(1, 0), (1, -1), (1, 1), (2, 0)],
                [(-1, 0), (-1, -1), (-1, 1), (-2, 0)],
                [(0, 1), (-1, 1), (1, 1), (0, 2)],
                [(0, -1), (-1, -1), (1, -1), (0, -2)]]
        for inc in incs:
            ret = ()
            key_i = Model.add_keys(key_in, inc[0])
            key_j = Model.add_keys(key_in, inc[1])
            key_k = Model.add_keys(key_in, inc[2])
            key_l = Model.add_keys(key_in, inc[3])
            if self.model.key_in_graph(key_i) and\
               self.model.datum_dic[key_i].corner == 5:

                if self.model.key_in_graph(key_j):
                    if self.model.datum_dic[key_j].corner != 0:
                        ret = key_i
                else:
                    ret = key_i
                if ret:
                    if self.model.key_in_graph(key_k):
                        if self.model.datum_dic[key_k].corner != 0:
                            ret = key_i
                    else:
                        ret = key_i
                    if ret:
                        if self.model.key_in_graph(key_l):
                            if self.model.datum_dic[key_l].corner != 0:
                                ret = key_i
                        else:
                            ret = key_i
            if ret:
                break

        return ret

    def shapes_ok(self):
        """
        Final check we don't have any non-rectangular shapes
        """
        incs = [[(1, 0), (0, 1), (1, 1), 4],
                [(1, 0), (1, 1), (0, 1), 3],
                [(0, 1), (-1, 1), (-1, 0), 2],
                [(0, 1), (1, 1), (1, 0), 1]]

        for y in range(Sgg.height):
            for x in range(Sgg.width):
                key = (x, y)
                if self.model.datum_dic[key].corner == 0:
                    for inc in incs:
                        good = [inc[3], 5, 6, 0]
                        key_i = Model.add_keys(key, inc[0])
                        key_j = Model.add_keys(key, inc[1])
                        key_k = Model.add_keys(key, inc[2])
                        if self.model.key_in_range(key_i) and\
                           self.model.key_in_range(key_j) and\
                           self.model.key_in_range(key_k):
                            i = self.model.datum_dic[key_i].corner
                            j = self.model.datum_dic[key_j].corner
                            k = self.model.datum_dic[key_k].corner
                            if i == 0 and j == 0 and k not in good:
                                return False
        return True

    def select_clues_1(self, puzzle, all_clues):
        """
        Find a subset of clues which still permit the puzzle to be solved.
        Loop:
        Shuffle clues and keep dropping the last until cannot solve. Save
        if fewest.
        Rinse and repeat max_tries times.
        We do this by noting the clues which can be set to white or black.
        """

        max_tries = 100
        best_blacks = []
        best_clues = list(all_clues)
        for tries in range(max_tries):
            clues = list(all_clues)
            shuffle(clues)
            blacks = []
            while True:
                if clues:
                    clue = clues.pop()
                    blacks.append(clue)
                else:
                    return [], []

                self.model = Model(self.model_data)
                self.model.make_puzzle_1(puzzle)
                # set popped clues to white or black (ie remove them)
                for key in blacks:
                    if self.is_isolated(key):
                        self.model.datum_dic[key] = Datum(key, 0)
                    else:
                        self.model.datum_dic[key] = Datum(key, 5)

                self.model.solve_puzzle()
                if self.model.is_finished():
                    if len(blacks) > len(best_blacks):
                        best_blacks = list(blacks)
                        best_clues = list(clues)
                else:
                    break

        return best_blacks, best_clues

    def select_clues(self, puzzle, all_clues):
        """
        Find a subset of clues which still permit the puzzle to be solved.
        Loop:
        Shuffle clues and keep dropping the last until cannot solve. Save
        if fewest.
        Rinse and repeat max_tries times.
        We do this by noting the clues which can be set to white or black.
        """

        best_blacks, best_clues = self.select_clues_1(puzzle, all_clues)
        if best_clues:
            # now repeat, starting with the shortest set of clues
            best_blacks, best_clues = self.select_clues_1(puzzle, best_clues)
            if not best_clues:
                return []
        else:
            return []
        self.model = Model(self.model_data)
        self.model.make_puzzle_1(puzzle)

        # set popped clues to white or black (ie remove them)

        for key in best_blacks:
            item = 3 + key[0] + Sgg.width * key[1]
            if self.is_isolated(key):
                self.model.datum_dic[key] = Datum(key, 0)
                puzzle[item] = 0
                ext = self.is_extensible(key)
                if ext:
                    jtem = 3 + ext[0] + Sgg.width * ext[1]
                    self.model.datum_dic[ext] = Datum(key, 0)

                    if puzzle[jtem] == 0:
                        #self.model.print_puzzle(0)
                        exit(1)
                    puzzle[jtem] = 0
            else:
                self.model.datum_dic[key] = Datum(key, 5)
                puzzle[item] = 5
        self.model = Model(self.model_data)
        self.model.make_puzzle_1(puzzle)
        self.model.solve_puzzle()
        if self.model.is_finished()and self.shapes_ok():
            self.model.get_difficulty()
            puzzle[0] = self.model.difficulty
        else:
            puzzle = []
        return puzzle


class Controller(object):
    """
    Set up and control
    """

    def __init__(self, root):
        self.settings = Settings()
        self.master = root
        self.flasher = None
        self.model_data = ModelData()
        self.model = None
        self.view = View(root, self.settings)
        self.error_detected = False
        self.puzzle_number = None
        puzzle_number = None
        #if len(sys.argv) > 1:
        #    puzzle_number = int(sys.argv[1])
        self.active = False
        self.start_game(puzzle_number)

    def set_puzzle_number(self, puzzle_number):
        if puzzle_number is None or puzzle_number < 0 or \
                puzzle_number > self.model_data.puzzle_count - 1:
            puzzle_number = randint(0, self.model_data.puzzle_count - 1)
        self.puzzle_number = puzzle_number

    def test_1(self):
        done = 0
        bad = []
        #new = []
        for puzzle_number in range(self.model_data.puzzle_count):
            self.model = Model(self.model_data)
            self.model.get_puzzle(puzzle_number)
            self.model.solve_puzzle()
            if not self.model.is_finished():
                bad.append(puzzle_number)
            else:
                self.model.get_difficulty()
                #print puzzle_number, ' score', self.model.difficulty
                #puzzle = self.model_data.get_puzzle(puzzle_number)
                #puzzle[0] = self.model.difficulty
                #new.append(puzzle)
            done += 1
        #print 'done', done, 'bad', bad, len(bad)
        #print Sgg.algorithm_counts

    def test(self):
        Sgg.GUI_ACTIVE = False
        for i in range(1):
            self.test_1()
        exit(1)

    def start_game(self, puzzle_number):
        #self.test()
        self.model = Model(self.model_data)
        #generate = Generate()
        self.set_puzzle_number(puzzle_number)
        self.view.clock_init()
        self.view.clock_restart()
        self.view.game_over()
        self.model.get_puzzle(self.puzzle_number)
        self.ready_for_hint()
        self.view.thumbs_up()
        self.view.make_board()
        self.view.board.datum_dic = self.model.datum_dic
        self.view.board.draw_it()
        self.add_callbacks()
        self.view.game_on()
        self.error_detected = False
        self.view.entry_box.clear()
        self.view.entry_box.write_box(str(self.puzzle_number))
        self.view.update_score_box(self.model.difficulty)
        self.model.set_hint_status(Sgg.GIVE_HINT)
        self.active = True
        self.flasher = Flasher(self.view.clock.box.label,
                               self.master, self.settings)

    def add_callbacks(self):
        self.view.add_menu_callback(self.service_menu)
        self.view.add_toolbar_callback(self.service_toolbar)
        self.view.entry_box.add_callback(self.get_puzzle_number)
        self.view.board.add_callbacks(self.board_event)

    def service_menu(self, *args):
        """
        Respond to menu choices
        """

        strargs = ''
        for item in args:
            strargs += ' ' + item
        PzlG.logger.logger.info("service_menu " + strargs)
        name = args[0]
        if name == 'font_size':
            self.set_font_size()
        elif name == 'save_settings':
            self.settings.preferences.save()
        elif name == 'about':
            self.view.about()
        elif name == 'help':
            self.view.help()

    def service_toolbar(self, button_index, mouse_index):
        PzlG.logger.logger.info("service_toolbar " +
                                str(button_index) +
                                ' ' +
                                str(mouse_index))
        if mouse_index == 2:
            alg_index = None
            if self.model.hint:
                alg_index = self.model.algorithm
            self.view.do_tooltip(button_index, alg_index)
        elif mouse_index == 3:
            if button_index == 0:
                self.puzzle_number += 1
            self.start_game(self.puzzle_number)
        elif mouse_index == 1:
            if button_index == 0:
                self.start_game(None)
            elif button_index == 1:
                self.check_errors()
                if self.model.status and not self.model.is_finished():
                    self.give_hint()
            elif button_index == 2:
                self.check_errors()
            elif button_index == 3:
                if not self.model.is_finished():
                    self.show_solution()

    def board_event(self, event):
        if self.active:
            PzlG.logger.logger.info("board_event " +
                                    str(event.x) +
                                    ' ' +
                                    str(event.y) +
                                    ' ' +
                                    str(event.num))

            self.view.board.destroy_hint()
            self.view.hint_off()
            board_x = event.x
            board_y = event.y
            if event.num == 1:
                widget_id = self.model.set_guess(board_x, board_y)
                if widget_id is not None:
                    self.view.board.itemconfig(widget_id, state='normal')
            elif event.num == 2:
                widget_id = self.model.set_empty(board_x, board_y)
                if widget_id is not None:
                    self.view.board.itemconfig(widget_id, state='normal')
            else:
                widget_id = self.model.unset_guess(board_x, board_y)
                if widget_id is not None:
                    self.view.board.itemconfig(widget_id, state='hidden')
                self.model.check_errors()
                if self.model.status:
                    self.view.thumbs_up()
            self.ready_for_hint()
            self.check_finished()

    def ready_for_hint(self):
        if not self.model.is_finished():
            self.model.set_hint_status(Sgg.GIVE_HINT)
            if self.model.status:
                self.view.hint_off()
                self.view.board.destroy_hint()
                self.view.thumbs_up()
                self.error_detected = False
            self.view.clock.set_show(True)

    def give_hint(self):
        if self.model.get_hint_status() == Sgg.GIVE_HINT:
            self.model.do_hint()
            if not self.model.status:
                self.view.thumbs_down()
                self.colour_errors()
                self.error_detected = True
            elif self.model.hint:
                self.view.hint_on()
                key = self.model.hint[0]
                self.view.board.show_hint(key)

                self.view.update_clock_box(self.model.algorithm)
                self.view.clock.set_show(False)
                self.model.set_hint_status(Sgg.APPLY_HINT)
        else:
            widget_id = self.model.apply_hint()
            key = self.model.hint[0]
            triangle = self.model.hint[1]

            if widget_id is not None and widget_id != 0:
                text = 'Hint: add triangle to ' +\
                       str(key[0]) + ',' + str(key[1]) + ' ' + str(triangle)
                PzlG.logger.logger.info(text)
                self.view.board.itemconfig(widget_id, state='normal')
                self.view.board.destroy_hint()
            self.ready_for_hint()
            self.check_finished()

    def check_errors(self):
        if self.error_detected:
            # we know there's an error already, so now we correct it
            self.uncolour_errors()
            self.model.unset_errors()
            self.model.errors = []
            self.model.check_errors()
            self.ready_for_hint()
        else:
            self.model.check_errors()
            if not self.model.status:
                self.view.thumbs_down()
                self.error_detected = True
                self.colour_errors()

    def check_finished(self):
        """
        Check if finished.
        If so, handle buttons, flasher and clock
        """
        if self.active:
            if self.model.is_finished():
                self.show_solution()
                self.flasher.flash()

    def show_solution(self):
        for key, datum in list(self.model.datum_dic.items()):
            datum.set_answer()
            if datum.corner < 5:
                if datum.corner == 0:
                    widget_id = datum.centre_jd
                    fill = 'white'
                else:
                    widget_id = datum.corners[datum.guess][1]
                    fill = 'black'
                self.view.board.itemconfig(widget_id, fill=fill, state='normal')
        self.view.board.update_idletasks()
        self.view.board.destroy_hint()
        self.view.hint_off()
        self.view.game_over()
        self.view.clock.set_show(True)
        self.view.clock_stop()
        self.active = False

    def colour_errors(self):
        """
        Colour the error squares
        """

        for key in self.model.errors:
            datum = self.model.datum_dic[key]

            if datum.guess == 0:
                widget_id = datum.centre_jd
            else:
                widget_id = datum.corners[datum.guess][1]
            self.view.board.itemconfig(widget_id, fill='red')

    def uncolour_errors(self):
        """
        Uncolour coloured squares
        """

        for key in self.model.errors:
            datum = self.model.datum_dic[key]
            # if not corrected by user
            if datum.guess != datum.corner:
                if datum.guess == 0:
                    widget_id = datum.centre_jd
                    fill = 'white'
                else:
                    widget_id = datum.corners[datum.guess][1]
                    fill = 'black'
                self.view.board.itemconfig(widget_id, fill=fill,
                                           state='hidden')

    def set_font_size(self):
        self.settings.do_scaling()
        self.start_game(self.puzzle_number)

    def get_puzzle_number(self, text):
        if text.find('XYZZY') != -1:
            self.view.xyzzy()
            self.view.entry_box.clear()
        else:
            try:
                puzzle_number = int(text)
                self.start_game(puzzle_number)
            except ValueError:
                self.view.entry_box.clear()


INITIALISE = PzlG()
if __name__ == '__main__':
    try:
        ROOT = tk.Tk()
        ROOT.withdraw()
        APP = Controller(ROOT)
        ROOT.mainloop()
        PzlG.logger.logger.info("Stop program")
    except:
        PzlG.logger.logger.info("Failure!")
        PzlG.logger.logger.exception("Unhandled exception during main")
