Advertisement
Guest User

ysnt.cs

a guest
Mar 6th, 2025
77
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.24 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading.Tasks;
  5. using System.Linq;
  6.  
  7. /// <summary>
  8. /// Program used when writing my answer at
  9. /// https://puzzling.stackexchange.com/questions/130857/you-see-no-three/130869#130869
  10. /// Made available by the author of that answer under the same licensing terms as the answer itself, or under MIT-0 licence as you prefer.
  11. ///
  12. /// The program as presented below outputs a "table" of win conditions for each player, as used in the strategy part of the answer,
  13. /// which is truncated due to an OutOfMemoryException.
  14. /// However, modifications to the "Main" function and/or uncommenting some of the Console.WriteLine lines will easily allow other kinds of investigations.
  15. ///
  16. /// There are likely many further optimisations that can be done, in particular replacing the class structure with lightweight objects having a better
  17. /// internal representation of the MultiGameState, earlier detection of states that are equivalent to each other, etc.
  18. /// It's also certainly possible to use maths to further simplify the search tree...
  19. /// </summary>
  20.  
  21. namespace Program
  22. {
  23. public class YouSeeNoThree
  24. {
  25. public enum Players : byte
  26. {
  27. Bear,
  28. Rabbit,
  29. }
  30.  
  31. public enum Colours : byte
  32. {
  33. Blue,
  34. Red,
  35. Mixed,
  36. }
  37.  
  38. public struct WhiteRun : IEquatable<WhiteRun>
  39. {
  40. public int runLength;
  41. public Colours endColour;
  42.  
  43. // IsDegenerate => nobody can ever play there
  44. public bool IsDegenerate => runLength < 2 || runLength == 2 && endColour == Colours.Mixed;
  45.  
  46. public override string ToString() => $"{endColour}{runLength}";
  47.  
  48. public override int GetHashCode() => endColour.GetHashCode() + runLength * 3;
  49. public override bool Equals(object obj) => obj is WhiteRun other && Equals(other);
  50. public bool Equals(WhiteRun other) => this.runLength == other.runLength && this.endColour == other.endColour;
  51. }
  52.  
  53. public abstract class GameState : IEquatable<GameState>
  54. {
  55. protected GameState(Players nextPlayer)
  56. {
  57. NextPlayer = nextPlayer;
  58. }
  59. public Players NextPlayer { get; }
  60. public abstract IEnumerable<WhiteRun> Runs { get; }
  61.  
  62. public abstract IEnumerable<GameState> EnumerateNextStates();
  63.  
  64. public abstract override string ToString();
  65.  
  66. public static GameState Create(Players nextPlayer, params WhiteRun[] runs)
  67. {
  68. switch (runs.Length)
  69. {
  70. case 0: return LostGameState.GetLostState(nextPlayer);
  71. case 1: return new SimpleGameState(nextPlayer, runs[0]);
  72. default: return new MultiGameState(nextPlayer, runs);
  73. }
  74. }
  75.  
  76. public abstract override int GetHashCode();
  77. public abstract bool Equals(GameState other);
  78. public override bool Equals(object obj) => obj is GameState other && Equals(other);
  79. }
  80.  
  81. public class LostGameState : GameState
  82. {
  83. private LostGameState(Players nextPlayer)
  84. : base(nextPlayer)
  85. { }
  86.  
  87. private static Dictionary<Players, LostGameState> LostGameStates { get; } = new Dictionary<Players, LostGameState>
  88. {
  89. { Players.Bear, new LostGameState(Players.Bear)},
  90. { Players.Rabbit, new LostGameState(Players.Rabbit)},
  91. };
  92.  
  93. public static LostGameState GetLostState(Players nextPlayer) => LostGameStates[nextPlayer];
  94.  
  95. public override IEnumerable<WhiteRun> Runs => Enumerable.Empty<WhiteRun>();
  96.  
  97. public override IEnumerable<GameState> EnumerateNextStates() => Enumerable.Empty<GameState>();
  98.  
  99. public override string ToString() => $"{NextPlayer} lost the game.";
  100.  
  101. public override bool Equals(GameState other) => other is LostGameState && NextPlayer == other.NextPlayer;
  102. public override int GetHashCode() => NextPlayer.GetHashCode();
  103. }
  104.  
  105. public class SimpleGameState : GameState
  106. {
  107. public SimpleGameState(Players nextPlayer, byte runLength, Colours endColour)
  108. : this(nextPlayer, new WhiteRun { runLength = runLength, endColour = endColour })
  109. { }
  110. public SimpleGameState(Players nextPlayer, WhiteRun run)
  111. : base(nextPlayer)
  112. {
  113. singleRun = run;
  114. }
  115.  
  116. public readonly WhiteRun singleRun;
  117. override public IEnumerable<WhiteRun> Runs => new[] { singleRun };
  118.  
  119. public override IEnumerable<GameState> EnumerateNextStates()
  120. {
  121. if (singleRun.runLength < 2)
  122. throw new InvalidOperationException();
  123.  
  124. var opponent = GetOpponent(NextPlayer);
  125. Colours playerColour = GetColour(NextPlayer);
  126. int minPlayPosition = singleRun.endColour == playerColour ? 1 : 0;
  127. int maxPlayPosition = singleRun.endColour == Colours.Mixed ? singleRun.runLength - 3 : (singleRun.runLength / 2 - 1);
  128.  
  129. Colours beforeColour = singleRun.endColour == playerColour ? playerColour : Colours.Mixed;
  130. Colours afterColour = singleRun.endColour == Colours.Mixed ? playerColour : beforeColour;
  131.  
  132. for (int playPosition = minPlayPosition; playPosition <= maxPlayPosition; playPosition++)
  133. {
  134. int runLengthBefore = playPosition;
  135. int runLengthAfter = singleRun.runLength - 2 - runLengthBefore;
  136.  
  137. var runAfter = new WhiteRun { runLength = runLengthAfter, endColour = afterColour };
  138. var runBefore = new WhiteRun { runLength = runLengthBefore, endColour = beforeColour };
  139.  
  140. if (runBefore.IsDegenerate)
  141. {
  142. if (runAfter.IsDegenerate)
  143. yield return GameState.Create(opponent);
  144. else
  145. yield return GameState.Create(opponent, runAfter);
  146. }
  147. else if (runAfter.IsDegenerate)
  148. yield return GameState.Create(opponent, runBefore);
  149. else
  150. {
  151. // invariant to have canonical ordering - longest run goes first, with colour as tie-break.
  152. if (runLengthAfter < runLengthBefore || runLengthAfter == runLengthBefore && afterColour < beforeColour)
  153. yield return GameState.Create(opponent, runBefore, runAfter);
  154. else
  155. yield return GameState.Create(opponent, runAfter, runBefore);
  156. }
  157. }
  158. }
  159.  
  160. public override string ToString() => $"{NextPlayer} to play, {singleRun}";
  161.  
  162. public override bool Equals(GameState other) => other is SimpleGameState otherSimple && NextPlayer == other.NextPlayer && singleRun.Equals(otherSimple.singleRun);
  163. public override int GetHashCode() => NextPlayer.GetHashCode() + singleRun.GetHashCode() * 2;
  164. }
  165.  
  166. public class MultiGameState : GameState
  167. {
  168. public MultiGameState(Players nextPlayer, params WhiteRun[] runs)
  169. : base(nextPlayer)
  170. {
  171. this.runs = runs;
  172. if (runs.Length < 2)
  173. throw new ArgumentOutOfRangeException();
  174. }
  175. public readonly WhiteRun[] runs;
  176. override public IEnumerable<WhiteRun> Runs => runs;
  177.  
  178. public override IEnumerable<GameState> EnumerateNextStates()
  179. {
  180. // make a valid move on each of the runs in turn...
  181. for (int runIndex = 0; runIndex < runs.Length; runIndex++)
  182. {
  183. // all runs except the one we are making a move on are unaltered...
  184. List<WhiteRun> runsAfterMove = runs.Where((_, i) => i != runIndex).ToList();
  185. foreach(var nextStateFromSimple in new SimpleGameState(NextPlayer, runs[runIndex]).EnumerateNextStates())
  186. {
  187. // and we then merge the runs list, preserving the invariant ordering.
  188. var combinedRunsAfterMove = runsAfterMove.Concat(nextStateFromSimple.Runs).Where(r => !r.IsDegenerate).OrderByDescending(r => r.runLength).ThenByDescending(r => r.endColour).ToArray();
  189. yield return GameState.Create(nextStateFromSimple.NextPlayer, combinedRunsAfterMove);
  190. }
  191. }
  192. }
  193.  
  194. public override string ToString() => $"{NextPlayer} to play, {string.Join(", ", runs)}";
  195. public override bool Equals(GameState other) => other is MultiGameState otherMulti && NextPlayer == other.NextPlayer && runs.SequenceEqual(otherMulti.runs);
  196. public override int GetHashCode()
  197. {
  198. int subtotal = 0;
  199. for (int i = 0; i < runs.Length; i++)
  200. {
  201. subtotal *= 257;
  202. subtotal += runs[i].GetHashCode();
  203. }
  204. return NextPlayer.GetHashCode() + subtotal * 2;
  205. }
  206. }
  207.  
  208.  
  209. public static Players GetOpponent(Players player)
  210. {
  211. switch (player)
  212. {
  213. case Players.Bear: return Players.Rabbit;
  214. case Players.Rabbit: return Players.Bear;
  215. default: throw new ArgumentOutOfRangeException();
  216. }
  217. }
  218.  
  219. public static Colours GetColour(Players player)
  220. {
  221. switch (player)
  222. {
  223. case Players.Bear: return Colours.Blue;
  224. case Players.Rabbit: return Colours.Red;
  225. default: throw new ArgumentOutOfRangeException();
  226. }
  227. }
  228.  
  229. static Dictionary<GameState, LostGameState> knownOutcomes = new Dictionary<GameState, LostGameState>();
  230.  
  231. static LostGameState GetOutcomeFromState(GameState state, string indent = "")
  232. {
  233. // Console.WriteLine($"{indent}Checking outcome from: {state}");
  234. if (knownOutcomes.TryGetValue(state, out var result))
  235. {
  236. // Console.WriteLine($"{indent}Cached result: {result}");
  237. return result;
  238. }
  239.  
  240. if (state is LostGameState lostState)
  241. result = lostState;
  242. else if (state is SimpleGameState simpleState && simpleState.singleRun.runLength < 5)
  243. {
  244. switch (simpleState.singleRun.runLength)
  245. {
  246. case 0:
  247. case 1:
  248. // degenerate game states where there is no possible move
  249. result = LostGameState.GetLostState(state.NextPlayer);
  250. break;
  251. case 2:
  252. // with 2 white beads, at most one player can play in this state, if they have non-matching colour for both ends.
  253. switch (simpleState.singleRun.endColour)
  254. {
  255. case Colours.Blue: result = LostGameState.GetLostState(Players.Bear); break;
  256. case Colours.Red: result = LostGameState.GetLostState(Players.Rabbit); break;
  257. case Colours.Mixed: result = LostGameState.GetLostState(state.NextPlayer); break;
  258. default: throw new InvalidOperationException();
  259. }
  260. break;
  261. case 3:
  262. // with 3 white beads, at least one player can play in this state, if they have non-matching colour for one end.
  263. switch (simpleState.singleRun.endColour)
  264. {
  265. case Colours.Blue: result = LostGameState.GetLostState(Players.Bear); break;
  266. case Colours.Red: result = LostGameState.GetLostState(Players.Rabbit); break;
  267. case Colours.Mixed: result = LostGameState.GetLostState(GetOpponent(state.NextPlayer)); break;
  268. default: throw new InvalidOperationException();
  269. }
  270. break;
  271. case 4:
  272. // with 4 white beads, a valid "losing" move may exist, but we don't play that.
  273. // opponent loses, as nextPlayer makes the obvious winning move.
  274. result = LostGameState.GetLostState(GetOpponent(state.NextPlayer));
  275. break;
  276.  
  277. default:
  278. throw new InvalidOperationException();
  279. }
  280. }
  281. else
  282. result = GenerateUnknownOutcome(state, indent + " ");
  283.  
  284. // Console.WriteLine($"{indent}Storing outcome: {result}");
  285. knownOutcomes.Add(state, result);
  286. return result;
  287. }
  288.  
  289. static LostGameState GenerateUnknownOutcome(GameState state, string indent = "")
  290. {
  291. // unless we have at least one winning move, the state is lost.
  292. var result = LostGameState.GetLostState(state.NextPlayer);
  293.  
  294. foreach(var nextState in state.EnumerateNextStates())
  295. {
  296. // Console.WriteLine($"{indent}Investigating state: {nextState}");
  297. var nextResult = GetOutcomeFromState(nextState, indent);
  298. if (nextResult != result)
  299. {
  300. // Console.WriteLine($"{indent}Winning move found!");
  301. return nextResult;
  302. }
  303. }
  304. return result;
  305. }
  306.  
  307. public static int Main()
  308. {
  309. var availableColours = new[] { Colours.Blue, Colours.Red, Colours.Mixed };
  310. var availablePlayers = new[] { Players.Rabbit, Players.Bear };
  311. int testIndex = 0;
  312. Console.Write("RunLength");
  313. foreach (var player in availablePlayers)
  314. foreach (var endColour in availableColours)
  315. Console.Write($"\t{player}:{endColour}");
  316. Console.WriteLine();
  317. for (byte count = 0; count <= 99; count++)
  318. {
  319. var winners = new List<Players>();
  320. foreach (var player in availablePlayers)
  321. {
  322. foreach (var endColour in availableColours)
  323. {
  324. var startState = GameState.Create(player, new WhiteRun { runLength = count, endColour = endColour });
  325. var result = GetOutcomeFromState(startState, $"[test{testIndex++}] ");
  326. //Console.WriteLine($"Result from [{startState}]: {result}");
  327. winners.Add(GetOpponent(result.NextPlayer));
  328. }
  329. }
  330. Console.WriteLine($"{count}\t{string.Join("\t", winners)}");
  331. }
  332.  
  333. return 0;
  334. }
  335. }
  336. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement