/PatienceSolverConsole/PatienceSolverConsole/PatienceField.cs
C# | 255 lines | 192 code | 32 blank | 31 comment | 24 complexity | 855847fc285be5de249569e308fc0f51 MD5 | raw file
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Text; 5using System.Collections.ObjectModel; 6using System.IO; 7 8namespace PatienceSolverConsole 9{ 10 11 /// <summary> 12 /// Immutable representation of a patience field state 13 /// </summary> 14 public class PatienceField 15 { 16 /// <summary> 17 /// Gets valid destination stacks, from least to most likely to lead to a solution 18 /// </summary> 19 /// <returns></returns> 20 public IEnumerable<CardStack> GetDestinationStacks() 21 { 22 return PlayStacks.Cast<CardStack>() 23 .Concat(FinishStacks.Cast<CardStack>()); 24 } 25 26 /// <summary> 27 /// Gets valid origin stacks, from least to most likely to lead to a solution 28 /// </summary> 29 /// <returns></returns> 30 public IEnumerable<CardStack> GetOriginStacks() 31 { 32 yield return Stock; 33 foreach (var stack in PlayStacks) 34 yield return stack; 35 } 36 37 38 public IEnumerable<PlayStack> PlayStacks { get; private set; } 39 public IEnumerable<FinishStack> FinishStacks { get; private set; } 40 public Stock Stock { get; private set; } 41 42 private int _hash; 43 private IList<PlayStack> _playStacksOrdered; 44 45 public PatienceField(Stock stock, IEnumerable<PlayStack> playStacks, IEnumerable<FinishStack> finishStacks) 46 { 47 Stock = stock; 48 PlayStacks = playStacks.ToList(); 49 FinishStacks = finishStacks.ToList(); 50 _playStacksOrdered = PlayStacks.Where(s => s.Count > 0).OrderBy(s => s.Top.GetHashCode()).ToList(); 51 _hash = DoGetHashCode(); 52 } 53 54 public static PatienceField FillWithRandomCards(Random random) 55 { 56 var cards = GetStock().ToList(); 57 Util.Shuffle(cards, random); 58 IEnumerable<Card> stackless = cards; 59 var playstacks = new List<PlayStack>(); 60 var finishstacks = new List<FinishStack>(); 61 for (int playstack = 1; playstack <= 7; playstack++) 62 { 63 var stack = PlayStack.Create(stackless.Take(playstack)); 64 stackless = stackless.Skip(playstack); 65 playstacks.Add(stack); 66 } 67 for (int finishstack = 1; finishstack <= 4; finishstack++) 68 { 69 var stack = FinishStack.Create(Enumerable.Empty<Card>()); 70 finishstacks.Add(stack); 71 } 72 var stock = new Stock(stackless, false); 73 74 return new PatienceField(stock, playstacks, finishstacks); 75 } 76 77 /// <summary> 78 /// returns all cards of every suit, ordered by suit. 79 /// </summary> 80 /// <returns></returns> 81 public static IEnumerable<Card> GetStock() 82 { 83 foreach (var suit in Util.GetValues<Suit>()) 84 foreach (var value in Util.GetValues<Value>()) 85 yield return new Card(suit, value); 86 } 87 88 /// <summary> 89 /// Prints the field to Console.Out 90 /// </summary> 91 public void DumpToConsole() 92 { 93 var toprow = FinishStacks.Cast<CardStack>() 94 .Concat(new[] { Stock }); 95 DumpRows(toprow); 96 DumpRows(PlayStacks.ToArray()); 97 } 98 99 100 private static void DumpRows(IEnumerable<CardStack> toprow) 101 { 102 int i = 0; 103 bool morerows; 104 do 105 { 106 morerows = false; 107 foreach (var row in toprow) 108 { 109 morerows |= row.WriteLine(i); 110 } 111 Console.WriteLine(); 112 i++; 113 } while (morerows); 114 } 115 116 /// <summary> 117 /// Two fields equal if all the stacks are the same. 118 /// But, if the location of the stacks are rearranged, the fields are equal too. 119 /// </summary> 120 /// <param name="obj"></param> 121 /// <returns></returns> 122 public override bool Equals(object obj) 123 { 124 if (ReferenceEquals(this, obj)) return true; 125 var other = obj as PatienceField; 126 if (other == null) return false; 127 if (other.GetHashCode() != this.GetHashCode()) 128 return false; // this should be performant 129 if (!Stock.Equals(other.Stock)) 130 return false; 131 132 133 return _playStacksOrdered.SequenceEqual(other._playStacksOrdered); 134 // The finish stacks are not checked, because only the cards not in stock or on the play stacks are there. 135 // There is only one significant order possible, so if the cards in stock and on the play stacks are equal, so must be the finish stacks. 136 } 137 138 public override int GetHashCode() 139 { 140 return _hash; 141 } 142 143 private int DoGetHashCode() 144 { 145 var mystacksOrdered = PlayStacks.OrderByDescending(s => s.GetHashCode()); 146 var hashcode = 0; 147 foreach (var stack in mystacksOrdered) 148 hashcode = hashcode * 81 + stack.GetHashCode(); 149 return hashcode; 150 } 151 152 public bool IsDone() 153 { 154 return 155 PlayStacks 156 .SelectMany(s => s) 157 .All(card => card.Visible); 158 } 159 160 private int GetValue(Card c) 161 { 162 if (c == null) return 0; 163 return (int)c.Value; 164 } 165 166 public PatienceField Move(Card toMove, CardStack from, CardStack to) 167 { 168 var newto = to.Accept(toMove, from); 169 var newfrom = from.Remove(toMove); 170 171 var newstock = Replace(Stock, from, newfrom); 172 173 var newplaystacks = PlayStacks; 174 var newfinishstacks = FinishStacks; 175 176 if (from is PlayStack) 177 newplaystacks = newplaystacks.Select(ps => Replace(ps, from, newfrom)); 178 else if (from is FinishStack) 179 newfinishstacks = newfinishstacks.Select(ps => Replace(ps, from, newfrom)); 180 181 if (to is PlayStack) 182 newplaystacks = newplaystacks.Select(ps => Replace(ps, to, newto)); 183 else if (to is FinishStack) 184 newfinishstacks = newfinishstacks.Select(ps => Replace(ps, to, newto)); 185 186 return new PatienceField(newstock, newplaystacks, newfinishstacks); 187 } 188 189 private T Replace<T>(T instance, object tosearch, object replacement) 190 { 191 if (object.ReferenceEquals(tosearch, instance)) 192 return (T)replacement; 193 return instance; 194 } 195 196 public PatienceField DoTrivialMoves() 197 { 198 // Try the tops of the playstacks 199 foreach (var stack in PlayStacks) 200 { 201 if (stack.Count == 0) continue; 202 var card = stack.Top; 203 if (IsSafeToPlay(card)) 204 { 205 foreach (var dest in FinishStacks.Where(s => s.CanAccept(card, stack))) 206 { 207 return Move(card, stack, dest).DoTrivialMoves(); 208 } 209 } 210 } 211 // Try all stock cards 212 foreach (var stockcard in Stock.Where(IsSafeToPlay)) 213 foreach (var dest in FinishStacks.Where(s => s.CanAccept(stockcard, Stock))) 214 { 215 return Move(stockcard, Stock, dest).DoTrivialMoves(); 216 } 217 return this; 218 } 219 220 private bool IsSafeToPlay(Card card) 221 { // this move does never block a solution since it 222 // cannot block another card: 223 // all cards that can go on top of this card, 224 // can also enter their finish stack. 225 if (card.Value == Value.Ace) return true; 226 return FinishStacks.Select(f => GetValue(f.Top)).All( 227 value => value >= GetValue(card) - 2); 228 } 229 230 internal PatienceField NextCard() 231 { 232 return new PatienceField(Stock.NextCard(), PlayStacks, FinishStacks); 233 } 234 } 235 236 static class Util 237 { 238 public static IEnumerable<T> GetValues<T>() 239 { 240 return Enum.GetValues(typeof(T)).Cast<T>(); 241 } 242 243 internal static void Shuffle<T>(IList<T> cards, Random random) 244 { 245 var number = cards.Count; 246 for (int i = 0; i < number; i++) 247 { 248 var toshuffle = cards[i]; 249 var newplace = random.Next(number); 250 cards[i] = cards[newplace]; 251 cards[newplace] = toshuffle; 252 } 253 } 254 } 255}