Sprawdzam Sudoku - Kurs Unity - 6. Podpowiedzi
W poprzednim kroku zaimplementowaliśmy zasady gry. W tym kroku dodamy podpowiedzi pomagające rozwiązać Sudoku:
Implementacja
Zacznijmy od podpowiedzi w roboczej komórce. Chcemy, aby podpowiedziała ona niektóre opcje w sposób wizualny, tj. wartość która jest jedyną możliwą w danej kolumnie oznaczymy na niebiesko (
#1E88E5), w danym wierszu, na czerwono (#D81B60), a w danym kwadracie 3x3 na żółto (#FFC107).Pobieramy darmową ikonę celownika z pixabay.com, którą następnie korzystając z GIMP'a przerabiamy i przemalowujemy na wybrane kolory:
Do możliwej wartości (
PossibilityRect) dodajemy obrazy (OnlyValueColImage, OnlyValueRowImage, OnlyValueGroupImage) obrócone tak, aby możliwe było wyświetlenie kilku podpowiedzi jednocześnie (jeśli dana wartość może być jedyna np. zarówno w kolumnie jak i wierszu):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;
}
}
}Pytanie 1:
Który element powinien włączać podpowiedzi w roboczej komórce?
Możliwości:
- Robocza komórka (
WorkCellImage) - Wybrana komórka (
CellImage)
Decyzja:
Aby stwierdzić, że w danej kolumnie, wierszu, lub kwadracie 3x3 dana wartość nie może wystąpić, potrzebny jest dostęp do modelu. Robocza komórka (
WorkCellImage) tego dostępu nie ma, więc ustawianie podpowiedzi dodajemy do wybranej komórki (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);
}
}
}Aby to zadziałało, model musi mieć metodę
GetCellSetTypesWithOnlyValue która sprawdzi w których zbiorach komórek dana wartość jest jedyną możliwą. Implementujemy metody CanHaveValue oraz GetCellSetTypesWithOnlyValue w modelu komórki. Dodatkowo implementujemy metodę MustHaveValue: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;
}
}W modelu zbioru komórek musimy zaimplementować metodę
CanAnyOtherCellHaveValue: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;
}
}Teraz robocza komórka musi aktywować podpowiedzi kiedy tylko wyświetlona jest możliwość, której wartość nie może wystąpić nigdzie indziej. Robimy to w dwóch miejscach:
- metodach
DisablePossibility oraz EnablePossibility wybranej komórki (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);
}
}
}- metodzie
SelectPossibility roboczej komórki (WorkCellImage):public class WorkCellImage : MonoBehaviour
{
...
public void SelectPossibility(PossibilityRect possibility)
{
...
if (selectedPossibility == possibility)
{
...
selectedCell.DisplayPossibilityOnlyValue(possibility);
}
else
{
...
selectedCell.ClearPossibilityOnlyValue(selectedPossibility);
}
}
}Następnym krokiem jest pokazanie podpowiedzi na planszy, jeżeli pusta komórka zawiera jakąkolwiek podpowiedź.
Do wybranej komórki (
CellImage) dodajemy pole tekstowe (SuggestionText), aby możliwe było wyświetlenie podpowiedzi:CellImage) dodajemy metody CalculateSuggestion oraz SetSuggestion wyliczające i ustawiające podpowiedź: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;
}
}
}
Z planszy (
BoardImage) robimy singleton, zapamiętujemy wszystkie komórki w zmiennej i dodajemy metodę uaktualniającą sugestie dla wszystkich komórek: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());
}
}
}Metodę tą wołamy przy każdej zmianie wartości komórki (
CellImage):public class CellImage : MonoBehaviour
{
...
public void SetValue(int value)
{
...
BoardImage.Instance.UpdateAllCellSuggestions();
}
...
}Na koniec, jeżeli komórka posiada podpowiedź, chcemy uniemożliwić wybranie jakiejkolwiek innej wartości. Robimy to w metodzie
SelectPossibility roboczej komórki (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;
}
...
}
}Otrzymujemy aplikację pokazaną we wstępie.
GitHub
Zmiany związane z tym krokiem znajdują się tutaj:
Kolejne kroki
Istniejąca aplikacja pozwala całkowicie rozwiązać nasze przykładowe Sudoku. W następnym kroku dodamy legendę, zapisywanie stanu aplikacji oraz tłumaczenie na inne języki.
Polski | Angielski










Komentarze
Prześlij komentarz