using UnityEngine; using System.Threading; using CharacterSystem; public class Bot : BasePlayer, IGamePlayer { [Header("Movement Settings")] [SerializeField] private float prepareSpeed; [SerializeField] private float moveSpeed; [Header("Bot Settings")] [SerializeField] private DifficultyPreset difficultyPreset; [SerializeField] private bool useDummyBots = false; [SerializeField] private float minThinkingTime = 1f; public DifficultyPreset dummyPreset; // State private PlayerState state = PlayerState.Sleep; private Chip currentChip; private int[] availableColumns; private int selectedColumnIndex; // Movement targets private Vector2 stickedTargetPosition; private Vector2 dropTargetPosition; // Thinking private Thread calculationThread; private int calculatedColumn = -1; private float thinkingStartTime; private bool isThinkingComplete = false; // Random movement private float randomMoveTimer; private float randomMoveInterval = 0.3f; private int droppedRow, droppedColumn; public event System.Action OnChipDropped; private int SelectedColumn => availableColumns[selectedColumnIndex]; public bool ControlsEnabled { get; set; } = true; public void PrepareChip() { SetState(PlayerState.Preparing); CacheAvailableColumns(); CreateChip(); SetInitialChipPosition(); } public void Sleep() { SetState(PlayerState.Sleep); currentChip = null; availableColumns = null; StopThinking(); } public void UpdateControls() { switch (state) { case PlayerState.Preparing: UpdatePreparing(); break; case PlayerState.Thinking: UpdateThinking(); break; case PlayerState.Moving: UpdateMoving(); break; case PlayerState.Dropping: UpdateDropping(); break; case PlayerState.Sleep: break; } } private void UpdatePreparing() { // Движение сверху вниз к stickedTargetPosition (без оффсета) if (currentChip.MoveToLinear(stickedTargetPosition, prepareSpeed)) { StartThinking(); } } private void UpdateThinking() { // Случайные движения (инициируют переход в Moving) TryRandomMovement(); // Проверяем, завершены ли вычисления и прошло ли минимальное время if (isThinkingComplete && Time.time - thinkingStartTime >= minThinkingTime) { MoveToCalculatedColumn(); } } private void UpdateMoving() { // Плавное горизонтальное движение к целевой позиции (Y сохраняется) if (currentChip.MoveToLinear(stickedTargetPosition, moveSpeed)) { // Если условия для дропа выполнены - бросаем if (isThinkingComplete && Time.time - thinkingStartTime >= minThinkingTime) { PerformDrop(); } else { // Иначе возвращаемся в режим раздумий SetState(PlayerState.Thinking); } } } private void UpdateDropping() { if (currentChip.MoveToAccelerated(dropTargetPosition)) { SetState(PlayerState.Sleep); OnChipDropped?.Invoke(droppedColumn, droppedRow); } } private void StartThinking() { SetState(PlayerState.Thinking); isThinkingComplete = false; calculatedColumn = -1; thinkingStartTime = Time.time; randomMoveTimer = 0f; StopThinking(); calculationThread = new Thread(CalculateBestMove); calculationThread.Start(); } private void StopThinking() { if (calculationThread != null && calculationThread.IsAlive) { calculationThread.Abort(); calculationThread = null; } } private void CalculateBestMove() { try { DifficultyPreset currentPreset = difficultyPreset; if (useDummyBots) currentPreset = dummyPreset; if (currentPreset != null && board != null) { int movesMade = board.MovesCount; currentPreset = currentPreset.GetPreset(movesMade); Solver.SetPreset(currentPreset); } GameManager gm = GameManager.Instance; if (gm != null) { bool onBossByScore = gm.vsBotScore == gm.botsAmount - 1; bool isHardCommonDifficulty = SettingsManager.GetString(SettingKey.DIFFICULTY) == "Сложная"; bool isUpperCashDifficulty = SettingsManager.GetString(SettingKey.DIFFICULTY_MODE) == "Заработок"; bool needSuperPreset = onBossByScore && (isHardCommonDifficulty || isUpperCashDifficulty); if (needSuperPreset) calculatedColumn = SolverV2.FindBestMove(board.GridToTeam(), team); else calculatedColumn = Solver.FindBestMove(board.GridToTeam(), team); } else { calculatedColumn = Solver.FindBestMove(board.GridToTeam(), team); } isThinkingComplete = true; } catch (ThreadAbortException) { } catch (System.Exception e) { Debug.LogError($"Bot calculation error: {e.Message}"); } } private void MoveToCalculatedColumn() { if (calculatedColumn == -1) return; int targetIndex = System.Array.IndexOf(availableColumns, calculatedColumn); if (targetIndex == -1) { Debug.LogWarning($"Column {calculatedColumn} is not available!"); return; } if (selectedColumnIndex == targetIndex) { PerformDrop(); return; } selectedColumnIndex = targetIndex; UpdateTargetPositionX(board.GetStickedPosition(SelectedColumn).x); SetState(PlayerState.Moving); } private void PerformDrop() { dropTargetPosition = board.GetTargetPosition(SelectedColumn); droppedColumn = SelectedColumn; droppedRow = board.GetLowestEmptyRow(SelectedColumn); board.DropChip(currentChip, SelectedColumn); SetState(PlayerState.Dropping); } private void TryRandomMovement() { if (availableColumns == null || availableColumns.Length <= 1) return; randomMoveTimer += Time.deltaTime; if (randomMoveTimer >= randomMoveInterval) { randomMoveTimer = 0f; int direction = Random.Range(0, 2) * 2 - 1; int newIndex = Mathf.Clamp(selectedColumnIndex + direction, 0, availableColumns.Length - 1); if (newIndex != selectedColumnIndex) { selectedColumnIndex = newIndex; // Сохраняем текущий Y, меняем только X UpdateTargetPositionX(board.GetStickedPosition(SelectedColumn).x); SetState(PlayerState.Moving); } } } private void CacheAvailableColumns() { availableColumns = board.GetNonFullColumns(); selectedColumnIndex = availableColumns.Length / 2; } private void CreateChip() { if (CharactersController.HasActiveCharacter && CharactersController.CurrentCharacter.visualModule.chipImage != null) chipSprite = CharactersController.CurrentCharacter.visualModule.chipImage; currentChip = ChipPool.Instance.GetConfigured(chipSprite, team); } private void SetInitialChipPosition() { // Начальная позиция – сверху с оффсетом stickedTargetPosition = board.GetMiddleStickedPosition(); currentChip.transform.position = stickedTargetPosition + Vector2.up * 5f; } private void SetState(PlayerState newState) => state = newState; // Обновляет только X целевой позиции, Y остаётся неизменным private void UpdateTargetPositionX(float newX) { stickedTargetPosition = new Vector2(newX, stickedTargetPosition.y); } private enum PlayerState { Sleep, Preparing, Thinking, Moving, Dropping } }