# HG changeset patch # User Jason Maltzen # Date 2015-12-19 05:46:18 # Node ID f28757bb21cb14f05bedaf2462a4142e23d57003 # Parent 5b5f2093c6c5daf5b519af06a98aa9ec9dc99027 Refactor recipe / reaction computation into a common class. Add some file utilities for supporting Mac bundles. Add some scripts for building Mac app bundles. Add a help window that shows the missing reactions. diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -5,3 +5,4 @@ obj/ *.swp *.zip .DS_Store +mac/build/DesertPaintLab.app diff --git a/DesertPaintLab.csproj b/DesertPaintLab.csproj --- a/DesertPaintLab.csproj +++ b/DesertPaintLab.csproj @@ -70,6 +70,11 @@ + + + + + diff --git a/FileUtils.cs b/FileUtils.cs new file mode 100644 --- /dev/null +++ b/FileUtils.cs @@ -0,0 +1,66 @@ +using System; + +namespace DesertPaintLab +{ + public class FileUtils + { + public FileUtils() + { + } + + public static string FindApplicationResourceDirectory(string dirname) + { + string dirPath = System.IO.Path.Combine( + System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), + dirname); + if (System.IO.Directory.Exists(dirPath)) + { + return dirPath; + } + // try "Resources" in case this is a Mac app bundle + dirPath = System.IO.Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Resources), dirname); + if (System.IO.Directory.Exists(dirPath)) + { + return dirPath; + } + dirPath = System.IO.Path.Combine( + System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), + "Resources", + dirname); + if (!System.IO.Directory.Exists(dirPath)) + { + // not found + dirPath = null; + } + return dirPath; + } + + public static string FindApplicationResourceFile(string filename) + { + string filePath = System.IO.Path.Combine( + System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), + filename); + if (!System.IO.File.Exists(filePath)) + { + // try "Resources" in case this is a Mac app bundle + filePath = System.IO.Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Resources), filename); + } + if (!System.IO.File.Exists(filePath)) + { + filePath = System.IO.Path.Combine( + System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), + "Resources", + filename); + } + if (!System.IO.File.Exists(filePath)) + { + // not found + filePath = null; + } + return filePath; + } + } +} + diff --git a/MainWindow.cs b/MainWindow.cs --- a/MainWindow.cs +++ b/MainWindow.cs @@ -55,16 +55,14 @@ public partial class MainWindow : Gtk.Wi Gdk.Window rootWindow = null; Gdk.Pixbuf screenBuffer = null; - Reagent reagent1 = null; - Reagent reagent2 = null; - Reagent reagent3 = null; + Reagent[] reagents = new Reagent[3]; + PaintRecipe recipe = new PaintRecipe(); - public bool ShouldShutDown { get { - return shouldShutDown; + return shouldShutDown; } } @@ -90,9 +88,12 @@ public partial class MainWindow : Gtk.Wi } } - Palette.Load(System.IO.Path.Combine( - System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), - "colors.txt")); + reagents[0] = null; + reagents[1] = null; + reagents[2] = null; + + string colorsPath = FileUtils.FindApplicationResourceFile("colors.txt"); + Palette.Load(colorsPath); Build(); @@ -385,29 +386,22 @@ public partial class MainWindow : Gtk.Wi Reaction reaction1, reaction2; TreeIter selectIter; string reagentName; - reagent1 = null; - reagent2 = null; - reagent3 = null; - - int expRedSum = 0; - int expGreenSum = 0; - int expBlueSum = 0; - - int reactRedSum = 0; - int reactGreenSum = 0; - int reactBlueSum = 0; + reagents[0] = null; + reagents[1] = null; + reagents[2] = null; bool reactionKnown = true; - int pigmentCount = 0; - saveButton.Sensitive = false; + + recipe.Clear(); if (ingredient1ComboBox.GetActiveIter(out selectIter)) { reagentName = (string)ingredient1ComboBox.Model.GetValue(selectIter, 0); if ((reagentName == null) || (reagentName.Length == 0)) { + // Nothing selected as reagent 1 ingredient2ComboBox.Sensitive = false; ingredient3ComboBox.Sensitive = false; unmodifiedSwatch.Clear(); @@ -415,15 +409,9 @@ public partial class MainWindow : Gtk.Wi } else { - reagent1 = ReagentManager.GetReagent(reagentName); + recipe.AddReagent(reagentName); + reagents[0] = ReagentManager.GetReagent(reagentName); ingredient2ComboBox.Sensitive = true; - if (!reagent1.IsCatalyst) - { - expRedSum = reagent1.Color.Red; - expGreenSum = reagent1.Color.Green; - expBlueSum = reagent1.Color.Blue; - pigmentCount = 1; - } if (ingredient2ComboBox.GetActiveIter(out selectIter)) { reagentName = (string)ingredient2ComboBox.Model.GetValue(selectIter, 0); @@ -435,25 +423,16 @@ public partial class MainWindow : Gtk.Wi } else { - reagent2 = ReagentManager.GetReagent(reagentName); + recipe.AddReagent(reagentName); + reagents[1] = ReagentManager.GetReagent(reagentName); ingredient3ComboBox.Sensitive = true; captureButton.Sensitive = true; - if (!reagent2.IsCatalyst) - { - expRedSum += reagent2.Color.Red; - expGreenSum += reagent2.Color.Green; - expBlueSum += reagent2.Color.Blue; - pigmentCount++; - } - reaction1 = profile.FindReaction(reagent1, reagent2); + reaction1 = profile.FindReaction(reagents[0], reagents[1]); - if (reaction1 != null) + if ((reaction1 != null) || (reagents[0] == reagents[1])) { ingredient3ComboBox.Sensitive = true; - reactRedSum = reaction1.Red; - reactGreenSum = reaction1.Green; - reactBlueSum = reaction1.Blue;; } else { @@ -466,7 +445,8 @@ public partial class MainWindow : Gtk.Wi reagentName = (string)ingredient3ComboBox.Model.GetValue(selectIter, 0); if ((reagentName != null) && (reagentName.Length != 0)) { - reagent3 = ReagentManager.GetReagent(reagentName); + recipe.AddReagent(reagentName); + reagents[2] = ReagentManager.GetReagent(reagentName); if (!reactionKnown) { @@ -482,16 +462,8 @@ public partial class MainWindow : Gtk.Wi captureButton.Sensitive = false; } - if (!reagent3.IsCatalyst) - { - expRedSum += reagent3.Color.Red; - expGreenSum += reagent3.Color.Green; - expBlueSum += reagent3.Color.Blue; - pigmentCount++; - } - - reaction1 = profile.FindReaction(reagent1, reagent3); - reaction2 = profile.FindReaction(reagent2, reagent3); + reaction1 = profile.FindReaction(reagents[0], reagents[2]); + reaction2 = profile.FindReaction(reagents[1], reagents[2]); if (reactionKnown && (reaction1 == null) && (reaction2 == null)) { @@ -508,24 +480,12 @@ public partial class MainWindow : Gtk.Wi captureButton.Sensitive = false; } - if (reaction1 != null) - { - reactRedSum += reaction1.Red; - reactGreenSum += reaction1.Green; - reactBlueSum += reaction1.Blue; - } - else + if ((reaction1 == null) && (reagents[0] != reagents[2])) { reactionKnown = false; } - if (reaction2 != null) - { - reactRedSum += reaction2.Red; - reactGreenSum += reaction2.Green; - reactBlueSum += reaction2.Blue; - } - else + if ((reaction2 == null) && (reagents[1] != reagents[2])) { reactionKnown = false; } @@ -533,15 +493,13 @@ public partial class MainWindow : Gtk.Wi } } } - SetExpectedColor((byte)Math.Round((float)expRedSum / (float)pigmentCount), - (byte)Math.Round((float)expGreenSum / (float)pigmentCount), - (byte)Math.Round((float)expBlueSum / (float)pigmentCount)); + recipe.ComputeBaseColor(ref expectedColor); + unmodifiedSwatch.Color = expectedColor; + //SetExpectedColor(recipeColor.Red, recipeColor.Green, recipeColor.Blue); if (reactionKnown) { - reactedColor.Red = (byte)Math.Min(255, Math.Max(0, expectedColor.Red + reactRedSum)); - reactedColor.Green = (byte)Math.Min(255, Math.Max(0, expectedColor.Green + reactGreenSum)); - reactedColor.Blue = (byte)Math.Min(255, Math.Max(0, expectedColor.Blue + reactBlueSum)); + recipe.ComputeReactedColor(profile, ref reactedColor); reactionSwatch.Color = reactedColor; } else @@ -574,9 +532,6 @@ public partial class MainWindow : Gtk.Wi unsafe bool CaptureReactionColor() { // Take a screenshot. - byte r, g, b; - int pixelStart, otherPixelStart; - bool colorMatch; Gdk.Image rootImage = rootWindow.GetImage(0, 0, screenWidth, screenHeight); screenBuffer.GetFromImage(rootImage, rootImage.Colormap, 0, 0, 0, 0, screenWidth, screenHeight); //screenBuffer.GetFromDrawable(rootWindow, @@ -584,117 +539,25 @@ public partial class MainWindow : Gtk.Wi int stride = screenBuffer.Rowstride; byte* pixBytes = (byte*)screenBuffer.Pixels; - for (int x = 0; x < screenWidth - colorBarWidth; ++x) - { - for (int y = 0; y < (screenHeight - 53); ++y) - { - // Look for the color swatch. - pixelStart = (y * stride) + (x * 3); - r = pixBytes[pixelStart]; - g = pixBytes[pixelStart + 1]; - b = pixBytes[pixelStart + 2]; - - // 1.) Check if this is a dark pixel. - if ((r < 0x46) && (g < 0x46) && (b < 0x46)) - { - // 2.) Check the pixel above it, - // to see if it's from the papy texture. - otherPixelStart = pixelStart - stride; - if ((otherPixelStart >= 0) && - IsPapyTexture(pixBytes[otherPixelStart++], - pixBytes[otherPixelStart++], - pixBytes[otherPixelStart])) - { - // 3.) Check the pixel below where the swatch should be, - // to see if it's also from the papy texture. - otherPixelStart = pixelStart + (stride * swatchHeight); - if (IsPapyTexture(pixBytes[otherPixelStart++], - pixBytes[otherPixelStart++], - pixBytes[otherPixelStart])) - { - // pixBytes[pixelStart] = 0xFF; - // pixBytes[pixelStart + 1] = 0x00; - // pixBytes[pixelStart + 2] = 0xFF; - - // 4.) Scan the left border of the potential swatch - // location. - colorMatch = true; - for (int i = 1; i < swatchHeight - 2; ++i) - { - otherPixelStart = pixelStart + (stride * i); - if ((Math.Abs(r - pixBytes[otherPixelStart++]) > colorTolerance) || - (Math.Abs(g - pixBytes[otherPixelStart++]) > colorTolerance) || - (Math.Abs(b - pixBytes[otherPixelStart]) > colorTolerance)) - { - colorMatch = false; - break; - } - } - - if (colorMatch) - { - // WE FOUND THE SWATCH! - // Now we know where the color bars are. - otherPixelStart = pixelStart + (redBarSpacing * stride); - int pixelCount = 0; - while ((pixBytes[otherPixelStart] > 0x9F) && - (pixBytes[otherPixelStart + 1] < 0x62) && - (pixBytes[otherPixelStart + 2] < 0x62)) - { - pixelCount++; - // pixBytes[otherPixelStart] = 0x00; - // pixBytes[otherPixelStart + 1] = 0xFF; - // pixBytes[otherPixelStart + 2] = 0xFF; - otherPixelStart += 3; - } - - reactedColor.Red = (byte)Math.Round((float)pixelCount * 255f / (float)colorBarWidth); - otherPixelStart = pixelStart + (greenBarSpacing * stride); - - pixelCount = 0; - while ((pixBytes[otherPixelStart] < 0x62) && - (pixBytes[otherPixelStart + 1] > 0x9F) && - (pixBytes[otherPixelStart + 2] < 0x62)) - { - pixelCount++; - // pixBytes[otherPixelStart] = 0x00; - // pixBytes[otherPixelStart + 1] = 0xFF; - // pixBytes[otherPixelStart + 2] = 0xFF; - otherPixelStart += 3; - } - - reactedColor.Green = (byte)Math.Round((float)pixelCount * 255f / (float)colorBarWidth); - otherPixelStart = pixelStart + (blueBarSpacing * stride); - - pixelCount = 0; - while ((pixBytes[otherPixelStart] < 0x62) && - (pixBytes[otherPixelStart + 1] < 0x62) && - (pixBytes[otherPixelStart + 2] > 0x9F)) - { - pixelCount++; - // pixBytes[otherPixelStart] = 0x00; - // pixBytes[otherPixelStart + 1] = 0xFF; - // pixBytes[otherPixelStart + 2] = 0xFF; - otherPixelStart += 3; - } - - reactedColor.Blue = (byte)Math.Round((float)pixelCount * 255f / (float)colorBarWidth); - - // write out the screenshot - //screenBuffer.Save("screenshot.png", "png"); - return true; - } - } - } - } - } - } + bool wasCaptured = ReactionRecorder.CaptureReaction(pixBytes, screenWidth, screenHeight, stride, ref reactedColor); + if (!wasCaptured && enableDebugMenu) + { + // write out the screenshot + string screenshotDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + string filename; + int i = 0; + do + { + ++i; + filename = System.IO.Path.Combine(screenshotDir, String.Format("DesertPaintLab_Colormatch{0}.png", i)); + } while (System.IO.File.Exists(filename)); + screenBuffer.Save(filename, "png"); + } //screenBuffer.Save("screenshot.png", "png"); - return false; - + return !wasCaptured; } - + protected virtual void OnDebugScreenshot(object sender, System.EventArgs e) { Gdk.Image rootImage = rootWindow.GetImage(0, 0, screenWidth, screenHeight); @@ -774,47 +637,10 @@ public partial class MainWindow : Gtk.Wi protected virtual void OnSaveButton(object sender, System.EventArgs e) { - int r, g, b; - if (reagent3 != null) - { - // A 3-reagent reaction. - Reaction reaction1 = profile.FindReaction(reagent1, reagent2); - Reaction reaction2 = profile.FindReaction(reagent1, reagent3); - Reaction reaction3 = profile.FindReaction(reagent2, reagent3); - - r = reactedColor.Red - expectedColor.Red; - g = reactedColor.Green - expectedColor.Green; - b = reactedColor.Blue - expectedColor.Blue; - - if (reaction2 == null) - { - r = r - reaction1.Red - reaction3.Red; - g = g - reaction1.Green - reaction3.Green; - b = b - reaction1.Blue - reaction3.Blue; - profile.SetReaction(reagent1, reagent3, new Reaction(r, g, b)); - profile.Save(); - saveButton.Sensitive = false; - } - else if (reaction3 == null) - { - r = r - reaction1.Red - reaction2.Red; - g = g - reaction1.Green - reaction2.Green; - b = b - reaction1.Blue - reaction2.Blue; - profile.SetReaction(reagent2, reagent3, new Reaction(r, g, b)); - profile.Save(); - saveButton.Sensitive = false; - } - } - else if ((reagent1 != null) && (reagent2 != null)) - { - // A 2-reagent reaction. - r = reactedColor.Red - expectedColor.Red; - g = reactedColor.Green - expectedColor.Green; - b = reactedColor.Blue - expectedColor.Blue; - profile.SetReaction(reagent1, reagent2, new Reaction(r, g, b)); - profile.Save(); - saveButton.Sensitive = false; - } + if (ReactionRecorder.RecordReaction(profile, expectedColor, reactedColor, reagents)) + { + saveButton.Sensitive = false; + } } protected virtual void OnChangedIngredient1(object sender, System.EventArgs e) @@ -884,6 +710,12 @@ public partial class MainWindow : Gtk.Wi protected virtual void OnAbout(object sender, System.EventArgs e) { AboutDialog aboutDialog = new AboutDialog(); + + aboutDialog.ProgramName = "Desert Paint Lab"; + aboutDialog.Version = "0.0.1"; + aboutDialog.Comments = "Desert Paint Lab paint reaction recorder for A Tale in the Desert"; + aboutDialog.Authors = new string [] {"Tess Snider", "Jason Maltzen"}; + //aboutDialog.Website = "http://www.google.com/"; aboutDialog.Run(); aboutDialog.Destroy(); } @@ -919,8 +751,15 @@ public partial class MainWindow : Gtk.Wi win.Show(); } - - - + protected void OnOpenRecipeGenerator(object sender, EventArgs e) + { + throw new NotImplementedException(); + } + + protected void OnShowReactionStatus(object sender, EventArgs e) + { + ReactionStatusWindow win = new ReactionStatusWindow(profile); + win.Show(); + } } diff --git a/PaintRecipe.cs b/PaintRecipe.cs new file mode 100644 --- /dev/null +++ b/PaintRecipe.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; + +namespace DesertPaintLab +{ + public class PaintRecipe + { + public struct RGB + { + public int r; + public int g; + public int b; + }; + + private List reagents = new List(); + + public PaintRecipe() + { + } + + public void AddReagent(String reagentName) + { + reagents.Add(reagentName); + } + + public void Clear() + { + reagents.Clear(); + } + + byte CalculateColor(int baseSum, int pigmentCount, int reactSum) + { + return (byte)Math.Max(Math.Min(Math.Round((((float)baseSum / (float)pigmentCount) + (float)reactSum)), 255), 0); + } + + // Compute the color including reactions based on the player's profile + public void ComputeReactedColor(PlayerProfile profile, ref PaintColor paintColor) + { + RGB baseColor; + baseColor.r = 0; + baseColor.g = 0; + baseColor.b = 0; + RGB reactionColor; + reactionColor.r = 0; + reactionColor.g = 0; + reactionColor.b = 0; + + int pigmentCount = 0; + string prevReagent = null; + + // track visited reagents so the reaction is only applied once + SortedDictionary reagentSet = new SortedDictionary(); + List prevReagents = new List(); + + foreach (string reagentName in reagents) + { + if (reagentName == null) + { + continue; + } + + Reagent reagent = ReagentManager.GetReagent(reagentName); + if (!reagent.IsCatalyst) + { + baseColor.r += reagent.Color.Red; + baseColor.g += reagent.Color.Green; + baseColor.b += reagent.Color.Blue; + pigmentCount += 1; + } + if (prevReagent == null || !prevReagent.Equals(reagentName)) + { + if (!reagentSet.ContainsKey(reagentName) && reagentSet.Count <= 4) + { + reagentSet[reagentName] = true; + // Run reactions. + foreach (Reagent otherReagent in prevReagents) + { + Reaction reaction = profile.FindReaction(otherReagent, reagent); + if (reaction != null) + { + reactionColor.r += reaction.Red; + reactionColor.g += reaction.Green; + reactionColor.b += reaction.Blue; + } + } + prevReagents.Add(reagent); + } + } + prevReagent = reagentName; + } + paintColor.Red = CalculateColor(baseColor.r, pigmentCount, reactionColor.r); + paintColor.Green = CalculateColor(baseColor.g, pigmentCount, reactionColor.g); + paintColor.Blue = CalculateColor(baseColor.b, pigmentCount, reactionColor.b); + } + + // Compute the base color without any reactions + public void ComputeBaseColor(ref PaintColor color) + { + RGB baseColor; + baseColor.r = 0; + baseColor.g = 0; + baseColor.b = 0; + int pigmentCount = 0; + foreach (string reagentName in reagents) + { + if (reagentName == null) + { + continue; + } + + Reagent reagent = ReagentManager.GetReagent(reagentName); + if (!reagent.IsCatalyst) + { + baseColor.r += reagent.Color.Red; + baseColor.g += reagent.Color.Green; + baseColor.b += reagent.Color.Blue; + pigmentCount += 1; + } + } + color.Red = CalculateColor(baseColor.r, pigmentCount, 0); + color.Green = CalculateColor(baseColor.g, pigmentCount, 0); + color.Blue = CalculateColor(baseColor.b, pigmentCount, 0); + } + } +} + diff --git a/PlayerProfile.cs b/PlayerProfile.cs --- a/PlayerProfile.cs +++ b/PlayerProfile.cs @@ -48,10 +48,12 @@ namespace DesertPaintLab System.IO.Directory.CreateDirectory(directory); // Copy template files into new directory. - string templatePath = System.IO.Path.Combine( - System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), - "template"); + string templatePath = FileUtils.FindApplicationResourceDirectory("template"); + if (!System.IO.Directory.Exists(templatePath)) + { + } + DirectoryInfo di = new DirectoryInfo(templatePath); FileInfo[] templateFiles = di.GetFiles(); diff --git a/ReactionRecorder.cs b/ReactionRecorder.cs new file mode 100644 --- /dev/null +++ b/ReactionRecorder.cs @@ -0,0 +1,180 @@ +using System; + +namespace DesertPaintLab +{ + // ReactionRecorder - business logic for recording paint reactions + public class ReactionRecorder + { + const int colorTolerance = 2; + + const int swatchHeight = 24; + const int colorBarWidth = 306; + const int redBarSpacing = 32; + const int greenBarSpacing = 42; + const int blueBarSpacing = 52; + + private static bool IsPapyTexture(byte r, byte g, byte b) + { + return ((r > 0xD0) && (g > 0xC8) && (b > 0xA0)) && + ((r < 0xF4) && (g < 0xE0) && (b < 0xC4)); + } + + unsafe public static bool CaptureReaction(byte* pixBytes, int screenshotWidth, int screenshotHeight, int stride, ref PaintColor reactedColor) + { + byte r, g, b; + int pixelStart, otherPixelStart; + bool colorMatch; + for (int x = 0; x < screenshotWidth - colorBarWidth; ++x) + { + for (int y = 0; y < (screenshotHeight - 53); ++y) + { + // Look for the color swatch. + pixelStart = (y * stride) + (x * 3); + r = pixBytes[pixelStart]; + g = pixBytes[pixelStart + 1]; + b = pixBytes[pixelStart + 2]; + + // 1.) Check if this is a dark pixel. + if ((r < 0x46) && (g < 0x46) && (b < 0x46)) + { + // 2.) Check the pixel above it, + // to see if it's from the papy texture. + otherPixelStart = pixelStart - stride; + if ((otherPixelStart >= 0) && + IsPapyTexture(pixBytes[otherPixelStart++], + pixBytes[otherPixelStart++], + pixBytes[otherPixelStart])) + { + // 3.) Check the pixel below where the swatch should be, + // to see if it's also from the papy texture. + otherPixelStart = pixelStart + (stride * swatchHeight); + if (IsPapyTexture(pixBytes[otherPixelStart++], + pixBytes[otherPixelStart++], + pixBytes[otherPixelStart])) + { + // pixBytes[pixelStart] = 0xFF; + // pixBytes[pixelStart + 1] = 0x00; + // pixBytes[pixelStart + 2] = 0xFF; + + // 4.) Scan the left border of the potential swatch + // location. + colorMatch = true; + for (int i = 1; i < swatchHeight - 2; ++i) + { + otherPixelStart = pixelStart + (stride * i); + if ((Math.Abs(r - pixBytes[otherPixelStart++]) > colorTolerance) || + (Math.Abs(g - pixBytes[otherPixelStart++]) > colorTolerance) || + (Math.Abs(b - pixBytes[otherPixelStart]) > colorTolerance)) + { + colorMatch = false; + break; + } + } + + if (colorMatch) + { + // WE FOUND THE SWATCH! + // Now we know where the color bars are. + int redPixelStart = pixelStart + (redBarSpacing * stride); + int redPixelCount = 0; + while ((pixBytes[redPixelStart] > 0x9F) && + (pixBytes[redPixelStart + 1] < 0x62) && + (pixBytes[redPixelStart + 2] < 0x62)) + { + redPixelCount++; + // pixBytes[redPixelStart] = 0x00; + // pixBytes[redPixelStart + 1] = 0xFF; + // pixBytes[redPixelStart + 2] = 0xFF; + redPixelStart += 3; + } + + reactedColor.Red = (byte)Math.Round((float)redPixelCount * 255f / (float)colorBarWidth); + int greenPixelStart = pixelStart + (greenBarSpacing * stride); + + int greenPixelCount = 0; + while ((pixBytes[greenPixelStart] < 0x62) && + (pixBytes[greenPixelStart + 1] > 0x9F) && + (pixBytes[greenPixelStart + 2] < 0x62)) + { + greenPixelCount++; + // pixBytes[greenPixelStart] = 0x00; + // pixBytes[greenPixelStart + 1] = 0xFF; + // pixBytes[greenPixelStart + 2] = 0xFF; + greenPixelStart += 3; + } + + reactedColor.Green = (byte)Math.Round((float)greenPixelCount * 255f / (float)colorBarWidth); + int bluePixelStart = pixelStart + (blueBarSpacing * stride); + + int bluePixelCount = 0; + while ((pixBytes[bluePixelStart] < 0x62) && + (pixBytes[bluePixelStart + 1] < 0x62) && + (pixBytes[bluePixelStart + 2] > 0x9F)) + { + bluePixelCount++; + // pixBytes[bluePixelStart] = 0x00; + // pixBytes[bluePixelStart + 1] = 0xFF; + // pixBytes[bluePixelStart + 2] = 0xFF; + bluePixelStart += 3; + } + + reactedColor.Blue = (byte)Math.Round((float)bluePixelCount * 255f / (float)colorBarWidth); + return true; + } + } + } + } + } + } + return false; + } + + public static bool RecordReaction(PlayerProfile profile, PaintColor expectedColor, PaintColor reactedColor, Reagent[] reagents) + { + bool saved = false; + int r, g, b; + if (reagents[2] != null) + { + // A 3-reagent reaction. + Reaction reaction1 = profile.FindReaction(reagents[0], reagents[1]); + Reaction reaction2 = profile.FindReaction(reagents[0], reagents[2]); + Reaction reaction3 = profile.FindReaction(reagents[1], reagents[2]); + + r = reactedColor.Red - expectedColor.Red; + g = reactedColor.Green - expectedColor.Green; + b = reactedColor.Blue - expectedColor.Blue; + + if (reaction2 == null) + { + r = r - reaction1.Red - reaction3.Red; + g = g - reaction1.Green - reaction3.Green; + b = b - reaction1.Blue - reaction3.Blue; + profile.SetReaction(reagents[0], reagents[2], new Reaction(r, g, b)); + profile.Save(); + saved = true; + } + else if (reaction3 == null) + { + r = r - reaction1.Red - reaction2.Red; + g = g - reaction1.Green - reaction2.Green; + b = b - reaction1.Blue - reaction2.Blue; + profile.SetReaction(reagents[1], reagents[2], new Reaction(r, g, b)); + profile.Save(); + saved = true; + } + } + else if ((reagents[0] != null) && (reagents[1] != null)) + { + // A 2-reagent reaction. + r = reactedColor.Red - expectedColor.Red; + g = reactedColor.Green - expectedColor.Green; + b = reactedColor.Blue - expectedColor.Blue; + profile.SetReaction(reagents[0], reagents[1], new Reaction(r, g, b)); + profile.Save(); + saved = true; + } + return saved; + } + } +} + diff --git a/ReactionStatusWindow.cs b/ReactionStatusWindow.cs new file mode 100644 --- /dev/null +++ b/ReactionStatusWindow.cs @@ -0,0 +1,54 @@ +using System; + +namespace DesertPaintLab +{ + public partial class ReactionStatusWindow : Gtk.Window + { + //PlayerProfile profile; + + public ReactionStatusWindow(PlayerProfile profile) : base(Gtk.WindowType.Toplevel) + { + //this.profile = profile; + this.Build(); + + //Gtk.CellRendererText reagentColumnCell = new Gtk.CellRendererText(); + + int count = 0; + foreach (string name1 in ReagentManager.Names) + { + Reagent reagent1 = ReagentManager.GetReagent(name1); + foreach (string name2 in ReagentManager.Names) + { + if (name1.Equals(name2)) + { + continue; + } + Reagent reagent2 = ReagentManager.GetReagent(name2); + Reaction reaction = profile.FindReaction(reagent1, reagent2); + if (reaction == null) + { + if (count == 0) + { + Gtk.Label header = new Gtk.Label("Missing Reactions:"); + resultbox.PackStart(header, false, false, 0); + Gtk.HSeparator sep = new Gtk.HSeparator(); + resultbox.PackStart(sep, false, false, 0); + } + Gtk.Label label = new Gtk.Label(name1 + " + " + name2); + resultbox.PackStart(label, false, false, 0); + + ++count; + } + } + } + + if (count == 0) + { + Gtk.Label header = new Gtk.Label("All reactions recorded!"); + resultbox.PackStart(header, false, false, 0); + } + ShowAll(); + } + } +} + diff --git a/Reagent.cs b/Reagent.cs --- a/Reagent.cs +++ b/Reagent.cs @@ -6,6 +6,7 @@ namespace DesertPaintLab { string name; bool isCatalyst = false; + int cost = 0; PaintColor color; public bool IsCatalyst @@ -32,16 +33,18 @@ namespace DesertPaintLab } } - public Reagent(string name) + public Reagent(string name, int cost) { this.name = name; + this.cost = cost; isCatalyst = true; } - public Reagent(string name, byte red, byte green, byte blue) + public Reagent(string name, byte red, byte green, byte blue, int cost) { color = new PaintColor(red, green, blue); this.name = name; + this.cost = cost; } public override string ToString() diff --git a/ReagentManager.cs b/ReagentManager.cs --- a/ReagentManager.cs +++ b/ReagentManager.cs @@ -7,10 +7,11 @@ namespace DesertPaintLab { public class ReagentManager { - static Regex reagentRegex = new Regex(@"(?\w+)\s*\|\s*(?\d+),\s*(?\d+),\s*(?\d+).*"); - static Regex catalystRegex = new Regex(@"(?\w+)\s*\|\s*catalyst"); + static Regex reagentRegex = new Regex(@"(?\w+)\s*\|\s*(?\d+),\s*(?\d+),\s*(?\d+)\s*\|\s*(?\d+)\s*\|.*"); + static Regex catalystRegex = new Regex(@"(?\w+)\s*\|\s*catalyst\s*\|\s*(?\d+)\s*\|.*"); static SortedDictionary reagents = new SortedDictionary(); + static List names = new List(); static Gtk.ListStore nameStore = new Gtk.ListStore(typeof(string)); @@ -21,6 +22,14 @@ namespace DesertPaintLab return nameStore; } } + + static public List Names + { + get + { + return names; + } + } public ReagentManager () { @@ -44,8 +53,10 @@ namespace DesertPaintLab new Reagent(name, byte.Parse(match.Groups["red"].Value), byte.Parse(match.Groups["green"].Value), - byte.Parse(match.Groups["blue"].Value))); + byte.Parse(match.Groups["blue"].Value), + int.Parse(match.Groups["cost"].Value))); nameStore.AppendValues(name); + names.Add(name); } else { @@ -53,8 +64,10 @@ namespace DesertPaintLab if (match.Success) { string name = match.Groups["name"].Value; - reagents.Add(name, new Reagent(name)); + int cost = int.Parse(match.Groups["cost"].Value); + reagents.Add(name, new Reagent(name, cost)); nameStore.AppendValues(name); + names.Add(name); } } } @@ -88,9 +101,9 @@ namespace DesertPaintLab comboBox.Model = store; store.AppendValues(""); - foreach (KeyValuePair pair in reagents) + foreach (string name in names) { - store.AppendValues(pair.Key); + store.AppendValues(name); } } diff --git a/SimulatorWindow.cs b/SimulatorWindow.cs --- a/SimulatorWindow.cs +++ b/SimulatorWindow.cs @@ -29,6 +29,7 @@ namespace DesertPaintLab { PlayerProfile profile; Gtk.ListStore recipeData = new Gtk.ListStore(typeof(string), typeof(int)); + PaintRecipe paintRecipe = new PaintRecipe(); public SimulatorWindow(PlayerProfile profile) : base(Gtk.WindowType.Toplevel) { @@ -119,25 +120,11 @@ namespace DesertPaintLab paintSwatch.Clear(); } - Reaction reaction; + paintRecipe.Clear(); + Gtk.TreeIter iter; string reagentName; int qty; - PaintColor color = null; - Reagent reagent = null; - - int baseRedSum = 0; - int baseGreenSum = 0; - int baseBlueSum = 0; - - int reactRedSum = 0; - int reactGreenSum = 0; - int reactBlueSum = 0; - - int pigmentCount = 0; - - SortedDictionary reagentSet = new SortedDictionary(); - List reagents = new List(); recipeData.GetIterFirst(out iter); @@ -151,46 +138,18 @@ namespace DesertPaintLab } qty = (int)recipeData.GetValue(iter, 1); - reagent = ReagentManager.GetReagent(reagentName); - if (!reagent.IsCatalyst) - { - color = reagent.Color; - baseRedSum += qty * color.Red; - baseGreenSum += qty * color.Green; - baseBlueSum += qty * color.Blue; - pigmentCount += qty; - } - if (!reagentSet.ContainsKey(reagentName) && reagentSet.Count <= 4) - { - reagentSet[reagentName] = true; - // Run reactions. - foreach (Reagent otherReagent in reagents) - { - reaction = profile.FindReaction(otherReagent, reagent); - if (reaction != null) - { - reactRedSum += reaction.Red; - reactGreenSum += reaction.Green; - reactBlueSum += reaction.Blue; - } - } - reagents.Add(reagent); - } - + for (int i = 0; i < qty; ++i) + { + paintRecipe.AddReagent(reagentName); + } } while (recipeData.IterNext(ref iter)); - paintSwatch.Color = new PaintColor( - CalculateColor(baseRedSum, pigmentCount, reactRedSum), - CalculateColor(baseGreenSum, pigmentCount, reactGreenSum), - CalculateColor(baseBlueSum, pigmentCount, reactBlueSum)); + PaintColor resultColor = new PaintColor(); + paintRecipe.ComputeReactedColor(profile, ref resultColor); + paintSwatch.Color = resultColor; } - byte CalculateColor(int baseSum, int pigmentCount, int reactSum) - { - return (byte)Math.Max(Math.Min(Math.Round((((float)baseSum / (float)pigmentCount) + (float)reactSum)), 255), 0); - } - protected virtual void OnIncrementReagent (object sender, System.EventArgs e) { Gtk.TreeModel model; diff --git a/gtk-gui/DesertPaintLab.ReactionStatusWindow.cs b/gtk-gui/DesertPaintLab.ReactionStatusWindow.cs new file mode 100644 --- /dev/null +++ b/gtk-gui/DesertPaintLab.ReactionStatusWindow.cs @@ -0,0 +1,41 @@ + +// This file has been generated by the GUI designer. Do not modify. +namespace DesertPaintLab +{ + public partial class ReactionStatusWindow + { + private global::Gtk.ScrolledWindow scroller; + + private global::Gtk.VBox resultbox; + + protected virtual void Build () + { + global::Stetic.Gui.Initialize (this); + // Widget DesertPaintLab.ReactionStatusWindow + this.Name = "DesertPaintLab.ReactionStatusWindow"; + this.Title = "Reaction Status"; + this.WindowPosition = ((global::Gtk.WindowPosition)(4)); + // Container child DesertPaintLab.ReactionStatusWindow.Gtk.Container+ContainerChild + this.scroller = new global::Gtk.ScrolledWindow (); + this.scroller.CanFocus = true; + this.scroller.Name = "scroller"; + this.scroller.ShadowType = ((global::Gtk.ShadowType)(1)); + // Container child scroller.Gtk.Container+ContainerChild + global::Gtk.Viewport w1 = new global::Gtk.Viewport (); + w1.ShadowType = ((global::Gtk.ShadowType)(0)); + // Container child GtkViewport.Gtk.Container+ContainerChild + this.resultbox = new global::Gtk.VBox (); + this.resultbox.Name = "resultbox"; + this.resultbox.Spacing = 6; + w1.Add (this.resultbox); + this.scroller.Add (w1); + this.Add (this.scroller); + if ((this.Child != null)) { + this.Child.ShowAll (); + } + this.DefaultWidth = 400; + this.DefaultHeight = 300; + this.Show (); + } + } +} diff --git a/gtk-gui/MainWindow.cs b/gtk-gui/MainWindow.cs --- a/gtk-gui/MainWindow.cs +++ b/gtk-gui/MainWindow.cs @@ -27,6 +27,10 @@ public partial class MainWindow private global::Gtk.Action ScreenshotAction; + private global::Gtk.Action RecipeGeneratorAction; + + private global::Gtk.Action ReactionStatusAction; + private global::Gtk.VBox vbox1; private global::Gtk.MenuBar menubar1; @@ -124,6 +128,12 @@ public partial class MainWindow this.ScreenshotAction = new global::Gtk.Action ("ScreenshotAction", "Screenshot", null, null); this.ScreenshotAction.ShortLabel = "Screenshot"; w1.Add (this.ScreenshotAction, null); + this.RecipeGeneratorAction = new global::Gtk.Action ("RecipeGeneratorAction", "Recipe Generator", null, null); + this.RecipeGeneratorAction.ShortLabel = "Recipe Generator"; + w1.Add (this.RecipeGeneratorAction, null); + this.ReactionStatusAction = new global::Gtk.Action ("ReactionStatusAction", "Reaction Status", null, null); + this.ReactionStatusAction.ShortLabel = "Reaction Status"; + w1.Add (this.ReactionStatusAction, null); this.UIManager.InsertActionGroup (w1, 0); this.AddAccelGroup (this.UIManager.AccelGroup); this.Name = "MainWindow"; @@ -133,7 +143,7 @@ public partial class MainWindow this.vbox1 = new global::Gtk.VBox (); this.vbox1.Name = "vbox1"; // Container child vbox1.Gtk.Box+BoxChild - this.UIManager.AddUiFromString (""); + this.UIManager.AddUiFromString (""); this.menubar1 = ((global::Gtk.MenuBar)(this.UIManager.GetWidget ("/menubar1"))); this.menubar1.Name = "menubar1"; this.vbox1.Add (this.menubar1); @@ -364,6 +374,8 @@ public partial class MainWindow this.ExportForPracticalPaintAction.Activated += new global::System.EventHandler (this.OnExport); this.RunSimulatorAction.Activated += new global::System.EventHandler (this.RunSimulator); this.ScreenshotAction.Activated += new global::System.EventHandler (this.OnDebugScreenshot); + this.RecipeGeneratorAction.Activated += new global::System.EventHandler (this.OnOpenRecipeGenerator); + this.ReactionStatusAction.Activated += new global::System.EventHandler (this.OnShowReactionStatus); this.ingredient1ComboBox.Changed += new global::System.EventHandler (this.OnChangedIngredient1); this.ingredient2ComboBox.Changed += new global::System.EventHandler (this.OnChangedIngredient2); this.ingredient3ComboBox.Changed += new global::System.EventHandler (this.OnChangedIngredient3); diff --git a/gtk-gui/gui.stetic b/gtk-gui/gui.stetic --- a/gtk-gui/gui.stetic +++ b/gtk-gui/gui.stetic @@ -78,6 +78,18 @@ Screenshot + + Action + Recipe Generator + Recipe Generator + + + + Action + Reaction Status + Reaction Status + + Desert Paint Lab @@ -99,9 +111,11 @@ + + @@ -1086,4 +1100,37 @@ You can either import an existing Practi + + + Reaction Status + CenterOnParent + + + + True + In + + + + None + + + + 6 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mac/Info.plist b/mac/Info.plist new file mode 100644 --- /dev/null +++ b/mac/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + launcher.sh + CFBundleIconFile + DesertPaintLab.icns + CFBundleIdentifier + org.malkyne.desert-paint-lab + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Desert Paint Lab + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.7.4 + CFBundleSignature + xmmd + CFBundleVersion + 1.7.4 + NSAppleScriptEnabled + NO + + diff --git a/mac/build_mac_bundle.sh b/mac/build_mac_bundle.sh new file mode 100644 --- /dev/null +++ b/mac/build_mac_bundle.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +/bin/mv DesertPaintLab.app DesertPaintLab.app.`/bin/date +"%Y%m%d%H%M%S"` +/bin/mkdir -p DesertPaintLab.app +/bin/cp Info.plist DesertPaintLab.app/Info.plist +/bin/mkdir -p DesertPaintLab.app/Contents/MacOS +/bin/cp launcher.sh DesertPaintLab.app/Contents/MacOS +/bin/chmod 755 DesertPaintLab.app/Contents/MacOS/launcher.sh +/bin/cp ../bin/Release/DesertPaintLab.exe DesertPaintLab.app/Contents/MacOS/ +/bin/mkdir -p DesertPaintLab.app/Contents/Resources +/bin/cp ../bin/Release/colors.txt DesertPaintLab.app/Contents/Resources/ +/bin/cp -r ../bin/Release/template DesertPaintLab.app/Contents/Resources/template diff --git a/mac/launcher.sh b/mac/launcher.sh new file mode 100755 --- /dev/null +++ b/mac/launcher.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +APPNAME="DesertPaintLab" + +DIR=$(cd "$(dirname "$0")"; pwd) + +EXE_PATH="$DIR\DesertPaintLab.exe" +PROCESS_NAME=desertpaintlab + +MONO_FRAMEWORK_PATH=/Library/Frameworks/Mono.framework/Versions/Current +export DYLD_FALLBACK_LIBRARY_PATH="$DIR:$MONO_FRAMEWORK_PATH/lib:/lib:/usr/lib" +export PATH="$MONO_FRAMEWORK_PATH/bin:$PATH" + +#mono version check + +REQUIRED_MAJOR=4 +REQUIRED_MINOR=0 + +VERSION_TITLE="Cannot launch $APPNAME" +VERSION_MSG="$APPNAME requires the Mono Framework version $REQUIRED_MAJOR.$REQUIRED_MINOR or later." +DOWNLOAD_URL="http://www.go-mono.com/mono-downloads/download.html" + +MONO_VERSION="$(mono --version | grep 'Mono JIT compiler version ' | cut -f5 -d\ )" +MONO_VERSION_MAJOR="$(echo $MONO_VERSION | cut -f1 -d.)" +MONO_VERSION_MINOR="$(echo $MONO_VERSION | cut -f2 -d.)" +if [ -z "$MONO_VERSION" ] \ + || [ $MONO_VERSION_MAJOR -lt $REQUIRED_MAJOR ] \ + || [ $MONO_VERSION_MAJOR -eq $REQUIRED_MAJOR -a $MONO_VERSION_MINOR -lt $REQUIRED_MINOR ] +then + /usr/bin/osascript \ + -e "set question to display dialog \"$VERSION_MSG\" with title \"$VERSION_TITLE\" buttons {\"Cancel\", \"Download...\"} default button 2" \ + -e "if button returned of question is equal to \"Download...\" then open location \"$DOWNLOAD_URL\"" + echo "$VERSION_TITLE" + echo "$VERSION_MSG" + exit 1 +fi + +OSX_VERSION=$(uname -r | cut -f1 -d.) +if [ $OSX_VERSION -lt 9 ]; then # If OSX version is 10.4 + MONO_EXEC="exec mono" +else + MONO_EXEC="exec -a \"$PROCESS_NAME\" mono" +fi + +LOG_FILE="$HOME/Library/Logs/$APPNAME/$APPNAME.log" +/bin/mkdir -p "`dirname \"$LOG_FILE\"`" +$MONO_EXEC $MONO_OPTIONS "$EXE_PATH" $* 2>&1 1> "$LOG_FILE" +