Правки в скрипте проверки поля; реализовано поведение бота, реализован TurnManager; Всё вышесказанное проброшено в игру
This commit is contained in:
parent
c8937a239d
commit
0731921ee7
@ -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}
|
||||
<team>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}
|
||||
<team>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
|
||||
<team>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}
|
||||
|
||||
9
Assets/Scripts/BasePlayer.cs
Normal file
9
Assets/Scripts/BasePlayer.cs
Normal file
@ -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; }
|
||||
}
|
||||
2
Assets/Scripts/BasePlayer.cs.meta
Normal file
2
Assets/Scripts/BasePlayer.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0f846fb928268440b9664deef1bcdfa
|
||||
@ -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();
|
||||
|
||||
298
Assets/Scripts/Gameplay/Bot.cs
Normal file
298
Assets/Scripts/Gameplay/Bot.cs
Normal file
@ -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
|
||||
}
|
||||
2
Assets/Scripts/Gameplay/Bot.cs.meta
Normal file
2
Assets/Scripts/Gameplay/Bot.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53a19fde86337d440b9594d33a8ed786
|
||||
@ -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++)
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
98
Assets/Scripts/Gameplay/TurnManager.cs
Normal file
98
Assets/Scripts/Gameplay/TurnManager.cs
Normal file
@ -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!");
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Gameplay/TurnManager.cs.meta
Normal file
2
Assets/Scripts/Gameplay/TurnManager.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c31f8116cb529434eafcc42e078c3b75
|
||||
8
Assets/Scripts/IGamePlayer.cs
Normal file
8
Assets/Scripts/IGamePlayer.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
public interface IGamePlayer : IControlable
|
||||
{
|
||||
void PrepareChip();
|
||||
void Sleep();
|
||||
event Action OnChipDropped;
|
||||
}
|
||||
2
Assets/Scripts/IGamePlayer.cs.meta
Normal file
2
Assets/Scripts/IGamePlayer.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dafb8cdd40e72cf4ba778ed003543473
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user