Skip to content

Instantly share code, notes, and snippets.

@SplenectomY
Last active August 13, 2018 00:41
Show Gist options
  • Save SplenectomY/859e85b2dd77246acc65ab6406bdc75b to your computer and use it in GitHub Desktop.
Save SplenectomY/859e85b2dd77246acc65ab6406bdc75b to your computer and use it in GitHub Desktop.
Space Engineers programming block script for calculating profitable trade routes, indirectly implements Frontier Economy mod and a handy LCD Network script
//For use with the Frontier Economy mod
//
//You'll need a grid that collects the prices of all the shops on the server. I recommend
//Network LCDs for this job. One shop per LCD is ideal. Doesn't matter if the prices don't all fit
//on screen. You should hide these ugly things out of sight anyway.
//
//On each screen, put [PRICES] in the name. Should then look like [Economy] [PRICES]
//
//Create an LCD with [Routes] in the name. That's it! This script will do the rest.
//It's a bit heavy on calculations so consider putting it on a timer block. See line 15.
bool debug = false;
public Program()
{
// Comment out this next line if you're using a timer block
Runtime.UpdateFrequency = UpdateFrequency.Update100;
}
List<IMyTextPanel> list = new List<IMyTextPanel>();
List<IMyTextPanel> statPanels = new List<IMyTextPanel>();
public void Main(string argument, UpdateType updateSource)
{
ScreenFormatter sf = new ScreenFormatter(2);
sf.SetFill(1, 1);
sf.SetAlign(1, 1);
sf.SetFill(0, 0);
sf.SetBar(1, true);
sf.Add(0, "Current Profitable Trade Routes:", true);
statPanels.Clear();
bool noroutes = true;
List<string[]> listbyitem = new List<string[]>();
string[] testString = new string[] {"test", "test", "test", "test", "test"};
listbyitem.Add(testString);
GridTerminalSystem.GetBlocksOfType<IMyTextPanel>(list, r =>
{
if (r.CustomName.Contains("[Routes]"))
statPanels.Add(r as IMyTextPanel);
if (r.CustomName.Contains("[PRICES]")) {
if (debug == true) Echo("Found a TextPanel with [PRICES] in the name!");
string input = r.GetPublicText();
string pattern = @"\s+([\w\s]+)\s«";
string shopname = "";
System.Text.RegularExpressions.Match match = System.Text.RegularExpressions.Regex.Match(input, pattern);
if (match.Success) {
if (debug == true) Echo("Match 1 was a success!");
foreach (System.Text.RegularExpressions.Capture capture in match.Groups[1].Captures) {
if (debug == true) Echo("Found a shopname!");
shopname = capture.Value;
}
}
if (shopname != "") {
string pattern2= @"([\w\d\.\s]*?)\s+([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)";
foreach (System.Text.RegularExpressions.Match m2 in System.Text.RegularExpressions.Regex.Matches(input, pattern2)) {
string name = m2.Groups[1].Value;
string sell = m2.Groups[2].Value;
string buy = m2.Groups[3].Value;
if (debug == true) Echo("Found an item: " + name + " " + sell + " " + buy);
string[] stringArr = new string[] {name, sell, shopname, buy, shopname};
listbyitem.Add(stringArr);
//Echo(stringArr);
}
foreach (System.Text.RegularExpressions.Match m2 in System.Text.RegularExpressions.Regex.Matches(input, pattern2)) {
string name = m2.Groups[1].Value;
string sell = m2.Groups[2].Value;
string buy = m2.Groups[3].Value;
foreach(var s in listbyitem) {
if (s[0] == name){
if (debug == true) Echo("Found an existing item, attempting to adjust values");
if (Convert.ToDecimal(sell) > Convert.ToDecimal(s[1])){
s[1] = sell;
s[2] = shopname;
}
if (Convert.ToDecimal(buy) < Convert.ToDecimal(s[3])){
s[3] = buy;
s[4] = shopname;
}
}
}
}
}
foreach(var k in listbyitem) {
if((k[2] != k[4]) && (Convert.ToDecimal(k[3]) < Convert.ToDecimal(k[1]))){
noroutes = false;
decimal profitMargin = Math.Round( ((Convert.ToDecimal(k[1]) - Convert.ToDecimal(k[3])) / Convert.ToDecimal(k[1])) * 100);
decimal profitPerUnit = Convert.ToDecimal(k[1]) - Convert.ToDecimal(k[3]);
string profitMarginString = profitMargin.ToString();
string profitPerUnitString = profitPerUnit.ToString();
string output = k[0] + ": Buy @" + k[4] + "(" + k[3] + "), Sell @" + k[2] + "(" + k[1] + ", " + profitMarginString + "%)";
if (debug == true) Echo(output);
sf.Add(0, k[0] + ":", true);
sf.Add(0, " Buy @ " + k[4], true);
sf.Add(0, " - Buy price per unit: $" + k[3], true);
sf.Add(0, " Sell @ " + k[2], true);
sf.Add(0, " - Sell price per unit: $" + k[1], true);
sf.Add(0, " Profit per unit / margin: $" + profitPerUnitString + " | " + profitMarginString + "%", true);
sf.AddBlankRow();
}
}
}
return false;
});
foreach (IMyTextPanel lcd in statPanels){
if (noroutes == true) {
sf.Add(0, "No profitable STC routes right now.", true);
sf.Add(0, "Check store prices for low stock items.", true);
sf.Add(0, "It's a manufacturer's market.", true);
}
WriteTableToPanel("LCD: Profitable Trade Routes", sf, lcd);
}
//Everything bellow written by taleden from TIM used with permission from taleden for the formatting of LCD status displays.
}
void WriteTableToPanel(string title, ScreenFormatter sf, IMyTextPanel panel, string before = "", string after = "")
{
int spanx, spany, wide, size, width, height;
int x, y;
float fontsize;
// get the spanning dimensions, if any
wide = panel.BlockDefinition.SubtypeName.EndsWith("Wide") ? 2 : 1;
size = panel.BlockDefinition.SubtypeName.StartsWith("Small") ? 3 : 1;
spanx = spany = 1;
// reduce font size to fit everything
x = sf.GetMinWidth();
x = (x / spanx) + ((x % spanx > 0) ? 1 : 0);
y = sf.GetNumRows();
y = (y / spany) + ((y % spany > 0) ? 1 : 0);
width = 658 * wide; // TODO monospace 26x17.5 chars
fontsize = panel.GetValueFloat("FontSize");
if (fontsize < 0.25f)
fontsize = 1.0f;
if (x > 0)
fontsize = Math.Min(fontsize, Math.Max(0.5f, (float)(width * 100 / x) / 100.0f));
if (y > 0)
fontsize = Math.Min(fontsize, Math.Max(0.5f, (float)(1760 / y) / 100.0f));
// calculate how much space is available on each panel
width = (int)((float)width / fontsize);
height = (int)(17.6f / fontsize);
panel.SetValueFloat("FontSize", fontsize);
panel.WritePublicTitle(title, false);
panel.WritePublicText(before + sf.ToString(width) + after, false);
panel.ShowPublicTextOnScreen();
} // WriteTableToPanel()
public class ScreenFormatter
{
private static Dictionary<char, byte> charWidth = new Dictionary<char, byte>();
private static Dictionary<string, int> textWidth = new Dictionary<string, int>();
private static byte SZ_SPACE;
private static byte SZ_SHYPH;
public static int GetWidth(string text, bool memoize = false)
{
int width;
if (!textWidth.TryGetValue(text, out width))
{
// this isn't faster (probably slower) but it's less "complex"
// according to SE's silly branch count metric
Dictionary<char, byte> cW = charWidth;
string t = text + "\0\0\0\0\0\0\0";
int i = t.Length - (t.Length % 8);
byte w0, w1, w2, w3, w4, w5, w6, w7;
while (i > 0)
{
cW.TryGetValue(t[i - 1], out w0);
cW.TryGetValue(t[i - 2], out w1);
cW.TryGetValue(t[i - 3], out w2);
cW.TryGetValue(t[i - 4], out w3);
cW.TryGetValue(t[i - 5], out w4);
cW.TryGetValue(t[i - 6], out w5);
cW.TryGetValue(t[i - 7], out w6);
cW.TryGetValue(t[i - 8], out w7);
width += w0 + w1 + w2 + w3 + w4 + w5 + w6 + w7;
i -= 8;
}
if (memoize)
textWidth[text] = width;
}
return width;
} // GetWidth()
public static string Format(string text, int width, out int unused, int align = -1, bool memoize = false)
{
int spaces, bars;
// '\u00AD' is a "soft hyphen" in UTF16 but Panels don't wrap lines so
// it's just a wider space character ' ', useful for column alignment
unused = width - GetWidth(text, memoize);
if (unused <= SZ_SPACE / 2)
return text;
spaces = unused / SZ_SPACE;
bars = 0;
unused -= spaces * SZ_SPACE;
if (2 * unused <= SZ_SPACE + (spaces * (SZ_SHYPH - SZ_SPACE)))
{
bars = Math.Min(spaces, (int)((float)unused / (SZ_SHYPH - SZ_SPACE) + 0.4999f));
spaces -= bars;
unused -= bars * (SZ_SHYPH - SZ_SPACE);
}
else if (unused > SZ_SPACE / 2)
{
spaces++;
unused -= SZ_SPACE;
}
if (align > 0)
return new String(' ', spaces) + new String('\u00AD', bars) + text;
if (align < 0)
return text + new String('\u00AD', bars) + new String(' ', spaces);
if ((spaces % 2) > 0 & (bars % 2) == 0)
return new String(' ', spaces / 2) + new String('\u00AD', bars / 2) + text + new String('\u00AD', bars / 2) + new String(' ', spaces - (spaces / 2));
return new String(' ', spaces - (spaces / 2)) + new String('\u00AD', bars / 2) + text + new String('\u00AD', bars - (bars / 2)) + new String(' ', spaces / 2);
} // Format()
public static string Format(double value, int width, out int unused)
{
int spaces, bars;
value = Math.Min(Math.Max(value, 0.0f), 1.0f);
spaces = width / SZ_SPACE;
bars = (int)(spaces * value + 0.5f);
unused = width - (spaces * SZ_SPACE);
return new String('I', bars) + new String(' ', spaces - bars);
} // Format()
public static void Init()
{
InitChars(0, "\u2028\u2029\u202F");
InitChars(7, "'|\u00A6\u02C9\u2018\u2019\u201A");
InitChars(8, "\u0458");
InitChars(9, " !I`ijl\u00A0\u00A1\u00A8\u00AF\u00B4\u00B8\u00CC\u00CD\u00CE\u00CF\u00EC\u00ED\u00EE\u00EF\u0128\u0129\u012A\u012B\u012E\u012F\u0130\u0131\u0135\u013A\u013C\u013E\u0142\u02C6\u02C7\u02D8\u02D9\u02DA\u02DB\u02DC\u02DD\u0406\u0407\u0456\u0457\u2039\u203A\u2219");
InitChars(10, "(),.1:;[]ft{}\u00B7\u0163\u0165\u0167\u021B");
InitChars(11, "\"-r\u00AA\u00AD\u00BA\u0140\u0155\u0157\u0159");
InitChars(12, "*\u00B2\u00B3\u00B9");
InitChars(13, "\\\u00B0\u201C\u201D\u201E");
InitChars(14, "\u0491");
InitChars(15, "/\u0133\u0442\u044D\u0454");
InitChars(16, "L_vx\u00AB\u00BB\u0139\u013B\u013D\u013F\u0141\u0413\u0433\u0437\u043B\u0445\u0447\u0490\u2013\u2022");
InitChars(17, "7?Jcz\u00A2\u00BF\u00E7\u0107\u0109\u010B\u010D\u0134\u017A\u017C\u017E\u0403\u0408\u0427\u0430\u0432\u0438\u0439\u043D\u043E\u043F\u0441\u044A\u044C\u0453\u0455\u045C");
InitChars(18, "3FKTabdeghknopqsuy\u00A3\u00B5\u00DD\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E8\u00E9\u00EA\u00EB\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\u00FF\u0101\u0103\u0105\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0136\u0137\u0144\u0146\u0148\u0149\u014D\u014F\u0151\u015B\u015D\u015F\u0161\u0162\u0164\u0166\u0169\u016B\u016D\u016F\u0171\u0173\u0176\u0177\u0178\u0219\u021A\u040E\u0417\u041A\u041B\u0431\u0434\u0435\u043A\u0440\u0443\u0446\u044F\u0451\u0452\u045B\u045E\u045F");
InitChars(19, "+<=>E^~\u00AC\u00B1\u00B6\u00C8\u00C9\u00CA\u00CB\u00D7\u00F7\u0112\u0114\u0116\u0118\u011A\u0404\u040F\u0415\u041D\u042D\u2212");
InitChars(20, "#0245689CXZ\u00A4\u00A5\u00C7\u00DF\u0106\u0108\u010A\u010C\u0179\u017B\u017D\u0192\u0401\u040C\u0410\u0411\u0412\u0414\u0418\u0419\u041F\u0420\u0421\u0422\u0423\u0425\u042C\u20AC");
InitChars(21, "$&GHPUVY\u00A7\u00D9\u00DA\u00DB\u00DC\u00DE\u0100\u011C\u011E\u0120\u0122\u0124\u0126\u0168\u016A\u016C\u016E\u0170\u0172\u041E\u0424\u0426\u042A\u042F\u0436\u044B\u2020\u2021");
InitChars(22, "ABDNOQRS\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D8\u0102\u0104\u010E\u0110\u0143\u0145\u0147\u014C\u014E\u0150\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0218\u0405\u040A\u0416\u0444");
InitChars(23, "\u0459");
InitChars(24, "\u044E");
InitChars(25, "%\u0132\u042B");
InitChars(26, "@\u00A9\u00AE\u043C\u0448\u045A");
InitChars(27, "M\u041C\u0428");
InitChars(28, "mw\u00BC\u0175\u042E\u0449");
InitChars(29, "\u00BE\u00E6\u0153\u0409");
InitChars(30, "\u00BD\u0429");
InitChars(31, "\u2122");
InitChars(32, "W\u00C6\u0152\u0174\u2014\u2026\u2030");
SZ_SPACE = charWidth[' '];
SZ_SHYPH = charWidth['\u00AD'];
} // Init()
private static void InitChars(byte width, string text)
{
// more silly loop-unrolling, as in GetWidth()
Dictionary<char, byte> cW = charWidth;
string t = text + "\0\0\0\0\0\0\0";
byte w = Math.Max((byte)0, width);
int i = t.Length - (t.Length % 8);
while (i > 0)
{
cW[t[--i]] = w;
cW[t[--i]] = w;
cW[t[--i]] = w;
cW[t[--i]] = w;
cW[t[--i]] = w;
cW[t[--i]] = w;
cW[t[--i]] = w;
cW[t[--i]] = w;
}
cW['\0'] = 0;
} // InitChars()
private int numCols;
private int numRows;
private int padding;
private List<string>[] colRowText;
private List<int>[] colRowWidth;
private int[] colAlign;
private int[] colFill;
private bool[] colBar;
private int[] colWidth;
public ScreenFormatter(int numCols, int padding = 1)
{
this.numCols = numCols;
this.numRows = 0;
this.padding = padding;
this.colRowText = new List<string>[numCols];
this.colRowWidth = new List<int>[numCols];
this.colAlign = new int[numCols];
this.colFill = new int[numCols];
this.colBar = new bool[numCols];
this.colWidth = new int[numCols];
for (int c = 0; c < numCols; c++)
{
this.colRowText[c] = new List<string>();
this.colRowWidth[c] = new List<int>();
this.colAlign[c] = -1;
this.colFill[c] = 0;
this.colBar[c] = false;
this.colWidth[c] = 0;
}
} // ScreenFormatter()
public void Add(int col, string text, bool memoize = false)
{
int width = 0;
this.colRowText[col].Add(text);
if (this.colBar[col] == false)
{
width = GetWidth(text, memoize);
this.colWidth[col] = Math.Max(this.colWidth[col], width);
}
this.colRowWidth[col].Add(width);
this.numRows = Math.Max(this.numRows, this.colRowText[col].Count);
} // Add()
public void AddBlankRow()
{
for (int c = 0; c < this.numCols; c++)
{
this.colRowText[c].Add("");
this.colRowWidth[c].Add(0);
}
this.numRows++;
} // AddBlankRow()
public int GetNumRows()
{
return this.numRows;
} // GetNumRows()
public int GetMinWidth()
{
int width = this.padding * SZ_SPACE;
for (int c = 0; c < this.numCols; c++)
width += this.padding * SZ_SPACE + this.colWidth[c];
return width;
} // GetMinWidth()
public void SetAlign(int col, int align)
{
this.colAlign[col] = align;
} // SetAlign()
public void SetFill(int col, int fill = 1)
{
this.colFill[col] = fill;
} // SetFill()
public void SetBar(int col, bool bar = true)
{
this.colBar[col] = bar;
} // SetBar()
public void SetWidth(int col, int width)
{
this.colWidth[col] = width;
} // SetWidth()
public string[][] ToSpan(int width = 0, int span = 1)
{
int c, r, s, i, j, textwidth, unused, remaining;
int[] colWidth;
byte w;
double value;
string text;
StringBuilder sb;
string[][] spanLines;
// clone the user-defined widths and tally fill columns
colWidth = (int[])this.colWidth.Clone();
unused = width * span - this.padding * SZ_SPACE;
remaining = 0;
for (c = 0; c < this.numCols; c++)
{
unused -= this.padding * SZ_SPACE;
if (this.colFill[c] == 0)
unused -= colWidth[c];
remaining += this.colFill[c];
}
// distribute remaining width to fill columns
for (c = 0; c < this.numCols & remaining > 0; c++)
{
if (this.colFill[c] > 0)
{
colWidth[c] = Math.Max(colWidth[c], this.colFill[c] * unused / remaining);
unused -= colWidth[c];
remaining -= this.colFill[c];
}
}
// initialize output arrays
spanLines = new string[span][];
for (s = 0; s < span; s++)
spanLines[s] = new string[this.numRows];
span--; // make "span" inclusive so "s < span" implies one left
// render all rows and columns
i = 0;
sb = new StringBuilder();
for (r = 0; r < this.numRows; r++)
{
sb.Clear();
s = 0;
remaining = width;
unused = 0;
for (c = 0; c < this.numCols; c++)
{
unused += this.padding * SZ_SPACE;
if (r >= this.colRowText[c].Count || colRowText[c][r] == "")
{
unused += colWidth[c];
}
else
{
// render the bar, or fetch the cell text
text = this.colRowText[c][r];
charWidth.TryGetValue(text[0], out w);
textwidth = this.colRowWidth[c][r];
if (this.colBar[c] == true)
{
value = 0.0;
if (double.TryParse(text, out value))
value = Math.Min(Math.Max(value, 0.0), 1.0);
i = (int)((colWidth[c] / SZ_SPACE) * value + 0.5);
w = SZ_SPACE;
textwidth = i * SZ_SPACE;
}
// if the column is not left-aligned, calculate left spacing
if (this.colAlign[c] > 0)
{
unused += (colWidth[c] - textwidth);
}
else if (this.colAlign[c] == 0)
{
unused += (colWidth[c] - textwidth) / 2;
}
// while the left spacing leaves no room for text, adjust it
while (s < span & unused > remaining - w)
{
sb.Append(' ');
spanLines[s][r] = sb.ToString();
sb.Clear();
s++;
unused -= remaining;
remaining = width;
}
// add left spacing
remaining -= unused;
sb.Append(Format("", unused, out unused));
remaining += unused;
// if the column is not right-aligned, calculate right spacing
if (this.colAlign[c] < 0)
{
unused += (colWidth[c] - textwidth);
}
else if (this.colAlign[c] == 0)
{
unused += (colWidth[c] - textwidth) - ((colWidth[c] - textwidth) / 2);
}
// while the bar or text runs to the next span, split it
if (this.colBar[c] == true)
{
while (s < span & textwidth > remaining)
{
j = remaining / SZ_SPACE;
remaining -= j * SZ_SPACE;
textwidth -= j * SZ_SPACE;
sb.Append(new String('I', j));
spanLines[s][r] = sb.ToString();
sb.Clear();
s++;
unused -= remaining;
remaining = width;
i -= j;
}
text = new String('I', i);
}
else
{
while (s < span & textwidth > remaining)
{
i = 0;
while (remaining >= w)
{
remaining -= w;
textwidth -= w;
charWidth.TryGetValue(text[++i], out w);
}
sb.Append(text, 0, i);
spanLines[s][r] = sb.ToString();
sb.Clear();
s++;
unused -= remaining;
remaining = width;
text = text.Substring(i);
}
}
// add cell text
remaining -= textwidth;
sb.Append(text);
}
}
spanLines[s][r] = sb.ToString();
}
return spanLines;
} // ToSpan()
public string ToString(int width = 0)
{
return String.Join("\n", this.ToSpan(width, 1)[0]);
} // ToString()
} // ScreenFormatter
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment