[gnome-sudoku/vala-port] vala port



commit f935f123faa7bc95c7dd6270e2c29216aae5f77c
Author: Thomas Hindoe Paaboel Andersen <phomes gmail com>
Date:   Fri Jun 7 22:07:55 2013 +0200

    vala port
    
    by Christopher Baines

 configure.ac                               |   22 +-
 data/Makefile.am                           |    7 +-
 data/gnome-sudoku-menu.ui                  |   65 ++
 data/gnome-sudoku.desktop.in.in            |    3 +-
 data/gnome-sudoku.ui                       |  319 +++++++++
 data/org.gnome.gnome-sudoku.gschema.xml.in |    5 +
 data/print_games.ui                        |  326 ---------
 data/select_game.ui                        |  134 ----
 data/tracker.ui                            |  104 ---
 src/Makefile.am                            |   50 ++-
 src/config.vapi                            |    8 +
 src/gnome-sudoku                           |   23 -
 src/gnome-sudoku.gresource.xml.in          |   13 +
 src/gnome-sudoku.vala                      |  501 ++++++++++++++
 src/lib/Makefile.am                        |   31 -
 src/lib/__init__.py                        |    1 -
 src/lib/colors.py                          |   85 ---
 src/lib/dancer.py                          |  137 ----
 src/lib/defaults.py                        |   58 --
 src/lib/defs.py.in                         |    2 -
 src/lib/dialog_swallower.py                |   77 ---
 src/lib/game_selector.py                   |  282 --------
 src/lib/gnome_sudoku.py                    |   22 -
 src/lib/gsudoku.py                         |  817 -----------------------
 src/lib/gtk_goodies/Makefile.am            |    7 -
 src/lib/gtk_goodies/Undo.py                |  474 -------------
 src/lib/gtk_goodies/__init__.py            |    1 -
 src/lib/gtk_goodies/dialog_extras.py       |  236 -------
 src/lib/main.py                            |  995 ----------------------------
 src/lib/number_box.py                      |  801 ----------------------
 src/lib/pausable.py                        |   65 --
 src/lib/printing.py                        |  215 ------
 src/lib/saver.py                           |  285 --------
 src/lib/simple_debug.py                    |   45 --
 src/lib/sudoku.py                          |  912 -------------------------
 src/lib/sudoku_maker.py                    |  607 -----------------
 src/lib/sudoku_thumber.py                  |  161 -----
 src/lib/timer.py                           |   87 ---
 src/lib/tracker_info.py                    |  208 ------
 src/main.vala                              |   85 +++
 src/number-picker.vala                     |   56 ++
 src/sudoku-board.vala                      |  512 ++++++++++++++
 src/sudoku-game.vala                       |  156 +++++
 src/sudoku-generator.vala                  |  413 ++++++++++++
 src/sudoku-solver.vala                     |  872 ++++++++++++++++++++++++
 src/sudoku-store.vala                      |  109 +++
 src/sudoku-view.vala                       |  819 +++++++++++++++++++++++
 47 files changed, 3995 insertions(+), 7218 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 3d8f133..19b2c5f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4,21 +4,29 @@ AM_SILENT_RULES([yes])
 AM_MAINTAINER_MODE([enable])
 GNOME_MAINTAINER_MODE_DEFINES
 
-GLIB_GSETTINGS
+AM_PROG_VALAC([0.16.0])
+AM_PROG_CC_C_O
 
-AM_PATH_PYTHON([2.4],,AC_MSG_ERROR([Python not found]))
+GLIB_GSETTINGS
 
 dnl ###########################################################################
 dnl Dependencies
 dnl ###########################################################################
 
-PYGOBJECT_REQUIRED=2.28.3
+GTK_REQUIRED=3.4.0
 
-PKG_CHECK_MODULES(PYGOBJECT, [
-    pygobject-3.0 >= $PYGOBJECT_REQUIRED
+PKG_CHECK_MODULES(GNOME_SUDOKU, [
+  gtk+-3.0 >= $GTK_REQUIRED
 ])
 
 dnl ###########################################################################
+dnl GResources
+dnl ###########################################################################
+
+GLIB_COMPILE_RESOURCES=`$PKG_CONFIG --variable glib_compile_resources gio-2.0`
+AC_SUBST(GLIB_COMPILE_RESOURCES)
+
+dnl ###########################################################################
 dnl Internationalization
 dnl ###########################################################################
 
@@ -45,7 +53,5 @@ data/icons/HighContrast/Makefile
 data/gnome-sudoku.desktop.in
 help/Makefile
 src/Makefile
-src/lib/Makefile
-src/lib/defs.py
-src/lib/gtk_goodies/Makefile
+src/gnome-sudoku.gresource.xml
 ])
diff --git a/data/Makefile.am b/data/Makefile.am
index f3dc54d..5f1d490 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -20,11 +20,8 @@ puzzle_DATA = \
        hard \
        very_hard
 
-uidir = $(datadir)/gnome-sudoku
-ui_DATA = \
-       print_games.ui \
-       select_game.ui \
-       tracker.ui
+dist_noinst_DATA = \
+       gnome-sudoku.ui
 
 man_MANS = gnome-sudoku.6
 
diff --git a/data/gnome-sudoku-menu.ui b/data/gnome-sudoku-menu.ui
new file mode 100644
index 0000000..9dc0515
--- /dev/null
+++ b/data/gnome-sudoku-menu.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+    <menu id="sudoku-menu">
+      <section>
+        <item>
+          <attribute name="label" translatable="yes">_New Game</attribute>
+          <attribute name="action">app.new-game</attribute>
+          <attribute name="accel">&lt;Primary&gt;n</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">_Reset</attribute>
+          <attribute name="action">app.reset</attribute>
+          <attribute name="accel">&lt;Primary&gt;r</attribute>
+        </item>
+      </section>
+      <section>
+        <item>
+          <attribute name="label" translatable="yes">_Undo</attribute>
+          <attribute name="action">app.undo</attribute>
+          <attribute name="accel">&lt;Primary&gt;u</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">_Redo</attribute>
+          <attribute name="action">app.redo</attribute>
+        </item>
+      </section>
+      <section>
+        <item>
+          <attribute name="label" translatable="yes">_Print</attribute>
+          <attribute name="action">app.print</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">_Print Multiple Sudoku's...</attribute>
+          <attribute name="action">app.print-multiple</attribute>
+        </item>
+      </section>
+      <section>
+        <item>
+          <attribute name="label" translatable="yes">_Show Possible Numbers</attribute>
+          <attribute name="action">app.possible-numbers</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">_Warn About Unfillable Squares</attribute>
+          <attribute name="action">app.unfillable-squares</attribute>
+        </item>
+      </section>
+      <section>
+        <item>
+          <attribute name="label" translatable="yes">_Help</attribute>
+          <attribute name="action">app.help</attribute>
+          <attribute name="accel">F1</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">_About</attribute>
+          <attribute name="action">app.about</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">_Quit</attribute>
+          <attribute name="action">app.quit</attribute>
+          <attribute name="accel">&lt;Primary&gt;q</attribute>
+        </item>
+      </section>
+    </menu>
+</interface>
diff --git a/data/gnome-sudoku.desktop.in.in b/data/gnome-sudoku.desktop.in.in
index f94e956..ddbfd60 100644
--- a/data/gnome-sudoku.desktop.in.in
+++ b/data/gnome-sudoku.desktop.in.in
@@ -1,14 +1,13 @@
 [Desktop Entry]
 _Name=Sudoku
 _Comment=Test your logic skills in this number grid puzzle
-_Keywords=game;board;tiles;japanese;
 Exec=gnome-sudoku
 Icon=gnome-sudoku
 Terminal=false
 Type=Application
 Categories=GNOME;GTK;Game;LogicGame;
 X-GNOME-Bugzilla-Bugzilla=GNOME
-X-GNOME-Bugzilla-Product=gnome-sudoku
+X-GNOME-Bugzilla-Product=gnome-games
 X-GNOME-Bugzilla-Component=BugBuddyBugs
 X-GNOME-Bugzilla-Version= VERSION@
 StartupNotify=true
diff --git a/data/gnome-sudoku.ui b/data/gnome-sudoku.ui
new file mode 100644
index 0000000..5fe83d8
--- /dev/null
+++ b/data/gnome-sudoku.ui
@@ -0,0 +1,319 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+    <requires lib="gtk+" version="2.16"/>
+    <!-- interface-naming-policy project-wide -->
+    <object class="GtkApplicationWindow" id="sudoku_app">
+        <property name="title" translatable="yes">Sudoku</property>
+        <property name="hide_titlebar_when_maximized">True</property>
+        <child>
+            <object class="GtkBox" id="main_box">
+                <property name="visible">True</property>
+                <child>
+                    <object class="GtkBox" id="start_box">
+                        <property name="orientation">vertical</property>
+                        <property name="visible">True</property>
+                        <child>
+                            <object class="GtkLabel" id="new_game_label">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">New Game</property>
+                                <attributes>
+                                    <attribute name="weight" value="bold"/>
+                                    <attribute name="gravity" value="west"/>
+                                </attributes>
+                            </object>
+                            <packing>
+                                <property name="position">0</property>
+                            </packing>
+                        </child>
+                        <child>
+                            <object class="GtkBox" id="new_game_box">
+                                <property name="visible">True</property>
+                                <child>
+                                    <object class="GtkBox" id="easy_grid">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">vertical</property>
+                                        <child>
+                                            <placeholder/>
+                                        </child>
+                                        <child>
+                                            <object class="GtkLabel" id="easy_label">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">False</property>
+                                                <property name="label" translatable="yes">Easy</property>
+                                                <attributes>
+                                                    <attribute name="weight" value="bold"/>
+                                                </attributes>
+                                            </object>
+                                        </child>
+                                    </object>
+                                    <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">20</property>
+                                    </packing>
+                                </child>
+                                <child>
+                                    <object class="GtkBox" id="medium_grid">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">vertical</property>
+                                        <child>
+                                            <placeholder/>
+                                        </child>
+                                        <child>
+                                            <object class="GtkLabel" id="medium_label">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">False</property>
+                                                <property name="label" translatable="yes">Medium</property>
+                                                <attributes>
+                                                    <attribute name="weight" value="bold"/>
+                                                </attributes>
+                                            </object>
+                                        </child>
+                                    </object>
+                                    <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">20</property>
+                                    </packing>
+                                </child>
+                                <child>
+                                    <object class="GtkBox" id="hard_grid">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">vertical</property>
+                                        <child>
+                                            <placeholder/>
+                                        </child>
+                                        <child>
+                                            <object class="GtkLabel" id="hard_label">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">False</property>
+                                                <property name="label" translatable="yes">Hard</property>
+                                                <attributes>
+                                                    <attribute name="weight" value="bold"/>
+                                                </attributes>
+                                            </object>
+                                        </child>
+                                    </object>
+                                    <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">20</property>
+                                    </packing>
+                                </child>
+                                <child>
+                                    <object class="GtkBox" id="very_hard_grid">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">vertical</property>
+                                        <child>
+                                            <placeholder/>
+                                        </child>
+                                        <child>
+                                            <object class="GtkLabel" id="very_hard_label">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">False</property>
+                                                <property name="label" translatable="yes">Very 
Hard</property>
+                                                <attributes>
+                                                    <attribute name="weight" value="bold"/>
+                                                </attributes>
+                                            </object>
+                                        </child>
+                                    </object>
+                                    <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="padding">20</property>
+                                    </packing>
+                                </child>
+                            </object> <!-- End of new game box -->
+                            <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                                <property name="padding">20</property>
+                            </packing>
+                        </child>
+                        <child>
+                            <object class="GtkLabel" id="saved_game_label">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">Saved Game</property>
+                                <attributes>
+                                    <attribute name="weight" value="bold"/>
+                                    <attribute name="gravity" value="west"/>
+                                </attributes>
+                            </object>
+                            <packing>
+                                <property name="position">2</property>
+                            </packing>
+                        </child>
+                    </object> <!-- End of new start_box -->
+                </child>
+                <child>
+                    <object class="GtkBox" id="game_box">
+                        <property name="visible">False</property>
+                        <property name="spacing">2</property>
+                        <child>
+                            <object class="GtkBox" id="controls_box">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                    <object class="GtkBox" id="number_picker_box">
+                                        <property name="visible">True</property>
+                                        <child>
+                                            <placeholder/>
+                                        </child>
+                                    </object>
+                                </child>
+                                <child>
+                                    <object class="GtkButton" id="back_button">
+                                        <property name="label" translatable="yes">Back</property>
+                                        <property name="use_action_appearance">False</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <property name="focus_on_click">False</property>
+                                        <property name="use_action_appearance">False</property>
+                                    </object>
+                                    <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                    </packing>
+                                </child>
+                                <child>
+                                    <object class="GtkButton" id="hint_button">
+                                        <property name="label" translatable="yes">Hint</property>
+                                        <property name="use_action_appearance">False</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="focus_on_click">False</property>
+                                        <property name="receives_default">True</property>
+                                        <property name="use_action_appearance">False</property>
+                                    </object>
+                                    <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">2</property>
+                                        <property name="pack_type">end</property>
+                                    </packing>
+                                </child>
+                                <child>
+                                    <object class="GtkButton" id="help_button">
+                                        <property name="label" translatable="yes">Help</property>
+                                        <property name="use_action_appearance">False</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="focus_on_click">False</property>
+                                        <property name="receives_default">True</property>
+                                        <property name="use_action_appearance">False</property>
+                                    </object>
+                                    <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">2</property>
+                                        <property name="pack_type">end</property>
+                                    </packing>
+                                </child>
+                            </object>  <!-- End of controls_box -->
+                            <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                                <property name="padding">20</property>
+                            </packing>
+                        </child>
+                        <child>
+                            <object class="GtkBox" id="grid_box">
+                                <property name="visible">True</property>
+                                <child>
+                                    <placeholder/>
+                                </child>
+                            </object>
+                            <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                            </packing>
+                        </child>
+                    </object> <!-- End of game_box -->
+                </child>
+                <child>
+                    <object class="GtkBox" id="help_box">
+                        <property name="visible">False</property>
+                        <property name="spacing">2</property>
+                        <property name="orientation">vertical</property>
+                        <child>
+                            <object class="GtkExpander" id="naked_singles">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <child>
+                                    <object class="GtkBox" id="naked_single_items">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="orientation">vertical</property>
+                                        <child>
+                                            <placeholder/>
+                                        </child>
+                                    </object>
+                                </child>
+                                <child type="label">
+                                    <object class="GtkLabel" id="naked_singles_label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">Naked Singles</property>
+                                    </object>
+                                </child>
+                            </object>
+                        </child>
+                        <child>
+                            <object class="GtkExpander" id="hidden_singles">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <child>
+                                    <object class="GtkBox" id="hidden_single_items">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="orientation">vertical</property>
+                                        <child>
+                                            <placeholder/>
+                                        </child>
+                                    </object>
+                                </child>
+                                <child type="label">
+                                    <object class="GtkLabel" id="hidden_singles_label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">Hidden Singles</property>
+                                    </object>
+                                </child>
+                            </object>
+                        </child>
+                        <child>
+                            <object class="GtkExpander" id="naked_subsets">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <child>
+                                    <object class="GtkBox" id="naked_subset_items">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="orientation">vertical</property>
+                                        <child>
+                                            <placeholder/>
+                                        </child>
+                                    </object>
+                                </child>
+                                <child type="label">
+                                    <object class="GtkLabel" id="naked_subsets_label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">Naked Subsets</property>
+                                    </object>
+                                </child>
+                            </object>
+                        </child>
+                    </object> <!-- End of help_box -->
+                </child>
+            </object>
+        </child>
+    </object>
+</interface>
diff --git a/data/org.gnome.gnome-sudoku.gschema.xml.in b/data/org.gnome.gnome-sudoku.gschema.xml.in
index 61c58ce..fb22252 100644
--- a/data/org.gnome.gnome-sudoku.gschema.xml.in
+++ b/data/org.gnome.gnome-sudoku.gschema.xml.in
@@ -101,5 +101,10 @@
       <summary>Number of puzzles to print on a page</summary>
       <description>Number of puzzles to print on a page</description>
     </key>
