Example #1
0
        public static void Run(IConfigurationRoot configuration)
        {
            var outputBlueprintFile = configuration["OutputBlueprint"];
            var outputJsonFile      = configuration["OutputJson"];
            var memoryType          = configuration["MemoryType"];

            var generatorType = typeof(MemoryInitializer).Assembly.GetTypes()
                                .FirstOrDefault(type => type.IsAssignableTo(typeof(IBlueprintGenerator)) &&
                                                type.Name.Equals($"{memoryType}Generator", StringComparison.CurrentCultureIgnoreCase));

            if (generatorType == null)
            {
                throw new Exception($"Unsupported memory type: {memoryType}");
            }

            var generator = (IBlueprintGenerator)Activator.CreateInstance(generatorType);
            var blueprint = generator.Generate(configuration);

            BlueprintUtil.PopulateIndices(blueprint);

            var blueprintWrapper = new BlueprintWrapper {
                Blueprint = blueprint
            };

            BlueprintUtil.WriteOutBlueprint(outputBlueprintFile, blueprintWrapper);
            BlueprintUtil.WriteOutJson(outputJsonFile, blueprintWrapper);
        }
        public static void Run(AssemblerConfiguration configuration)
        {
            var inputProgramFile       = configuration.InputProgram;
            var outputBlueprintFile    = configuration.OutputBlueprint;
            var outputJsonFile         = configuration.OutputJson;
            var outputInstructionsFile = configuration.OutputInstructions;
            var snapToGrid             = configuration.SnapToGrid;
            var x      = configuration.X;
            var y      = configuration.Y;
            var width  = configuration.Width;
            var height = configuration.Height;

            using var instructionsWriter = new StreamWriter(outputInstructionsFile);

            var compiledProgram = AssembleCode(inputProgramFile, instructionsWriter);

            if (compiledProgram != null)
            {
                var blueprint = ProgramRomGenerator.CreateBlueprintFromCompiledProgram(compiledProgram, snapToGrid, x, y, width, height, instructionsWriter);
                BlueprintUtil.PopulateIndices(blueprint);

                var blueprintWrapper = new BlueprintWrapper {
                    Blueprint = blueprint
                };

                BlueprintUtil.WriteOutBlueprint(outputBlueprintFile, blueprintWrapper);
                BlueprintUtil.WriteOutJson(outputJsonFile, blueprintWrapper);
            }
        }
        public static void Run(IConfigurationRoot configuration)
        {
            var inputBlueprintFile = configuration["InputBlueprint"];
            var outputJsonFile     = configuration["OutputJson"];

            var json    = BlueprintUtil.ReadBlueprintFileAsJson(inputBlueprintFile);
            var jsonObj = JsonSerializer.Deserialize <object>(json);

            BlueprintUtil.WriteOutJson(outputJsonFile, jsonObj);
        }
