Last active
August 29, 2015 13:56
-
-
Save Metapyziks/9195636 to your computer and use it in GitHub Desktop.
Solver for the card flip minigame in Pokemon Heart Gold / Soul Silver.
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
| //#define TEST | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| namespace CardFlipSolver | |
| { | |
| class Program | |
| { | |
| struct LineConstraint | |
| { | |
| public int Value; | |
| public int Bombs; | |
| } | |
| class Card | |
| { | |
| private HashSet<int> _possibilities; | |
| private double _bombProbability; | |
| public IEnumerable<int> Possibilities { get { return _possibilities; } } | |
| public bool MustBeBomb { get { return !MustBeValue && Possibilities.Count() == 1; } } | |
| public bool MustBeValue { get { return !Possibilities.Contains(0); } } | |
| public bool Revealed { get; private set; } | |
| public bool Confirmed { get; private set; } | |
| public double BombProbability | |
| { | |
| get { return MustBeBomb ? 1.0 : _bombProbability; } | |
| set { _bombProbability = value; } | |
| } | |
| public double Expected { get; set; } | |
| public int TrueValue | |
| { | |
| get | |
| { | |
| if (Revealed) return _possibilities.First(); | |
| throw new InvalidOperationException(); | |
| } | |
| } | |
| public Card() | |
| { | |
| _possibilities = new HashSet<int> { 0, 1, 2, 3 }; | |
| } | |
| public void Reveal(int value) | |
| { | |
| if (_possibilities.Contains(value)) { | |
| _possibilities = new HashSet<int> { value }; | |
| Revealed = true; | |
| Confirmed = false; | |
| } | |
| } | |
| public void Confirm() | |
| { | |
| Confirmed = true; | |
| } | |
| public bool Constrain(IEnumerable<int> constraint) | |
| { | |
| var diff = _possibilities | |
| .Where(x => !constraint.Contains(x)) | |
| .ToArray(); | |
| if (diff.Count() == _possibilities.Count) { | |
| throw new Exception(); | |
| } | |
| foreach (var val in diff) { | |
| _possibilities.Remove(val); | |
| } | |
| return diff.Length > 0; | |
| } | |
| } | |
| static Card[] GetRow(Card[,] grid, int row) | |
| { | |
| return new[] { grid[row, 0], grid[row, 1], grid[row, 2], grid[row, 3], grid[row, 4] }; | |
| } | |
| static Card[] GetCol(Card[,] grid, int col) | |
| { | |
| return new[] { grid[0, col], grid[1, col], grid[2, col], grid[3, col], grid[4, col] }; | |
| } | |
| static IEnumerable<int[]> GetPermutations(int count, int bombs, int value, int min, int max) | |
| { | |
| int values = count - bombs; | |
| if (value < min * values || value > max * values) return new int[0][]; | |
| if (bombs < 0 || bombs > count) return new int[0][]; | |
| if (value == 0 && bombs == 0) return new int[0][]; | |
| if (count == 1) { | |
| return new[] { new[] { value } }; | |
| } | |
| return Enumerable.Range(min, max + 1 - min) | |
| .SelectMany(x => GetPermutations(count - 1, bombs - (x == 0 ? 1 : 0), value - x, min, max) | |
| .Select(y => new[] { x } | |
| .Concat(y) | |
| .ToArray())) | |
| .ToArray(); | |
| } | |
| static int[][] GetPossibilities(LineConstraint constraint, Card[] cards) | |
| { | |
| constraint.Value -= cards.Where(x => x.Revealed).Sum(x => x.TrueValue); | |
| constraint.Bombs -= cards.Where(x => x.MustBeBomb).Count(); | |
| var cardArray = cards.Where(x => !x.MustBeBomb && !x.Revealed).ToArray(); | |
| if (cardArray.Length == 0) return new[] { cards.Select(x => x.Revealed ? x.TrueValue : 0).ToArray() }; | |
| int count = cardArray.Length; | |
| int max = cardArray.SelectMany(x => x.Possibilities).Max(); | |
| int min = cardArray.SelectMany(x => x.Possibilities).Min(); | |
| max = Math.Min(max, constraint.Value - (count - constraint.Bombs - 1)); | |
| min = Math.Max(min, constraint.Bombs == 0 ? 1 : 0); | |
| var perms = GetPermutations(count, constraint.Bombs, constraint.Value, min, max) | |
| .ToArray() | |
| .Where(x => Enumerable.Range(0, count) | |
| .All(y => cardArray[y].Possibilities.Contains(x[y]))) | |
| .ToArray(); | |
| if (perms.Any(x => perms.Any(y => x != y && Enumerable.Range(0, 5).All(i => x[i] == y[i])))) { | |
| throw new Exception(); | |
| } | |
| var final = new int[perms.Length][]; | |
| for (int i = 0; i < perms.Length; ++i) { | |
| final[i] = new int[5]; | |
| for (int j = 0, k = 0; j < 5; ++j) { | |
| if (cards[j].Revealed) { | |
| final[i][j] = cards[j].TrueValue; | |
| } else if (cards[j].MustBeBomb) { | |
| final[i][j] = 0; | |
| } else { | |
| final[i][j] = perms[i][k++]; | |
| } | |
| } | |
| } | |
| return final; | |
| } | |
| static void UpdateConstraint(LineConstraint constraint, Card[] cards) | |
| { | |
| var perms = GetPossibilities(constraint, cards); | |
| var possible = Enumerable.Range(0, 5) | |
| .Select(x => perms | |
| .Select(y => y[x]) | |
| .Distinct() | |
| .ToArray()) | |
| .ToArray(); | |
| for (int i = 0; i < 5; ++i) { | |
| cards[i].Constrain(possible[i]); | |
| } | |
| } | |
| static bool AutoReveal(Card[,] grid) | |
| { | |
| bool changed = false; | |
| foreach (var card in grid) { | |
| if (!card.Revealed && card.MustBeValue && card.Possibilities.Count() == 1 && card.Possibilities.First() == 1) { | |
| card.Reveal(card.Possibilities.First()); | |
| changed = true; | |
| } | |
| } | |
| return changed; | |
| } | |
| static int[,] SelectConfiguration(List<int[]>[] rows, int index) | |
| { | |
| var grid = new int[5, 5]; | |
| for (int r = 0; r < 5; ++r) { | |
| int i = index % rows[r].Count; | |
| var row = rows[r][i]; | |
| for (int c = 0; c < 5; ++c) { | |
| grid[r, c] = row[c]; | |
| } | |
| index /= rows[r].Count; | |
| } | |
| return grid; | |
| } | |
| static Random _rand = new Random(); | |
| static bool CalculateProbabilities(Card[,] grid, LineConstraint[] rows, LineConstraint[] cols, int level, out int minScore, out int maxScore, out int avgScore) | |
| { | |
| Console.WriteLine("Calculating probabilities: "); | |
| int left = Console.CursorLeft; | |
| var rowPoss = new List<int[]>[5]; | |
| var colPoss = new List<int[]>[5]; | |
| int count = 1; | |
| for (int i = 0; i < 5; ++i) { | |
| rowPoss[i] = GetPossibilities(rows[i], GetRow(grid, i)).ToList(); | |
| colPoss[i] = GetPossibilities(cols[i], GetCol(grid, i)).ToList(); | |
| count *= rowPoss[i].Count; | |
| } | |
| int limit = Math.Min(count, 5000000); | |
| var valid = new List<int[,]>(); | |
| long totalScore = 0; | |
| minScore = 0; | |
| maxScore = 0; | |
| int lastPercent = -1; | |
| for (int i = 0; i < limit; ++i) { | |
| var config = SelectConfiguration(rowPoss, limit == count ? i : _rand.Next(count)); | |
| int percent = ((i + 1) * 100) / limit; | |
| if (percent > lastPercent) { | |
| Console.CursorLeft = left; | |
| Console.Write("{0}%", percent); | |
| lastPercent = percent; | |
| } | |
| int score = Enumerable.Range(0, 5) | |
| .SelectMany(r => Enumerable.Range(0, 5) | |
| .Select(c => Math.Max(1, config[r, c]))) | |
| .Aggregate(1, (x, y) => x * y); | |
| if (score < GetLevelMinScore(level) || score > GetLevelMaxScore(level)) { | |
| continue; | |
| } | |
| if (Enumerable.Range(0, 5) | |
| .All(c => colPoss[c] | |
| .Any(col => Enumerable.Range(0, 5) | |
| .All(r => col[r] == config[r, c])))) { | |
| valid.Add(config); | |
| if (score < minScore || minScore == 0) { | |
| minScore = score; | |
| } | |
| if (score > maxScore) { | |
| maxScore = score; | |
| } | |
| totalScore += score; | |
| } | |
| } | |
| Console.WriteLine(); | |
| bool changed = false; | |
| for (int r = 0; r < 5; ++r) { | |
| for (int c = 0; c < 5; ++c) { | |
| var prob = valid.Count(x => x[r, c] == 0) / (double) valid.Count; | |
| var expected = valid.Sum(x => x[r, c]) / (double) valid.Count; | |
| var card = grid[r, c]; | |
| if (limit != count) { | |
| prob += (0.5 - prob) * 0.02; | |
| } else { | |
| changed = card.Constrain(card.Possibilities.Where(x => valid.Any(y => y[r, c] == x))) || changed; | |
| } | |
| grid[r, c].Expected = expected; | |
| if (prob != grid[r, c].BombProbability) { | |
| grid[r, c].BombProbability = prob; | |
| } | |
| } | |
| } | |
| avgScore = (int) (totalScore / valid.Count); | |
| return changed; | |
| } | |
| static ConsoleColor GetCardColor(Card card, Card[,] grid) | |
| { | |
| if (card.Revealed) { | |
| return card.Confirmed ? ConsoleColor.DarkGray : ConsoleColor.Yellow; | |
| } | |
| if (card.MustBeBomb) { | |
| return ConsoleColor.Red; | |
| } | |
| if (card.MustBeValue) { | |
| return ConsoleColor.Green; | |
| } | |
| var min = Enumerable.Range(0, 5) | |
| .Select(r => Enumerable.Range(0, 5) | |
| .Select(c => !grid[r, c].Revealed ? grid[r, c].BombProbability : 1.0) | |
| .Min()) | |
| .Min(); | |
| if (card.BombProbability <= 1.1 * min) { | |
| return ConsoleColor.Cyan; | |
| } | |
| if (card.BombProbability <= min + (1.0 - min) / 4.0) { | |
| return ConsoleColor.DarkCyan; | |
| } | |
| return ConsoleColor.White; | |
| } | |
| static void PrintCard(Card card, Card[,] grid, int part) | |
| { | |
| Console.ForegroundColor = GetCardColor(card, grid); | |
| switch (part) { | |
| case 0: | |
| Console.Write(" {0} {1} ", | |
| !card.Revealed && !card.MustBeBomb && card.Possibilities.Contains(0) ? "x" : " ", | |
| !card.Revealed && card.Possibilities.Contains(1) ? "1" : " "); | |
| break; | |
| case 1: | |
| Console.Write(" {0} ", | |
| card.Revealed ? " " + card.TrueValue.ToString() + " " : | |
| card.MustBeBomb ? " X " : | |
| card.MustBeValue ? " # " : | |
| ((int) (card.BombProbability * 100)).ToString("D2") + "%"); | |
| break; | |
| case 2: | |
| Console.Write(" {0} {1} ", | |
| !card.Revealed && card.Possibilities.Contains(2) ? "2" : " ", | |
| !card.Revealed && card.Possibilities.Contains(3) ? "3" : " "); | |
| break; | |
| default: | |
| Console.ResetColor(); | |
| Console.Write("-----"); | |
| break; | |
| } | |
| Console.ResetColor(); | |
| if (part >= 3) Console.Write("+"); | |
| else Console.Write("|"); | |
| } | |
| static void PrintGrid(Card[,] grid) | |
| { | |
| Console.Write("|"); | |
| for (int c = 0; c < 5; ++c) { | |
| Console.Write(" {0} |", (c + 1).ToString()); | |
| } | |
| Console.WriteLine(); | |
| Console.Write("+"); | |
| for (int c = 0; c < 5; ++c) { | |
| Console.Write("-----+"); | |
| } | |
| Console.WriteLine("-"); | |
| for (int r = 0; r < 5; ++r) { | |
| for (int i = 0; i < 4; ++i) { | |
| if (i < 3) { | |
| Console.Write("|"); | |
| } else { | |
| Console.Write("+"); | |
| } | |
| for (int c = 0; c < 5; ++c) { | |
| PrintCard(grid[r, c], grid, i); | |
| if (c == 4) { | |
| if (i == 3) { | |
| Console.Write("-"); | |
| } else if (i == 1) { | |
| Console.Write(r + 1); | |
| } | |
| } | |
| } | |
| Console.WriteLine(); | |
| } | |
| } | |
| Console.WriteLine(); | |
| } | |
| static void PrintCardBorder(int r, int c, ConsoleColor color) | |
| { | |
| var orig = Console.CursorTop; | |
| var top = Console.CursorTop - 5 * 4 - 3; | |
| Console.ForegroundColor = color; | |
| Console.CursorTop = top + 1 + r * 4; | |
| Console.CursorLeft = c * 6; | |
| Console.Write("+-----+"); | |
| for (int i = 0; i < 3; ++i) { | |
| Console.CursorLeft -= 7; | |
| Console.CursorTop += 1; | |
| Console.Write("|"); | |
| Console.CursorLeft += 5; | |
| Console.Write("|"); | |
| } | |
| Console.CursorLeft -= 7; | |
| Console.CursorTop += 1; | |
| Console.Write("+-----+"); | |
| Console.ResetColor(); | |
| Console.CursorTop = orig; | |
| Console.CursorLeft = 0; | |
| } | |
| static int GetLevelMinScore(int level) | |
| { | |
| switch (level) { | |
| case 1: | |
| return 16; | |
| case 2: | |
| return 32; | |
| case 3: | |
| return 64; | |
| case 4: | |
| return 128; | |
| case 5: | |
| return 256; | |
| default: | |
| return 512; | |
| } | |
| } | |
| static int GetLevelMaxScore(int level) | |
| { | |
| switch (level) { | |
| case 1: | |
| return 64; | |
| case 2: | |
| return 128; | |
| case 3: | |
| return 256; | |
| case 4: | |
| return 512; | |
| case 5: | |
| return 1024; | |
| default: | |
| return 50000; | |
| } | |
| } | |
| static int ReadNumber(String message, int min, int max) | |
| { | |
| int val; | |
| do { | |
| Console.Write("{0}: ", message); | |
| } while (!int.TryParse(Console.ReadLine(), out val) || val < min || val > max); | |
| return val; | |
| } | |
| static IEnumerable<Card> EnumerateGrid(Card[,] grid) | |
| { | |
| for (int r = 0; r < 5; ++r) { | |
| for (int c = 0; c < 5; ++c) { | |
| yield return grid[r, c]; | |
| } | |
| } | |
| } | |
| static void GetCardLocation(Card[,] grid, Card card, out int r, out int c) | |
| { | |
| r = 0; c = 0; | |
| for (r = 0; r < 5; ++r) { | |
| for (c = 0; c < 5; ++c) { | |
| if (grid[r, c] == card) return; | |
| } | |
| } | |
| } | |
| static void Main(string[] args) | |
| { | |
| Console.SetWindowSize(40, 24); | |
| Console.SetBufferSize(40, 24); | |
| #if TEST | |
| var test = new int[] { | |
| 7, 1, | |
| 4, 3, | |
| 7, 1, | |
| 2, 3, | |
| 4, 2, | |
| 6, 1, | |
| 5, 2, | |
| 6, 1, | |
| 4, 3, | |
| 3, 3 | |
| }; | |
| #endif | |
| var rows = new LineConstraint[5]; | |
| var cols = new LineConstraint[5]; | |
| var grid = new Card[5, 5]; | |
| while (true) { | |
| Console.Clear(); | |
| for (int r = 0; r < 5; ++r) { | |
| for (int c = 0; c < 5; ++c) { | |
| grid[r, c] = new Card(); | |
| } | |
| } | |
| Console.Write("Level: "); | |
| int level = int.Parse(Console.ReadLine()); | |
| for (int r = 0; r < 5; ++r) { | |
| #if TEST | |
| rows[r].Value = test[r * 2]; | |
| rows[r].Bombs = test[r * 2 + 1]; | |
| #else | |
| rows[r].Value = ReadNumber(String.Format("Row {0} Value", r + 1), 0, 15); | |
| rows[r].Bombs = ReadNumber(String.Format("Row {0} Bombs", r + 1), 0, 5); | |
| #endif | |
| } | |
| for (int c = 0; c < 5; ++c) { | |
| #if TEST | |
| cols[c].Value = test[10 + c * 2]; | |
| cols[c].Bombs = test[10 + c * 2 + 1]; | |
| #else | |
| cols[c].Value = ReadNumber(String.Format("Col {0} Value", c + 1), 0, 15); | |
| cols[c].Bombs = ReadNumber(String.Format("Col {0} Bombs", c + 1), 0, 5); | |
| #endif | |
| } | |
| #if TEST | |
| //grid[0, 1].Reveal(2); | |
| //grid[1, 0].Reveal(1); | |
| //grid[1, 1].Reveal(1); | |
| //grid[1, 2].Reveal(2); | |
| //grid[1, 3].Reveal(3); | |
| //grid[1, 4].Reveal(1); | |
| //grid[4, 2].Reveal(2); | |
| #endif | |
| Console.WriteLine(); | |
| int top = Console.CursorTop; | |
| int curRow = 0, curCol = 0; | |
| var cancelled = false; | |
| do { | |
| int minScore, maxScore, avgScore; | |
| Console.Clear(); | |
| do { | |
| for (int i = 0; i < 5; ++i) { | |
| UpdateConstraint(rows[i], GetRow(grid, i)); | |
| UpdateConstraint(cols[i], GetCol(grid, i)); | |
| } | |
| } while (AutoReveal(grid) || CalculateProbabilities(grid, rows, cols, level, out minScore, out maxScore, out avgScore) || AutoReveal(grid)); | |
| Console.Clear(); | |
| PrintGrid(grid); | |
| Console.WriteLine("Min: {0}, Max: {1}, Avg: {2}", minScore, maxScore, avgScore); | |
| Console.CursorTop -= 1; | |
| var bestArr = EnumerateGrid(grid) | |
| .Where(x => !x.Revealed && !x.MustBeBomb) | |
| .OrderBy(x => x.BombProbability) | |
| .ToArray(); | |
| if (bestArr.Length > 0) { | |
| var best = bestArr[0]; | |
| bestArr = bestArr | |
| .Where(x => x.BombProbability == best.BombProbability) | |
| .OrderByDescending(x => x.Expected) | |
| .ToArray(); | |
| best = bestArr[0]; | |
| bestArr = bestArr | |
| .Where(x => x.Expected == best.Expected) | |
| .ToArray(); | |
| best = bestArr[_rand.Next(bestArr.Length)]; | |
| GetCardLocation(grid, best, out curRow, out curCol); | |
| } | |
| bool revealed = false; | |
| do { | |
| PrintCardBorder(curRow, curCol, ConsoleColor.Yellow); | |
| switch (Console.ReadKey(true).Key) { | |
| case ConsoleKey.LeftArrow: | |
| if (curCol >= 1) { | |
| PrintCardBorder(curRow, curCol, ConsoleColor.Gray); | |
| curCol -= 1; | |
| } | |
| break; | |
| case ConsoleKey.RightArrow: | |
| if (curCol < 4) { | |
| PrintCardBorder(curRow, curCol, ConsoleColor.Gray); | |
| curCol += 1; | |
| } | |
| break; | |
| case ConsoleKey.UpArrow: | |
| if (curRow >= 1) { | |
| PrintCardBorder(curRow, curCol, ConsoleColor.Gray); | |
| curRow -= 1; | |
| } | |
| break; | |
| case ConsoleKey.DownArrow: | |
| if (curRow < 4) { | |
| PrintCardBorder(curRow, curCol, ConsoleColor.Gray); | |
| curRow += 1; | |
| } | |
| break; | |
| case ConsoleKey.D1: | |
| case ConsoleKey.NumPad1: | |
| grid[curRow, curCol].Reveal(1); | |
| revealed = true; | |
| break; | |
| case ConsoleKey.D2: | |
| case ConsoleKey.NumPad2: | |
| grid[curRow, curCol].Reveal(2); | |
| revealed = true; | |
| break; | |
| case ConsoleKey.D3: | |
| case ConsoleKey.NumPad3: | |
| grid[curRow, curCol].Reveal(3); | |
| revealed = true; | |
| break; | |
| case ConsoleKey.R: | |
| cancelled = true; | |
| break; | |
| } | |
| foreach (var card in EnumerateGrid(grid)) { | |
| if (card.Revealed && !card.Confirmed) { | |
| card.Confirm(); | |
| } | |
| } | |
| } while (!revealed && !cancelled); | |
| } while (!cancelled); | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment