// Spludlow Software
// Copyright © Samuel P. Ludlow 2020 All Rights Reserved
// Distributed under the terms of the GNU General Public License version 3
// Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
// https://www.spludlow.co.uk/LICENCE.TXT
// The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission
// v1.14
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace Spludlow.Tetris
{
///
/// Run a Tetris Bot on a supplied Tetris Client
///
public class TetrisBot
{
private bool AskStop = true;
public TetrisBot()
{
}
public void Start(TetrisClient client, int ms)
{
this.AskStop = false;
Task.Run(() => Run(client, ms));
}
public void Stop()
{
this.AskStop = true;
}
public bool Running
{
get
{
return !this.AskStop;
}
}
public void Run(TetrisClient client, int ms)
{
Random random = new Random();
try
{
while (AskStop == false)
{
NextMove(client, ms);
Thread.Sleep(250);
if (random.Next(100) == 0)
{
client.TargetNext();
Thread.Sleep(100);
}
}
}
catch (ObjectDisposedException)
{
AskStop = true;
}
catch (Exception ee)
{
Spludlow.Log.Error("Tetris Bot, Run Loop", ee);
}
}
///
/// Make the next move on a client
///
public static void NextMove(TetrisClient client, int ms)
{
int bestMoveX = 0;
int bestRot = 0;
int bestScore = 0;
// for each rotation
for (int rot = 0; rot < 4; ++rot)
{
TetrisBoard board = client.GetBoards(0)[0];
if (board == null) // Not got board yet
{
Thread.Sleep(1000);
return;
}
// Calculate scores at each X postion
int[] moveXScore = TetrisBot.Calculate(board);
int moveX = moveXScore[0];
int score = moveXScore[1];
// Keep note of X and rot for best score
if (score > bestScore)
{
bestScore = score;
bestMoveX = moveX;
bestRot = rot;
}
// Rotate for next test
client.RotateCW();
Thread.Sleep(ms);
}
// Rotate to where best result was
for (int rot = 0; rot < bestRot; ++rot)
client.RotateCW();
// Move to correct X position
for (int move = 0; move < Math.Abs(bestMoveX); ++move)
{
if (bestMoveX < 0)
client.Left();
else
client.Right();
Thread.Sleep(ms);
}
// Perform the drop
client.Up();
}
///
/// Calculate the score for each X postion
///
public static int[] Calculate(TetrisBoard clientBoard)
{
// Comb out current shape current=(9-15) placed=(1-8)
int shapeX = clientBoard.Width;
int shapeY = clientBoard.Height;
int maxX = 0;
int maxY = 0;
TetrisBoard shape = new TetrisBoard(4, 4);
for (int pass = 0; pass < 2; ++pass)
{
for (int y = 0; y < clientBoard.Height; ++y)
{
for (int x = 0; x < clientBoard.Width; ++x)
{
byte value = clientBoard.Peek(x, y);
if (value >= 9)
{
if (pass == 0)
{
shapeX = Math.Min(x, shapeX);
shapeY = Math.Min(y, shapeY);
maxX = Math.Max(x, maxX);
maxY = Math.Max(y, maxY);
}
else
{
byte fillValue = (byte)(value - 8);
shape.Poke(x - shapeX, y - shapeY, fillValue);
}
}
}
}
}
int shapeWidth = (maxX - shapeX) + 1;
int shapeHeight = (maxY - shapeY) + 1;
// Create a map of blocks that want filling, anything within curent shape that will empty underneith
List wantedBlocks = new List();
for (int x = 0; x < shapeWidth; ++x)
{
for (int y = shapeHeight - 1; y >= 0; --y)
{
if (shape.Peek(x, y) == 0)
wantedBlocks.Add(new int[] { x, y });
else
break;
}
// Add 2 rows on the bottom of the map to keep holes open
wantedBlocks.Add(new int[] { x, shapeHeight });
wantedBlocks.Add(new int[] { x, shapeHeight + 1 });
}
int bestX = 0;
int bestBestY = 0;
// For each X position
for (int x = 0; x < (clientBoard.Width - (shapeWidth - 1)); ++x)
{
int bestY = 0;
int emptyCount = 0;
// For each Y postion
for (int y = 0; y < (clientBoard.Height - (shapeHeight - 1)); ++y)
{
// If can go here
if (ShapePositionValid(clientBoard, shape, x, y) == true)
{
bestY = y;
// Conut up empties in map
emptyCount = 0;
foreach (int[] xy in wantedBlocks)
{
int px = x + xy[0];
int py = y + xy[1];
if (py < clientBoard.Height && clientBoard.Peek(px, py) == 0)
++emptyCount;
}
}
else
{
break;
}
}
// Calculate score
bestY += shapeHeight;
bestY -= (emptyCount * 2);
// Keep best
if (bestY > bestBestY)
{
bestX = x;
bestBestY = bestY;
}
}
int moveX = bestX - shapeX;
return new int[] { moveX, bestBestY };
}
///
/// Modified version of what's in server, wont detect the current shape
///
public static bool ShapePositionValid(TetrisBoard playerBoard, TetrisBoard shape, int x, int y)
{
for (int shapeY = 0; shapeY < 4; ++shapeY)
{
for (int shapeX = 0; shapeX < 4; ++shapeX)
{
byte shapeValue = shape.Peek(shapeX, shapeY);
if (shapeValue == 0)
continue;
int boardX = x + shapeX;
int boardY = y + shapeY;
if (boardX < 0 || boardY < 0)
return false;
if (boardX >= playerBoard.Width || boardY >= playerBoard.Height)
return false;
byte boardValue = playerBoard.Peek(boardX, boardY);
if (boardValue > 0 && boardValue < 9)
return false;
}
}
return true;
}
}
}