Keeps the repo root clean - only README.md visible on landing page. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
631 lines
18 KiB
C#
631 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
int sameCommandDelay = 75;
|
|
int verifyTimeout = 250;
|
|
int downDelay = 50;
|
|
int landingTimeout = 400;
|
|
|
|
int botPosX = 18;
|
|
int botPosY = 28;
|
|
|
|
int waitAreaMinX = 5;
|
|
int waitAreaMaxX = 8;
|
|
int waitAreaMinY = 19;
|
|
int waitAreaMaxY = 22;
|
|
int walkToX = 8;
|
|
int walkToY = 23;
|
|
|
|
const int FIELD_MIN_X = 11;
|
|
const int FIELD_MAX_X = 20;
|
|
const int FIELD_MIN_Y = 3;
|
|
const int FIELD_MAX_Y = 22;
|
|
const int SPAWN_MIN_X = 14;
|
|
const int SPAWN_MAX_X = 17;
|
|
const int SPAWN_MIN_Y = 3;
|
|
const int SPAWN_MAX_Y = 5;
|
|
const int CTRL_LEFT = 256117487;
|
|
const int CTRL_DOWN = 256117486;
|
|
const int CTRL_RIGHT = 256117485;
|
|
const int CTRL_ROTATE = 256117484;
|
|
|
|
DateTime lastLeftCmd = DateTime.MinValue;
|
|
DateTime lastRightCmd = DateTime.MinValue;
|
|
DateTime lastRotateCmd = DateTime.MinValue;
|
|
DateTime lastDownCmd = DateTime.MinValue;
|
|
DateTime lastMoveDetected = DateTime.MinValue;
|
|
DateTime spawnTime = DateTime.MinValue;
|
|
|
|
int pendingLeftFromX = -1;
|
|
int pendingRightFromX = -1;
|
|
int pendingRotateFromRot = -1;
|
|
|
|
int targetX = -1;
|
|
int targetRot = -1;
|
|
int currentX = -1;
|
|
int currentRot = -1;
|
|
char currentPiece = '?';
|
|
|
|
bool pieceActive = false;
|
|
bool positioned = false;
|
|
HashSet<int> pieceIds = new HashSet<int>();
|
|
|
|
public struct Point : IEquatable<Point>
|
|
{
|
|
public int X { get; }
|
|
public int Y { get; }
|
|
public Point(int x, int y) { X = x; Y = y; }
|
|
public bool Equals(Point other) => X == other.X && Y == other.Y;
|
|
public override bool Equals(object obj) => obj is Point other && Equals(other);
|
|
public override int GetHashCode() => HashCode.Combine(X, Y);
|
|
}
|
|
|
|
var shapes = new Dictionary<string, int[]> {
|
|
["I0"] = new[] {0,0,1,0,2,0,3,0}, ["I1"] = new[] {0,0,0,1,0,2,0,3},
|
|
["O0"] = new[] {0,0,1,0,0,1,1,1},
|
|
["T0"] = new[] {0,0,1,0,2,0,1,1}, ["T1"] = new[] {1,0,0,1,1,1,1,2},
|
|
["T2"] = new[] {1,0,0,1,1,1,2,1}, ["T3"] = new[] {0,0,0,1,1,1,0,2},
|
|
["S0"] = new[] {1,0,2,0,0,1,1,1}, ["S1"] = new[] {0,0,0,1,1,1,1,2},
|
|
["Z0"] = new[] {0,0,1,0,1,1,2,1}, ["Z1"] = new[] {1,0,0,1,1,1,0,2},
|
|
["L0"] = new[] {0,0,0,1,0,2,1,2}, ["L1"] = new[] {0,0,1,0,2,0,0,1},
|
|
["L2"] = new[] {0,0,1,0,1,1,1,2}, ["L3"] = new[] {2,0,0,1,1,1,2,1},
|
|
["J0"] = new[] {1,0,1,1,0,2,1,2}, ["J1"] = new[] {0,0,0,1,1,1,2,1},
|
|
["J2"] = new[] {0,0,1,0,0,1,0,2}, ["J3"] = new[] {0,0,1,0,2,0,2,1}
|
|
};
|
|
|
|
HashSet<string> ignoredItems = new HashSet<string> {
|
|
"Large Block 18", "Großer Bauklotz 18",
|
|
"Large Block 13", "Großer Bauklotz 13"
|
|
};
|
|
|
|
bool IsBlockItem(string name)
|
|
{
|
|
if (string.IsNullOrEmpty(name)) return false;
|
|
if (ignoredItems.Contains(name)) return false;
|
|
return name.StartsWith("Großer Bauklotz") || name.StartsWith("Large Block");
|
|
}
|
|
|
|
bool IsInField(int x, int y) => x >= FIELD_MIN_X && x <= FIELD_MAX_X && y >= FIELD_MIN_Y && y <= FIELD_MAX_Y;
|
|
bool IsInSpawn(int x, int y) => x >= SPAWN_MIN_X && x <= SPAWN_MAX_X && y >= SPAWN_MIN_Y && y <= SPAWN_MAX_Y;
|
|
|
|
void SendLeft()
|
|
{
|
|
pendingLeftFromX = currentX;
|
|
Send(Out["ClickFurni"], CTRL_LEFT, 0);
|
|
lastLeftCmd = DateTime.Now;
|
|
}
|
|
|
|
void SendRight()
|
|
{
|
|
pendingRightFromX = currentX;
|
|
Send(Out["ClickFurni"], CTRL_RIGHT, 0);
|
|
lastRightCmd = DateTime.Now;
|
|
}
|
|
|
|
void SendRotate()
|
|
{
|
|
pendingRotateFromRot = currentRot;
|
|
Send(Out["ClickFurni"], CTRL_ROTATE, 0);
|
|
lastRotateCmd = DateTime.Now;
|
|
}
|
|
|
|
void SendDown()
|
|
{
|
|
Send(Out["ClickFurni"], CTRL_DOWN, 0);
|
|
lastDownCmd = DateTime.Now;
|
|
}
|
|
|
|
bool CanLeft() => (DateTime.Now - lastLeftCmd).TotalMilliseconds >= sameCommandDelay;
|
|
bool CanRight() => (DateTime.Now - lastRightCmd).TotalMilliseconds >= sameCommandDelay;
|
|
bool CanRotate() => (DateTime.Now - lastRotateCmd).TotalMilliseconds >= sameCommandDelay;
|
|
bool CanDown() => (DateTime.Now - lastDownCmd).TotalMilliseconds >= downDelay;
|
|
|
|
string IdentifyShape(List<Point> positions)
|
|
{
|
|
if (positions.Count != 4) return "?";
|
|
int minX = positions.Min(p => p.X);
|
|
int minY = positions.Min(p => p.Y);
|
|
var norm = positions.Select(p => new Point(p.X - minX, p.Y - minY)).OrderBy(p => p.Y).ThenBy(p => p.X).ToList();
|
|
foreach (var kvp in shapes)
|
|
{
|
|
var pts = new List<Point>();
|
|
for (int i = 0; i < 8; i += 2) pts.Add(new Point(kvp.Value[i], kvp.Value[i+1]));
|
|
pts = pts.OrderBy(p => p.Y).ThenBy(p => p.X).ToList();
|
|
bool match = true;
|
|
for (int i = 0; i < 4; i++) if (!norm[i].Equals(pts[i])) { match = false; break; }
|
|
if (match) return kvp.Key;
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
char GetPiece(string s) => s != "?" && s.Length >= 1 ? s[0] : '?';
|
|
int GetRot(string s) => s != "?" && s.Length >= 2 ? int.Parse(s.Substring(1)) : -1;
|
|
|
|
List<Point> GetShapePoints(char piece, int rot)
|
|
{
|
|
string key = $"{piece}{rot}";
|
|
if (!shapes.ContainsKey(key)) return new List<Point>();
|
|
var def = shapes[key];
|
|
var pts = new List<Point>();
|
|
for (int i = 0; i < 8; i += 2) pts.Add(new Point(def[i], def[i+1]));
|
|
return pts;
|
|
}
|
|
|
|
int GetMaxRotations(char piece)
|
|
{
|
|
if (piece == 'O') return 1;
|
|
if (piece == 'I' || piece == 'S' || piece == 'Z') return 2;
|
|
return 4;
|
|
}
|
|
|
|
HashSet<Point> GetFieldBlocks()
|
|
{
|
|
var result = new HashSet<Point>();
|
|
foreach (var item in FloorItems)
|
|
{
|
|
if (item == null) continue;
|
|
if (!IsBlockItem(item.GetName())) continue;
|
|
int x = item.Location.X;
|
|
int y = item.Location.Y;
|
|
if (IsInField(x, y) && !pieceIds.Contains((int)item.Id))
|
|
result.Add(new Point(x, y));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int SimulateDrop(char piece, int rot, int startX, HashSet<Point> field)
|
|
{
|
|
var shape = GetShapePoints(piece, rot);
|
|
if (shape.Count != 4) return -1;
|
|
for (int y = FIELD_MIN_Y; y <= FIELD_MAX_Y + 1; y++)
|
|
{
|
|
foreach (var p in shape)
|
|
{
|
|
int px = startX + p.X;
|
|
int py = y + p.Y;
|
|
if (py > FIELD_MAX_Y || field.Contains(new Point(px, py)))
|
|
return y - 1;
|
|
}
|
|
}
|
|
return FIELD_MAX_Y;
|
|
}
|
|
|
|
int CountHoles(HashSet<Point> field)
|
|
{
|
|
int holes = 0;
|
|
for (int x = FIELD_MIN_X; x <= FIELD_MAX_X; x++)
|
|
{
|
|
bool blockFound = false;
|
|
for (int y = FIELD_MIN_Y; y <= FIELD_MAX_Y; y++)
|
|
{
|
|
if (field.Contains(new Point(x, y))) blockFound = true;
|
|
else if (blockFound) holes++;
|
|
}
|
|
}
|
|
return holes;
|
|
}
|
|
|
|
int CountHoleDepth(HashSet<Point> field)
|
|
{
|
|
int depth = 0;
|
|
for (int x = FIELD_MIN_X; x <= FIELD_MAX_X; x++)
|
|
{
|
|
int above = 0;
|
|
for (int y = FIELD_MIN_Y; y <= FIELD_MAX_Y; y++)
|
|
{
|
|
if (field.Contains(new Point(x, y))) above++;
|
|
else if (above > 0) depth += above;
|
|
}
|
|
}
|
|
return depth;
|
|
}
|
|
|
|
int CountRowsWithHoles(HashSet<Point> field)
|
|
{
|
|
int count = 0;
|
|
for (int y = FIELD_MIN_Y; y <= FIELD_MAX_Y; y++)
|
|
{
|
|
for (int x = FIELD_MIN_X; x <= FIELD_MAX_X; x++)
|
|
{
|
|
if (!field.Contains(new Point(x, y)))
|
|
{
|
|
for (int yy = FIELD_MIN_Y; yy < y; yy++)
|
|
{
|
|
if (field.Contains(new Point(x, yy))) { count++; goto nextRow; }
|
|
}
|
|
}
|
|
}
|
|
nextRow:;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int CountRowTransitions(HashSet<Point> field)
|
|
{
|
|
int t = 0;
|
|
for (int y = FIELD_MIN_Y; y <= FIELD_MAX_Y; y++)
|
|
{
|
|
bool last = true;
|
|
for (int x = FIELD_MIN_X; x <= FIELD_MAX_X; x++)
|
|
{
|
|
bool cur = field.Contains(new Point(x, y));
|
|
if (cur != last) t++;
|
|
last = cur;
|
|
}
|
|
if (!last) t++;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
int CountColTransitions(HashSet<Point> field)
|
|
{
|
|
int t = 0;
|
|
for (int x = FIELD_MIN_X; x <= FIELD_MAX_X; x++)
|
|
{
|
|
bool last = true;
|
|
for (int y = FIELD_MIN_Y; y <= FIELD_MAX_Y; y++)
|
|
{
|
|
bool cur = field.Contains(new Point(x, y));
|
|
if (cur != last) t++;
|
|
last = cur;
|
|
}
|
|
if (!last) t++;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
int CountWellSums(HashSet<Point> field)
|
|
{
|
|
int wellSum = 0;
|
|
for (int x = FIELD_MIN_X; x <= FIELD_MAX_X; x++)
|
|
{
|
|
for (int y = FIELD_MIN_Y; y <= FIELD_MAX_Y; y++)
|
|
{
|
|
if (field.Contains(new Point(x, y))) continue;
|
|
bool lb = (x == FIELD_MIN_X) || field.Contains(new Point(x - 1, y));
|
|
bool rb = (x == FIELD_MAX_X) || field.Contains(new Point(x + 1, y));
|
|
if (lb && rb)
|
|
{
|
|
int d = 1;
|
|
for (int yy = y + 1; yy <= FIELD_MAX_Y; yy++)
|
|
{
|
|
if (field.Contains(new Point(x, yy))) break;
|
|
bool lb2 = (x == FIELD_MIN_X) || field.Contains(new Point(x - 1, yy));
|
|
bool rb2 = (x == FIELD_MAX_X) || field.Contains(new Point(x + 1, yy));
|
|
if (lb2 && rb2) d++;
|
|
else break;
|
|
}
|
|
wellSum += (d * (d + 1)) / 2;
|
|
}
|
|
}
|
|
}
|
|
return wellSum;
|
|
}
|
|
|
|
int[] GetHeights(HashSet<Point> field)
|
|
{
|
|
int[] h = new int[10];
|
|
for (int x = FIELD_MIN_X; x <= FIELD_MAX_X; x++)
|
|
{
|
|
for (int y = FIELD_MIN_Y; y <= FIELD_MAX_Y; y++)
|
|
{
|
|
if (field.Contains(new Point(x, y)))
|
|
{
|
|
h[x - FIELD_MIN_X] = FIELD_MAX_Y - y + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return h;
|
|
}
|
|
|
|
double ScorePlacement(char piece, int rot, int x, HashSet<Point> field)
|
|
{
|
|
var shape = GetShapePoints(piece, rot);
|
|
if (shape.Count != 4) return double.MinValue;
|
|
int landY = SimulateDrop(piece, rot, x, field);
|
|
if (landY < FIELD_MIN_Y) return double.MinValue;
|
|
foreach (var p in shape)
|
|
{
|
|
int px = x + p.X;
|
|
int py = landY + p.Y;
|
|
if (px < FIELD_MIN_X || px > FIELD_MAX_X || py > FIELD_MAX_Y)
|
|
return double.MinValue;
|
|
}
|
|
var newField = new HashSet<Point>(field);
|
|
var piecePoints = new HashSet<Point>();
|
|
foreach (var p in shape)
|
|
{
|
|
var pt = new Point(x + p.X, landY + p.Y);
|
|
newField.Add(pt);
|
|
piecePoints.Add(pt);
|
|
}
|
|
int linesCleared = 0;
|
|
int pieceCellsCleared = 0;
|
|
for (int row = FIELD_MIN_Y; row <= FIELD_MAX_Y; row++)
|
|
{
|
|
bool full = true;
|
|
for (int col = FIELD_MIN_X; col <= FIELD_MAX_X; col++)
|
|
if (!newField.Contains(new Point(col, row))) { full = false; break; }
|
|
if (full)
|
|
{
|
|
linesCleared++;
|
|
for (int col = FIELD_MIN_X; col <= FIELD_MAX_X; col++)
|
|
{
|
|
var pt = new Point(col, row);
|
|
if (piecePoints.Contains(pt)) pieceCellsCleared++;
|
|
newField.Remove(pt);
|
|
}
|
|
}
|
|
}
|
|
if (linesCleared > 0)
|
|
{
|
|
var dropped = new HashSet<Point>();
|
|
for (int row = FIELD_MAX_Y; row >= FIELD_MIN_Y; row--)
|
|
{
|
|
for (int col = FIELD_MIN_X; col <= FIELD_MAX_X; col++)
|
|
{
|
|
var pt = new Point(col, row);
|
|
if (newField.Contains(pt))
|
|
{
|
|
newField.Remove(pt);
|
|
int newRow = row;
|
|
while (newRow < FIELD_MAX_Y && !dropped.Contains(new Point(col, newRow + 1))) newRow++;
|
|
dropped.Add(new Point(col, newRow));
|
|
}
|
|
}
|
|
}
|
|
newField = dropped;
|
|
}
|
|
int pieceH = shape.Max(p => p.Y) + 1;
|
|
double landingHeight = (FIELD_MAX_Y - landY + 1) + (pieceH - 1) / 2.0;
|
|
double erodedPieceCells = linesCleared * pieceCellsCleared;
|
|
int rowTrans = CountRowTransitions(newField);
|
|
int colTrans = CountColTransitions(newField);
|
|
int holes = CountHoles(newField);
|
|
int wellSums = CountWellSums(newField);
|
|
int holeDepth = CountHoleDepth(newField);
|
|
int rowsWithHoles = CountRowsWithHoles(newField);
|
|
double score = 0;
|
|
score += -12.63 * landingHeight;
|
|
score += 6.60 * erodedPieceCells;
|
|
score += -9.22 * rowTrans;
|
|
score += -19.77 * colTrans;
|
|
score += -13.08 * holes;
|
|
score += -10.49 * wellSums;
|
|
score += -1.61 * holeDepth;
|
|
score += -24.04 * rowsWithHoles;
|
|
var heights = GetHeights(newField);
|
|
int maxH = heights.Max();
|
|
if (maxH >= 12)
|
|
{
|
|
score += linesCleared * 5000;
|
|
score -= maxH * 50;
|
|
}
|
|
return score;
|
|
}
|
|
|
|
(int, int) FindBestPlacement(char piece, HashSet<Point> field)
|
|
{
|
|
int bestX = 15, bestRot = 0;
|
|
double bestScore = double.MinValue;
|
|
int maxRot = GetMaxRotations(piece);
|
|
for (int rot = 0; rot < maxRot; rot++)
|
|
{
|
|
var shape = GetShapePoints(piece, rot);
|
|
if (shape.Count != 4) continue;
|
|
int w = shape.Max(p => p.X) + 1;
|
|
for (int px = FIELD_MIN_X; px <= FIELD_MAX_X - w + 1; px++)
|
|
{
|
|
double s = ScorePlacement(piece, rot, px, field);
|
|
if (s > bestScore) { bestScore = s; bestX = px; bestRot = rot; }
|
|
}
|
|
}
|
|
return (bestX, bestRot);
|
|
}
|
|
|
|
(List<Point> positions, int minX, int minY, string shape, char piece, int rot) GetPieceState()
|
|
{
|
|
var positions = new List<Point>();
|
|
foreach (var item in FloorItems)
|
|
{
|
|
if (item == null) continue;
|
|
if (!pieceIds.Contains((int)item.Id)) continue;
|
|
positions.Add(new Point(item.Location.X, item.Location.Y));
|
|
}
|
|
if (positions.Count != 4) return (positions, -1, -1, "?", '?', -1);
|
|
int minX = positions.Min(p => p.X);
|
|
int minY = positions.Min(p => p.Y);
|
|
string shape = IdentifyShape(positions);
|
|
char piece = GetPiece(shape);
|
|
int rot = GetRot(shape);
|
|
return (positions, minX, minY, shape, piece, rot);
|
|
}
|
|
|
|
OnIntercept(In["ObjectUpdate"], e => {
|
|
var p = e.Packet;
|
|
int id = p.ReadInt();
|
|
if (pieceIds.Contains(id))
|
|
lastMoveDetected = DateTime.Now;
|
|
});
|
|
|
|
Log("TETRIS BOT");
|
|
Log($"Play position: ({botPosX},{botPosY})");
|
|
|
|
while (Run)
|
|
{
|
|
Delay(1);
|
|
|
|
if (Self == null) continue;
|
|
|
|
int myX = Self.Location.X;
|
|
int myY = Self.Location.Y;
|
|
|
|
if (myX >= waitAreaMinX && myX <= waitAreaMaxX && myY >= waitAreaMinY && myY <= waitAreaMaxY)
|
|
{
|
|
Move(walkToX, walkToY);
|
|
Delay(500);
|
|
continue;
|
|
}
|
|
|
|
if (myX != botPosX || myY != botPosY)
|
|
{
|
|
pieceIds.Clear();
|
|
pieceActive = false;
|
|
currentPiece = '?';
|
|
targetX = -1;
|
|
targetRot = -1;
|
|
positioned = false;
|
|
Delay(100);
|
|
continue;
|
|
}
|
|
|
|
if (!pieceActive)
|
|
{
|
|
var spawnBlocks = new List<(int id, int x, int y)>();
|
|
foreach (var item in FloorItems)
|
|
{
|
|
if (item == null) continue;
|
|
if (!IsBlockItem(item.GetName())) continue;
|
|
int x = item.Location.X;
|
|
int y = item.Location.Y;
|
|
if (IsInSpawn(x, y))
|
|
spawnBlocks.Add(((int)item.Id, x, y));
|
|
}
|
|
|
|
if (spawnBlocks.Count == 4)
|
|
{
|
|
pieceIds.Clear();
|
|
foreach (var b in spawnBlocks)
|
|
pieceIds.Add(b.id);
|
|
|
|
var positions = spawnBlocks.Select(b => new Point(b.x, b.y)).ToList();
|
|
string shape = IdentifyShape(positions);
|
|
currentPiece = GetPiece(shape);
|
|
currentRot = GetRot(shape);
|
|
currentX = positions.Min(p => p.X);
|
|
|
|
if (currentPiece != '?')
|
|
{
|
|
var field = GetFieldBlocks();
|
|
var (bx, br) = FindBestPlacement(currentPiece, field);
|
|
targetX = bx;
|
|
targetRot = br;
|
|
positioned = false;
|
|
pieceActive = true;
|
|
spawnTime = DateTime.Now;
|
|
lastMoveDetected = DateTime.Now;
|
|
pendingLeftFromX = -1;
|
|
pendingRightFromX = -1;
|
|
pendingRotateFromRot = -1;
|
|
Log($"{currentPiece}{currentRot} @ X{currentX} -> X{targetX} r{targetRot}");
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
var state = GetPieceState();
|
|
if (state.positions.Count != 4)
|
|
{
|
|
pieceIds.Clear();
|
|
pieceActive = false;
|
|
currentPiece = '?';
|
|
targetX = -1;
|
|
targetRot = -1;
|
|
positioned = false;
|
|
continue;
|
|
}
|
|
|
|
int newX = state.minX;
|
|
int newRot = state.rot != -1 ? state.rot : currentRot;
|
|
|
|
if (newX != currentX)
|
|
{
|
|
if (pendingLeftFromX != -1 && newX < pendingLeftFromX)
|
|
pendingLeftFromX = -1;
|
|
if (pendingRightFromX != -1 && newX > pendingRightFromX)
|
|
pendingRightFromX = -1;
|
|
currentX = newX;
|
|
}
|
|
|
|
if (newRot != currentRot)
|
|
{
|
|
pendingRotateFromRot = -1;
|
|
currentRot = newRot;
|
|
}
|
|
|
|
double timeSinceMove = (DateTime.Now - lastMoveDetected).TotalMilliseconds;
|
|
if (timeSinceMove > landingTimeout)
|
|
{
|
|
pieceIds.Clear();
|
|
pieceActive = false;
|
|
currentPiece = '?';
|
|
targetX = -1;
|
|
targetRot = -1;
|
|
positioned = false;
|
|
continue;
|
|
}
|
|
|
|
int maxRot = GetMaxRotations(currentPiece);
|
|
int rotNeeded = (targetRot - currentRot + maxRot) % maxRot;
|
|
int xDiff = targetX - currentX;
|
|
|
|
if (rotNeeded == 0 && xDiff == 0)
|
|
positioned = true;
|
|
|
|
if (positioned)
|
|
{
|
|
if (CanDown())
|
|
SendDown();
|
|
continue;
|
|
}
|
|
|
|
double timeSinceLeft = (DateTime.Now - lastLeftCmd).TotalMilliseconds;
|
|
double timeSinceRight = (DateTime.Now - lastRightCmd).TotalMilliseconds;
|
|
double timeSinceRotate = (DateTime.Now - lastRotateCmd).TotalMilliseconds;
|
|
|
|
bool leftPending = pendingLeftFromX != -1 && timeSinceLeft < verifyTimeout;
|
|
bool rightPending = pendingRightFromX != -1 && timeSinceRight < verifyTimeout;
|
|
bool rotatePending = pendingRotateFromRot != -1 && timeSinceRotate < verifyTimeout;
|
|
|
|
bool leftTimeout = pendingLeftFromX != -1 && timeSinceLeft >= verifyTimeout;
|
|
bool rightTimeout = pendingRightFromX != -1 && timeSinceRight >= verifyTimeout;
|
|
bool rotateTimeout = pendingRotateFromRot != -1 && timeSinceRotate >= verifyTimeout;
|
|
|
|
if (rotNeeded > 0)
|
|
{
|
|
if (rotateTimeout && CanRotate())
|
|
{
|
|
SendRotate();
|
|
}
|
|
else if (!rotatePending && CanRotate())
|
|
{
|
|
SendRotate();
|
|
}
|
|
}
|
|
|
|
if (xDiff < 0)
|
|
{
|
|
if (leftTimeout && CanLeft())
|
|
{
|
|
SendLeft();
|
|
}
|
|
else if (!leftPending && CanLeft())
|
|
{
|
|
SendLeft();
|
|
}
|
|
}
|
|
else if (xDiff > 0)
|
|
{
|
|
if (rightTimeout && CanRight())
|
|
{
|
|
SendRight();
|
|
}
|
|
else if (!rightPending && CanRight())
|
|
{
|
|
SendRight();
|
|
}
|
|
}
|
|
}
|