From 0731921ee72523a0e3318be17eedb7bedcd64248 Mon Sep 17 00:00:00 2001 From: sega Date: Tue, 4 Aug 2026 12:34:31 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B2?= =?UTF-8?q?=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=B5=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8F?= =?UTF-8?q?;=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B1=D0=BE=D1=82=D0=B0,=20=D1=80=D0=B5=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=20TurnManager;=20=D0=92?= =?UTF-8?q?=D1=81=D1=91=20=D0=B2=D1=8B=D1=88=D0=B5=D1=81=D0=BA=D0=B0=D0=B7?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D0=BE=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=BE=D1=88=D0=B5=D0=BD=D0=BE=20=D0=B2=20=D0=B8=D0=B3=D1=80?= =?UTF-8?q?=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scenes/GameScene3D.unity | 168 ++++++++++- Assets/Scripts/BasePlayer.cs | 9 + Assets/Scripts/BasePlayer.cs.meta | 2 + Assets/Scripts/Common/GameManager.cs | 14 +- Assets/Scripts/Gameplay/Bot.cs | 298 ++++++++++++++++++++ Assets/Scripts/Gameplay/Bot.cs.meta | 2 + Assets/Scripts/Gameplay/FieldChecker.cs | 71 +++-- Assets/Scripts/Gameplay/Player.cs | 20 +- Assets/Scripts/Gameplay/TurnManager.cs | 98 +++++++ Assets/Scripts/Gameplay/TurnManager.cs.meta | 2 + Assets/Scripts/IGamePlayer.cs | 8 + Assets/Scripts/IGamePlayer.cs.meta | 2 + Assets/Scripts/MenuGameplayTransit.cs | 9 + 13 files changed, 663 insertions(+), 40 deletions(-) create mode 100644 Assets/Scripts/BasePlayer.cs create mode 100644 Assets/Scripts/BasePlayer.cs.meta create mode 100644 Assets/Scripts/Gameplay/Bot.cs create mode 100644 Assets/Scripts/Gameplay/Bot.cs.meta create mode 100644 Assets/Scripts/Gameplay/TurnManager.cs create mode 100644 Assets/Scripts/Gameplay/TurnManager.cs.meta create mode 100644 Assets/Scripts/IGamePlayer.cs create mode 100644 Assets/Scripts/IGamePlayer.cs.meta diff --git a/Assets/Scenes/GameScene3D.unity b/Assets/Scenes/GameScene3D.unity index d17203a..89d3e36 100644 --- a/Assets/Scenes/GameScene3D.unity +++ b/Assets/Scenes/GameScene3D.unity @@ -3249,6 +3249,58 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 164597568} m_CullTransparentMesh: 1 +--- !u!1 &167531120 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 167531122} + - component: {fileID: 167531121} + m_Layer: 0 + m_Name: Bot + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &167531121 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 167531120} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 53a19fde86337d440b9594d33a8ed786, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::Bot + board: {fileID: 1768372455} + chipSprite: {fileID: 21300000, guid: 2194de45dbf1e8c46a827a05cdd2d905, type: 3} + k__BackingField: 2 + prepareSpeed: 30 + moveSpeed: 10 + difficultyPreset: {fileID: 11400000, guid: 1c381d73d66d6254d8e6326516732d27, type: 2} + useDummyBots: 0 + minThinkingTime: 2 +--- !u!4 &167531122 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 167531120} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -5.96509, y: 11.30334, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1329752847} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &172285184 GameObject: m_ObjectHideFlags: 0 @@ -4235,6 +4287,59 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 233260561} m_CullTransparentMesh: 1 +--- !u!1 &245771467 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 245771469} + - component: {fileID: 245771468} + m_Layer: 0 + m_Name: Player2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &245771468 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 245771467} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0302f7201f8ad714280dbc7ceefc55a7, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::Player + board: {fileID: 1768372455} + chipSprite: {fileID: 21300000, guid: 2194de45dbf1e8c46a827a05cdd2d905, type: 3} + k__BackingField: 2 + chipYOffset: 5 + prepareSpeed: 30 + moveSpeed: 10 + leftButtons: 03000000 + rightButtons: 01000000 + actionButtons: 04000000 +--- !u!4 &245771469 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 245771467} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -3.6197202, y: 4.48496, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1329752847} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &251509681 GameObject: m_ObjectHideFlags: 0 @@ -4524,7 +4629,7 @@ MonoBehaviour: leaderboard: {fileID: 1784614873} CharactersConfigurator: {fileID: 141381088} menuGameplayTransit: {fileID: 29868612} - player1: {fileID: 304437819} + turnManager: {fileID: 1329752846} setPasswordPanel: {fileID: 131572818} vitrinaPanel: {fileID: 61619038} qrPanel: {fileID: 1060301113} @@ -5284,7 +5389,7 @@ MonoBehaviour: m_EditorClassIdentifier: Assembly-CSharp::Player board: {fileID: 1768372455} chipSprite: {fileID: -1055215911, guid: b196c55df50055d4cafb336e9a05b519, type: 3} - team: 1 + k__BackingField: 1 chipYOffset: 5 prepareSpeed: 30 moveSpeed: 10 @@ -5299,12 +5404,12 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 304437818} serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -5.18764, y: 2.92598, z: 0} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -3.6197202, y: 4.48496, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 0} + m_Father: {fileID: 1329752847} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &325463671 GameObject: @@ -22515,6 +22620,57 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1329533185} m_CullTransparentMesh: 1 +--- !u!1 &1329752845 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1329752847} + - component: {fileID: 1329752846} + m_Layer: 0 + m_Name: TurnManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1329752846 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1329752845} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c31f8116cb529434eafcc42e078c3b75, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::TurnManager + player1: {fileID: 304437819} + player2: {fileID: 245771468} + bot: {fileID: 167531121} + gameMode: 0 +--- !u!4 &1329752847 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1329752845} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -1.56792, y: -1.55898, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 304437820} + - {fileID: 245771469} + - {fileID: 167531122} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1330901877 GameObject: m_ObjectHideFlags: 0 @@ -38619,4 +38775,4 @@ SceneRoots: - {fileID: 1615046716} - {fileID: 46193421} - {fileID: 1606109336} - - {fileID: 304437820} + - {fileID: 1329752847} diff --git a/Assets/Scripts/BasePlayer.cs b/Assets/Scripts/BasePlayer.cs new file mode 100644 index 0000000..15f0180 --- /dev/null +++ b/Assets/Scripts/BasePlayer.cs @@ -0,0 +1,9 @@ +using UnityEngine; + +public class BasePlayer : MonoBehaviour +{ + [Header("Components")] + [SerializeField] protected Board board; + [SerializeField] protected Sprite chipSprite; + [field: SerializeField] public Team team { get; private set; } +} diff --git a/Assets/Scripts/BasePlayer.cs.meta b/Assets/Scripts/BasePlayer.cs.meta new file mode 100644 index 0000000..c625097 --- /dev/null +++ b/Assets/Scripts/BasePlayer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d0f846fb928268440b9664deef1bcdfa \ No newline at end of file diff --git a/Assets/Scripts/Common/GameManager.cs b/Assets/Scripts/Common/GameManager.cs index 87007e6..681814b 100644 --- a/Assets/Scripts/Common/GameManager.cs +++ b/Assets/Scripts/Common/GameManager.cs @@ -64,7 +64,7 @@ public class GameManager : MonoBehaviour [Header("Переходы")] [SerializeField] private MenuGameplayTransit menuGameplayTransit; - [SerializeField] Player player1; + [SerializeField] TurnManager turnManager; //public PvpPointer pvpPointer; //public MovesAndTimeCounter movesAndTimeCounter; @@ -103,12 +103,16 @@ public class GameManager : MonoBehaviour // return; SetMode(mode); + menuGameplayTransit.GameplayOpened += OnGameplayOpened; menuGameplayTransit.GotoGameplay(); + ControlsPerformer.Instance.SetControlable(null); + } + + void OnGameplayOpened() + { + menuGameplayTransit.GameplayOpened -= OnGameplayOpened; OnGameStarted?.Invoke(gameMode); - - player1.PrepareChip(); - ControlsPerformer.Instance.SetControlable(player1); - //timerManager.SetTimersActivity(gameMode); + turnManager.StartGame(gameMode); } private void OnApplicationQuit() => ComNativeWrapper.Serial_CloseAll(); diff --git a/Assets/Scripts/Gameplay/Bot.cs b/Assets/Scripts/Gameplay/Bot.cs new file mode 100644 index 0000000..76eef59 --- /dev/null +++ b/Assets/Scripts/Gameplay/Bot.cs @@ -0,0 +1,298 @@ +using UnityEngine; +using System.Threading; + +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; + + // 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; + + public event System.Action OnChipDropped; + + private int SelectedColumn => availableColumns[selectedColumnIndex]; + public bool ControlsEnabled { get; set; } = true; + + #region Public Methods + + 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; + } + } + + #endregion + + #region State Updates + + 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(); + Debug.Log("Chip dropped"); + } + } + + #endregion + + #region Bot Logic + + 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 && DifficultyManager.Instance != null) + currentPreset = DifficultyManager.Instance.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); + board.DropChip(currentChip, SelectedColumn); + SetState(PlayerState.Dropping); + } + + #endregion + + #region Random Movement + + 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); + } + } + } + + #endregion + + #region Helpers + + private void CacheAvailableColumns() + { + availableColumns = board.GetNonFullColumns(); + selectedColumnIndex = availableColumns.Length / 2; + } + + private void CreateChip() + { + 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); + } + + #endregion + + #region Nested Types + + private enum PlayerState + { + Sleep, + Preparing, + Thinking, + Moving, + Dropping + } + + #endregion +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Bot.cs.meta b/Assets/Scripts/Gameplay/Bot.cs.meta new file mode 100644 index 0000000..681b546 --- /dev/null +++ b/Assets/Scripts/Gameplay/Bot.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 53a19fde86337d440b9594d33a8ed786 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/FieldChecker.cs b/Assets/Scripts/Gameplay/FieldChecker.cs index 0f8fc1f..05d9443 100644 --- a/Assets/Scripts/Gameplay/FieldChecker.cs +++ b/Assets/Scripts/Gameplay/FieldChecker.cs @@ -12,6 +12,8 @@ public static class FieldChecker public static int Height => Board.Instance.Rows; public static int Width => Board.Instance.Columns; public static int TargetLineLength = 4; + public static CheckResult lastResult; + public static CheckResult Check(Team lastPlayer) { @@ -31,25 +33,25 @@ public static class FieldChecker WinningIndexes.UnionWith(colsResult.winningIndexes); WinningIndexes.UnionWith(diagonalsResult.winningIndexes); - return new CheckResult(end: true, + lastResult = new CheckResult(end: true, isDraw: false, WinningIndexes); + return lastResult; } else if (checkDraw()) { - return new CheckResult(end: true, + lastResult = new CheckResult(end: true, isDraw: true, WinningIndexes); + return lastResult; } else { - return new CheckResult(end: false, + lastResult = new CheckResult(end: false, isDraw: false, WinningIndexes); + return lastResult; } - - - } private static (bool hasWin, HashSet<(int row, int col)> winningIndexes) checkAllRows(Team targetTeam) { @@ -58,17 +60,15 @@ public static class FieldChecker for (int row = 0; row < Height; row++) { int start = -1; - - for (int col = 0; col <= Width; col++) // теперь проходим до Width включительно + for (int col = 0; col < Width; col++) { - // Проверяем, не вышли ли за границы массива var chip = FieldArray[col, row]; - bool isMatch = col < Width && chip != null && chip.team == targetTeam; + bool isMatch = chip != null && chip.team == targetTeam; if (isMatch && start == -1) start = col; - if ((!isMatch || col == Width) && start != -1) + if (!isMatch && start != -1) { int length = col - start; if (length >= TargetLineLength) @@ -79,6 +79,16 @@ public static class FieldChecker start = -1; } } + // Проверка последовательности в конце строки + if (start != -1) + { + int length = Width - start; + if (length >= TargetLineLength) + { + for (int c = start; c < Width; c++) + winningCells.Add((row, c)); + } + } } return (winningCells.Count > 0, winningCells); @@ -91,15 +101,15 @@ public static class FieldChecker { int start = -1; - for (int row = 0; row <= Height; row++) + for (int row = 0; row < Height; row++) { var chip = FieldArray[col, row]; - bool isMatch = row < Height && chip != null && chip.team == targetTeam; + bool isMatch = chip != null && chip.team == targetTeam; if (isMatch && start == -1) start = row; - if ((!isMatch || row == Height) && start != -1) + if (!isMatch && start != -1) { int length = row - start; if (length >= TargetLineLength) @@ -110,6 +120,17 @@ public static class FieldChecker start = -1; } } + + // Проверка последовательности в конце столбца + if (start != -1) + { + int length = Height - start; + if (length >= TargetLineLength) + { + for (int r = start; r < Height; r++) + winningCells.Add((r, col)); + } + } } return (winningCells.Count > 0, winningCells); @@ -143,7 +164,7 @@ public static class FieldChecker } private static void CheckDiagonalStartingAt(int startRow, int startCol, int rowStep, int colStep, - Team targetCellState, HashSet<(int, int)> winningCells) + Team targetCellState, HashSet<(int, int)> winningCells) { int rows = Height; int cols = Width; @@ -162,10 +183,11 @@ public static class FieldChecker if (isMatch) { sequenceLength++; - + } + else + { if (sequenceLength >= TargetLineLength) { - // Добавляем ВСЕ ячейки из текущей последовательности for (int i = 0; i < sequenceLength; i++) { int r = sequenceStartRow + i * rowStep; @@ -173,9 +195,6 @@ public static class FieldChecker winningCells.Add((r, c)); } } - } - else - { sequenceLength = 0; sequenceStartRow = currentRow + rowStep; sequenceStartCol = currentCol + colStep; @@ -184,8 +203,20 @@ public static class FieldChecker currentRow += rowStep; currentCol += colStep; } + + // Проверка последовательности в конце диагонали + if (sequenceLength >= TargetLineLength) + { + for (int i = 0; i < sequenceLength; i++) + { + int r = sequenceStartRow + i * rowStep; + int c = sequenceStartCol + i * colStep; + winningCells.Add((r, c)); + } + } } + public static bool checkDraw() { for (int col = 0; col < Width; col++) diff --git a/Assets/Scripts/Gameplay/Player.cs b/Assets/Scripts/Gameplay/Player.cs index 6d6c0f4..a289359 100644 --- a/Assets/Scripts/Gameplay/Player.cs +++ b/Assets/Scripts/Gameplay/Player.cs @@ -1,13 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using UnityEngine; -public class Player : MonoBehaviour, IControlable +public class Player : BasePlayer, IGamePlayer { - [Header("Components")] - [SerializeField] private Board board; - [SerializeField] private Sprite chipSprite; - [SerializeField] private Team team; - [Header("Movement Settings")] [SerializeField] private float chipYOffset = 5f; [SerializeField] private float prepareSpeed; @@ -28,6 +24,8 @@ public class Player : MonoBehaviour, IControlable private Vector2 stickedTargetPosition; private Vector2 dropTargetPosition; + public event Action OnChipDropped; + private int SelectedColumn => availableColumns[selectedColumnIndex]; public bool ControlsEnabled { get; set; } = true; @@ -120,8 +118,12 @@ public class Player : MonoBehaviour, IControlable { if (currentChip.MoveToAccelerated(dropTargetPosition)) { - SetState(PlayerState.Sleep); - Debug.Log("Chip dropped successfully"); + if (currentChip.MoveToAccelerated(dropTargetPosition)) + { + SetState(PlayerState.Sleep); + OnChipDropped?.Invoke(); + Debug.Log("Chip dropped"); + } } } diff --git a/Assets/Scripts/Gameplay/TurnManager.cs b/Assets/Scripts/Gameplay/TurnManager.cs new file mode 100644 index 0000000..05cd081 --- /dev/null +++ b/Assets/Scripts/Gameplay/TurnManager.cs @@ -0,0 +1,98 @@ +using Cysharp.Threading.Tasks.Triggers; +using System; +using UnityEngine; + +public class TurnManager : MonoBehaviour +{ + [Header("Players")] + [SerializeField] private Player player1; + [SerializeField] private Player player2; + [SerializeField] private Bot bot; + + [Header("Settings")] + [SerializeField] private GameMode gameMode; + + private IGamePlayer currentPlayer; + private bool isGameOver = false; + + public void StartGame(GameMode mode) + { + gameMode = mode; + isGameOver = false; + SetupPlayers(); + StartTurn(currentPlayer); + } + + private void SetupPlayers() + { + // Всегда начинаем с первого игрока + currentPlayer = player1; + } + + private void StartTurn(IGamePlayer player) + { + if (isGameOver) return; + + // Отписываемся, чтобы избежать дублирования подписок + player.OnChipDropped -= OnTurnCompleted; + player.OnChipDropped += OnTurnCompleted; + player.PrepareChip(); + ControlsPerformer.Instance.SetControlable(player); + currentPlayer = player; + } + + private void OnTurnCompleted() + { + // Отписываемся от события текущего игрока + currentPlayer.OnChipDropped -= OnTurnCompleted; + + if (CheckGameOver()) + { + OnGameOver(); + return; + } + + NextTurn(); + } + + private void NextTurn() + { + // Отключаем текущего игрока + currentPlayer.Sleep(); + + // Определяем следующего игрока в зависимости от режима + IGamePlayer nextPlayer; + if (gameMode == GameMode.pve) + { + nextPlayer = (currentPlayer == player1) ? (IGamePlayer)bot : player1; + } + else // pvp + { + nextPlayer = (currentPlayer == player1) ? (IGamePlayer)player2 : player1; + } + + currentPlayer = nextPlayer; + StartTurn(currentPlayer); + } + + private bool CheckGameOver() + { + if(currentPlayer is BasePlayer player) + { + var checkResult = FieldChecker.Check(player.team); + return checkResult.End; + } + else + { + throw new Exception($"Не удалось привести {currentPlayer}"); + } + } + + private void OnGameOver() + { + isGameOver = true; + currentPlayer.Sleep(); + ControlsPerformer.Instance.SetControlable(null); + Debug.Log("Game Over!"); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/TurnManager.cs.meta b/Assets/Scripts/Gameplay/TurnManager.cs.meta new file mode 100644 index 0000000..98078be --- /dev/null +++ b/Assets/Scripts/Gameplay/TurnManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c31f8116cb529434eafcc42e078c3b75 \ No newline at end of file diff --git a/Assets/Scripts/IGamePlayer.cs b/Assets/Scripts/IGamePlayer.cs new file mode 100644 index 0000000..6a7ff61 --- /dev/null +++ b/Assets/Scripts/IGamePlayer.cs @@ -0,0 +1,8 @@ +using System; + +public interface IGamePlayer : IControlable +{ + void PrepareChip(); + void Sleep(); + event Action OnChipDropped; +} \ No newline at end of file diff --git a/Assets/Scripts/IGamePlayer.cs.meta b/Assets/Scripts/IGamePlayer.cs.meta new file mode 100644 index 0000000..5fbea42 --- /dev/null +++ b/Assets/Scripts/IGamePlayer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dafb8cdd40e72cf4ba778ed003543473 \ No newline at end of file diff --git a/Assets/Scripts/MenuGameplayTransit.cs b/Assets/Scripts/MenuGameplayTransit.cs index 447954f..e33a438 100644 --- a/Assets/Scripts/MenuGameplayTransit.cs +++ b/Assets/Scripts/MenuGameplayTransit.cs @@ -1,4 +1,5 @@ using DG.Tweening; +using System; using UnityEngine; public class MenuGameplayTransit : MonoBehaviour @@ -6,6 +7,8 @@ public class MenuGameplayTransit : MonoBehaviour [SerializeField] MenuLayoutController menu; [SerializeField] GameplayLayoutController gameplay; + public event Action GameplayOpened; + Sequence _sequence; bool locked = false; private void Update() @@ -32,9 +35,15 @@ public class MenuGameplayTransit : MonoBehaviour _sequence.Append(gameplay.GetOpenSequence()); _sequence.onComplete += Unlock; + _sequence.onComplete += NotifyGameplayOpened; _sequence.PlayForward(); } + private void NotifyGameplayOpened() + { + GameplayOpened?.Invoke(); + } + public void GotoMenu() { Lock();