From 05b56603f1071c7b0c14c1c028708556330e2fae Mon Sep 17 00:00:00 2001 From: Lucas Scharenbroich Date: Sun, 15 Apr 2018 13:51:05 -0500 Subject: [PATCH] Add start of a GUI for visualizing the search process --- SpriteCompiler.GUI/App.config | 6 + SpriteCompiler.GUI/MainForm.Designer.cs | 471 ++++++++++++++++++ SpriteCompiler.GUI/MainForm.cs | 185 +++++++ SpriteCompiler.GUI/MainForm.resx | 244 +++++++++ SpriteCompiler.GUI/Program.cs | 22 + SpriteCompiler.GUI/Properties/AssemblyInfo.cs | 36 ++ .../Properties/Resources.Designer.cs | 71 +++ SpriteCompiler.GUI/Properties/Resources.resx | 117 +++++ .../Properties/Settings.Designer.cs | 30 ++ .../Properties/Settings.settings | 7 + SpriteCompiler.GUI/SpriteCompiler.GUI.csproj | 96 ++++ SpriteCompiler.Test/MarioTests.cs | 72 ++- SpriteCompiler.sln | 6 + SpriteCompiler/AI/AbstractSearchNode.cs | 2 +- SpriteCompiler/AI/AbstractSearchStrategy.cs | 6 +- SpriteCompiler/AI/HeuristicSearchNode.cs | 7 +- SpriteCompiler/AI/IPathCost.cs | 12 +- SpriteCompiler/AI/ISearch.cs | 2 +- SpriteCompiler/AI/Queue/IQueue.cs | 1 - SpriteCompiler/AI/RecursiveBestFirstSearch.cs | 88 ++++ .../AI/SimplifiedMemoryBoundedAStarSearch.cs | 20 + SpriteCompiler/Adapters/QueueAdapter.cs | 1 - .../Helpers/BrutalDeluxeClassifier.cs | 426 ++++++++++++++++ .../Problem/SpriteGeneratorSearchProblem.cs | 19 +- .../Problem/SpriteGeneratorState.cs | 9 +- .../SpriteGeneratorSuccessorFunction.cs | 6 +- SpriteCompiler/Program.cs | 115 ++--- SpriteCompiler/Samples/Barbarian.gif | Bin 0 -> 40717 bytes SpriteCompiler/Samples/FullSprite.gif | Bin 0 -> 397 bytes SpriteCompiler/Samples/Sprite.gif | Bin 0 -> 1178 bytes SpriteCompiler/SpriteCompiler.csproj | 16 +- SpriteCompiler/packages.config | 3 +- 32 files changed, 1992 insertions(+), 104 deletions(-) create mode 100644 SpriteCompiler.GUI/App.config create mode 100644 SpriteCompiler.GUI/MainForm.Designer.cs create mode 100644 SpriteCompiler.GUI/MainForm.cs create mode 100644 SpriteCompiler.GUI/MainForm.resx create mode 100644 SpriteCompiler.GUI/Program.cs create mode 100644 SpriteCompiler.GUI/Properties/AssemblyInfo.cs create mode 100644 SpriteCompiler.GUI/Properties/Resources.Designer.cs create mode 100644 SpriteCompiler.GUI/Properties/Resources.resx create mode 100644 SpriteCompiler.GUI/Properties/Settings.Designer.cs create mode 100644 SpriteCompiler.GUI/Properties/Settings.settings create mode 100644 SpriteCompiler.GUI/SpriteCompiler.GUI.csproj create mode 100644 SpriteCompiler/AI/RecursiveBestFirstSearch.cs create mode 100644 SpriteCompiler/AI/SimplifiedMemoryBoundedAStarSearch.cs create mode 100644 SpriteCompiler/Helpers/BrutalDeluxeClassifier.cs create mode 100644 SpriteCompiler/Samples/Barbarian.gif create mode 100644 SpriteCompiler/Samples/FullSprite.gif create mode 100644 SpriteCompiler/Samples/Sprite.gif diff --git a/SpriteCompiler.GUI/App.config b/SpriteCompiler.GUI/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/SpriteCompiler.GUI/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/SpriteCompiler.GUI/MainForm.Designer.cs b/SpriteCompiler.GUI/MainForm.Designer.cs new file mode 100644 index 0000000..f065520 --- /dev/null +++ b/SpriteCompiler.GUI/MainForm.Designer.cs @@ -0,0 +1,471 @@ +namespace SpriteCompiler.GUI +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); + this.mainMenu = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.newToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator = new System.Windows.Forms.ToolStripSeparator(); + this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.printToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.printPreviewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.undoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.redoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.cutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.copyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.pasteToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + this.selectAllToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.customizeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.contentsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.indexToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.searchToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); + this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.openFileDialog = new System.Windows.Forms.OpenFileDialog(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.pictureBox2 = new System.Windows.Forms.PictureBox(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.pictureBox3 = new System.Windows.Forms.PictureBox(); + this.label3 = new System.Windows.Forms.Label(); + this.mainMenu.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).BeginInit(); + this.SuspendLayout(); + // + // mainMenu + // + this.mainMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem, + this.editToolStripMenuItem, + this.toolsToolStripMenuItem, + this.helpToolStripMenuItem}); + this.mainMenu.Location = new System.Drawing.Point(0, 0); + this.mainMenu.Name = "mainMenu"; + this.mainMenu.Size = new System.Drawing.Size(1051, 24); + this.mainMenu.TabIndex = 0; + this.mainMenu.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.newToolStripMenuItem, + this.openToolStripMenuItem, + this.toolStripSeparator, + this.saveToolStripMenuItem, + this.saveAsToolStripMenuItem, + this.toolStripSeparator1, + this.printToolStripMenuItem, + this.printPreviewToolStripMenuItem, + this.toolStripSeparator2, + this.exitToolStripMenuItem}); + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); + this.fileToolStripMenuItem.Text = "&File"; + // + // newToolStripMenuItem + // + this.newToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("newToolStripMenuItem.Image"))); + this.newToolStripMenuItem.ImageTransparentColor = System.Drawing.Color.Magenta; + this.newToolStripMenuItem.Name = "newToolStripMenuItem"; + this.newToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.N))); + this.newToolStripMenuItem.Size = new System.Drawing.Size(146, 22); + this.newToolStripMenuItem.Text = "&New"; + // + // openToolStripMenuItem + // + this.openToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("openToolStripMenuItem.Image"))); + this.openToolStripMenuItem.ImageTransparentColor = System.Drawing.Color.Magenta; + this.openToolStripMenuItem.Name = "openToolStripMenuItem"; + this.openToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); + this.openToolStripMenuItem.Size = new System.Drawing.Size(146, 22); + this.openToolStripMenuItem.Text = "&Open"; + this.openToolStripMenuItem.Click += new System.EventHandler(this.openToolStripMenuItem_Click); + // + // toolStripSeparator + // + this.toolStripSeparator.Name = "toolStripSeparator"; + this.toolStripSeparator.Size = new System.Drawing.Size(143, 6); + // + // saveToolStripMenuItem + // + this.saveToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("saveToolStripMenuItem.Image"))); + this.saveToolStripMenuItem.ImageTransparentColor = System.Drawing.Color.Magenta; + this.saveToolStripMenuItem.Name = "saveToolStripMenuItem"; + this.saveToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S))); + this.saveToolStripMenuItem.Size = new System.Drawing.Size(146, 22); + this.saveToolStripMenuItem.Text = "&Save"; + // + // saveAsToolStripMenuItem + // + this.saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; + this.saveAsToolStripMenuItem.Size = new System.Drawing.Size(146, 22); + this.saveAsToolStripMenuItem.Text = "Save &As"; + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(143, 6); + // + // printToolStripMenuItem + // + this.printToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("printToolStripMenuItem.Image"))); + this.printToolStripMenuItem.ImageTransparentColor = System.Drawing.Color.Magenta; + this.printToolStripMenuItem.Name = "printToolStripMenuItem"; + this.printToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.P))); + this.printToolStripMenuItem.Size = new System.Drawing.Size(146, 22); + this.printToolStripMenuItem.Text = "&Print"; + // + // printPreviewToolStripMenuItem + // + this.printPreviewToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("printPreviewToolStripMenuItem.Image"))); + this.printPreviewToolStripMenuItem.ImageTransparentColor = System.Drawing.Color.Magenta; + this.printPreviewToolStripMenuItem.Name = "printPreviewToolStripMenuItem"; + this.printPreviewToolStripMenuItem.Size = new System.Drawing.Size(146, 22); + this.printPreviewToolStripMenuItem.Text = "Print Pre&view"; + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(143, 6); + // + // exitToolStripMenuItem + // + this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + this.exitToolStripMenuItem.Size = new System.Drawing.Size(146, 22); + this.exitToolStripMenuItem.Text = "E&xit"; + // + // editToolStripMenuItem + // + this.editToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.undoToolStripMenuItem, + this.redoToolStripMenuItem, + this.toolStripSeparator3, + this.cutToolStripMenuItem, + this.copyToolStripMenuItem, + this.pasteToolStripMenuItem, + this.toolStripSeparator4, + this.selectAllToolStripMenuItem}); + this.editToolStripMenuItem.Name = "editToolStripMenuItem"; + this.editToolStripMenuItem.Size = new System.Drawing.Size(39, 20); + this.editToolStripMenuItem.Text = "&Edit"; + // + // undoToolStripMenuItem + // + this.undoToolStripMenuItem.Name = "undoToolStripMenuItem"; + this.undoToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Z))); + this.undoToolStripMenuItem.Size = new System.Drawing.Size(144, 22); + this.undoToolStripMenuItem.Text = "&Undo"; + // + // redoToolStripMenuItem + // + this.redoToolStripMenuItem.Name = "redoToolStripMenuItem"; + this.redoToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Y))); + this.redoToolStripMenuItem.Size = new System.Drawing.Size(144, 22); + this.redoToolStripMenuItem.Text = "&Redo"; + // + // toolStripSeparator3 + // + this.toolStripSeparator3.Name = "toolStripSeparator3"; + this.toolStripSeparator3.Size = new System.Drawing.Size(141, 6); + // + // cutToolStripMenuItem + // + this.cutToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("cutToolStripMenuItem.Image"))); + this.cutToolStripMenuItem.ImageTransparentColor = System.Drawing.Color.Magenta; + this.cutToolStripMenuItem.Name = "cutToolStripMenuItem"; + this.cutToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.X))); + this.cutToolStripMenuItem.Size = new System.Drawing.Size(144, 22); + this.cutToolStripMenuItem.Text = "Cu&t"; + // + // copyToolStripMenuItem + // + this.copyToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("copyToolStripMenuItem.Image"))); + this.copyToolStripMenuItem.ImageTransparentColor = System.Drawing.Color.Magenta; + this.copyToolStripMenuItem.Name = "copyToolStripMenuItem"; + this.copyToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.C))); + this.copyToolStripMenuItem.Size = new System.Drawing.Size(144, 22); + this.copyToolStripMenuItem.Text = "&Copy"; + // + // pasteToolStripMenuItem + // + this.pasteToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("pasteToolStripMenuItem.Image"))); + this.pasteToolStripMenuItem.ImageTransparentColor = System.Drawing.Color.Magenta; + this.pasteToolStripMenuItem.Name = "pasteToolStripMenuItem"; + this.pasteToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.V))); + this.pasteToolStripMenuItem.Size = new System.Drawing.Size(144, 22); + this.pasteToolStripMenuItem.Text = "&Paste"; + // + // toolStripSeparator4 + // + this.toolStripSeparator4.Name = "toolStripSeparator4"; + this.toolStripSeparator4.Size = new System.Drawing.Size(141, 6); + // + // selectAllToolStripMenuItem + // + this.selectAllToolStripMenuItem.Name = "selectAllToolStripMenuItem"; + this.selectAllToolStripMenuItem.Size = new System.Drawing.Size(144, 22); + this.selectAllToolStripMenuItem.Text = "Select &All"; + // + // toolsToolStripMenuItem + // + this.toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.customizeToolStripMenuItem, + this.optionsToolStripMenuItem}); + this.toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; + this.toolsToolStripMenuItem.Size = new System.Drawing.Size(47, 20); + this.toolsToolStripMenuItem.Text = "&Tools"; + // + // customizeToolStripMenuItem + // + this.customizeToolStripMenuItem.Name = "customizeToolStripMenuItem"; + this.customizeToolStripMenuItem.Size = new System.Drawing.Size(130, 22); + this.customizeToolStripMenuItem.Text = "&Customize"; + // + // optionsToolStripMenuItem + // + this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; + this.optionsToolStripMenuItem.Size = new System.Drawing.Size(130, 22); + this.optionsToolStripMenuItem.Text = "&Options"; + // + // helpToolStripMenuItem + // + this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.contentsToolStripMenuItem, + this.indexToolStripMenuItem, + this.searchToolStripMenuItem, + this.toolStripSeparator5, + this.aboutToolStripMenuItem}); + this.helpToolStripMenuItem.Name = "helpToolStripMenuItem"; + this.helpToolStripMenuItem.Size = new System.Drawing.Size(44, 20); + this.helpToolStripMenuItem.Text = "&Help"; + // + // contentsToolStripMenuItem + // + this.contentsToolStripMenuItem.Name = "contentsToolStripMenuItem"; + this.contentsToolStripMenuItem.Size = new System.Drawing.Size(122, 22); + this.contentsToolStripMenuItem.Text = "&Contents"; + // + // indexToolStripMenuItem + // + this.indexToolStripMenuItem.Name = "indexToolStripMenuItem"; + this.indexToolStripMenuItem.Size = new System.Drawing.Size(122, 22); + this.indexToolStripMenuItem.Text = "&Index"; + // + // searchToolStripMenuItem + // + this.searchToolStripMenuItem.Name = "searchToolStripMenuItem"; + this.searchToolStripMenuItem.Size = new System.Drawing.Size(122, 22); + this.searchToolStripMenuItem.Text = "&Search"; + // + // toolStripSeparator5 + // + this.toolStripSeparator5.Name = "toolStripSeparator5"; + this.toolStripSeparator5.Size = new System.Drawing.Size(119, 6); + // + // aboutToolStripMenuItem + // + this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; + this.aboutToolStripMenuItem.Size = new System.Drawing.Size(122, 22); + this.aboutToolStripMenuItem.Text = "&About..."; + // + // openFileDialog + // + this.openFileDialog.FileName = "openFileDialog"; + this.openFileDialog.FileOk += new System.ComponentModel.CancelEventHandler(this.openFileDialog_FileOk); + // + // pictureBox1 + // + this.pictureBox1.Location = new System.Drawing.Point(12, 40); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(200, 344); + this.pictureBox1.TabIndex = 1; + this.pictureBox1.TabStop = false; + // + // button1 + // + this.button1.Location = new System.Drawing.Point(13, 391); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 2; + this.button1.Text = "Initialize"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // button2 + // + this.button2.Location = new System.Drawing.Point(95, 391); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(75, 23); + this.button2.TabIndex = 3; + this.button2.Text = "Single Step"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(9, 24); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(73, 13); + this.label1.TabIndex = 5; + this.label1.Text = "Source Image"; + // + // pictureBox2 + // + this.pictureBox2.Location = new System.Drawing.Point(218, 40); + this.pictureBox2.Name = "pictureBox2"; + this.pictureBox2.Size = new System.Drawing.Size(200, 344); + this.pictureBox2.TabIndex = 6; + this.pictureBox2.TabStop = false; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(218, 24); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(50, 13); + this.label2.TabIndex = 7; + this.label2.Text = "Analyzed"; + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(630, 40); + this.textBox1.Multiline = true; + this.textBox1.Name = "textBox1"; + this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.textBox1.Size = new System.Drawing.Size(408, 344); + this.textBox1.TabIndex = 8; + // + // pictureBox3 + // + this.pictureBox3.Location = new System.Drawing.Point(424, 40); + this.pictureBox3.Name = "pictureBox3"; + this.pictureBox3.Size = new System.Drawing.Size(200, 344); + this.pictureBox3.TabIndex = 9; + this.pictureBox3.TabStop = false; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(421, 24); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(50, 13); + this.label3.TabIndex = 10; + this.label3.Text = "Analyzed"; + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1051, 421); + this.Controls.Add(this.label3); + this.Controls.Add(this.pictureBox3); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label2); + this.Controls.Add(this.pictureBox2); + this.Controls.Add(this.label1); + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Controls.Add(this.pictureBox1); + this.Controls.Add(this.mainMenu); + this.MainMenuStrip = this.mainMenu; + this.Name = "MainForm"; + this.Text = "IIgs Sprite Compiler"; + this.mainMenu.ResumeLayout(false); + this.mainMenu.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.MenuStrip mainMenu; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem newToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator; + private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripMenuItem printToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem printPreviewToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem undoToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem redoToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + private System.Windows.Forms.ToolStripMenuItem cutToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem pasteToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; + private System.Windows.Forms.ToolStripMenuItem selectAllToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem toolsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem customizeToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem contentsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem indexToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem searchToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; + private System.Windows.Forms.OpenFileDialog openFileDialog; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.PictureBox pictureBox2; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.PictureBox pictureBox3; + private System.Windows.Forms.Label label3; + } +} + diff --git a/SpriteCompiler.GUI/MainForm.cs b/SpriteCompiler.GUI/MainForm.cs new file mode 100644 index 0000000..14c28c5 --- /dev/null +++ b/SpriteCompiler.GUI/MainForm.cs @@ -0,0 +1,185 @@ +using SpriteCompiler.AI; +using SpriteCompiler.AI.Queue; +using SpriteCompiler.Helpers; +using SpriteCompiler.Problem; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace SpriteCompiler.GUI +{ + public partial class MainForm : Form + { + public MainForm() + { + InitializeComponent(); + } + + private void newToolStripMenuItem_ItemClicked(object sender, ToolStripItemClickedEventArgs e) + { + } + + private void openFileDialog_FileOk(object sender, CancelEventArgs e) + { + } + + private Bitmap source = null; + + private Bitmap Expand(Bitmap src, int width, int height) + { + // Create a bitmap exactly the same size as the picture box + var bitmap = new Bitmap(width, height); + using (var g = Graphics.FromImage(bitmap)) + { + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; + g.DrawImage(src, 0, 0, bitmap.Width, bitmap.Height); + } + + return bitmap; + } + + private void openToolStripMenuItem_Click(object sender, EventArgs e) + { + if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) + { + // Load the image from the file + source = new Bitmap(openFileDialog.FileName); + + pictureBox1.Image = Expand(source, pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height); + pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; + // Load and analyze the image... + } + } + + private SpriteGeneratorSearchProblem problem; + private SpriteGeneratorState initialState = null; + private ISearch search; + private InspectableTreeSearch strategy; + + private void button1_Click(object sender, EventArgs e) + { + var bgcolor = source.GetPixel(0, 0); + var record = BrutalDeluxeClassifier.Decompose(source, bgcolor); + + var classified = new Bitmap(source.Width, source.Height); + for (int y = 0; y < source.Height; y++) + { + for (int x = 0; x < (source.Width / 2); x++) + { + // A mask value of 255 (0xFF) is a tansparent pair of pixels, use the background color + var color = BrutalDeluxeClassifier.ToRGB(record.Classes[x, y]); + classified.SetPixel(2 * x, y, color); + classified.SetPixel(2 * x + 1, y, color); + } + } + + pictureBox3.Image = Expand(classified, pictureBox3.ClientSize.Width, pictureBox3.ClientSize.Height); ; + pictureBox3.SizeMode = PictureBoxSizeMode.Zoom; + + + var rb_record = BrutalDeluxeClassifier.DecomposeIntoRedBlueImageMap(source, bgcolor); + + var rb_classified = new Bitmap(source.Width, source.Height); + for (int y = 0; y < source.Height; y++) + { + for (int x = 0; x < (source.Width / 2); x++) + { + // A mask value of 255 (0xFF) is a tansparent pair of pixels, use the background color + var color = BrutalDeluxeClassifier.ToRGB(rb_record.Classes[x, y]); + rb_classified.SetPixel(2 * x, y, color); + rb_classified.SetPixel(2 * x + 1, y, color); + } + } + + pictureBox2.Image = Expand(rb_classified, pictureBox2.ClientSize.Width, pictureBox2.ClientSize.Height); ; + pictureBox2.SizeMode = PictureBoxSizeMode.Zoom; + + var histogram = BrutalDeluxeClassifier.GenerateStatistics(rb_record); + textBox1.Clear(); + + textBox1.AppendText("Most Common Values\n"); + textBox1.AppendText("------------------\n"); + foreach (var stat in histogram.OrderByDescending(x => x.Value).Take(10)) + { + textBox1.AppendText(String.Format("0x{0:X} : {1}\n", stat.Key, stat.Value)); + } + + // Initialize the search + var maxCycles = 5 + source.Height * (3 + (source.Width / 4 * 31) - 1 + 41) - 1; + + problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); + initialState = SpriteGeneratorState.Init(rb_record.SpriteData); + + var expander = new SpriteGeneratorNodeExpander(); + strategy = new InspectableTreeSearch(expander); + + strategy.Initialize(problem, initialState); + } + + private void button2_Click(object sender, EventArgs e) + { + // Execute one step of the search + var node = strategy.SearchStep(problem); + foreach (var n in strategy.Solution(node)) + { + textBox1.AppendText(String.Format("{0}: {1} ; {2} cycles\n", n.Depth, n.Action, n.PathCost)); + } + textBox1.AppendText(node.State.ToString() + "\n"); + } + + public class InspectableTreeSearch : AbstractSearchStrategy + { + private IQueue fringe; + + public InspectableTreeSearch(INodeExpander expander) + : base(expander) + { + } + + public void Initialize(SpriteGeneratorSearchProblem problem, SpriteGeneratorState initialState) + { + // Create a new fringe + // fringe = new Adapters.QueueAdapter(); + fringe = new LIFO(); + + // Add the initial state to the fringe + fringe.Enqueue(Expander.CreateNode(initialState)); + } + + public SpriteGeneratorSearchNode SearchStep(SpriteGeneratorSearchProblem problem) + { + // If the fringe is empty, return null to indicate that no solution was found + if (fringe.Empty) + { + return null; + } + + // Select the next node + var node = fringe.Remove(); + + // If the node is a solution, then we're done! Otherwise expand the node and add to the queue + if (!problem.IsGoal(node.State)) + { + AddNodes(fringe, node, problem); + } + + // Return the node that we selected to the caller + return node; + } + + /// + /// Generic tree search. See page 72 in Russell and Norvig + /// + protected override void AddNodes(IQueue fringe, SpriteGeneratorSearchNode node, ISearchProblem problem) + { + fringe.AddRange(Expander.Expand(problem, node)); + } + } + } +} diff --git a/SpriteCompiler.GUI/MainForm.resx b/SpriteCompiler.GUI/MainForm.resx new file mode 100644 index 0000000..aa067c9 --- /dev/null +++ b/SpriteCompiler.GUI/MainForm.resx @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAERSURBVDhPrZDbSgJRGIXnpewd6jXsjSQvIrwoI0RQMChU + 0iiDPCGiE3ZCRkvR8VzTeBhnyR5/ccaZNnPhB4t9sdf6Ln5hb8QeathNJFVFKF5C8DqL4ksDVHWGDf7j + LHyPg6NjviSaFqlu5yQYR+KpupaIkrMknCxT3Y7v/NYYb0ITK1c3BarbWWhLQ7IR0cTKReyZ6lZ0XYei + ztHpK4bAc+h1FgQijzSxMptrGIxVSO0xX3AaStFki7bUMVFmaMm/eJMGfIH/MkGzLep0AXn4h/r3CJV3 + mS9gn2bY4UY/UzQ7E9TqfeTFtnuB+XAfzSHKr11kSl/uBebDiZ89ZCst3OUkdwL28sIVsE83ock+EIQV + 2Mz2wxeg6/UAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJHSURBVDhPxZBdSNNhFMb/F110ZZEVhVBgeeHNICiiuggp + olAUyyxI0oSaH1QYC3N+tKnp5ubm1JUua5uuqdNKMwr7kApFItTUkWZqVhSVYmao5Nevvy7UoYR3HXh4 + 4XCe33nOKyy3lAY7l9RWMo0O/raWXxEyo5spVYTNvOGyfIRPfW+ptOkXqaPl6T83hcRmExSdgzAz3NVm + YWyoYla/B+1M9JtxWLPpaH22JORIjI6gKAMB0jyEimIdo4OlbuaprwVMOOMovammpDADc34qppwUrmnl + 5Kni3aFlFg2j3y1z5mnRTJccnNIltQhwq0jFry+mOXNtpWZWDx1Z1NhV3C3JwGFOw25SYjVe5oYhiUKd + HKMmwQUrMWUw/CF3NnZvvYKqUh1TvUroS3fXe7HXkwidMngTS2t5KLbregSzMY2f3Wr4qKW6LJvGR1rX + 0MLor8OhKYTJBn/GHvvxrliCTBrsOqXIoOBHh5K+hmSq7FqmexTQHuUytkaKxuNMNgYyVneA4Qd7GKjc + hjLaRzxH7gIU6JIZaEvgtk1D8wsxSWecCDgNzWFMvwxm/PkhRmr3Mli1nW9lvjRdWc0Jf+/5jzRmyWmv + S+GOLQu6U6BFjPvqKOP1AYw88WOoZif9DgmfLVtxaj1RSLdwNvrkPCA3M54KqxrnvRia9MKcGrUrqFOt + 5H7qKsqT1mGO9+Lqhc2ELdw+U/r0i+gVZ8hMiCDx3DHORwZyKnQ/hw/uYt9uCTskPvh6e7Fp41rWr/Fg + g6eHO+A/lyD8ARfG3mk9fv1YAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIySURBVDhPrZLfS5NRGMfff6H7boIuuq2pMZyL1eAt11CW + DcOKsB9vpFmaLtNExco0av6CbIVLJ61Wk3BSkT/AFCkRZSpZmrmiJQ41xSaCwdfznL15XEUX0Reem5f3 + 8znnec4j/Zc8fxYGla91CS3eRTx0z6OpMYS7jmnU1X6B/VYA18snUVoyjsKCt8jLHcH5c36ouCQR2NUJ + 1Nas4G9ZXlmFKbULh1Kf8lJxSfI+WeCCyopv6q+/h+DQ/DJ2WV5Ao1FgPegRAveDOS4oLfmq/h6dn/DH + 4AJizD4UXJrCAUuzEDgbZrjgou2DiohshIcnQtgme5GTPYbkJKcQ1N8OckHW2REVi+RXuM8fxGaDG4oy + ALPZIQQ11Z+5QDk1oKJ/hjv7P2FTfCMOH3mFxMQ6IbhROYWOdrCnBI4dfwPr0V4+bRoY9UzXppMjcDdS + rC8hy3YhuFI2gTYf2A4Aza4f7N2/o/zaLB8qDYx6zszwr8P7k1thNFYIweXCMXgeAfedq2xxwjClZUeV + Jd2GtDNFETiJwfs8MBjKhMCWN8pgoLoqzE8miH1GjE7G4PsZjE7OQsm9ij2mFg7rdrug1xcJAa2l4w7W + r00Cgk/n38S7wBwC04u4UGxHrMHF4CbEJtyDLj5fCDIzhljfSxzeavRgyw4Zj9t64GvvQ0d3P3pfD2Kv + 2QqNvgFxDN6urYdWmyMElJMnevh60obRktA701PRtGlg1DOdSkXwzrisaMG/RZLWAE60OMW5fNhvAAAA + AElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIpSURBVDhPtZL/T1JRGMb5p1itrVZbbRpqZbawnBENV1I0 + jGlByTSyJTXJwq2oKZQb1KAv6JCYWSxvBrkkZUq4CeQEiRABFeLL072Xa0zRra31bO8v57zP5znnPYf1 + X+TxhWF6O7VtGYcnwbSWijKPOLzYrPSvLPwLS3huGUMlT7o9wGD9grVUBj+icdid03S9tDmgNxNwTgVQ + J+rA8XNtWwM+uuZATMwxmQVRycuJFNyzIRitDlScugKzjSgFRGJJaIwEsrk8AsHIhnSL/Ssck37UNipQ + I5DjtuYV7uksRYhr2kebhx2eP6nrycFIEh5fBA/1Nvru8q5+PDaOovK0rABwfwugWzcErfkzHhjsePL6 + E7q1VrTdNUDcrgGvSYlDZHN5XTNOnL8BVe8AJAoNDtZfLgDu9L1BPJmikzcrk81hlRwodZJwdBXziwnI + OrVoaOkiT8C8hKLHBPO7CbywOaE1jeC+bhAd6meQdvZC1KoG/5IS3MZ2HObLUHZSggvkWq3wOvbWiAqA + VpWeyStVfCUNf3AZ4zNhfHCFMEDMgye+hYr6FrDLzxQAUuVTpr0ocn74mchg5vsKRt1RcHp2Qv9+kZ78 + UcE17KkWFgHNN/uQzgBkGKLJPBZiecyGchjzrmFwPIF++xJUbDbUQzEacIArLpopSRSP4CUN1Obf1Abz + uqob5KjiXwWH/GVl5HPt5zZh37GL2H1EiF1VZ7GDI6CNW5r/TSzWbwHYL0mKJ5czAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGCSURBVDhPnZK9S0JRGMb9F1xb2gqaq6mhwCGDtvYIIyLI + cJOE1paoIYpMKUjFRDH87lpoakGlIZF9DA2hZJEQhJXl1xPn3HPV29WQfvBwOfA+P95zuDJ39A6/4wyl + YOOSMHvOcHGThuwvSKEVRvsR+pQqWD3R1pK98DUbl7Jm5hA8SfESd6S5xH5wycalrO4E0D8yWQuriLH6 + E2xcSqlcoRJBxCpiTO5TNi4m/ZgDF4nDsOulsfujyGRzUsmWM8YqdcggKbveS3A88bEkslRye58RSzZt + IVarY/FFaPmlwp+fUaESYRNW5Vm3BPmpBpZNvppACDmTLbS6FbGAPFAj5OGI4PALOK/yZfIlAlk4j7n5 + xdaCarWKj0KRXmE2+UklJEJZZ/RCPTPdWvBdLOP1rYD41QNcgRiVkKJQ1mjGsa2VNxeQb2OWDC7sh47p + ddQLeoyOTSFiVAAFvVhChsmv2k6Uvd3Icx1UolMNiDdpl4nhLiohW/xb0tMph2JwCJxjAz9A30JI8zYA + tAAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGDSURBVDhPrZFNSwJRGIX9NYGbFoUlFElY1EJQKEYhCJsi + LaVsERnRF5iCaSZJO1toCDVGFkgoFpWQWWRR2aIvUxm1BKN1wSnHCFw4TOCzue+9nPNw4eVVnav4Izzb + QfxeGZ5TWaxT/rK3irzmC7CsusvC1G4IkbNLboIiDieF4GGUKeTeClDpppF8eeEu2PIfwfrzizSdw3Hk + EnKlFpkMzV2wH77AosOFTV8A+vkl9CiHuJeLJNNZjM8tYWB0FkTvMAwmy/8ERTR6CwjlGAi1Ccence6C + 1NsXzN4PKIxJLLgeIJ2MoXvmFraNBKK3eXZRIveJPvs7FIYniEkXZENOdE+GIZ2Ko10TwLK7tJmKmL0F + EEYarYM+NMnt0C1sQzpx/lcSEnZ2gcKY/gs0dlmZuWvmjjmpwA1qxVp2AWFIMAF/OAGBzMjMI7ZrtJCb + 4Df3o4Zfxy7QrdxDRFKol5khkpR2H4qmIOzUQNBGwrsXYxccnNOQqNbQ0KGGZ+eEPVwdeLxvqqrf4wGh + TNAAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHkSURBVDhPvZHfS1NhHIf3p5QypLr2D4goMwoMCi/qIugH + Xe1Cr7qKDIMkZixwNhfWLGWbnuki0kXKzLU023KubBNPJrbRdOzocm6e2dPOO21mMS+CHvjcvOf9PF++ + 79H9M+7RT2iRRsIi9sEAXe43yAvf2LpSHq28G9uAnytNT4jMLewtcQ2Ht2pF8ps/aOt+gccX5lxD694S + +1BQFD1RkN5DSFa4Z3uONKbgHE3h8KZ4OJTC1J8UiSzmfhd2uf1CoJHbyKOsZokl0kKwm+aeJaov+wjO + rpQkVqdXfOz0bWAcVLghfaXxkUz3y2VxvpMGSwL3uMKh+gHezSSLEnNhX23vtYzKUirDfGyFj/Iy1mdx + UWqR8iKhwtQLxjgH659y4EwvVXWPiwJt3/Ws+muywRrlqvkDdx3zQrCN8l1ldnEd3/QqFmkS/akHJYGS + zjLzOUEwEsMf+sLI2zmaOou/93pPGoM5zvk7UU7fnBKxSBPoT7SXBNW1F/9Io2lKCNTCeomUyrS8xnBA + wfUqyf1eP5U1ptJD/o1LzeNCsHPydtqdr6k4aiwvOHvNSya3ibU/QIdrEkvfhJislc32MfYfuV1eUGPw + FF7bIVJVZ0N/soPK421UHGstlFvYd/hWecF/Qqf7CR0A5wwgSQA2AAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJSSURBVDhPtZJrSJNRGMdf6IN9KbpQn/pUEH2JIoLqQ0Zh + FqYZRmJG1iKmUqKyLB2pqSm6vC1Nm5GXoeatEsVJ0RASR3eNzegikRq5lrV3857Fr/d9ddlICoL+8OfA + Oef/e57zcIT/os7WLMw302muSGJ2689qqi7A44q8IzjtNYzarzHQm8tZtT8FmRqu6LToMxN+B8qhCbGR + KVcDE85ajKUaxoaryEuL4UVXIudPB5Ko2oy98xjDptXERuz3hsgAOTzlqqMk6yjdllzE90UM9Wp5azlB + S1kwkeG+1CSv4mmBQPThfd6Ahqq8GYB4A11yBKmaMLQxoZyLDkGjDiZOFUhUuB+FsWsUQFiArzegtlzH + pFjPpMPA2GA2jucx2KqWK7ZWLqO7dBGP9D5KWLbfto3eAKMhi3FHBeP9GYy9PMXos4OIrYvJrzSRbWjm + wuV6EnVG4tLLiEzSExGf4w0oL05nZEDPaK+akceBuO9v4uPtFUrYo6npbzhdE/QPOQmNSiPouHYOUpaf + gvgqA/dDf9wd63G1r2SgUlAqyyq/1anYUGfG2mdXwne7bOwJUc1AinOS+NxzBpd5HWLbUhyNPvRdF5S2 + v05/54tbqvzBifWNHUvPOwLC4/CXwrv2HsB3+w6EwosJOB5ESeElfGpayGD1AmwlArHSm+W2PR1clToo + MrbT0mFTVtlbN6xFuJQar3wQz5Q9VksD+7XyPctrJdx4p5s605M5gKz8lJPSDwtGFbKboJ1blAN52vKb + PdXm80/AfDokTVu+8DfPXv9XCcIPTvjvLQ8YoakAAAAASUVORK5CYII= + + + + 127, 17 + + + 179 + + \ No newline at end of file diff --git a/SpriteCompiler.GUI/Program.cs b/SpriteCompiler.GUI/Program.cs new file mode 100644 index 0000000..beced70 --- /dev/null +++ b/SpriteCompiler.GUI/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace SpriteCompiler.GUI +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/SpriteCompiler.GUI/Properties/AssemblyInfo.cs b/SpriteCompiler.GUI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2c52a96 --- /dev/null +++ b/SpriteCompiler.GUI/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SpriteCompiler.GUI")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SpriteCompiler.GUI")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("da09c3ea-7742-4faf-bab9-6b39e994090d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SpriteCompiler.GUI/Properties/Resources.Designer.cs b/SpriteCompiler.GUI/Properties/Resources.Designer.cs new file mode 100644 index 0000000..ca9faf0 --- /dev/null +++ b/SpriteCompiler.GUI/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SpriteCompiler.GUI.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SpriteCompiler.GUI.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/SpriteCompiler.GUI/Properties/Resources.resx b/SpriteCompiler.GUI/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/SpriteCompiler.GUI/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SpriteCompiler.GUI/Properties/Settings.Designer.cs b/SpriteCompiler.GUI/Properties/Settings.Designer.cs new file mode 100644 index 0000000..0e4e417 --- /dev/null +++ b/SpriteCompiler.GUI/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SpriteCompiler.GUI.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/SpriteCompiler.GUI/Properties/Settings.settings b/SpriteCompiler.GUI/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/SpriteCompiler.GUI/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/SpriteCompiler.GUI/SpriteCompiler.GUI.csproj b/SpriteCompiler.GUI/SpriteCompiler.GUI.csproj new file mode 100644 index 0000000..cc4f393 --- /dev/null +++ b/SpriteCompiler.GUI/SpriteCompiler.GUI.csproj @@ -0,0 +1,96 @@ + + + + + Debug + AnyCPU + {DA09C3EA-7742-4FAF-BAB9-6B39E994090D} + WinExe + Properties + SpriteCompiler.GUI + SpriteCompiler.GUI + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + Form + + + MainForm.cs + + + + + MainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {56f54ca9-17c1-45c6-915b-6fabf4dae5d0} + SpriteCompiler + + + + + \ No newline at end of file diff --git a/SpriteCompiler.Test/MarioTests.cs b/SpriteCompiler.Test/MarioTests.cs index 7c1e7b7..8635053 100644 --- a/SpriteCompiler.Test/MarioTests.cs +++ b/SpriteCompiler.Test/MarioTests.cs @@ -70,7 +70,7 @@ namespace SpriteCompiler.Test { // Arrange var problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); - var search = SpriteGeneratorSearchProblem.Create(80); // max budget of 80 cycles + var search = SpriteGeneratorSearchProblem.Create(100); // max budget of 100 cycles var sprite = new List { new SpriteByte(0x11, 0x00, 3), @@ -97,6 +97,76 @@ namespace SpriteCompiler.Test // Assert : The initial state IS the goal state WriteOutSolution(solution); + + // TCS ; 2 cycles + // LDA 04,s ; 5 cycles + // AND #$0F00 ; 3 cycles + // ORA #$1011 ; 3 cycles + // STA 04,s ; 5 cycles + // LDA #$1111 ; 3 cycles + // STA 03,s ; 5 cycles + // STA A2,s ; 5 cycles + // LDA A4,s ; 5 cycles + // AND #$0F00 ; 3 cycles + // ORA #$2011 ; 3 cycles + // STA A4,s ; 5 cycles + // TSC ; 2 cycles + // ADC #321 ; 3 cycles + // TCS ; 2 cycles + // LDA 00,s ; 5 cycles + // AND #$00F0 ; 3 cycles + // ORA #$1101 ; 3 cycles + // STA 00,s ; 5 cycles + // LDA 03,s ; 5 cycles + // AND #$0F00 ; 3 cycles + // ORA #$2012 ; 3 cycles + // STA 03,s ; 5 cycles + // LDA #$1211 ; 3 cycles + // STA 02,s ; 5 cycles + //; Total Cost = 94 cycles + + initialHeuristic.Should().BeLessOrEqualTo(solution.Last().PathCost); + } + + [TestMethod] + public void TestLines_1_To_4() + { + // Arrange + var problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); + var search = SpriteGeneratorSearchProblem.Create(200); // max budget of 200 cycles + var sprite = new List + { + new SpriteByte(0x11, 0x00, 3), + new SpriteByte(0x11, 0x00, 4), + new SpriteByte(0x10, 0x0F, 5), + + new SpriteByte(0x11, 0x00, 162), + new SpriteByte(0x11, 0x00, 163), + new SpriteByte(0x11, 0x00, 164), + new SpriteByte(0x20, 0x0F, 165), + + new SpriteByte(0x01, 0xF0, 321), + new SpriteByte(0x11, 0x00, 322), + new SpriteByte(0x11, 0x00, 323), + new SpriteByte(0x12, 0x00, 324), + new SpriteByte(0x20, 0x0F, 325), + + new SpriteByte(0x01, 0xF0, 481), + new SpriteByte(0x11, 0x00, 482), + new SpriteByte(0x11, 0x00, 483), + new SpriteByte(0x11, 0x00, 484), + new SpriteByte(0x11, 0x00, 485), + new SpriteByte(0x11, 0x00, 486) + }; + + // Act : solve the problem + var initialState = SpriteGeneratorState.Init(sprite); + var initialHeuristic = problem.Heuristic(initialState); + + var solution = search.Search(problem, initialState); + + // Assert : The initial state IS the goal state + WriteOutSolution(solution); } private void WriteOutSolution(IEnumerable solution, IntegerCost h0 = null) diff --git a/SpriteCompiler.sln b/SpriteCompiler.sln index 720fdb2..e5a1340 100644 --- a/SpriteCompiler.sln +++ b/SpriteCompiler.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpriteCompiler.Test", "Spri EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AI.Test", "AI.Test\AI.Test.csproj", "{D76DB013-8897-4C2D-ACF3-FB55585D3677}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpriteCompiler.GUI", "SpriteCompiler.GUI\SpriteCompiler.GUI.csproj", "{DA09C3EA-7742-4FAF-BAB9-6B39E994090D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {D76DB013-8897-4C2D-ACF3-FB55585D3677}.Debug|Any CPU.Build.0 = Debug|Any CPU {D76DB013-8897-4C2D-ACF3-FB55585D3677}.Release|Any CPU.ActiveCfg = Release|Any CPU {D76DB013-8897-4C2D-ACF3-FB55585D3677}.Release|Any CPU.Build.0 = Release|Any CPU + {DA09C3EA-7742-4FAF-BAB9-6B39E994090D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA09C3EA-7742-4FAF-BAB9-6B39E994090D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA09C3EA-7742-4FAF-BAB9-6B39E994090D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA09C3EA-7742-4FAF-BAB9-6B39E994090D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SpriteCompiler/AI/AbstractSearchNode.cs b/SpriteCompiler/AI/AbstractSearchNode.cs index 5c5ad8e..4126d68 100644 --- a/SpriteCompiler/AI/AbstractSearchNode.cs +++ b/SpriteCompiler/AI/AbstractSearchNode.cs @@ -30,7 +30,7 @@ public int Depth { get { return depth; } } public S State { get { return state; } } - public C EstCost { get { return PathCost; } } + public virtual C EstCost { get { return PathCost; } } public C StepCost { diff --git a/SpriteCompiler/AI/AbstractSearchStrategy.cs b/SpriteCompiler/AI/AbstractSearchStrategy.cs index 530aed5..a308efc 100644 --- a/SpriteCompiler/AI/AbstractSearchStrategy.cs +++ b/SpriteCompiler/AI/AbstractSearchStrategy.cs @@ -59,7 +59,7 @@ /// /// /// - /// Must be initialize -- usually that means being empty + /// Must be initialized -- usually that means being empty /// /// public virtual IEnumerable Search(ISearchProblem problem, IQueue fringe, S initialState) @@ -80,12 +80,12 @@ AddNodes(fringe, node, problem); } - return Enumerable.Empty(); + return Enumerable.Empty(); } /// /// When it's time to actually expand a node and add the new states to the fringe, different - /// algorhtms can make different choices + /// algorithms can make different choices /// /// /// diff --git a/SpriteCompiler/AI/HeuristicSearchNode.cs b/SpriteCompiler/AI/HeuristicSearchNode.cs index a3e5c25..ef92d9f 100644 --- a/SpriteCompiler/AI/HeuristicSearchNode.cs +++ b/SpriteCompiler/AI/HeuristicSearchNode.cs @@ -2,9 +2,12 @@ { using System; + public interface IHeuristicSearchNodeWithMemory : IHeuristicSearchNode where C : ICost + { + C F { get; set; } + } public interface IHeuristicSearchNode : ISearchNode where C : ICost { - C EstCost { get; } } public class HeuristicSearchNode : AbstractSearchNode, IHeuristicSearchNode @@ -19,7 +22,7 @@ public C Heuristic { get; set; } - public C EstCost + public override C EstCost { get { diff --git a/SpriteCompiler/AI/IPathCost.cs b/SpriteCompiler/AI/IPathCost.cs index 56b53dd..16fadce 100644 --- a/SpriteCompiler/AI/IPathCost.cs +++ b/SpriteCompiler/AI/IPathCost.cs @@ -2,11 +2,21 @@ { using System; + public static class CostExtensions + { + public static C Max(this C left, C right) where C : ICost + { + return (left.CompareTo(right) >= 0) + ? left + : right; + } + } + public interface ICost : IComparable { C Add(C value); - // Number theoretic values, i.e. C + ZERO = C, C * ONE = C + // Number theoretic values, i.e. C + ZERO = C, C * ONE = C C Zero(); C One(); C Maximum(); diff --git a/SpriteCompiler/AI/ISearch.cs b/SpriteCompiler/AI/ISearch.cs index 5279f63..90aa91b 100644 --- a/SpriteCompiler/AI/ISearch.cs +++ b/SpriteCompiler/AI/ISearch.cs @@ -9,6 +9,6 @@ /// Perform a new search on the specified search problem using the given /// initial state as a starting point. The method will return an empty /// list on failure. - IEnumerable Search(ISearchProblem problem, S initialState); + IEnumerable Search(ISearchProblem problem, S initialState); } } diff --git a/SpriteCompiler/AI/Queue/IQueue.cs b/SpriteCompiler/AI/Queue/IQueue.cs index e3d6ae7..1723a66 100644 --- a/SpriteCompiler/AI/Queue/IQueue.cs +++ b/SpriteCompiler/AI/Queue/IQueue.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; - public interface IQueue { void Clear(); diff --git a/SpriteCompiler/AI/RecursiveBestFirstSearch.cs b/SpriteCompiler/AI/RecursiveBestFirstSearch.cs new file mode 100644 index 0000000..051765e --- /dev/null +++ b/SpriteCompiler/AI/RecursiveBestFirstSearch.cs @@ -0,0 +1,88 @@ +namespace SpriteCompiler.AI +{ + using Queue; + using System; + using System.Linq; + +#if False + public class RecursiveBestFirstSearch : BestFirstSearch + where T : IHeuristicSearchNodeWithMemory + where C : ICost, new() + { + public RecursiveBestFirstSearch(ISearchStrategy search, Func> fringe) + : base(search, fringe) + { + } + } + + public class RecursiveBestFirstSearchStrategy : AbstractSearchStrategy + where T : IHeuristicSearchNodeWithMemory + where C : ICost, new() + { + private static readonly C Cost = new C(); + private ISearchProblem problem; + + public RecursiveBestFirstSearchStrategy(INodeExpander expander) + : base(expander) + { + } + + public override System.Collections.Generic.IEnumerable Search(ISearchProblem problem, IQueue fringe, S initialState) + { + RBFS(Expander.CreateNode(initialState), Cost.Zero(), Cost.Maximum()); + } + + private C RBFS(T node, C F_N, C bound) + { + var f_N = problem.Heuristic(node.State); + + if (f_N.CompareTo(bound) > 0) + { + return f_N; + } + + if (problem.IsGoal(node.State)) + { + throw new Exception(); + } + + var children = Expander.Expand(problem, node); + if (!children.Any()) + { + return Cost.Maximum(); + } + + foreach (var N_i in children) + { + if (f_N.CompareTo(F_N) < 0) + { + N_i.F = F_N.Max(N_i.EstCost); + } + else + { + N_i.F = N_i.EstCost; + } + } + + children = children.OrderBy(x => x.F); + + + /* + RBFS (node: N, value: F(N), bound: B) + IF f(N)>B, RETURN f(N) + IF N is a goal, EXIT algorithm + IF N has no children, RETURN infinity + FOR each child Ni of N, + IF f(N) : BestFirstSearch + where T : ISMASearchNode + where C : ICost, new() + { + public SimplifiedMemoryBoundedAStarSearch(ISearchStrategy search, Func> fringe) + : base(search, fringe) + { + } + } + + public interface ISMASearchNode : IHeuristicSearchNode where C : ICost + { + C BestForgottenSuccessor { get; } + } +} diff --git a/SpriteCompiler/Adapters/QueueAdapter.cs b/SpriteCompiler/Adapters/QueueAdapter.cs index e5cf0fe..fc87b18 100644 --- a/SpriteCompiler/Adapters/QueueAdapter.cs +++ b/SpriteCompiler/Adapters/QueueAdapter.cs @@ -14,7 +14,6 @@ where C : ICost { private readonly SimplePriorityQueue queue = new SimplePriorityQueue(); - public bool Empty { get { return queue.Count == 0; } } public void AddRange(IEnumerable items) diff --git a/SpriteCompiler/Helpers/BrutalDeluxeClassifier.cs b/SpriteCompiler/Helpers/BrutalDeluxeClassifier.cs new file mode 100644 index 0000000..f6f82e8 --- /dev/null +++ b/SpriteCompiler/Helpers/BrutalDeluxeClassifier.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SpriteCompiler.Helpers +{ + public static class BrutalDeluxeClassifier + { + public static bool IsYellow(this ByteColor color) { return ByteColor.YELLOW.Equals(color); } + public static bool IsOrange(this ByteColor color) { return ByteColor.ORANGE.Equals(color); } + public static bool IsRed(this ByteColor color) { return ByteColor.RED.Equals(color); } + public static bool IsBlue(this ByteColor color) { return ByteColor.BLUE.Equals(color); } + public static bool IsGreen(this ByteColor color) { return ByteColor.GREEN.Equals(color); } + + public sealed class Palette + { + private IDictionary palette = new Dictionary(); + public Color? MaskColor { get; set; } + + public int this[Color key] + { + get + { + return palette[key]; + } + set + { + palette[key] = value; + } + } + + public bool Contains(Color color) + { + return palette.ContainsKey(color); + } + + public int Count { get { return palette.Count; } } + + public int RGBToData(Color rgb) + { + if (MaskColor.HasValue && rgb.Equals(MaskColor.Value)) + { + // Always set masked data values to zero by convention + return 0x0; + } + else + { + return palette[rgb]; + } + } + + public int RGBToMask(Color rgb) + { + if (MaskColor.HasValue && rgb.Equals(MaskColor.Value)) + { + return 0xF; + } + else + { + return 0x0; + } + } + } + + public enum ByteColor + { + GREEN, + YELLOW, + BLUE, + ORANGE, + PURPLE, + RED + } + + public static Color ToRGB(ByteColor color) + { + switch (color) + { + case ByteColor.GREEN: return Color.PaleGreen; + case ByteColor.YELLOW: return Color.Yellow; + case ByteColor.BLUE: return Color.Blue; + case ByteColor.ORANGE: return Color.Orange; + case ByteColor.PURPLE: return Color.Purple; + case ByteColor.RED: return Color.Red; + } + + return Color.Transparent; + } + + public struct ColoredSpriteData + { + public int Offset; + public ByteColor Color; + public int Data; + public int Mask; + } + + public static ByteColor RedBlueClassifier(int mask) + { + if (mask == 0x00) return ByteColor.RED; + if (mask == 0xFF) return ByteColor.GREEN; + return ByteColor.BLUE; + } + + public static ByteColor YellowBlueClassifier(int mask) + { + if (mask == 0x00) return ByteColor.YELLOW; + if (mask == 0xFF) return ByteColor.GREEN; + return ByteColor.BLUE; + } + + public static Tuple, List>[] ReductionRules = new [] + { + // B-R-R-B -> P-P-P-P + Tuple.Create(new List { ByteColor.BLUE, ByteColor.RED, ByteColor.RED, ByteColor.GREEN }, new List { ByteColor.PURPLE, ByteColor.PURPLE, ByteColor.PURPLE, ByteColor.PURPLE }), + Tuple.Create(new List { ByteColor.GREEN, ByteColor.RED, ByteColor.GREEN }, new List { ByteColor.GREEN, ByteColor.YELLOW, ByteColor.YELLOW }) + }; + + public static List Extend(List neighbors, ByteColor current) + { + // Base case: Just append to an empty list + int n = neighbors.Count; + if (n == 0) + { + neighbors.Add(current); + return neighbors; + } + + // Interesting cases + // + // ORANGE + ORANGE + YELLOW + YELLOW = RED + RED + RED + RED + // RED + YELLOW + YELLOW = RED + RED + RED + // YELLOW + YELLOW = ORANGE + ORANGE + // YELLOW + BLUE = PURPLE + PURPLE + // BLUE + YELLOW = PURPLE + PURPLE + // BLUE + BLUE = PURPLE + PURPLE + // + // Everything else just appends the current classification + if (current.IsYellow()) + { + // Check to see if we have a new red span + if (n >= 3 && neighbors[n-1].IsYellow() && neighbors[n-2].IsOrange() && neighbors[n-3].IsOrange()) + { + neighbors[n - 3] = neighbors[n - 2] = neighbors[n - 1] = ByteColor.RED; + neighbors.Add(ByteColor.RED); + return neighbors; + } + + if (n >= 2 && neighbors[n-1].IsYellow() && neighbors[n-2].IsRed()) + { + neighbors[n - 1] = ByteColor.RED; + neighbors.Add(ByteColor.RED); + return neighbors; + } + + if (neighbors[n-1].IsYellow()) + { + neighbors[n - 1] = ByteColor.ORANGE; + neighbors.Add(ByteColor.ORANGE); + return neighbors; + } + + if (neighbors[n - 1].IsBlue()) + { + neighbors[n - 1] = ByteColor.PURPLE; + neighbors.Add(ByteColor.PURPLE); + return neighbors; + } + } + else if (current.IsBlue()) + { + if (neighbors[n - 1].IsYellow()) + { + neighbors[n - 1] = ByteColor.ORANGE; + neighbors.Add(ByteColor.ORANGE); + return neighbors; + } + + if (neighbors[n - 1].IsBlue()) + { + neighbors[n - 1] = ByteColor.PURPLE; + neighbors.Add(ByteColor.PURPLE); + return neighbors; + } + } + + neighbors.Add(current); + return neighbors; + } + + public static Palette ExtractColorPalette(Bitmap bitmap, Color? maskColor = null) + { + var palette = new Palette(); + int nextIndex = 1; + + for (int r = 0; r < bitmap.Height; r++) + { + for (int w = 0; w < bitmap.Width; w++) + { + var rgb = bitmap.GetPixel(w, r); + + if (!palette.Contains(rgb)) + { + if (palette.Count >= 16) + { + throw new Exception("Image cannot have more than 15 unique colors"); + } + + palette[rgb] = nextIndex++; + } + } + } + + palette.MaskColor = maskColor; + return palette; + } + + public static int[,] Extract4BitPixelData(Bitmap bitmap, Palette palette) + { + var data_buffer = new int[bitmap.Width, bitmap.Height]; + + for (int r = 0; r < bitmap.Height; r++) + { + for (int w = 0; w < bitmap.Width; w++) + { + data_buffer[w, r] = palette.RGBToData(bitmap.GetPixel(w, r)); + } + } + + return data_buffer; + } + + public static int[,] Extract4BitPixelMask(Bitmap bitmap, Palette palette) + { + var mask_buffer = new int[bitmap.Width, bitmap.Height]; + + for (int r = 0; r < bitmap.Height; r++) + { + for (int w = 0; w < bitmap.Width; w++) + { + mask_buffer[w, r] = palette.RGBToMask(bitmap.GetPixel(w, r)); + } + } + + return mask_buffer; + } + + public static int[,] Decimate(int[,] fourBitData) + { + // Conver the 4-bit data into bytes + int width = fourBitData.GetLength(0); + int height = fourBitData.GetLength(1); + var byte_buffer = new int[width / 2, height]; + + for (int r = 0; r SpriteData; + } + + public static IDictionary GenerateStatistics(SpriteBitmapRecord record) + { + // Look at the data and find top 16-bit patterns in the image (scan the data/mask arrays and create a list of all 16-bit values) + // NOTE: These are overlapping values, a pattern of 0xAA 0x55 0xAA will give values of 0x55AA and 0xAA55 + var data = record.Data; + var mask = record.Mask; + + var width = data.GetLength(0); + var height = data.GetLength(1); + + var histogram = new Dictionary(); + for (int row = 0; row < height; row++) + { + for (int col = 1; col < width; col++) + { + if (mask[col, row] == 0x00 && mask[col - 1, row] == 0x00) + { + var value = data[col, row] * 256 + data[col - 1, row]; + if (!histogram.ContainsKey(value)) + { + histogram[value] = 0; + } + + histogram[value] += 1; + } + } + } + + return histogram; + } + + public static SpriteBitmapRecord DecomposeIntoRedBlueImageMap(Bitmap bitmap, Color? maskColor = null) + { + var sprite = new List(); + + // Build the palette of this sprite image + var palette = ExtractColorPalette(bitmap, maskColor); + + // Get the 4-bit data and mask arrays + var data = Decimate(Extract4BitPixelData(bitmap, palette)); + var mask = Decimate(Extract4BitPixelMask(bitmap, palette)); + + // Start scanning the data, row by row. Each line is so far away from the + // previous line that we always reset + var width = data.GetLength(0); + var height = data.GetLength(1); + var classes = new ByteColor[width, height]; + + for (int row = 0; row < height; row++) + { + for (int col = 0; col < width; col++) + { + classes[col, row] = RedBlueClassifier(mask[col, row]); + } + + // Once a full row is classified, create the sprite data + for (int col = 0; col < width; col++) + { + if (mask[col, row] == 0xFF) + { + continue; + } + + sprite.Add(new ColoredSpriteData + { + Color = classes[col, row], + Offset = row * 160 + col, + Mask = mask[col, row], + Data = data[col, row] + }); + } + } + + return new SpriteBitmapRecord + { + Data = data, + Mask = mask, + Classes = classes, + SpriteData = sprite + }; + } + + public static SpriteBitmapRecord Decompose(Bitmap bitmap, Color? maskColor = null) + { + // Classified the data according to the method described at http://www.brutaldeluxe.fr/products/crossdevtools/mrspritetech/index.html + // + // GREEN : is skipped (this is a sparse structure) + // YELLOW : Solid, isolated byte (-- -- XX -- --) + // BLUE : Mixed, isolated byte (-- -- -X -- --) + // ORANGE : Solid, isolated word (-- -- XX XX -- --) + // PURPLE : Mixed, isolated word (-- -- -X XX -- --) at least 1 pixel out of 4. + // RED: : At least 4 solid pixels (-- -- XX XX XX XX ...) + // + // A yellow can turn into a purple or red + // A blue can be turned into a purple + var sprite = new List(); + + // Build the palette of this sprite image + var palette = ExtractColorPalette(bitmap, maskColor); + + // Get the 4-bit data and mask arrays + var data = Decimate(Extract4BitPixelData(bitmap, palette)); + var mask = Decimate(Extract4BitPixelMask(bitmap, palette)); + + // Start scanning the data, row by row. Each line is so far away from the + // previous line that we always reset + var width = data.GetLength(0); + var height = data.GetLength(1); + var classes = new ByteColor[width, height]; + + for (int row = 0; row < height; row++) + { + var classification = new List(); + + for (int col = 0; col < width; col++) + { + classes[col, row] = YellowBlueClassifier(mask[col, row]); + classification = Extend(classification, classes[col, row]); + } + + // Once a full row is classified, create the sprite data + for (int col = 0; col < width; col++) + { + classes[col, row] = classification[col]; + + if (classification[col].IsGreen()) + { + continue; + } + + sprite.Add(new ColoredSpriteData + { + Color = classification[col], + Offset = row * 160 + col, + Mask = mask[col, row], + Data = data[col, row] + }); + } + } + + return new SpriteBitmapRecord + { + Data = data, + Mask = mask, + Classes = classes, + SpriteData = sprite + }; + } + } +} diff --git a/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs b/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs index 130a394..b29e7bc 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorSearchProblem.cs @@ -4,16 +4,21 @@ using SpriteCompiler.AI.Queue; using System; - public sealed class SpriteGeneratorSearchProblem + public sealed class SpriteGeneratorSearchProblem : SearchProblem { - public static ISearchProblem CreateSearchProblem() + public SpriteGeneratorSearchProblem() + : base( + new SpriteGeneratorGoalTest(), + new SpriteGeneratorStepCost(), + new SpriteGeneratorSuccessorFunction(), + new SpriteGeneratorHeuristicFunction() + ) { - var goalTest = new SpriteGeneratorGoalTest(); - var stepCost = new SpriteGeneratorStepCost(); - var successors = new SpriteGeneratorSuccessorFunction(); - var heuristic = new SpriteGeneratorHeuristicFunction(); + } - return new SearchProblem(goalTest, stepCost, successors, heuristic); + public static SpriteGeneratorSearchProblem CreateSearchProblem() + { + return new SpriteGeneratorSearchProblem(); } public static ISearch Create() diff --git a/SpriteCompiler/Problem/SpriteGeneratorState.cs b/SpriteCompiler/Problem/SpriteGeneratorState.cs index 399473b..f17e276 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorState.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorState.cs @@ -16,6 +16,11 @@ // Histogram of all possible words -- includes overlaps, e.g. $11 $11 $11 $11 = ($1111, 3) public static IDictionary DATASET_SOLID_WORDS = null; + public static SpriteGeneratorState Init(IEnumerable bytes) + { + return Init(bytes.Select(x => new SpriteByte((byte)x.Data, (byte)x.Mask, (ushort)x.Offset))); + } + public static SpriteGeneratorState Init(IEnumerable bytes) { DATASET = bytes.OrderBy(x => x.Offset).ToList(); @@ -139,7 +144,9 @@ // Flag that is cleared whenever there is a switch from // 8/16-bit mode. It is reset once a PHA or STA occurs. // A PEA instruction has no effect. This gates allowable - // state transition to prevent long REP/SEP seqences. + // state transition to prevent long REP/SEP seqences by ensuring + // that switching modes (2 cycles) is always treated as more + // expensive than a data store. public bool AllowModeChange { get; set; } public const byte LONG_A = 0x10; diff --git a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs index 478fde8..ecb0ce6 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs @@ -81,7 +81,7 @@ // Get the list of remaining bytes by removing the closed list from the global sprite dataset var open = state.RemainingBytes(); - // If the open list is empty, there can be only one reaons -- we're in an 8-bit + // If the open list is empty, there can be only one reason -- we're in 8-bit // mode. if (!open.Any()) { @@ -107,11 +107,11 @@ // bytes and moving the stack forward a bit can allow the code to reach them without needing // a second stack adjustment. // - // 3. Set the stack to the first, right-most offset that end a sequence of solid bytes + // 3. Set the stack to the first, right-most offset that ends a sequence of solid bytes if (!state.S.IsScreenOffset && state.A.IsScreenOffset && state.LongA) { // If the first byte is within 255 bytes of the accumulator, propose setting - // the stack to the accumulator value + // the stack to the accumulator value, which is the fastest var delta = firstByte.Offset - state.A.Value; if (delta >= 0 && delta < 256) { diff --git a/SpriteCompiler/Program.cs b/SpriteCompiler/Program.cs index cdfc543..157a54a 100644 --- a/SpriteCompiler/Program.cs +++ b/SpriteCompiler/Program.cs @@ -1,6 +1,7 @@ namespace SpriteCompiler { using Fclp; + using SpriteCompiler.Helpers; using SpriteCompiler.Problem; using System; using System.Collections.Generic; @@ -9,6 +10,31 @@ public static class ExtensionMethods { + public static void Dump(this SpriteCompiler.Helpers.BrutalDeluxeClassifier.ByteColor[,] array) + { + var rows = array.GetLength(1); + var cols = array.GetLength(0); + + for (int r = 0; r < rows; r++) + { + for (int c = 0; c < cols; c++) + { + char chr = ' '; + switch (array[c, r]) + { + case BrutalDeluxeClassifier.ByteColor.BLUE: chr = 'B'; break; + case BrutalDeluxeClassifier.ByteColor.GREEN: chr = 'G'; break; + case BrutalDeluxeClassifier.ByteColor.ORANGE: chr = 'O'; break; + case BrutalDeluxeClassifier.ByteColor.PURPLE: chr = 'P'; break; + case BrutalDeluxeClassifier.ByteColor.RED: chr = 'R'; break; + case BrutalDeluxeClassifier.ByteColor.YELLOW: chr = 'Y'; break; + } + Console.Write(chr); + } + Console.Write(Environment.NewLine); + } + } + public static void Dump(this int[,] array) { var rows = array.GetLength(1); @@ -18,13 +44,13 @@ { for (int c = 0; c < cols; c++) { - Console.Write(array[c, r].ToString("X1")); + Console.Write(array[c, r].ToString("X2")); } Console.Write(Environment.NewLine); } } - } + public class ApplicationArguments { public List Data { get; set; } @@ -91,84 +117,16 @@ // Handle the difference command line cases if (!String.IsNullOrEmpty(filename)) { - var palette = new Dictionary(); - int nextIndex = 1; - - // Convert the image / mask to a paletted image var bitmap = new Bitmap(filename); - int[,] data_buffer = new int[bitmap.Width, bitmap.Height]; - int[,] mask_buffer = new int[bitmap.Width, bitmap.Height]; + var record = BrutalDeluxeClassifier.Decompose(bitmap, maskColor); - Console.WriteLine(String.Format(" Image is {0} x {1}", bitmap.Width, bitmap.Height)); - - if (maskColor.HasValue) - { - palette[maskColor.Value] = 0; - } - - for (int r = 0; r < bitmap.Height; r++) - { - for (int w = 0; w < bitmap.Width; w++) - { - var rgb = bitmap.GetPixel(w, r); - - if (!palette.ContainsKey(rgb)) - { - if (palette.Count >= 15) - { - throw new Exception("Image cannot have more than 15 unique colors"); - } - palette[rgb] = nextIndex++; - } - - data_buffer[w, r] = palette[rgb]; - - if (maskColor.HasValue) - { - if (rgb.Equals(maskColor.Value)) - { - data_buffer[w, r] = 0x0; - mask_buffer[w, r] = 0xF; - } - else - { - data_buffer[w, r] = palette[rgb]; - mask_buffer[w, r] = 0x0; - } - } - else - { - data_buffer[w, r] = palette[rgb]; - } - } - } - - data_buffer.Dump(); + record.Data.Dump(); Console.WriteLine(); - mask_buffer.Dump(); + record.Mask.Dump(); + Console.WriteLine(); + record.Classes.Dump(); - // Pair up pixels to build bytes - for (int r = 0; r < bitmap.Height; r++) - { - for (int w = 0; w < bitmap.Width; w += 2) - { - var mask_byte = (byte)((mask_buffer[w, r] << 4) + mask_buffer[w + 1, r]); - var data_byte = (byte)((data_buffer[w, r] << 4) + data_buffer[w + 1, r]); - var offset = (ushort)(r * 160 + (w / 2)); - - // Skip fully transparent bytes - if (mask_byte == 0xFF) - { - continue; - } - - Console.WriteLine(String.Format("Adding ({0:X2}, {1:X2}, {2})", data_byte, mask_byte, offset)); - - sprite.Add(new SpriteByte(data_byte, mask_byte, offset)); - } - } - - initialState = SpriteGeneratorState.Init(sprite); + //initialState = SpriteGeneratorState.Init(sprite); } else if (data.Count == mask.Count) { @@ -179,9 +137,8 @@ initialState = SpriteGeneratorState.Init(data); } - var solution = search.Search(problem, initialState); - - WriteOutSolution(solution); + //var solution = search.Search(problem, initialState); + //WriteOutSolution(solution); } } } diff --git a/SpriteCompiler/Samples/Barbarian.gif b/SpriteCompiler/Samples/Barbarian.gif new file mode 100644 index 0000000000000000000000000000000000000000..b86bdc7fb66a20f6fb3c3841a511210d60bbc36e GIT binary patch literal 40717 zcmWh!S5%YD7X4CbN$3))p$SL}y{e&iq!$J0y%-P>5H%rG2@tAOrArqO5fL@?j-eM( zgCI>%R8;iGc)c^{?L5p{Gka$3v(FwAGhsyQ4B}zo z=H*3kbMXNzX8@Q6z$FcEYe<3sEdbE|H<1PayA8l=2=LhhfIlGN1V~@i2K=?P{jryC zY5V)@_}#!k{w4l}w*S8b;O~0L$-&+KvhQW5fU6;w9emw=)xz#b-l5b&rQgqMwv6_`QxU=%g&iY zE_cVAN*DFAHcZO5jmnSg%Ql_w{j{i>GOb#5tsHl*TG6ZeWLWdvwEaJanl1Z=?=Ee7 zjvdDVSwsHCBfiB8kr{naC4<+?=X^>w0xQS-s+at`USF>sxmGjhTesm`cj(u#=l7UF z(FXp%7ydxk|3jO2*)-YDzRTbLHAVY1#ecgz=0ol6&7OqsHDN#NZ~pD_|JxCtog|63>uD@O7(|;+ee;fN=H4d&m>!0WuS$a0J)-bx$`07j3 z+-}wU-|q1bPiFU?E&S;E`?~Az-*?sNt4)P#k4mSXHjh2+UTkhyecG`6tZTO^eYd%A zx1GG#Rs6mA&QWv0@2-sho|f*mR_^pX*m~Ca^I`SRXSF|iAC2@555AaR>K**>eCYLS z%GkiOS2NFE_cyH!JbnGQ>m#-N?*RF~zS6(1DLeg*+k;OIW?vo;Joq`>`FE!IC$0VO z->#v7q0!NWY1-7-%<9tc@WS}~+nKSowY8DSjgh&n{<*KSujotj8?XPqe*O3F*2vJt z#O#OJ@%5ScpW{PEGXsDBzW(sx1B1bk1OAQw-vxk~0sLhVGHt8qBQbNIbsKG~97yF5 zH!m}7uNulgs|2r&wpWj2V+`&HnRV2RCo>HKi>dY|}cwp#oG^N($Zw)KIug)+^Y?g;OGZ*hxzmc`<(n1GqR zHy}f%BE^4e24B~Qs=IowB}IHAO_5*vpqfa%^GfgiCQfa19mM{y*NSen5ipj|5R$oQ z-K}X5LoQPHiR!j(+Dc_Vjhm;48vG}7^=$?_mzY?CsMGfRIm>9v0I0fu;i#eXs3Q_GiOz8fw|NVWeEk6hGOLzRvA zeI}^`t|6Ed6vy&UT=Ps__%L~FCZA(&mtithSHG)sMad$lRkr|c0du$c6L3?)HVm!iEXcK z@Iv#oqvC>Lp1ri{O!c1#0~`ob1^}Sl!P#k5=TX~%7D<6qXYISJSg1W<2=+s4*SR$w zbPdAitPTjETdMX4`XsVQk=wv*a@R$T1@03g;DxwAx8>{otg`Jx_Zz*w*%3?p3em4~ zL{l!y^&Td|72GVqnrp|Auc(c8#F0{AYdr(6Olvb<50-D0VaRU(%?sUJC?_UM)8)9Q z&M$d#;YPV3)Io{#I`M3V@MYbdwmJ?er)GiZrRVR5dJ8gn(9RM90GJhuRb~Nm1318~ zti6Bh=TpyQ9iClCuMPY>Dvr6#hUx+2{IPu^0|zPzstW zOC3OP;B;0ID(QUgtzUlITv=)fSWm2Fy!ob8%X`^1a8L}THGxwhOF58j^xH7fqKWGC zw+2Y5Am20SA9^ezUEld$E~EeGyK`D#e~#*NeZ?f>RR@d6ma+Z=R?$!sx5{NFz@ONB4W}TrElkJ4Zo{^ zd|^?bXqF*RD9se3Ndk3C5P&^n!fkgHD6(5$v(T$C%57c#oBhUaulhY@)E7!n0;_68 z1lh|4gnZ04qC{0f0Q+AfSuh4q=X1+lTZw^fYb@k@D=PkV+)}PRvyOlrje3Jc@u_ z-~b=mn*Nd^imH=mbF1^q6%x5T2bH4$5*o zQXf@)Ue%}MRe7GHPRBFXKITpi%ur5gNIWs(& z`hu(OJpZK|dn&Nlqz(Q}0$j7hwl{rBp4%D24WMlhk%WyJ8DyD3dLTMR>{IQw@C-l<9mLv>RF5MWUF-apJ0u!2WjSf))zyoym zWx`I?(6R!_1L*ZMn0a1F2vG5gh(3D%XFg z4LmQ9E-JoQF*}>A-3AF>3j`qM0BK?-6ZkI}uA0A&jFI+y&gm%kNaNbYBR{kd`B z_k^raoH+kXAi>Mz2OxUaTYGEqOnv+5CCeFuZY z&NqUjEV8Cbs`=Ll!+B_IM*^v|l_STh6L#IwDno%i?r z3LToD#^ful4D!v~WzJ~VbH{puhiyq<={@wsa;VZU(IT8;fTm)jL(3c=Nn6Zg<@{BA z?ho!~c0Q21b;I>ktLSDx+nu{Bfhiij?POm$1HrhoI&bIR#q>Bm*E5Qg}OzXQ~#O)@*G#s~N8U?Zw>n_0K+TmjCqId=Pn5}w~MdoXaI?VtZM9GgaM)F(07_^!$72)0m?fzo4g&|SHWkb7j`AY zVC7>zr_vwgnD7B1H!3RQXX!ioPOS>HG84dwvWL+e_o??*k*xQoZfO5!^Fq3)qm26% z2ydcpy=cLiMkTzZr#$>fgB=OMcSVCN8D*C34|AqI>+iC~tQ{sxLotH+Qn<7TB=V?- zn(=u?L@VMVSDEBx_x~WbY?2soM|byF*4^$A<=%GJ=f5+6eP9LLTMwTeoQ_-lY341U z8&fT%z)&?qxI(XyR1^AO5R$UgBZV_K-~|T?N&te|;F?tUlhoU{m2Vqv`}TX|Uv3Ng z|1gaNfK`gJ=%WOfAJ~8ftCf_#$rstRE+bzj`0)(Bv_C2}!aBnQxk*8|GNG7F0Vx|W zr~)uMgnMHyurFUp^TP8&;`!3_Xx?Z^E(t|CSjy7`{D>4qR0iQ-u{N@YN=XpsByR(y zPmxg^%!cN(*F4);@*xmWELeB}u}NnW;sX0)txK6AU%p50>&JK%-F&NL@uBk^Z!PE- z4bd$ct&Nsrh*O|pSP0HDV74#W%h}M+`F16f-?o#2@)`88ulyODmdqoC>-owGSSDkY zAj)!Fu1k!!eR>;Q>8_}>CthB(Ek%lEn*yLV582wdF8n7YJ+6OK93&FwqSP97$Gh`( z$_#%T9gN{XZM0cEmmtVI0tewvr*8EStuzc4syP8hS6M@)(^CqNxTmLzeZy5;&mg0Vt9NyH$jyN^@zd$a%VuSJ0ZH z(r6tz+>NFx0eGk4lq5V&&VGZbR6{oX$?ha=k5Prb+$8<&)SrQ}8dR7QM{*1l0>LxD z7*BA$GB}zB_vnY;f@Ieh-Qnp@eHSbFC_lZVI;9o_Ncn(MaaxcSIBYjjL!8WOnhn=V z;XJ0egU^@{%=pul3%=zl0&s;nNgb*j@~LH-Es@PN_l)ZWX&IkzeBiHG`E6-19Iq_Z zmZCsmI+x0i9LNhSE?n!rQzeyOg4Xe91Ak1+2k7x1JMO-kxvMi4XPB01ubG@Nb_Wxu z?Xn`LmkSf;zIsGc4wD}2I>|d$d2KL3m#iRId`J~=t7GZlzX<@{YJp!yXXmIv< zALBR^3;+>0gazTfgDE8**D1m|iA|;%h4U?z)`GBk;*iyjgLMR0W6=57$1t_KjG1dm9~MR+PL;dBgG#p5XE# zWpNBxybU-|a+rTw`H6+AQ7wCD<;tt~7{3TOCjk&|E4n^d8cBm+zy}hmWDm^ln?9~8 z-VF?6;UT>_o=g3$QmK2K*u+|e z@sc9Vph-Jh8;(4T4G>bV9EXRTqw=sU(Q+f=3h(Q@8CTjLwWM4aiRz2-TW zC(<`+J~*@sRbmoCI!k_+hT$O(egmvn>Of6wxpOzA^(()W3o zD_+Y$)kt&>x?oz!-q6jb)Ru6$Qy?U}{mw-;szt;NtuG|VijvQB>1P6c-I+I@4L_}% z5`(K9!c!Mvu6|De8=z4TxWHJhzQSc1r&G@*-4i6s({Yq9{2H#_)H7R(y9S^?&y){d z=Cr%~Oz}pG+>Ij7!!GgH7SHGHC5|83YWE6ew7mnp9DU7GOia&_5e}mOBwkd1o9)X^ zz>N<7yq=@K{DOzG$NYo$CsxBTboEXp40&1eI0Ls10M~FAd+hrDmfqH9^>{0o&++Z9 zp?LR&uFKs{{me9Yx4ufIZ{H(9s!wW{Un$RjKOY7C7V#tiPyqSrkZkwUB01(_-SHTysbdIpg!)Pz%tSr zLlI3+`W(7vm}#3&^f|)>S<$b)z#dW0|98mN3-#?MWULaP(@h<(1v`D_z3&N)r>*fI zzww3HJenuqR&l7|3fC74P>(|)f>19}dGr%oZ1eCXo7X($V+}a`gC`XB#Ko)6hXGs) z@*)*h7cX&P~i@+Ox|}E{s7Yjl=2`rO@ewzOwgX&?&bAUz*-b`G<~ScMQ`L zETTZ|x>yY38Y~)lCs4NT_g%g}9O08Qp>;$05!wDqDvEh5R64?09rc33o^61fp^MRI zC}(;;BXl~fH#o&@4%^F9_!Xjuhj?JzM({Y{59CoS>J=4nK-JZ^pV2#pD9LluLPz~I z2Aj>qc9c{-aZKOnp6V@d8q;QufL+W-wbJPr@r#ib3>2VY<#RuKiBQeFb8$?dlp$bjR$hL81Ew4HZO_b4XZ3D$OC2-@vBk4cY~^ zCAzQIB2JK~Tq=|M}=B;=B7UK7JEq*c3xPIX9d2@=lA@4P`O7lhHNz@;Z4 z`vfMby%iyH_!4qKaFTnrmal6G=1fDpX}dZ>LmX0|-ohOR=DYi(2 z1s+X7jZoipe?jDoAb&n~U3AFY&-lQ0iY6honJlkQ>yXteU}CfasQ2ft|FnT!r-}p< z;PZ5`aSl`z4X&$j!Q%zTSK;v(23>~hZY&0wpB$EL#qtf8W*CFm$9{-jo}QB1R;&DQ zU;ka|(GEiNf-+eo1k3b5@U@KA?|XmTAW&4Me>ZzMm?H` z`%Pai9YW0GgMI1nbBAyco?Q)%TmaZ>_8$ISn}<4g=85c|1J(Q5Fs)bE_k^RCXwWvB z`IBMxHriVg0~O`ry)Wf6NQKSgR*qsN_*<)s=LwK74%qu`u_-DF2B4Oc1*a-db~dt` zt=v&pwtw&~T*#sOw?o`1Ea7(-G!uba969G28IV|jZTb)~Lq{A?V7GL?0G8YOI;#dR zcyIvI6#@`8fE=OwEgeFg+EC|_^EuDq=jo>UkCXnOcIz2pdp`~?H}l&J!8PfSE=i_Y zTENLE)E69VmOf{ti(0|4c+v?re>b4lF&oya=Qxu>^v17IQCl$N0+!v8zNABi$JD8> zYao7BAlJ(pEv|ClvW%_&ybky~qd{P5$EA(XJbqGGT(Pir8|1tMs+7VM@&yt&_p+ep zb+4$0#Q9^Uh*S6O3twA@zsN464B!M{&w;#%My%(R`NJ6FNLizdZ0q@ zo4Q;!hZ^SsIsWIX_eNxXweZd5nT?TbuDh8MGPcOEoHLHC`KFwk7DY;4jAPLp%Q7oO zcZ9fSa)ux#IK1swyMJbpy6;}#6AP*QPrC+R0$7r?T&u5C!v*VGC5O^OE|{6E*^Sic^Xx@`E%i%TR7Ro>q0f_ zi>Nq4uO0-Z*AvA!_@s9uC426!O5lBtG`KwfV@17Mr@EWb?MnsZ2-$n?ub&uCT^OXa z=j~lliCm#cay~NXLJ4y!W#kO-TqmkMlwV)Xg)RqJ zeO)^Q)v!N{c$*dEb6xQ?03GeNtU5hy@fk<%inn}{-*Kl|2P%GIqB1#8vqWNRY)l04 z{hW3Dfn|NL$LdsyxG)&qtx$fx>Z6tZQTSz=9(;tvF+4zUsb*3-ERrbm%N0!(cAK^F zNmUEXRXD5ELm>%sRC}f7F;AzBJ!(FX#oX&@;A1+Y*HX zs=RPRK2(OSN!Eb`*9I9oG@mP(e=5{QOpodL%*A&Ko8gFB-!cC(zb(W_aZIlCmkjOj znKMH8l;Z6>3X9l=i-r@d4=wmbRib3=EMR6#wsSb3JR+Z8s_e`rb9AO~V57|0l4<(C ze#fXNh5)@aTj}WBvf4o#4kf3xKQ!iTavfL5s}>Gm#$d|u%Pc^n`W6lV!iKfrc9DRK zC)*T-hvXU>X3uizVdI-z3B8t)hzISuye zv_k&=>g|zn)=T>QWGAI~ofq{8<*l^DJ26S35BS4c>F>!H!-Yv{Hd5pOo2v~YR&;q@G*f1AMYSY{s%PS?X6Rn+`=GCy z)A4Iei1tAvm_bYxoTY5I-PNh7D?d|LB+jm-nN!Da+DG(bSCAwB4(s+B&pfDwEbv)o z367*8uwpnI5$NPh0bPG(14%6z#@<9uNkbt-fP%uXDz-MSeWlzpijRCtnf|FX9K;mF zq)yrQW{oOw{fQ3vKT$iAjqwwR86~C&d9mU!UovlM{9aM~?D$ys_Ty}pw9`?Pj~TL_ zEGBR8t1C6E=Vy1`vn`rjP7w^F=Tsww*KAnA4$fIef}y8B{3AGmyl)da9;o_+ft`fF6XYx$=)3CjM&3Ef00U|F49)HS;ND|y+9%(hg?0te88^s0R&B_*ik`s&xA#+8{Yqy}yP491(NiDKB@yZZ#z=qapQmMYqT z27EL^>a{nV8Q~bQgU|qd!Ag8&=8>?_?@_o=GYEM#a#*pKm!Q}ydRBWGED~RrdFKxz zW2({@V5xnU6L--O?@(>>dkMx}K5=c`CY3k$Fh#Iik12pr;Q1y{yNkT z7npM8O06%cUS5&=8VzD^f!Kh*Qj^u2^_Ytt43ZVPjcV{-*F>NY{?=l3t}ff4hxq{l zAx}B|3$yh-uv@QKIRJ?YV(~4g0l51Y)whe76Vdy?NW@jY6*o^nE->4@pa6@)-|oLm znr7$Pfi0>XW--U&1p3FC4sGWvJMsfnC4)p#)WezZcrFo6Y-)Ge7bbnoyC~(l`p6%Z zs5Jx?LOry8@Hpz;(V(#1Tvv)$Pi2DL4ZxNO3qa|8aMfKfl;dzg2P_Xn(i831f6!jH ztF~}E?ix|?NpkqsXz@%O-#?+ZzO~ORMu-sv?xis)@6Z!p`HFEZ?b-44ceaIfms(Yd z39BJe&J=wqz)$a_=nN9;@k${h`91ToU$fe1hT}*A2CBcM-0Yo6NM6LH=njg4;6|%k z+NJY%8z0}h6ZX+)593^MGnzFPAPL6MNE3Lk`s)rjUVOaqd~JBA5p==}8S>`!`+DS% z5J9X`UsMUY?aWy$=RZnMmOc+p3>sDF($}#HdffT^`?%%JEd3PK1U*y>HxB>X}A#DIY@p)2GX;WB%yx=uYEM@rsCfzcKZ6ggdYca(M>R=mZVea6?YRbpe9_ znJG(N`7!UtVOeRq5@a;dy-%>9T6K{Kl<1!x#6vXq)0zJMry6avzhP41n!rj40XP7- zh_p;VKc~dKA^MEEDIHW|K;`+d;}v*5iWZn0BmXgUJF-w3obdfS5A|2@WVaVGl-4hj zRuUv6iSBz=`#q5%Z)aIyMMTWVOWmEL0JcqcYlfbOwne|8UB2Ga^WE^e--{l?%6W_f z-<{T6x%zsaM|w=|rX;>x0uvtokiJ{|gF4sq1!9zh@NKixQXJQY!wCp~)z|#QHi**X zde$45R^oi-a_Q?wPr~Srz+~!$-4b7>@a4}SN`tpjfSlRi7uzix7KfK~4v;}u=A>7L z_84f#+u0$+-aI`;AwLa)}LiGmg<6o)4Ycha^5OjvuT4 z`CB~NK1h5+y~ku)fxPKvM^-d66|^~r45&Ciy+3`#uJX5F>OzIk+2soWIZVCFr5adi)7}Pz-5T z8)Kb!MtDtcO18NyM_@WmPgkH{G)hm*kic9+7Iy1bwa_1^PsALl$w=s*h01!67|%wk zaJ&+#XWaA;9|@gB7$^xDc&Oy2;h1i_vy(@qn_MOOJf6(Kt2XR@dlbFOgcyU z1=Io9$44X5gmY2CP*3i{!vPihM|$~EhCHWl+b~6;VP<89=FNr{y@uw7OwpzTZ{r6| z2MuiyLzZiXnn#AVxCzZ8++*v>vl2+<{O9B82b(yheM6S_6L3UheM-= zgXK)F>6%1Ynnb&q#Dtn$PcVrko5Yow#5bGV=ru{0G`YEEa%mtfkSZ`v_< zO22#5(1v(BZQ^#(L{Gj&k1jbm)S@$C!oS_3m&dY4VzR^2f*Li^m}K#Ma^l(AgdqU% zxJ)+fObnh_j7RYfs7^l1pPVGK44zoNG|Zn0wKVM>y|zPOIMF9)o#uHmQzIv-b3A4X zZWD<~&>pfSU`lX8kLncxj@7sbG!yjUgz6W4gd_`ay5n zwPONBXf%vA{c?Z$px3h4#dK}o^1b0SUC8>a>Z^^=>CMj64@Xnm^JzO})9aIFA1BlH z5f=|ntUn7`Q3#`7$rpcGUVQI+aqGm&(Pnxi@Z$EQ*-iptx5oM;>eWY{@xPWtMwBU# z$?|n_3T|aXubKWXVFUA{eG8rbHJSFi#s)Wfk-U8IFXC0wJ}sNWhQrDhCOPvSVfznf zOzyViz?uINY}poGA>P;&?KA1QSXB?&LX9qpWHM&KuvsYYYzAg_Pj?n=HH(lpKNCHh zZ8P@0mwC-{EaL>uBt1Q*YR`+apN#q^KPRc5Gi75oGqg!oE3l%60&RoVpC_jsndjNj z^SMXX)e`nf1ydT|?K{`z4D0QUljqWP?M-Uu8c$Mm{-o-@u{YhByD&sEP;7up`&pQ_XXoHM|F8~mzH_& z0xPqEQLBS_H`qevp`-n|`3If0**1<^(GF_o7Q?LUi^>-KjV1zy9M8X5u#sP|g)Mqo z&3pY>H2Lo2>h9!MJ{R%W(OS*f{ezLOqnEV|G0*avOZ(R{z}%RlX{C(``*{^@Ze{HOIHF4ptYe)^0_+KWVv4M7VDqOl;P^8DF^4 zzi{!L-y3?tm8se*SsXSg&nLdQk3MyHchus3^x+MujC9-6uDf=w>J9U<(B$yo(%$o_ zZkqZO`~6*&q-QZNJ+X%VM$Pj$Y!rGb>GwI0 zlQ7F^!z&K8tY1&wL2g<8fVm+*T}j`kv-{J@cu&F~%OV&0saCqi)H>n?ohR$vukW5h z)0P6C7E{*d8Gef_)?RG4-hqEG){(c?FBo{0c#lN+QF%B);l&zO z#D{Nse59M$0v+p8ohX?%muQ`oLyal_c%KaOQTXAV*t{Wa?9=MzQ(|SQac}IAM21%D zgzlA99b;dmMV}k=4Hc2G3->nEhsP`Ctxm{3O@Obl`bLVouPJ=RhT&%BEPyt#mz?grZfD5A!^FCb=(fVX};*!yy^GY zO?0_sBp_uZ@ZLyJ%t&z7NQjfT*{!9m?>^ziBVp%hG2h?5mYIu01YA315q0LBuG_Z2 zr#F%Q+g*{{5&o}Zub9L$K5Zprncn&oaGMc8Km`&-0uvPiNp-BIRJ#<*K&M5M6ze5I zLg20Zz}s~@F@~lc2La^Psf4w_xGj_H#SzNZc21!QXj)Sn#PWDOs%*Ce9$Y*eRL-!kWCR6&+O1R%s(u^P=^I=N4?qD+ zMN?Z{drqcwt4jx$n%=s+KR1$fvPVINT$>MWEu3%v}0-g!aCW8oXyZ;=g~RYGFt{w3zYH zJZx^F&SawX%}8r4V-)2)4G$Yn8F=+JWb%B-)R}`d-q7gDo$0!*MbwH;LD<}tgKMo* zE9$$eU5;x`VeyFYOlz&(Jr%xs{!?Z_cunP} zIpc#*!{HlQp`TJ#wy%tQh3*{P+j)Mn{r&C8&rjRO;Un1dW4BsAbG4eB{Mi1pIKtT4 z{(C+UbQ%v<#6vFNp%?Km4?O%Tp6NE8IUCP%AJ6&_&-N0Jc!fv4!=paq+5h4>*dsVk zM{p@da9@>1PawW(35(!J+@(#jo~RO9#aT@v=P}p|oY-n1T!I-1v?p_G5i&pnzeFgO$>GI=WG@Oyq^IqO-9NoNRJW0dVUJXp)XF=Il(=wB z_2r$3*zS@vR9kh+eGm}ik}6{g=A!|87_dA?c_~pYFVOI`P&Sq?GPtUv1vpZ@7-h7b zHgTL0Zx7L=gSGHPPIMGE0FqrI@D_pRnZXFLhr2j6shT@+0#Wwt*JLw#j69+xt{&Mq zL*#+h5oRKXCJ2ou@?lByJ7AGR4MawJyiVUE+wa4m7JH4O7@ugrfM}nP_ImmQ&D&&* z29Z0KbtWSXgRiPw|EerVvb~!gBpBuUSO3cj>Pr<%wF?Q+FQnC74$z^hntgiacZL`~ z>0%z0wX3RcyMOv=n9f(SAQIbEv1ck+A~`Tb{|fj^Dpp7Qvc&@|!Jm49ED?X7 z33it0u75#4ldUu*b+Zu|@#HBp$i{5W`k{6q_S@;d?Q6NG3O`>@>D2K^f2!q~y~qzj z14IKX)PMj_T_)CGS;x4t=GMQI9RDaPQqR!%K56lzt_f3?u2khoQa?=0hX~M^0o`LZ z(Pbh-tJ+}u`WIV)QS|jTTvRcWDO#{dmyj4oNwKO(yp&4h8ij`wQhe%16)~)jW{S8w zpY51(mDt3O@T&&$AJxwE^UiW=&qWH^Km>3k88nEq?F&YV$aVNsgB*Hxjkz z5XIvr1?nhKe%`9rSpmI20uF;3Q_gM=p9x0qf=svz`r6$BA%r8H;6C};hB-X z8=_I5_&A2apZSL)Mh5re&?$a4DR^4SMg6;>ra7BH8UR{!_jxe2f(4jc=}*7b2&={X zE_CDn{^|5ESwJIlf9H$%C`EXo-qk;n-z*mZ%5N%k#G#H%ATs6=(j&vz_0JTtdtXIbbbU@M-&lzsZbBBkLG(D2Xikq{=mi128Z z@%Y;8@E)lp=6pp)mJ?+52dT5_J|Cr(*y3TBIgTZeuxN zYMEtPlaBTVyPhek$X8iM%w=&^IOv(2GK&%&7P7Y#W#SJ%-0{B{WM{gX-0WDiT9f}t zVi6T$N&&!-Nkg3a6nPE-0pz@fn8h(1CIZ1bT|SrZB}V&F&W5>cnqP=%Y_j&*`@zkx zfSwsfooDV#7Gjno5WuXK?Lz|Ie|+<_!?-?H*S>9dyyf)fmaN_RehOqta}9S5p}MJA zjRXNjHegq=Gyno_7*!b}daUUUSA{e)Y^xmRkT+UQ3eBbM>(JpY>&k2@lR0Qlfk`-r zPpqv8(8r=d_IW{3nbcPMs$(Rg0rVNOcVC(0F44PJAsTGVTKaKPJAJ71n)k{4&{Y{Q z4hLc*aEjT^tt4eVId}V#N}G0*-r^-2iz!>~U9_sDrkr-21LZhZEs@Q3`O6T$Zjs(F z8vc;8Q9yUCt90nHPP6}@RIIaT?&aGODT2ZK9A`Pa3ARiCqJDfOA5(VskPd!s1Kbq?d9n&VP|R z7WQx;vdMk40TV=1O!pr!i!;gy>MdR0DtydC$U$?T&V5EE49dD6zcK4!zuIH$?aMZZ zN_T!bc5X;+1F4b1TsOs1u-(_r(bG~1iS5x)Nrkc4*XiW}&r`LrEB+C(0EC~4g$<-t zT>N>5aA4Z6N{aZLu(M(M`M0q`Wbz2<+yD_hdzgF<2LfRaxtTpN?c7u#%-(bW+3}$b zs=R-LUg~u`*gtU1YMV*F9*ORtp}Y*M(;56sAXB;&HOD@RzoCJ$ zB!T$C5|b!<*oVBZ@;)3bJ*e|_+KaP#Ipl=doJ=vad@M>5a%%%qip^!rS(rcwj%0Q8 zT+w}@I^+H!E>HK&q$Slie+g|V>Ir&~CeS}8ceMKXD3HIV=fGVAJpZ&CS9ZyXsMPgz zE8!R-dl?$0W%)kSXEXyyZ7T+>DJbZq9@7PuMARBbN_?S`TVuOx%o(TiUZ`u@3p@}3 zCpuWRC|GhD!vyJ8LWR-ZBNjS&&rPLHg;u5OXnpht;>Ox08rI=qqXWzUCJ{5l3ddj= zdd#^vs{lv)GGC#sa~dm8$nn?G-US=*ZP}zwCT*a{(bB3FX22tmC$v4(gm(*1+YmB=(I;#amYT~n&zejER z%swE){%O!)d?H|~7%y%r0f1?#*bfBc0R$Y@wY|u0%xkKiA-fAs9Z|Ft75;@bE`QG} z{iU36u|4c(C4D^4`D`B_h?LUp8%9rehsRusYyH{ zAmO1E(#ltRPMyPALui1=5sNc@DVYrV>B*#H*Js_^^Any?An!0I;qf;9+w;WIQh`+h ze7U>=>f@e?LkTvLo)Uwe=(}6DH+U`$7mP#;pHb`Qw7H6?Mn!7&iJs&uf(YuJpdGAg zMIU>VoE|D?jTjl$0q4E3XHxgYT1xy!8w=+pXVH$!m^btk!5S}C@wTrs{Z5J&v0|)o z0OZD1Aobq8mDU@A?hki9YJE!715G)wBD{cthWd+49;~*4j8*?OocM8z&lPO;oS2V| zcD2!4mtkxjJ(=S;z46!hMWi!VmcLN-KY`Hb)FwTZ!|DsEidI8$x>#!89|l!{5JT%TcZqr#ZQg;Kpnq&Wzwrk7?9g-<^V4p%>19N7G< z@{NcV;~@A;3`h(frYQY$Nc(F-PJ?CA)K76|g|ugXEJ2)CO6a$^d2_0*k7FDnobfo{;`>8;O_D$G%f=(s2J$nc>HwBpBlk>5KJg zD4>QFc{X`b3lHffLkMz00UW^Rr=|obVRka-+jp**K%Pqgcg`T4$;cc%3eq`F)TH)T z51Laf-yYH5%$nMDrS%CL>gfa;1ik^w(>$O*(*C`Zc?By~u(^+3x%N4RT=k?zRT0(w z^mm3cTV^@;eIZ)R5MWe~_@NRJZj|DdW_F1F)^Jb!2l+i_sPk#68G1l7L2TpC$PHSC z2N~iiM^Yc`KNNO>gYR`C%ZzQkMqNdJr-3r3^yFf_91@6ZgRfaS*N!&(YT z?+XR8pm8=r@oDVI*$k<3mME0{+Ojdy(hN6nGt}w6P`@aJz+6O7uOB}rM3irE zQUSj~%zDCpPd$w&qj&e68tuC(;aLWxXs8-Y%`ib|CaRhSkmDhbScql~iBEG~b(K3- zuGnhKd%eXcK8cyFjmqOmypgUa(hYoAVf&CtKn@}VPpIqrfLT-yd_BvsYDA^5*w@Ta z_Li;MJ)%&zzIr{+GwR3$3xPQwU?xy`I0;xBN#&53)BfIE+egQkZ)VKVf_h&!dDD_soPQtFS(IT$p0V0~T2v@N)#Iz^9J!muk{;_|FLNf2!Q;Q6 z8JJj=-BRBtv^H>3=B9`@X2uxBN^+IUZ~(!Gu_s=aoDPp$%nI@)rK;=wk1^xhNsv?i@R;D42W}YzPt&+A&)-UO$xa+G@BLIakYy z;uv(;E3;+Fa(h};Ql=+R1G=+TyscDuXPyl#K^2Mxl|`1K z0S=T|6sC+btN~n(trNt*fOer6tMD~+QE25>@t7Q?< zqo3SWHTwxTA%II)Z-vq>A@G+F56!e6tXme{b>OY5;_5*u<79ezcZ zIv!w999VY&-?c6qqdg%s;>;ty5T&yw56`~zJi{KM@CGO1rl-9IQR#J8K2gwg0iOvZ zX&(3A=p5Bdf}Hg=DneY-xt(EUvp%q?B+~!QGLtB{LkM@f7=8rW=b>6M8>s9XsP)bO z33@H<#JflJ%8FO1eajjeP4ls4>VhkX(T1>P)Ym(aUwFAcmUQU6R5^Wn=34v?yUP)J z4N!iguamFjiHG<=al(e6e}6)5^a^XXk%Vw9Va-uVoew0f`BL|747fShsXXkHGyK$P zLCzXcA%_R>3Um>}Y3;D&qTpeoc_SSb{c72oC~WKbzSC-7_2SRYR~?Er14m5fq=2Oq zNNJ(59Y|-?EK3^#S~C?Q2X9o^sdO4R-7rhXN4x1pbqJUxd_o3Jzyj(vAv$v)Ef=Cm>RjVm$bFl+Pym`=bag%`O86WiCB{qmA zvw^mo-s$BkG@aVK8pHBBZ1m5R+}?Mtk8ohjN>^<`q%m-)reby+Yt0*4DtWKuUeU` zNros=Na|Drp~D{n^xWI`J2w086E2_Cv>%W0v$*TnoydkIJ$h7RbWAFpj#Q+f$#w#x z#9Mm<*m{e}DjuyeF;4COadaO3RKM>Ze;;R&gJWgaF+y3#-W+>#vW4uGb!;*^94qsP zL?l}pw#;;jjE}vyjv^zoRFY0V-``*Geq4{oecjjldfl(Am1Oyhdr-EI z`_%%+5C2U*n5pF))DV>+NS!(>R8oVm%L*5>a}B*>WR3fQ4lW_zs}$VF4I>#oCb`mUu8)@5b0be_u?kce@l3uWzAH-6`BUCs zT=UJC^(wMBo^=IWUUDp@0A8bkQxq+)ehMZi+iEy0_sjY2d6`R{h)1=%+-?3SS3Q2$ zY~=fETqpNTJXKyzF}J+}@%_z=`Exu$W>AYKQsZi<2HIZo^)%x99sM!-_JGehiMY?m$V!99e!tdXIM2ugh zBNSJPq1y|oSb*5(eb&9A6%q^LcK0|+#Ahb@nwL`8r!4=}_{(*dx(oVLXFy-*2nNPS zYGYY<&AX{I_|^`B`f3LHg#NZjs19%S!7|^O6@zdBI?|;pAO~T<0rc$QkM+t8SM@jg z-pmA8dmxj7@8tPylX$sq%5W!;D&ro;M%GYL=&ctDP-r2r&Rm+bkFKzo|0ojm@z>zg z{EraUo`n=`n4>5|Y5d?qVcD2A5P5?`O46Y3wrR?nDvL{t|3schoXrX6|9qx#abGHk zYDqvW%R;DDDv4B;VCtS#4KUt-A@0Kq$U2AgB{P|6ts9>Lo3J9)v^M)uG=WfQ;!GJT$YJMx@aa*%|ElN(8_oO0KzaV zwsQQ@b&!JKX)vYNL`zmZ?imLdlFIo|#477<8`9IbP@1u+B`V~aPMT=gZeMNxHIp)} z`jP6Z|F2Qv2` z1g!T~xX_R7-&_C<@+sU)8g>KiV_-@A1#95ao!uk3)}Q#Y`OzGB5j!F3lW}uJ3bd!BGnnx$uQ}X<3#KLdJD~U9ZsnKm*7Y z4xC{UE3ytXl1O{zn=@Ya{3Kvp(@SZlMVmFVHvQUSds(_V69yIEm=B@i!vxsAgilzf zng6FZ?t0R~(kL@4{v3}4X9M4clH+mI0Ko8HAMW#zY(Lf*p-4skB0@z)Bp{gVsw640 zm|sjMW>z%z)?$|6ki1(vFIQ=o{b=uK!R7AS5}IWAA~6|P(=3tYCT`3-2Ie$B!f*P* zMIWvemO{#DO&5lei#*w}aFEm-zR5&*b-@Lp=<4Ii;hlURZs+m+LweO$D*I!z*M7VC zv;G~U-(3B5l4&<$h93B5%Jp4%E@@f6()B)%RJmgrWh2(nl6zCI(2KwV=3@xY;t+E7 ze-2d8=3Gg8^wPP8*Q?O|xn0)FJo7KVwL4(b3!h5=9sl-fWaZ;>YNq{9sp{D~eK&)L z+N#eD9+q<3?LVGaQ#>TNTJY2O_izdohkj|dmqQr__hvfs7^m!d9rz@4RJ<0aM2Xcy z!OI=pzrJ)G#;g5f_io;-v#0z0N$yo;la`QoXuf!je3e|eH@w)5RQ4rbEwU=g;bADg zA{{PBmBMWg)<-RlImz46Jz43Lml&(Dvac7^dAQs^^&v}jZ(d&KZ$R-a`Kw_QAK?Ga zw%|&q=^_9MQyeX~&TpQwE#4$ANy0kEK~NYF6}I81BhasQ_kPRC`^&qdS!VKfBl_Fn zEJbCI+X?aHriJ;Qtw}M7n>T3k%4L#Gjz2_ed?mwb!w709XMnj(pQ&%6I_Dg3;r=@w zxiFCgG2XDg{9E(8N-7C*11Xze=NxXE%Kxz7=)U0jJBU~$FQQ4?YnMX1ddXt2c^{fA zuo?Y{#&Jtcobx8i>p~7kfdOcxizJ=FJVQ7MCb@#5#*jc^n96g8wMi#GKODW{s1xerU z)}D}JZ8LsoJR#tQuQYjXz+@md9g<(^)$>?j^!SPCwK;c+ft*>BiI|c_nfE!oW&)K2 zAW$^VweEM~g!RrYOt@|+0(Zdgf(wyVt!rwlBIdWzv3clqUX7lI z_Y|7e@@@&NC(&2GeMdY>*9&vio!o|nDC&YnIMxtfn#W}DWeg7bTAT+M7<$CzlJ&%W zDkdTaSE$BwMoR8W48;x*!zhZ>;IfBL&44+7yzmQ$HcP2kb8prEttlYbb z6I{94P$uAesu2L%F!`kefZ#~-w`ALHh(lWDqYh*HWBw(o%jlLv6Cldb(*EBW+kKYx zL+{hyl|u2n3{I(FB5K+hqOC#iM5eMj^;!Xi=wX1LW-iTPuq(J~S|8#j$CNYSCt%R? z&5@M0iB4(De28$HP2h^dfuFxze?O_QaZjjr(rC1vPeU`1h_%B^ilZ2Yt+d$A&~S+p z7h0cf%?E4wZYL`zZz-)!P8jMcX%+IDXEXPKcmy8W@Q~{1om9WL1p0fZUv_rYUrkp0 z`W$5kdyYNa5Qodo>i;-EOeorVN{PJ}WXWReeoxIs@zqv*_Hg8)HD}2HE{PbsT(WGS zx?YY=5x{}y%Mz$L&e5>rB}PMkZyC-uop~$x=+#}!fUvitul+~P`HneRrI}+-NdMir z&dWa=B36C*sfrectmW!s_to4*xRyQy{H*uj@%PP6Smb0KY_5Of=e9L^l@dA7KI9k> z@|0_JhrUg>_ens++vU}-x^7+Tj)!-L|15lZ{i|nIbwB1i7x|FX_Iig07tP?XOk;EU zo?{J{qR#z^@zH7D_1i(|PTZe4=1=<-*MhR|ac}Ul{T?*99b6L25B-T&)Wp_(dq`Wk zDIu>v;$a$6$k6wNArNm0P|AqKLK|BXif}l9j(0qF+tyLPJ9QypzKwC)V%brgu(*<2 z+9tT8tEV*CIu=?}acvhUP#S(gpZlU&@T<|**(6+(nx$Pu47=4aKHNiW&K2CZw^7nq zp^qp}jNOBGC~Ls(6?9)M*#`76A_sgCwbQGImf77C^L4-KgyN1RYtL3`v-veoR2WVF zovUm9W6(e8zMA0N?dUr8yR-SuNmcfnX|sRXGxa^fMfYMgl+-I0|9$>N#j6bqcgmY% zySg^>@i1}nzBG{RtHu7*>=nG+OXX_MLekvY2)qcgM0% zX6jk@4*!0V`2H=B>enG<4+LJU|99!rKPoh(FAJb82QlM7U+X~QpFoplRM)Y9#1T{$ z%X$aAr9ujFt zM46(YiAPsm>!`DlybsWqmg}iD&1imPBm8kRLFgk``6c)S+yqT+F>%GZ;m^o8bbjLR z<0FJLnojWuRx?3&oeGFLy7U=MyM?3vN<8=E+m#ALjWO)rJ>cSYrmTrGSvfOxH5>>u zg6>l2nb0{$qU3F)2CL}g$;u%!Lpi85kae&g^oJMOQg?iX@xpP)Y}3dj!FZsO!=x<9 zrg4)cD2wIaJC>V_OqPvwbjZu{Sd1pGT1;|3%0b2BPz=N!MAKzvsjFd)SE7S><8NL~ zVf=E&c#+dQ&|n^AbHU2-V_@RX`ye*YDNc8(U2pTvxPa{z@h@X#U^US1sUUtX#vO_h z`SSRu(>;)o?*o>nO+pr?HyDCZalwqa4}{Cch3AiNJY*6JeSi!Jz7Z{TBjSPRYU7RA z2Vi{DZep+qn29snoQWm?8wCV^_%!dM!}N@ z6849p15=XI55!i@HxaoajMDscNKv~$sj_c^;AYW(Qr{Ywu1Z>n%T9?XhFtGGl2R|_ zyO|{=KThQ;4fUMn#NL#HPD7TPU=kt13?bAV=q=rGX{Tv9uW8X-i(kRH$KlPQc9ZfS zr4*z?6n2P*=wQJ$Cb_Wha%B$`Yo-(xZz@)WfLqNK6mt~5(j#~osC%0we5WbOK_O~U zA?R^tS^IBF%@5AlnMeTWRPvW%X+@<3MWUYJ!<>$h*>zl*<_r%>5u)8C5zX5rgv7hz>nA$x53 zkT0Rt>P73fI@Z0hhxP-r_K#Q{OlEnyW=*PQ9c#l5zl|LWOWLn9+O4d8G?Mc&oAU@X^-jt6rlik#XJ7Wowf1?G z??YQ<+iLNp17Pw(3BjhC;z8}mQi64%Sj_f?kO=#ld_G4<1{^GpBfCvR$<9qvE+ z(<$Y(zm9mo{qTUR2iMzpkB_E4^=JS;NaD*!0pI7;`NeUYvTlEV21ZGskLPgG;XzOv ze@1yY&0!GNBQHfV+mN}N>>V~;zZfu{d+gLI4VPzp9vtLeSce!TP{7qF`N+VCsAxv!*7@+O;?Y7fcNtUfK6zyLiY-<)ASPa(62o&4({&s@6cIi7 z%=ev5lyAgcgX6fJU$GXKMUQ_){djcaw>$U7fe?s%Nu@lo*gZ~vqj z#(Cd5<=)ZfyQkjqksFEa-uK&mvf1zF+TMRuaKC8b{!{j}3fr`rg0vS4Y4z;s&9>=n z1?gQ2>2KLHdTlcX3NnTlGRE02B-PCMf=oNG?Gzq5;oO1K<*yd;eD3PG{RMvhYTbDQ z5|EJ1agg~rGE1W(t7=$tYA1n3h{!gU4dVC&v&*`=OFXpA5us3PL7wN>(OFR^TZ53;8 zM^y%QWLs?ztv}`4yf0u4cx3cD@1EVmq{WBKLf6bSOCQ@+91T@lx)!!}1YZ6OA9IFU z1BlX%Ji$*r4mao=s`a%uXW6qbchaw!Iu}~;m9Uu>4=ol?+KtRbMJ;tC`@x=SL>1Bb zyx#p?!ZBL<)}Z_Jck$n?r&c1RtA(YG9ERTtbHPa^52FfYl>(c-AZ}y8B+|mad$6i;~~bR5)%?c z7p2af04LVIU`)j9anv7omUPsv-yV87dF$2U8DxdCj^Bgs%iR*bgSzjx>K_)?vyRnQ zkYA48cs1Yo>Z4K}1J?sn*UH^n10_j~T+2n-!(+yoh;0%yjZCX;RF!gFX;cyVN8z z^eg$GoHH2RbP|=8pz~%ins22DAlvus6y4wyD(|Q5Z1j3N5M4H`ygp`6n(un^QMqTU ztB2z}8KZ^xRwRSG|2E`LiH?exX06nJzb~oX;d>9_z95Ix#a}+dB)+8i@J=~|oBHJ} z>FsrgbxRcn+m#O>px>jo-`}C|=+8S7uYn6ltE1}>Hj$19$QU4x$Uk?$UE z0)NND?#}oacF&Lzmty0n@W5ah`-2pKg+!Z|=*j(Ns@DB&O$wi|>a3*Kto@TwC)GKx zRck+<`B#NQysO0i^O+tljx7;I>r}T_7gtwlfwj#iYx}X1N2;+`98199B{QB`yH{D^WW+b}C1)}Ok_r@n zI3d)`FE0oI2#0)w(vv54#Kl?)NOhuh)MNqJv?|$jyz<4P6}10r!upUDS*O_*th$Ui*wK#Vmt{Ft~05M(z~l~Gr5)To>NOz4(13LC0G)eNKGD4 zn@$H9fO*F(4l3;~oU9ysq7t3eJy~T6+@Yf3)P6=R(@JH!eyi`xJUj=><2E5DB3QSvTpQT5$vkM#l(x+Wg(*C#uRD$655 z2sWZgPjCP7$4T|LfDfDhwKhK?VPCb-akO9oii&0f^Vt~i*~98`Y)_^* z8tzAVU~O)_vE)hQ30sCXFPGZeui^|+TaYILB&=j&|2Z06;FR)nFH?sZm9b3`seW-tRx z5Ad;D{*NN6Nu(C!)}oi*#~=5yyZH@yI0?Uzv3Od?k+_)^bpx);;;fmADD*6^t-dM9 z&4*B=46R6~1Py8^K68yW-4=?mr+HlKdi#o!XQ3Ar@ylcX9*(Wzu({TL2D9YnJ9#l@ zLvy}Q|I_oevLQ8_6?~SdFtOp|2HxWZ@wfyU1O|rCab3ZC)jzAf$y|4jYS)}!gvaxh zk3}D|s7#QdHI{miN#8E5NJBb<`?4cvz^lbich*CPBa5o?3+9w(>o@7`{WJyyl~K)O znj-sjcV0I3a?VmYOyw=aRr&Ww91n|pP&P?0lNRCO=KxK~4*O}0Nrf0I*cAuC-drha zy>86W4CP6tjQLqJ@24V0r6;{%Bx?S_mOUD@iXk37u;K>ia3}th7+0C~b9p+o`SLSm z%-RgvG&oE&4y`kIF#-&W_ns3t-LzGX=daks1lA~g{o}tP{gHn}YK>i*9{iXQt4R$K zy{ownI9C%zypND>v%xmqR&%UtnGtXAnjK84_MAR3mPr_3c~4CRI9pN+Aju=~>)lc* z>`X>u9r|qZnbKe4!yURzWB$;!lOMCjofh6tX*gBLRKJqQ?pCZ9eaN+_WLsk&@@DD_ zi+n}5)S67i;x3rG#8(3%^LbQ?+(&?KEd7d8?%y2HX8-wEPa;+ib+=-qL9hsgKQ0Tf zM_l+Y^BL)*M#^ZLhd-Kr@@oD)S+Cg@{c)px;O${jHb5o%k!I{s&yUtXIweY^ErY1H z?C$LTU8%;amtKWsfW--*iuC)FOg8vB?_|&bR#(Q9u?wa&A#hwo-t^Q|Kik<$3RX@w zy5rr;NN(`f^C3y)fwFAyWqiixw-|#G`A33AeYIE&5H^}xxjI2kAN z;a)^@fQ+5no9>fQqmli5%@av-E$_0Wiv(=YUPF$JOwRb2WR$AiU=xQxPEBZKF8+ux zsPj!AGR7l`w5@48mcccesw-0>nT3iUdzX} zNO@!83=`)lDn*)<)Bio5{}C*)U!n9Jec+r&encWl@q0l>$Tw++kx)UIDgzVW$3W52 zMz#FYEilT~y|ks=ezVhj3@=V7mrkR(SNH;c`;B>eYh?JKY;kUsF`g;O1hT(Jx3O+qjGaOxM_6lOsZGu1Hd zJLW8NDffqroJC;fSBI~6ZM-(Z>SK8ZoWRPqdF%uJWPw*7`hCVeSXBe!;UMGX+{hmJ-E!r#U_{4gW^yzQa@Etgt#h77%XaU?>B`@qwSS(N z-sAAK#_!|llo#GHjNp@cSxK36xM~UV1_E$cr}grlVi#vxS9t}09&;X$?7Ic#C1Mdi zSkexHtP8{NSB?Qbph_Z)=bY)i{FjvsN@JhibJOenDog*94PAkZ0?@Ur{`;%7u9v$` z`55eby}gvyAA4H<$DSp?b@a(aS{MbSfC2DNUYX!LQ9$&|JI6WTv*1%!^g?>mJ*b%c z)eZInpJyS))c7s$#LrhC)Bua?4Uwku={D&v0hu)!-X(81sDnyEo*Wi$ss8#s{$(gY zVETMU^B$+=Z{KCknbId96dCujckea`kC(CSGbO3GJhRaEuH)dGIo(XnT?!{iATY!H zo;*UW8UTd7jKTQ9!+@`mekEmAPQ%vz?bkhh9prWAyTya0<9PO5)W;Pf7q>bt2Cfh0 z+~<-;opi3uK^12dQ<5QL6~8GfPJyWa9hXXKU5jl?i9NjeHOrQ9n%mt zQJHGB0jGN#p{^{Zd&bkb=-(Sgf>XS+a>BxrxWVmV0oenh33_GeSV*YqqYe$5i~vW(Uh&}7TX3I%35rhG~(=6JZn(z2I# z)oXs34jCp(h?0ofU_6LE80qqiVCsqk&y-PIQ?#h(tjx~(QX*kIfD6J|AOGYXFNYcv z`O1K|u4wgmu({3Wt>c?f3j{KkQYWP~Zw6a9^50xZw!gcesNT z3MA=+9eOX#w+>g+8U2{P5#&@0jcxwAn#_Zb=R^g2qN(ZexC;#HylgXJ)|vYPYOm`{+b!bbZYUh3OE2c0ve)KiWjeH@;!kjR4Ppl9k?+^)K zgqZ9lI}$YZS0Knc7$E2=_DcL^N;0_yYo3RNP0*W$A}+Xi8%UsQj()5GG1!2ZB+**{ zxA=egC$RJSv^yzm#!oRv<0hcqXxf7bt^bIKF${wV9suxa|6veoWJFtoPGfNLsOPPk zO*8CvxCtvv!qd}vf_{aRwgaZ4BkR5nWiTPa)3;$p!qQ9aSH|HP2VS-UeK2<-61L=<*wAtdT&|X*P8py#?N;#pQ+Mt$Ag)QVLil8Cq9L_g^#hXC&Pe zi6#;Y@5SkCVi4p8`to&WC9mjJ?^|WUX&>7y44*~bLIaB=y0r7RIDMgfLzTP*0i*f0@`=YrV)AK8<)({g zGWde3Xjw{k?=#&XiQX9lbKFL}*p@&3X7u<=S#wZ+o{XtUp}Hm>>?sI~#H36@>1Oaz zhOc1}Xu2_ydNzjsmNtyuJfCu!pTA&Gm8@g!T59NugsWF1?NxHDBk6YmsF>~@x8Ie+ zK^3Sw1`q7aU#8aWzoMBB0crNk=mT$6(Wf7)3MK3&3IkTDnL?4*)z$V=@i)ZM`MZzLcDJ zR%^`Liaw_u@g7a9d#iLQ>_uXJbf=O+VQH@%Csz@Ip<7{NE+au-?!tm6AbWrr;)oVB0ac#J=MlNa z0^{LKXH`(PAVEYP*MOY>;5|wqA(w8a0mZ8v4Y0TGEpo4hg(WK+-$6#J5_5bgSom*@ z`63MFh=chb$%UX1G20hssnm-k(Ck}Uv{QTixT9TJQ3KzuM1s#uTqH34hsQ9JDQFv} zgJ|>8;z&hjxML@+;*MbF&W3+eQ$=RGLf&(ZJtW{>8rJ416aji8^T+)h>icz=tt}ah zTIOwRct|d#H^6p#5plkltTgwSd&A+G73?1E>kdHkY7qb7&4P(h7-9|sxX-WXI#;_0^V|n_7a;)_i1(4S z3lrTw3jWihkfs3$y-feX5M+O3RIJ}Mk)u5&QhwP^xuM3;83l)9fF%+=soI?Z3D8j! zdyzmiFp#p6c=jQ)QLu`e+n}cf96;{&X&7SfWcp05l69`*-F_R){RX}i9`2xP#0LvR z`d47hMghdxOoaOpKz)><&FmMVJ?JGjIwKMkx>EP4|02<#gBcN2B#0dj9Dt4p(57EU zg$HA7yU!nx5ZGm_|D;wdeI{TH18%oaxbA)m-3NdUf`!Kcl6=2{AK%Q#7<32@ zas?EloV_-8XfD{nvG9J~uij83aO~9!DtZTtosdtPXtiJqK#zCH`Aaeav81QU?oyYC zfHSU;)BnldxleM$wDbqC3INe0R}>k0g?dTZG(elCAN)(6X)&5MUY*GixYhK_rMsF# z!`Cw;j}YZN=UVfYjaN2Pg%w#7Txvgms3IMJ^HsvZywUzOpQjuq;N61Z%#F`Y&nBhv z{lZqqY`vZh>4j#612HWCl7yKVnw=`{3il5?wAV!vz=3m6$4F%T0_>g;L}>zAWQ?OG zVK8NcU@_KE$5BJa(IyinUCMQcCVJ^a8frQKbv#WAM)?)qVv|xGNdG%q!edbWyvrL2 z)bNL2YVZY0eHl@qsdpC~Gn3O-7vJw;=ym6QEKXPN6Z_x6DhuJWqh8q?0?PPhRgHO- zfxyR0hK=z00IU}nC5FT))Sp7FxoZY^K8B1g+62wdt*p4u!X`2!Y|zjyT`wCl$@x#8u1T-pGcblM4qUe)(8q+0n2Yo7KU@i9UIm_M2Z$f z&#QoPgp}CyjB^}U(}vzVXp23%Ikm%Z1bn=`s(5lFDYQvT1cHvB%~>nc#mCNcyB^xN zkw-wFT6jPSFd)7jTD?b&JYaDCMuR-!o&2W7a9QJC5EJ_QrZE6Re-eBOcOXHXQ9qn8 zKYWh5ugwpim~83ColeYVP=#;j5|0DC-u@JFb5ZbKFO$;i9KAFs&tmTbb0Po_-9 zuA2RfdJ9+(A>NoDv81pMBFg@OtUkIY4o(s(`10E*crod&H4&mW0l%QHck8tTmJ%8t zfi>~;6ylGD{;y$muqP5>1vh3R-hrKnkduHzhQPM$KG^StG0lA=B_Om`;i@+YN` z5Q~KKnyMEfp}{yvm@~u%3AI2%|19n_Pfzsy z43yPu`d#vGmPdb=uTplNQa(FTM#Cw)4F5ikQ_O!+jsyRF&vyC|`0tPUrPGg=z`7Yv z=S?Wx73~5nuTmI=qMFEl^OJTOtJv*6@5!fcBY0HfARJSl`!lYYlxPNS+wB>(M>MKe*ulbNF8_$Fmc4{h~nQxe<;N*NmsHs^u~t zhX%~mMfAfa7R&e3`f5L^Wu7~gj}}=`|Aje39#6EsaI&f^j{3eOT{SN56LaV1YE1=L zf8tX3_Z^lGMln|(cJJQXnl9=-S5vdqe8Q}H7}&9eTK8`{uOl$LYoak&ZE(0yxKJJ;xaTw zvY*4K6Y*f{wNZV8h9Gv6iebVcd(b*VdkQU-nde`=kEJx|8jO`X(3OX%p(T?1NiWA6 zG-sOk(7cb9Ukh56V4#DQzma z=+0zlRq2rTA6fX5#jyIGl4!vZbFOFfOH(}mpj%w__u#m!T?*E3H{RQdm#F~b0v z+tcqgt1>JtEDTx9<=5R?4Q$pE!xrp#pNAQlg0F?lds{q%>)tG*wYJi;&tzRx&UXA+ z<|(f_W$E~#`1P$H!5RB%sfihN&4JwB-v(01{+PAhqkZFk*4V#rMyqWPx}lisXD7o} zgO&v?IA1@u(P*pGHnjxi#z(Sg4AmJe1`MU#sm-#IMYD&IvS@o8RgYfIZRD}S*&_se=t{hEU6B6m|4UUPbvm3b8U zYHa1RSI~@ufnl1cwWOc{3mM~J-z(z}2`k*nH3Tzlfu*DJQRd&hAKZ%0Z#jvn$2QOS zbNPQCR{B@mUq_o=bmDaL&YR=m>t>CXbMaRxOCuiY8Q~KlZ&f!|-I^v#K54LA`SI}r z)%R|JR%i74W{_8uEdFNS+S!1O{L|Bu%wOgql!J$Ds-YEsz6ufPBGT0UkoldQB>#fL zKDLrC2M?tQ!t1w;} z6;t2#jqR2*`Et_IXN?4Tg9Gp8^Y8)c-gpgsWkI zymPTnpY+p}nZujU6W3JDq_J`3V^7aEYFvcVj91$Q%W}FHwLI8U_1^hOpY}Zii|v-? znR-`1!`L;ibxwQ>Z7GY#@M*hzyO~ebQeM~f%nZ6@?l(1AAx-CF^3XzE;BlUsgvq2~ z*^TG5@5QUHR$Y(Hb9)+gKct3f|2jpZ;?him=gbwW*)^-2JIY0ZHPsHIMp@_(rjD^N z1^K40rpJ%xGiDyvM_iy$5I5wwkR(rhLb&<15n&F2njB>#pUb75}$gtH*rF zVavFMvC}g;v7PuzA?3ikVCDJK2du4ojt`>!)A_%b&G-D;uaB`vcLKKAdO(yw@2%^4 zJNplNP7<{5o>z0l{vE3?dWGAhaA$Bl)@f&DrU()wR0@zb4e!3k9;I9>Ze;p{>kAUw zxZnK3g(KU#kE<>tQ#8YE=rp_kzc-R;irk;s;Qa%#)=$!-Rvy(Bwh!VJg9qX^Hm*rm z4;fw#&hio64CUi!wOI?wkM7=#HQ^Wz_Yi;h;k-cDrq0qwt@+M6_ZN<*3-Ihd5_DNc36kmbU1u_5`cQ? zf^ePC;Chb4e}SL?3yc<*ns~BwUh&5}$lq@bN&<_1*(3g_DUB>o1wXfOq$^xCo^=bz zfBZE1g1s7lL5fa+h&qEnco4$jC_yF^w54RLHB-ArOtE()U$Tkx-0Jz!1-QUny$S47 zrJ&X>07PwjL60C(l4}kz8~p0>Q#t{-&VL^c;NzleY~n}%O+(2+@b{yuBq;Pm1-OKX zd2M^30mf-i35=+9 zTL06|&Z|#P?p@{PdHvMr;QNzbt8M3Jag4t^rdMHzb^!M6ry@Xg1g+4z^6`QE=`XSR zzazeDXA53NKd#;TE*-=ByO1X-6a%Q>y2ymLgV8DzMQT;OZS7hLzb$C@yHo(%WRGpN z!(R0U{Z83!5XzY#uCM--8Sgik%*+mAw*{dm;&~K0cD}b~E4SZ_OzG@W8GS*b)mGp* zN#}M3lbR_VT!REgg?Oe5I+ios9}E9UP_M~V$!1pIDSo^1g5)vLJu0mrTuICBrzwd6 zi{j#uSOrEjh*<#yiByxFh!=z;3|d@pk2UcYZQRB0T8Wr0F#blL^7CHJmecqKW^l+d zl?ono@d+fdR|^#nG$b(MK#CV+2`=#IpLY3}_lh&=E72XweOe+J8b&L$3@QZa!8SR4 zsuE=oBT|bwHA&`3IX7J)=6k%YL+`KQHq}avl?u(#;ZD;QT6~e3q=IsZ2tb7bf~Zq& zlEIPy6)&|Kn`m2bNbh6X9xr!AdzEyhmagk7s=zZ{GnI5{96<&_kQ)R;yy;yjGs%9q z6j6OD2)2t2AdtZAzjC|q&HcAql)FFc%1-FMbZd|9PQQo~YYD!BAx!ZD01D8SiDz_H z_nrZfEc$H`9fM#6d><{%jLzB%?Jl%Z5ts^n;jqW5Q90v1odGHZDtQu!!B~q;yJMlb zBb1=K^jSx#umAZng0}Bnk#V{>7A$u}635^{Sbf-0Qm#7y;I!2+fNmd^xNv8ck-TEJ z&hz1mR?^_oiQdx{S{{9^Xd^I`8c(J0pC=l^gze%_)hnhols)IYY|g`SSn$A$fvk-8%YDkj8-wX5qira#2ST+xmjE1H zkX2z*BgsGkL2e>`yjwoOp%+_hc*{>~;e|Y;|Ar9vzN4AR|Z~8o0M#r>~7Fjd`&o#8J=`Vb|WX_ zoZ-xQ@@!-RXKGhi?m+OgDQQrTYi4`|LYeGP0GEA@XXpc8*H@q9zKB*|5vjD}XLeIt zsk#f>J{`Y9P`I^nv!&had}O@yrFnBZNS+!iUqE=klI#h<`E>vZ>TYQw80tOAzD=b- z9Fyt8j{7P)va~;08Kqk_@5Y*ChhR%C-l^DAf`F7n_k`~uCMv?6(Z1mO_5_}Wf&G!e z=P%yB7&aT3ndBVB@|=P^jS1JOfeMw2i35<9zJWg`No_%%SHCyg-g2E;Q8)?AL!Dqy zY)UB}5m8kbS=(vp3K9l$23#hRLrF<0rwI^T{AI^@6`W;QSBHM<h6^k0J-iZ3U4Qb6~)Vuz!54Sy3~2`1^K_O z-GIHXheM9ovT~xsI6+woX@f(~xTPk0dMD!=5=Bs889%W2v&9IjE{Rk-Ioq}nX* z#hBIE{@9iBSF~g>L17TeE({u(g{{28cz6RaBs>&Pg-)FeuF#8HrsBhagfFsebvUHL zmTcr8^5M;nzv9RqmaEvY0ZZVr9gSesWFAZ&b%X_+LhVRkX=jxkKY|i9UNbbFl}xq0 z=hPl~dtO#yhi7qiHobirBt;F<5;oEBz{B)m-t8&2xIQ_6z*e`IMgY;-0_Z`Ctvt1g zr1x#5H`5Jt3VyA|=d0^27q`^{l_CksYpKAb015I=_P`_u6=>p0Q|t-|Y!oWRiBBRh zuwr|{sBMb9GgKWb|Db5lp2xL*P2AM$OB?a&V57_lMn71=f zGYh-p3=eOI`vb|?2C8uClqm%Ku6_a+#u(;5MN3H={)&zDcd(=Myt~n@MSDKmxBx;^ z<5{o0zX%xTjrTxLtkJpaZO&t>83D>n}CIzg$LRbRH!cyoEqY(0NatG zA_lB@njqs%;5mwaK4!JrrB%-ACx3hAYsP1a!5pm=KoEEaf(l!P=LOl@HTnlTEB%yCs_Sg0xc4z%J}?W*|O| z%gR+I#Z`E^QWtx%@>u(_q8th=(Vl?)u*m8hf8ck}xja}Z>sO}ZxA!_-{FF+>HieNo zzU^APqafZyFvVMccO?lHjG0$X1R=5Qj6}j~elRIK-DXZ_=Zk@g@m^hP`jd1|obZuF zpE92csKugB6rW%sY##0n_n4z3-vpAblL(3nRE3oH9C4twKb_ooOQ#qW#Q3F`d*p-z z&sUWPCYB2RmVV6(ia2&Slk-hY0jp*~3n?i4qN0ePovvapiV%nn>CASTz=bp{;t&3_ z+GF@;E^sc8dv!wAEPXa3i51$*j9scwG4Nl=h~7@B42OkdQ^Hb{?9p#WkM^^wL$Wt# znFsHvyi(`3^6x+a;Yb(`2aCbpVnzWs#+8{){caQ_T+r%O3#df%3D$;WBMFd~0s2t_eN{SArYDf@? zOb8_=hwG;pH&6)|P+6xW*q5e+<5NVQ5rW?8#&sXi1wjAxWfQw1Ci?i8V$a5 zg9F!7gFL8%6xPv8V# zb0^Q9IsOge40=MR#bri~T0}W>DbuDd?}1x(jDW!sLq`NK2tWfEH(kESL*)(_I#>e& zOiA(uPds((U|snl28dFpcJJbKS@fvQf=Bt@)T%Y8U%++qB2LV7&6j)QsGcxE!o-OY z14aM{z-7%EAUkx3;j9DV4L3c2{P+R-u;SLPTlS2-v*1vnKar|o{d)J(Vf>guBiHT@ znFIj_4y1fS#0U*$*g#AjlCwxQKh~&LBgBUfHc5_;aFGOzn28m0r{t~vpU<4K-`lip zyWrlxNz<=?4S-rRa8#`t6aWkWh!6k*Z7{S07>$PzR=@y*3oOi~ z{#$+@z9bZUJ<&E8P=zf+l!qX)$Py5Jpd*YpkeOftb0HWsLK5*d9}Ip;mJYXNF*gW5u%jpd0@R382spXWMMh${@z6u5O||jxk6>hA^7%z*p&PF^Mx3#@v#3EJJUSE%X^7E9LTms59l9imafZ$&1x)f? z9b5Eqpn#3+GhRQOAq4=`jx$VJ0|0QV2;7=50vBmYTGNjr{a})&5QXGM8(b_fLUVGo z(HRbG^lU7?Ljm0$$budva?wV&q(d7CC;$Le2HkoB!j<)LgGmbcdgqy9jKQ4`IJb>A zOmPPdXhD~Y9J%B$tr12ad!z$Q0R_Qe6;36LkOUcT@W2KRKAgs*kDLCIk?BeJZQfek zmKO#v?zzw8&>3>16HYVYOtMJ;%Lg#}j&J zpt+$pAn6qEszdM;fr4Va?F-2AYIR7`)190Jn*Eg_XUL#qYNVt=(_yJFS>|3DS z?gl)(_(%+2*hD(+x4-}_01aK>!5X&HkTn3wA|MQ*10RSNpfM10@2d#{NtU%Olp+AX z0Eaf*k(uS;?|Er(9Yv`310JMiahhTibae0)1&lyLU*JM{TBxMT!A*U$Ng=b0(!df* z&Ih2$0|J_tnSu~N00jv^A5vp8HJE3B5Aj;Y9I^%{N=|`Pr2Y^5erH0bC`1iph{7wf z;=IdI#Xvh!h!1k`xHF-PTyRXFzUJtcI^J<6JqQB#wjhUjXrn)4WS@b$P_wLAgnH-N z$wGGU5J1!q0UFT23i8keH!KoFYUq;`rLK|kV5bR{{ zPB_ciu+l^qJ4R9_a-1VibZNAO3@HzLupj^qfH?!9Ei@E)T-QP}k`Y=HZyj@F1JkJ| z9`GPEYv_Xg?ja5^f-#T?0K*y-a-P^w#8=h%j1kkoMHjeW1zosZBxza8_1!W>vT^6J zZqNoYpkV=C1m@)s;7T>Pj#8PrStv7B6C^=1nv;tg{yTG-Qk8NpJ6*s74%z2C15JTE zdE|+CGzQbAG_s|T{Ao+v;0CO%p?g<|Me_(i0DcGq9N8eiFnOhef8Gpd=%N$=x8nn& zg60bdNN66?K+)Z#@1Zq8QHn0N!q}YEHC`h_8sy|Rr~d7R9z~E4GUhdOm9&~KCF;G_ z=}5Q^mar=MCm1X^y1!AS0MtMW%r+H7G*S zi~wNa1|Sr}c6%fd?+W)>owcG{%WGa1Ciadz?V4B#;EZOB!yDZ&1~eu+1vl*AIwFKf zY-NL0lYEM23Eo(7dBUtl`0K!ky<-4D(*`v3Z5UWm#xqn_5CtGWEw@E;G( z5CwSL0~*Be#+{`~m0{o`6L@ID57z$c;~y)R%pD`JztsHJP?MQSXrz>J!Z8k`i~|}} zCIADP!3}U+0~8XX4n)|5;8b&VM1!hgk=aaGikJl1Vz|LZ000heBx4-LD919GK^1uH zfd}0ggDHz7MMURi&_!YEun#@rXL+VaK{x{*;NgsAQ2Nf^07pT{!3=E#piU!2%|t*A zT3{pV%*6KBM9=+GKL4NxZh&(*l0oTYEF&4wU;qPr0dH;q0|B}cLk-}th5-H>*g}TS zz{y=Vmxfo_6ai8j(r^WIaN`l|+_taEB7`Kn73ud&K}R)vyb#Dr1v5NIoBu4&-nT zcq;=LMu~<~$)SvH*ts2j01bwLu+uS*{XNG;k*QDp>(GLFTw+KA6x8wRJI{O><#4Jm z+>r$?kf98iCCRr9?zW+~)$E2xxd~I#qlS>-3gNIbwBa3&C|u#)(8$J?zy9Uaq>J>a zHZ#H1aFoKo2k9+3szfBh7H+aJ_ zKwO1=0UuODw1ESz{>{O90a5OuU3mG)ak&pJfgiAq!A`jW4)nn^M1wa(!ZM7*F{n(| zDIcD(g92!fIpx6o@n4I)$G{Pt1?rw+^+>XfM9er{Gmzk4Km%2P12>R^G|T}RfCLc4 z4*f+^iIv^=ncE%h%rS|<9Haxa#2YlULmqfRHAvSn7(+N{!W}T3k7eNYHA(*A1{9tl znaz$u=vNnT!T>6RH*mu^Xv0~sgAYPOG&qAVu)`FzK^uV7KgA%62wvgUAO=n#Os&ye zkkiDFK^=s{GO*TB&>I1O1C}^L9n_9~T^8jW;+_=O6WU-DnvmCcnHqpW6|}>%Aw#UO zgB-ZsGensF)lmZ}coGowmA@U{?r~r5725vQUo@$g_yK_!$U!_bgEAO{9csfAd_e%P zLp1ndI6y-^Mu1Q%o|J+;`Y=MK1!2X6&VeV-VNqX4|&-d;Q=))12W)U zIKckP0sufT*xfRaLpscX8sLBsWM9aMT1FO|?-_-fp`yr12T8SEH9&*IdBZQ@!3~(f z7f1jC5I{2o2ROh27-XdPxT)l0+Ls!96^K`nkgwutB5U0UZ#)H~)sf{e*pK0pB)e1R3vS~HLX0~7!nTt~QcUK2i4VH}uw2489}Ne`Wr826O8d^>09X(?d^&BrYPecR#`UW0{|?5OlHg%u)}JN z!!u9=9%KiOg5z-#%~~a^i2f+0P$osN0TH5^4aq(hfk z6&T>dGt`_mez$RwcMfPG!zPD%?Xf7)3Y)Kmw%x!5J6{7{r5&k>D_J z0zY-f#WHQU!DYaC<9qmU8Iklti3B!94!>pjiTgEXaX=QG^b_0T-Ya ztua7mAV3;Wimx$dXw4u45pT_gFJAOh8}xw}!~-+Hn+eW?7X%5g009oTK_8&jT~XD6 zVDJa4Ekw1?v6`n1PmUWHj=DfWI6Q(kkOMWuf*g=R7dUX5^ud!|82~820>o~*DvoOs z?uXP;3U%cTmVFY6i0{lVDE^A?t>jJIq)AI03ESvwRK^H_o0E8wp zIKw9J0U;8i4ulH?7r-B3kQeBxGX2vi;Vg+sk^UCnv5Ez-qKLrZliv;nVUg z023*5#@bR23toARGw>wY4V(dB5;FnZ!!+1|7IY&I1i_?`K_){N6q5%PE0Yxi=qop~ z;bKlJKa1Yfj}ueHKDhx!xWO5;!S=WT7Z`^Zoh&aZEi~sNGhYrzcXS4)oEmJwcia^} zm&Y31z#AlC4>VbqJkJ#*K_7e##u_UQ%X0S?^en?v09h0mXT|ai00Q*ScO~4ANRUPy z09Bx|Q~GoMdhE;!^hejRIj6Jz6-P}C{s11l(poB7N!);EhyfR6#aW=uniw=sZm?8) z^nq#gQg}rKyfX%ALHnu~8g19(kbxKc5&|4ykW}?DlZaZcH669}N(jjsY=K5Sj{90SZ_2)(PP2+S<|Fxm4ftTKofy9e534u4#%qS}W4jIT7 zzzi$v^k${CU{AGL7q(0&C~7&6##wR|ABo?!v4P~lOwVzLmUF=6aBly@8mvuY0aLzN za1MNs_;E)Nn1M$9G+%G?=K|DI7j9>J_Gino@y^o%{1UGAGvc7ELx2Wv{SpOFfh$>+ zO}x<+^RJFl^pz`B9K9DL^TC_tT@=7tH8cZ0Oihxn}sqlIV20(=2Pf5jYN%8hr58Qgdtkb)l>2pXh8 z5{Jf4siLwf80GBuhz#^>`-~h!LEi+x6A+U&=YgCUNEhK)!^%M&P=On7Z5l+xr$0oQ zkDN`bH7wgwf8Ws*M1lTdb3z_$K^kB=rFRhm{P<^DffX>%=orWYkU>N+R3no*9Z?fb zA9pn6z`EMc<#56g%mJO_!H@s=S>V(axUm8xz@>lzL~F`OIF6Jmy3e3_4iEd8r#Q)6 zK^N(I9)Q6EJOErTPZt3-0xUoRBmjDUCbTC21XRFjqBKoU<|DJyVR5Wfi#kidJG{$5 z9(2hAC^qxV`e%Noz5lee>w9Sw{J#c#z#}f^FgFM#xEKl36-2-T)ca45^}EY^mjrpG zFF?D;ADi6C<9K_qgL_bAJT=)d&2J3zSU8RuNS8qTFdIkZ_d0giWpj;Fzn`!JfU z$;A)Z&8PXY^8P%4J3Pe8`^eMc(swNwaDkTR0i6$`;r#ltMt#OJw?-(yS%}dX4L#q? zJFR=v7F^V_bpbG^$;1rebS&|S8~3;8x43Qm&7=B~SQm6fRzX049PGgq@WByK!6saR z3YSEN%1c97B*(!)ko40Upo2dIuUZZJ=P#?Yb5lW3!4)(;Ydw^0A?r#Fg~ zR8R2!0}1|MEBW^4jz6daI*da3oB#QvfBLKc`lEw7fIhH^Kl!i!{L?@CyZ^GrfBozK z{I~!6{^LJDs6#i3U_pZi5hgTebInkfLcQm~msrk0D2vJehK3%a<`{*1Wmyw-oj`yY&n@^w!a(*`7u{RrPAqpI!fJ z0Dv~2i>+_NmV1r^fC;<)@%9Z|_;1#;Yv(475%=5T#Cscmt`s={+ZLNw%-(kT!*ebE zKzCpIt@?HCx|ySgEB<@<=)i@RS6*^ETlU}~wm(kW`uvRb*Sc>x{YrujKmhe3#I{6K z_(#Ei80;qy+aN(ftHTB%1c1a9Su^oMiBiN1 z#ui@$Q7IX*JF%}D6LFEWAW38@LmwZi@h%{Re2B;yErJb_27?43fmS>lWCB$#@W9Ge zTKqE0EkCRB$}E$cs7e&9>9V#l;mhv>;5^%A&VTB>^PD@!lafl>&P+i|F1@@m%s>Y% z6HqJ#O;e&Z+w{^pI3?PX&J^stG}GYt1j^4t86DJ3L(L48OnXX2vp+RyW7Gc4Nd+}7 zB1`eqZ8Tby{O{9LVSI$e#MK4$6425V)@@)IY0c?x> z=idebM(^MeXZ*$j;1E$|V1u_rm?B#DTt&|hR_OrDegpw?Jbp!fXkk1jPNd+ITRXYJ zh9CYX;)x-t*xP$K)tESrJ^lyel4l}0B9>ELSp|S=UC!l#lZ<(0Fl$!D;)yWk6WX1b z4T`))hd$b3feBXH>x9Kd`ehoOmKbWAFWwlwt2xa&CZYWu8|AR;M*g~GyU#{3ZJODp z+U+Ful{TiWf6e>rqRakTVZNEh^Y68#zPVYf312Q(84+ibM~8Rm+#1f2^BlD1cr)&_ zb2|9M$EtOX8*y2cI?^7|KYtka(?8oi=}I|J9X8fohcfP*-JV@;lt@Q5_s~lpo%h?L z=W*8H@eQu^*9T9!>g8p!-S-T2uYUURH%vWy;T5%eL!uslAZx8=$Q*@aAF#?p8kZzzv4Nt zR-1yL@xnL345na%2C~2kTWF0Avhah~JC*iEC^$`(D}WKi{)h@^*uvAjFo-6SA(%Xv zKdJ>Ugg9*9@7|ZaK81&ZMa-YWU}!`uLJ@?+YoHFH2sZ&v1c*qa;T4H!#u#pKJE?0T z`~1g4E{?HBRLmk8?`T0bzK)4r%weVEc*1F^%XAPb_p-IIW_0Qhakr|grH3Z7}+_{_!9sC z6(T_mno8jS;0DU8&vd2(3EIr^m|Ei}a1a{M(hc;V0sTxy#p#{iFw~()g6Mk;HqoVA z)IlBH;6__oP>lo!*4o(`rpot{y-DAI|R6ouWXQcrgp z6qX`YphBG~RnvD#((Qnjcql@V5Fv{0PV<~KSWj+4B~Q_C%ejp3K>rd}IFVxMd`Ya%-Wy zVkd7#$W`WXl3APO9uJtx3bV47rH*`==@xm*5-#)Y9=7HyvsTV%R&tl)+_O2aw$5YT z?wdi^XE59Kwl4CsfdL(}LYvOWmRaz-)24gi~2}(Qh##)`KrFE-sJ=Hpq2|A_!PARQpR3@rZwPG~akwRoLv_~X7>kfVBI zhDDz0RHnlWYZhrvv9P-w^}Ehc?)0b0Z(AhfHQ2g&C(IN&_0q;k{Zi`Hs7$DPj<_U?9twy6|!ozQ$_3aBeA8y zS?T(-(wWY0xp>(};i`RnOo{!Oo7XirB`r(X(K74VbCb!hUT~Z&GkzIFoeih#3 z|K;SR?-?gvmnSaRU#?#BH)w_9e2(NFYA1egNp+du?$g7uW`=(95pHK5!*mE(L&z?Eg&tG~k literal 0 HcmV?d00001 diff --git a/SpriteCompiler/Samples/Sprite.gif b/SpriteCompiler/Samples/Sprite.gif new file mode 100644 index 0000000000000000000000000000000000000000..88144b0117f6da8e52ffa06e39fdd6c366ca1752 GIT binary patch literal 1178 zcmZ?wbh9u|RAI1Vc+SAU!o=dZ_t8(} z{L@M2&-OJBc1cV&TQ^%`)AbYitwGaPm?ocU_wNjQ_eQKZ>7( zoSoa?f8Tn>f~;pNPxFWTom`Xm@VRGKetp5d-|xj^_g`Bdu-E1G3tp9b$=<$KuGQPz zREckVsVecn@sFa2Vnv{;iDLTW$RG2Z0u-kQgjrZV4{%eg`=Z#XT4nJl>}=%LN6r6N nRw#w*TvJc(Houm!xKq4SL#E%}O8v..\packages\FluentCommandLineParser.1.4.3\lib\net35\FluentCommandLineParser.dll True - - ..\packages\OptimizedPriorityQueue.4.0.0\lib\net45\Priority Queue.dll + + ..\packages\NLog.4.5.0\lib\net45\NLog.dll + True + + + ..\packages\OptimizedPriorityQueue.4.1.1\lib\net45\Priority Queue.dll True + + + + + @@ -57,6 +66,8 @@ + + @@ -88,6 +99,7 @@ + diff --git a/SpriteCompiler/packages.config b/SpriteCompiler/packages.config index 501c013..1e4a89d 100644 --- a/SpriteCompiler/packages.config +++ b/SpriteCompiler/packages.config @@ -1,5 +1,6 @@  - + + \ No newline at end of file