Example #4
0
        public static void Run(IConfigurationRoot configuration)
        {
            var inputBlueprintFile    = configuration["InputBlueprint"];
            var outputBlueprintFile   = configuration["OutputBlueprint"];
            var outputJsonFile        = configuration["OutputJson"];
            var outputUpdatedJsonFile = configuration["OutputUpdatedJson"];
            var signalToFind          = configuration["SignalToFind"];
            var replacementSignal     = configuration["ReplacementSignal"];

            var json             = BlueprintUtil.ReadBlueprintFileAsJson(inputBlueprintFile);
            var jsonObj          = JsonSerializer.Deserialize <object>(json);
            var blueprintWrapper = BlueprintUtil.DeserializeBlueprintWrapper(json);

            BlueprintUtil.WriteOutJson(outputJsonFile, jsonObj);

            foreach (var entity in blueprintWrapper.Blueprint.Entities)
            {
                var signals = new List <SignalID>
                {
                    entity.Control_behavior?.Circuit_condition?.First_signal,
                    entity.Control_behavior?.Circuit_condition?.Second_signal,
                    entity.Control_behavior?.Arithmetic_conditions?.First_signal,
                    entity.Control_behavior?.Arithmetic_conditions?.Second_signal,
                    entity.Control_behavior?.Arithmetic_conditions?.Output_signal,
                    entity.Control_behavior?.Decider_conditions?.First_signal,
                    entity.Control_behavior?.Decider_conditions?.Second_signal,
                    entity.Control_behavior?.Decider_conditions?.Output_signal
                }
                .Concat(entity.Control_behavior?.Filters?.Select(filter => filter.Signal) ?? new List <SignalID>())
                .Where(signal => signal != null);

                foreach (var signal in signals)
                {
                    if (signal.Name == signalToFind)
                    {
                        signal.Name = replacementSignal;
                    }
                }
            }

            BlueprintUtil.WriteOutBlueprint(outputBlueprintFile, blueprintWrapper);
            BlueprintUtil.WriteOutJson(outputUpdatedJsonFile, blueprintWrapper);
        }
        public static Blueprint Generate(SpriteMemoryConfiguration configuration)
        {
            var spriteCount = configuration.SpriteCount ?? 16;
            var baseAddress = configuration.BaseAddress ?? 1;

            var rowSignals  = ComputerSignals.OrderedSignals.Take(36).ToList();
            var inputSignal = VirtualSignalNames.LetterOrDigit('0');

            var entities   = new List <Entity>();
            var sprites    = new Sprite[spriteCount];
            var rowFilters = new RowFilter[rowSignals.Count + 1];

            for (var index = 0; index < spriteCount; index++)
            {
                var reader = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = 0.5,
                        Y = index
                    },
                    Direction        = Direction.Left,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Check),
                            Constant              = 0,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(reader);

                var drawSelector = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = 2.5,
                        Y = index
                    },
                    Direction        = Direction.Left,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Dot),
                            Constant              = index + 1,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(drawSelector);

                var memory = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = 4.5,
                        Y = index
                    },
                    Direction        = Direction.Left,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Info),
                            Constant              = 0,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(memory);

                var writer = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = 6.5,
                        Y = index
                    },
                    Direction        = Direction.Left,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Check),
                            Constant              = 0,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(writer);

                var spriteSelector = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = 8.5,
                        Y = index
                    },
                    Direction        = Direction.Left,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Info),
                            Constant              = index + 1,
                            Comparator            = Comparators.IsNotEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Check),
                            Copy_count_from_input = false
                        }
                    }
                };
                entities.Add(spriteSelector);

                sprites[index] = new Sprite
                {
                    Reader         = reader,
                    DrawSelector   = drawSelector,
                    Memory         = memory,
                    Writer         = writer,
                    SpriteSelector = spriteSelector
                };
            }

            for (var index = 0; index < rowFilters.Length; index++)
            {
                var renamer = new Entity
                {
                    Name     = index == 0 ? ItemNames.DeciderCombinator : ItemNames.ArithmeticCombinator,
                    Position = new Position
                    {
                        X = 10.5,
                        Y = index
                    },
                    Direction        = Direction.Left,
                    Control_behavior = index == 0
                        ? new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(inputSignal),
                            Constant              = 1,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Info),
                            Copy_count_from_input = false
                        }
                    }
                        : new ControlBehavior
                    {
                        Arithmetic_conditions = new ArithmeticConditions
                        {
                            First_signal    = SignalID.Create(inputSignal),
                            Second_constant = 1,
                            Operation       = ArithmeticOperations.Multiplication,
                            Output_signal   = SignalID.Create(rowSignals[index - 1])
                        }
                    }
                };
                entities.Add(renamer);

                var addressMatcher = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = 12.5,
                        Y = index
                    },
                    Direction        = Direction.Left,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Check),
                            Constant              = baseAddress + index,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(inputSignal),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(addressMatcher);

                rowFilters[index] = new RowFilter
                {
                    Renamer        = renamer,
                    AddressMatcher = addressMatcher
                };
            }

            BlueprintUtil.PopulateEntityNumbers(entities);

            AddConnection(CircuitColor.Green, sprites[0].Writer, CircuitId.Input, rowFilters[0].Renamer, CircuitId.Output);

            for (var index = 0; index < spriteCount; index++)
            {
                var sprite = sprites[index];

                AddConnection(CircuitColor.Green, sprite.Reader, CircuitId.Input, sprite.DrawSelector, CircuitId.Input);
                AddConnection(CircuitColor.Green, sprite.DrawSelector, CircuitId.Input, sprite.Memory, CircuitId.Output);
                AddConnection(CircuitColor.Green, sprite.Memory, CircuitId.Output, sprite.Memory, CircuitId.Input);
                AddConnection(CircuitColor.Red, sprite.Memory, CircuitId.Input, sprite.Writer, CircuitId.Output);
                AddConnection(CircuitColor.Red, sprite.Reader, CircuitId.Input, sprite.Writer, CircuitId.Input);
                AddConnection(CircuitColor.Red, sprite.Writer, CircuitId.Input, sprite.SpriteSelector, CircuitId.Output);

                if (index > 0)
                {
                    var adjacentSprite = sprites[index - 1];

                    AddConnection(CircuitColor.Green, sprite.Reader, CircuitId.Output, adjacentSprite.Reader, CircuitId.Output);
                    AddConnection(CircuitColor.Green, sprite.DrawSelector, CircuitId.Output, adjacentSprite.DrawSelector, CircuitId.Output);
                    AddConnection(CircuitColor.Red, sprite.DrawSelector, CircuitId.Input, adjacentSprite.DrawSelector, CircuitId.Input);
                    AddConnection(CircuitColor.Green, sprite.Writer, CircuitId.Input, adjacentSprite.Writer, CircuitId.Input);
                    AddConnection(CircuitColor.Green, sprite.SpriteSelector, CircuitId.Input, adjacentSprite.SpriteSelector, CircuitId.Input);
                }
            }

            for (var index = 0; index < rowFilters.Length; index++)
            {
                var rowFilter = rowFilters[index];

                AddConnection(CircuitColor.Green, rowFilter.Renamer, CircuitId.Input, rowFilter.AddressMatcher, CircuitId.Output);

                if (index > 0)
                {
                    var adjacentRowFilter = rowFilters[index - 1];

                    AddConnection(CircuitColor.Green, rowFilter.Renamer, CircuitId.Output, adjacentRowFilter.Renamer, CircuitId.Output);
                    AddConnection(CircuitColor.Green, rowFilter.AddressMatcher, CircuitId.Input, adjacentRowFilter.AddressMatcher, CircuitId.Input);
                    AddConnection(CircuitColor.Red, rowFilter.AddressMatcher, CircuitId.Input, adjacentRowFilter.AddressMatcher, CircuitId.Input);
                }
            }

            return(new Blueprint
            {
                Label = $"Sprite Memory",
                Icons = new List <Icon>
                {
                    Icon.Create(ItemNames.DeciderCombinator),
                    Icon.Create(ItemNames.Lamp)
                },
                Entities = entities,
                Item = ItemNames.Blueprint,
                Version = BlueprintVersions.CurrentVersion
            });
        }
        public static Blueprint Generate(VideoMemoryConfiguration configuration, List <bool[, ]> frames = null)
        {
            var width       = configuration.Width ?? 32;
            var height      = configuration.Height ?? 2;
            var baseAddress = configuration.BaseAddress ?? 1;

            var frameHeight = frames?.ElementAtOrDefault(0)?.GetLength(0) ?? 0;

            const int framesPerRow = 32;
            const int maxFilters   = 20;

            var entities   = new List <Entity>();
            var memoryRows = new MemoryRow[height];

            var metadata = new Entity
            {
                Name     = ItemNames.ConstantCombinator,
                Position = new Position
                {
                    X = 2,
                    Y = 0
                },
                Direction        = Direction.Down,
                Control_behavior = new ControlBehavior
                {
                    Filters = new List <Filter>
                    {
                        Filter.Create(VirtualSignalNames.LetterOrDigit('Z'), frames?.Count ?? 0)
                    }
                }
            };

            entities.Add(metadata);

            for (var row = 0; row < height; row++)
            {
                var cellY     = row * 9 - (row % 2 == 1 ? 1 : 0) + 2;
                var rowFrames = frames?.Skip(row * framesPerRow).Take(framesPerRow).ToList();

                var addressMatcher = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = 1,
                        Y = cellY + 0.5
                    },
                    Direction        = Direction.Down,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Info),
                            Constant              = row + baseAddress,
                            Comparator            = Comparators.IsNotEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Check),
                            Copy_count_from_input = false
                        }
                    }
                };
                entities.Add(addressMatcher);

                var memoryCells = new MemoryCell[width];

                for (var column = 0; column < width; column++)
                {
                    var cellX = column + 2;

                    var enabler = new Entity
                    {
                        Name     = ItemNames.DeciderCombinator,
                        Position = new Position
                        {
                            X = cellX,
                            Y = cellY + 0.5
                        },
                        Direction        = Direction.Up,
                        Control_behavior = new ControlBehavior
                        {
                            Decider_conditions = new DeciderConditions
                            {
                                First_signal          = SignalID.Create(VirtualSignalNames.Check),
                                Constant              = 0,
                                Comparator            = Comparators.IsEqual,
                                Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                                Copy_count_from_input = true
                            }
                        }
                    };
                    entities.Add(enabler);

                    var pixelFilters = Enumerable.Range(0, frameHeight)
                                       .Select(frameRow =>
                    {
                        var pixel = rowFrames
                                    .Select((frame, frameOffset) => frame[frameRow, column] ? 1 << frameOffset : 0)
                                    .Sum();

                        return(Filter.Create(ScreenUtil.PixelSignals[frameRow], pixel));
                    })
                                       .Where(pixelFilter => pixelFilter.Count != 0)
                                       .ToList();

                    var subCellCount = column % 18 >= 16 ? 6 : 7;
                    var subCells     = new Entity[subCellCount];

                    for (var subCellIndex = 0; subCellIndex < subCellCount; subCellIndex++)
                    {
                        var subCell = new Entity
                        {
                            Name     = ItemNames.ConstantCombinator,
                            Position = new Position
                            {
                                X = cellX,
                                Y = subCellIndex < 6 || row % 2 == 1 ? cellY + subCellIndex + 2 : cellY - 1
                            },
                            Direction        = Direction.Down,
                            Control_behavior = new ControlBehavior
                            {
                                Filters = pixelFilters.Skip(subCellIndex * maxFilters).Take(maxFilters).ToList()
                            }
                        };
                        entities.Add(subCell);
                        subCells[subCellIndex] = subCell;
                    }

                    memoryCells[column] = new MemoryCell
                    {
                        Enabler  = enabler,
                        SubCells = subCells
                    };
                }

                memoryRows[row] = new MemoryRow
                {
                    AddressMatcher = addressMatcher,
                    Cells          = memoryCells
                };
            }

            BlueprintUtil.PopulateEntityNumbers(entities);

            var substationWidth  = (width + 9) / 18 + 1;
            var substationHeight = height / 2 + 1;

            entities.AddRange(CreateSubstations(substationWidth, substationHeight, 0, 0, entities.Count + 1));

            for (var row = 0; row < height; row++)
            {
                var memoryRow = memoryRows[row];

                AddConnection(CircuitColor.Red, memoryRow.AddressMatcher, CircuitId.Output, memoryRow.Cells[0].Enabler, CircuitId.Input);

                var adjacentRow = row - 1;
                if (adjacentRow >= 0)
                {
                    var adjacentMemoryRow = memoryRows[adjacentRow];

                    AddConnection(CircuitColor.Green, memoryRow.AddressMatcher, CircuitId.Input, adjacentMemoryRow.AddressMatcher, CircuitId.Input);

                    for (var column = 0; column < width; column++)
                    {
                        var memoryCell         = memoryRow.Cells[column];
                        var adjacentMemoryCell = adjacentMemoryRow.Cells[column];

                        AddConnection(CircuitColor.Green, memoryCell.Enabler, CircuitId.Output, adjacentMemoryCell.Enabler, CircuitId.Output);
                    }
                }

                for (var column = 0; column < width; column++)
                {
                    var memoryCell = memoryRow.Cells[column];

                    AddConnection(CircuitColor.Green, memoryCell.SubCells[0], null, memoryCell.Enabler, CircuitId.Input);

                    for (var subCellIndex = 1; subCellIndex < memoryCell.SubCells.Length; subCellIndex++)
                    {
                        var subCell         = memoryCell.SubCells[subCellIndex];
                        var adjacentSubCell = memoryCell.SubCells[subCellIndex < 6 || row % 2 == 1 ? subCellIndex - 1 : 0];

                        AddConnection(CircuitColor.Green, subCell, null, adjacentSubCell, null);
                    }

                    var adjacentColumn = column - 1;
                    if (adjacentColumn >= 0)
                    {
                        var adjacentMemoryCell = memoryRow.Cells[adjacentColumn];

                        AddConnection(CircuitColor.Red, memoryCell.Enabler, CircuitId.Input, adjacentMemoryCell.Enabler, CircuitId.Input);
                    }
                }
            }

            return(new Blueprint
            {
                Label = $"Video ROM",
                Icons = new List <Icon>
                {
                    Icon.Create(ItemNames.Lamp),
                    Icon.Create(ItemNames.ConstantCombinator)
                },
                Entities = entities,
                Item = ItemNames.Blueprint,
                Version = BlueprintVersions.CurrentVersion
            });
        }
        public static void Run(VideoConfiguration configuration)
        {
            var videoFile           = configuration.VideoFile;
            var outputBlueprintFile = configuration.OutputBlueprint;
            var outputJsonFile      = configuration.OutputJson;
            var frameWidth          = configuration.FrameWidth ?? 32;
            var frameHeight         = configuration.FrameHeight ?? 32;
            var colorMode           = configuration.ColorMode ?? ColorMode.Monochrome;
            var ditheringMode       = configuration.DitheringMode ?? DitheringMode.None;
            var romHeight           = configuration.RomHeight ?? 2;

            var maxFrames = romHeight * 32;

            var videoBuffer = new MemoryStream();

            using (var videoStream = File.OpenRead(videoFile))
            {
                videoStream.CopyTo(videoBuffer);
            }

            var pixelSize = colorMode switch
            {
                ColorMode.Monochrome => 1,
                ColorMode.RedGreenBlue => 2,
                ColorMode.RedGreenBlueWhite => 2,
                _ => throw new Exception($"Unexpected color mode: {colorMode}")
            };

            var palette = colorMode switch
            {
                ColorMode.Monochrome => GeneratePalette(new HdrColor[]
                {
                    HdrColor.FromRgb(1, 1, 1)
                }),
                ColorMode.RedGreenBlue => GeneratePalette(new HdrColor[]
                {
                    HdrColor.FromRgb(1, 0, 0),
                    HdrColor.FromRgb(0, 1, 0),
                    HdrColor.FromRgb(0, 0, 1)
                }),
                ColorMode.RedGreenBlueWhite => GeneratePalette(new HdrColor[]
                {
                    HdrColor.FromRgb(0.8, 0, 0),
                    HdrColor.FromRgb(0, 0.8, 0),
                    HdrColor.FromRgb(0, 0, 0.8),
                    HdrColor.FromRgb(0.8, 0.8, 0.8)
                }),
                _ => throw new Exception($"Unexpected color mode: {colorMode}")
            };

            // Information on dithering: https://cmitja.files.wordpress.com/2015/01/hellandtanner_imagedithering11algorithms.pdf
            var ditheringWeights = ditheringMode switch
            {
                DitheringMode.None => Array.Empty <DitheringWeight>(),
                DitheringMode.Sierra => new DitheringWeight[]
                {
                    new DitheringWeight(5 / 32d, 1, 0),
                    new DitheringWeight(3 / 32d, 2, 0),
                    new DitheringWeight(2 / 32d, -2, 1),
                    new DitheringWeight(4 / 32d, -1, 1),
                    new DitheringWeight(5 / 32d, 0, 1),
                    new DitheringWeight(4 / 32d, 1, 1),
                    new DitheringWeight(2 / 32d, 2, 1),
                    new DitheringWeight(2 / 32d, -1, 2),
                    new DitheringWeight(3 / 32d, 0, 2),
                    new DitheringWeight(2 / 32d, 1, 2),
                },
                DitheringMode.SierraLite => new DitheringWeight[]
                {
                    new DitheringWeight(2 / 4d, 1, 0),
                    new DitheringWeight(1 / 4d, -1, 1),
                    new DitheringWeight(1 / 4d, 0, 1)
                },
                DitheringMode.Temporal => new DitheringWeight[]
                {
                    new DitheringWeight(2 / 6d, 1, 0, 0),
                    new DitheringWeight(1 / 6d, -1, 1, 0),
                    new DitheringWeight(1 / 6d, 0, 1, 0),
                    new DitheringWeight(2 / 6d, 0, 0, 1)
                },
                _ => throw new Exception($"Unexpected dithering mode: {ditheringMode}")
            };

            var rawFrameWidth  = frameWidth / pixelSize;
            var rawFrameHeight = frameHeight / pixelSize;

            var video    = new Video(videoBuffer, rawFrameWidth, rawFrameHeight);
            var rawFrame = new int[rawFrameWidth * rawFrameHeight];
            var frames   = new List <bool[, ]>();

            var ditheringFrames = ditheringWeights.Max(ditheringWeight => ditheringWeight.Frame) + 1;
            var colorErrors     = new HdrColor[ditheringFrames][, ];

            for (var index = 0; index < ditheringFrames; index++)
            {
                colorErrors[index] = new HdrColor[frameHeight, frameWidth];
            }

            while (video.AdvanceFrame(rawFrame) && frames.Count < maxFrames)
            {
                var frame = new bool[frameHeight, frameWidth];
                frames.Add(frame);

                var totalBrightness = 0d;

                for (var rawY = 0; rawY < rawFrameHeight; rawY++)
                {
                    for (var rawX = 0; rawX < rawFrameWidth; rawX++)
                    {
                        var color = System.Drawing.Color.FromArgb(rawFrame[rawX + rawY * rawFrameWidth]);
                        totalBrightness += color.GetBrightness();
                    }
                }

                var averageBrightness = totalBrightness / (rawFrameWidth * rawFrameHeight);

                const double gammaMultiplier = 2;
                var          gamma           = gammaMultiplier * (averageBrightness is > 0.1 and < 0.9 ? Math.Log(0.5, averageBrightness) : 1);

                for (var rawY = 0; rawY < rawFrameHeight; rawY++)
                {
                    for (var rawX = 0; rawX < rawFrameWidth; rawX++)
                    {
                        var x                   = rawX * pixelSize;
                        var y                   = rawY * pixelSize;
                        var rawColor            = HdrColor.FromArgb(rawFrame[rawX + rawY * rawFrameWidth]);
                        var gammaCorrectedColor = rawColor.Pow(gamma);
                        var ditheredColor       = gammaCorrectedColor + colorErrors[0][rawY, rawX];
                        var closestPaletteEntry = GetClosestPaletteEntry(palette, ditheredColor);
                        var newColorError       = ditheredColor - closestPaletteEntry.Color;
                        var outputColor         = closestPaletteEntry.OutputColor;

                        for (var subPixelY = 0; subPixelY < pixelSize; subPixelY++)
                        {
                            for (var subPixelX = 0; subPixelX < pixelSize; subPixelX++)
                            {
                                var subPixelIndex = subPixelX + subPixelY * pixelSize;

                                if (subPixelIndex < outputColor.Length)
                                {
                                    frame[y + subPixelY, x + subPixelX] = outputColor[subPixelIndex];
                                }
                            }
                        }

                        for (var index = 0; index < ditheringWeights.Length; index++)
                        {
                            var ditheringWeight = ditheringWeights[index];
                            var errorX          = rawX + ditheringWeight.X;
                            var errorY          = rawY + ditheringWeight.Y;

                            if (errorX >= 0 && errorX < frameWidth && errorY < frameHeight)
                            {
                                colorErrors[ditheringWeight.Frame][errorY, errorX] += newColorError * ditheringWeight.Weight;
                            }
                        }
                    }
                }

                for (var index = 0; index < ditheringFrames - 1; index++)
                {
                    colorErrors[index] = colorErrors[index + 1];
                }

                colorErrors[ditheringFrames - 1] = new HdrColor[frameHeight, frameWidth];

                Console.Write('.');
            }

            Console.WriteLine($"Frames: {frames.Count}");

            var blueprint = VideoRomGenerator.Generate(new VideoMemoryConfiguration
            {
                SnapToGrid  = configuration.SnapToGrid,
                X           = configuration.X,
                Y           = configuration.Y,
                Width       = frameWidth,
                Height      = romHeight,
                BaseAddress = configuration.BaseAddress
            }, frames);

            BlueprintUtil.PopulateIndices(blueprint);

            var blueprintWrapper = new BlueprintWrapper {
                Blueprint = blueprint
            };

            BlueprintUtil.WriteOutBlueprint(outputBlueprintFile, blueprintWrapper);
            BlueprintUtil.WriteOutJson(outputJsonFile, blueprintWrapper);
        }
        public static Blueprint Generate(ScreenConfiguration configuration)
        {
            var width  = configuration.Width ?? 18;
            var height = configuration.Height ?? 18;

            const int cycle         = 2;
            const int parallelCycle = 32;

            var entities          = new List <Entity>();
            var pixels            = new Entity[height, width];
            var columnControllers = new Controller[width];
            var rowControllers    = new Controller[height];

            // Pixels
            for (var row = 0; row < height; row++)
            {
                for (var column = 0; column < width; column++)
                {
                    var relativeRow    = row % 18;
                    var relativeColumn = column % 18;

                    // Don't place lights that intersect the substations
                    if (relativeRow > 15 && relativeColumn > 15)
                    {
                        continue;
                    }

                    var pixel = new Entity
                    {
                        Name     = ItemNames.Lamp,
                        Position = new Position
                        {
                            X = column + 2,
                            Y = row + 2
                        },
                        Control_behavior = new ControlBehavior
                        {
                            Circuit_condition = new CircuitCondition
                            {
                                First_signal = SignalID.Create(ScreenUtil.PixelSignals[row]),
                                Comparator   = Comparators.GreaterThan,
                                Constant     = 0
                            },
                            Use_colors = true
                        }
                    };
                    pixels[row, column] = pixel;
                    entities.Add(pixel);
                }
            }

            // Column controllers
            for (var column = 0; column < width; column++)
            {
                var controllerX = column + 2;
                var controllerY = height + 4;

                var memory = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 0.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Each),
                            Constant              = 0,
                            Comparator            = Comparators.GreaterThan,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Each),
                            Copy_count_from_input = false
                        }
                    }
                };
                entities.Add(memory);

                var writer = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 2.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Check),
                            Constant              = 0,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(writer);

                var addressMatcher = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 4.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Info),
                            Constant              = column + 1,
                            Comparator            = Comparators.IsNotEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Check),
                            Copy_count_from_input = false
                        }
                    }
                };
                entities.Add(addressMatcher);

                var cyclicWriter = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 6.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Check),
                            Constant              = 0,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(cyclicWriter);

                var cyclicMatcher = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 8.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Info),
                            Constant              = column % cycle + 1,
                            Comparator            = Comparators.IsNotEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Check),
                            Copy_count_from_input = false
                        }
                    }
                };
                entities.Add(cyclicMatcher);

                var parallelWriter = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 14.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Check),
                            Constant              = 0,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(parallelWriter);

                var parallelAddressRangeLow = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 10.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Info),
                            Constant              = column + 1,
                            Comparator            = Comparators.LessThan,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Check),
                            Copy_count_from_input = false
                        }
                    }
                };
                entities.Add(parallelAddressRangeLow);

                var parallelAddressRangeHigh = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 12.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Info),
                            Constant              = column + parallelCycle + 1,
                            Comparator            = Comparators.GreaterThanOrEqualTo,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Check),
                            Copy_count_from_input = false
                        }
                    }
                };
                entities.Add(parallelAddressRangeHigh);

                var isOdd = column % 2 == 1;

                var parallelHorizontalLink1 = new Entity
                {
                    Name     = column % 18 == 16 ? ItemNames.Substation : ItemNames.BigElectricPole,
                    Position = new Position
                    {
                        X = controllerX + 0.5,
                        Y = controllerY + 16.5 + (isOdd ? 2 : 0)
                    }
                };
                entities.Add(parallelHorizontalLink1);

                var parallelHorizontalLink2 = new Entity
                {
                    Name     = ItemNames.BigElectricPole,
                    Position = new Position
                    {
                        X = controllerX + 0.5,
                        Y = controllerY + 20.5 + (isOdd ? 2 : 0)
                    }
                };
                entities.Add(parallelHorizontalLink2);

                var videoEnabler = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 24.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Check),
                            Constant              = 0,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(videoEnabler);

                var videoReferenceSignalSubtractor = new Entity
                {
                    Name     = ItemNames.ArithmeticCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 26.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Arithmetic_conditions = new ArithmeticConditions
                        {
                            First_signal    = SignalID.Create(VirtualSignalNames.Each),
                            Second_constant = -3,
                            Operation       = ArithmeticOperations.Multiplication,
                            Output_signal   = SignalID.Create(VirtualSignalNames.Each)
                        }
                    }
                };
                entities.Add(videoReferenceSignalSubtractor);

                var videoValueSpreader = new Entity
                {
                    Name     = ItemNames.ArithmeticCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 28.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Arithmetic_conditions = new ArithmeticConditions
                        {
                            First_signal    = SignalID.Create(VirtualSignalNames.Each),
                            Second_constant = 2,
                            Operation       = ArithmeticOperations.Multiplication,
                            Output_signal   = SignalID.Create(VirtualSignalNames.Each)
                        }
                    }
                };
                entities.Add(videoValueSpreader);

                var videoBitIsolator = new Entity
                {
                    Name     = ItemNames.ArithmeticCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 30.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Arithmetic_conditions = new ArithmeticConditions
                        {
                            First_signal    = SignalID.Create(VirtualSignalNames.Each),
                            Second_constant = 1,
                            Operation       = ArithmeticOperations.And,
                            Output_signal   = SignalID.Create(VirtualSignalNames.Each)
                        }
                    }
                };
                entities.Add(videoBitIsolator);

                var videoBitShifter = new Entity
                {
                    Name     = ItemNames.ArithmeticCombinator,
                    Position = new Position
                    {
                        X = controllerX,
                        Y = controllerY + 32.5
                    },
                    Direction        = Direction.Up,
                    Control_behavior = new ControlBehavior
                    {
                        Arithmetic_conditions = new ArithmeticConditions
                        {
                            First_signal  = SignalID.Create(VirtualSignalNames.Each),
                            Second_signal = SignalID.CreateLetterOrDigit('W'),
                            Operation     = ArithmeticOperations.RightShift,
                            Output_signal = SignalID.Create(VirtualSignalNames.Each)
                        }
                    }
                };
                entities.Add(videoBitShifter);

                columnControllers[column] = new Controller
                {
                    Memory         = memory,
                    Writer         = writer,
                    AddressMatcher = addressMatcher,
                    Cyclic         = new CyclicInput
                    {
                        Writer  = cyclicWriter,
                        Matcher = cyclicMatcher
                    },
                    Parallel = new ParallelInput
                    {
                        Writer           = parallelWriter,
                        AddressRangeLow  = parallelAddressRangeLow,
                        AddressRangeHigh = parallelAddressRangeHigh,
                        HorizontalLink1  = parallelHorizontalLink1,
                        HorizontalLink2  = parallelHorizontalLink2
                    },
                    Video = new VideoInput
                    {
                        Enabler = videoEnabler,
                        ReferenceSignalSubtractor = videoReferenceSignalSubtractor,
                        ValueSpreader             = videoValueSpreader,
                        BitIsolator = videoBitIsolator,
                        BitShifter  = videoBitShifter
                    }
                };
            }

            // Row controllers
            for (var row = 0; row < height; row++)
            {
                var controllerY = row + 2;

                var memory = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = -1.5,
                        Y = controllerY
                    },
                    Direction        = Direction.Right,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Each),
                            Constant              = 0,
                            Comparator            = Comparators.LessThan,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Each),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(memory);

                var writer = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = -3.5,
                        Y = controllerY
                    },
                    Direction        = Direction.Right,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Check),
                            Constant              = 0,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(writer);

                var addressMatcher = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = -5.5,
                        Y = controllerY
                    },
                    Direction        = Direction.Right,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Info),
                            Constant              = row + 1,
                            Comparator            = Comparators.IsNotEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Check),
                            Copy_count_from_input = false
                        }
                    }
                };
                entities.Add(addressMatcher);

                var cyclicWriter = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = -7.5,
                        Y = controllerY
                    },
                    Direction        = Direction.Right,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Check),
                            Constant              = 0,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(cyclicWriter);

                var cyclicMatcher = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = -9.5,
                        Y = controllerY
                    },
                    Direction        = Direction.Right,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(VirtualSignalNames.Info),
                            Constant              = row % cycle + 1,
                            Comparator            = Comparators.IsNotEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Check),
                            Copy_count_from_input = false
                        }
                    }
                };
                entities.Add(cyclicMatcher);

                rowControllers[row] = new Controller
                {
                    Memory         = memory,
                    Writer         = writer,
                    AddressMatcher = addressMatcher,
                    Cyclic         = new CyclicInput
                    {
                        Writer  = cyclicWriter,
                        Matcher = cyclicMatcher
                    }
                };
            }

            BlueprintUtil.PopulateEntityNumbers(entities);

            var substationWidth  = (width + 8) / 18 + 1;
            var substationHeight = (height + 8) / 18 + 1;
            var substations      = CreateSubstations(substationWidth, substationHeight, 0, 0, entities.Count + 1, GridConnectivity.Top | GridConnectivity.Vertical);

            entities.AddRange(substations);
            var substations2 = CreateSubstations(substationWidth, 1, 0, height + 38, entities.Count + 1);

            entities.AddRange(substations2);

            // Pixel connections
            for (var row = 0; row < height; row++)
            {
                for (var column = 0; column < width; column++)
                {
                    var pixel = pixels[row, column];
                    if (pixel == null)
                    {
                        continue;
                    }
        public static void Run(VideoConfiguration configuration)
        {
            var videoFile           = configuration.VideoFile;
            var outputBlueprintFile = configuration.OutputBlueprint;
            var outputJsonFile      = configuration.OutputJson;
            var frameWidth          = configuration.FrameWidth ?? 32;
            var frameHeight         = configuration.FrameHeight ?? 32;
            var colorMode           = configuration.ColorMode ?? ColorMode.Monochrome;
            var ditherSize          = configuration.DitherSize ?? 1;
            var useEdgeDetection    = configuration.UseEdgeDetection ?? false;
            var romHeight           = configuration.RomHeight ?? 2;

            var maxFrames = romHeight * 32;

            var videoBuffer = new MemoryStream();

            using (var videoStream = File.OpenRead(videoFile))
            {
                videoStream.CopyTo(videoBuffer);
            }

            var basePixelSize = colorMode switch
            {
                ColorMode.Monochrome => 1,
                ColorMode.RedGreenBlue => 2,
                ColorMode.RedGreenBlueWhite => 2,
                _ => throw new Exception($"Unexpected color mode: {colorMode}")
            };
            var pixelSize = basePixelSize * ditherSize;

            var rawFrameWidth  = frameWidth / pixelSize;
            var rawFrameHeight = frameHeight / pixelSize;

            var video    = new Video(videoBuffer, rawFrameWidth, rawFrameHeight);
            var rawFrame = new int[rawFrameWidth * rawFrameHeight];
            var frames   = new List <bool[, ]>();

            while (video.AdvanceFrame(rawFrame) && frames.Count < maxFrames)
            {
                var frame = new bool[frameHeight, frameWidth];
                frames.Add(frame);

                var brightnessCutoff = 0d;

                if (ditherSize == 1)
                {
                    var totalBrightness = 0d;

                    for (var rawY = 0; rawY < rawFrameHeight; rawY++)
                    {
                        for (var rawX = 0; rawX < rawFrameWidth; rawX++)
                        {
                            var color = System.Drawing.Color.FromArgb(rawFrame[rawX + rawY * rawFrameWidth]);
                            totalBrightness += color.GetBrightness();
                        }
                    }

                    brightnessCutoff = totalBrightness / (rawFrameWidth * rawFrameHeight);
                }

                for (var rawY = 0; rawY < rawFrameHeight; rawY++)
                {
                    for (var rawX = 0; rawX < rawFrameWidth; rawX++)
                    {
                        var x      = rawX * pixelSize;
                        var y      = rawY * pixelSize;
                        var color  = SysColor.FromArgb(rawFrame[rawX + rawY * rawFrameWidth]);
                        var levels = ditherSize * ditherSize + 1;

                        switch (colorMode)
                        {
                        case ColorMode.Monochrome:
                        {
                            var brightness = color.GetBrightness();

                            for (var ditherY = 0; ditherY < ditherSize; ditherY++)
                            {
                                for (var ditherX = 0; ditherX < ditherSize; ditherX++)
                                {
                                    var currentBrightnessCutoff = ditherSize == 1 ? brightnessCutoff : (double)(ditherX + ditherY * ditherSize + 1) / levels;
                                    frame[y + ditherY, x + ditherX] = brightness > currentBrightnessCutoff;
                                }
                            }
                        }

                        break;

                        case ColorMode.RedGreenBlue:
                            for (var ditherY = 0; ditherY < ditherSize; ditherY++)
                            {
                                for (var ditherX = 0; ditherX < ditherSize; ditherX++)
                                {
                                    var currentBrightnessCutoff = ditherSize == 1 ? brightnessCutoff : (double)(ditherX + ditherY * ditherSize + 1) / levels;
                                    var cutoff = (byte)(currentBrightnessCutoff * 255);
                                    var red    = color.R >= cutoff;
                                    var green  = color.G >= cutoff;
                                    var blue   = color.B >= cutoff;
                                    var baseX  = x + ditherX * 2;
                                    var baseY  = y + ditherY * 2;

                                    frame[baseY, baseX]     = red;
                                    frame[baseY, baseX + 1] = green;
                                    frame[baseY + 1, baseX] = blue;
                                }
                            }

                            break;

                        case ColorMode.RedGreenBlueWhite:
                            for (var ditherY = 0; ditherY < ditherSize; ditherY++)
                            {
                                for (var ditherX = 0; ditherX < ditherSize; ditherX++)
                                {
                                    var currentBrightnessCutoff = ditherSize == 1 ? brightnessCutoff : (double)(ditherX + ditherY * ditherSize + 1) / levels;
                                    var cutoff = (byte)(currentBrightnessCutoff * 255);
                                    var red    = color.R >= cutoff;
                                    var green  = color.G >= cutoff;
                                    var blue   = color.B >= cutoff;

                                    bool white = useEdgeDetection
                                            ? DetectEdge(color, rawFrame, rawX, rawY, rawFrameWidth, -1, 0) ||
                                                 DetectEdge(color, rawFrame, rawX, rawY, rawFrameWidth, 0, -1) ||
                                                 DetectEdge(color, rawFrame, rawX, rawY, rawFrameWidth, -1, -1)
                                            : red && green && blue && color.GetBrightness() >= (currentBrightnessCutoff + 1) / 2;

                                    var baseX = x + ditherX * 2;
                                    var baseY = y + ditherY * 2;

                                    frame[baseY, baseX]         = red;
                                    frame[baseY, baseX + 1]     = green;
                                    frame[baseY + 1, baseX]     = blue;
                                    frame[baseY + 1, baseX + 1] = white;
                                }
                            }

                            break;
                        }
                    }
                }
            }

            Console.WriteLine($"Frames: {frames.Count}");

            var blueprint = VideoRomGenerator.Generate(new VideoMemoryConfiguration
            {
                SnapToGrid  = configuration.SnapToGrid,
                X           = configuration.X,
                Y           = configuration.Y,
                Width       = frameWidth,
                Height      = romHeight,
                BaseAddress = configuration.BaseAddress
            }, frames);

            BlueprintUtil.PopulateIndices(blueprint);

            var blueprintWrapper = new BlueprintWrapper {
                Blueprint = blueprint
            };

            BlueprintUtil.WriteOutBlueprint(outputBlueprintFile, blueprintWrapper);
            BlueprintUtil.WriteOutJson(outputJsonFile, blueprintWrapper);
        }
        public static Blueprint Generate(FontConfiguration configuration)
        {
            var fontImageFile      = configuration.FontImage;
            var combinatorsPerRow  = configuration.CombinatorsPerRow ?? 5;
            var useOneSignalPerRow = configuration.UseOneSignalPerRow ?? false;
            var inputSignal        = configuration.InputSignal ?? VirtualSignalNames.Dot;
            var widthSignal        = configuration.WidthSignal;
            var heightSignal       = configuration.HeightSignal;
            var signals            = configuration.Signals.Contains(',') ? configuration.Signals.Split(',').ToList() : configuration.Signals.Select(signal => VirtualSignalNames.LetterOrDigit(signal)).ToList();

            const int maxFilters = 20;

            var font       = FontUtil.ReadFont(fontImageFile);
            var characters = font.Characters;

            var entities          = new List <Entity>();
            var characterEntities = new List <(Entity Matcher, List <Entity> Glyph)>();

            if (heightSignal != null)
            {
                characters.Add(new FontUtil.Character
                {
                    CharacterCode = '\n',
                    GlyphPixels   = new bool[font.Height, 0]
                });
            }

            var combinatorX = 0;

            for (var characterIndex = 0; characterIndex < characters.Count; characterIndex++)
            {
                var character   = characters[characterIndex];
                var glyphPixels = character.GlyphPixels;
                var height      = glyphPixels.GetLength(0);
                var width       = glyphPixels.GetLength(1);

                if (characterIndex % combinatorsPerRow == 0)
                {
                    combinatorX = 0;
                }

                var glyphFilters = new List <Filter>();

                if (widthSignal != null)
                {
                    glyphFilters.Add(Filter.Create(widthSignal, width));
                }

                if (heightSignal != null)
                {
                    glyphFilters.Add(Filter.Create(heightSignal, height));
                }

                for (int y = 0; y < height; y++)
                {
                    if (useOneSignalPerRow)
                    {
                        var rowSignal = 0;

                        for (int x = 0; x < width; x++)
                        {
                            if (glyphPixels[y, x])
                            {
                                rowSignal |= 1 << x;
                            }
                        }

                        glyphFilters.Add(Filter.Create(signals[y], rowSignal));
                    }
                    else
                    {
                        for (int x = 0; x < width; x++)
                        {
                            if (glyphPixels[y, x])
                            {
                                glyphFilters.Add(Filter.Create(signals[y * width + x]));
                            }
                        }
                    }
                }

                var combinatorY = characterIndex / combinatorsPerRow;

                var matcher = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = (characterIndex % combinatorsPerRow - combinatorsPerRow) * 2 + 0.5,
                        Y = combinatorY
                    },
                    Direction        = Direction.Left,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(inputSignal),
                            Constant              = character.CharacterCode,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Everything),
                            Copy_count_from_input = true
                        }
                    }
                };
                entities.Add(matcher);

                var glyph = new List <Entity>();

                for (int index = 0; index < (glyphFilters.Count + maxFilters - 1) / maxFilters; index++)
                {
                    var glyphPart = new Entity
                    {
                        Name     = ItemNames.ConstantCombinator,
                        Position = new Position
                        {
                            X = combinatorX++,
                            Y = combinatorY
                        },
                        Direction        = Direction.Down,
                        Control_behavior = new ControlBehavior
                        {
                            Filters = glyphFilters.Skip(index * maxFilters).Take(maxFilters).ToList()
                        }
                    };
                    glyph.Add(glyphPart);
                    entities.Add(glyphPart);
                }

                characterEntities.Add((matcher, glyph));
            }

            BlueprintUtil.PopulateEntityNumbers(entities);

            for (int characterIndex = 0; characterIndex < characterEntities.Count; characterIndex++)
            {
                var(matcher, glyph) = characterEntities[characterIndex];

                AddConnection(CircuitColor.Green, matcher, CircuitId.Input, glyph[0], null); // Connect to constant combinators holding glyphs

                var adjacentCharacterIndex = characterIndex - (characterIndex / combinatorsPerRow == 0 ? 1 : combinatorsPerRow);
                if (adjacentCharacterIndex >= 0)
                {
                    var adjacentMatcher = characterEntities[adjacentCharacterIndex].Matcher;

                    AddConnection(CircuitColor.Red, matcher, CircuitId.Input, adjacentMatcher, CircuitId.Input);     // Connect inputs together
                    AddConnection(CircuitColor.Green, matcher, CircuitId.Output, adjacentMatcher, CircuitId.Output); // Connect outputs together
                }

                // Connections between glyph parts
                for (int index = 1; index < glyph.Count; index++)
                {
                    AddConnection(CircuitColor.Green, glyph[index], null, glyph[index - 1], null);
                }
            }

            return(new Blueprint
            {
                Label = $"{font.Width}x{font.Height} Font",
                Icons = new List <Icon>
                {
                    Icon.Create(ItemNames.ConstantCombinator),
                    Icon.Create(VirtualSignalNames.LetterOrDigit('A')),
                    Icon.Create(VirtualSignalNames.LetterOrDigit('B')),
                    Icon.Create(VirtualSignalNames.LetterOrDigit('C'))
                },
                Entities = entities,
                Item = ItemNames.Blueprint,
                Version = BlueprintVersions.CurrentVersion
            });
        }
