TetrisBot: Better search shape and search best move algorithm.

This commit is contained in:
2019-11-10 18:23:03 +01:00
parent 7379b52334
commit 7b97e3249e

View File

@@ -13,7 +13,9 @@ namespace VAR.ScreenAutomation.Bots
private TetrisGrid _grid; private TetrisGrid _grid;
private List<TetrisShape> _currentShape; private List<TetrisShape> _currentShape;
private TetrisGrid _workGrid; private TetrisGrid _workGrid0;
private TetrisGrid _workGrid1;
private double[] _columnEvaluation;
private bool _shapeFound = false; private bool _shapeFound = false;
private int _shapeX; private int _shapeX;
@@ -41,7 +43,9 @@ namespace VAR.ScreenAutomation.Bots
int gridHeight = config.Get("GridHeight", DefaultGridHeight); int gridHeight = config.Get("GridHeight", DefaultGridHeight);
_grid = new TetrisGrid(gridWidth, gridHeight); _grid = new TetrisGrid(gridWidth, gridHeight);
_workGrid = new TetrisGrid(gridWidth, gridHeight); _workGrid0 = new TetrisGrid(gridWidth, gridHeight);
_workGrid1 = new TetrisGrid(gridWidth, gridHeight);
_columnEvaluation = new double[gridWidth];
_currentShape = new List<TetrisShape> _currentShape = new List<TetrisShape>
{ {
new TetrisShape(), new TetrisShape(),
@@ -56,122 +60,119 @@ namespace VAR.ScreenAutomation.Bots
public Bitmap Process(Bitmap bmpInput, IOutputHandler output) public Bitmap Process(Bitmap bmpInput, IOutputHandler output)
{ {
_grid.SampleFromBitmap(bmpInput); _grid.SampleFromBitmap(bmpInput);
SearchShape();
// Search shape
_workGrid.SampleOther(_grid, 1, 1);
_workGrid.MarkGround();
if (_workGrid.SearchFirstCell(1, out _shapeX, out _shapeY))
{
_currentShape[0].SampleFromGrid(_workGrid, _shapeX, _shapeY, 1);
_shapeFound = _currentShape[0].IsValid();
for (int i = 1; i < 4; i++)
{
_currentShape[i].RotateCW(_currentShape[i - 1]);
}
}
SearchBestAction(); SearchBestAction();
// DEBUG Show information // Show information
_workGrid.SampleOther(_grid, 1, 1); _workGrid0.SampleOther(_grid, TetrisGrid.Solid, TetrisGrid.Solid);
if (_shapeFound) if (_shapeFound)
{ {
_currentShape[0].PutOnGrid(_workGrid, _shapeX, _shapeY, 0); _currentShape[0].PutOnGrid(_workGrid0, _shapeX, _shapeY, TetrisGrid.Empty);
_currentShape[_bestRotation].Drop(_workGrid, _shapeX + _bestXOffset, _shapeY, 2); _currentShape[_bestRotation].Drop(_workGrid0, _shapeX + _bestXOffset, _shapeY, TetrisGrid.ShapeA);
_currentShape[0].PutOnGrid(_workGrid0, _shapeX, _shapeY, TetrisGrid.ShapeB);
} }
_workGrid.Draw(bmpInput); _workGrid0.Draw(bmpInput, 0.75f);
_workGrid0.SampleOther(_grid, TetrisGrid.Solid, TetrisGrid.Solid);
_workGrid0.RemoveGround();
_workGrid0.Draw(bmpInput, 0.25f);
return bmpInput; return bmpInput;
} }
private void SearchShape()
{
_workGrid0.SampleOther(_grid, TetrisGrid.Solid, TetrisGrid.Solid);
_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.Solid, TetrisGrid.Solid);
matchedShape.PutOnGrid(_workGrid1, x, y, TetrisGrid.Empty);
if (matchedShape.CheckIntersection(_workGrid1, x, y + 1)) { continue; }
// Shape found
_currentShape[0].Copy(matchedShape);
for (int i = 1; i < 4; i++)
{
_currentShape[i].RotateCW(_currentShape[i - 1]);
}
_shapeX = x;
_shapeY = y;
_shapeFound = true;
break;
}
}
if (_shapeFound) { break; }
}
}
private void SearchBestAction() private void SearchBestAction()
{ {
_bestEvaluation = double.MinValue; _bestEvaluation = double.MinValue;
_bestXOffset = 0; _bestXOffset = 0;
_bestRotation = 0; _bestRotation = 0;
if (_shapeFound) if (!_shapeFound)
{ {
_workGrid.SampleOther(_grid, 1, 1); _workGrid0.SampleOther(_grid, TetrisGrid.Solid, TetrisGrid.Solid);
_currentShape[0].PutOnGrid(_workGrid, _shapeX, _shapeY, 0); return;
if (_currentShape[0].Drop(_workGrid, _shapeX, _shapeY, 2))
{
_bestXOffset = 0;
_bestRotation = 0;
_bestEvaluation = EvaluateWorkingGrid();
}
int offsetX = 1;
double newEvaluation;
for (int rotation = 0; rotation < 4; rotation++)
{
// Check positive offset
offsetX = 1;
do
{
_workGrid.SampleOther(_grid, 1, 1);
_currentShape[0].PutOnGrid(_workGrid, _shapeX, _shapeY, 0);
if (_currentShape[rotation].Drop(_workGrid, _shapeX + offsetX, _shapeY, 2))
{
newEvaluation = EvaluateWorkingGrid();
if (newEvaluation > _bestEvaluation)
{
_bestEvaluation = newEvaluation;
_bestXOffset = offsetX;
_bestRotation = rotation;
}
}
else
{
break;
}
offsetX++;
} while (true);
// Check negative offset
offsetX = -1;
do
{
_workGrid.SampleOther(_grid, 1, 1);
_currentShape[0].PutOnGrid(_workGrid, _shapeX, _shapeY, 0);
if (_currentShape[rotation].Drop(_workGrid, _shapeX + offsetX, _shapeY, 2))
{
newEvaluation = EvaluateWorkingGrid();
if (newEvaluation > _bestEvaluation)
{
_bestEvaluation = newEvaluation;
_bestXOffset = offsetX;
_bestRotation = rotation;
}
}
else
{
break;
}
offsetX--;
} while (true);
}
} }
else
for (int rotation = 0; rotation < 4; rotation++)
{ {
_workGrid.SampleOther(_grid, 1, 1); for (int x = 0; x < _grid.Width; x++)
{
_workGrid0.SampleOther(_grid, TetrisGrid.Solid, TetrisGrid.Solid);
_currentShape[0].PutOnGrid(_workGrid0, _shapeX, _shapeY, TetrisGrid.Empty);
if (_currentShape[rotation].Drop(_workGrid0, x, _shapeY, TetrisGrid.ShapeA))
{
double newEvaluation = EvaluateWorkingGrid();
_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 double EvaluateWorkingGrid() private double EvaluateWorkingGrid()
{ {
return _workGrid.Evaluate( return _workGrid0.Evaluate(
aggregateHeightWeight: -0.510066, aggregateHeightWeight: -0.510066,
completeLinesWeight: 0.760666, completeLinesWeight: 0.760666,
holesWeight: -0.35663, holesWeight: -0.35663,
bumpinessWeight: -0.184483); bumpinessWeight: -0.184483);
} }
private const int ShowCooldownFrames = 2; private const int ShotCooldownFrames = 2;
private int _shotCooldown = ShowCooldownFrames; private int _shotCooldown = ShotCooldownFrames;
public string ResponseKeys() public string ResponseKeys()
{ {
@@ -181,7 +182,7 @@ namespace VAR.ScreenAutomation.Bots
{ {
if (_shotCooldown <= 0) if (_shotCooldown <= 0)
{ {
_shotCooldown = ShowCooldownFrames; _shotCooldown = ShotCooldownFrames;
return " "; return " ";
} }
else else
@@ -191,7 +192,7 @@ namespace VAR.ScreenAutomation.Bots
} }
} }
_shotCooldown = ShowCooldownFrames; _shotCooldown = ShotCooldownFrames;
if (_bestRotation != 0 && _bestXOffset < 0) { return "{UP}{LEFT}"; } if (_bestRotation != 0 && _bestXOffset < 0) { return "{UP}{LEFT}"; }
if (_bestRotation != 0 && _bestXOffset > 0) { return "{UP}{RIGHT}"; } if (_bestRotation != 0 && _bestXOffset > 0) { return "{UP}{RIGHT}"; }
@@ -535,10 +536,41 @@ namespace VAR.ScreenAutomation.Bots
output.AddLine(sbLine.ToString()); 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 class TetrisGrid
{ {
public const byte Empty = 0;
public const byte Solid = 1;
public const byte ShapeA = 2;
public const byte ShapeB = 3;
private int _gridWidth; private int _gridWidth;
private int _gridHeight; private int _gridHeight;
@@ -628,13 +660,13 @@ namespace VAR.ScreenAutomation.Bots
} }
} }
public void MarkGround() public void RemoveGround()
{ {
for (int i = 0; i < _gridWidth; i++) for (int i = 0; i < _gridWidth; i++)
{ {
if (_grid[_gridHeight - 1][i] == 1) if (_grid[_gridHeight - 1][i] == 1)
{ {
FloodFill(i, _gridHeight - 1, 1, 2); FloodFill(i, _gridHeight - 1, Solid, Empty);
} }
} }
} }
@@ -722,7 +754,7 @@ namespace VAR.ScreenAutomation.Bots
for (int i = 0; i < _gridWidth; i++) for (int i = 0; i < _gridWidth; i++)
{ {
int j = 0; int j = 0;
while (j < _gridHeight && _grid[j][i] == 0) { j++; } while (j < _gridHeight && _grid[j][i] == Empty) { j++; }
_heights[i] = _gridHeight - j; _heights[i] = _gridHeight - j;
} }
double agregateHeight = _heights.Sum(); double agregateHeight = _heights.Sum();
@@ -741,11 +773,11 @@ namespace VAR.ScreenAutomation.Bots
bool block = false; bool block = false;
for (int y = 1; y < _gridHeight; y++) for (int y = 1; y < _gridHeight; y++)
{ {
if (_grid[y][x] != 0 && IsCompleteLine(y) == false) if (_grid[y][x] != Empty && IsCompleteLine(y) == false)
{ {
block = true; block = true;
} }
else if (_grid[y][x] == 0 && block) else if (_grid[y][x] == Empty && block)
{ {
holes++; holes++;
} }
@@ -769,38 +801,14 @@ namespace VAR.ScreenAutomation.Bots
return evaluationValue; return evaluationValue;
} }
public void Print(IOutputHandler output) public void Draw(Bitmap bmp, float dotWith = 0.5f)
{
for (int y = 0; y < _gridHeight; y++)
{
StringBuilder sbLine = new StringBuilder();
for (int x = 0; x < _gridWidth; x++)
{
if (_grid[y][x] == 0)
{
sbLine.Append("..");
}
else if (_grid[y][x] == 1)
{
sbLine.Append("$$");
}
else
{
sbLine.Append("[]");
}
}
output.AddLine(sbLine.ToString());
}
}
public void Draw(Bitmap bmp)
{ {
float xStep = bmp.Width / (float)_gridWidth; float xStep = bmp.Width / (float)_gridWidth;
float yStep = bmp.Height / (float)_gridHeight; float yStep = bmp.Height / (float)_gridHeight;
float halfXStep = xStep / 2; float halfXStep = xStep * dotWith;
float halfYStep = yStep / 2; float halfYStep = yStep * dotWith;
float offX = halfXStep / 2; float offX = (xStep - halfXStep) / 2;
float offY = halfYStep / 2; float offY = (yStep - halfYStep) / 2;
using (Pen borderPen = new Pen(Color.DarkGray)) using (Pen borderPen = new Pen(Color.DarkGray))
using (Graphics g = Graphics.FromImage(bmp)) using (Graphics g = Graphics.FromImage(bmp))
@@ -810,18 +818,22 @@ namespace VAR.ScreenAutomation.Bots
for (int x = 0; x < _gridWidth; x++) for (int x = 0; x < _gridWidth; x++)
{ {
Brush br = null; Brush br = null;
if (_grid[y][x] == 0) if (_grid[y][x] == Empty)
{ {
br = Brushes.Black; br = Brushes.Black;
} }
else if (_grid[y][x] == 1) else if (_grid[y][x] == Solid)
{ {
br = Brushes.Blue; br = Brushes.Blue;
} }
else if (_grid[y][x] == 2) else if (_grid[y][x] == ShapeA)
{ {
br = Brushes.Red; br = Brushes.Red;
} }
else if (_grid[y][x] == ShapeB)
{
br = Brushes.Green;
}
if (br == null) { continue; } if (br == null) { continue; }
g.DrawRectangle(borderPen, (xStep * x) + offX - 1, (yStep * y) + offY - 1, halfXStep + 2, halfYStep + 2); g.DrawRectangle(borderPen, (xStep * x) + offX - 1, (yStep * y) + offY - 1, halfXStep + 2, halfYStep + 2);