+    <key name="fullscreen" type="b">
+      <default>false</default>
+      <_summary>A flag to enable fullscreen mode</_summary>
+      <_description>A flag to enable fullscreen mode</_description>
+    </key>
   </schema>
 </schemalist>
diff --git a/src/Makefile.am b/src/Makefile.am
index 5318775..38ffad3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,8 +1,48 @@
-SUBDIRS = lib
+bin_PROGRAMS = gnome-sudoku
 
-#################################################################
+BUILT_SOURCES = gnome-sudoku-resources.c
 
-## Executable
-dist_bin_SCRIPTS = gnome-sudoku
+gnome_sudoku_SOURCES = \
+       config.vapi \
+       main.vala \
+       gnome-sudoku.vala \
+       sudoku-board.vala \
+    sudoku-game.vala \
+       sudoku-generator.vala \
+       sudoku-store.vala \
+       sudoku-solver.vala \
+       logical-sudoku-solver.vala \
+       sudoku-view.vala \
+       number-picker.vala \
+       $(BUILT_SOURCES)
 
--include $(top_srcdir)/git.mk
+gnome_sudoku_CFLAGS = \
+       -DPKGDATADIR=\"@datadir@/gnome-sudoku\" \
+       -DLOCALEDIR=\"@localedir \" \
+       -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
+       $(GTK_CFLAGS) \
+       $(GEE_CFLAGS) \
+       $(GMODULE_CFLAGS) \
+       $(WARN_CFLAGS)
+
+gnome_sudoku_LDADD = \
+       $(GTK_LIBS) \
+       $(GEE_LIBS) \
+       $(GMODULE_LIBS)
+
+gnome_sudoku_VALAFLAGS = \
+    --pkg posix \
+    --pkg gtk+-3.0 \
+    --pkg gee-1.0 \
+    --pkg gmodule-2.0 \
+    --vapidir $(top_srcdir)/libgames-support \
+    --pkg GnomeGamesSupport-1.0
+
+gnome-sudoku-resources.c: gnome-sudoku.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) 
--generate-dependencies gnome-sudoku.gresource.xml)
+       $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --generate-source 
gnome-sudoku.gresource.xml
+
+EXTRA_DIST = \
+       gnome_sudoku.gresource.xml
+
+DISTCLEANFILES = \
+       Makefile.in
diff --git a/src/config.vapi b/src/config.vapi
new file mode 100644
index 0000000..aae449c
--- /dev/null
+++ b/src/config.vapi
@@ -0,0 +1,8 @@
+[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")]
+namespace Config
+{
+  public const string PKGDATADIR;
+  public const string LOCALEDIR;
+  public const string GETTEXT_PACKAGE;
+  public const string VERSION;
+}
diff --git a/src/gnome-sudoku.gresource.xml.in b/src/gnome-sudoku.gresource.xml.in
new file mode 100644
index 0000000..f8c08ab
--- /dev/null
+++ b/src/gnome-sudoku.gresource.xml.in
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/gnome-sudoku/ui">
+    <file alias="gnome-sudoku.ui" preprocess="xml-stripblanks">@top_srcdir@/data/gnome-sudoku.ui</file>
+    <file alias="gnome-sudoku-menu.ui" 
preprocess="xml-stripblanks">@top_srcdir@/data/gnome-sudoku-menu.ui</file>
+  </gresource>
+  <gresource prefix="/org/gnome/gnome-sudoku/puzzles">
+    <file alias="easy">@top_srcdir@/data/easy</file>
+    <file alias="medium">@top_srcdir@/data/medium</file>
+    <file alias="hard">@top_srcdir@/data/hard</file>
+    <file alias="very_hard">@top_srcdir@/data/very_hard</file>
+  </gresource>
+</gresources>
diff --git a/src/gnome-sudoku.vala b/src/gnome-sudoku.vala
new file mode 100644
index 0000000..233eb9e
--- /dev/null
+++ b/src/gnome-sudoku.vala
@@ -0,0 +1,501 @@
+using Gtk;
+using Gee;
+
+public class Sudoku : Gtk.Application
+{
+    private GLib.Settings settings;
+    private Builder builder;
+
+    private ApplicationWindow window;
+    private CheckMenuItem fullscreen_menu;
+
+    // The current game and view, if they exist
+    private SudokuGame game;
+    private SudokuView view;
+
+    // The start box (contains the new game previews)
+    private Box start_box;
+    private Box game_box; // Holds the grid and controls boxes
+    private Box grid_box; // Holds the view
+    private Box help_box;
+
+    private Box controls_box; // Holds the controls (including the number picker)
+    private NumberPicker number_picker;
+
+    private SudokuView easy_preview;
+    private SudokuView medium_preview;
+    private SudokuView hard_preview;
+    private SudokuView very_hard_preview;
+
+    private SudokuStore sudoku_store;
+
+    // Help Stuff
+    private Box naked_single_items;
+    private Box hidden_single_items;
+    private Box naked_subset_items;
+
+    private const GLib.ActionEntry action_entries[] =
+    {
+        {"new-game", new_game_cb                                                                },
+        {"reset", reset_cb                                                                      },
+        {"undo", undo_cb                                                                        },
+        {"redo", redo_cb                                                                        },
+        {"print", print_cb                                                                      },
+        {"print-multiple", print_multiple_cb                                                    },
+        {"possible-numbers",   possible_numbers_cb,   null, "false", possible_numbers_changed   },
+        {"unfillable-squares", unfillable_squares_cb, null, "false", unfillable_squares_changed },
+        {"help", help_cb                                                                        },
+        {"about", about_cb                                                                      },
+        {"quit", quit_cb                                                                        }
+    };
+
+    public Sudoku ()
+    {
+        Object (application_id: "org.gnome.gnome-sudoku", flags: ApplicationFlags.FLAGS_NONE);
+    }
+
+    protected override void startup()
+    {
+        base.startup ();
+        add_action_entries (action_entries, this);
+    }
+
+    protected override void activate () {
+        settings = new GLib.Settings ("org.gnome.gnome-sudoku");
+
+        builder = new Builder ();
+        try
+        {
+            builder.add_from_resource ("/org/gnome/gnome-sudoku/ui/gnome-sudoku.ui");
+            builder.add_from_resource ("/org/gnome/gnome-sudoku/ui/gnome-sudoku-menu.ui");
+        }
+        catch (GLib.Error e)
+        {
+            GLib.warning ("Could not load UI: %s", e.message);
+        }
+        window = (ApplicationWindow) builder.get_object ("sudoku_app");
+
+        if (settings.get_boolean ("fullscreen"))
+            window.fullscreen ();
+
+        add_window (window);
+
+        set_app_menu (builder.get_object ("sudoku-menu") as MenuModel);
+
+        fullscreen_menu = (CheckMenuItem) builder.get_object ("toggle_fullscreen_imagemenuitem");
+
+        start_box = (Box) builder.get_object ("start_box");
+        game_box = (Box) builder.get_object ("game_box");
+        grid_box = (Box) builder.get_object ("grid_box");
+        help_box = (Box) builder.get_object ("help_box");
+
+        naked_single_items = (Box) builder.get_object ("naked_single_items");
+        hidden_single_items = (Box) builder.get_object ("hidden_single_items");
+
+        controls_box = (Box) builder.get_object ("number_picker_box");
+
+        var back_button = (Button) builder.get_object ("back_button");
+        back_button.clicked.connect (() => {
+            undo_cb();
+        });
+
+        var hint_button = (Button) builder.get_object ("hint_button");
+        hint_button.clicked.connect (() => {
+            view.hint ();
+        });
+
+        var help_button = (Button) builder.get_object ("help_button");
+        help_button.clicked.connect (() => {
+            help_box.visible = !help_box.visible;
+        });
+
+        sudoku_store = new SudokuStore ();
+        //SudokuGenerator gen = new SudokuGenerator();
+
+        var easy_grid = (Box) builder.get_object ("easy_grid");
+        var medium_grid = (Box) builder.get_object ("medium_grid");
+        var hard_grid = (Box) builder.get_object ("hard_grid");
+        var very_hard_grid = (Box) builder.get_object ("very_hard_grid");
+
+        SudokuBoard easy_board = sudoku_store.get_random_easy_board ();
+        //gen.make_symmetric_puzzle(Random.int_range(0, 4));
+        //gen.generate (DifficultyRating.easy_range);
+        easy_preview = new SudokuView (new SudokuGame (easy_board), true);
+        easy_preview.show ();
+        easy_grid.pack_start (easy_preview);
+
+        easy_grid.button_press_event.connect ((event) => {
+            if (event.button == 1)
+                start_game (easy_board);
+
+            return false;
+        });
+
+        SudokuBoard medium_board = sudoku_store.get_random_medium_board ();
+        //gen.make_symmetric_puzzle(Random.int_range(0, 4));
+        // gen.generate (DifficultyRating.medium_range);
+        medium_preview = new SudokuView (new SudokuGame (medium_board), true);
+        medium_preview.show ();
+        medium_grid.pack_start (medium_preview);
+
+        medium_grid.button_press_event.connect ((event) => {
+            if (event.button == 1)
+                start_game (medium_board);
+
+            return false;
+        });
+
+        SudokuBoard hard_board = sudoku_store.get_random_hard_board ();
+        //gen.make_symmetric_puzzle(Random.int_range(0, 4));
+        //gen.generate (DifficultyRating.hard_range);
+        hard_preview = new SudokuView (new SudokuGame (hard_board), true);
+        hard_preview.show ();
+        hard_grid.pack_start (hard_preview);
+
+        hard_grid.button_press_event.connect ((event) => {
+            if (event.button == 1)
+                start_game (hard_board);
+
+            return false;
+        });
+
+        SudokuBoard very_hard_board = sudoku_store.get_random_very_hard_board ();
+        //gen.make_symmetric_puzzle(Random.int_range(0, 4));
+        //gen.generate (DifficultyRating.very_hard_range);
+        very_hard_preview = new SudokuView (new SudokuGame (very_hard_board), true);
+        very_hard_preview.show ();
+        very_hard_grid.pack_start (very_hard_preview);
+
+        very_hard_grid.button_press_event.connect ((event) => {
+            if (event.button == 1)
+                start_game (very_hard_board);
+
+            return false;
+        });
+
+        show_start ();
+
+        builder.connect_signals (this);
+
+        window.show ();
+    }
+
+    private void start_game (SudokuBoard board)
+    {
+        SudokuBoard completed_board = board.clone ();
+
+        SudokuRater rater = new SudokuRater(ref completed_board);
+        DifficultyRating rating = rater.get_difficulty ();
+        rating.pretty_print ();
+
+        bool show_possibilities = false;
+        bool show_warnings = false;
+
+        if (view != null) {
+            show_possibilities = view.show_possibilities;
+            show_warnings = view.show_warnings;
+
+            grid_box.remove (view);
+            controls_box.remove (number_picker);
+        }
+
+        game_box.visible = true;
+        start_box.visible = false;
+
+        game = new SudokuGame (board);
+
+        if (game.timer.elapsed() <= 0.000002)
+        {
+            game.timer.start ();
+        }
+        else
+        {
+            game.timer.continue ();
+        }
+
+        view = new SudokuView (game);
+
+        view.show_possibilities = show_possibilities;
+        view.show_warnings = show_warnings;
+
+        view.show ();
+        grid_box.pack_start (view);
+
+        LogicalSudokuSolver logical_solver = new LogicalSudokuSolver(ref game.board);
+        update_help (logical_solver);
+
+        number_picker = new NumberPicker(ref game.board);
+        controls_box.pack_start (number_picker);
+
+        view.cell_focus_in_event.connect ((row, col) => {
+            // Only enable the NumberPicker for unfixed cells
+            number_picker.sensitive = !game.board.is_fixed[row, col];
+        });
+
+        view.cell_value_changed_event.connect ((row, col) => {
+            update_help (logical_solver);
+        });
+
+
+        number_picker.number_picked.connect ((number) => {
+            view.set_cell_value (view.selected_x, view.selected_y, number);
+        });
+
+        game.board.completed.connect (() => {
+            view.dance ();
+
+            double time = game.timer.elapsed ();
+
+            for (var i = 0; i < game.board.rows; i++)
+            {
+                for (var j = 0; j < game.board.cols; j++)
+                {
+                    view.can_focus = false;
+                }
+            }
+
+            var dialog = new MessageDialog(null, DialogFlags.DESTROY_WITH_PARENT, MessageType.INFO, 
ButtonsType.NONE, "Well done, you completed the puzzle in %f seconds", time);
+
+            dialog.add_button ("Same difficulty again", 0);
+            dialog.add_button ("New difficulty", 1);
+
+            dialog.response.connect ((response_id) => {
+                switch (response_id)
+                {
+                    case 0:
+                        start_game (sudoku_store.get_random_board (rating.get_catagory ()));
+                        break;
+                    case 1:
+                        show_start ();
+                        break;
+                }
+                dialog.destroy ();
+            });
+
+            dialog.show ();
+        });
+    }
+
+    private void update_help (LogicalSudokuSolver logical_solver)
+    {
+        foreach (Widget w in naked_single_items.get_children())
+        {
+            naked_single_items.remove (w);
+        }
+
+        ArrayList<Cell?> naked_singles = logical_solver.get_naked_singles ();
+
+        foreach (Cell? cell in naked_singles)
+        {
+            var event_box = new Gtk.EventBox ();
+
+            var label = new Gtk.Label ("%d is the only possiblility in %d, %d".printf (cell.val, 
cell.coord.col, cell.coord.row));
+
+            event_box.enter_notify_event.connect ((event) => {
+                view.cell_grab_focus (cell.coord.row, cell.coord.col);
+                return true;
+            });
+
+            label.use_markup = true;
+            event_box.add (label);
+            naked_single_items.add (event_box);
+            event_box.show ();
+            label.show ();
+        }
+
+        foreach (Widget w in hidden_single_items.get_children())
+        {
+            hidden_single_items.remove (w);
+        }
+
+        foreach (HiddenSingle? hidden_single in logical_solver.get_hidden_singles ())
+        {
+            var event_box = new Gtk.EventBox ();
+
+            string description = "%d should go in %d, %d because its the only place it can go in the 
associated ".printf (hidden_single.cell.val, hidden_single.cell.coord.col, hidden_single.cell.coord.row);
+            if (hidden_single.row && hidden_single.col && hidden_single.block)
+            {
+                description += "row, column and block";
+            }
+            else if (hidden_single.row && hidden_single.col)
+            {
+                description += "row and column";
+            }
+            else if (hidden_single.col && hidden_single.block)
+            {
+                description += "column and block";
+            }
+            else if (hidden_single.row && hidden_single.block)
+            {
+                description += "row and block";
+            }
+            else if (hidden_single.row)
+            {
+                description += "row";
+            }
+            else if (hidden_single.col)
+            {
+                description += "column";
+            }
+            else if (hidden_single.block)
+            {
+                description += "block";
+            }
+
+            var label = new Gtk.Label (description);
+
+            event_box.enter_notify_event.connect ((event) => {
+                view.cell_grab_focus (hidden_single.cell.coord.row, hidden_single.cell.coord.col);
+                return true;
+            });
+
+            label.enter_notify_event.connect ((event) => {
+                view.selected_x = hidden_single.cell.coord.col;
+                view.selected_y = hidden_single.cell.coord.row;
+                return true;
+            });
+
+            label.use_markup = true;
+            event_box.add (label);
+            hidden_single_items.add (event_box);
+            event_box.show ();
+            label.show ();
+        }
+    }
+
+    private void show_start ()
+    {
+        game_box.visible = false;
+        start_box.visible = true;
+    }
+
+    public void new_game_cb ()
+    {
+        show_start ();
+    }
+
+    public void reset_cb ()
+    {
+        game.reset ();
+    }
+
+    public void undo_cb ()
+    {
+        game.undo ();
+    }
+
+    public void redo_cb ()
+    {
+        game.redo ();
+    }
+
+    public void print_cb ()
+    {
+        stdout.printf ("TODO: Print\n");
+    }
+
+    public void print_multiple_cb ()
+    {
+        stdout.printf ("TODO: Print multiple\n");
+    }
+
+    public void possible_numbers_cb ()
+    {
+        view.show_possibilities = !view.show_possibilities;
+    }
+
+    private void possible_numbers_changed (SimpleAction action, Variant state)
+    {
+        view.show_possibilities = (bool) state;
+    }
+
+    public void unfillable_squares_cb ()
+    {
+        view.show_warnings = !view.show_warnings;
+    }
+
+    private void unfillable_squares_changed (SimpleAction action, Variant state)
+    {
+        view.show_warnings = (bool) state;
+    }
+
+    public void quit_cb ()
+    {
+        window.destroy ();
+    }
+
+    public bool sudoku_app_window_state_event_cb (Widget widget, Gdk.EventWindowState event)
+    {
+        if ((event.changed_mask & Gdk.WindowState.FULLSCREEN) != 0)
+        {
+            bool is_fullscreen = (event.new_window_state & Gdk.WindowState.FULLSCREEN) != 0;
+            settings.set_boolean ("fullscreen", is_fullscreen);
+            fullscreen_menu.active = is_fullscreen;
+        }
+
+        return false;
+    }
+
+    public void toggle_hints_cb (Widget widget)
+    {
+        view.show_hints = !view.show_hints;
+    }
+
+    public void toggle_warnings_cb (Widget widget)
+    {
+        view.show_warnings = !view.show_warnings;
+    }
+
+    public void hint_cb (Widget widget)
+    {
+        view.hint ();
+    }
+
+    public void clear_top_notes_cb (Widget widget)
+    {
+        view.clear_top_notes ();
+    }
+
+    public void clear_bottom_notes_cb (Widget widget)
+    {
+        view.clear_bottom_notes ();
+    }
+
+    public void toggle_tracker_cb (Widget widget)
+    {
+        stdout.printf ("TODO: Toggle tracker\n");
+    }
+
+    public void help_cb ()
+    {
+        try
+        {
+            show_uri (window.get_screen (), "ghelp:gnome-sudoku", get_current_event_time ());
+        }
+        catch (GLib.Error e)
+        {
+            GLib.warning ("Unable to open help: %s", e.message);
+        }
+    }
+
+    private const string[] authors = { "Robert Ancell <robert ancell gmail com>", "Christopher Baines 
<cbaines8 gmail com>" };
+    private const string[] artists = { "Place Holder <place holder com>" };
+
+    public void about_cb ()
+    {
+        show_about_dialog (window,
+                               "program-name", _("Sudoku"),
+                               "logo-icon-name", "gnome-sudoku",
+                               "version", Config.VERSION,
+                               "comments", _("GNOME Sudoku is a simple Sudoku generator and player. Sudoku 
is a Japanese logic puzzle.\n\nGNOME Sudoku is a part of GNOME Games."),
+                               "copyright", "Copyright 2010-2011 Robert Ancell <robert ancell gmail com>",
+                               "license-type", License.GPL_2_0,
+                               "wrap-license", false,
+                               "authors", authors,
+                               "artists", artists,
+                               "translator-credits", _("translator-credits"),
+                               "website", "http://www.gnome.org/projects/gnome-games/";,
+                               "website-label", _("GNOME Games web site")
+                               );
+    }
+}
diff --git a/src/main.vala b/src/main.vala
new file mode 100644
index 0000000..7a0dfdd
--- /dev/null
+++ b/src/main.vala
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 Robert Ancell <robert ancell gmail com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+private static bool show_version;
+
+private static const OptionEntry[] options =
+{
+    { "version", 'v', 0, OptionArg.NONE, ref show_version,
+    /* Help string for command line --version flag */
+    N_("Show release version"), null},
+    { null }
+};
+
+public static int main (string[] args)
+{
+    //SudokuGenerator.gen_stats ();
+    //SudokuRater.gen_python_test ();
+
+    //SudokuGenerator gen = new SudokuGenerator ();
+    //SudokuBoard board = gen.make_symmetric_puzzle(GLib.Random.int_range(0, 4));
+    //board.get_string ();
+    //stdout.printf("\n");
+
+    //SudokuBoard test_board = board.clone ();
+    //SudokuSolver test_solver = new SudokuSolver (ref test_board);
+
+    /*if (test_solver.quick_has_unique_solution ())
+        stdout.printf("Unique solution\n");
+    else
+        stdout.printf("No unique solution\n");
+
+
+    SudokuSolver solver = new SudokuSolver (ref board);
+    foreach (SudokuBoard solved_board in solver)
+    {
+        solved_board.get_string ();
+        stdout.printf("\n");
+    }*/
+
+    //return 0;
+
+    Gtk.init (ref args);
+
+    var c = new OptionContext (/* Arguments and description for --help text */
+                                   _("[FILE] - Play Sudoku"));
+    c.add_main_entries (options, Config.GETTEXT_PACKAGE);
+    c.add_group (Gtk.get_option_group (true));
+    try
+    {
+        c.parse (ref args);
+    }
+    catch (Error e)
+    {
+        stderr.printf ("%s\n", e.message);
+        stderr.printf (/* Text printed out when an unknown command-line argument provided */
+                       _("Run '%s --help' to see a full list of available command line options."), args[0]);
+        stderr.printf ("\n");
+        return Posix.EXIT_FAILURE;
+    }
+    if (show_version)
+    {
+        /* Note, not translated so can be easily parsed */
+        stderr.printf ("gnome-sudoku %s\n", Config.VERSION);
+        return Posix.EXIT_SUCCESS;
+    }
+
+    var app = new Sudoku ();
+
+    return app.run ();
+}
diff --git a/src/number-picker.vala b/src/number-picker.vala
new file mode 100644
index 0000000..9ce3a38
--- /dev/null
+++ b/src/number-picker.vala
@@ -0,0 +1,56 @@
+private class NumberPicker : Gtk.Grid
+{
+    private SudokuBoard board;
+
+    public signal void number_picked (int number);
+
+    public NumberPicker (ref SudokuBoard board, bool show_clear = true) {
+        this.board = board;
+
+        for (var col = 0; col < board.block_cols; col++)
+        {
+            for (var row = 0; row < board.block_rows; row++)
+            {
+                int n = col + row * board.block_cols + 1;
+
+                var button = new Gtk.Button ();
+                button.focus_on_click = false;
+                this.attach (button, col, row, 1, 1);
+
+                var label = new Gtk.Label ("<big>%d</big>".printf (n));
+                label.use_markup = true;
+                button.add (label);
+                label.show ();
+
+                button.clicked.connect (() => {
+                    number_picked(n);
+                });
+
+                if (n == 5)
+                    button.grab_focus ();
+
+                button.show ();
+            }
+        }
+
+        if (show_clear)
+        {
+            var button = new Gtk.Button ();
+            button.focus_on_click = false;
+            this.attach (button, 0, 4, 3, 1);
+
+            var label = new Gtk.Label ("<big>Clear</big>");
+            label.use_markup = true;
+            button.add (label);
+            label.show ();
+
+            button.clicked.connect (() => {
+                number_picked(0);
+            });
+
+            button.show ();
+        }
+
+        this.show ();
+    }
+}
diff --git a/src/sudoku-board.vala b/src/sudoku-board.vala
new file mode 100644
index 0000000..1a85260
--- /dev/null
+++ b/src/sudoku-board.vala
@@ -0,0 +1,512 @@
+using Gee;
+
+public class SudokuBoard
+{
+    /* Implemented in such a way that it can be extended for other sizes ( like 2x3 sudoku or 4x4 sudoku ) 
instead of normal 3x3 sudoku. */
+
+    protected int[,] cells;                     /* stores the value of the cells */
+    public bool[,] is_fixed;                    /* if the value at location is fixed or not */
+    private bool[,] possible_in_row;            /* if specific value is possible in specific row */
+    private bool[,] possible_in_col;            /* if specific value is possible in specific col */
+    private bool[,,] possible_in_block;         /* if specific value is possible in specific block */
+
+    /* Number of rows in one block */
+    private int _block_rows;
+    public int block_rows
+    {
+        get { return _block_rows; }
+    }
+
+    /* Number of columns in one block */
+    private int _block_cols;
+    public int block_cols
+    {
+        get { return _block_cols; }
+    }
+
+    /* Number of rows in board */
+    private int _rows;
+    public int rows
+    {
+        get { return _rows; }
+    }
+
+    /* Number of columns in board */
+    private int _cols;
+    public int cols
+    {
+        get { return _cols; }
+    }
+
+    /* Maximum possible val on board. 9 for 3x3 sudoku*/
+    public int max_val
+    {
+        get { return block_rows * block_cols; }
+    }
+
+    public bool broken
+    {
+        get { return broken_coords.size != 0; }
+    }
+
+    /* the number of filled squares on the board */
+    private int _filled;
+    public int filled
+    {
+        get { return _filled; }
+    }
+
+    public bool complete
+    {
+        get { return _filled == _cols * _rows && !broken; }
+    }
+
+    public signal void completed ();
+
+    /* The set of coordinates on the board which are invalid */
+    public Gee.Set<Coord?> broken_coords;
+
+    /* The list of coordinates for each column on the board */
+    public Gee.List<Gee.List<Coord?>> coords_for_col;
+
+    /* The list of coordinates for each row on the board */
+    public Gee.List<Gee.List<Coord?>> coords_for_row;
+
+    /* The map from the coordinate of a box, to the list of coordinates in that box, for each box on the 
board */
+    public Map<Coord?, Gee.List<Coord?>> coords_for_block;
+
+    public SudokuBoard (int block_rows = 3, int block_cols = 3)
+    {
+        _rows = _cols = block_rows * block_cols;
+        _block_rows = block_rows;
+        _block_cols = block_cols;
+        cells = new int[_rows, _cols];
+        is_fixed = new bool[_rows, _cols];
+        possible_in_row = new bool[_rows, _cols];
+        possible_in_col = new bool[_cols, _rows];
+        possible_in_block = new bool[_block_rows, _block_cols, _block_rows * _block_cols];
+
+        for (var l1 = 0; l1 < _rows; l1++)
+        {
+            for (var l2 = 0; l2 < _cols; l2++)
+            {
+                cells[l1, l2] = 0;
+                is_fixed[l1, l2] = false;
+                possible_in_row[l1, l2] = true;
+                possible_in_col[l2, l1] = true;
+            }
+        }
+        for (var l1 = 0; l1 < _block_rows; l1++)
+        {
+            for (var l2 = 0; l2 < _block_cols; l2++)
+            {
+                for (var l3 = 0; l3 < max_val; l3++)
+                    possible_in_block[l1, l2, l3] = true;
+            }
+        }
+
+        broken_coords = new HashSet<Coord?>((GLib.HashFunc) Coord.hash, (GLib.EqualFunc) Coord.equal);
+
+        coords_for_col = new ArrayList<Gee.List<Coord?>> ();
+        for (int col = 0; col < _cols; col++)
+        {
+            coords_for_col.add (new ArrayList<Coord?> ((GLib.EqualFunc) Coord.equal));
+            for (int row = 0; row < _rows; row++)
+            {
+                coords_for_col.get (col).add (Coord(row, col));
+            }
+            coords_for_col[col] = coords_for_col[col].read_only_view;
+        }
+        coords_for_col = coords_for_col.read_only_view;
+
+        coords_for_row = new ArrayList<Gee.List<Coord?>> ();
+        for (int row = 0; row < _rows; row++)
+        {
+            coords_for_row.add (new ArrayList<Coord?> ((GLib.EqualFunc) Coord.equal));
+            for (int col = 0; col < _cols; col++)
+            {
+                coords_for_row.get (row).add (Coord(row, col));
+            }
+            coords_for_row[row] = coords_for_row[row].read_only_view;
+        }
+        coords_for_row = coords_for_row.read_only_view;
+
+        coords_for_block = new HashMap<Coord?, Gee.List<Coord?>> ((GLib.HashFunc) Coord.hash, 
(GLib.EqualFunc) Coord.equal);
+        for (int col = 0; col < _block_cols; col++)
+        {
+            for (int row = 0; row < _block_rows; row++)
+            {
+                coords_for_block.set (Coord(row, col), new ArrayList<Coord?> ((GLib.EqualFunc) Coord.equal));
+            }
+        }
+        for (int col = 0; col < _cols; col++)
+        {
+            for (int row = 0; row < _rows; row++)
+            {
+                coords_for_block.get(Coord(row / _block_rows, col / _block_cols)).add(Coord(row, col));
+            }
+        }
+        for (int col = 0; col < _block_cols; col++)
+        {
+            for (int row = 0; row < _block_rows; row++)
+            {
+                coords_for_block[Coord(row, col)] = coords_for_block[Coord(row, col)].read_only_view;
+            }
+        }
+        coords_for_block = coords_for_block.read_only_view;
+    }
+
+    public SudokuBoard clone ()
+    {
+        SudokuBoard board = new SudokuBoard (_block_rows , _block_cols);
+        board.cells = cells;
+        board.is_fixed = is_fixed;
+        board.possible_in_row = possible_in_row;
+        board.possible_in_col = possible_in_col;
+        board.possible_in_block = possible_in_block;
+        board._filled = _filled;
+        board.broken_coords.add_all (broken_coords);
+
+        return board;
+    }
+
+    public void set_from_string (string s, string delimiter = "", string empty_value = "0")
+    {
+        //stdout.printf("Processing %s\n", s);
+
+        int number_of_cells = _cols * _rows;
+
+        string[] cells = s.split (delimiter, number_of_cells);
+
+        //stdout.printf("Cells %d %d\n", number_of_cells, cells.length);
+        for (int i = 0; i < number_of_cells; i++)
+        {
+            string cell = cells[i];
+            //stdout.printf("Cell %d: %s\n", i, cell);
+
+            if (cell != empty_value)
+            {
+                int val = int.parse(cell);
+                //stdout.printf("Cell val: %d\n", val);
+
+                assert (val >= 1 && val <= max_val);
+
+                insert (i / _cols, i % _cols, val, true);
+            }
+        }
+    }
+
+    public bool is_possible (int row, int col, int val)
+    {
+        val--;
+        return (possible_in_row[row, val] && possible_in_col[col, val] && possible_in_block [row / 
_block_cols, col / _block_rows, val]);
+    }
+
+    public int count_possibilities (int row, int col)
+    {
+        return get_possibilities(row, col).length;
+    }
+
+    public int[] get_possibilities (int row, int col)
+    {
+        if (cells [row, col] != 0)
+            return new int[0];
+
+        var possibilities = new int[9];
+        var count = 0;
+
+        for (var l = 1; l <= max_val; l++)
+        {
+            if (is_possible (row, col, l)) {
+                possibilities[count] = l;
+                count++;
+            }
+        }
+        return possibilities[0:count];
+    }
+
+    public bool[] get_possibilities_as_bool_array (int row, int col)
+    {
+        var possibilities = new bool[max_val];
+
+        for (var l = 1; l <= max_val; l++)
+        {
+            possibilities[l - 1] = is_possible (row, col, l);
+        }
+
+        return possibilities;
+    }
+
+    public Coord get_block_for(int row, int col)
+    {
+        return Coord(row / _block_rows, col / _block_cols);
+    }
+
+    public void insert (int row, int col, int val, bool is_fixed = false)
+    {
+        /* This should not happen when coded properly ;) */
+        assert (val > 0);
+        assert (val <= max_val);
+
+        /* Cant insert in to a fixed cell, unless you know what you are doing */
+        if (!is_fixed)
+            assert (!this.is_fixed[row, col]);
+
+        // If the cell has a previous value, remove it before continuing
+        if (cells[row, col] != 0)
+            remove(row, col, is_fixed);
+
+        cells[row, col] = val;
+        this.is_fixed[row, col] = is_fixed;
+        _filled++;
+
+        if (!possible_in_row[row, val - 1]) // If val was not possible in this row
+        {
+            mark_breakages_for(coords_for_row[row], val); // Mark the breakages
+        }
+
+        if (!possible_in_col[col, val - 1]) // If val was not possible in this col
+        {
+            mark_breakages_for(coords_for_col[col], val); // Mark the breakages
+        }
+
+        if (!possible_in_block[row / _block_cols, col / _block_rows, val - 1]) // If val was not possible in 
this block
+        {
+            mark_breakages_for(coords_for_block[Coord(row / _block_cols, col / _block_rows)], val); // Mark 
the breakages
+        }
+
+        // Then just mark it as not possible
+        val--;
+        possible_in_row[row, val] = false;
+        possible_in_col[col, val] = false;
+        possible_in_block[row / _block_cols, col / _block_rows, val] = false;
+
+        if (complete)
+            completed();
+    }
+
+    public void set (int row, int col, int val)
+    {
+        if (val == 0)
+        {
+            remove (row, col);
+        }
+        else if (val > 0 && val <= max_val)
+        {
+            insert (row, col, val);
+        }
+        else
+        {
+            assert_not_reached();
+        }
+    }
+
+    public int get (int row, int col)
+    {
+        return cells[row, col];
+    }
+
+    public void remove (int row, int col, bool is_fixed = false)
+    {
+        /* You can't remove an empty cell */
+        if (cells[row, col] == 0)
+            return;
+
+        /* You can't remove an fixed cell */
+        if (!is_fixed)
+            assert (!this.is_fixed[row, col]);
+
+        int previous_val = cells[row, col];
+        cells[row, col] = 0;
+
+        if (broken_coords.contains(Coord(row, col))) // If this cell was broken
+        {
+            // Remove all the related breakages in the related sets of cells
+            remove_breakages_for(coords_for_row[row], previous_val);
+            remove_breakages_for(coords_for_col[col], previous_val);
+            remove_breakages_for(coords_for_block[Coord(row / _block_rows, col / _block_cols)], 
previous_val);
+            broken_coords.remove(Coord(row, col));
+
+            // Re-mark all the breakages,
+            mark_breakages_for(coords_for_row[row], previous_val);
+            mark_breakages_for(coords_for_col[col], previous_val);
+            mark_breakages_for(coords_for_block[Coord(row / _block_rows, col / _block_cols)], previous_val);
+
+            // and update the possibilities accordingly
+            possible_in_row[row, previous_val - 1] = get_occurances(coords_for_row[row], previous_val).size 
== 0;
+            possible_in_col[col, previous_val - 1] = get_occurances(coords_for_col[col], previous_val).size 
== 0;
+            possible_in_block[row / _block_cols, col / _block_rows, previous_val - 1] = 
get_occurances(coords_for_block[Coord(row / _block_rows, col / _block_cols)], previous_val).size == 0;
+        }
+        else // Not previously broken, so just mark as a possible value
+        {
+            previous_val--;
+
+            possible_in_row[row, previous_val] = true;
+            possible_in_col[col, previous_val] = true;
+            possible_in_block[row / _block_cols, col / _block_rows, previous_val] = true;
+        }
+
+        _filled--;
+    }
+
+    public Set<Coord?> get_occurances(Gee.List<Coord?> coords, int val)
+    {
+        Set<Coord?> occurances = new HashSet<Coord?>((GLib.HashFunc) Coord.hash, (GLib.EqualFunc) 
Coord.equal);
+        foreach (Coord coord in coords)
+        {
+            if (cells[coord.row, coord.col] == val) {
+                occurances.add (coord);
+            }
+        }
+        return occurances;
+    }
+
+    public bool row_contains(int row, int val)
+    {
+        return get_occurances(coords_for_row[row], val).size != 0;
+    }
+
+    public bool col_contains(int col, int val)
+    {
+        return get_occurances(coords_for_col[col], val).size != 0;
+    }
+
+    public bool block_contains(Coord block, int val)
+    {
+        return get_occurances(coords_for_block[block], val).size != 0;
+    }
+
+    private void remove_breakages_for(Gee.List<Coord?> coords, int val)
+    {
+        foreach (Coord coord in coords)
+        {
+            if (cells[coord.row, coord.col] == val && broken_coords.contains(coord)) {
+                broken_coords.remove(coord);
+            }
+        }
+    }
+
+    /* returns if val is possible in coords */
+    private void mark_breakages_for(Gee.List<Coord?> coords, int val)
+    {
+        Set<Coord?> occurances = get_occurances(coords, val);
+        if (occurances.size != 1)
+        {
+            broken_coords.add_all(occurances);
+        }
+    }
+
+    public void to_initial_state ()
+    {
+        for (var l1 = 0; l1 < _rows; l1++)
+        {
+            for (var l2 = 0; l2 < _cols; l2++)
+            {
+                if (!is_fixed[l1, l2])
+                    remove (l1, l2);
+            }
+        }
+    }
+
+    public void print (int indent = 0) {
+        for (var l1 = 0; l1 < 9; l1++)
+        {
+            for (int i = 0; i < indent; i++)
+            {
+                stdout.printf(" ");
+            }
+            for (var l2 = 0; l2 < 9; l2++)
+            {
+                if (cells[l1,l2] != 0)
+                    stdout.printf ("%d ", cells[l1,l2]);
+                else
+                    stdout.printf ("  ");
+            }
+            stdout.printf ("\n");
+        }
+        stdout.flush ();
+    }
+
+    public void get_string () {
+        stdout.printf ("[ ");
+        for (var l1 = 0; l1 < 9; l1++)
+        {
+            stdout.printf ("[ ");
+            for (var l2 = 0; l2 < 9; l2++)
+            {
+                stdout.printf ("%d", cells[l1,l2]);
+                if (l2 != 8)
+                    stdout.printf (",");
+            }
+            stdout.printf (" ]");
+            if (l1 != 8)
+                stdout.printf (",");
+        }
+        stdout.printf (" ]");
+    }
+
+    public int[,] get_cells()
+    {
+        return cells;
+    }
+
+    public HashMap<Coord?, ArrayList<int>> calculate_open_squares () {
+        var possibilities = new HashMap<Coord?, ArrayList<int>> ((GLib.HashFunc) Coord.hash, 
(GLib.EqualFunc) Coord.equal);
+        for (var l1 = 0; l1 < _rows; l1++)
+        {
+            for (var l2 = 0; l2 < _cols; l2++)
+            {
+                if (cells[l1, l2] == 0)
+                {
+                    ArrayList<int> possArrayList = new ArrayList<int> ();
+                    int[] possArray = get_possibilities (l1, l2);
+                    foreach (int i in possArray) {
+                        possArrayList.add (i);
+                    }
+                    possibilities[Coord(l1, l2)] = possArrayList;
+                }
+            }
+        }
+        return possibilities;
+    }
+}
+
+public struct Coord
+{
+    public int row;
+    public int col;
+
+    public Coord(int row, int col)
+    {
+        this.row = row;
+        this.col = col;
+    }
+
+    public static int hash (Coord coord) {
+        return (coord.row * 33) ^ coord.col;
+    }
+
+    public static bool equal (Coord a, Coord b) {
+        return ((a.row == b.row) && (a.col == b.col));
+    }
+}
+
+public struct Cell
+{
+    public Coord coord;
+    public int val;
+
+    public Cell(Coord coord, int val)
+    {
+        this.coord = coord;
+        this.val = val;
+    }
+
+    public static int hash (Cell cell) {
+        return (Coord.hash(cell.coord) * 33) ^ cell.val;
+    }
+
+    public static bool equal (Cell a, Cell b) {
+        return (Coord.equal(a.coord, b.coord) && (a.val == b.val));
+    }
+}
diff --git a/src/sudoku-game.vala b/src/sudoku-game.vala
new file mode 100644
index 0000000..5558ed0
--- /dev/null
+++ b/src/sudoku-game.vala
@@ -0,0 +1,156 @@
+using Gee;
+
+public class SudokuGame
+{
+    public SudokuBoard board;
+    public GLib.Timer timer;
+
+    private struct UndoItem
+    {
+        public int row;
+        public int col;
+        public int val;
+    }
+
+    public signal void cell_changed (int row, int col, int old_val, int new_val);
+
+    private SList<UndoItem?> undostack;
+    private SList<UndoItem?> redostack;
+
+    public SudokuGame (SudokuBoard board)
+    {
+        this.board = board;
+        timer = new Timer();
+        undostack = null;
+        redostack = null;
+    }
+
+    public void insert (int row, int col, int val)
+    {
+        var old_val = board[row, col];
+        update_undo (row, col, old_val, val);
+        board.insert (row, col, val);
+        cell_changed (row, col, old_val, val);
+    }
+
+    public void remove (int row, int col)
+    {
+        int old_val = board[row, col];
+        update_undo (row, col, old_val, 0);
+        board.remove (row, col);
+        cell_changed (row, col, old_val, 0);
+    }
+
+    public void undo ()
+    {
+        apply_stack (ref undostack, ref redostack);
+    }
+
+    public void redo ()
+    {
+        apply_stack (ref redostack, ref undostack);
+    }
+
+    public void reset ()
+    {
+        timer.reset();
+        var count = board.filled;
+        for (var l1 = 0; l1 < board.rows; l1++)
+        {
+            for (var l2 = 0; l2 < board.cols; l2++)
+            {
+                if (!board.is_fixed[l1, l2])
+                    remove (l1, l2);
+            }
+        }
+        count -= board.filled;
+        add_to_stack (ref undostack, -1, -1, count);
+    }
+
+    public void hint (ref int row, ref int col)
+    {
+        var total_pos = new int [board.rows, board.cols];
+        var min = board.max_val + 1;
+        var count = 0;
+        for (var l1 = 0; l1 < board.rows; l1++)
+        {
+            for (var l2 = 0; l2 < board.cols; l2++)
+            {
+                total_pos [l1, l2] = board.count_possibilities (l1, l2);
+                if (total_pos [l1, l2] < min && total_pos [l1, l2] > 0)
+                {
+                    min = total_pos [l1, l2];
+                    count = 0;
+                }
+                if (total_pos [l1, l2] == min)
+                {
+                    count++;
+                }
+            }
+        }
+        if (count == 0)
+            return;
+        count = Random.int_range (1, count + 1);
+        for (var l1 = 0; l1 < board.rows; l1++)
+        {
+            for (var l2 = 0; l2 < board.cols; l2++)
+            {
+                if (total_pos[l1, l2] == min)
+                {
+                    count--;
+                    if (count == 0)
+                    {
+                        row = l1;
+                        col = l2;
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    public void cell_changed_cb (int row, int col, int old_val, int new_val)
+    {
+        cell_changed (row, col, old_val, new_val);
+    }
+
+    public void update_undo (int row, int col, int old_val, int new_val)
+    {
+        add_to_stack (ref undostack, row, col, old_val);
+        redostack = null;
+    }
+
+    private void add_to_stack (ref SList<UndoItem?> stack, int r, int c, int v)
+    {
+        UndoItem step = { r, c, v };
+        stack.prepend (step);
+    }
+
+    private void apply_stack (ref SList<UndoItem?> from, ref SList<UndoItem?> to)
+    {
+        if (from == null)
+            return;
+
+        /* Undoing change of single cell */
+        if (from.data.row >= 0 && from.data.col >= 0)
+        {
+            int old_val = board [from.data.row, from.data.col];
+            add_to_stack (ref to, from.data.row, from.data.col, old_val);
+            board.remove (from.data.row, from.data.col);
+            if (from.data.val != 0) {
+                board.insert (from.data.row, from.data.col, from.data.val);
+            }
+            cell_changed (from.data.row, from.data.col, old_val, from.data.val);
+            from.remove (from.data);
+        }
+        /* Undoing reset action */
+        else
+        {
+            var num = from.data.val;
+            from.remove (from.data);
+            for (var l = 0; l < num; l++)
+                apply_stack (ref from, ref to);
+            add_to_stack (ref to, -1, -1, num);
+        }
+    }
+}
diff --git a/src/sudoku-generator.vala b/src/sudoku-generator.vala
new file mode 100644
index 0000000..2bc635b
--- /dev/null
+++ b/src/sudoku-generator.vala
@@ -0,0 +1,413 @@
+public class SudokuGenerator
+{
+    public int clues = 40;
+
+    public SudokuBoard start_board = new SudokuBoard(3, 3);
+    private SudokuBoard temp_start_board = new SudokuBoard(3, 3);
+
+    public struct RatedSudoku {
+
+        public SudokuBoard board;
+        public DifficultyRating diff;
+
+        public RatedSudoku(SudokuBoard board, DifficultyRating diff)
+        {
+            this.board = board;
+            this.diff = diff;
+        }
+    }
+
+    public SudokuGenerator() {
+        int solution = 0;
+        generate_start_board(-1, -1, -1, -1, ref solution);
+    }
+
+    public static void gen_stats ()
+    {
+        for (int i = 22; i < 77; i++)
+        {
+            stdout.printf("%d, ", i);
+            for (int repeat = 0; repeat < 50; repeat++)
+            {
+                stdout.printf("repeat %d\n", repeat);
+                SudokuGenerator gen = new SudokuGenerator();
+                gen.clues = i;
+
+
+                RatedSudoku? rated_sudoku = gen.make_unique_puzzle(Random.int_range(0, 4), true);
+                while (true) {
+                    if (rated_sudoku == null)
+                        gen.make_unique_puzzle(Random.int_range(0, 4), true);
+                    else
+                        break;
+                }
+
+                /*SudokuBoard board = gen.make_symmetric_puzzle (Random.int_range(0, 4));
+                while (true)
+                {
+                    SudokuBoard test_board = board.clone ();
+
+                    SudokuSolver test_solver = new SudokuSolver(ref test_board);
+                    if (test_solver.quick_has_unique_solution ())
+                        break;
+                }*/
+
+                stdout.printf("%f, ", rated_sudoku.diff.rating);
+            }
+            stdout.printf("\n");
+        }
+    }
+
+    public SudokuBoard generate (float[] difficulty_range, bool symmetric = true)
+    {
+        int count = 0;
+
+        do {
+            float skew = 0.0f;
+
+            clues = get_cells_for ((float) Random.double_range((double) difficulty_range[0], (double) 
difficulty_range[1]));
+
+            if (clues < 17)
+                clues = 17;
+
+            if (clues > 60)
+                clues = 60;
+
+            if (count >= 10 && count % 10 == 0)
+            {
+                int solution = 0;
+                generate_start_board(-1, -1, -1, -1, ref solution);
+            }
+
+            //stdout.printf("Generating with %d clues\n", clues);
+
+            SudokuBoard puzzle;
+
+            if (symmetric) {
+                RatedSudoku rated_sudoku = make_unique_puzzle (GLib.Random.int_range(0, 3));
+
+                if (rated_sudoku.board != null && rated_sudoku.diff.in_range(difficulty_range))
+                    return rated_sudoku.board;
+            } else {
+                puzzle = make_puzzle_by_boxes (skew);
+            }
+
+            count++;
+        } while (count < 100);
+
+        stdout.printf ("Puzzle not found..\n");
+
+        return new SudokuBoard(3, 3);
+    }
+
+    private int get_cells_for (float difficulty)
+    {
+        return (int) ((-22.3275863216 * difficulty) + 44.9652207631);
+    }
+
+    private void generate_start_board (int row, int col, int no, int filled, ref int solution)
+    {
+        if (filled == -1)
+            filled = temp_start_board.filled;
+
+        if (filled == temp_start_board.rows * temp_start_board.cols)
+        {
+            solution++;
+            for (var y = 0; y < 9; y++)
+            {
+                for (var x = 0; x < 9; x++)
+                {
+                    start_board.insert (x, y, temp_start_board[x, y]);
+                }
+            }
+            return;
+        }
+
+        if (row == -1 || col == -1)
+        {
+            for (var l1 = 0; l1 < temp_start_board.rows; l1++)
+            {
+                for (var l2 = 0;l2 < temp_start_board.cols; l2++)
+                {
+                    if (temp_start_board[l1, l2] == 0)
+                    {
+                        generate_start_board (l1, l2, no, filled, ref solution);
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (no == -1)
+        {
+            bool[] values_tried = new bool[temp_start_board.max_val];
+            int values_tried_count = 0;
+            while (values_tried_count < temp_start_board.max_val)
+            {
+                // Find a value that has not been tried yet
+                int l1 = -1;
+                do {
+                    l1 = GLib.Random.int_range(0, temp_start_board.max_val) + 1;
+                } while (values_tried[l1]);
+
+                values_tried_count++;
+
+                if (temp_start_board.is_possible (row, col, l1))
+                {
+                    generate_start_board (row, col, l1, filled, ref solution);
+
+                    if (solution > 0)
+                        return;
+                }
+            }
+            return;
+        }
+
+        temp_start_board.insert (row, col, no);
+        generate_start_board (-1, -1, -1, filled + 1, ref solution);
+        temp_start_board.remove (row, col);
+    }
+
+    public SudokuBoard make_symmetric_puzzle (int line)
+    {
+        SudokuBoard new_puzzle = new SudokuBoard(3, 3);
+
+        while (new_puzzle.filled < clues) {
+            int row = GLib.Random.int_range(0, 9);
+            int col = GLib.Random.int_range(0, 9);
+
+            if (!new_puzzle.is_fixed[row, col]) {
+
+                Coord reflection = reflect(row, col, line);
+
+                new_puzzle.insert(row, col, start_board[row, col], true);
+
+                if (reflection.row == row && reflection.col == col)
+                    continue; // Skip as the reflection is itself
+
+                new_puzzle.insert(reflection.row, reflection.col, start_board[reflection.row, 
reflection.col], true);
+            }
+
+        }
+        return new_puzzle;
+    }
+
+    private Coord reflect(int row, int col, int line)
+    {
+        if (line == 0) // Vertical, through the middle
+        {
+            return Coord(row, 8 - col);
+        }
+        else if (line == 1) // From bottom right to top left
+        {
+            return Coord(8 - col, 8 - row);
+        }
+        else if (line == 2) // Horizontal through the middle
+        {
+            return Coord(8 - row, col);
+        }
+        else if (line == 3) // From top left to bottom right
+        {
+            return Coord(8 - row, 8 - col);
+        }
+        else
+        {
+            return Coord(row, col);
+        }
+    }
+
+    /*  Make a puzzle paying attention to evenness of clue
+        distribution.
+
+        If skew_by is 0, we distribute our clues as evenly as possible
+        across boxes.  If skew by is 1.0, we make the distribution of
+        clues as uneven as possible. In other words, if we had 27
+        boxes for a 9x9 grid, a skew_by of 0 would put exactly 3 clues
+        in each 3x3 grid whereas a skew_by of 1.0 would completely
+        fill 3 3x3 grids with clues.
+
+        We believe this skewing may have something to do with how
+        difficult a puzzle is to solve. By toying with the ratios,
+        this method may make it considerably easier to generate
+        difficult or easy puzzles.
+    */
+    public SudokuBoard make_puzzle_by_boxes (float skew_by = 0.0f) {
+        SudokuBoard new_puzzle = new SudokuBoard(3, 3);
+
+        // Number of total boxes
+        int nboxes = 9; // TODO: Fix
+        // If no max is given, we calculate one based on our skew_by --
+        // a skew_by of 1 will always produce full squares, 0 will
+        // produce the minimum fullness, and between between in
+        // proportion to its betweenness.
+        int max_squares = clues / nboxes;
+        max_squares += (int) ((nboxes - max_squares) * skew_by);
+        int clued = 0;
+        // nclues will be a list of the number of clues we want per
+        // box, counting from the top left, along, and wraping.
+        //  _ _ _
+        // |0|1|2|
+        // |3|4|5|
+        // |6|7|8|
+        //  - - -
+
+        int[] nclues = new int[nboxes];
+        for (int n = 0; n < nboxes; n++) {
+            // Make sure we'll have enough clues to fill our target
+            // number, regardless of our calculation of the current max
+
+            // TODO: The following code (directly translated from python) does nothing?
+            // int minimum = (this.clues - clued) / (nboxes - n);
+            //if (max_squares < minimum) {
+            //    cls = minimum
+            //} else {
+            //    cls = int(max_squares)
+            //}
+            int clues = max_squares;
+            if (clues > (this.clues - clued)) {
+                clues = this.clues - clued;
+            }
+            nclues[n] = clues;
+
+            clued += clues;
+            if (skew_by != 0) {
+                // Reduce our number of squares proportionally to
+                // skewiness.
+                max_squares = (int) GLib.Math.round(max_squares * skew_by);
+            }
+        }
+
+        // shuffle ourselves... probably badly (TODO)
+        for (int i=0; i<nboxes; i++) {
+            int from = GLib.Random.int_range(0, nboxes);
+            int to = GLib.Random.int_range(0, nboxes);
+
+            int temp = nclues[to];
+            nclues[to] = nclues[from];
+            nclues[from] = temp;
+        }
+
+        for (int i = 0; i < nboxes; i++) {
+            while (nclues[i] > 0) {
+                int base_x = (i % new_puzzle.block_cols) * new_puzzle.block_cols;
+                int base_y = (i / new_puzzle.block_rows) * new_puzzle.block_rows;
+
+                int x = GLib.Random.int_range(base_x, base_x + new_puzzle.block_rows);
+                int y = GLib.Random.int_range(base_y, base_y + new_puzzle.block_cols);
+
+                if (!new_puzzle.is_fixed[x, y]) {
+                    new_puzzle.insert(x, y, start_board[x, y], true);
+                    nclues[i]--;
+                }
+            }
+        }
+        return new_puzzle;
+    }
+
+    public RatedSudoku? make_unique_puzzle (int line = -1, bool strict_number_of_clues = false)
+    {
+        SudokuBoard board;
+        DifficultyRating diff;
+
+        if (line != -1)
+        {
+            int old_clues = clues;
+            clues = 4;
+            board = make_symmetric_puzzle (line);
+            clues = old_clues;
+            //stdout.printf("made a board with %d clues\n", board.filled);
+        }
+        else
+        {
+            board = make_puzzle_by_boxes ();
+        }
+
+        while (true)
+        {
+            SudokuBoard solved_board = board.clone ();
+            SudokuRater solver = new SudokuRater(ref solved_board);
+            if (solver.has_unique_solution())
+            {
+                //stdout.printf("unique solution found\n");
+                diff = solver.get_difficulty();
+                break;
+            }
+            //stdout.printf("no unique solution found\n");
+
+            //board.print ();
+
+            //stdout.printf("possible solutions\n");
+
+            /*SudokuBoard solved_board_two = board.clone ();
+            SudokuRater solver_two = new SudokuRater(ref solved_board_two);
+
+            int solutions = solver_two.quick_count_solutions ();
+            if (solutions < 5)
+            {
+                foreach (SudokuBoard solution in solver_two)
+                {
+                    solution.print ();
+                    stdout.printf("\n");
+                }
+            }
+            else
+            {
+                stdout.printf("have %d solutions\n", solutions);
+            }*/
+
+            for (int i = 0; i < solver.breadcrumbs.size; i++)
+            {
+                Guess guess = solver.breadcrumbs[i];
+                //stdout.printf("crumb (%d, %d) %d\n", guess.col, guess.row, guess.val);
+            }
+
+            // Otherwise...
+            Guess crumb = solver.breadcrumbs[solver.breadcrumbs.size - 1];
+            // stdout.printf("got crumb (%d, %d) %d\n", crumb.col, crumb.row, crumb.val);
+
+            board.insert (crumb.row, crumb.col, start_board[crumb.row, crumb.col], true);
+
+            Coord reflection = reflect(crumb.row, crumb.col, line);
+
+            board.insert (reflection.row, reflection.col, start_board[reflection.row, reflection.col], true);
+
+            //stdout.printf("Not unique, filling in (%d, %d) and (%d, %d)\n", crumb.row, crumb.col, 
reflection.row, reflection.col);
+
+            if (strict_number_of_clues && board.filled > clues)
+            {
+                //stdout.printf("failed to get the number of clues, instead got %d\n", board.filled);
+                return null;
+            }
+            //stdout.printf("trying again\n");
+        }
+
+        // make sure we have the proper number of clues
+        if (strict_number_of_clues)
+        {
+            //stdout.printf("checking the number of clues\n");
+            bool changed = false;
+            while (board.filled < clues)
+            {
+                int row = -1;
+                int col = -1;
+                do
+                {
+                    row = Random.int_range(0, 9);
+                    col = Random.int_range(0, 9);
+                }
+                while (board[row, col] == 0);
+
+                board.insert (row, col, start_board[row, col], true);
+
+                Coord reflection = reflect(row, col, line);
+                board.insert (reflection.row, reflection.col, start_board[reflection.row, reflection.col], 
true);
+
+                changed = true;
+            }
+            if (changed)
+                diff = new SudokuRater(ref board).get_difficulty();
+        }
+        //stdout.printf("returning rated puzzle\n");
+        return RatedSudoku(board, diff);
+    }
+}
diff --git a/src/sudoku-solver.vala b/src/sudoku-solver.vala
new file mode 100644
index 0000000..f732ce3
--- /dev/null
+++ b/src/sudoku-solver.vala
@@ -0,0 +1,872 @@
+using Gee;
+
+protected errordomain SudokuError {
+    UNSOLVABLE_PUZZLE,
+    CONFLICT_ERROR,
+    ALREADY_SET_ERROR
+}
+
+public class SudokuSolver
+{
+    protected SudokuBoard board;
+
+    private ParallelDict conflicts;
+    protected GuessList guesses;
+    public BreadcrumbTrail breadcrumbs;
+    protected int backtraces = 0;
+    private Guess current_guess;
+
+    private bool count_solutions = false;
+    private int break_at = 100;
+
+    private ArrayList<Guess> trail = new ArrayList<Guess> ();
+    private ArrayList<string> trailDetails = new ArrayList<string> ();
+
+    private int debug_indent = 0;
+
+    protected bool solved;
+
+    public SudokuSolver (ref SudokuBoard board)
+    {
+        this.board = board;
+        this.conflicts = new ParallelDict();
+        this.guesses = new GuessList();
+        this.breadcrumbs = new BreadcrumbTrail();
+        this.solved = false;
+    }
+
+    /* Check if current SudokuBoard has at least one solution or not */
+    public bool quick_has_solution ()
+    {
+        int solutions = 0;
+        count_solutions = false;
+        quick_solve (-1, -1, -1, -1, ref solutions);
+        return (solutions > 0);
+    }
+
+    public bool quick_has_unique_solution ()
+    /*Check if current SudokuBoard has unique solution or not*/
+    {
+        int solutions = 0;
+        count_solutions = false;
+        quick_solve (-1, -1, -1, -1, ref solutions);
+        return (solutions == 1);
+    }
+
+    /* Check if current SudokuBoard has more than one solution or not */
+    public bool quick_has_many_solution ()
+    {
+        int solutions = 0;
+        count_solutions = false;
+        quick_solve (-1, -1, -1, -1, ref solutions);
+        return (solutions > 1);
+    }
+
+    /* Check if current SudokuBoard has more than one solution or not */
+    public int quick_count_solutions (int break_at = 100)
+    {
+        int solutions = 0;
+        count_solutions = true;
+        this.break_at = break_at;
+        quick_solve (-1, -1, -1, -1, ref solutions);
+        return solutions;
+    }
+
+    private void quick_solve (int row, int col, int no, int filled, ref int solution)
+    {
+        if (filled == -1)
+            filled = board.filled;
+
+        if (filled == board.rows * board.cols)
+        {
+            solution++;
+            return;
+        }
+
+        if (row == -1 || col == -1)
+        {
+            for (var l1 = 0; l1 < board.rows; l1++)
+            {
+                for (var l2 = 0;l2 < board.cols; l2++)
+                {
+                    if (board[l1, l2] == 0)
+                    {
+                        quick_solve (l1, l2, no, filled, ref solution);
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (no == -1)
+        {
+            for (var l1 = 1; l1 <= board.max_val; l1++)
+            {
+                if (board.is_possible (row, col, l1))
+                {
+                    quick_solve (row, col, l1, filled, ref solution);
+                    /* Break at solutions == 2 as we don't need exact count of possible solutions */
+                    if ((!count_solutions && solution > 1) || (solution > break_at))
+                        return;
+                }
+            }
+            return;
+        }
+
+        board.insert (row, col, no);
+        quick_solve (-1, -1, -1, filled + 1, ref solution);
+        board.remove (row, col);
+    }
+
+
+
+    public Iterator iterator ()
+    {
+        return new Iterator(this);
+    }
+
+    public bool has_unique_solution ()
+    {
+        Iterator sf = iterator ();
+
+        if (sf.next() == true && sf.next() == false)
+            return true;
+        else
+            return false;
+    }
+
+    public class Iterator {
+        private SudokuSolver solver;
+        private SudokuBoard solution;
+
+        public Iterator(SudokuSolver solver) {
+            this.solver = solver;
+        }
+
+        public bool next() {
+            if (!solver.solved)
+            {
+                solver.auto_fill ();
+                try
+                {
+                    while (!solver.guess_least_open_square());
+                }
+                catch (SudokuError e)
+                {
+                    solution = null;
+                    return false;
+                }
+
+                solver.solved = true;
+                solution = solver.board;
+
+                return true;
+            } else {
+                while (solver.breadcrumbs.size != 0)
+                {
+                    solver.unwrap_guess(solver.breadcrumbs[solver.breadcrumbs.size -1]);
+                    try
+                    {
+                        while (!solver.guess_least_open_square());
+                    }
+                    catch (SudokuError e)
+                    {
+                        solution = null;
+                        return false;
+                    }
+                    solution = solver.board;
+                    return true;
+                }
+
+                return false;
+            }
+        }
+
+        public SudokuBoard? get() {
+            return solution;
+        }
+    }
+
+    /*
+     * Fills the board looking at the possibilities, then starts guessing using guess_least_open_square
+     */
+    public SudokuBoard solve () {
+        auto_fill();
+        try {
+           while (!guess_least_open_square());
+        } catch (SudokuError e) {
+
+        }
+        solved = true;
+        return board;
+    }
+
+    /*
+     * This method uses fill_must_fills, then fill_deterministically to fill the board
+     */
+    private ArrayList<Cell?> auto_fill () {
+        ArrayList<Cell?> changed = new ArrayList<Cell?> ();
+        try {
+            changed.add_all (fill_must_fills ());
+        } catch ( SudokuError e ) {
+            return changed;
+        }
+        changed.add_all (fill_deterministically ());
+        return changed;
+    }
+
+    /*
+     * This method looks at each cell on the board, and if it has one possible value, inserts it
+     */
+    protected ArrayList<Cell?> fill_deterministically () {
+        HashMap<Coord?, ArrayList<int>> poss = board.calculate_open_squares ();
+
+        ArrayList<Cell?> changed = new ArrayList<Cell?> ();
+        foreach (Coord coord in poss.keys) {
+            ArrayList<int> choices = poss[coord];
+
+            if (choices.size != 1)
+                continue;
+
+            int val = choices[0];
+
+            insert (coord.row, coord.col, val);
+            changed.add ( Cell(coord, val));
+        }
+        return changed;
+    }
+
+    /*
+     * This method looks at each set of cells on the board, and then calls fill_must_fills_for on them, it 
returns the changes it has made as an array
+     */
+    protected ArrayList<Cell?> fill_must_fills () throws SudokuError {
+        ArrayList<Cell?> changed = new ArrayList<Cell?> ();
+
+        for (int col = 0; col < board.cols; col++) {
+            changed.add_all(fill_must_fills_for(board.coords_for_col.get(col)));
+        }
+
+        for (int row = 0; row < board.rows; row++) {
+            changed.add_all(fill_must_fills_for(board.coords_for_row.get (row)));
+        }
+
+        int blocks_across = board.cols / board.block_cols;
+        int blocks_down = board.rows / board.block_rows;
+
+        for (int block_down = 0; block_down < blocks_down; block_down++) {
+            for (int block_across = 0; block_across < blocks_across; block_across++) {
+                changed.add_all(fill_must_fills_for(board.coords_for_block.get (Coord(block_across, 
block_down))));
+            }
+        }
+        return changed;
+    }
+
+    /*
+     * This method looks at the list of coords its been given, it returns the changes it has made as an array
+     */
+    private ArrayList<Cell?> fill_must_fills_for (Gee.List<Coord?> coords) throws SudokuError {
+        bool skip_set = false;
+        foreach (Coord coord in coords) {
+            if (conflicts.contains(coord)) {
+                skip_set = true;
+                break;
+            }
+        }
+
+        if (skip_set)
+            return new ArrayList<Cell?> ();
+
+        // This list holds the changes that are made to the board
+        ArrayList<Cell?> changed = new ArrayList<Cell?> ((GLib.EqualFunc) Coord.equal);
+
+        // Maps the number needed to a cell that can hold it
+        var needs = new HashMap<int, Coord?> (null, null, (GLib.EqualFunc) Coord.equal);
+        for (int i=1; i <= board.max_val; i++)
+            needs[i] = null;
+
+        // Look at each of the coords in the list
+        foreach (Coord coord in coords) {
+
+            int val = board[coord.row, coord.col];
+            if (val != 0) { // If it contains a value
+                if (needs.has_key(val)) { // If the value that it contains is still down as being needed
+                    needs.unset(val); // Remove it
+                }
+            } else { // If its empty
+                // Find out what can go in it
+                int[] possibilities = board.get_possibilities(coord.row, coord.col);
+
+                // For each value it can hold
+                foreach (int possibility in possibilities) {
+                    // If its needed
+                    if (needs.has_key(possibility)) {
+                        if (needs[possibility] == null) { // If it has no candidate, put the current coord in
+                            needs[possibility] = coord;
+                        } else { // Else, as this value can go in two cells (either coord or 
needs[possibility]),
+                                 // we cant deal with it, so remove it
+                            needs.unset(possibility);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (needs.size != 0) { // If this block needs any values
+            foreach (int n in needs.keys) { // Foreach value n, that this block needs
+                if (needs[n] == null) { // If n is needed, but there is no candidate, panic?!?
+                    //stdout.printf("but its null, oh noes\n");
+                    throw new SudokuError.UNSOLVABLE_PUZZLE("Missing a %d in\n", n);
+                }
+                else
+                {
+                    int val = board[needs[n].row, needs[n].col]; // Get the value currently in the cell
+                    // FIXME: Not sure why the val == n is here, the python code is a bit flakey with the 
exceptions in add here...
+                    if (val == 0 || val == n) { // If this cell is empty
+                        insert(needs[n].row, needs[n].col, n); // Insert the value
+                        changed.add (Cell(needs[n], n));
+                    } else { // Else, we need val in here, but its already occupied?!?
+                        //stdout.printf("%d, %d must be two values at once! %d and %d\n", needs[n].col, 
needs[n].row, val, n);
+                        throw new SudokuError.UNSOLVABLE_PUZZLE("%d, %d must be two values at once! %d and 
%d", needs[n].col, needs[n].row, val, n);
+                    }
+                }
+            }
+        }
+
+        return changed;
+    }
+
+    /*
+     * Guesses the least open cell (cell with the smallest number of possibilitites) on the board.
+     * It returns true if there are no open squares, or false ...
+     */
+    protected virtual bool guess_least_open_square () throws SudokuError
+    {
+        HashMap<Coord?, ArrayList<int>> poss = board.calculate_open_squares ();
+
+        // if there are no open squares, we're done!
+        if (poss.keys.size == 0) {
+            return true;
+        }
+
+        // Find the square with the least possibilties
+        MapIterator<Coord?, ArrayList<int>> iter = poss.map_iterator ();
+        iter.first ();
+        Coord least_coord = iter.get_key ();
+        ArrayList<int> least_coord_possibilties = iter.get_value ();
+
+        foreach (Coord coord in poss.keys) {
+            if (poss[coord].size > least_coord_possibilties.size)
+            {
+                continue;
+            }
+            else if (poss[coord].size < least_coord_possibilties.size)
+            {
+                least_coord = coord;
+                least_coord_possibilties = poss[coord];
+            }
+            else if (coord.col < least_coord.col)
+            {
+                least_coord = coord;
+                least_coord_possibilties = poss[coord];
+            }
+            else if (coord.col == least_coord.col && coord.row < least_coord.row)
+            {
+                least_coord = coord;
+                least_coord_possibilties = poss[coord];
+            }
+        }
+
+        ArrayList<int> possible_values = new ArrayList<int> ();
+        Guess[] guesses_for_coord = guesses.guesses_for (least_coord.row, least_coord.col);
+
+        // Remove all possibilties already guessed
+        foreach (int possibility in least_coord_possibilties)
+        {
+            bool found = false;
+            foreach (Guess guess in guesses_for_coord)
+            {
+                if (guess.val == possibility)
+                {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found)
+                possible_values.add (possibility);
+        }
+
+        if (possible_values.size == 0)
+        {
+            if (breadcrumbs.size != 0)
+            {
+                backtraces += 1;
+                unwrap_guess( breadcrumbs.get (breadcrumbs.size - 1) );
+                debug_indent++;
+                return guess_least_open_square();
+            }
+            else
+            {
+                throw new SudokuError.UNSOLVABLE_PUZZLE ("Unsolvable");
+            }
+        }
+
+        // Pick a random number to guess (from the list of possible numbers)
+        int guess = possible_values.get (Random.int_range (0, possible_values.size));
+
+        Guess guess_obj = new Guess(least_coord.row, least_coord.col, guess);
+
+        if (breadcrumbs.size != 0) {
+            breadcrumbs.get (breadcrumbs.size -1).children.add (guess_obj);
+        }
+
+        current_guess = null; // reset (we're tracked via guess.get_child())
+
+        insert (least_coord.row, least_coord.col, guess);
+        current_guess = guess_obj; // (All deterministic additions
+                                   // get added to our
+                                   // consequences)
+        guesses.add (guess_obj);
+
+        trail.add (guess_obj);
+        trailDetails.add ("+");
+
+        breadcrumbs.add (guess_obj);
+        ArrayList<Cell?> fills = auto_fill ();
+
+        bool contains_empty = false;
+
+        Collection<ArrayList<int>> possibilties_left = board.calculate_open_squares().values;
+        foreach (ArrayList<int> i in possibilties_left) {
+            if (i.size == 0)
+                contains_empty = true;
+        }
+
+        if (contains_empty) {
+            trailDetails.add ("Guess leaves us with impossible squares.");
+            unwrap_guess (guess_obj);
+            debug_indent++;
+            return guess_least_open_square();
+        }
+
+        return false;
+    }
+
+    private void unwrap_guess (Guess guess)
+    {
+        trail.add (guess);
+        trailDetails.add ("-");
+
+        if (board[guess.row, guess.col] != 0) {
+            board.remove (guess.row, guess.col);
+        }
+        foreach (Coord coord in guess.consequences.keys) {
+            if (board[coord.row, coord.col] != 0) {
+                board.remove (coord.row, coord.col);
+            }
+        }
+
+        foreach (Guess child in guess.children) {
+            unwrap_guess (child);
+            if (child in guesses)
+                guesses.remove (child);
+        }
+
+        if (guess in breadcrumbs) {
+            breadcrumbs.remove (guess);
+        }
+    }
+
+    protected virtual void insert (int row, int col, int val)
+    {
+        if (current_guess != null)
+            current_guess.add_consequence(row, col, val);
+        board.insert (row, col, val);
+    }
+}
+
+class SudokuRater : SudokuSolver {
+
+    private bool guessing;
+    private bool fake_add;
+    private ArrayList<Cell?> fake_additions;
+    private ArrayList<Cell?> add_me_queue;
+    private HashSet<Cell?> filled;
+    private HashMap<int, HashSet<Cell?>> fill_must_fillables;
+    private HashMap<int, HashSet<Cell?>> elimination_fillables;
+    private int tier;
+
+    public SudokuRater (ref SudokuBoard board) {
+        base(ref board);
+        guessing = false;
+        fake_add = false;
+        fake_additions = new ArrayList<Cell?> ();
+        filled = new HashSet<Cell?> ((GLib.HashFunc) Cell.hash, (GLib.EqualFunc) Cell.equal);
+        fill_must_fillables = new HashMap<int, HashSet<Cell?>> ();
+        elimination_fillables = new HashMap<int, HashSet<Cell?>> ();
+        tier = 0;
+    }
+
+    protected override void insert (int row, int col, int val)
+    {
+        if (!fake_add) {
+            if (!guessing) {
+                scan_fillables();
+                foreach (Cell delayed_cell in add_me_queue)
+                {
+                    if (board[delayed_cell.coord.row, delayed_cell.coord.col] == 0) {
+                        base.insert(delayed_cell.coord.row, delayed_cell.coord.col, delayed_cell.val);
+                    }
+                }
+                if (board[row, col] == 0)
+                    base.insert(row, col, val);
+                tier += 1;
+            }
+            else
+            {
+                base.insert(row, col, val);
+            }
+        }
+        else
+        {
+            fake_additions.add (Cell(Coord(row, col), val));
+        }
+    }
+
+    private void scan_fillables () {
+        fake_add = true;
+        // this will now tell us how many squares at current
+        // difficulty could be filled at this moment.
+        fake_additions = new ArrayList<Cell?> ();
+        try {
+            fill_must_fills();
+        } catch (SudokuError e) {
+        }
+        fill_must_fillables[tier] = new HashSet<Cell?> ((GLib.HashFunc) Cell.hash, (GLib.EqualFunc) 
Cell.equal);
+        foreach (Cell cell in fake_additions) {
+            if (!filled.contains(cell))
+                fill_must_fillables[tier].add (cell);
+        }
+
+        add_me_queue = fake_additions;
+        fake_additions = new ArrayList<Cell?> ();
+
+        try {
+            fill_deterministically();
+        } catch (SudokuError e) {
+        }
+
+        elimination_fillables[tier] = new HashSet<Cell?> ((GLib.HashFunc) Cell.hash, (GLib.EqualFunc) 
Cell.equal);
+        foreach (Cell cell in fake_additions) {
+            if (!filled.contains(cell))
+                elimination_fillables[tier].add (cell);
+        }
+
+        filled.add_all(fill_must_fillables[tier]);
+        filled.add_all(elimination_fillables[tier]);
+
+        add_me_queue.add_all(fake_additions);
+        fake_add = false;
+    }
+
+    protected override bool guess_least_open_square () throws SudokuError
+    {
+        guessing = true;
+        return base.guess_least_open_square();
+    }
+
+    public DifficultyRating get_difficulty () {
+        if (!solved)
+            solve();
+        int clues = 0;
+        for (int row = 0; row<board.rows; row++)
+            for (int col = 0; col<board.cols; col++)
+                if (board.is_fixed[row,col])
+                    clues++;
+
+        int numbers_added = (board.rows * board.cols) - clues;
+
+        DifficultyRating rating = new DifficultyRating(fill_must_fillables,
+                                                   elimination_fillables,
+                                                   guesses,
+                                                   backtraces,
+                                                   numbers_added);
+        return rating;
+    }
+
+    public static void gen_python_test () {
+        stdout.printf("import sudoku\n\n");
+
+        for (int repeat = 0; repeat < 20; repeat++)
+        {
+            SudokuGenerator gen = new SudokuGenerator();
+            gen.clues = Random.int_range(17, 60);
+
+            SudokuBoard board = gen.make_symmetric_puzzle (Random.int_range(0, 4));
+
+            stdout.printf("diff = sudoku.SudokuRater(");
+            board.get_string ();
+            stdout.printf(").difficulty()\n");
+
+            SudokuRater rater = new SudokuRater(ref board);
+            DifficultyRating diff = rater.get_difficulty ();
+
+            stdout.printf("print diff.value, %f\n\n", diff.rating);
+        }
+    }
+}
+
+public class Guess {
+    private int _row;
+    public int row
+    {
+        get { return _row; }
+    }
+
+    private int _col;
+    public int col
+    {
+        get { return _col; }
+    }
+
+    private int _val;
+    public int val
+    {
+        get { return _val; }
+    }
+
+    public ArrayList<Guess> children;
+
+    public HashMap<Coord?, int> consequences;
+
+    public Guess(int row, int col, int val) {
+        _row = row;
+        _col = col;
+        _val = val;
+        consequences = new HashMap<Coord?, int> ((GLib.HashFunc) Coord.hash, (GLib.EqualFunc) Coord.equal);
+        children = new ArrayList<Guess> ();
+    }
+
+    public void add_consequence (int row, int col, int val)
+    {
+        consequences.set (Coord(row, col), val);
+    }
+}
+
+public class GuessList : ArrayList<Guess> {
+
+    public Guess[] guesses_for (int row, int col) {
+        Guess[] guesses = {};
+
+        foreach (Guess guess in this) {
+            if (guess.row == row && guess.col == col) {
+                guesses += guess;
+            }
+        }
+
+        return guesses;
+    }
+
+    public Guess[] remove_children (Guess guess) {
+        Guess[] removed = {};
+
+        foreach (Guess g in guess.children) {
+            if (this.contains(g)) {
+                removed += g;
+                this.remove(g);
+            }
+        }
+
+        return removed;
+    }
+
+    public Guess[] remove_guesses_for ( int row, int col) {
+        Guess[] removed = {};
+
+        foreach (Guess guess in this) {
+            if (guess.row == row && guess.col == col) {
+                removed += guess;
+                this.remove(guess);
+            }
+        }
+
+        return removed;
+    }
+}
+
+public class BreadcrumbTrail : GuessList {
+
+    public new void append(Guess guess) {
+        if (guesses_for(guess.row, guess.col).length != 0) {
+            // "We already have crumbs on %s, %s" % (guess.x, guess.y))
+        } else {
+            add(guess);
+        }
+    }
+}
+
+public class ParallelDict {
+    private HashMap<Coord?, HashSet<Coord?>> map = new HashMap<Coord?, HashSet<Coord?>> ();
+
+    public void set (Coord k, HashSet<Coord?> v)
+    {
+        map.set (k, v);
+        foreach (Coord i in v)
+        {
+            if (i == k)
+                continue;
+            if (map.has_key(i)) {
+                map[k].add (i);
+            } else {
+                HashSet<Coord?> kSet = new HashSet<Coord?> ();
+                kSet.add (k);
+                map.set (i, kSet);
+            }
+        }
+    }
+
+    public void unset (Coord k)
+    {
+        HashSet<Coord?> v = map[k];
+        map.unset (k);
+        foreach (Coord i in v)
+        {
+            if (i == k)
+                continue;
+            if (map.has_key(i))
+            {
+                if (k in map[i])
+                    map[i].remove(k);
+                if (map[i].size == 0)
+                    map.unset (i);
+                    // If k was the last value in the list of values
+                    // for i, then we delete i from our dictionary
+            }
+        }
+    }
+
+    public bool contains (Coord key)
+    {
+        return map.has_key (key);
+    }
+}
+
+public enum DifficultyCatagory {
+    EASY,
+    MEDIUM,
+    HARD,
+    VERY_HARD
+}
+
+public class DifficultyRating {
+
+    public const float[] VERY_HARD_RANGE = { 0.75f, 10 };
+    public const float[] HARD_RANGE = { 0.6f, 0.75f };
+    public const float[] MEDIUM_RANGE = { 0.45f, 0.6f };
+    public const float[] EASY_RANGE = { -10, 0.45f };
+
+    HashMap<int, HashSet<Cell?>> fill_must_fillables;
+    HashMap<int, HashSet<Cell?>> elimination_fillables;
+    GuessList guesses;
+    int backtraces;
+    int squares_filled;
+
+    float elimination_ease;
+    float fillable_ease;
+
+    float instant_fill_fillable;
+    float instant_elimination_fillable;
+    float proportion_instant_elimination_fillable;
+    float proportion_instant_fill_fillable;
+
+    public float rating;
+
+    public DifficultyRating (HashMap<int, HashSet<Cell?>> fill_must_fillables, HashMap<int, HashSet<Cell?>> 
elimination_fillables, GuessList guesses, int backtraces, int squares_filled  )
+    {
+        this.fill_must_fillables = fill_must_fillables;
+        this.elimination_fillables = elimination_fillables;
+
+        this.guesses = guesses;
+        this.backtraces = backtraces;
+        this.squares_filled = squares_filled;
+
+        if (fill_must_fillables.size != 0)
+            instant_fill_fillable = (float) fill_must_fillables[0].size;
+        else
+            instant_fill_fillable = 0.0f;
+
+        if (elimination_fillables.size != 0)
+            instant_elimination_fillable = (float) elimination_fillables[0].size;
+        else
+            instant_elimination_fillable = 0.0f;
+
+        proportion_instant_elimination_fillable = instant_elimination_fillable / squares_filled;
+        // some more numbers that may be crazy...
+        proportion_instant_fill_fillable = instant_fill_fillable / squares_filled;
+        elimination_ease = add_with_diminishing_importance(count_values(elimination_fillables));
+        fillable_ease = add_with_diminishing_importance(count_values(fill_must_fillables));
+        rating = calculate();
+    }
+
+    private int[] count_values (HashMap<int, HashSet<Cell?>> map) {
+        TreeMap<int, HashSet<Cell?>> sortedMap = new TreeMap<int, HashSet<Cell?>> ();
+        foreach (int key in map.keys) {
+            sortedMap[key] = map[key];
+        }
+        int[] array = new int[map.size];
+        int p = 0;
+        foreach (int i in sortedMap.keys) {
+            array[p] = sortedMap[i].size;
+            p++;
+        }
+        return array;
+    }
+
+    private float calculate () {
+        return 1 - (((float)fillable_ease) / squares_filled) - (((float)elimination_ease / squares_filled)) 
+ (guesses.size / squares_filled) + (backtraces / squares_filled);
+    }
+
+    delegate int DiminshBy(int a);
+
+    private static int diminsh_by_one (int a) {
+        return a + 1;
+    }
+
+    public bool in_range (float[] range) {
+        return rating >= range[0] && rating < range[1];
+    }
+
+    public DifficultyCatagory get_catagory ()
+    {
+        if (in_range(EASY_RANGE))
+            return DifficultyCatagory.EASY;
+        else if (in_range(MEDIUM_RANGE))
+            return DifficultyCatagory.MEDIUM;
+        else if (in_range(HARD_RANGE))
+            return DifficultyCatagory.HARD;
+        else if (in_range(VERY_HARD_RANGE))
+            return DifficultyCatagory.VERY_HARD;
+        else
+            assert_not_reached();
+    }
+
+    static float add_with_diminishing_importance (int[] array, DiminshBy diminish_by = diminsh_by_one) {
+        float sum = 0;
+        for (int i = 0; i < array.length; i++)
+        {
+            sum += ((float) array[i]) / diminish_by(i);
+        }
+        return sum;
+    }
+
+    public void pretty_print () {
+        stdout.printf ("Number of moves instantly fillable by elimination: %f\n", 
instant_elimination_fillable);
+        stdout.printf ("Percentage of moves instantly fillable by elimination: %f\n", 
proportion_instant_elimination_fillable * 100);
+        stdout.printf ("Number of moves instantly fillable by filling: %f\n", instant_fill_fillable);
+        stdout.printf ("Percentage of moves instantly fillable by filling: %f\n", 
proportion_instant_fill_fillable * 100);
+        stdout.printf ("Number of guesses made: %d\n", guesses.size);
+        stdout.printf ("Number of backtraces: %d\n", backtraces);
+        stdout.printf ("Ease by filling: %f\n", fillable_ease);
+        stdout.printf ("Ease by elimination: %f\n", elimination_ease);
+        stdout.printf ("Calculated difficulty: %f\n", rating);
+    }
+}
diff --git a/src/sudoku-store.vala b/src/sudoku-store.vala
new file mode 100644
index 0000000..8477c35
--- /dev/null
+++ b/src/sudoku-store.vala
@@ -0,0 +1,109 @@
+using Gee;
+
+class SudokuStore
+{
+    private ArrayList<SudokuBoard> easy_boards = new ArrayList<SudokuBoard> ();
+    private ArrayList<SudokuBoard> medium_boards = new ArrayList<SudokuBoard> ();
+    private ArrayList<SudokuBoard> hard_boards = new ArrayList<SudokuBoard> ();
+    private ArrayList<SudokuBoard> very_hard_boards = new ArrayList<SudokuBoard> ();
+
+    public SudokuStore () {
+        try {
+            { // Easy boards
+                var file = File.new_for_uri ("resource:///org/gnome/gnome-sudoku/puzzles/easy");
+
+                var dis = new DataInputStream (file.read ());
+
+                string line;
+                // Read lines until end of file (null) is reached
+                while ((line = dis.read_line (null)) != null) {
+                    SudokuBoard board = new SudokuBoard();
+                    board.set_from_string(line[0:161], " ");
+
+                    easy_boards.add(board);
+                }
+            }
+
+            { // Medium boards
+                var file = File.new_for_uri ("resource:///org/gnome/gnome-sudoku/puzzles/medium");
+
+                var dis = new DataInputStream (file.read ());
+
+                string line;
+                // Read lines until end of file (null) is reached
+                while ((line = dis.read_line (null)) != null) {
+                    SudokuBoard board = new SudokuBoard();
+                    board.set_from_string(line[0:161], " ");
+
+                    medium_boards.add(board);
+                }
+            }
+
+            { // Hard boards
+                var file = File.new_for_uri ("resource:///org/gnome/gnome-sudoku/puzzles/hard");
+
+                var dis = new DataInputStream (file.read ());
+
+                string line;
+                // Read lines until end of file (null) is reached
+                while ((line = dis.read_line (null)) != null) {
+                    SudokuBoard board = new SudokuBoard();
+                    board.set_from_string(line[0:161], " ");
+
+                    hard_boards.add(board);
+                }
+            }
+
+            { // Very hard boards
+                var file = File.new_for_uri ("resource:///org/gnome/gnome-sudoku/puzzles/very_hard");
+
+                var dis = new DataInputStream (file.read ());
+
+                string line;
+                // Read lines until end of file (null) is reached
+                while ((line = dis.read_line (null)) != null) {
+                    SudokuBoard board = new SudokuBoard();
+                    board.set_from_string(line[0:161], " ");
+
+                    very_hard_boards.add(board);
+                }
+            }
+        } catch (Error e) {
+            error ("%s", e.message);
+        }
+    }
+
+    public SudokuBoard get_random_easy_board()
+    {
+        return easy_boards[Random.int_range(0, easy_boards.size)];
+    }
+
+    public SudokuBoard get_random_medium_board()
+    {
+        return medium_boards[Random.int_range(0, medium_boards.size)];
+    }
+
+    public SudokuBoard get_random_hard_board()
+    {
+        return hard_boards[Random.int_range(0, hard_boards.size)];
+    }
+
+    public SudokuBoard get_random_very_hard_board()
+    {
+        return very_hard_boards[Random.int_range(0, very_hard_boards.size)];
+    }
+
+    public SudokuBoard get_random_board(DifficultyCatagory catagory)
+    {
+        if (catagory == DifficultyCatagory.EASY)
+            return get_random_easy_board();
+        else if (catagory == DifficultyCatagory.MEDIUM)
+            return get_random_medium_board();
+        else if (catagory == DifficultyCatagory.HARD)
+            return get_random_hard_board();
+        else if (catagory == DifficultyCatagory.VERY_HARD)
+            return get_random_very_hard_board();
+        else
+            assert_not_reached();
+    }
+}
diff --git a/src/sudoku-view.vala b/src/sudoku-view.vala
new file mode 100644
index 0000000..753672a
--- /dev/null
+++ b/src/sudoku-view.vala
@@ -0,0 +1,819 @@
+using Gtk;
+using Gdk;
+
+private class SudokuCellView : Gtk.DrawingArea
+{
+    private Pango.Layout layout;
+
+    private double size_ratio = 2;
+
+    private Gtk.Window? popup = null;
+
+    private SudokuGame game;
+
+    private int _row;
+    public int row
+    {
+        get { return _row; }
+        set { _row = value; }
+    }
+
+    private int _col;
+    public int col
+    {
+        get { return _col; }
+        set { _col = value; }
+    }
+
+    public int value
+    {
+        get
+        {
+            return game.board [_row, _col];
+        }
+        set
+        {
+            if (is_fixed)
+            {
+                string text = "%d".printf (game.board [_row, _col]);
+                layout = create_pango_layout (text);
+                layout.set_font_description (style.font_desc);
+                return;
+            }
+            if (value == 0)
+            {
+                string text = "";
+                layout = create_pango_layout (text);
+                layout.set_font_description (style.font_desc);
+                if (game.board [_row, _col] != 0)
+                    game.remove (_row, _col);
+                return;
+            }
+            if (value == game.board [_row, _col])
+            {
+                string text = "%d".printf (value);
+                layout = create_pango_layout (text);
+                layout.set_font_description (style.font_desc);
+                return;
+            }
+            game.insert (_row, _col, value);
+        }
+    }
+
+    public bool is_fixed
+    {
+        get
+        {
+            return game.board.is_fixed[_row, _col];
+        }
+    }
+
+    public string top_notes { set; get; default = ""; }
+    public string bottom_notes { set; get; default = ""; }
+
+    private bool _show_possibilities;
+    public bool show_possibilities
+    {
+        get { return _show_possibilities; }
+        set {
+            _show_possibilities = value;
+            queue_draw ();
+        }
+    }
+
+    private bool _warn_about_unfillable_squares = false;
+    public bool warn_about_unfillable_squares
+    {
+        get { return _warn_about_unfillable_squares; }
+        set {
+            _warn_about_unfillable_squares = value;
+            queue_draw ();
+        }
+    }
+
+    private bool _selected;
+    public bool selected
+    {
+        get { return _selected; }
+        set { _selected = value; }
+    }
+
+    private bool _highlight;
+    public bool highlight
+    {
+        get { return _highlight; }
+        set { _highlight = value; }
+    }
+
+    private bool _invalid;
+    public bool invalid
+    {
+        get { return _invalid; }
+        set { _invalid = value; }
+    }
+
+    private RGBA _background_color;
+    public RGBA background_color
+    {
+        get { return _background_color; }
+        set
+        {
+            _background_color = value;
+        }
+    }
+
+    public SudokuCellView (int row, int col, ref SudokuGame game, bool small = false)
+    {
+        this.game = game;
+        this._row = row;
+        this._col = col;
+
+        if (is_fixed)
+        {
+            _background_color = { 0.8, 0.8, 0.8, 0.0 };
+        }
+        else
+        {
+            _background_color = { 1.0, 1.0, 1.0, 0.0 };
+        }
+
+        set_can_focus(true);
+        can_focus = true;
+
+        style.font_desc.set_size (Pango.SCALE * 13);
+        this.value = game.board [_row, _col];
+
+        if (small)
+        {
+            size_ratio = 1;
+        }
+
+        events = EventMask.EXPOSURE_MASK | EventMask.BUTTON_PRESS_MASK | EventMask.KEY_PRESS_MASK;
+        focus_out_event.connect (focus_out_cb);
+        game.cell_changed.connect (cell_changed_cb);
+    }
+
+    public override void get_preferred_width (out int minimal_width, out int natural_width)
+    {
+        int width, height, side;
+        layout.get_size (out width, out height);
+        side = width > height ? width : height;
+        minimal_width = natural_width = (int) (size_ratio * side) / Pango.SCALE;
+    }
+
+    public override void get_preferred_height (out int minimal_height, out int natural_height)
+    {
+        int width, height, side;
+        layout.get_size (out width, out height);
+        side = width > height ? width : height;
+        minimal_height = natural_height = (int) (size_ratio * side) / Pango.SCALE;
+    }
+
+    public override bool button_press_event (Gdk.EventButton event)
+    {
+        if (event.button != 1)
+            return false;
+
+        if (!is_focus)
+        {
+            grab_focus ();
+            return false;
+        }
+
+        if (event.y / get_allocated_height () < 0.25)
+        {
+            show_note_editor (0);
+        }
+        else
+        {
+            if (event.y / get_allocated_height () > 0.75)
+            {
+                if (!_show_possibilities)
+                {
+                    show_note_editor (1);
+                }
+            }
+            else
+            {
+                show_number_picker ();
+            }
+        }
+
+        return false;
+    }
+
+    private bool number_pick { get; set; default = false; }
+
+    private void show_number_picker ()
+    {
+        if (popup != null)
+            return;
+        if (is_fixed)
+            return;
+        number_pick = true;
+        popup = new Gtk.Window (Gtk.WindowType.POPUP);
+        popup.decorated = false;
+        popup.resizable = false;
+        popup.skip_taskbar_hint = true;
+        popup.skip_pager_hint = true;
+        popup.set_type_hint (Gdk.WindowTypeHint.DIALOG);
+
+        var number_picker = new NumberPicker(ref game.board, value != 0);
+        number_picker.number_picked.connect ((o, number) => {
+            value = number;
+            if (number == 0)
+            {
+                notify_property("value");
+            }
+            hide_popup ();
+        });
+        popup.add (number_picker);
+
+        int x, y, width, height;
+        get_window ().get_origin (out x, out y);
+        popup.show ();
+        popup.get_size (out width, out height);
+        popup.focus_out_event.connect (() => { hide_popup (); return true; });
+        var screen = popup.get_screen ();
+        var popup_x = x - (width - get_allocated_width ()) / 2;
+        var popup_y = y - (height - get_allocated_height ()) / 2;
+        if (popup_x < 0)
+            popup_x = 0;
+        else if (popup_x + width > screen.get_width ())
+            popup_x = screen.get_width () - width;
+        if (popup_y < 0)
+            popup_y = 0;
+        else if (popup_y + height > screen.get_height ())
+            popup_y = screen.get_height () - height;
+        popup.move (popup_x, popup_y);
+    }
+
+    private void hide_popup ()
+    {
+        number_pick = false;
+        if (popup == null)
+            return;
+        popup.destroy ();
+        popup = null;
+    }
+
+    private bool editing_notes { get; set; default = false; }
+
+    private void show_note_editor (int top)
+    {
+        if (popup != null)
+            return;
+        if (is_fixed)
+            return;
+
+        editing_notes = true;
+        // TODO: This probably should be changed to Gtk.WindowType.POPUP
+        popup = new Gtk.Window (Gtk.WindowType.TOPLEVEL);
+        popup.decorated = false;
+        popup.resizable = false;
+        popup.skip_taskbar_hint = true;
+        popup.skip_pager_hint = true;
+        popup.set_type_hint (Gdk.WindowTypeHint.DIALOG);
+        popup.set_size_request (get_allocated_width (), -1);
+
+        var entry = new Gtk.Entry ();
+        entry.has_frame = false;
+        if (top == 0)
+            entry.set_text (top_notes);
+        else
+            entry.set_text (bottom_notes);
+        popup.add (entry);
+
+        entry.focus_out_event.connect (() => {
+            hide_note_editor (entry, top);
+            return true;
+        });
+
+        entry.activate.connect (() => { hide_note_editor (entry, top); });
+
+        int x, y, height;
+
+        get_window ().get_origin (out x, out y);
+        popup.show_all ();
+        popup.get_size (null, out height);
+        popup.move (x, y + top * (get_allocated_height () - height));
+    }
+
+    private void hide_note_editor (Gtk.Entry entry, int top)
+    {
+        editing_notes = false;
+        if (top == 0)
+            top_notes = entry.get_text ();
+        else
+            bottom_notes = entry.get_text ();
+        hide_popup ();
+    }
+
+    private bool focus_out_cb (Gtk.Widget widget, Gdk.EventFocus event)
+    {
+        if (editing_notes || number_pick) return false;
+        if (popup != null)
+            hide_popup ();
+        return false;
+    }
+
+    /* Key mapping function to help convert Gdk.keyval_name string to numbers */
+    private int key_map_keypad (string key_name)
+    {
+        /* Compared with "0" to make sure, actual "0" is not misinterpreted as parse error in int.parse() */
+        if (key_name == "KP_0" || key_name == "0")
+            return 0;
+        if (key_name == "KP_1")
+            return 1;
+        if (key_name == "KP_2")
+            return 2;
+        if (key_name == "KP_3")
+            return 3;
+        if (key_name == "KP_4")
+            return 4;
+        if (key_name == "KP_5")
+            return 5;
+        if (key_name == "KP_6")
+            return 6;
+        if (key_name == "KP_7")
+            return 7;
+        if (key_name == "KP_8")
+            return 8;
+        if (key_name == "KP_9")
+            return 9;
+        return -1;
+    }
+
+    public override bool key_press_event (Gdk.EventKey event)
+    {
+        string k_name = Gdk.keyval_name (event.keyval);
+        int k_no = int.parse (k_name);
+        /* If k_no is 0, there might be some error in parsing, crosscheck with keypad values. */
+        if (k_no == 0)
+            k_no = key_map_keypad (k_name);
+        if (k_no >= 1 && k_no <= 9)
+        {
+            value = k_no;
+            return true;
+        }
+        if (k_no == 0 || k_name == "BackSpace" || k_name == "Delete")
+        {
+            value = 0;
+            notify_property("value");
+            return true;
+        }
+
+        if (k_name == "space" || k_name == "Return" || k_name == "KP_Enter")
+        {
+            show_number_picker ();
+            return true;
+        }
+
+        return false;
+    }
+
+    public override bool draw (Cairo.Context c)
+    {
+        StyleContext styleContext = get_style_context ();
+
+        // Draw the background
+        if (_selected)
+        {
+            RGBA color = styleContext.get_background_color (StateFlags.SELECTED);
+            c.set_source_rgb (color.red, color.green, color.blue);
+        }
+        else
+        {
+            c.set_source_rgb (_background_color.red, _background_color.green, _background_color.blue);
+        }
+
+        c.rectangle (0, 0, get_allocated_width (), get_allocated_height ());
+        c.fill ();
+
+        int glyph_width, glyph_height;
+        layout.get_pixel_size (out glyph_width, out glyph_height);
+        if (game.board.broken_coords.contains(Coord(row, col)))
+        {
+            c.set_source_rgb (1.0, 0.0, 0.0);
+        }
+        else if (_selected)
+        {
+            RGBA color = styleContext.get_color (StateFlags.SELECTED);
+            c.set_source_rgb (color.red, color.green, color.blue);
+        }
+        else
+        {
+            c.set_source_rgb (0.0, 0.0, 0.0);
+        }
+
+        if (value != 0)
+        {
+            int width, height;
+            layout.get_size (out width, out height);
+            height /= Pango.SCALE;
+
+            double scale = ((double) get_allocated_height () / size_ratio) / height;
+            c.move_to ((get_allocated_width () - glyph_width * scale) / 2, (get_allocated_height () - 
glyph_height * scale) / 2);
+            c.save ();
+            c.scale (scale, scale);
+            Pango.cairo_update_layout (c, layout);
+            Pango.cairo_show_layout (c, layout);
+            c.restore ();
+        }
+        else if (_show_possibilities)
+        {
+            double possibility_size = get_allocated_height () / (size_ratio * 2);
+            c.set_font_size (possibility_size);
+            c.set_source_rgb (0.0, 0.0, 0.0);
+
+            bool[] possibilities = game.board.get_possibilities_as_bool_array(row, col);
+
+            int height = get_allocated_height () / game.board.block_cols;
+            int width = get_allocated_height () / game.board.block_rows;
+
+            int num = 0;
+            for (int row = 0; row < game.board.block_rows; row++)
+            {
+                for (int col = 0; col < game.board.block_cols; col++)
+                {
+                    num++;
+
+                    if (possibilities[num - 1])
+                    {
+                        c.move_to (col * width, (row * height) + possibility_size);
+                        c.show_text ("%d".printf(num));
+                    }
+                }
+            }
+        }
+
+        if (is_fixed)
+            return false;
+
+        // Draw the notes
+        double note_size = get_allocated_height () / (size_ratio * 2);
+        c.set_font_size (note_size);
+
+        c.move_to (0, note_size);
+
+        c.set_source_rgb (0.0, 0.0, 0.0);
+        c.show_text (top_notes);
+
+        c.move_to (0, (get_allocated_height () - 3));
+        if (_warn_about_unfillable_squares)
+        {
+            c.set_source_rgb (1.0, 0.0, 0.0);
+            c.show_text ("None");
+        }
+        else
+        {
+            c.set_source_rgb (0.0, 0.0, 0.0);
+            //if (_show_possibilities)
+            //{
+            //    c.show_text (get_possibility_string (game.board.get_possibilities(_row, _col)));
+            //}
+            //else
+            //{
+                c.show_text (bottom_notes);
+            //}
+        }
+
+        return false;
+    }
+
+    private static string get_possibility_string (int[] possibilities) {
+        var builder = new StringBuilder ();
+        foreach (int a in possibilities) {
+            builder.append (@"$a ");
+        }
+        builder.truncate ((possibilities.length * 2) - 1);
+        return builder.str;
+    }
+
+    public void cell_changed_cb (int row, int col, int old_val, int new_val)
+    {
+        if (row == this.row && col == this.col)
+        {
+            this.value = new_val;
+            if (new_val == 0)
+            {
+                notify_property("value");
+            }
+        }
+    }
+
+    public void hint ()
+    {
+        show_hint ();
+        Timeout.add (200, remove_hint);
+        Timeout.add (400, show_hint);
+        Timeout.add (600, remove_hint);
+        Timeout.add (800, show_hint);
+        Timeout.add (1000, remove_hint);
+    }
+
+    private bool show_hint ()
+    {
+        background_color = { 1.0, 0.0, 0.0, 0.0 };
+        queue_draw ();
+        return false;
+    }
+
+    private bool remove_hint ()
+    {
+        background_color = { 1.0, 1.0, 1.0, 0.0 };
+        queue_draw ();
+        return false;
+    }
+}
+
+public class SudokuView : Gtk.AspectFrame
+{
+    private SudokuGame game;
+    private SudokuCellView[,] cells;
+
+    public signal void cell_focus_in_event (int row, int col);
+    public signal void cell_focus_out_event (int row, int col);
+    public signal void cell_value_changed_event (int row, int col);
+
+    private bool previous_board_broken_state = false;
+
+    private Gtk.EventBox box;
+    private Gtk.Table table;
+
+    private int dance_step;
+
+    private const RGBA[] dance_colors = { {0.8,                0.0,                0.0,                0.0},
+                                        {0.9372549019607843, 0.1607843137254902, 0.1607843137254902, 0.0},
+                                        {0.9607843137254902, 0.4745098039215686, 0.0,                0.0},
+                                        {0.9882352941176471, 0.6862745098039216, 0.2431372549019607, 0.0},
+                                        {0.9882352941176471, 0.9137254901960784, 0.3098039215686274, 0.0},
+                                        {0.5411764705882353, 0.8862745098039215, 0.2039215686274509, 0.0},
+                                        {0.4509803921568627, 0.8235294117647058, 0.0862745098039215, 0.0},
+                                        {0.4470588235294118, 0.6235294117647059, 0.8117647058823529, 0.0},
+                                        {0.2039215686274509, 0.3960784313725497, 0.6431372549019608, 0.0},
+                                        {0.6784313725490196, 0.4980392156862745, 0.6588235294117647, 0.0},
+                                        {0.4588235294117647, 0.3137254901960784, 0.4823529411764706, 0.0}
+                                      };
+
+    private int _selected_x = 0;
+    public int selected_x
+    {
+        get { return _selected_x; }
+        set {
+            cells[selected_y, selected_x].selected = false;
+            cells[selected_y, selected_x].queue_draw ();
+             _selected_x = value;
+            cells[selected_y, selected_x].selected = true;
+        }
+    }
+
+    private int _selected_y = 0;
+    public int selected_y
+    {
+        get { return _selected_y; }
+        set {
+            cells[selected_y, selected_x].selected = false;
+            cells[selected_y, selected_x].queue_draw ();
+             _selected_y = value;
+            cells[selected_y, selected_x].selected = true;
+        }
+    }
+
+    public SudokuView (SudokuGame game, bool preview = false)
+    {
+        shadow_type = Gtk.ShadowType.NONE;
+
+        /* Use an EventBox to be able to set background */
+        box = new Gtk.EventBox ();
+        box.modify_bg (Gtk.StateType.NORMAL, box.style.black);
+        add (box);
+        box.show ();
+
+        this.obey_child = false;
+
+        set_game (game, preview);
+    }
+
+    public void set_game (SudokuGame game, bool preview = false)
+    {
+        if (table != null)
+        {
+            box.remove (table);
+        }
+
+        this.game = game;
+
+        table = new Gtk.Table (game.board.rows, game.board.cols, false);
+        table.row_spacing = 1;
+        table.column_spacing = 1;
+        for (var row = game.board.block_rows; row < game.board.rows; row += game.board.block_rows)
+        {
+            table.set_row_spacing (row - 1, 2);
+        }
+        for (var col = game.board.block_cols; col < game.board.cols; col += game.board.block_cols)
+        {
+            table.set_col_spacing (col - 1, 2);
+        }
+        table.border_width = 2;
+
+        cells = new SudokuCellView[game.board.rows, game.board.cols];
+        for (var row = 0; row < game.board.rows; row++)
+        {
+            for (var col = 0; col < game.board.cols; col++)
+            {
+                var cell = new SudokuCellView (row, col, ref this.game, preview);
+                var cell_row = row;
+                var cell_col = col;
+
+                if (!preview)
+                {
+                    cell.focus_out_event.connect (() => {
+                        cell_focus_out_event(cell_row, cell_col);
+                        return false;
+                    });
+
+                    cell.focus_in_event.connect (() => {
+                        this.selected_x = cell_col;
+                        this.selected_y = cell_row;
+                        cell_focus_in_event(cell_row, cell_col);
+                        return false;
+                    });
+
+                    cell.notify["value"].connect((s, p)=> {
+                        /* The board needs redrawing if it was/is broken, or if the possibilities are being 
displayed */
+                        if (_show_possibilities || _show_warnings || game.board.broken || 
previous_board_broken_state) {
+                            for (var i = 0; i < game.board.rows; i++)
+                            {
+                                for (var j = 0; j < game.board.cols; j++)
+                                {
+                                    if (_show_warnings && cells[i,j].value == 0 && 
game.board.count_possibilities (cells[i,j].row, cells[i,j].col) == 0) {
+                                        if (!cells[i,j].warn_about_unfillable_squares) {
+                                            cells[i,j].warn_about_unfillable_squares = true;
+                                            cells[i,j].hint ();
+                                        }
+                                    }
+                                    else
+                                    {
+                                        cells[i,j].warn_about_unfillable_squares = false;
+                                    }
+                                }
+                            }
+                            previous_board_broken_state = game.board.broken;
+                        }
+                        cell_value_changed_event(cell_row, cell_col);
+                    });
+                }
+
+                cells[row, col] = cell;
+                cell.show ();
+                table.attach_defaults (cell, col, col+1, row, row+1);
+
+            }
+        }
+        box.add (table);
+        table.show ();
+    }
+
+    private bool _show_highlights = false;
+    public bool show_highlights
+    {
+        get { return _show_highlights; }
+        set { _show_highlights = value; }
+    }
+
+    private bool _show_hints = false;
+    public bool show_hints
+    {
+        get { return _show_hints; }
+        set { _show_hints = value; }
+    }
+
+    private bool _show_warnings = false;
+    public bool show_warnings
+    {
+        get { return _show_warnings; }
+        set {
+            _show_warnings = value;
+            for (var i = 0; i < game.board.rows; i++)
+            {
+                for (var j = 0; j < game.board.cols; j++)
+                {
+                    if (_show_warnings && cells[i,j].value == 0 && game.board.count_possibilities 
(cells[i,j].row, cells[i,j].col) == 0)
+                    {
+                        cells[i,j].warn_about_unfillable_squares = true;
+                        cells[i,j].hint ();
+                    }
+                    else
+                    {
+                        cells[i,j].warn_about_unfillable_squares = false;
+                    }
+                }
+            }
+         }
+    }
+
+    private bool _show_possibilities = false;
+    public bool show_possibilities
+    {
+        get { return _show_possibilities; }
+        set {
+            _show_possibilities = value;
+            for (var i = 0; i < game.board.rows; i++)
+                for (var j = 0; j < game.board.cols; j++)
+                    cells[i,j].show_possibilities = value;
+        }
+    }
+
+    public void set_cell_value (int x, int y, int value) {
+        cells[y, x].value = value;
+        if (value == 0)
+        {
+            cells[y, x].notify_property("value");
+        }
+    }
+
+    public void hint ()
+    {
+        int row=0, col=0;
+        game.hint (ref row, ref col);
+        cells [row, col].hint ();
+    }
+
+    public bool dance () {
+        if (dance_step < 90)
+        {
+            for (var j = 0; j < game.board.cols; j++)
+            {
+                RGBA color = dance_colors[dance_step % dance_colors.length];
+                dance_step++;
+                for (var i = 0; i < game.board.rows; i++)
+                    cells[i,j].background_color = color;
+            }
+        }
+        else
+        {
+            for (var i = 0; i < game.board.rows; i++)
+            {
+                RGBA color = dance_colors[dance_step % dance_colors.length];
+                dance_step++;
+                for (var j = 0; j < game.board.cols; j++)
+                    cells[i,j].background_color = color;
+            }
+        }
+
+        if (dance_step > 180)
+            dance_step = 0;
+
+        if (dance_step >= 0)
+            Timeout.add (200, dance);
+        queue_draw ();
+
+        return false;
+    }
+
+    public void stop_dance ()
+    {
+        dance_step = -1;
+        for (var j = 0; j < game.board.cols; j++)
+        {
+            for (var i = 0; i < game.board.rows; i++)
+            {
+                if (cells[i,j].is_fixed)
+                {
+                    cells[i,j].background_color = { 0.8, 0.8, 0.8, 0.0 };
+                }
+                else
+                {
+                    cells[i,j].background_color = { 1.0, 1.0, 1.0, 0.0 };
+                }
+            }
+        }
+    }
+
+    private RGBA get_next_color (RGBA color)
+    {
+        return dance_colors[Random.int_range(0, dance_colors.length)];
+    }
+
+    public void clear_top_notes ()
+    {
+        for (var i = 0; i < game.board.rows; i++)
+            for (var j = 0; j < game.board.cols; j++)
+                cells[i,j].top_notes = "";
+        queue_draw ();
+    }
+
+    public void clear_bottom_notes ()
+    {
+        for (var i = 0; i < game.board.rows; i++)
+            for (var j = 0; j < game.board.cols; j++)
+                cells[i,j].bottom_notes = "";
+        queue_draw ();
+    }
+
+    public void cell_grab_focus(int row, int col)
+    {
+        cells[row, col].grab_focus ();
+    }
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]