diff --git a/.gitignore b/.gitignore
index cb14307..81187fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,8 @@
*.exe
*.dll
*.pdb
-*/bin/*
-*/obj/*
-*.csproj.user
-*.DotSettings.user
+**/bin/**
+**/obj/**
*.suo
.vs
+*.user
diff --git a/.idea/.idea.VAR.ScreenAutomation/.idea/.gitignore b/.idea/.idea.VAR.ScreenAutomation/.idea/.gitignore
new file mode 100644
index 0000000..c516e08
--- /dev/null
+++ b/.idea/.idea.VAR.ScreenAutomation/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/projectSettingsUpdater.xml
+/.idea.VAR.ScreenAutomation.iml
+/contentModel.xml
+/modules.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.VAR.ScreenAutomation/.idea/encodings.xml b/.idea/.idea.VAR.ScreenAutomation/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/.idea/.idea.VAR.ScreenAutomation/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.VAR.ScreenAutomation/.idea/indexLayout.xml b/.idea/.idea.VAR.ScreenAutomation/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.VAR.ScreenAutomation/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.VAR.ScreenAutomation/.idea/vcs.xml b/.idea/.idea.VAR.ScreenAutomation/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/.idea.VAR.ScreenAutomation/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VAR.ScreenAutomation.sln b/VAR.ScreenAutomation.sln
new file mode 100644
index 0000000..8f78bf1
--- /dev/null
+++ b/VAR.ScreenAutomation.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29418.71
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VAR.ScreenAutomation", "VAR.ScreenAutomation\VAR.ScreenAutomation.csproj", "{E2BE8E2A-3422-42A6-82FA-5E0CCDC42032}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E2BE8E2A-3422-42A6-82FA-5E0CCDC42032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E2BE8E2A-3422-42A6-82FA-5E0CCDC42032}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E2BE8E2A-3422-42A6-82FA-5E0CCDC42032}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E2BE8E2A-3422-42A6-82FA-5E0CCDC42032}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {8DEFE02E-819D-4286-8378-BE842DBBBFC4}
+ EndGlobalSection
+EndGlobal
diff --git a/VAR.ScreenAutomation.sln.DotSettings b/VAR.ScreenAutomation.sln.DotSettings
new file mode 100644
index 0000000..33636c2
--- /dev/null
+++ b/VAR.ScreenAutomation.sln.DotSettings
@@ -0,0 +1,60 @@
+
+ NotRequired
+ True
+ True
+ TOGETHER_SAME_LINE
+ True
+ True
+ False
+ True
+ True
+ True
+ OneStep
+ OneStep
+ True
+ UseVarWhenEvident
+ UseVarWhenEvident
+ AES
+ AM
+ AUX
+ BPP
+ CYC
+ DC
+ DES
+ EPM
+ GDI
+ ID
+ IP
+ RECT
+ RGB
+ SCART
+ SPDIF
+ SQL
+ SRCCOPY
+ TCP
+ URL
+ USB
+ VAR
+ WMIC
+ YRYBY
+ False
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb"><ExtraRule Prefix="T" Suffix="" Style="AaBb_AaBb" /></Policy>
+ <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb_AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" />
+ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb_AaBb" />
+ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb_AaBb" />
+ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb_AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" />
+ True
+ True
+ True
+ True
+
\ No newline at end of file
diff --git a/VAR.ScreenAutomation/App.config b/VAR.ScreenAutomation/App.config
new file mode 100644
index 0000000..7597626
--- /dev/null
+++ b/VAR.ScreenAutomation/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VAR.ScreenAutomation/Bots/DummyBot.cs b/VAR.ScreenAutomation/Bots/DummyBot.cs
new file mode 100644
index 0000000..300a60b
--- /dev/null
+++ b/VAR.ScreenAutomation/Bots/DummyBot.cs
@@ -0,0 +1,32 @@
+using System.Drawing;
+using VAR.ScreenAutomation.Interfaces;
+
+namespace VAR.ScreenAutomation.Bots
+{
+ public class DummyBot : IAutomationBot
+ {
+ public string Name => "Dummy";
+
+ public IConfiguration GetDefaultConfiguration()
+ {
+ return null;
+ }
+
+ public void Init(IOutputHandler output, IConfiguration config)
+ {
+ output.Clean();
+ }
+
+ public Bitmap Process(Bitmap bmpInput, IOutputHandler output)
+ {
+ return bmpInput;
+ }
+
+ public string ResponseKeys()
+ {
+ // https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.sendkeys?view=netframework-4.8
+ //return "{UP}";
+ return string.Empty;
+ }
+ }
+}
\ No newline at end of file
diff --git a/VAR.ScreenAutomation/Bots/TetrisBot.cs b/VAR.ScreenAutomation/Bots/TetrisBot.cs
new file mode 100644
index 0000000..c6b0007
--- /dev/null
+++ b/VAR.ScreenAutomation/Bots/TetrisBot.cs
@@ -0,0 +1,895 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using VAR.ScreenAutomation.Code;
+using VAR.ScreenAutomation.Interfaces;
+
+namespace VAR.ScreenAutomation.Bots
+{
+ public class TetrisBot : IAutomationBot
+ {
+ private TetrisGrid _grid;
+
+ private List _currentShape;
+ private TetrisGrid _workGrid0;
+ private TetrisGrid _workGrid1;
+ private double[] _columnEvaluation;
+
+ private bool _shapeFound;
+ private int _shapeX;
+ private int _shapeY;
+ private double _bestEvaluation = double.MinValue;
+ private int _bestXOffset;
+ private int _bestRotation;
+
+ public string Name => "Tetris";
+
+ private const int DefaultGridWidth = 10;
+ private const int DefaultGridHeight = 20;
+ private const int DefaultShotCooldownFrames = 2;
+
+ public IConfiguration GetDefaultConfiguration()
+ {
+ var defaultConfiguration = new MemoryBackedConfiguration();
+ defaultConfiguration.Set("GridWidth", DefaultGridWidth);
+ defaultConfiguration.Set("GridHeight", DefaultGridHeight);
+ defaultConfiguration.Set("ShotCooldownFrames", DefaultShotCooldownFrames);
+ return defaultConfiguration;
+ }
+
+ public void Init(IOutputHandler output, IConfiguration config)
+ {
+ int gridWidth = config.Get("GridWidth", DefaultGridWidth);
+ int gridHeight = config.Get("GridHeight", DefaultGridHeight);
+ _shotCooldownFrames = config.Get("ShotCooldownFrames", DefaultShotCooldownFrames);
+
+ _grid = new TetrisGrid(gridWidth, gridHeight);
+ _workGrid0 = new TetrisGrid(gridWidth, gridHeight);
+ _workGrid1 = new TetrisGrid(gridWidth, gridHeight);
+ _columnEvaluation = new double[gridWidth];
+ _currentShape = new List
+ {
+ new TetrisShape(),
+ new TetrisShape(),
+ new TetrisShape(),
+ new TetrisShape(),
+ };
+ output.Clean();
+ output.AddLine($"TetrisBot: Starting {DateTime.UtcNow:s}");
+ }
+
+ public Bitmap Process(Bitmap bmpInput, IOutputHandler output)
+ {
+ _grid.SampleFromBitmap(bmpInput);
+ SearchShape();
+ SearchBestAction();
+
+ // Show information
+ _workGrid0.SampleOther(_grid, TetrisGrid.CellSolid, TetrisGrid.CellSolid);
+ if (_shapeFound)
+ {
+ _currentShape[0].PutOnGrid(_workGrid0, _shapeX, _shapeY, TetrisGrid.CellEmpty);
+ _currentShape[_bestRotation].Drop(_workGrid0, _shapeX + _bestXOffset, _shapeY, TetrisGrid.CellShapeA);
+ _currentShape[0].PutOnGrid(_workGrid0, _shapeX, _shapeY, TetrisGrid.CellShapeB);
+ }
+
+ _workGrid0.Draw(bmpInput, 0.75f);
+ _workGrid0.SampleOther(_grid, TetrisGrid.CellSolid, TetrisGrid.CellSolid);
+ _workGrid0.RemoveGround();
+ _workGrid0.Draw(bmpInput, 0.25f);
+
+ return bmpInput;
+ }
+
+ private void SearchShape()
+ {
+ _workGrid0.SampleOther(_grid, TetrisGrid.CellSolid, TetrisGrid.CellSolid);
+ _workGrid0.RemoveGround();
+ _shapeFound = false;
+ for (int y = 0; y < _grid.Height; y++)
+ {
+ for (int x = 0; x < _grid.Width; x++)
+ {
+ TetrisShape matchedShape =
+ TetrisShape.DefaultShapes.FirstOrDefault(s => s.MatchOnGrid(_workGrid0, x, y));
+ if (matchedShape != null)
+ {
+ _workGrid1.SampleOther(_workGrid0, TetrisGrid.CellSolid, TetrisGrid.CellSolid);
+ matchedShape.PutOnGrid(_workGrid1, x, y, TetrisGrid.CellEmpty);
+ if (matchedShape.CheckIntersection(_workGrid1, x, y + 1)) { continue; }
+
+ // Shape found
+ _currentShape[0].Copy(matchedShape);
+ for (int i = 1; i < 4; i++)
+ {
+ _currentShape[i].RotateClockWise(_currentShape[i - 1]);
+ }
+
+ _shapeX = x;
+ _shapeY = y;
+ _shapeFound = true;
+ break;
+ }
+ }
+
+ if (_shapeFound) { break; }
+ }
+ }
+
+ private void SearchBestAction()
+ {
+ _bestEvaluation = double.MinValue;
+ _bestXOffset = 0;
+ _bestRotation = 0;
+ if (!_shapeFound)
+ {
+ _workGrid0.SampleOther(_grid, TetrisGrid.CellSolid, TetrisGrid.CellSolid);
+ return;
+ }
+
+
+ for (int rotation = 0; rotation < 4; rotation++)
+ {
+ for (int x = 0; x < _grid.Width; x++)
+ {
+ _workGrid0.SampleOther(_grid, TetrisGrid.CellSolid, TetrisGrid.CellSolid);
+ _currentShape[0].PutOnGrid(_workGrid0, _shapeX, _shapeY, TetrisGrid.CellEmpty);
+
+ if (_currentShape[rotation].Drop(_workGrid0, x, _shapeY, TetrisGrid.CellShapeA))
+ {
+ double newEvaluation = _workGrid0.Evaluate();
+ _columnEvaluation[x] = newEvaluation;
+ }
+ else
+ {
+ _columnEvaluation[x] = double.MinValue;
+ }
+ }
+
+ // Search valid X range
+ int minX = _shapeX;
+ while (minX >= 0 && _columnEvaluation[minX] > double.MinValue) { minX--; }
+
+ minX++;
+ int maxX = _shapeX;
+ while (maxX < _grid.Width && _columnEvaluation[maxX] > double.MinValue) { maxX++; }
+
+ maxX--;
+
+ // Apply best value inside valid X range
+ for (int x = minX; x <= maxX; x++)
+ {
+ if (_columnEvaluation[x] > _bestEvaluation)
+ {
+ _bestEvaluation = _columnEvaluation[x];
+ _bestXOffset = x - _shapeX;
+ _bestRotation = rotation;
+ }
+ }
+ }
+ }
+
+ private int _shotCooldownFrames;
+ private int _shotCooldown;
+
+ public string ResponseKeys()
+ {
+ if (_shapeFound == false) { return string.Empty; }
+
+ if (_bestRotation == 0 && _bestXOffset == 0 && _bestEvaluation > double.MinValue)
+ {
+ if (_shotCooldown <= 0)
+ {
+ _shotCooldown = _shotCooldownFrames;
+ return " ";
+ }
+ else
+ {
+ _shotCooldown--;
+ return string.Empty;
+ }
+ }
+
+ _shotCooldown = _shotCooldownFrames;
+
+ if (_bestRotation != 0 && _bestXOffset < 0) { return "{UP}{LEFT}"; }
+
+ if (_bestRotation != 0 && _bestXOffset > 0) { return "{UP}{RIGHT}"; }
+
+ if (_bestRotation != 0) { return "{UP}"; }
+
+ if (_bestXOffset < 0) { return "{LEFT}"; }
+
+ if (_bestXOffset > 0) { return "{RIGHT}"; }
+
+ return string.Empty;
+ }
+ }
+
+ public class TetrisShape
+ {
+ private const int ShapeSize = 4;
+
+ private readonly byte[][] _cells;
+
+ private int _count;
+
+ public TetrisShape(byte[][] cells = null)
+ {
+ _cells = new byte[ShapeSize][];
+ for (int y = 0; y < ShapeSize; y++)
+ {
+ _cells[y] = new byte[ShapeSize];
+ }
+
+ _count = 0;
+ if (cells != null)
+ {
+ for (int j = 0; j < ShapeSize; j++)
+ {
+ if (j >= cells.Length) { break; }
+
+ for (int i = 0; i < ShapeSize; i++)
+ {
+ if (i >= cells[j].Length) { break; }
+
+ _cells[j][i] = cells[j][i];
+ if (_cells[j][i] != 0) { _count++; }
+ }
+ }
+ }
+ }
+
+ public int GetCount()
+ {
+ return _count;
+ }
+
+ private static List _defaultShapes;
+
+ public static List DefaultShapes
+ {
+ get
+ {
+ return _defaultShapes ?? (_defaultShapes = new List
+ {
+ // I
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, 1, 1, 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, },
+ new byte[] { 1, },
+ new byte[] { 1, },
+ new byte[] { 1, },
+ }),
+
+ // J
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, },
+ new byte[] { 1, 1, 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, 1, },
+ new byte[] { 1, },
+ new byte[] { 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, 1, 1, },
+ new byte[] { 0, 0, 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 0, 1, },
+ new byte[] { 0, 1, },
+ new byte[] { 1, 1, },
+ }),
+
+ // L
+ new TetrisShape(new[]
+ {
+ new byte[] { 0, 0, 1, },
+ new byte[] { 1, 1, 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, },
+ new byte[] { 1, },
+ new byte[] { 1, 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, 1, 1, },
+ new byte[] { 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, 1, },
+ new byte[] { 0, 1, },
+ new byte[] { 0, 1, },
+ }),
+
+ // S
+ new TetrisShape(new[]
+ {
+ new byte[] { 0, 1, 1, },
+ new byte[] { 1, 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, },
+ new byte[] { 1, 1, },
+ new byte[] { 0, 1, },
+ }),
+
+ // T
+ new TetrisShape(new[]
+ {
+ new byte[] { 0, 1, },
+ new byte[] { 1, 1, 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, },
+ new byte[] { 1, 1, },
+ new byte[] { 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, 1, 1, },
+ new byte[] { 0, 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 0, 1, },
+ new byte[] { 1, 1, },
+ new byte[] { 0, 1, },
+ }),
+
+ // Z
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, 1, },
+ new byte[] { 0, 1, 1, },
+ }),
+ new TetrisShape(new[]
+ {
+ new byte[] { 0, 1, },
+ new byte[] { 1, 1, },
+ new byte[] { 1, },
+ }),
+
+ // O
+ new TetrisShape(new[]
+ {
+ new byte[] { 1, 1, },
+ new byte[] { 1, 1, },
+ })
+ });
+ }
+ }
+
+ public bool IsValid()
+ {
+ if (_count != 4) { return false; }
+
+ bool matchesAnyDefault = DefaultShapes.Any(CompareShape);
+ return matchesAnyDefault;
+ }
+
+ public void Copy(TetrisShape shape)
+ {
+ for (int j = 0; j < ShapeSize; j++)
+ {
+ for (int i = 0; i < ShapeSize; i++)
+ {
+ _cells[j][i] = shape._cells[j][i];
+ _count = shape._count;
+ }
+ }
+ }
+
+ private bool CompareShape(TetrisShape shape)
+ {
+ for (int j = 0; j < ShapeSize; j++)
+ {
+ for (int i = 0; i < ShapeSize; i++)
+ {
+ if (_cells[j][i] != shape._cells[j][i])
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public void SampleFromGrid(TetrisGrid grid, int x, int y, byte value)
+ {
+ _count = 0;
+ for (int j = 0; j < ShapeSize; j++)
+ {
+ for (int i = 0; i < ShapeSize; i++)
+ {
+ if (grid.Get(x + i, y + j) == value)
+ {
+ _cells[j][i] = 1;
+ _count++;
+ }
+ else
+ {
+ _cells[j][i] = 0;
+ }
+ }
+ }
+ }
+
+ public void PutOnGrid(TetrisGrid grid, int x, int y, byte value)
+ {
+ for (int j = 0; j < ShapeSize; j++)
+ {
+ for (int i = 0; i < ShapeSize; i++)
+ {
+ if (_cells[j][i] == 0) { continue; }
+
+ grid.Set(x + i, y + j, value);
+ }
+ }
+ }
+
+ public bool CheckIntersection(TetrisGrid grid, int x, int y)
+ {
+ for (int j = 0; j < ShapeSize; j++)
+ {
+ for (int i = 0; i < ShapeSize; i++)
+ {
+ if (_cells[j][i] == 0) { continue; }
+
+ if (grid.Get(x + i, y + j) != 0)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public bool Drop(TetrisGrid grid, int x, int y, byte value)
+ {
+ if (CheckIntersection(grid, x, y)) { return false; }
+
+ while (CheckIntersection(grid, x, y + 1) == false)
+ {
+ y++;
+ }
+
+ PutOnGrid(grid, x, y, value);
+ return true;
+ }
+
+ private bool SearchFirstCell(byte value, out int x, out int y)
+ {
+ x = -1;
+ y = -1;
+ for (int j = 0; j < ShapeSize && y == -1; j++)
+ {
+ for (int i = 0; i < ShapeSize && y == -1; i++)
+ {
+ if (_cells[j][i] == value)
+ {
+ y = j;
+ }
+ }
+ }
+
+ if (y == -1) { return false; }
+
+ for (int i = 0; i < ShapeSize && x == -1; i++)
+ {
+ for (int j = 0; j < ShapeSize && x == -1; j++)
+ {
+ if (_cells[j][i] == value)
+ {
+ x = i;
+ }
+ }
+ }
+
+ if (x == -1) { return false; }
+
+ return true;
+ }
+
+ private void Offset(int x, int y)
+ {
+ _count = 0;
+ for (int j = 0; j < ShapeSize; j++)
+ {
+ for (int i = 0; i < ShapeSize; i++)
+ {
+ if ((j + y) < ShapeSize && (i + x) < ShapeSize)
+ {
+ _cells[j][i] = _cells[j + y][i + x];
+ if (_cells[j][i] != 0)
+ {
+ _count++;
+ }
+ }
+ else
+ {
+ _cells[j][i] = 0;
+ }
+ }
+ }
+ }
+
+ public void RotateClockWise(TetrisShape shape)
+ {
+ for (int j = 0; j < ShapeSize; j++)
+ {
+ for (int i = 0; i < ShapeSize; i++)
+ {
+ _cells[i][ShapeSize - (j + 1)] = shape._cells[j][i];
+ }
+ }
+
+ _count = shape._count;
+
+ if (SearchFirstCell(1, out int offsetX, out int offsetY))
+ {
+ Offset(offsetX, offsetY);
+ }
+ }
+
+ public void Print(IOutputHandler output)
+ {
+ for (int y = 0; y < ShapeSize; y++)
+ {
+ StringBuilder sbLine = new StringBuilder();
+ for (int x = 0; x < ShapeSize; x++)
+ {
+ sbLine.Append(_cells[y][x] == 0 ? ".." : "[]");
+ }
+
+ output.AddLine(sbLine.ToString());
+ }
+ }
+
+ public bool MatchOnGrid(TetrisGrid grid, int x, int y)
+ {
+ for (int j = 0; j < ShapeSize; j++)
+ {
+ for (int i = 0; i < ShapeSize; i++)
+ {
+ int currentCell = grid.Get(x + i, y + j);
+ if (_cells[j][i] == 0)
+ {
+ if (currentCell != 0 && currentCell != 0xFF)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (currentCell == 0 || currentCell == 0xFF)
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+
+ public class TetrisGrid
+ {
+ public const byte CellEmpty = 0;
+ public const byte CellSolid = 1;
+ public const byte CellShapeA = 2;
+ public const byte CellShapeB = 3;
+
+ private readonly int _gridWidth;
+ private readonly int _gridHeight;
+
+ public int Width => _gridWidth;
+ public int Height => _gridHeight;
+
+ private readonly byte[][] _grid;
+
+ private readonly int[] _heights;
+
+ public TetrisGrid(int gridWidth, int gridHeight)
+ {
+ _gridWidth = gridWidth;
+ _gridHeight = gridHeight;
+ _grid = new byte[_gridHeight][];
+ for (int y = 0; y < _gridHeight; y++)
+ {
+ _grid[y] = new byte[_gridWidth];
+ }
+
+ _heights = new int[_gridWidth];
+ }
+
+ public byte Get(int x, int y)
+ {
+ if (x >= _gridWidth || x < 0) { return 0xFF; }
+
+ if (y >= _gridHeight || y < 0) { return 0xFF; }
+
+ return _grid[y][x];
+ }
+
+ public void Set(int x, int y, byte value)
+ {
+ if (x >= _gridWidth || x < 0) { return; }
+
+ if (y >= _gridHeight || y < 0) { return; }
+
+ _grid[y][x] = value;
+ }
+
+ public void SampleFromBitmap(Bitmap bmp)
+ {
+ float xStep = bmp.Width / (float)_gridWidth;
+ float yStep = bmp.Height / (float)_gridHeight;
+ float xOff0 = xStep / 2;
+ float xOff1 = xOff0 / 2;
+ float xOff2 = xOff0 + xOff1;
+ float yOff0 = yStep / 2;
+ float yOff1 = yOff0 / 2;
+ float yOff2 = yOff0 + yOff1;
+ for (int y = 0; y < _gridHeight; y++)
+ {
+ for (int x = 0; x < _gridWidth; x++)
+ {
+ Color color = bmp.GetPixel(
+ x: (int)((x * xStep) + xOff0),
+ y: (int)((y * yStep) + yOff0));
+ if (color.R > 128 || color.G > 128 || color.B > 128)
+ {
+ Color color0 = bmp.GetPixel(
+ x: (int)((x * xStep) + xOff1),
+ y: (int)((y * yStep) + yOff1));
+ Color color1 = bmp.GetPixel(
+ x: (int)((x * xStep) + xOff1),
+ y: (int)((y * yStep) + yOff2));
+ Color color2 = bmp.GetPixel(
+ x: (int)((x * xStep) + xOff2),
+ y: (int)((y * yStep) + yOff1));
+ Color color3 = bmp.GetPixel(
+ x: (int)((x * xStep) + xOff2),
+ y: (int)((y * yStep) + yOff2));
+ if (
+ (color0.R > 128 || color0.G > 128 || color0.B > 128) &&
+ (color1.R > 128 || color1.G > 128 || color1.B > 128) &&
+ (color2.R > 128 || color2.G > 128 || color2.B > 128) &&
+ (color3.R > 128 || color3.G > 128 || color3.B > 128) &&
+ true)
+ {
+ _grid[y][x] = 1;
+ }
+ else
+ {
+ _grid[y][x] = 0;
+ }
+ }
+ else
+ {
+ _grid[y][x] = 0;
+ }
+ }
+ }
+ }
+
+ public void RemoveGround()
+ {
+ for (int i = 0; i < _gridWidth; i++)
+ {
+ if (_grid[_gridHeight - 1][i] == 1)
+ {
+ FloodFill(i, _gridHeight - 1, CellSolid, CellEmpty);
+ }
+ }
+ }
+
+ private void FloodFill(int x, int y, byte expectedValue, byte fillValue)
+ {
+ if (x >= _gridWidth || x < 0) { return; }
+
+ if (y >= _gridHeight || y < 0) { return; }
+
+ if (_grid[y][x] != expectedValue) { return; }
+
+ _grid[y][x] = fillValue;
+ FloodFill(x - 1, y - 1, expectedValue, fillValue);
+ FloodFill(x - 1, y + 0, expectedValue, fillValue);
+ FloodFill(x - 1, y + 1, expectedValue, fillValue);
+ FloodFill(x + 0, y - 1, expectedValue, fillValue);
+ FloodFill(x + 0, y + 1, expectedValue, fillValue);
+ FloodFill(x + 1, y - 1, expectedValue, fillValue);
+ FloodFill(x + 1, y + 0, expectedValue, fillValue);
+ FloodFill(x + 1, y + 1, expectedValue, fillValue);
+ }
+
+ public void SampleOther(TetrisGrid grid, byte value, byte setValue)
+ {
+ for (int y = 0; y < _gridHeight; y++)
+ {
+ for (int x = 0; x < _gridWidth; x++)
+ {
+ if (grid._grid[y][x] == value)
+ {
+ _grid[y][x] = setValue;
+ }
+ else
+ {
+ _grid[y][x] = 0;
+ }
+ }
+ }
+ }
+
+ public bool SearchFirstCell(byte value, out int x, out int y)
+ {
+ x = -1;
+ y = -1;
+ for (int j = 0; j < _gridHeight && y == -1; j++)
+ {
+ for (int i = 0; i < _gridWidth && y == -1; i++)
+ {
+ if (_grid[j][i] == value)
+ {
+ y = j;
+ }
+ }
+ }
+
+ if (y == -1) { return false; }
+
+ for (int i = 0; i < _gridWidth && x == -1; i++)
+ {
+ for (int j = 0; j < _gridHeight && x == -1; j++)
+ {
+ if (_grid[j][i] == value)
+ {
+ x = i;
+ }
+ }
+ }
+
+ if (x == -1) { return false; }
+
+ return true;
+ }
+
+ private bool IsCompleteLine(int y)
+ {
+ bool complete = true;
+ for (int x = 0; x < _gridWidth; x++)
+ {
+ if (_grid[y][x] == 0)
+ {
+ complete = false;
+ break;
+ }
+ }
+
+ return complete;
+ }
+
+ public double Evaluate(
+ double aggregateHeightWeight = -0.510066,
+ double completeLinesWeight = 0.760666,
+ double holesWeight = -0.35663,
+ double bumpinessWeight = -0.184483)
+ {
+ // Calculate aggregate height
+ for (int i = 0; i < _gridWidth; i++)
+ {
+ int j = 0;
+ while (j < _gridHeight && _grid[j][i] == CellEmpty) { j++; }
+
+ _heights[i] = _gridHeight - j;
+ }
+
+ double aggregateHeight = _heights.Sum();
+
+ // Calculate complete lines
+ int completeLines = 0;
+ for (int y = 0; y < _gridHeight; y++)
+ {
+ if (IsCompleteLine(y)) { completeLines++; }
+ }
+
+ // Calculate holes
+ int holes = 0;
+ for (int x = 0; x < _gridWidth; x++)
+ {
+ bool block = false;
+ for (int y = 1; y < _gridHeight; y++)
+ {
+ if (_grid[y][x] != CellEmpty && IsCompleteLine(y) == false)
+ {
+ block = true;
+ }
+ else if (_grid[y][x] == CellEmpty && block)
+ {
+ holes++;
+ }
+ }
+ }
+
+ // Calculate bumpiness
+ int bumpiness = 0;
+ for (int i = 1; i < _gridWidth; i++)
+ {
+ bumpiness += Math.Abs(_heights[i] - _heights[i - 1]);
+ }
+
+ // Evaluate formula
+ double evaluationValue =
+ aggregateHeightWeight * aggregateHeight +
+ completeLinesWeight * completeLines +
+ holesWeight * holes +
+ bumpinessWeight * bumpiness +
+ 0;
+ return evaluationValue;
+ }
+
+ public void Draw(Bitmap bmp, float dotWith = 0.5f)
+ {
+ float xStep = bmp.Width / (float)_gridWidth;
+ float yStep = bmp.Height / (float)_gridHeight;
+ float halfXStep = xStep * dotWith;
+ float halfYStep = yStep * dotWith;
+ float offX = (xStep - halfXStep) / 2;
+ float offY = (yStep - halfYStep) / 2;
+
+ using (Pen borderPen = new Pen(Color.DarkGray))
+ using (Graphics g = Graphics.FromImage(bmp))
+ {
+ for (int y = 0; y < _gridHeight; y++)
+ {
+ for (int x = 0; x < _gridWidth; x++)
+ {
+ Brush br = null;
+ if (_grid[y][x] == CellEmpty)
+ {
+ br = Brushes.Black;
+ }
+ else if (_grid[y][x] == CellSolid)
+ {
+ br = Brushes.Blue;
+ }
+ else if (_grid[y][x] == CellShapeA)
+ {
+ br = Brushes.Red;
+ }
+ else if (_grid[y][x] == CellShapeB)
+ {
+ br = Brushes.Green;
+ }
+
+ if (br == null) { continue; }
+
+ g.DrawRectangle(borderPen, (xStep * x) + offX - 1, (yStep * y) + offY - 1, halfXStep + 2,
+ halfYStep + 2);
+ g.FillRectangle(br, (xStep * x) + offX, (yStep * y) + offY, halfXStep, halfYStep);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/VAR.ScreenAutomation/Code/AutomationBotFactory.cs b/VAR.ScreenAutomation/Code/AutomationBotFactory.cs
new file mode 100644
index 0000000..c5ee5cc
--- /dev/null
+++ b/VAR.ScreenAutomation/Code/AutomationBotFactory.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using VAR.ScreenAutomation.Bots;
+using VAR.ScreenAutomation.Interfaces;
+
+namespace VAR.ScreenAutomation.Code
+{
+ public static class AutomationBotFactory
+ {
+ private static Dictionary _dictAutomationBots;
+
+ private static Dictionary GetDict()
+ {
+ if (_dictAutomationBots != null)
+ {
+ return _dictAutomationBots;
+ }
+
+ Type iAutomationBot = typeof(IAutomationBot);
+ IEnumerable automationBotTypes = AppDomain.CurrentDomain
+ .GetAssemblies()
+ .SelectMany(x => x.GetTypes())
+ .Where(x =>
+ x.IsAbstract == false &&
+ x.IsInterface == false &&
+ iAutomationBot.IsAssignableFrom(x) &&
+ true);
+ _dictAutomationBots = automationBotTypes.ToDictionary(t =>
+ {
+ IAutomationBot automationBot =
+ System.Runtime.Serialization.FormatterServices.GetUninitializedObject(t) as IAutomationBot;
+ return automationBot?.Name ?? t.Name;
+ });
+
+ return _dictAutomationBots;
+ }
+
+ public static object[] GetAllAutomationBots()
+ {
+ Dictionary dict = GetDict();
+ string[] allAutomationBots = dict.Select(p => p.Key).ToArray();
+ return allAutomationBots.ToArray