private static object edgeworkHtml(Edgework edgework) { return(new DIV { class_ = "edgework" }._( new DIV { class_ = "widget serial" }._(edgework.SerialNumber), edgework.Widgets.Any(w => w.Type == WidgetType.BatteryHolder) ? new DIV { class_ = "widget separator" } : null, edgework.Widgets.Where(w => w.Type == WidgetType.BatteryHolder).Select(w => new DIV { class_ = $"widget battery {(w.BatteryType == BatteryType.BatteryAA ? "aa" : "d")}" }), edgework.Widgets.Any(w => w.Type == WidgetType.Indicator) ? new DIV { class_ = "widget separator" } : null, edgework.Widgets.Where(w => w.Type == WidgetType.Indicator).Select(w => new DIV { class_ = $"widget indicator {(w.Indicator.Value.Type == IndicatorType.Lit ? "lit" : "unlit")}" }._(new SPAN { class_ = "label" }._(w.Indicator.Value.Label))), edgework.Widgets.Any(w => w.Type == WidgetType.PortPlate) ? new DIV { class_ = "widget separator" } : null, edgework.Widgets.Where(w => w.Type == WidgetType.PortPlate).Select(w => new DIV { class_ = $"widget portplate" }._(w.PortTypes.Select(p => new SPAN { class_ = p.ToString().ToLowerInvariant() }))))); }
public static void Test() { var ruleCombinationHistogram = new Dictionary <string, int>(); var numCorrectCardsHistorgram = new Dictionary <int, Dictionary <string, int> >(); var numWrongCardsHistorgram = new Dictionary <int, Dictionary <string, int> >(); const int numIter = 2000; var rnd = new Random(); var numRules = 0; for (int iter = 0; iter < numIter; iter++) { var rules = getRules(Edgework.Generate(5, 10, false, rnd)); numRules = rules.Length; var puzzle = generatePuzzle(rules, rnd); var rulesIndexesStr = puzzle.ActiveRuleIndexes.Order().Select(r => r + 1).JoinString(","); ruleCombinationHistogram.IncSafe(rulesIndexesStr); numCorrectCardsHistorgram.IncSafe(puzzle.CorrectCards.Length, rulesIndexesStr); numWrongCardsHistorgram.IncSafe(puzzle.WrongCards.Length, rulesIndexesStr); } foreach (var kvp in ruleCombinationHistogram.OrderBy(k => k.Key)) { Console.WriteLine($"{kvp.Key} = {kvp.Value * 100.0 / numIter:0.0}%"); } Console.WriteLine($"Factor: {ruleCombinationHistogram.Max(k => k.Value) / (double) ruleCombinationHistogram.Min(k => k.Value):0.0}"); Console.WriteLine(); for (int i = 0; i < numRules; i++) { Console.WriteLine($"Rule #{i + 1} = {ruleCombinationHistogram.Where(p => p.Key.Contains((i + 1).ToString())).Sum(p => p.Value) * 100.0 / numIter:0.0}%"); } Console.WriteLine(); ConsoleUtil.WriteLine("Distribution of number of correct cards:".Color(ConsoleColor.White)); var tt = new TextTable { ColumnSpacing = 2 }; var rulesIndexesStrs = ruleCombinationHistogram.Keys.Order().ToArray(); for (int col = 0; col < rulesIndexesStrs.Length; col++) { tt.SetCell(col + 1, 0, rulesIndexesStrs[col].Color(ConsoleColor.White)); } for (int i = numCorrectCardsHistorgram.Keys.Max(); i >= 0; i--) { tt.SetCell(0, i + 1, $"{i} cards".Color(ConsoleColor.White), alignment: HorizontalTextAlignment.Right); for (int col = 0; col < rulesIndexesStrs.Length; col++) { tt.SetCell(col + 1, i + 1, numCorrectCardsHistorgram.Get(i, null)?.Get(rulesIndexesStrs[col]).NullOr(val => $"{val * 100.0 / numIter:0.0}%".Color(ConsoleColor.Cyan)) ?? "", alignment: HorizontalTextAlignment.Right); } //Console.WriteLine($"{i} correct cards: {numCorrectCardsHistorgram.Get(i, 0) * 100.0 / numIter:0.0}%"); } tt.WriteToConsole(); //Console.WriteLine(); //ConsoleUtil.WriteLine("Distribution of number of wrong cards:".Color(ConsoleColor.White)); //for (int i = numWrongCardsHistorgram.Keys.Max(); i >= 0; i--) // Console.WriteLine($"{i} wrong cards: {numWrongCardsHistorgram.Get(i, 0) * 100.0 / numIter:0.0}%"); }
public static void RunStatistics() { var opinions = new List <int> [6]; for (int i = 0; i < 6; i++) { opinions[i] = new List <int>(); } const int iterations = 1000000; for (int iter = 0; iter < iterations; iter++) { var edgework = Edgework.Generate(5, 5); var i = edgework.GetNumIndicators(); var b = edgework.GetNumBatteries(); var m = Rnd.Next(7, 16); var h = edgework.GetNumBatteryHolders(); var f = Rnd.NextDouble() < .02 ? 1 : 0; var p = edgework.GetNumPorts(); var l = edgework.GetNumPortPlates(); var s = edgework.SerialNumberDigits().Sum(); opinions[0].Add((h * f - l * l) + 20 * b); opinions[1].Add(s * p - b * b * b); opinions[2].Add(m + 3 * (i + p) - f * b); opinions[3].Add(i * (20 + m) - p * l); opinions[4].Add(p * (l * b + 3) + l + b - s); opinions[5].Add(h * h * h + l * l + f * f * f * f - m); } var tt = new TextTable { ColumnSpacing = 2 }; tt.SetCell(0, 0, "Faction"); tt.SetCell(1, 0, "Mean", alignment: HorizontalTextAlignment.Right); tt.SetCell(2, 0, "Variance", alignment: HorizontalTextAlignment.Right); tt.SetCell(3, 0, "Stddev", alignment: HorizontalTextAlignment.Right); tt.SetCell(4, 0, "Min", alignment: HorizontalTextAlignment.Right); tt.SetCell(5, 0, "Max", alignment: HorizontalTextAlignment.Right); for (int i = 0; i < 6; i++) { tt.SetCell(0, i + 1, "red,blue,yellow,green,purple,orange".Split(',')[i]); var mean = (double)opinions[i].Sum() / iterations; var variance = opinions[i].Sum(o => (o - mean) * (o - mean)) / iterations; tt.SetCell(1, i + 1, $"{mean:0.0}", alignment: HorizontalTextAlignment.Right); tt.SetCell(2, i + 1, $"{variance:0.0}", alignment: HorizontalTextAlignment.Right); tt.SetCell(3, i + 1, $"{Math.Sqrt(variance):0.0}", alignment: HorizontalTextAlignment.Right); tt.SetCell(4, i + 1, opinions[i].Min().ToString(), alignment: HorizontalTextAlignment.Right); tt.SetCell(5, i + 1, opinions[i].Max().ToString(), alignment: HorizontalTextAlignment.Right); } tt.WriteToConsole(); }
internal static void Simulations() { var conditions = Ut.NewArray <(string name, Func <Edgework, bool> fnc)>( ("Last digit of the serial number is 0.", e => e.SerialNumber.EndsWith("0")), ("No indicators present.", e => e.GetNumIndicators() == 0), ("Five or more ports present.", e => e.GetNumPorts() >= 5), ("Four or more batteries present.", e => e.GetNumBatteries() >= 4), ("Empty port plate present.", e => e.GetNumEmptyPortPlates() >= 1), ("Number of modules on the bomb divisible by 3.", e => Rnd.Next(0, 3) == 0), ("More lit than unlit indicators.", e => e.GetNumLitIndicators() > e.GetNumUnlitIndicators()), ("Otherwise", e => true) //("Band practice — 3 or more batteries", e => e.GetNumBatteries() >= 3), //("Sleepy Gary — No indicators present", e => e.GetNumIndicators() == 0), //("Mathlete — Last digit of the serial is 0", e => e.SerialNumber.EndsWith("0")), //("Part-timer — More than 4 other modules present", e => true), //("BYOB — Less than 2 ports present", e => e.GetNumPorts() < 2), //("Freshman Year — Otherwise", e => true) ); var stats = new int[conditions.Length]; const int iterations = 1000; for (int iter = 0; iter < iterations; iter++) { var ew = Edgework.Generate(5, 5, true); for (int i = 0; i < conditions.Length; i++) { if (conditions[i].fnc(ew)) { stats[i]++; break; } } } for (int i = 0; i < conditions.Length; i++) { Console.WriteLine($"{i + 1}. {conditions[i].name} = {stats[i] * 100 / (double) iterations}%"); } }
public static void RunSimulations() { const int numIterations = 100000; var astrologyElements = new[] { "Fire", "Water", "Earth", "Air" }; var astrologyPlanets = new[] { "Sun", "Jupiter", "Moon", "Saturn", "Mercury", "Uranus", "Venus", "Neptune", "Mars", "Pluto" }; var astrologyZodiacs = new[] { "Aries", "Leo", "Sagittarius", "Taurus", "Virgo", "Capricorn", "Gemini", "Libra", "Aquarius", "Cancer", "Scorpio", "Pisces" }; var yes = new[] { "Yes" }; var blindMazeColorNames = "Red,Green,White,Gray,Yellow".Split(','); var simulatables = Ut.NewArray( new Simulatable { Active = false, Name = "Connection Check", GetResult = ew => { var numbers = Enumerable.Range(0, 8).Select(_ => Rnd.Next(1, 9)).ToArray(); return(Ut.NewArray( numbers.Distinct().Count() == 8 ? "1. all distinct" : numbers.Count(c => c == 1) > 1 ? "2. more than one “1”" : numbers.Count(c => c == 7) > 1 ? "3. more than one “7”" : numbers.Count(c => c == 2) >= 3 ? "4. at least three “2”s" : numbers.Count(c => c == 5) == 0 ? "5. no “5”s" : numbers.Count(c => c == 8) == 2 ? "6. exactly two “8”s" : ew.GetNumBatteries().Apply(bs => bs > 6 || bs == 0) ? "7. more than 6 or no batteries" : "8. count the batteries")); } }, new Simulatable { Active = false, Name = "Laundry: 4/2+BOB (“unicorn”)", GetResult = ew => ew.GetNumAABatteries() == 4 && ew.GetNumDBatteries() == 0 && ew.HasLitIndicator("BOB") ? yes : null }, new Simulatable { Active = false, Name = "Lettered Keys", GetResult = ew => { var number = Rnd.Next(0, 100); return(Ut.NewArray( number == 69 ? "1. 69 → D" : number % 6 == 0 ? "2. divisible by 6 → A" : ew.GetNumBatteries() >= 2 && number % 3 == 0 ? "3. ≥ 2 batteries and divisible by 3 → B" : ew.SerialNumber.Any("CE3".Contains) ? (number >= 22 && number <= 79 ? "4. C, E, 3 & 22 ≤ n ≤ 79 → B" : "5. C, E, 3 → C") : number < 46 ? "6. n < 46 → D" : "7. otherwise → A")); } }, new Simulatable { Active = false, Name = "Astrology", GetResult = ew => { var planet = Rnd.Next(astrologyPlanets.Length); var zodiac = Rnd.Next(astrologyZodiacs.Length); var element = Rnd.Next(astrologyElements.Length); var elemPlanet = new[, ] { { 0, 0, 1, -1, 0, 1, -2, 2, 0, -1 }, { -2, 0, -1, 0, 2, 0, -2, 2, 0, 1 }, { -1, -1, 0, -1, 1, 2, 0, 2, 1, -2 }, { -1, 2, -1, 0, -2, -1, 0, 2, -2, 2 } }; var elemZodiac = new[, ] { { 1, 0, -1, 0, 0, 2, 2, 0, 1, 0, 1, 0 }, { 2, 2, -1, 2, -1, -1, -2, 1, 2, 0, 0, 2 }, { -2, -1, 0, 0, 1, 0, 1, 2, -1, -2, 1, 1 }, { 1, 1, -2, -2, 2, 0, -1, 1, 0, 0, -1, -1 } }; var planetZodiac = new[, ] { { -1, -1, 2, 0, -1, 0, -1, 1, 0, 0, -2, -2 }, { -2, 0, 1, 0, 2, 0, -1, 1, 2, 0, 1, 0 }, { -2, -2, -1, -1, 1, -1, 0, -2, 0, 0, -1, 1 }, { -2, 2, -2, 0, 0, 1, -1, 0, 2, -2, -1, 1 }, { -2, 0, -1, -2, -2, -2, -1, 1, 1, 1, 0, -1 }, { -1, -2, 1, -1, 0, 0, 0, 1, 0, -1, 2, 0 }, { -1, -1, 0, 0, 1, 1, 0, 0, 0, 0, -1, -1 }, { -1, 2, 0, 0, 1, -2, 1, 0, 2, -1, 1, 0 }, { 1, 0, 2, 1, -1, 1, 1, 1, 0, -2, 2, 0 }, { -1, 0, 0, -1, -2, 1, 2, 1, 1, 0, 0, -1 } }; var omen = new[] { astrologyPlanets[planet], astrologyZodiacs[zodiac], astrologyElements[element] }.Sum(str => str.Any(ew.SerialNumber.Contains) ? 1 : -1); omen += elemPlanet[element, planet] + elemZodiac[element, zodiac] + planetZodiac[planet, zodiac]; return(omen == 0 ? new[] { "No Omen" } : omen > 0 ? new[] { "Good Omen", omen.ToString() } : new[] { "Poor Omen", (-omen).ToString() }); } }, new Simulatable { Active = false, Name = "Plumbing", GetResult = ew => { // Plumbing: most inputs and outputs var redInput = // For: Serial contains a '1' +(ew.SerialNumber.Contains('1') ? 1 : 0) // For: Exactly 1 RJ45 port + (ew.GetNumPorts(PortType.RJ45) == 1 ? 1 : 0) // Against: Any duplicate ports - (ew.HasDuplicatePorts() ? 1 : 0) // Against: Any duplicate ew.SerialNumber characters - (ew.SerialNumber.Order().SelectConsecutivePairs(false, (c1, c2) => c1 == c2).Any() ? 1 : 0) > 0; var yellowInput = // For: Serial contains a '2' +(ew.SerialNumber.Contains('2') ? 1 : 0) // For: One or more Stereo RCA ports + (ew.GetNumPorts(PortType.StereoRCA) > 0 ? 1 : 0) // Against: No duplicate ports - (ew.HasDuplicatePorts() ? 0 : 1) // Against: Serial contains a '1' or 'L' - (ew.SerialNumber.Contains('1') || ew.SerialNumber.Contains('L') ? 1 : 0) > 0; var greenInput = // For: Serial contains 3 or more numbers +(ew.SerialNumber.Count(ch => ch >= '0' && ch <= '9') >= 3 ? 1 : 0) // For: One or more DVI-D ports + (ew.GetNumPorts(PortType.DVI) > 0 ? 1 : 0) // Against: Red Input is inactive - (redInput ? 0 : 1) // Against: Yellow Input is inactive - (yellowInput ? 0 : 1) > 0; var blueInput = // Note: Always active if all other inputs are inactive (!redInput && !yellowInput && !greenInput) || ( // For: At least 4 unique ports (ew.GetNumPortTypes() >= 4 ? 1 : 0) // For: At least 4 batteries + (ew.GetNumBatteries() >= 4 ? 1 : 0) // Against: No ports - (ew.GetPorts().Any() ? 0 : 1) // Against: No batteries - (ew.GetNumBatteries() == 0 ? 1 : 0) > 0 ); var redOutput = // For: One or more Serial ports (ew.GetNumPorts(PortType.Serial) > 0 ? 1 : 0) // For: Exactly one battery + (ew.GetNumBatteries() == 1 ? 1 : 0) // Against: Serial contains more than 2 numbers - (ew.SerialNumber.Count(ch => ch >= '0' && ch <= '9') >= 3 ? 1 : 0) // Against: More than 2 inputs are active - ((redInput ? 1 : 0) + (blueInput ? 1 : 0) + (greenInput ? 1 : 0) + (yellowInput ? 1 : 0) > 2 ? 1 : 0) > 0; var yellowOutput = // For: Any duplicate ports (ew.HasDuplicatePorts() ? 1 : 0) // For: Serial contains a '4' or '8' + (ew.SerialNumber.Contains('4') || ew.SerialNumber.Contains('8') ? 1 : 0) // Against: Serial doesn't contain a '2' - (ew.SerialNumber.Contains('2') ? 0 : 1) // Against: Green Input is active - (greenInput ? 1 : 0) > 0; var greenOutput = // For: Exactly 3 inputs are active ((redInput ? 1 : 0) + (blueInput ? 1 : 0) + (greenInput ? 1 : 0) + (yellowInput ? 1 : 0) == 3 ? 1 : 0) // For: Exactly 3 ports are present + (ew.GetNumPorts() == 3 ? 1 : 0) // Against: Less than 3 ports are present - (ew.GetNumPorts() < 3 ? 1 : 0) // Against: Serial contains more than 3 numbers - (ew.SerialNumberDigits().Count() > 3 ? 1 : 0) > 0; var blueOutput = // Note: Always active if all other outputs are inactive (!redOutput && !greenOutput && !yellowOutput) || // For: All inputs are active +((redInput && greenInput && yellowInput && blueInput) ? 1 : 0) // For: Any other output is inactive + (!redOutput || !yellowOutput || !greenOutput ? 1 : 0) // Against: Less than 2 batteries - (ew.GetNumBatteries() < 2 ? 1 : 0) // Against: No Parallel port - (ew.GetNumPorts(PortType.Parallel) == 0 ? 1 : 0) > 0; var numInputs = (redInput ? 1 : 0) + (blueInput ? 1 : 0) + (greenInput ? 1 : 0) + (yellowInput ? 1 : 0); var numOutputs = (redOutput ? 1 : 0) + (blueOutput ? 1 : 0) + (greenOutput ? 1 : 0) + (yellowOutput ? 1 : 0); return(Ut.NewArray( //$"{numInputs} inputs", //$"{numOutputs} outputs" new[] { redInput ? "Red in" : null, yellowInput ? "Yellow in" : null, greenInput ? "Green in" : null, blueInput ? "Blue in" : null, redOutput ? "Red out" : null, yellowOutput ? "Yellow out" : null, greenOutput ? "Green out" : null, blueOutput ? "Blue out" : null } .Where(s => s != null).JoinString(", ") )); } }, new Simulatable { Active = false, Name = "Chess", GetResult = ew => new[] { Chess.GetSolution(ew.SerialNumber.Last() % 2 != 0, out _, out _)[6] } }, new Simulatable { Active = true, Name = "Blind Maze", GetResult = ew => { // Colors of the N/S/W/E buttons var cols = new[] { Rnd.Next(0, 5), Rnd.Next(0, 5), Rnd.Next(0, 5), Rnd.Next(0, 5) }; // 0 = Red // 1 = Green // 2 = White // 3 = Gray // 4 = Yellow // If there are at least two red buttons, if (cols.Count(c => c == 0) >= 2) { // rotate the maze 90 degrees clockwise and then calculate starting position. return new[] { "90", "after" } } ; // Otherwise, if there are at least 5 batteries, if (ew.GetNumBatteries() >= 5) { // calculate starting position and then rotate the maze 90 degrees clockwise. return new[] { "90", "before" } } ; // Otherwise, if there is an IND indicator, if (ew.HasIndicator("IND")) { // rotate the maze 180 degrees and then calculate starting position. return new[] { "180", "after" } } ; // Otherwise, if there are no yellow buttons and at least one red button, if (cols.Count(c => c == 4) == 0 && cols.Count(c => c == 0) > 0) { // rotate your perspective of the maze 90 degrees clockwise and then calculate starting position. return new[] { "270", "after" } } ; // Otherwise, if there are at least 2 types of maze-based modules on the bomb*, if (Rnd.NextDouble() < .25) { // calculate starting position and then rotate the maze 180 degrees clockwise. return new[] { "180", "before" } } ; // Otherwise, if there is at most 1 port type on the bomb, if (ew.GetNumPortTypes() < 2) { // calculate starting position and then rotate your perspective of the maze 90 degrees clockwise. return new[] { "270", "before" } } ; // Otherwise, keep the maze as it is. return(new[] { "0" }); } } ).Where(s => s.Active).ToArray(); var results = new AutoDictionary <string, SimulatedResult>(str => new SimulatedResult()); var hundredths = numIterations / 100; for (int attempt = 0; attempt < numIterations; attempt++) { if (attempt % hundredths == 0) { Console.Write($"{attempt / hundredths}%\r"); } var edgework = Edgework.Generate(); foreach (var sim in simulatables) { var result = sim.GetResult(edgework); if (result == null) { continue; } var sr = results[sim.Name]; for (int i = 0; i < result.Length; i++) { sr = sr.Children[result[i]]; } sr.Count++; } } Console.WriteLine("Done"); Console.WriteLine(); var tt = new TextTable { ColumnSpacing = 2 }; var row = 0; foreach (var kvp in results) { tt.SetCell(0, row, kvp.Key.Color(ConsoleColor.White)); tt.SetCell(1, row, "{0/Cyan:0.####}% (or 1 in {1/Magenta:0}){2}".Color(ConsoleColor.Gray).Fmt( kvp.Value.TotalCount / (double)numIterations * 100, numIterations / (double)kvp.Value.TotalCount, generateTree(kvp.Value, numIterations, skipTop: true))); row++; } tt.WriteToConsole(); Console.WriteLine(); }
public static void CreateRaffleHtml() { var rnd = new Random(10); var conditions = Ut.NewArray <Func <Edgework, bool> >( //ew => ew.GetNumLitIndicators() < ew.GetNumUnlitIndicators(), //ew => ew.GetNumLitIndicators() > ew.GetNumUnlitIndicators(), //ew => ew.GetNumLitIndicators() == ew.GetNumUnlitIndicators(), ew => char.IsLetter(ew.SerialNumber[0]) && char.IsLetter(ew.SerialNumber[1]), ew => !char.IsLetter(ew.SerialNumber[0]) && char.IsLetter(ew.SerialNumber[1]), ew => char.IsLetter(ew.SerialNumber[0]) && !char.IsLetter(ew.SerialNumber[1]), ew => !char.IsLetter(ew.SerialNumber[0]) && !char.IsLetter(ew.SerialNumber[1]), ew => (ew.SerialNumber[3] - 'A') % 3 == 0, ew => (ew.SerialNumber[3] - 'A') % 3 == 1, ew => (ew.SerialNumber[3] - 'A') % 3 == 2, ew => (ew.SerialNumber[4] - 'A') % 3 == 0, ew => (ew.SerialNumber[4] - 'A') % 3 == 1, ew => (ew.SerialNumber[4] - 'A') % 3 == 2 ); File.WriteAllText(@"D:\c\KTANE\Public\HTML\Point of Order.html", new HTML { class_ = "no-js" }._( new HEAD( new TITLE("Point of Order"), new META { httpEquiv = "Content-Type", content = "text/html; charset=UTF-8" }, new META { httpEquiv = "X-UA-Compatible", content = "IE=edge" }, new META { name = "viewport", content = "initial-scale=1" }, new SCRIPT { src = "js/highlighter.js" }, new LINK { rel = "stylesheet", type = "text/css", href = "css/font.css" }, new STYLELiteral($@" @font-face {{ font-family: 'Anonymous Pro'; font-style: normal; font-weight: normal; src: local('Anonymous Pro'), url(font/AnonymousPro-Regular.ttf); }} @font-face {{ font-family: 'Anonymous Pro'; font-style: normal; font-weight: bold; src: local('Anonymous Pro Bold'), local('Anonymous Pro'), url(font/AnonymousPro-Bold.ttf); }} @font-face {{ font-family: 'Anonymous Pro'; font-style: italic; font-weight: normal; src: local('Anonymous Pro Italic'), local('Anonymous Pro'), url(font/AnonymousPro-Italic.ttf); }} @font-face {{ font-family: 'Anonymous Pro'; font-style: italic; font-weight: bold; src: local('Anonymous Pro Bold Italic'), local('Anonymous Pro'), url(font/AnonymousPro-Bold.ttf); }} @font-face {{ font-family: 'Californian FB'; font-style: normal; font-weight: normal; src: local('Californian FB'), url(font/CALIFR.TTF); }} @font-face {{ font-family: 'Californian FB'; font-style: normal; font-weight: bold; src: local('Californian FB Bold'), local('Californian FB'), url(font/CALIFB.TTF); }} @font-face {{ font-family: 'Californian FB'; font-style: italic; font-weight: normal; src: local('Californian FB Italic'), local('Californian FB'), url(font/CALIFI.TTF); }} @font-face {{ font-family: 'Ostrich'; src: local('Ostrich Sans'), local('Ostrich'), url(font/OstrichSans-Heavy_90.otf); }} * {{ box-sizing: border-box; }} body {{ margin: 0; text-align: center; font-family: 'Trebuchet MS'; font-size: 15pt; }} .text, .full-phrase {{ font-family: 'Californian FB'; max-width: 40em; position: relative; text-align: left; line-height: 1.4; margin: auto; }} .text {{ margin: 2em auto; padding: 0 1em; }} .text::before {{ content: ''; position: absolute; left: -275px; top: 10px; width: 216px; height: 300px; background: url(img/Point%20of%20Order/Nine%20of%20Diamonds.png) 50% 50% no-repeat; background-size: contain; opacity: .5; transform: rotate(-5deg); }} em {{ font-style: normal; text-decoration: underline; }} code {{ font-size: 80%; }} p.eye-catching {{ font-size: 115%; font-weight: bold; }} h2 {{ text-align: center; }} .author {{ font-weight: normal; font-size: smaller; }} .author::before {{ content: '('; }} .author::after {{ content: ')'; }} /* Edgework */ .edgework {{ clear: both; padding: 0 20px; overflow: auto; background: url(img/Point%20of%20Order/EdgeworkBackground.png) top left repeat; background-size: 80px; margin: 10px 0; text-align: center; }} .edgework > .widget {{ display: inline-block; vertical-align: bottom; height: 50px; background-size: cover; background-repeat: no-repeat; background-position: center center; position: relative; margin: 12px 15px 8px 0; }} .edgework > .widget.separator {{ background-image: url(img/Point%20of%20Order/EdgeworkSeparator.png); width: 9px; }} .edgework > .widget.serial {{ background-image: url(img/Point%20of%20Order/Serial%20number.png); width: 99px; padding: 23px 0 0 11px; font-family: 'Anonymous Pro'; font-weight: bold; font-size: 24px; text-align: left; }} .edgework > .widget.indicator {{ width: 115px; }} .edgework > .widget.indicator > .label {{ color: white; font-family: 'Ostrich'; font-size: 30px; letter-spacing: .05em; position: absolute; left: 76px; top: 23px; transform: translate(-50%, -50%); }} .edgework > .widget.indicator.lit {{ background-image: url(img/Point%20of%20Order/LitIndicator.png); }} .edgework > .widget.indicator.unlit {{ background-image: url(img/Point%20of%20Order/UnlitIndicator.png); }} .edgework > .widget.battery {{ }} .edgework > .widget.battery.aa {{ width: 89px; background-image: url(img/Point%20of%20Order/BatteryAA.png); }} .edgework > .widget.battery.d {{ width: 85px; background-image: url(img/Point%20of%20Order/BatteryD.png); }} .edgework > .widget.portplate {{ width: 112px; background-image: url(img/Point%20of%20Order/PortPlate.png); }} .edgework > .widget.portplate > span {{ position: absolute; background-size: cover; background-repeat: no-repeat; background-position: center center; }} .edgework > .widget.portplate > span.stereorca {{ left: 91px; top: 13px; width: 15px; height: 30px; background-image: url(img/Point%20of%20Order/PortRCA.png); }} .edgework > .widget.portplate > span.dvi {{ left: 6px; top: 23px; width: 71px; height: 23px; background-image: url(img/Point%20of%20Order/PortDVI.png); }} .edgework > .widget.portplate > span.rj45 {{ left: 3px; top: 3px; width: 21px; height: 21px; background-image: url(img/Point%20of%20Order/PortRJ.png); }} .edgework > .widget.portplate > span.ps2 {{ left: 67px; top: 5px; width: 21px; height: 21px; background-image: url(img/Point%20of%20Order/PortPS2.png); }} .edgework > .widget.portplate > span.parallel {{ left: 6px; top: 4px; width: 98px; height: 20px; background-image: url(img/Point%20of%20Order/PortParallel.png); }} .edgework > .widget.portplate > span.serial {{ left: 30px; top: 24px; width: 52px; height: 22px; background-image: url(img/Point%20of%20Order/PortSerial.png); }} .example {{ display: inline-block; width: 500px; height: 125px; background: #eee; margin-left: 1em; position: relative; }} .example .card {{ box-sizing: content-box; position: absolute; width: 72px; height: 100px; background-size: contain; background-position: 50% 50%; background-repeat: no-repeat; }} .example .card.correct {{ border: 2px solid #0c2; }} .example .card.wrong {{ border: 2px solid #c42; }} {Enumerable.Range(0, 4).SelectMany(suit => Enumerable.Range(0, 13).Select(rank => $".example .card.{(Rank) rank}.{(Suit) suit} {{ background-image: url(img/Point%20of%20Order/{(Rank) rank}%20of%20{(Suit) suit}.png); }}")).JoinString()} {Enumerable.Range(0, 5).Select(ix => $".example .pile .card:nth-child({ix + 1}) {{ left: {20 + 15 * ix}px; top: {5 + 2 * ix}px; transform: rotate({-20 + 10 * ix}deg); }}").JoinString()} {Enumerable.Range(0, 5).Select(ix => $".example .choices .card:nth-child({ix + 1}) {{ left: {180 + 75 * ix}px; top: 10px; }}").JoinString()} {Enumerable.Range(0, 5).Select(ix => $".example .choices .card.correct:nth-child({ix + 1}), .example .choices .card.wrong:nth-child({ix + 1}) {{ left: {178 + 75 * ix}px; top: 8px; }}").JoinString()} ")), new BODY( new Func <object>(() => { // Find 4 sets of edgework which between them cover all conditions // for 4 edgeworks, OLD rules: seed=4049 var seed = 20; retry: seed++; var conditionsSatisfied = new bool[conditions.Length]; var rnd2 = new Random(seed); var edgeworks = Ut.NewArray(4, _ => Edgework.Generate(5, 10, false, rnd2)); for (int i = 0; i < edgeworks.Length; i++) { for (int j = 0; j < conditions.Length; j++) { conditionsSatisfied[j] = conditionsSatisfied[j] || conditions[j](edgeworks[i]); } } if (conditionsSatisfied.Any(c => c == false)) { goto retry; } var fullResults = new List <object>(); var counterExampleIndex = new List <object>(); // Generate examples (one for each rule combination) for every set of edgework foreach (var ew in edgeworks) { Console.WriteLine(); Console.WriteLine("Edgework: " + ew); var rules = getRules(ew); // EDGEWORK HTML fullResults.Add(edgeworkHtml(ew)); // EXAMPLES foreach (var ss in Enumerable.Range(0, rules.Length).Subsequences()) { var activeRuleIxs = ss.ToArray(); if (activeRuleIxs.Length != _numActiveRules) { continue; } Console.WriteLine("Active rules: " + activeRuleIxs.Select(ix => ix + 1).JoinString(", ")); for (int i = 0; i < 3; i++) { var puzzle = generatePuzzle(rules, rnd, activeRuleIxs); var choices = puzzle.CorrectCards.Shuffle(rnd).Take(1).Concat(puzzle.WrongCards.Shuffle(rnd).Take(3)).ToArray().Shuffle(rnd); fullResults.Add(exampleHtml(puzzle.Pile, choices, choices.IndexOf(puzzle.CorrectCards.Contains))); } } } Console.WriteLine(); fullResults.Add(new H1("Counter-examples")); // Generate counter-examples foreach (var example in Ut.NewArray( new { Phrase = "Make a Full House from Two Pair", FullPhrase = (string)null, Author = "luc537#4890", Id = "full-house-from-two-pair", GetCounterExample = new Func <Puzzle, Edgework, Tuple <PlayingCard, PlayingCard> >((puzzle, edgework) => { var pair1 = puzzle.Pile.UniquePairs().FirstOrDefault(p => p.Item1.Rank == p.Item2.Rank); if (pair1 == null) { return(null); } var pair2 = puzzle.Pile.UniquePairs().FirstOrDefault(p => p.Item1.Rank == p.Item2.Rank && p.Item1.Rank != pair1.Item1.Rank); if (pair2 == null) { return(null); } return(puzzle.WrongCards.Where(card => card.Rank == pair1.Item1.Rank || card.Rank == pair2.Item1.Rank).FirstOrNull().NullOr(cc => Tuple.Create(cc, puzzle.CorrectCards[0]))); }) }, new { Phrase = "Groups of ranks", FullPhrase = "Consider the following groups of ranks: 5/10; 3/6/9; 4/8/Q. If the 2nd and 4th cards in the pile are from a single set and no other cards in the pile are from that set, then all ranks within the set are valid to play.", Author = "Storm Vision#6438", Id = "rank-sets", GetCounterExample = new Func <Puzzle, Edgework, Tuple <PlayingCard, PlayingCard> >((puzzle, edgework) => { var sets = new[] { new[] { Rank.Five, Rank.Ten }, new[] { Rank.Three, Rank.Six, Rank.Nine }, new[] { Rank.Four, Rank.Eight, Rank.Queen } }; var applicableSetIx = sets.IndexOf(s => !s.Contains(puzzle.Pile[0].Rank) && s.Contains(puzzle.Pile[1].Rank) && !s.Contains(puzzle.Pile[2].Rank) && s.Contains(puzzle.Pile[3].Rank) && !s.Contains(puzzle.Pile[4].Rank)); if (applicableSetIx == -1) { return(null); } return(puzzle.WrongCards.Where(card => sets[applicableSetIx].Contains(card.Rank)).FirstOrNull().NullOr(cc => Tuple.Create(cc, puzzle.CorrectCards[0]))); }) } )) { ConsoleUtil.WriteLine("Generating counter-example for: " + example.Phrase.Color(ConsoleColor.Magenta)); // Keep trying to find a counter-example while (true) { var edgework = Edgework.Generate(5, 7, false, rnd); if (edgework.Widgets.Any(w => w.Type == WidgetType.Indicator && !Indicator.WellKnown.Contains(w.Indicator.Value.Label))) { continue; } var rules = getRules(edgework); var puzzle = generatePuzzle(rules, rnd); var counterExample = example.GetCounterExample(puzzle, edgework); if (counterExample == null) { continue; } // Counter-example found! var counterCard = counterExample.Item1; var correctCard = counterExample.Item2; var choices = puzzle.WrongCards.Shuffle(rnd).Where(wc => wc != counterCard).Take(2).Concat(correctCard).Concat(counterCard).ToArray().Shuffle(rnd); fullResults.Add(new DIV { class_ = "counter-example", id = example.Id }._( new H2(new SPAN { class_ = "rule" }._(example.Phrase), " ", new SPAN { class_ = "author" }._(example.Author)), example.FullPhrase == null ? null : new P { class_ = "full-phrase" }._(example.FullPhrase), edgeworkHtml(edgework), exampleHtml(puzzle.Pile, choices, choices.IndexOf(correctCard), choices.IndexOf(counterCard)))); counterExampleIndex.Add(new LI(new A { href = "#" + example.Id }._(new SPAN { class_ = "rule" }._(example.Phrase), " ", new SPAN { class_ = "author" }._(example.Author)))); goto endOfCounterExample; } endOfCounterExample:; } return(Ut.NewArray <object>( new DIV { class_ = "text" }._( new H1("Point of Order!"), new P("Welcome to the exciting world of figuring stuff out."), new P(new EM("Point of Order"), @" is a new modded module for “Keep Talking and Nobody Explodes”. This module is an homage to a card game in which the fundamental premise is that the rules of the game are not explained; players merely receive hints as they play and must figure out the rules on their own through logical deduction and trial and error."), new P("In keeping with the tradition of said game, the manual for ", new EM("Point of Order"), @" is withheld until the community has deduced the rules correctly."), new P { class_ = "eye-catching" }._(@"The goal is to collaboratively identify all of the rules and form a full manual. Your prestige and recognition will be proportional to how many of the rules you figured out."), new P(@"Below, we present several hints on which cards are legal to play. Each example shows a pile of five cards on the module, a choice of four cards to play, and a green border indicating which would be the only correct selection (out of those four). In addition, you are free to ", new A { href = "http://steamcommunity.com/sharedfiles/filedetails/?id=955137794" }._("subscribe to the mod"), @" and experiment with it on your own."), new P(@"Every participant may take a “guess” by writing a manual (or fragment of a manual) and submitting it to me (", new CODE("Timwi#0551"), @" on Discord). If any part of the guess is correct, that part of the manual is published, allowing subsequent guesses to use it as a scaffold. For any part that is wrong, I will point out which example on this page contradicts it. If no contradiction is already on the page, I will add one, thus providing everyone with more hints. How exactly your proposed rules are split into “parts” is up to my own discretion."), new P(@"Guesses may be made an unlimited number of times, but you cannot submit a new guess until your previous guess has been addressed. Manuals may be written in any format that I can read, including PDF, HTML, Google Docs or Microsoft Word. (Scans of handwritten pages might be a stretch, but if your handwriting is beautiful then go for it.)"), new H2(new A { href = "Point of Order incomplete manual.html" }._("Incomplete manual so far")), new H4("Known information:"), new UL( new LI("The color or pattern on the back of the cards does not matter."), new LI("The number of strikes on the bomb does not matter."), new LI("The other modules on the bomb do not matter, nor how many of them are solved or unsolved."), new LI("Given any specific module (set of 5 exposed cards) with the same edgework, there are multiple valid cards; however, only one will show as an option in any particular set of 4 possible answers. (Credit: OceanWaves)"), new LI("You can not play the same value card that was just played. (Credit: onewingedangel30)"), new LI("Given any particular module, there is a subset of ranks and suits, such that every combination of rank/suit of those subsets is a valid answer. (Credit: OceanWaves)"), new LI("The first, second, fourth and fifth characters of the serial number matter. No other edgework matters. (Credit: samfun123)"), new LI("The cards in the pile are a sequence, and the next card to be played is determined by the previous cards. (Credit: onewingedangel30)"), new LI("The previous cards played all follow a specific set of rules which the defuser/expert must follow to play the next card. (Credit: OceanWaves)")), new H4("Counter-examples:"), new UL(counterExampleIndex)), fullResults)); }) ) ).ToString()); }
static Func <PlayingCard, List <PlayingCard>, bool>[] getRules(Edgework edgework) { var serial = edgework.SerialNumber; var serial1Letter = char.IsLetter(serial[0]); var serial2Letter = char.IsLetter(serial[1]); var allowedSuits = (serial1Letter ? serial2Letter ? "01;12;23;30" : "03;10;21;32" : serial2Letter ? "12;23;30;01" : "32;03;10;21").Split(';'); ConsoleUtil.WriteLine("{0/White} Allowed suits: {1}".Color(null).Fmt("Rule 1:", allowedSuits.Select((s, i) => "♠♥♣♦"[i].Color(ConsoleColor.Cyan) + " → " + s.Select(ch => "♠♥♣♦"[ch - '0'].ToString().Color(ConsoleColor.Green)).JoinColoredString("/".Color(ConsoleColor.DarkGray))).JoinColoredString("; ".Color(ConsoleColor.DarkGray)))); // OUTDATED Debug.LogFormat(serial1Letter // OUTDATED ? serial2Letter // OUTDATED ? "[Point of Order #{0}] Rule 2: No two consecutive cards of same suit." // OUTDATED : "[Point of Order #{0}] Rule 2: Can’t have ♠ touch ♣ or ♥ touch ♦." // OUTDATED : serial2Letter // OUTDATED ? "[Point of Order #{0}] Rule 2: Can’t have ♠ touch ♥ or ♣ touch ♦." // OUTDATED : "[Point of Order #{0}] Rule 2: Can’t have ♠ touch ♦ or ♣ touch ♥.", _moduleId); var divisibleBy = (serial[3] - 'A' + 1) % 3 + 3; ConsoleUtil.WriteLine("{0/White} Alternating divisibility by {1/Cyan}".Color(null).Fmt("Rule 2:", divisibleBy)); // OUTDATED Debug.LogFormat("[Point of Order #{0}] Rule 3: Ranks must alternate between being divisible by {1} and not.", _moduleId, divisibleBy); var difference = (serial[4] - 'A' + 1) % 3 + 2; ConsoleUtil.WriteLine("{0/White} Rank difference of {1/Cyan}{2/DarkGray}{3/Cyan}".Color(null).Fmt("Rule 3:", difference, "–", difference + 1)); // OBSOLETE Debug.LogFormat("[Point of Order #{0}] Rule 4: Consecutive ranks must have a difference between {1} and {2} (with wraparound allowed).", _moduleId, difference, difference + 3); // OBSOLETE var suitAssocNum = ((serial[2] - '0') % 2 == 0 ? 1 : 0) + ((serial[5] - '0') % 2 == 0 ? 2 : 0); // OBSOLETE var suitAssoc = new[] { "0123", "0213", "0123", "0321" }[suitAssocNum].Select(ch => (Suit) (ch - '0')).ToArray(); // OBSOLETE var numAABatteries = edgework.GetNumAABatteries(); // OBSOLETE var numDBatteries = edgework.GetNumDBatteries(); // OBSOLETE var permissibleRankDifferences = // OBSOLETE numAABatteries > numDBatteries ? new[] { 1, 3, 5, 7, 9, 11, 13 } : // OBSOLETE numAABatteries < numDBatteries ? new[] { 0, 2, 4, 6, 8, 10, 12 } : new[] { 2, 3, 5, 7, 11 }; // OBSOLETE Debug.LogFormat("[Point of Order #{0}] Rule 5: {1} OR {2} rank difference", _moduleId, // OBSOLETE suitAssocNum == 0 ? "Same suit" : string.Format("Associated suit (♠↔{0}, {1}↔{2})", "♣♥♦"[suitAssocNum - 1], "♥♣♣"[suitAssocNum - 1], "♦♦♥"[suitAssocNum - 1]), // OBSOLETE numAABatteries > numDBatteries ? "odd" : numAABatteries < numDBatteries ? "even" : "prime"); return(Ut.NewArray <Func <PlayingCard, List <PlayingCard>, bool> >( // OLD // Rule 1: Ranks must be 2×ascending+1×descending / 2×descending+1×ascending / alternate between descending and ascending // OLD (card, cards) => // OLD { // OLD if (card.Rank == cards.Last().Rank) // OLD return false; // OLD if (cards.Count < 2) // OLD return true; // OLD if (lit < unlit) // 2×asc, 1×desc // OLD { // OLD if (cards[1].Rank < cards[0].Rank) // OLD return (card.Rank > cards.Last().Rank) ^ (cards.Count % 3 == 1); // OLD if (cards.Count < 3) // OLD return true; // OLD if (cards[2].Rank < cards[1].Rank) // OLD return (card.Rank > cards.Last().Rank) ^ (cards.Count % 3 == 2); // OLD return (card.Rank > cards.Last().Rank) ^ (cards.Count % 3 == 0); // OLD } // OLD if (lit > unlit) // 2×desc, 1×asc // OLD { // OLD if (cards[1].Rank > cards[0].Rank) // OLD return (card.Rank < cards.Last().Rank) ^ (cards.Count % 3 == 1); // OLD if (cards.Count < 3) // OLD return true; // OLD if (cards[2].Rank > cards[1].Rank) // OLD return (card.Rank < cards.Last().Rank) ^ (cards.Count % 3 == 2); // OLD return (card.Rank < cards.Last().Rank) ^ (cards.Count % 3 == 0); // OLD } // OLD // alternate between desc and asc // OLD return card.Rank != cards.Last().Rank && ((card.Rank > cards.Last().Rank) ^ (cards[1].Rank > cards[0].Rank) ^ (cards.Count % 2 != 0)); // OLD }, // OLD // Rule 2: No two consecutive cards of associated suits // OLD (card, cards) => serial1Letter // OLD ? serial2Letter // OLD ? card.Suit != cards.Last().Suit // OLD : card.Suit == Suit.Spades ? cards.Last().Suit != Suit.Clubs : card.Suit == Suit.Clubs ? cards.Last().Suit != Suit.Spades : card.Suit == Suit.Diamonds ? cards.Last().Suit != Suit.Hearts : cards.Last().Suit != Suit.Diamonds // OLD : serial2Letter // OLD ? card.Suit == Suit.Spades ? cards.Last().Suit != Suit.Hearts : card.Suit == Suit.Hearts ? cards.Last().Suit != Suit.Spades : card.Suit == Suit.Diamonds ? cards.Last().Suit != Suit.Clubs : cards.Last().Suit != Suit.Diamonds // OLD : card.Suit == Suit.Spades ? cards.Last().Suit != Suit.Diamonds : card.Suit == Suit.Diamonds ? cards.Last().Suit != Suit.Spades : card.Suit == Suit.Hearts ? cards.Last().Suit != Suit.Clubs : cards.Last().Suit != Suit.Hearts, // NEW Rule 1: Consecutive cards of associated suits (card, cards) => allowedSuits[(int)cards.Last().Suit].Contains((char)('0' + (int)card.Suit)), // NEW Rule 2: Ranks must alternate between being divisible by 𝑛 and not. (card, cards) => (((int)card.Rank + 1) % divisibleBy == 0) ^ (((int)cards.Last().Rank + 1) % divisibleBy == 0), // NEW Rule 3: Consecutive ranks must have a difference of 𝑛 .. (𝑛+1) (with wraparound allowed). (card, cards) => { var thisRank = (int)card.Rank; var lastRank = (int)cards.Last().Rank; for (int i = 0; i < 2; i++) { if (thisRank == (lastRank + difference + i) % 13 || thisRank == ((lastRank - difference - i) % 13 + 13) % 13) { return true; } } return false; } // OLD // Rule 5: Consecutive cards must have associated suits or ranks // OLD (card, cards) => // OLD { // OLD if (suitAssocNum == 0 && card.Suit == cards.Last().Suit) // OLD return true; // OLD else if (suitAssocNum > 0 && card.Suit == suitAssoc[1 ^ Array.IndexOf(suitAssoc, cards.Last().Suit)]) // OLD return true; // OLD return permissibleRankDifferences.Contains(Math.Abs((int) card.Rank - (int) cards.Last().Rank)); // OLD } )); }
public static void Simulations() { const int iterations = 100000; const int quadrantCount = 5; var getQuadrantCounts = Ut.Lambda((bool[][] arr) => { var qCounts = new int[4]; for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { if (arr[x][y]) { qCounts[(y / 4) * 2 + (x / 4)]++; } } } return(qCounts); }); var quadrantCountRule = Ut.Lambda((bool white) => new Tuple <string, Func <bool[][], Edgework, int> >( $"Exactly one quadrant has {quadrantCount} or fewer {(white ? "white" : "black")} pixels ⇒ number of {(white ? "white" : "black")} pixels in the other 3 quadrants", (arr, edgework) => { var qCounts = getQuadrantCounts(arr); if ((white ? qCounts.Count(sum => sum <= quadrantCount) : qCounts.Count(sum => sum >= (16 - quadrantCount))) != 1) { return(0); } var qIx = (white ? qCounts.IndexOf(sum => sum <= quadrantCount) : qCounts.IndexOf(sum => sum >= (16 - quadrantCount))) + 1; return((qCounts.Where((sum, ix) => ix != qIx).Sum() + 3) % 4 + 1); })); var totalCountRule = Ut.Lambda((int num, bool white) => new Tuple <string, Func <bool[][], Edgework, int> >( $"The entire bitmap has {num} or more {(white ? "white" : "black")} pixels ⇒ number of {(white ? "white" : "black")} pixels", (arr, edgework) => { var sum = 0; for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { sum += (arr[x][y] ^ white) ? 0 : 1; } } if (sum >= num) { return(((sum + 3) % 4) + 1); } return(0); })); var rowColumnRule = new Tuple <string, Func <bool[][], Edgework, int> >( "Exactly one row or column is completely white or completely black ⇒ x- or y-coordinate", (arr, edgework) => { int answer = 0; for (int x = 0; x < 8; x++) { var isWhite = arr[x][0]; for (int y = 1; y < 8; y++) { if (arr[x][y] != isWhite) { goto next; } } if (answer != 0) { return(0); } // The coordinate is 0-based, but the answer needs to be 1-based. answer = (x % 4) + 1; next:; } for (int y = 0; y < 8; y++) { var isWhite = arr[0][y]; for (int x = 1; x < 8; x++) { if (arr[x][y] != isWhite) { goto next; } } if (answer != 0) { return(0); } // The coordinate is 0-based, but the answer needs to be 1-based. answer = (y % 4) + 1; next:; } return(answer); }); var squareRule = new Tuple <string, Func <bool[][], Edgework, int> >( "There is a 3×3 square that is completely white or completely black ⇒ x-coordinate of center of first in reading order", (arr, edgework) => { for (int x = 1; x < 7; x++) { for (int y = 1; y < 7; y++) { var isWhite = arr[x][y]; for (int xx = -1; xx < 2; xx++) { for (int yy = -1; yy < 2; yy++) { if (arr[x + xx][y + yy] != isWhite) { goto next; } } } // x is 0-based, but the answer needs to be 1-based. return((x % 4) + 1); next:; } } return(0); }); var quadrantMajorityRule = Ut.Lambda((string name, Func <int, int, Edgework, bool> compare, Func <int, int, Edgework, bool[][], int> getAnswer) => new Tuple <string, Func <bool[][], Edgework, int> >( name, (arr, widgets) => { var quadrantCounts = new int[4]; for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { if (arr[x][y]) { quadrantCounts[(x / 4) * 2 + (y / 4)]++; } } } var w = quadrantCounts.Count(q => q > 8); var b = quadrantCounts.Count(q => q < 8); return(compare(b, w, widgets) ? ((getAnswer(b, w, widgets, arr) + 3) % 4) + 1 : 0); })); var rules = Ut.NewArray( quadrantCountRule(true), quadrantMajorityRule("There are as many mostly-white quadrants as there are lit indicators ⇒ number of batteries", (b, w, widgets) => w == widgets.GetNumLitIndicators(), (b, w, widgets, arr) => widgets.GetNumBatteries()), rowColumnRule, quadrantMajorityRule("There are fewer mostly-white quadrants than mostly-black quadrants ⇒ number of mostly-black quadrants", (b, w, widgets) => w < b, (b, w, widgets, arr) => b), totalCountRule(36, true), quadrantMajorityRule("There are more mostly-white quadrants than mostly-black quadrants ⇒ smallest number of black in any quadrant", (b, w, widgets) => w > b, (b, w, widgets, arr) => 16 - getQuadrantCounts(arr).Max()), quadrantCountRule(false), quadrantMajorityRule("There are as many mostly-black quadrants as there are unlit indicators ⇒ number of ports", (b, w, widgets) => b == widgets.GetNumUnlitIndicators(), (b, w, widgets, arr) => widgets.GetNumPorts()), squareRule, quadrantMajorityRule("There are as many mostly-white quadrants as mostly-black quadrants ⇒ first numeric digit of the serial number", (b, w, widgets) => w == b, (b, w, widgets, arr) => Rnd.Next(0, 10))); var counts = new int[rules.Length, 4]; for (int iter = 0; iter < iterations; iter++) { var edgework = Edgework.Generate(); var bitmap = Ut.NewArray(8, 8, (_, __) => Rnd.Next(0, 2) == 0); var startRule = Rnd.Next(0, rules.Length); var answer = 0; string rule; int ruleIndex; for (int r = 0; r < rules.Length; r++) { ruleIndex = (r + startRule) % rules.Length; var tup = rules[ruleIndex]; answer = tup.Item2(bitmap, edgework); if (answer != 0) { rule = tup.Item1; goto found; } } Console.WriteLine(rules.Select(r => r.Item2(bitmap, edgework)).JoinString(", ")); System.Diagnostics.Debugger.Break(); break; found :; counts[ruleIndex, answer - 1]++; } var tt = new TextTable { ColumnSpacing = 2 }; var ruleCounts = new int[rules.Length]; for (int a = 0; a < 4; a++) { tt.SetCell(a + 1, 0, (a + 1).ToString().Color(ConsoleColor.White), alignment: HorizontalTextAlignment.Right); tt.SetCell(a + 1, rules.Length + 1, ((Enumerable.Range(0, rules.Length).Sum(r => counts[r, a]) * 100 / (double)iterations).ToString("0.0") + "%").Color(ConsoleColor.Green), alignment: HorizontalTextAlignment.Right); } for (int r = 0; r < rules.Length; r++) { tt.SetCell(0, r + 1, rules[r].Item1.Color(ConsoleColor.Cyan).Apply(s => s.ColorSubstring(s.IndexOf('⇒'), ConsoleColor.DarkCyan)), alignment: HorizontalTextAlignment.Right); for (int a = 0; a < 4; a++) { tt.SetCell(a + 1, r + 1, ((counts[r, a] * 100 / (double)iterations).ToString("0.0") + "%").Color(ConsoleColor.Magenta), alignment: HorizontalTextAlignment.Right); } tt.SetCell(5, r + 1, ((Enumerable.Range(0, 4).Sum(a => counts[r, a]) * 100 / (double)iterations).ToString("0.0") + "%").Color(ConsoleColor.White), alignment: HorizontalTextAlignment.Right); ruleCounts[r] += Enumerable.Range(0, 4).Sum(a => counts[r, a]); } tt.WriteToConsole(); Console.WriteLine(); ConsoleUtil.WriteLine("Ratio of most likely to least likely rule: {0/White:0.0}".Color(ConsoleColor.White).Fmt(ruleCounts.Max() / (double)ruleCounts.Min())); }
public static void RunSimulation() { const int iterations = 100000; var portTypes = EnumStrong.GetValues <PortType>(); var portTypeToDir = new int[6]; portTypeToDir[(int)PortType.Parallel] = 0; portTypeToDir[(int)PortType.DVI] = 1; portTypeToDir[(int)PortType.StereoRCA] = 2; portTypeToDir[(int)PortType.Serial] = 3; portTypeToDir[(int)PortType.PS2] = 4; portTypeToDir[(int)PortType.RJ45] = 5; var countConds = new Dictionary <string, int>(); for (int i = 0; i < iterations; i++) { var edgework = Edgework.Generate(5, 7); var counts = Ut.NewArray(edgework.GetNumPortPlates() + 1, j => j == 0 ? portTypes.ToHashSet() : new HashSet <PortType>()); foreach (var port in edgework.Widgets.Where(w => w.PortTypes != null).SelectMany(w => w.PortTypes)) { var ix = counts.IndexOf(c => c.Contains(port)); counts[ix].Remove(port); counts[ix + 1].Add(port); } var hexes = Hex.LargeHexagon(5).Select(hex => { Console.WriteLine(edgework); // Check the port types in order of most common to least common. for (int c = counts.Length - 1; c >= 0; c--) { // Check which of these port types can form a line of 5 var eligiblePortTypes = counts[c].Where(pt => Enumerable.Range(0, 5).All(dist => (hex + dist * Hex.GetDirection(portTypeToDir[(int)pt])).Distance < 5)).Take(2).ToArray(); if (eligiblePortTypes.Length != 1) { continue; } // We found an eligible port type; return the line return(new { Hex = hex, PortType = (PortType?)eligiblePortTypes[0], Line = Enumerable.Range(0, 5).Select(dist => hex + dist * Hex.GetDirection(portTypeToDir[(int)eligiblePortTypes[0]])).ToArray() }); } // Check if the two-step rule works for this hex for (int dir = 0; dir < 6; dir++) { if (Enumerable.Range(0, 5).All(dist => (hex + 2 * dist * Hex.GetDirection(dir)).Distance < 5)) { return new { Hex = hex, PortType = (PortType?)null, Line = Enumerable.Range(0, 5).Select(dist => hex + 2 * dist * Hex.GetDirection(dir)).ToArray() } } } ; return(null); }).ToArray(); } foreach (var kvp in countConds) { Console.WriteLine($"{kvp.Key} = {kvp.Value / (double) iterations * 100:0.00}%"); } }