Check Sudoku - Unity Tutorial - 6. Suggestions
In the previous step we implemented game rules. In this one we will add suggestions helping solve the Sudoku:
Implementation
Let's start from the suggestions in the work cell. We want it to suggestion options visually, i.e. the only possible value in the column will be marked blue (
#1E88E5), in the row will be marked red (#D81B60), in the 3x3 square will be marked yellow (#FFC107).We download a free crosshair icon from pixabay.com, which, using GIMP, we turn into three other colors:
In the possibility (
PossibilityRect) we add three images (OnlyValueColImage, OnlyValueRowImage, OnlyValueGroupImage) rotated so that a few of them can be displayed at the same time (if e.g. given value is the only one both in the column, and in the row):public class PossibilityRect : MonoBehaviour
{
public GameObject onlyValueColImage;
public GameObject onlyValueRowImage;
public GameObject onlyValueGroupImage;
...
public void ClearOnlyValue()
{
onlyValueColImage.SetActive(false);
onlyValueRowImage.SetActive(false);
onlyValueGroupImage.SetActive(false);
}
public void DisplayOnlyValueForType(CellSetType cellSetType)
{
switch (cellSetType)
{
case CellSetType.COLUMN:
onlyValueColImage.SetActive(true);
break;
case CellSetType.ROW:
onlyValueRowImage.SetActive(true);
break;
case CellSetType.GROUP:
onlyValueGroupImage.SetActive(true);
break;
}
}
}Question 1:
Which element should turn on the suggestions in a work cell?
Possibilities:
- The work cell (
WorkCellImage) - The selected cell (
CellImage)
Decision:
In order to figure out that a value cannot occure anywhere else in the column, row, or 3x3 square, we need access to the model. The work cell (
WorkCellImage) doesn't have it, so we add setting the suggestions to the selected cell (CellImage):public class CellImage : MonoBehaviour {
...
public void ClearPossibilityOnlyValue(PossibilityRect possibility)
{
possibility.ClearOnlyValue();
}
public void DisplayPossibilityOnlyValue(PossibilityRect possibility)
{
foreach (CellSetType cellSetType in model.GetCellSetTypesWithOnlyValue(possibility.Value))
{
possibility.DisplayOnlyValueForType(cellSetType);
}
}
}In order for this to work, the model needs a
GetCellSetTypesWithOnlyValue method, which returns the cell sets, where given value is the only possible. We implement metods CanHaveValue and GetCellSetTypesWithOnlyValue in the cell model. Additionally, we implement the MustHaveValue method:public class CellModel
{
...
public void SetValue(int value)
{
Debug.AssertFormat(
CanHaveValue(value),
"Invalid value being set: {0}", value);
...
}
public bool CanHaveValue(int value)
{
if (value == 0)
{
return true;
}
if (Value == 0)
{
return GetCellSetTypesHavingValue(value).Count == 0;
}
else
{
return value == Value;
}
}
public List<CellSetType> GetCellSetTypesWithOnlyValue(int value)
{
List<CellSetType> result = new List<CellSetType>();
foreach (CellSetModel cellSet in cellSets)
{
if (!cellSet.CanAnyOtherCellHaveValue(this, value))
{
result.Add(cellSet.Type);
}
}
return result;
}
public bool MustHaveValue(int value)
{
return value != 0 && GetCellSetTypesWithOnlyValue(value).Count != 0;
}
}In the cell set model, we need to implement the
CanAnyOtherCellHaveValue method:public class CellSetModel
{
...
public bool CanAnyOtherCellHaveValue(CellModel ignoredCell, int value)
{
foreach (CellModel cell in cells)
{
if (!cell.Equals(ignoredCell) && cell.CanHaveValue(value))
{
return true;
}
}
return false;
}
}Now, the work cell needs to activate suggestions whenever a possibility, that cannot occur anywhere else, is displayed. We do that in two places:
- methods
DisablePossibility and EnablePossibility of the selected cell (CellImage):public class class CellImage : MonoBehaviour {
...
public void DisablePossibility(PossibilityRect possibility)
{
...
if (possibility.Value == model.Value)
{
...
}
else
{
...
ClearPossibilityOnlyValue(possibility);
}
...
}
public void EnablePossibility(PossibilityRect possibility)
{
...
if (possibility.Value == model.Value)
{
...
}
else
{
...
DisplayPossibilityOnlyValue(possibility);
}
}
}- method
SelectPossibility of the work cell (WorkCellImage):public class WorkCellImage : MonoBehaviour
{
...
public void SelectPossibility(PossibilityRect possibility)
{
...
if (selectedPossibility == possibility)
{
...
selectedCell.DisplayPossibilityOnlyValue(possibility);
}
else
{
...
selectedCell.ClearPossibilityOnlyValue(selectedPossibility);
}
}
}The next step is to display suggestions in the empty cells of the board.
We add a text field (
SuggestionText) into to the selected cell (CellImage) to be able to display a suggestion:CalculateSuggestion and SetSuggestion to the selected cell (CellImage) which calculate and set the suggestion:public class CellImage : MonoBehaviour
{
...
public TextMeshProUGUI suggestionText;
...
public void SetSuggestion(int value)
{
suggestionText.SetText(model.Value == 0 && value != 0 ? value.ToString() : "");
}
public int CalculateSuggestion()
{
int largestRequiredValue = 0;
int largestPossibleValue = 0;
int possibleValueCount = 0;
for (int value = 1; value <= 9; ++value)
{
if (model.MustHaveValue(value))
{
Debug.AssertFormat(
largestRequiredValue == 0,
"Multiple required values: {0} and {1}", largestRequiredValue, value);
Debug.AssertFormat(
model.CanHaveValue(value),
"Impossible value required: {0}", value);
largestRequiredValue = value;
}
if (model.CanHaveValue(value))
{
++possibleValueCount;
largestPossibleValue = value;
}
}
if (largestRequiredValue != 0)
{
return largestRequiredValue;
}
else if (possibleValueCount == 1)
{
return largestPossibleValue;
}
else
{
return 0;
}
}
}
We turn the board (
BoardImage) into a singleton, memorize all the cells, and add a method which updates all cell suggestions:public class BoardImage : MonoBehaviour
{
public static BoardImage Instance { get; private set; }
private CellImage[] cellImages;
...
private void Awake()
{
cellImages = GetComponentsInChildren<CellImage>();
if (Instance != null && Instance != this)
{
Destroy(this);
}
else
{
Instance = this;
}
model = new BoardModel(cellImages);
...
}
...
public void UpdateAllCellSuggestions()
{
foreach(CellImage cellImage in cellImages)
{
cellImage.SetSuggestion(cellImage.CalculateSuggestion());
}
}
}We invoke this method with every value change of a cell (
CellImage):public class CellImage : MonoBehaviour
{
...
public void SetValue(int value)
{
...
BoardImage.Instance.UpdateAllCellSuggestions();
}
...
}Finally, when a cell has a suggestion, we want to disallow selecting any other value. We do this in the
SelectPossibility method of the work cell (WorkCellImage):public class WorkCellImage : MonoBehaviour
{
...
public void SelectPossibility(PossibilityRect possibility)
{
Debug.Assert(selectedCell != null, "Selected cell cannot be null.");
Debug.Assert(possibility != null, "Possibility cannot be null.");
// Do not allow selecting possibility other than the suggestion.
int selectedCellSuggestion = selectedCell.CalculateSuggestion();
if (selectedCellSuggestion != 0 && selectedCellSuggestion != possibility.Value)
{
return;
}
...
}
}That's how we get the app from the introduction.
GitHub
Commits related to this step are here:
Next steps
The app now allows solving our example sudoku. In the next step we will add the legend, persistence of application state, and translation to other languages.
Polski | Angielski










Comments
Post a Comment