[gnome-sudoku] Warn when the solution, if any, is violated
- From: Michael Catanzaro <mcatanzaro src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-sudoku] Warn when the solution, if any, is violated
- Date: Mon, 25 Jul 2022 13:05:21 +0000 (UTC)
commit 47bc730d65276015d7161b7c46c34a6849a3ad3a
Author: Steven Elliott <selliott512 gmail com>
Date: Sun Jul 17 15:19:51 2022 -0400
Warn when the solution, if any, is violated
lib/qqwing-wrapper.cpp | 38 +++++++++++++++++++++-
lib/qqwing-wrapper.h | 1 +
lib/qqwing.vapi | 1 +
lib/sudoku-board.vala | 51 +++++++++++++++++++++++++++++
src/gnome-sudoku.vala | 3 ++
src/sudoku-view.vala | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 180 insertions(+), 1 deletion(-)
---
diff --git a/lib/qqwing-wrapper.cpp b/lib/qqwing-wrapper.cpp
index 3c6bc36..f056a55 100644
--- a/lib/qqwing-wrapper.cpp
+++ b/lib/qqwing-wrapper.cpp
@@ -29,6 +29,10 @@
#include <glib.h>
#include <qqwing.hpp>
+// Constants
+
+const int BOARD_SIZE = 81;
+
/*
* Generate a symmetric puzzle of specified difficulty.
* The result must be freed with g_free().
@@ -37,7 +41,6 @@ int* qqwing_generate_puzzle(int difficulty)
{
int i = 0;
const int MAX_ITERATIONS = 1000;
- const int BOARD_SIZE = 81;
qqwing::SudokuBoard board;
static std::once_flag flag;
@@ -67,6 +70,39 @@ int* qqwing_generate_puzzle(int difficulty)
return copy;
}
+/*
+ * Solve a given puzzle in place. If true is returned the puzzle will be solved.
+ * If false is returned the puzzle will be unchanged.
+ */
+gboolean qqwing_solve_puzzle(int* puzzle)
+{
+ qqwing::SudokuBoard board;
+
+ if (!board.setPuzzle(puzzle))
+ {
+ // This can happen when there is no solution.
+ g_warning("Failed to solve puzzle: the puzzle could not be set.");
+ return FALSE;
+ }
+ if (!board.hasUniqueSolution())
+ {
+ // The multiple solution case.
+ g_warning("Failed to solve puzzle: the puzzle does not have a unique solution.");
+ return FALSE;
+ }
+ if (!board.solve())
+ {
+ // This should not happen given the above checks.
+ g_warning("Failed to solve puzzle: the call to solve() failed.");
+ return FALSE;
+ }
+
+ // Valid. Copy and return true.
+ const int* solution = board.getSolution();
+ std::copy(solution, &solution[BOARD_SIZE], puzzle);
+ return TRUE;
+}
+
/*
* Count the number of solutions of a puzzle
* but return 2 if there are multiple.
diff --git a/lib/qqwing-wrapper.h b/lib/qqwing-wrapper.h
index a546e8d..384e8cb 100644
--- a/lib/qqwing-wrapper.h
+++ b/lib/qqwing-wrapper.h
@@ -27,6 +27,7 @@
G_BEGIN_DECLS
int *qqwing_generate_puzzle(int difficulty);
+gboolean qqwing_solve_puzzle(int* puzzle);
int qqwing_count_solutions_limited(int *puzzle);
void qqwing_print_stats(int *puzzle);
char *qqwing_get_version(void);
diff --git a/lib/qqwing.vapi b/lib/qqwing.vapi
index f29205b..05ff5bc 100644
--- a/lib/qqwing.vapi
+++ b/lib/qqwing.vapi
@@ -23,6 +23,7 @@
namespace QQwing {
[CCode (array_length=false)]
int[] generate_puzzle (int difficulty);
+ bool solve_puzzle([CCode (array_length = false)] int[] puzzle);
int count_solutions_limited ([CCode (array_length = false)] int[] puzzle);
void print_stats ([CCode (array_length = false)] int[] puzzle);
string get_version ();
diff --git a/lib/sudoku-board.vala b/lib/sudoku-board.vala
index 3b61700..661bfa3 100644
--- a/lib/sudoku-board.vala
+++ b/lib/sudoku-board.vala
@@ -26,6 +26,7 @@ public class SudokuBoard : Object
/* 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 */
+ protected int[,] solution; /* stores the solution, if any, null otherwise */
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 */
@@ -172,6 +173,7 @@ public class SudokuBoard : Object
{
SudokuBoard board = new SudokuBoard (block_rows , block_cols);
board.cells = cells;
+ board.solution = solution;
board.is_fixed = is_fixed;
board.possible_in_row = possible_in_row;
board.possible_in_col = possible_in_col;
@@ -364,6 +366,31 @@ public class SudokuBoard : Object
fixed--;
}
+ public void set_solution (int row, int col, int val)
+ {
+ solution[row, col] = val;
+ }
+
+ public int get_solution (int row, int col)
+ {
+ return solution[row, col];
+ }
+
+ public void solve ()
+ {
+ int[] solution_1d = convert_2d_to_1d(cells);
+
+ if (QQwing.solve_puzzle (solution_1d))
+ solution = convert_1d_to_2d(solution_1d);
+ else
+ solution = null;
+ }
+
+ public bool solved ()
+ {
+ return solution != null;
+ }
+
public int count_solutions_limited ()
{
return QQwing.count_solutions_limited ((int[]) cells);
@@ -521,6 +548,30 @@ public class SudokuBoard : Object
return s;
}
+
+ // Convert a 2D array to a 1D array. The 2D array is assumed to have
+ // dimensions rows, cols.
+ private int[] convert_2d_to_1d(int[,] ints_2d)
+ {
+ int[] ints_1d = new int[rows * cols];
+ int i = 0;
+ for (int row = 0; row < rows; row++)
+ for (int col = 0; col < cols; col++)
+ ints_1d[i++] = ints_2d[row, col];
+ return ints_1d;
+ }
+
+ // Convert a 1D array to a 2D array. The 1D array is assumed to have
+ // length rows * cols.
+ private int[,] convert_1d_to_2d(int[] ints_1d)
+ {
+ int[,] ints_2d = new int[rows, cols];
+ int i = 0;
+ for (int row = 0; row < rows; row++)
+ for (int col = 0; col < cols; col++)
+ ints_2d[row, col] = ints_1d[i++];
+ return ints_2d;
+ }
}
public enum House
diff --git a/src/gnome-sudoku.vala b/src/gnome-sudoku.vala
index ec9f8e5..84c29b7 100644
--- a/src/gnome-sudoku.vala
+++ b/src/gnome-sudoku.vala
@@ -310,6 +310,9 @@ public class Sudoku : Gtk.Application
private void start_game (SudokuBoard board)
{
+ if (current_game_mode == GameMode.PLAY)
+ board.solve();
+
if (game != null)
{
game.paused_changed.disconnect (paused_changed_cb);
diff --git a/src/sudoku-view.vala b/src/sudoku-view.vala
index 1c0da5e..656e81b 100644
--- a/src/sudoku-view.vala
+++ b/src/sudoku-view.vala
@@ -36,6 +36,10 @@ private class SudokuCellView : DrawingArea
private bool initialized_earmarks;
+ // Whether the control keys are pressed.
+ private bool left_control;
+ private bool right_control;
+
public int value
{
get { return game.board [row, col]; }
@@ -236,6 +240,10 @@ private class SudokuCellView : DrawingArea
{
popover.destroy ();
popover = null;
+
+ // Destroying a popover means that this type of warning is now possible.
+ if (warn_incorrect_solution())
+ queue_draw ();
}
}
@@ -284,10 +292,16 @@ private class SudokuCellView : DrawingArea
{
key_controller = new EventControllerKey (this);
key_controller.key_pressed.connect (on_key_pressed);
+ key_controller.key_released.connect (on_key_release);
}
private inline bool on_key_pressed (EventControllerKey _key_controller, uint keyval, uint keycode,
ModifierType state)
{
+ if (keyval == Gdk.Key.Control_L)
+ left_control = true;
+ if (keyval == Gdk.Key.Control_R)
+ right_control = true;
+
if (game.mode == GameMode.PLAY && (is_fixed || game.paused))
return false;
string k_name = keyval_name (keyval);
@@ -347,6 +361,25 @@ private class SudokuCellView : DrawingArea
return false;
}
+ private inline void on_key_release (EventControllerKey _key_controller, uint keyval, uint keycode,
ModifierType state)
+ {
+ bool control_released = false;
+ if (keyval == Gdk.Key.Control_L)
+ {
+ left_control = false;
+ control_released = true;
+ }
+ if (keyval == Gdk.Key.Control_R)
+ {
+ right_control = false;
+ control_released = true;
+ }
+
+ // Releasing a control means that this type of warning is now possible.
+ if (control_released && warn_incorrect_solution())
+ queue_draw ();
+ }
+
public override bool draw (Cairo.Context c)
{
RGBA background_color;
@@ -358,6 +391,45 @@ private class SudokuCellView : DrawingArea
background_color = highlight_color;
else
background_color = free_cell_color;
+
+ // Highlight the cell if the value or earmarks are inconsistent with
+ // a known solution, if any.
+ if (warn_incorrect_solution())
+ {
+ bool cell_error = false;
+ int solution = game.board.get_solution (row, col);
+ if (value != 0)
+ {
+ // Check value against the solution.
+ cell_error = value != solution;
+ }
+ else
+ {
+ // Check earmarks against the solution.
+ var marks = game.board.get_earmarks (row, col);
+ bool earmarked = false;
+ bool solution_found = false;
+ for (int num = 1; num <= marks.length; num++)
+ {
+ if (marks[num - 1])
+ {
+ earmarked = true;
+ if (num == solution)
+ solution_found = true;
+ }
+ }
+ if (earmarked && !solution_found)
+ cell_error = true;
+ }
+
+ // Make the error cell more red by reducing the other colors to 60%.
+ if (cell_error)
+ {
+ background_color.green *= 0.6;
+ background_color.blue *= 0.6;
+ }
+ }
+
c.set_source_rgba (background_color.red, background_color.green, background_color.blue,
background_color.alpha);
c.rectangle (0, 0, get_allocated_width (), get_allocated_height ());
c.fill();
@@ -489,6 +561,21 @@ private class SudokuCellView : DrawingArea
{
game.board.disable_all_earmarks (row, col);
}
+
+ // Return true if the user is to be warned when the value or earmarks are
+ // inconsistent with the known solution, and it is ok for the user to be
+ // warned.
+ private bool warn_incorrect_solution()
+ {
+ // In the following popovers are checked so that the solution of the cell
+ // is not revealed to the user as the user enters candidate numbers for
+ // the cell using the earmark picker. Similarly don't reveal the solution
+ // while earmarks are being entered with the control key.
+ return _show_warnings && // show warnings?
+ (popover == null) && (earmark_popover == null) && // popovers gone?
+ (!left_control) && (!right_control) && // control keys not pressed?
+ game.board.solved(); // solution exists?
+ }
}
public const RGBA fixed_cell_color = {0.8, 0.8, 0.8, 1.0};
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]