Last active
May 29, 2024 14:07
-
-
Save TAGC/a9625e4b1e886e015860736047cb786c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
public class Program | |
{ | |
public static void Main() | |
{ | |
var game = GameGenerator.NewGame(); | |
Console.WriteLine(game); | |
Console.WriteLine("Determining solution..."); | |
foreach (var solution in SolutionGenerator.Generate()) | |
{ | |
if (game.Guess(solution)) break; | |
} | |
} | |
} | |
public static class SolutionGenerator | |
{ | |
public static IEnumerable<Solution> Generate() | |
{ | |
var digits = Enumerable.Range(0, 10); | |
return from leftDigit in digits | |
from middleDigit in digits | |
from rightDigit in digits | |
select new Solution(leftDigit, middleDigit, rightDigit); | |
} | |
} | |
public static class GameGenerator | |
{ | |
private static readonly IList<Solution> _possibleSolutions = SolutionGenerator.Generate().ToArray(); | |
private static readonly Random _random = new Random(); | |
public static Game NewGame() | |
{ | |
var attempt = 0; | |
while (true) | |
{ | |
attempt++; | |
var constraints = GenerateConstraintSet().ToArray(); | |
bool ValidSolution(Solution solution) => constraints.All(it => it.Permits(solution)); | |
var solutionsSatisfyingConstraints = _possibleSolutions.Count(ValidSolution); | |
Console.WriteLine($"Attempt {attempt}: {solutionsSatisfyingConstraints} solutions"); | |
//Console.WriteLine(new Game(constraints)); | |
//Console.ReadKey(); | |
if (solutionsSatisfyingConstraints == 1) return new Game(constraints); | |
} | |
} | |
private static HashSet<IConstraint> GenerateConstraintSet() | |
{ | |
const int constraintsPerGame = 5; | |
var constraints = new HashSet<IConstraint>(); | |
while (constraints.Count < constraintsPerGame) | |
constraints.Add(GenerateConstraint()); | |
return constraints; | |
} | |
private static IConstraint GenerateConstraint() | |
{ | |
while (true) | |
{ | |
var digitConstraints = new[] | |
{ | |
GenerateDigitConstraint(), | |
GenerateDigitConstraint(), | |
GenerateDigitConstraint() | |
}; | |
if (digitConstraints.Select(it => it.Digit).Distinct().Count() != 3) continue; | |
if (digitConstraints.All(it => it.Correct)) continue; | |
return new PlacementContraint(digitConstraints[0], digitConstraints[1], digitConstraints[2]); | |
} | |
} | |
private static SingleDigitPlacementConstraint GenerateDigitConstraint() | |
{ | |
var digit = _random.Next(0, 10); | |
switch (_random.Next(3)) | |
{ | |
case 0: return new SingleDigitPlacementConstraint(digit, true, true); | |
case 1: return new SingleDigitPlacementConstraint(digit, true, false); | |
default: return new SingleDigitPlacementConstraint(digit, false, false); | |
} | |
} | |
} | |
public class Game | |
{ | |
private readonly IConstraint[] _constraints; | |
private Solution _solution; | |
public Game(params IConstraint[] constraints) | |
{ | |
_constraints = constraints; | |
} | |
public bool Guess(Solution solution) | |
{ | |
if (_solution != null) | |
throw new InvalidOperationException("Game has already been solved"); | |
var correctSolution = _constraints.All(it => it.Permits(solution)); | |
if (correctSolution) | |
{ | |
_solution = solution; | |
Console.WriteLine("Correct! You win."); | |
Console.WriteLine(this.ToString()); | |
} | |
return correctSolution; | |
} | |
public override string ToString() | |
{ | |
var description = new StringBuilder(_solution?.ToString() ?? "[ ][ ][ ]"); | |
description.AppendLine(); | |
description.AppendLine(); | |
_constraints.ToList().ForEach(it => description.AppendLine(it.ToString())); | |
return description.ToString(); | |
} | |
} | |
public class Solution | |
{ | |
public Solution(int leftDigit, int middleDigit, int rightDigit) | |
{ | |
Digits = new[] { leftDigit, middleDigit, rightDigit }; | |
} | |
public int[] Digits { get; } | |
public override string ToString() => string.Join(null, Digits.Select(it => $"[{it}]")); | |
} | |
public interface IConstraint | |
{ | |
bool Permits(Solution solution); | |
} | |
public class PlacementContraint : IConstraint, IEquatable<PlacementContraint> | |
{ | |
private readonly SingleDigitPlacementConstraint[] _digitConstraints; | |
public PlacementContraint( | |
SingleDigitPlacementConstraint leftDigitConstraint, | |
SingleDigitPlacementConstraint middleDigitConstraint, | |
SingleDigitPlacementConstraint rightDigitConstraint) | |
{ | |
leftDigitConstraint.PlacementIndex = 0; | |
middleDigitConstraint.PlacementIndex = 1; | |
rightDigitConstraint.PlacementIndex = 2; | |
_digitConstraints = new[] { leftDigitConstraint, middleDigitConstraint, rightDigitConstraint }; | |
} | |
public override string ToString() => | |
string.Join(null, _digitConstraints.Select(it => $"[{it.Digit}]")) + "\t" + DescribeConstraints(); | |
public bool Permits(Solution solution) => _digitConstraints.All(it => it.Permits(solution)); | |
private string DescribeConstraints() | |
{ | |
var numCorrectAndWellPlaced = _digitConstraints.Count(it => it.Correct && it.WellPlaced); | |
var numOnlyCorrect = _digitConstraints.Count(it => it.Correct && !it.WellPlaced); | |
var numWrong = _digitConstraints.Count(it => !it.Correct && !it.WellPlaced); | |
switch ((numCorrectAndWellPlaced, numOnlyCorrect, numWrong)) | |
{ | |
case var t when t.Equals((3, 0, 0)): return "All correct and well-placed"; | |
case var t when t.Equals((0, 3, 0)): return "All correct but in wrong places"; | |
case var t when t.Equals((0, 0, 3)): return "Nothing is correct"; | |
case var t when t.Equals((1, 1, 1)): return "Two numbers are correct but only one is well-placed"; | |
case var t when t.numCorrectAndWellPlaced == 2: return "Two numbers are correct and well-placed"; | |
case var t when t.numOnlyCorrect == 2: return "Two numbers are correct but wrong places"; | |
case var t when t.numCorrectAndWellPlaced == 1: return "One number is correct and well-placed"; | |
case var t when t.numOnlyCorrect == 1: return "One number is correct but wrong place"; | |
default: throw new Exception("No description suitable for current configuration"); | |
} | |
} | |
public bool Equals(PlacementContraint other) | |
{ | |
if (other == null) return false; | |
return this._digitConstraints.SequenceEqual(other._digitConstraints); | |
} | |
public override int GetHashCode() | |
{ | |
unchecked | |
{ | |
return _digitConstraints.Aggregate(31, (hashcode, it) => hashcode + it.GetHashCode()); | |
} | |
} | |
} | |
public class SingleDigitPlacementConstraint : IConstraint, IEquatable<SingleDigitPlacementConstraint> | |
{ | |
public SingleDigitPlacementConstraint(int digit, bool correct, bool wellPlaced) | |
{ | |
if (!correct && wellPlaced) | |
throw new ArgumentException("Digit cannot be wrong but well-placed"); | |
Digit = digit; | |
Correct = correct; | |
WellPlaced = wellPlaced; | |
} | |
public int Digit { get; } | |
public int PlacementIndex { get; set; } | |
public bool Correct { get; } | |
public bool WellPlaced { get; } | |
public bool Permits(Solution solution) | |
{ | |
var correspondingDigit = solution.Digits[PlacementIndex]; | |
var otherDigits = solution.Digits.Where((d, i) => i != PlacementIndex); | |
if (Correct && WellPlaced) return Digit == correspondingDigit; | |
if (Correct && !WellPlaced) return Digit != correspondingDigit && otherDigits.Contains(Digit); | |
else return !solution.Digits.Contains(Digit); | |
} | |
public bool Equals(SingleDigitPlacementConstraint other) | |
{ | |
if (other == null) return false; | |
return (Digit == other.Digit) && | |
(PlacementIndex == other.PlacementIndex) && | |
(Correct == other.Correct) && | |
(WellPlaced == other.WellPlaced); | |
} | |
public override int GetHashCode() | |
{ | |
unchecked | |
{ | |
var hashcode = 17; | |
hashcode = 31 * hashcode + Digit.GetHashCode(); | |
hashcode = 31 * hashcode + PlacementIndex.GetHashCode(); | |
hashcode = 31 * hashcode + Correct.GetHashCode(); | |
hashcode = 31 * hashcode + WellPlaced.GetHashCode(); | |
return hashcode; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment