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(); + } + + public static IAutomationBot CreateFromName(string name) + { + Dictionary dict = GetDict(); + if (string.IsNullOrEmpty(name)) + { + return new DummyBot(); + } + + if (dict.ContainsKey(name) == false) + { + throw new NotImplementedException($"Can't create IAutomationBot with this name: {name}"); + } + + Type proxyCmdExecutorType = dict[name]; + + IAutomationBot automationBot = Activator.CreateInstance(proxyCmdExecutorType) as IAutomationBot; + + return automationBot; + } + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Code/FileBackedConfiguration.cs b/VAR.ScreenAutomation/Code/FileBackedConfiguration.cs new file mode 100644 index 0000000..31d092b --- /dev/null +++ b/VAR.ScreenAutomation/Code/FileBackedConfiguration.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using VAR.ScreenAutomation.Interfaces; + +namespace VAR.ScreenAutomation.Code +{ + public class FileBackedConfiguration : IConfiguration + { + private readonly MemoryBackedConfiguration _config = new MemoryBackedConfiguration(); + + private readonly string _name; + + public FileBackedConfiguration(string name = null) + { + _name = name; + } + + private static string GetConfigFileName(string name = null) + { + string location = System.Reflection.Assembly.GetEntryAssembly()?.Location; + string path = Path.GetDirectoryName(location); + string filenameWithoutExtension = Path.GetFileNameWithoutExtension(location); + string configFile = string.IsNullOrEmpty(name) + ? $"{path}/{filenameWithoutExtension}.cfg" + : $"{path}/{filenameWithoutExtension}_{name}.cfg"; + return configFile; + } + + private static string[] GetConfigurationLines(string name = null) + { + string configFile = GetConfigFileName(name); + string[] config = File.Exists(configFile) == false + ? Array.Empty() + : File.ReadAllLines(configFile); + + return config; + } + + public void Load(IConfiguration other = null) + { + _config.Clear(); + if (other != null) + { + foreach (string key in other.GetKeys()) + { + _config.Set(key, other.Get(key, null)); + } + } + + string[] configLines = GetConfigurationLines(_name); + foreach (string configLine in configLines) + { + int idxSplit = configLine.IndexOf('|'); + if (idxSplit < 0) { continue; } + + string configName = configLine.Substring(0, idxSplit); + string configData = configLine.Substring(idxSplit + 1); + + _config.Set(configName, configData); + } + } + + public void Save() + { + StringBuilder sbConfig = new StringBuilder(); + foreach (string key in _config.GetKeys()) + { + sbConfig.AppendFormat("{0}|{1}\n", key, _config.Get(key, string.Empty)); + } + + string configFileName = GetConfigFileName(_name); + File.WriteAllText(configFileName, sbConfig.ToString()); + } + + public IEnumerable GetKeys() + { + return _config.GetKeys(); + } + + public void Clear() + { + _config.Clear(); + } + + public string Get(string key, string defaultValue) + { + return _config.Get(key, defaultValue); + } + + public int Get(string key, int defaultValue) + { + return _config.Get(key, defaultValue); + } + + public bool Get(string key, bool defaultValue) + { + return _config.Get(key, defaultValue); + } + + public void Set(string key, string value) + { + _config.Set(key, value); + } + + public void Set(string key, int value) + { + _config.Set(key, value); + } + + public void Set(string key, bool value) + { + _config.Set(key, value); + } + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Code/MemoryBackedConfiguration.cs b/VAR.ScreenAutomation/Code/MemoryBackedConfiguration.cs new file mode 100644 index 0000000..a7ab978 --- /dev/null +++ b/VAR.ScreenAutomation/Code/MemoryBackedConfiguration.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using VAR.ScreenAutomation.Interfaces; + +namespace VAR.ScreenAutomation.Code +{ + public class MemoryBackedConfiguration : IConfiguration + { + private readonly Dictionary _configItems = new Dictionary(); + + public IEnumerable GetKeys() + { + return _configItems == null + ? new List() + : _configItems.Select(p => p.Key); + } + + public void Clear() + { + _configItems.Clear(); + } + + public string Get(string key, string defaultValue) + { + if (_configItems == null) { return defaultValue; } + + return _configItems.ContainsKey(key) ? _configItems[key] : defaultValue; + } + + public int Get(string key, int defaultValue) + { + if (_configItems == null) { return defaultValue; } + + if (_configItems.ContainsKey(key)) + { + if (int.TryParse(_configItems[key], out int value)) + { + return value; + } + + return defaultValue; + } + + return defaultValue; + } + + public bool Get(string key, bool defaultValue) + { + if (_configItems == null) { return defaultValue; } + + if (_configItems.ContainsKey(key)) + { + string value = _configItems[key]; + return (value == "true"); + } + + return defaultValue; + } + + public void Set(string key, string value) + { + if (_configItems == null) { return; } + + if (_configItems.ContainsKey(key)) + { + _configItems[key] = value; + } + else + { + _configItems.Add(key, value); + } + } + + public void Set(string key, int value) + { + if (_configItems == null) { return; } + + if (_configItems.ContainsKey(key)) + { + _configItems[key] = Convert.ToString(value); + } + else + { + _configItems.Add(key, Convert.ToString(value)); + } + } + + public void Set(string key, bool value) + { + if (_configItems == null) { return; } + + if (_configItems.ContainsKey(key)) + { + _configItems[key] = value ? "true" : "false"; + } + else + { + _configItems.Add(key, value ? "true" : "false"); + } + } + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Code/Mouse.cs b/VAR.ScreenAutomation/Code/Mouse.cs new file mode 100644 index 0000000..0cbe672 --- /dev/null +++ b/VAR.ScreenAutomation/Code/Mouse.cs @@ -0,0 +1,169 @@ +using System; +using System.Runtime.InteropServices; + +// ReSharper disable InconsistentNaming + +namespace VAR.ScreenAutomation.Code +{ + public static class Mouse + { + public enum MouseButtons + { + Left, + Middle, + Right + } + + public static void SetButton(MouseButtons button, bool down) + { + INPUT input = new INPUT + { + Type = INPUT_MOUSE + }; + input.Data.Mouse.X = 0; + input.Data.Mouse.Y = 0; + if (button == MouseButtons.Left) + { + input.Data.Mouse.Flags = down ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; + } + + if (button == MouseButtons.Middle) + { + input.Data.Mouse.Flags = down ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; + } + + if (button == MouseButtons.Right) + { + input.Data.Mouse.Flags = down ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; + } + + INPUT[] inputs = new INPUT[] { input }; + if (SendInput(1, inputs, Marshal.SizeOf(typeof(INPUT))) == 0) + throw new Exception(); + } + + public static void Click(MouseButtons button) + { + SetButton(button, true); + System.Threading.Thread.Sleep(500); + SetButton(button, false); + } + + public static void GetPosition(out UInt32 x, out UInt32 y) + { + GetCursorPos(out POINT lpPoint); + x = lpPoint.X; + y = lpPoint.Y; + } + + public static void SetPosition(UInt32 x, UInt32 y) + { + SetCursorPos(x, y); + } + + [StructLayout(LayoutKind.Sequential)] + public struct INPUT + { + public uint Type; + public MOUSEKEYBDHARDWAREINPUT Data; + } + + public const int INPUT_MOUSE = 0; + public const int INPUT_KEYBOARD = 1; + public const int INPUT_HARDWARE = 2; + + /// + /// http://social.msdn.microsoft.com/Forums/en/csharplanguage/thread/f0e82d6e-4999-4d22-b3d3-32b25f61fb2a + /// + [StructLayout(LayoutKind.Explicit)] + public struct MOUSEKEYBDHARDWAREINPUT + { + [FieldOffset(0)] public HARDWAREINPUT Hardware; + + [FieldOffset(0)] public KEYBDINPUT Keyboard; + + [FieldOffset(0)] public MOUSEINPUT Mouse; + } + + /// + /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx + /// + [StructLayout(LayoutKind.Sequential)] + public struct HARDWAREINPUT + { + public uint Msg; + public ushort ParamL; + public ushort ParamH; + } + + /// + /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx + /// + [StructLayout(LayoutKind.Sequential)] + public struct KEYBDINPUT + { + public ushort Vk; + public ushort Scan; + public uint Flags; + public uint Time; + public IntPtr ExtraInfo; + } + + /// + /// http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/2abc6be8-c593-4686-93d2-89785232dacd + /// https://msdn.microsoft.com/es-es/library/windows/desktop/ms646273%28v=vs.85%29.aspx + /// + [StructLayout(LayoutKind.Sequential)] + public struct MOUSEINPUT + { + public int X; + public int Y; + public uint MouseData; + public uint Flags; + public uint Time; + public IntPtr ExtraInfo; + } + + public const int MOUSEEVENTD_XBUTTON1 = 0x0001; + public const int MOUSEEVENTD_XBUTTON2 = 0x0002; + + public const uint MOUSEEVENTF_ABSOLUTE = 0x8000; + public const uint MOUSEEVENTF_HWHEEL = 0x01000; + public const uint MOUSEEVENTF_MOVE = 0x0001; + public const uint MOUSEEVENTF_MOVE_NOCOALESCE = 0x2000; + public const uint MOUSEEVENTF_LEFTDOWN = 0x0002; + public const uint MOUSEEVENTF_LEFTUP = 0x0004; + public const uint MOUSEEVENTF_RIGHTDOWN = 0x0008; + public const uint MOUSEEVENTF_RIGHTUP = 0x0010; + public const uint MOUSEEVENTF_MIDDLEDOWN = 0x0020; + public const uint MOUSEEVENTF_MIDDLEUP = 0x0040; + public const uint MOUSEEVENTF_VIRTUALDESK = 0x4000; + public const uint MOUSEEVENTF_WHEEL = 0x0800; + public const uint MOUSEEVENTF_XDOWN = 0x0080; + public const uint MOUSEEVENTF_XUP = 0x0100; + + [DllImport("User32.dll")] + public static extern int SendInput(int nInputs, INPUT[] pInputs, int cbSize); + + + /// + /// Struct representing a point. + /// + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public UInt32 X; + public UInt32 Y; + } + + /// + /// Retrieves the cursor's position, in screen coordinates. + /// + /// See MSDN documentation for further information. + [DllImport("user32.dll")] + public static extern bool GetCursorPos(out POINT lpPoint); + + [DllImport("User32.dll")] + public static extern Boolean SetCursorPos(UInt32 x, UInt32 y); + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Code/Screenshoter.cs b/VAR.ScreenAutomation/Code/Screenshoter.cs new file mode 100644 index 0000000..8003614 --- /dev/null +++ b/VAR.ScreenAutomation/Code/Screenshoter.cs @@ -0,0 +1,51 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace VAR.ScreenAutomation.Code +{ + public static class Screenshoter + { + public static Bitmap CaptureControl(Control ctrl, Bitmap bmp = null) + { + if (ctrl == null) { return bmp; } + + Point picCapturerOrigin = ctrl.PointToScreen(new Point(0, 0)); + bmp = CaptureScreen(bmp, picCapturerOrigin.X, picCapturerOrigin.Y, ctrl.Width, ctrl.Height); + return bmp; + } + + private static Bitmap CaptureScreen(Bitmap bmp = null, int? left = null, int? top = null, int? width = null, + int? height = null) + { + if (width <= 0 || height <= 0) { return bmp; } + + // Determine the size of the "virtual screen", which includes all monitors. + left = left ?? SystemInformation.VirtualScreen.Left; + top = top ?? SystemInformation.VirtualScreen.Top; + width = width ?? SystemInformation.VirtualScreen.Width; + height = height ?? SystemInformation.VirtualScreen.Height; + + // Create a bitmap of the appropriate size to receive the screenshot. + if (bmp == null || bmp.Width != width || bmp.Height != height) + { + bmp = new Bitmap((int)width, (int)height); + } + + try + { + // Draw the screenshot into our bitmap. + using (Graphics g = Graphics.FromImage(bmp)) + { + g.CopyFromScreen((int)left, (int)top, 0, 0, bmp.Size); + } + } + catch (Exception) + { + /* Nom Nom Nom */ + } + + return bmp; + } + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Code/WindowHandling.cs b/VAR.ScreenAutomation/Code/WindowHandling.cs new file mode 100644 index 0000000..fe9f5a4 --- /dev/null +++ b/VAR.ScreenAutomation/Code/WindowHandling.cs @@ -0,0 +1,50 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +// ReSharper disable InconsistentNaming + +namespace VAR.ScreenAutomation.Code +{ + public static class WindowHandling + { + private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); + private const UInt32 SWP_NOSIZE = 0x0001; + private const UInt32 SWP_NOMOVE = 0x0002; + private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE; + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, + uint uFlags); + + public static void WindowSetTopLevel(Form form, bool top = true) + { + SetWindowPos(form.Handle, top + ? HWND_TOPMOST + : HWND_NOTOPMOST, + 0, 0, 0, 0, TOPMOST_FLAGS); + } + + public static bool ApplicationIsActivated() + { + var activatedHandle = GetForegroundWindow(); + if (activatedHandle == IntPtr.Zero) + { + return false; + } + + var procId = Process.GetCurrentProcess().Id; + GetWindowThreadProcessId(activatedHandle, out int activeProcId); + return activeProcId == procId; + } + + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Controls/CtrImageViewer.cs b/VAR.ScreenAutomation/Controls/CtrImageViewer.cs new file mode 100644 index 0000000..082787e --- /dev/null +++ b/VAR.ScreenAutomation/Controls/CtrImageViewer.cs @@ -0,0 +1,121 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace VAR.ScreenAutomation.Controls +{ + public class CtrImageViewer : PictureBox + { + #region Declarations + + private Image _imageShow; + + #endregion + + #region Properties + + public Image ImageShow + { + // ReSharper disable once InconsistentlySynchronizedField + get => _imageShow; + set + { + lock (this) + { + _imageShow = value; + Invalidate(); + } + } + } + + #endregion + + #region Control life cycle + + public CtrImageViewer() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + BackColor = Color.Black; + } + + protected override void OnPaint(PaintEventArgs pe) + { + base.OnPaint(pe); + Redraw(pe.Graphics); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + //Redraw(null); + this.Invalidate(); + } + + #endregion + + #region Private methods + + private void Redraw(Graphics graph) + { + if (_imageShow == null) + { + return; + } + + lock (_imageShow) + { + if (graph == null) + { + graph = this.CreateGraphics(); + } + + // Calcular dimensiones a dibujar y centrar + int imgDrawWidth; + int imgDrawHeight; + float imgDrawX = 0; + float imgDrawY = 0; + float relation = _imageShow.Width / (float)_imageShow.Height; + if (relation > 0) + { + // Imagen mas ancha que alta + imgDrawHeight = (int)(Width / relation); + if (imgDrawHeight > Height) + { + imgDrawHeight = Height; + imgDrawWidth = (int)(Height * relation); + imgDrawX = ((Width - imgDrawWidth) / 2.0f); + } + else + { + imgDrawWidth = Width; + imgDrawY = ((Height - imgDrawHeight) / 2.0f); + } + } + else + { + // Imagen mas alta que ancha + imgDrawWidth = (int)(Width * relation); + if (imgDrawWidth > Width) + { + imgDrawWidth = Width; + imgDrawHeight = (int)(Height / relation); + imgDrawY = ((Height - imgDrawHeight) / 2.0f); + } + else + { + imgDrawHeight = Height; + imgDrawX = ((Width - imgDrawWidth) / 2.0f); + } + } + + graph.DrawImage(_imageShow, imgDrawX, imgDrawY, imgDrawWidth, imgDrawHeight); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Controls/CtrOutput.cs b/VAR.ScreenAutomation/Controls/CtrOutput.cs new file mode 100644 index 0000000..5889580 --- /dev/null +++ b/VAR.ScreenAutomation/Controls/CtrOutput.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows.Forms; +using VAR.ScreenAutomation.Interfaces; + +// ReSharper disable InconsistentNaming + +namespace VAR.ScreenAutomation.Controls +{ + public class CtrOutput : Control, IOutputHandler + { + private ListBox _listBox; + + private Timer _timer; + + private class OutputItem + { + public string Text { get; set; } + public object Data { get; set; } + + public override string ToString() + { + return Text; + } + } + + public new event EventHandler DoubleClick; + + public CtrOutput() + { + InitializeControls(); + } + + private void InitializeControls() + { + _listBox = new ListBox + { + Dock = DockStyle.Fill, + FormattingEnabled = true, + Font = new Font("Consolas", 9), + BackColor = Color.Black, + ForeColor = Color.Gray, + SelectionMode = SelectionMode.MultiExtended, + }; + _listBox.MouseDoubleClick += ListBox_MouseDoubleClick; + _listBox.KeyDown += ListBox_KeyDown; + Controls.Add(_listBox); + + _timer = new Timer + { + Interval = 100, + Enabled = true + }; + _timer.Tick += Timer_Tick; + + Disposed += CtrOutput_Disposed; + } + + private void CtrOutput_Disposed(object sender, EventArgs e) + { + _timer.Stop(); + _timer.Enabled = false; + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + if ((keyData & Keys.Control) == Keys.Control && (keyData & Keys.C) == Keys.C) + { + CopyToClipboard(); + return true; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + private void ListBox_KeyDown(object sender, KeyEventArgs e) + { + if (e.Control && e.KeyCode == Keys.C) + { + CopyToClipboard(); + } + } + + private void CopyToClipboard() + { + StringBuilder sbText = new StringBuilder(); + foreach (OutputItem item in _listBox.SelectedItems) + { + sbText.AppendLine(item.Text); + } + + if (sbText.Length > 0) + { + Clipboard.SetText(sbText.ToString()); + } + } + + private void ListBox_MouseDoubleClick(object sender, MouseEventArgs e) + { + DoubleClick?.Invoke(sender, e); + } + + private void Timer_Tick(object sender, EventArgs e) + { + if (_updated) + { + UpdatePosition(); + } + } + + private bool _updated; + private readonly List _pendingOutput = new List(); + + private void UpdatePosition() + { + lock (_pendingOutput) + { + EnableRepaint(new HandleRef(_listBox, _listBox.Handle), false); + _listBox.SuspendLayout(); + foreach (OutputItem item in _pendingOutput) + { + _listBox.Items.Add(item); + } + + _pendingOutput.Clear(); + _listBox.ResumeLayout(); + + int visibleItems = _listBox.ClientSize.Height / _listBox.ItemHeight; + _listBox.TopIndex = Math.Max(_listBox.Items.Count - visibleItems + 1, 0); + _updated = false; + EnableRepaint(new HandleRef(_listBox, _listBox.Handle), true); + _listBox.Invalidate(); + } + } + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + private static extern IntPtr SendMessage(HandleRef hWnd, Int32 msg, IntPtr wParam, IntPtr lParam); + + private static void EnableRepaint(HandleRef handle, bool enable) + { + const int WM_SETREDRAW = 0x000B; + SendMessage(handle, WM_SETREDRAW, new IntPtr(enable ? 1 : 0), IntPtr.Zero); + } + + public void Clean() + { + if (_listBox.InvokeRequired) + { + _listBox.Invoke((MethodInvoker)(() => + { + _listBox.Items.Clear(); + _updated = true; + })); + } + else + { + _listBox.Items.Clear(); + _updated = true; + } + } + + public void AddLine(string line, object data = null) + { + lock (_pendingOutput) + { + _pendingOutput.Add(new OutputItem { Text = line, Data = data, }); + _updated = true; + } + } + + public string GetCurrentText() + { + if (_listBox.SelectedItems.Count == 0) { return null; } + + OutputItem item = (OutputItem)_listBox.SelectedItems[0]; + return item?.Text; + } + + public object GetCurrentData() + { + if (_listBox.SelectedItems.Count == 0) { return null; } + + OutputItem item = (OutputItem)_listBox.SelectedItems[0]; + return item?.Data; + } + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/FrmAutomationBotParams.Designer.cs b/VAR.ScreenAutomation/FrmAutomationBotParams.Designer.cs new file mode 100644 index 0000000..ca46f89 --- /dev/null +++ b/VAR.ScreenAutomation/FrmAutomationBotParams.Designer.cs @@ -0,0 +1,48 @@ +namespace VAR.ScreenAutomation +{ + partial class FrmAutomationBotParams + { + /// + /// 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() + { + this.SuspendLayout(); + // + // FrmAutomationBotParams + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(297, 473); + this.Name = "FrmAutomationBotParams"; + this.Text = "AutomationBotParams"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FrmAutomationBotParams_FormClosing); + this.Load += new System.EventHandler(this.FrmAutomationBotParams_Load); + this.ResumeLayout(false); + + } + + #endregion + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/FrmAutomationBotParams.cs b/VAR.ScreenAutomation/FrmAutomationBotParams.cs new file mode 100644 index 0000000..9c3d16c --- /dev/null +++ b/VAR.ScreenAutomation/FrmAutomationBotParams.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows.Forms; +using VAR.ScreenAutomation.Code; + +namespace VAR.ScreenAutomation +{ + public partial class FrmAutomationBotParams : Form + { + private readonly FileBackedConfiguration _config; + + private BindingList _pairs; + + private DataGridView _dgvParams; + + public FrmAutomationBotParams(FileBackedConfiguration config) + { + _config = config; + + InitializeComponent(); + } + + private void FrmAutomationBotParams_Load(object sender, EventArgs e) + { + _pairs = new BindingList(); + foreach (string key in _config.GetKeys()) + { + _pairs.Add(new Pair { Key = key, Value = _config.Get(key, string.Empty), }); + } + + _pairs.AddingNew += (s, a) => + { + a.NewObject = new Pair { Parent = _pairs }; + }; + _dgvParams = new DataGridView + { + Dock = DockStyle.Fill, + DataSource = _pairs + }; + + Controls.Add(_dgvParams); + } + + private void FrmAutomationBotParams_FormClosing(object sender, FormClosingEventArgs e) + { + foreach (Pair pair in _pairs) + { + if (string.IsNullOrEmpty(pair.Key)) { continue; } + + _config.Set(pair.Key, pair.Value); + } + + _config.Save(); + } + + internal class Pair : IDataErrorInfo + { + internal IList Parent { get; set; } + public string Key { get; set; } + public string Value { get; set; } + + string IDataErrorInfo.Error => string.Empty; + + string IDataErrorInfo.this[string columnName] + { + get + { + if (columnName == "Key" && Parent != null && Parent.Any( + x => x.Key == this.Key && !ReferenceEquals(x, this))) + { + return "duplicate key"; + } + + return string.Empty; + } + } + } + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/FrmAutomationBotParams.resx b/VAR.ScreenAutomation/FrmAutomationBotParams.resx new file mode 100644 index 0000000..d5a483d --- /dev/null +++ b/VAR.ScreenAutomation/FrmAutomationBotParams.resx @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + \ No newline at end of file diff --git a/VAR.ScreenAutomation/FrmScreenAutomation.Designer.cs b/VAR.ScreenAutomation/FrmScreenAutomation.Designer.cs new file mode 100644 index 0000000..c033591 --- /dev/null +++ b/VAR.ScreenAutomation/FrmScreenAutomation.Designer.cs @@ -0,0 +1,266 @@ +namespace VAR.ScreenAutomation +{ + partial class FrmScreenAutomation + { + /// + /// 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() + { + this.picCapturer = new System.Windows.Forms.PictureBox(); + this.splitMain = new System.Windows.Forms.SplitContainer(); + this.splitOutput = new System.Windows.Forms.SplitContainer(); + this.btnAutomationBotConfig = new System.Windows.Forms.Button(); + this.numFPS = new System.Windows.Forms.NumericUpDown(); + this.ddlAutomationBot = new System.Windows.Forms.ComboBox(); + this.btnStartEnd = new System.Windows.Forms.Button(); + this.chkKeepToplevel = new System.Windows.Forms.CheckBox(); + this.picPreview = new VAR.ScreenAutomation.Controls.CtrImageViewer(); + this.ctrOutput = new VAR.ScreenAutomation.Controls.CtrOutput(); + this.chkClick = new System.Windows.Forms.CheckBox(); + ((System.ComponentModel.ISupportInitialize)(this.picCapturer)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.splitMain)).BeginInit(); + this.splitMain.Panel1.SuspendLayout(); + this.splitMain.Panel2.SuspendLayout(); + this.splitMain.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitOutput)).BeginInit(); + this.splitOutput.Panel1.SuspendLayout(); + this.splitOutput.Panel2.SuspendLayout(); + this.splitOutput.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numFPS)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.picPreview)).BeginInit(); + this.SuspendLayout(); + // + // picCapturer + // + this.picCapturer.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.picCapturer.Location = new System.Drawing.Point(4, 4); + this.picCapturer.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.picCapturer.Name = "picCapturer"; + this.picCapturer.Padding = new System.Windows.Forms.Padding(11, 10, 11, 10); + this.picCapturer.Size = new System.Drawing.Size(505, 799); + this.picCapturer.TabIndex = 0; + this.picCapturer.TabStop = false; + // + // splitMain + // + this.splitMain.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitMain.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.splitMain.Location = new System.Drawing.Point(0, 0); + this.splitMain.Margin = new System.Windows.Forms.Padding(11, 10, 11, 10); + this.splitMain.Name = "splitMain"; + // + // splitMain.Panel1 + // + this.splitMain.Panel1.Controls.Add(this.splitOutput); + // + // splitMain.Panel2 + // + this.splitMain.Panel2.Controls.Add(this.picCapturer); + this.splitMain.Size = new System.Drawing.Size(755, 816); + this.splitMain.SplitterDistance = 232; + this.splitMain.TabIndex = 3; + // + // splitOutput + // + this.splitOutput.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitOutput.Location = new System.Drawing.Point(0, 0); + this.splitOutput.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.splitOutput.Name = "splitOutput"; + this.splitOutput.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitOutput.Panel1 + // + this.splitOutput.Panel1.Controls.Add(this.picPreview); + // + // splitOutput.Panel2 + // + this.splitOutput.Panel2.Controls.Add(this.chkClick); + this.splitOutput.Panel2.Controls.Add(this.chkKeepToplevel); + this.splitOutput.Panel2.Controls.Add(this.btnAutomationBotConfig); + this.splitOutput.Panel2.Controls.Add(this.numFPS); + this.splitOutput.Panel2.Controls.Add(this.ddlAutomationBot); + this.splitOutput.Panel2.Controls.Add(this.btnStartEnd); + this.splitOutput.Panel2.Controls.Add(this.ctrOutput); + this.splitOutput.Size = new System.Drawing.Size(232, 816); + this.splitOutput.SplitterDistance = 281; + this.splitOutput.TabIndex = 4; + // + // btnAutomationBotConfig + // + this.btnAutomationBotConfig.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnAutomationBotConfig.Location = new System.Drawing.Point(172, 4); + this.btnAutomationBotConfig.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.btnAutomationBotConfig.Name = "btnAutomationBotConfig"; + this.btnAutomationBotConfig.Size = new System.Drawing.Size(60, 28); + this.btnAutomationBotConfig.TabIndex = 6; + this.btnAutomationBotConfig.Text = "Cfg."; + this.btnAutomationBotConfig.UseVisualStyleBackColor = true; + this.btnAutomationBotConfig.Click += new System.EventHandler(this.BtnAutomationBotConfig_Click); + // + // numFPS + // + this.numFPS.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.numFPS.Location = new System.Drawing.Point(171, 50); + this.numFPS.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.numFPS.Maximum = new decimal(new int[] { + 60, + 0, + 0, + 0}); + this.numFPS.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numFPS.Name = "numFPS"; + this.numFPS.Size = new System.Drawing.Size(59, 22); + this.numFPS.TabIndex = 5; + this.numFPS.Value = new decimal(new int[] { + 20, + 0, + 0, + 0}); + // + // ddlAutomationBot + // + this.ddlAutomationBot.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.ddlAutomationBot.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.ddlAutomationBot.FormattingEnabled = true; + this.ddlAutomationBot.Location = new System.Drawing.Point(13, 4); + this.ddlAutomationBot.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.ddlAutomationBot.Name = "ddlAutomationBot"; + this.ddlAutomationBot.Size = new System.Drawing.Size(150, 24); + this.ddlAutomationBot.TabIndex = 4; + this.ddlAutomationBot.SelectedIndexChanged += new System.EventHandler(this.DdlAutomationBot_SelectedIndexChanged); + // + // btnStartEnd + // + this.btnStartEnd.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.btnStartEnd.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.btnStartEnd.Location = new System.Drawing.Point(15, 34); + this.btnStartEnd.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.btnStartEnd.Name = "btnStartEnd"; + this.btnStartEnd.Size = new System.Drawing.Size(105, 39); + this.btnStartEnd.TabIndex = 3; + this.btnStartEnd.Text = "Start"; + this.btnStartEnd.UseVisualStyleBackColor = true; + this.btnStartEnd.Click += new System.EventHandler(this.BtnStartEnd_Click); + // + // chkKeepToplevel + // + this.chkKeepToplevel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.chkKeepToplevel.AutoSize = true; + this.chkKeepToplevel.Location = new System.Drawing.Point(15, 498); + this.chkKeepToplevel.Name = "chkKeepToplevel"; + this.chkKeepToplevel.Size = new System.Drawing.Size(117, 21); + this.chkKeepToplevel.TabIndex = 7; + this.chkKeepToplevel.Text = "KeepToplevel"; + this.chkKeepToplevel.UseVisualStyleBackColor = true; + this.chkKeepToplevel.CheckedChanged += new System.EventHandler(this.chkKeepToplevel_CheckedChanged); + // + // picPreview + // + this.picPreview.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.picPreview.BackColor = System.Drawing.Color.Black; + this.picPreview.ImageShow = null; + this.picPreview.Location = new System.Drawing.Point(12, 12); + this.picPreview.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.picPreview.Name = "picPreview"; + this.picPreview.Size = new System.Drawing.Size(218, 266); + this.picPreview.TabIndex = 1; + this.picPreview.TabStop = false; + // + // ctrOutput + // + this.ctrOutput.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.ctrOutput.Location = new System.Drawing.Point(12, 79); + this.ctrOutput.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.ctrOutput.Name = "ctrOutput"; + this.ctrOutput.Size = new System.Drawing.Size(218, 414); + this.ctrOutput.TabIndex = 2; + this.ctrOutput.Text = "ctrOutput1"; + // + // chkClick + // + this.chkClick.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.chkClick.AutoSize = true; + this.chkClick.Location = new System.Drawing.Point(126, 53); + this.chkClick.Name = "chkClick"; + this.chkClick.Size = new System.Drawing.Size(39, 21); + this.chkClick.TabIndex = 8; + this.chkClick.Text = "C"; + this.chkClick.UseVisualStyleBackColor = true; + // + // FrmScreenAutomation + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(755, 816); + this.Controls.Add(this.splitMain); + this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.Name = "FrmScreenAutomation"; + this.Text = "ScreenAutomation"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FrmScreenAutomation_FormClosing); + this.Load += new System.EventHandler(this.FrmScreenAutomation_Load); + ((System.ComponentModel.ISupportInitialize)(this.picCapturer)).EndInit(); + this.splitMain.Panel1.ResumeLayout(false); + this.splitMain.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitMain)).EndInit(); + this.splitMain.ResumeLayout(false); + this.splitOutput.Panel1.ResumeLayout(false); + this.splitOutput.Panel2.ResumeLayout(false); + this.splitOutput.Panel2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitOutput)).EndInit(); + this.splitOutput.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.numFPS)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.picPreview)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.PictureBox picCapturer; + private Controls.CtrImageViewer picPreview; + private Controls.CtrOutput ctrOutput; + private System.Windows.Forms.SplitContainer splitMain; + private System.Windows.Forms.SplitContainer splitOutput; + private System.Windows.Forms.Button btnStartEnd; + private System.Windows.Forms.ComboBox ddlAutomationBot; + private System.Windows.Forms.NumericUpDown numFPS; + private System.Windows.Forms.Button btnAutomationBotConfig; + private System.Windows.Forms.CheckBox chkKeepToplevel; + private System.Windows.Forms.CheckBox chkClick; + } +} + diff --git a/VAR.ScreenAutomation/FrmScreenAutomation.cs b/VAR.ScreenAutomation/FrmScreenAutomation.cs new file mode 100644 index 0000000..d85d534 --- /dev/null +++ b/VAR.ScreenAutomation/FrmScreenAutomation.cs @@ -0,0 +1,215 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; +using VAR.ScreenAutomation.Code; +using VAR.ScreenAutomation.Interfaces; + +// ReSharper disable LocalizableElement + +namespace VAR.ScreenAutomation +{ + public partial class FrmScreenAutomation : Form + { + private bool _running; + private IAutomationBot _automationBot; + + private Timer _timTicker; + private Bitmap _bmpScreen; + + public FrmScreenAutomation() + { + PreInitializeComponent(); + InitializeComponent(); + } + + private void PreInitializeComponent() + { + AutoScaleMode = AutoScaleMode.None; + Font = new Font(Font.Name, 8.25f * 96f / CreateGraphics().DpiX, Font.Style, Font.Unit, Font.GdiCharSet, + Font.GdiVerticalFont); + } + + private void FrmScreenAutomation_Load(object sender, EventArgs e) + { + var configuration = new FileBackedConfiguration(); + configuration.Load(); + Top = configuration.Get("Top", Top); + Left = configuration.Get("Left", Left); + Width = configuration.Get("Width", Width); + Height = configuration.Get("Height", Height); + splitMain.SplitterDistance = configuration.Get("splitMain.SplitterDistance", splitMain.SplitterDistance); + splitOutput.SplitterDistance = + configuration.Get("splitOutput.SplitterDistance", splitOutput.SplitterDistance); + + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + SetStyle(ControlStyles.SupportsTransparentBackColor, true); + TransparencyKey = Color.LimeGreen; + picCapturer.BackColor = Color.LimeGreen; + + ddlAutomationBot.Items.AddRange(AutomationBotFactory.GetAllAutomationBots()); + ddlAutomationBot.SelectedItem = + configuration.Get("ddlAutomationBot", (string)ddlAutomationBot.SelectedItem); + if (ddlAutomationBot.SelectedIndex < 0) + { + ddlAutomationBot.SelectedIndex = 0; + } + + numFPS.Value = configuration.Get("numFPS", (int)numFPS.Value); + + chkKeepToplevel.Checked = configuration.Get("chkKeepToplevel", chkKeepToplevel.Checked); + chkClick.Checked = configuration.Get("chkClick", chkClick.Checked); + + if (components == null) { components = new Container(); } + + _timTicker = new Timer(components) + { + Interval = Convert.ToInt32(1000 / numFPS.Value), + }; + _timTicker.Tick += TimTicker_Tick; + _timTicker.Enabled = true; + _timTicker.Start(); + } + + private void FrmScreenAutomation_FormClosing(object sender, FormClosingEventArgs e) + { + var configuration = new FileBackedConfiguration(); + configuration.Set("Top", Top); + configuration.Set("Left", Left); + configuration.Set("Width", Width); + configuration.Set("Height", Height); + configuration.Set("splitMain.SplitterDistance", splitMain.SplitterDistance); + configuration.Set("splitOutput.SplitterDistance", splitOutput.SplitterDistance); + configuration.Set("ddlAutomationBot", (string)ddlAutomationBot.SelectedItem); + configuration.Set("numFPS", (int)numFPS.Value); + configuration.Set("chkKeepToplevel", chkKeepToplevel.Checked); + configuration.Set("chkClick", chkClick.Checked); + configuration.Save(); + } + + private void TimTicker_Tick(object sender, EventArgs e) + { + _timTicker.Enabled = false; + _timTicker.Stop(); + + _bmpScreen = Screenshoter.CaptureControl(picCapturer, _bmpScreen); + + if (_automationBot != null) + { + _bmpScreen = _automationBot.Process(_bmpScreen, ctrOutput); + if (_running) + { + string responseKeys = _automationBot.ResponseKeys(); + if (string.IsNullOrEmpty(responseKeys) == false && WindowHandling.ApplicationIsActivated() == false) + { + SendKeys.Send(responseKeys); + } + } + } + + picPreview.ImageShow = _bmpScreen; + + _timTicker.Interval = Convert.ToInt32(1000 / numFPS.Value); + _timTicker.Enabled = true; + _timTicker.Start(); + } + + private void BtnStartEnd_Click(object sender, EventArgs e) + { + if (_running) + { + End(); + } + else + { + Start(); + } + } + + private void DdlAutomationBot_SelectedIndexChanged(object sender, EventArgs e) + { + InitBot((string)ddlAutomationBot.SelectedItem); + } + + private void BtnAutomationBotConfig_Click(object sender, EventArgs e) + { + if (_automationBot == null) { return; } + + IConfiguration defaultConfig = _automationBot.GetDefaultConfiguration(); + var config = new FileBackedConfiguration(_automationBot.Name); + config.Load(defaultConfig); + var frmAutomationBotParameters = new FrmAutomationBotParams(config) + { + StartPosition = FormStartPosition.CenterParent + }; + if (_isToplevel) + { + WindowHandling.WindowSetTopLevel(this, false); + } + + frmAutomationBotParameters.ShowDialog(); + InitBot(_automationBot.Name); + if (_isToplevel) + { + WindowHandling.WindowSetTopLevel(this); + } + } + + private void Start() + { + if (_running) { return; } + + _running = true; + btnStartEnd.Text = "End"; + InitBot((string)ddlAutomationBot.SelectedItem); + if (!chkClick.Checked) { return; } + + Point pointCapturerCenter = + picCapturer.PointToScreen(new Point(picCapturer.Width / 2, picCapturer.Height / 2)); + Mouse.SetPosition((uint)pointCapturerCenter.X, (uint)pointCapturerCenter.Y); + Mouse.Click(Mouse.MouseButtons.Left); + } + + private void End() + { + if (_running == false) { return; } + + _running = false; + btnStartEnd.Text = "Start"; + } + + private void InitBot(string botName) + { + _automationBot = AutomationBotFactory.CreateFromName(botName); + var botConfiguration = new FileBackedConfiguration(botName); + botConfiguration.Load(); + _automationBot?.Init(ctrOutput, botConfiguration); + } + + private void chkKeepToplevel_CheckedChanged(object sender, EventArgs e) + { + WindowSetTopLevel(chkKeepToplevel.Checked); + } + + private bool _isToplevel; + + private void WindowSetTopLevel(bool toplevel) + { + if (toplevel) + { + if (_isToplevel) { return; } + + WindowHandling.WindowSetTopLevel(this); + _isToplevel = true; + } + else + { + if (_isToplevel == false) { return; } + + WindowHandling.WindowSetTopLevel(this, false); + _isToplevel = false; + } + } + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/FrmScreenAutomation.resx b/VAR.ScreenAutomation/FrmScreenAutomation.resx new file mode 100644 index 0000000..d5a483d --- /dev/null +++ b/VAR.ScreenAutomation/FrmScreenAutomation.resx @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + \ No newline at end of file diff --git a/VAR.ScreenAutomation/Interfaces/IAutomationBot.cs b/VAR.ScreenAutomation/Interfaces/IAutomationBot.cs new file mode 100644 index 0000000..b4364e4 --- /dev/null +++ b/VAR.ScreenAutomation/Interfaces/IAutomationBot.cs @@ -0,0 +1,13 @@ +using System.Drawing; + +namespace VAR.ScreenAutomation.Interfaces +{ + public interface IAutomationBot + { + string Name { get; } + IConfiguration GetDefaultConfiguration(); + void Init(IOutputHandler output, IConfiguration config); + Bitmap Process(Bitmap bmpInput, IOutputHandler output); + string ResponseKeys(); + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Interfaces/IConfiguration.cs b/VAR.ScreenAutomation/Interfaces/IConfiguration.cs new file mode 100644 index 0000000..a394f27 --- /dev/null +++ b/VAR.ScreenAutomation/Interfaces/IConfiguration.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace VAR.ScreenAutomation.Interfaces +{ + public interface IConfiguration + { + IEnumerable GetKeys(); + void Clear(); + bool Get(string key, bool defaultValue); + int Get(string key, int defaultValue); + string Get(string key, string defaultValue); + void Set(string key, bool value); + void Set(string key, int value); + void Set(string key, string value); + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Interfaces/IOutputHandler.cs b/VAR.ScreenAutomation/Interfaces/IOutputHandler.cs new file mode 100644 index 0000000..99002e6 --- /dev/null +++ b/VAR.ScreenAutomation/Interfaces/IOutputHandler.cs @@ -0,0 +1,8 @@ +namespace VAR.ScreenAutomation.Interfaces +{ + public interface IOutputHandler + { + void Clean(); + void AddLine(string line, object data = null); + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Program.cs b/VAR.ScreenAutomation/Program.cs new file mode 100644 index 0000000..62a2ead --- /dev/null +++ b/VAR.ScreenAutomation/Program.cs @@ -0,0 +1,19 @@ +using System; +using System.Windows.Forms; + +namespace VAR.ScreenAutomation +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + private static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new FrmScreenAutomation()); + } + } +} \ No newline at end of file diff --git a/VAR.ScreenAutomation/Properties/AssemblyInfo.cs b/VAR.ScreenAutomation/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0ece480 --- /dev/null +++ b/VAR.ScreenAutomation/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +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("VAR.ScreenAutomation")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("VAR.ScreenAutomation")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[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("e2be8e2a-3422-42a6-82fa-5e0ccdc42032")] + +// 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")] \ No newline at end of file diff --git a/VAR.ScreenAutomation/Properties/Resources.Designer.cs b/VAR.ScreenAutomation/Properties/Resources.Designer.cs new file mode 100644 index 0000000..106fa66 --- /dev/null +++ b/VAR.ScreenAutomation/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 VAR.ScreenAutomation.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("VAR.ScreenAutomation.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/VAR.ScreenAutomation/Properties/Resources.resx b/VAR.ScreenAutomation/Properties/Resources.resx new file mode 100644 index 0000000..1c875c9 --- /dev/null +++ b/VAR.ScreenAutomation/Properties/Resources.resx @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/VAR.ScreenAutomation/Properties/Settings.Designer.cs b/VAR.ScreenAutomation/Properties/Settings.Designer.cs new file mode 100644 index 0000000..15e5d03 --- /dev/null +++ b/VAR.ScreenAutomation/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 VAR.ScreenAutomation.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/VAR.ScreenAutomation/Properties/Settings.settings b/VAR.ScreenAutomation/Properties/Settings.settings new file mode 100644 index 0000000..796d34b --- /dev/null +++ b/VAR.ScreenAutomation/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/VAR.ScreenAutomation/VAR.ScreenAutomation.csproj b/VAR.ScreenAutomation/VAR.ScreenAutomation.csproj new file mode 100644 index 0000000..905957e --- /dev/null +++ b/VAR.ScreenAutomation/VAR.ScreenAutomation.csproj @@ -0,0 +1,113 @@ + + + + + Debug + AnyCPU + {E2BE8E2A-3422-42A6-82FA-5E0CCDC42032} + WinExe + VAR.ScreenAutomation + VAR.ScreenAutomation + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + app.manifest + + + + + + + + + + + + + + + + + + + + + + Form + + + FrmAutomationBotParams.cs + + + + + + + Component + + + Component + + + Form + + + FrmScreenAutomation.cs + + + + + + + FrmAutomationBotParams.cs + + + FrmScreenAutomation.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/VAR.ScreenAutomation/app.manifest b/VAR.ScreenAutomation/app.manifest new file mode 100644 index 0000000..d25cb99 --- /dev/null +++ b/VAR.ScreenAutomation/app.manifest @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + +