Skip to content

Instantly share code, notes, and snippets.

@TAGC
Last active May 29, 2024 14:07
Show Gist options
  • Save TAGC/a9625e4b1e886e015860736047cb786c to your computer and use it in GitHub Desktop.
Save TAGC/a9625e4b1e886e015860736047cb786c to your computer and use it in GitHub Desktop.
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