Example #11
0
        public static Blueprint Generate(PixelSignalsConfiguration configuration)
        {
            var signalCount = configuration.SignalCount ?? ScreenUtil.PixelSignals.Count;

            const int maxFilters = 20;

            var entities        = new List <Entity>();
            var signalConstants = new Entity[(signalCount + maxFilters - 1) / maxFilters];

            // Signal constants
            for (var index = 0; index < signalConstants.Length; index++)
            {
                var outputSignalMap = new Entity
                {
                    Name     = ItemNames.ConstantCombinator,
                    Position = new Position
                    {
                        X = index + 1,
                        Y = 0
                    },
                    Direction        = Direction.Right,
                    Control_behavior = new ControlBehavior
                    {
                        Filters = ScreenUtil.PixelSignals.Skip(index * maxFilters).Take(Math.Min(maxFilters, signalCount - index * maxFilters)).Select((signal, signalIndex) => new Filter
                        {
                            Signal = SignalID.Create(signal),
                            Count  = 1
                        }).ToList()
                    }
                };
                signalConstants[index] = outputSignalMap;
                entities.Add(outputSignalMap);
            }

            BlueprintUtil.PopulateEntityNumbers(entities);

            // Signal constant connections
            for (var index = 1; index < signalConstants.Length; index++)
            {
                var outputSignalMap         = signalConstants[index];
                var adjacentOutputSignalMap = signalConstants[index - 1];

                AddConnection(CircuitColor.Red, outputSignalMap, null, adjacentOutputSignalMap, null);
            }

            return(new Blueprint
            {
                Label = $"Pixel signals",
                Icons = new List <Icon>
                {
                    new Icon
                    {
                        Signal = SignalID.Create(ItemNames.Lamp)
                    },
                    new Icon
                    {
                        Signal = SignalID.Create(ItemNames.ConstantCombinator)
                    }
                },
                Entities = entities,
                Item = ItemNames.Blueprint,
                Version = BlueprintVersions.CurrentVersion
            });
        }
        public static Blueprint Generate(DemuxConfiguration configuration)
        {
            var signalCount   = configuration.SignalCount ?? ComputerSignals.OrderedSignals.Count;
            var width         = configuration.Width ?? 1;
            var addressSignal = configuration.AddressSignal ?? VirtualSignalNames.Dot;
            var outputSignal  = configuration.OutputSignal ?? VirtualSignalNames.LetterOrDigit('A');

            var entities      = new List <Entity>();
            var signalFilters = new SignalFilter[signalCount];

            for (int index = 0; index < signalCount; index++)
            {
                var filterX = index % width * 4;
                var filterY = index / width;

                var addressChecker = new Entity
                {
                    Name     = ItemNames.DeciderCombinator,
                    Position = new Position
                    {
                        X = 0.5 + filterX,
                        Y = filterY
                    },
                    Direction        = Direction.Right,
                    Control_behavior = new ControlBehavior
                    {
                        Decider_conditions = new DeciderConditions
                        {
                            First_signal          = SignalID.Create(addressSignal),
                            Constant              = index + 1,
                            Comparator            = Comparators.IsEqual,
                            Output_signal         = SignalID.Create(VirtualSignalNames.Dot),
                            Copy_count_from_input = false
                        }
                    }
                };
                entities.Add(addressChecker);

                var signalRenamer = new Entity
                {
                    Name     = ItemNames.ArithmeticCombinator,
                    Position = new Position
                    {
                        X = 2.5 + filterX,
                        Y = filterY
                    },
                    Direction        = Direction.Right,
                    Control_behavior = new ControlBehavior
                    {
                        Arithmetic_conditions = new ArithmeticConditions
                        {
                            First_signal  = SignalID.Create(ComputerSignals.OrderedSignals[index]),
                            Second_signal = SignalID.Create(VirtualSignalNames.Dot),
                            Operation     = ArithmeticOperations.Multiplication,
                            Output_signal = SignalID.Create(outputSignal)
                        }
                    }
                };
                entities.Add(signalRenamer);

                signalFilters[index] = new SignalFilter
                {
                    AddressChecker = addressChecker,
                    SignalRenamer  = signalRenamer
                };
            }

            BlueprintUtil.PopulateEntityNumbers(entities);

            for (var index = 0; index < signalCount; index++)
            {
                var signalFilter = signalFilters[index];

                AddConnection(CircuitColor.Red, signalFilter.AddressChecker, CircuitId.Output, signalFilter.SignalRenamer, CircuitId.Input);

                if (index > 0)
                {
                    var adjacentSignalFilterIndex = index / width == 0 ? index - 1 : index - width;
                    var adjacentSignalFilter      = signalFilters[adjacentSignalFilterIndex];

                    AddConnection(CircuitColor.Red, signalFilter.AddressChecker, CircuitId.Input, adjacentSignalFilter.AddressChecker, CircuitId.Input);
                    AddConnection(CircuitColor.Green, signalFilter.SignalRenamer, CircuitId.Input, adjacentSignalFilter.SignalRenamer, CircuitId.Input);
                    AddConnection(CircuitColor.Red, signalFilter.SignalRenamer, CircuitId.Output, adjacentSignalFilter.SignalRenamer, CircuitId.Output);
                }
            }

            return(new Blueprint
            {
                Label = $"Demultiplexer",
                Icons = new List <Icon>
                {
                    Icon.Create(ItemNames.DeciderCombinator),
                    Icon.Create(ItemNames.ArithmeticCombinator)
                },
                Entities = entities,
                Item = ItemNames.Blueprint,
                Version = BlueprintVersions.CurrentVersion
            });
        }