diff --git a/.idea/.idea.Match3/.idea/.gitignore b/.idea/.idea.Match3/.idea/.gitignore new file mode 100644 index 0000000..9b4f605 --- /dev/null +++ b/.idea/.idea.Match3/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/modules.xml +/contentModel.xml +/.idea.Match3.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.Match3/.idea/indexLayout.xml b/.idea/.idea.Match3/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.Match3/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.Match3/.idea/vcs.xml b/.idea/.idea.Match3/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/.idea.Match3/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Assets/GameVariables.asset b/Assets/GameVariables.asset index 8fab45d..98fbe4b 100644 --- a/Assets/GameVariables.asset +++ b/Assets/GameVariables.asset @@ -14,15 +14,43 @@ MonoBehaviour: m_EditorClassIdentifier: bgTilePrefabs: {fileID: 2914066502361773997, guid: 3f39182b81f944a4d93213431acb41c3, type: 3} - bombPrefab: {fileID: 5652386976359944012, guid: aa0291e650b382941875040db9e8a232, - type: 3} gemsPrefabs: - - {fileID: 3808538059049426536, guid: 724e93e48c6cc0b4ab3d44e5ea34f2ec, type: 3} - - {fileID: 4490600519223577409, guid: 784323496d719684cb6201b200b95864, type: 3} - - {fileID: 6213027626580313688, guid: 473855e3d0d3c8143836b678c9a1b8b5, type: 3} - - {fileID: 1845825807271331471, guid: 91ba2370328500d4db689dad894b1602, type: 3} - - {fileID: 745607475630438949, guid: 93bd623174244c047af9ce43cc254c32, type: 3} - destroyEffectPrefabs: [] + - type: 0 + gemPrefab: {fileID: 3808538059049426536, guid: 724e93e48c6cc0b4ab3d44e5ea34f2ec, + type: 3} + explosionPrefab: {fileID: 8904178830182364799, guid: d794be08823edd34da1790efd1739074, + type: 3} + scoreValue: 10 + - type: 1 + gemPrefab: {fileID: 4490600519223577409, guid: 784323496d719684cb6201b200b95864, + type: 3} + explosionPrefab: {fileID: 4157327684024108653, guid: e5bba4196a7d43441bf62bd125171449, + type: 3} + scoreValue: 10 + - type: 2 + gemPrefab: {fileID: 6213027626580313688, guid: 473855e3d0d3c8143836b678c9a1b8b5, + type: 3} + explosionPrefab: {fileID: 5902279534240010874, guid: 072620c89288cc14e9872d103c2b2fbe, + type: 3} + scoreValue: 10 + - type: 3 + gemPrefab: {fileID: 1845825807271331471, guid: 91ba2370328500d4db689dad894b1602, + type: 3} + explosionPrefab: {fileID: 4897855320600288343, guid: 7fcedfb0407bb5d438de37da2d61df30, + type: 3} + scoreValue: 10 + - type: 4 + gemPrefab: {fileID: 745607475630438949, guid: 93bd623174244c047af9ce43cc254c32, + type: 3} + explosionPrefab: {fileID: 1865769399618337668, guid: 986dd822e2e6d4b45b2a054259f1241b, + type: 3} + scoreValue: 10 + - type: 5 + gemPrefab: {fileID: 5652386976359944012, guid: aa0291e650b382941875040db9e8a232, + type: 3} + explosionPrefab: {fileID: 8968486364681163996, guid: 05c754e3d4f9fd349ac1def58d17670f, + type: 3} + scoreValue: 10 bonusAmount: 0.5 bombChance: 2 dropHeight: 1 diff --git a/Assets/Scenes/Scene_Submission.unity b/Assets/Scenes/Scene_Submission.unity index 25a9f62..2c74cd5 100644 --- a/Assets/Scenes/Scene_Submission.unity +++ b/Assets/Scenes/Scene_Submission.unity @@ -154,6 +154,55 @@ Transform: - {fileID: 259844899} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &109682480 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 109682481} + - component: {fileID: 109682482} + m_Layer: 0 + m_Name: ScoreView + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &109682481 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 109682480} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 786657518} + m_Father: {fileID: 1450061022} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 0, y: -245} + m_SizeDelta: {x: 500, y: 100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &109682482 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 109682480} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 86d965b2c3d5474082911bdd360847ce, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &259844896 GameObject: m_ObjectHideFlags: 0 @@ -507,17 +556,17 @@ RectTransform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 786657517} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 1450061022} + m_Father: {fileID: 109682481} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0.5, y: 1} - m_AnchorMax: {x: 0.5, y: 1} - m_AnchoredPosition: {x: 0, y: -245} - m_SizeDelta: {x: 500, y: 100} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &786657519 MonoBehaviour: @@ -858,7 +907,7 @@ RectTransform: m_LocalScale: {x: 0, y: 0, z: 0} m_ConstrainProportionsScale: 0 m_Children: - - {fileID: 786657518} + - {fileID: 109682481} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} diff --git a/Assets/Scripts/GameBoard.cs b/Assets/Scripts/GameBoard.cs deleted file mode 100644 index 23f5764..0000000 --- a/Assets/Scripts/GameBoard.cs +++ /dev/null @@ -1,188 +0,0 @@ -// using System.Collections; -// using System.Collections.Generic; -// using System.Linq; -// using UnityEngine; -// using Enums; -// -// //Done, moved to MatchService and GameBoard -// public class GameBoard -// { -// // - sets size of the grid -// // - handles the score -// // - holds the current matches -// #region Variables -// -// private int height = 0; -// public int Height { get { return this.height; } } -// -// private int width = 0; -// public int Width { get { return this.width; } } -// -// private SC_Gem[,] allGems; -// // public Gem[,] AllGems { get { return allGems; } } -// -// private int score = 0; -// public int Score -// { -// get { return this.score; } -// set { -// this.score = value; } -// } -// -// private List currentMatches = new List(); -// public List CurrentMatches { get { return this.currentMatches; } } -// #endregion -// -// public GameBoard(int _Width, int _Height) -// { -// this.height = _Height; -// this.width = _Width; -// this.allGems = new SC_Gem[this.width, this.height]; -// } -// -// //checks if there are 3 gems of the same type next to each other -// //used during setup to avoid matches on game start -// //MatchService -// public bool MatchesAt(Vector2Int _PositionToCheck, SC_Gem _GemToCheck) -// { -// if (_PositionToCheck.x > 1) -// { -// if (this.allGems[_PositionToCheck.x - 1, _PositionToCheck.y].type == _GemToCheck.type && this.allGems[_PositionToCheck.x - 2, _PositionToCheck.y].type == _GemToCheck.type) -// return true; -// } -// -// if (_PositionToCheck.y > 1) -// { -// if (this.allGems[_PositionToCheck.x, _PositionToCheck.y - 1].type == _GemToCheck.type && this.allGems[_PositionToCheck.x, _PositionToCheck.y - 2].type == _GemToCheck.type) -// return true; -// } -// -// return false; -// } -// -// //places the gem in the 2d array -// public void SetGem(int _X, int _Y, SC_Gem _Gem) -// { -// this.allGems[_X, _Y] = _Gem; -// } -// -// public SC_Gem GetGem(int _X,int _Y) -// { -// return this.allGems[_X, _Y]; -// } -// -// //MatchService -// public void FindAllMatches() -// { -// this.currentMatches.Clear(); -// -// for (int x = 0; x < this.width; x++) -// for (int y = 0; y < this.height; y++) -// { -// SC_Gem currentGem = this.allGems[x, y]; -// if (currentGem != null) -// { -// if (x > 0 && x < this.width - 1) -// { -// SC_Gem leftGem = this.allGems[x - 1, y]; -// SC_Gem rightGem = this.allGems[x + 1, y]; -// //checking no empty spots -// if (leftGem != null && rightGem != null) -// { -// //Match -// if (leftGem.type == currentGem.type && rightGem.type == currentGem.type) -// { -// currentGem.isMatch = true; -// leftGem.isMatch = true; -// rightGem.isMatch = true; -// this.currentMatches.Add(currentGem); -// this.currentMatches.Add(leftGem); -// this.currentMatches.Add(rightGem); -// } -// } -// } -// -// if (y > 0 && y < this.height - 1) -// { -// SC_Gem aboveGem = this.allGems[x, y - 1]; -// SC_Gem bellowGem = this.allGems[x, y + 1]; -// //checking no empty spots -// if (aboveGem != null && bellowGem != null) -// { -// //Match -// if (aboveGem.type == currentGem.type && bellowGem.type == currentGem.type) -// { -// currentGem.isMatch = true; -// aboveGem.isMatch = true; -// bellowGem.isMatch = true; -// this.currentMatches.Add(currentGem); -// this.currentMatches.Add(aboveGem); -// this.currentMatches.Add(bellowGem); -// } -// } -// } -// } -// } -// -// if (this.currentMatches.Count > 0) this.currentMatches = this.currentMatches.Distinct().ToList(); -// -// CheckForBombs(); -// } -// -// public void CheckForBombs() -// { -// for (int i = 0; i < this.currentMatches.Count; i++) -// { -// SC_Gem gem = this.currentMatches[i]; -// int x = gem.posIndex.x; -// int y = gem.posIndex.y; -// -// if (gem.posIndex.x > 0) -// { -// if (this.allGems[x - 1, y] != null && this.allGems[x - 1, y].type == GemType.Bomb) -// MarkBombArea(new Vector2Int(x - 1, y), this.allGems[x - 1, y].blastSize); -// } -// -// if (gem.posIndex.x + 1 < this.width) -// { -// if (this.allGems[x + 1, y] != null && this.allGems[x + 1, y].type == GemType.Bomb) -// MarkBombArea(new Vector2Int(x + 1, y), this.allGems[x + 1, y].blastSize); -// } -// -// if (gem.posIndex.y > 0) -// { -// if (this.allGems[x, y - 1] != null && this.allGems[x, y - 1].type == GemType.Bomb) -// MarkBombArea(new Vector2Int(x, y - 1), this.allGems[x, y - 1].blastSize); -// } -// -// if (gem.posIndex.y + 1 < this.height) -// { -// if (this.allGems[x, y + 1] != null && this.allGems[x, y + 1].type == GemType.Bomb) -// MarkBombArea(new Vector2Int(x, y + 1), this.allGems[x, y + 1].blastSize); -// } -// } -// } -// -// public void MarkBombArea(Vector2Int bombPos, int _BlastSize) -// { -// string _print = ""; -// for (int x = bombPos.x - _BlastSize; x <= bombPos.x + _BlastSize; x++) -// { -// for (int y = bombPos.y - _BlastSize; y <= bombPos.y + _BlastSize; y++) -// { -// if (x >= 0 && x < this.width && y >= 0 && y < this.height) -// { -// if (this.allGems[x, y] != null) -// { -// _print += "(" + x + "," + y + ")" + System.Environment.NewLine; -// this.allGems[x, y].isMatch = true; -// this.currentMatches.Add(this.allGems[x, y]); -// } -// } -// } -// } -// -// this.currentMatches = this.currentMatches.Distinct().ToList(); -// } -// } -// diff --git a/Assets/Scripts/GameBoard.cs.meta b/Assets/Scripts/GameBoard.cs.meta deleted file mode 100644 index c6830de..0000000 --- a/Assets/Scripts/GameBoard.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 05610b3b492499a49bd43c2cc8260ab1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Models/Gem.cs b/Assets/Scripts/Models/Gem.cs index 8289e44..7d6788b 100644 --- a/Assets/Scripts/Models/Gem.cs +++ b/Assets/Scripts/Models/Gem.cs @@ -14,9 +14,10 @@ namespace Services { public bool isMatch = false; - public Gem(GemType type, Vector2Int position) { + public Gem(GemType type, Vector2Int position, int scoreValue) { this.type = type; this.position = position; + this.scoreValue = scoreValue; } public void SetPosition(Vector2Int position) { diff --git a/Assets/Scripts/Presenter/ScorePresenter.cs b/Assets/Scripts/Presenter/ScorePresenter.cs new file mode 100644 index 0000000..579c5bf --- /dev/null +++ b/Assets/Scripts/Presenter/ScorePresenter.cs @@ -0,0 +1,30 @@ +using System; +using Services.Interfaces; +using VContainer.Unity; +using Views; + +namespace Presenter { + public class ScorePresenter : IDisposable{ + private IScoreService scoreService; + private ScoreView scoreView; + public ScorePresenter(IScoreService scoreService, ScoreView scoreView) { + this.scoreService = scoreService; + this.scoreView = scoreView; + + this.scoreService.OnScoreChanged += OnScoreChanged; + this.scoreView.SetScore(this.scoreService.Score); + } + + public void Tick() { + this.scoreView.UpdateScore(); + } + + private void OnScoreChanged(int score) { + this.scoreView.SetScore(score); + } + + public void Dispose() { + this.scoreService.OnScoreChanged -= OnScoreChanged; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Presenter/ScorePresenter.cs.meta b/Assets/Scripts/Presenter/ScorePresenter.cs.meta new file mode 100644 index 0000000..690ff59 --- /dev/null +++ b/Assets/Scripts/Presenter/ScorePresenter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c48fea73cb7c491295823cea6675795d +timeCreated: 1765733852 \ No newline at end of file diff --git a/Assets/Scripts/SC_GameLogic.cs b/Assets/Scripts/SC_GameLogic.cs deleted file mode 100644 index 01a755e..0000000 --- a/Assets/Scripts/SC_GameLogic.cs +++ /dev/null @@ -1,225 +0,0 @@ -// using System.Collections; -// using System.Collections.Generic; -// using TMPro; -// using UnityEngine; -// using Enums; -// -// public class SC_GameLogic : MonoBehaviour -// { -// private Dictionary unityObjects; //this is a dictionary of all game objects in the scene -// private int score = 0; //current score -// private float displayScore = 0; //for animation, i think -// private GameBoard gameBoard; //game board object -// private GameState currentState = GameState.Move; //current game state -// public GameState CurrentState { get { return this.currentState; } } -// -// #region MonoBehaviour -// private void Awake() -// { -// Init(); -// } -// -// private void Start() -// { -// StartGame(); -// } -// -// private void Update() -// { -// this.displayScore = Mathf.Lerp(this.displayScore, this.gameBoard.Score, SC_GameVariables.Instance.scoreSpeed * Time.deltaTime); -// this.unityObjects["Txt_Score"].GetComponent().text = this.displayScore.ToString("0"); -// } -// #endregion -// -// #region Logic -// private void Init() -// { -// this.unityObjects = new Dictionary(); -// GameObject[] _obj = GameObject.FindGameObjectsWithTag("UnityObject"); -// foreach (GameObject g in _obj) this.unityObjects.Add(g.name,g); -// -// this.gameBoard = new GameBoard(7, 7); -// Setup(); -// } -// -// //GameBoardService -// private void Setup() -// { -// for (int x = 0; x < this.gameBoard.Width; x++) -// for (int y = 0; y < this.gameBoard.Height; y++) -// { -// Vector2 _pos = new Vector2(x, y); -// GameObject _bgTile = Instantiate(SC_GameVariables.Instance.bgTilePrefabs, _pos, Quaternion.identity); -// _bgTile.transform.SetParent(this.unityObjects["GemsHolder"].transform); -// _bgTile.name = "BG Tile - " + x + ", " + y; -// -// int _gemToUse = Random.Range(0, SC_GameVariables.Instance.gems.Length); -// -// int iterations = 0; -// while (this.gameBoard.MatchesAt(new Vector2Int(x, y), SC_GameVariables.Instance.gems[_gemToUse]) && iterations < 100) -// { -// _gemToUse = Random.Range(0, SC_GameVariables.Instance.gems.Length); -// iterations++; -// } -// SpawnGem(new Vector2Int(x, y), SC_GameVariables.Instance.gems[_gemToUse]); -// } -// } -// public void StartGame() -// { -// this.unityObjects["Txt_Score"].GetComponent().text = this.score.ToString("0"); -// } -// -// //GameBoardService -// private void SpawnGem(Vector2Int _Position, SC_Gem _GemToSpawn) -// { -// if (Random.Range(0, 100f) < SC_GameVariables.Instance.bombChance) -// _GemToSpawn = SC_GameVariables.Instance.bomb; -// -// SC_Gem _gem = Instantiate(_GemToSpawn, new Vector3(_Position.x, _Position.y + SC_GameVariables.Instance.dropHeight, 0f), Quaternion.identity); -// _gem.transform.SetParent(this.unityObjects["GemsHolder"].transform); -// _gem.name = "Gem - " + _Position.x + ", " + _Position.y; -// this.gameBoard.SetGem(_Position.x,_Position.y, _gem); -// _gem.SetupGem(this,_Position); -// } -// -// //GameBoardService -// public void SetGem(int _X,int _Y, SC_Gem _Gem) -// { -// this.gameBoard.SetGem(_X,_Y, _Gem); -// } -// -// //GameBoardService -// public SC_Gem GetGem(int _X, int _Y) -// { -// return this.gameBoard.GetGem(_X, _Y); -// } -// public void SetState(GameState _CurrentState) -// { -// this.currentState = _CurrentState; -// } -// -// //GameBoardService -// public void DestroyMatches() -// { -// for (int i = 0; i < this.gameBoard.CurrentMatches.Count; i++) -// if (this.gameBoard.CurrentMatches[i] != null) -// { -// ScoreCheck(this.gameBoard.CurrentMatches[i]); -// DestroyMatchedGemsAt(this.gameBoard.CurrentMatches[i].posIndex); -// } -// -// StartCoroutine(DecreaseRowCo()); -// } -// -// //I think, this moves gems down after a match? -// //MoveGemsDown -// //GameBoardService -// private IEnumerator DecreaseRowCo() -// { -// yield return new WaitForSeconds(.2f); -// -// int nullCounter = 0; -// for (int x = 0; x < this.gameBoard.Width; x++) -// { -// for (int y = 0; y < this.gameBoard.Height; y++) -// { -// SC_Gem _curGem = this.gameBoard.GetGem(x, y); -// if (_curGem == null) -// { -// nullCounter++; -// } -// else if (nullCounter > 0) -// { -// _curGem.posIndex.y -= nullCounter; -// SetGem(x, y - nullCounter, _curGem); -// SetGem(x, y, null); -// } -// } -// nullCounter = 0; -// } -// -// StartCoroutine(FilledBoardCo()); -// } -// -// //IScoreService -// public void ScoreCheck(SC_Gem gemToCheck) -// { -// this.gameBoard.Score += gemToCheck.scoreValue; -// } -// -// //GameBoardService - DestroyMatchedGems -// private void DestroyMatchedGemsAt(Vector2Int _Pos) -// { -// SC_Gem _curGem = this.gameBoard.GetGem(_Pos.x,_Pos.y); -// if (_curGem != null) -// { -// Instantiate(_curGem.destroyEffect, new Vector2(_Pos.x, _Pos.y), Quaternion.identity); -// -// Destroy(_curGem.gameObject); -// SetGem(_Pos.x,_Pos.y, null); -// } -// } -// -// //GameBoardService - FillBoard -// -// private IEnumerator FilledBoardCo() -// { -// yield return new WaitForSeconds(0.5f); -// RefillBoard(); -// yield return new WaitForSeconds(0.5f); -// this.gameBoard.FindAllMatches(); -// if (this.gameBoard.CurrentMatches.Count > 0) -// { -// yield return new WaitForSeconds(0.5f); -// DestroyMatches(); -// } -// else -// { -// yield return new WaitForSeconds(0.5f); -// this.currentState = GameState.Move; -// } -// } -// -// //GameBoardService -// private void RefillBoard() -// { -// for (int x = 0; x < this.gameBoard.Width; x++) -// { -// for (int y = 0; y < this.gameBoard.Height; y++) -// { -// SC_Gem _curGem = this.gameBoard.GetGem(x,y); -// if (_curGem == null) -// { -// int gemToUse = Random.Range(0, SC_GameVariables.Instance.gems.Length); -// SpawnGem(new Vector2Int(x, y), SC_GameVariables.Instance.gems[gemToUse]); -// } -// } -// } -// CheckMisplacedGems(); -// } -// //gets all gem game objects -// //if the gem is not in GameBoard, destroy it -// private void CheckMisplacedGems() -// { -// List foundGems = new List(); -// foundGems.AddRange(FindObjectsOfType()); -// for (int x = 0; x < this.gameBoard.Width; x++) -// { -// for (int y = 0; y < this.gameBoard.Height; y++) -// { -// SC_Gem _curGem = this.gameBoard.GetGem(x, y); -// if (foundGems.Contains(_curGem)) -// foundGems.Remove(_curGem); -// } -// } -// -// foreach (SC_Gem g in foundGems) -// Destroy(g.gameObject); -// } -// public void FindAllMatches() -// { -// this.gameBoard.FindAllMatches(); -// } -// -// #endregion -// } diff --git a/Assets/Scripts/SC_GameLogic.cs.meta b/Assets/Scripts/SC_GameLogic.cs.meta deleted file mode 100644 index c061d6b..0000000 --- a/Assets/Scripts/SC_GameLogic.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: db185b18195d3814aab9ba4ac5f95047 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/SC_GameVariables.cs b/Assets/Scripts/SC_GameVariables.cs deleted file mode 100644 index 18eca71..0000000 --- a/Assets/Scripts/SC_GameVariables.cs +++ /dev/null @@ -1,38 +0,0 @@ -// using System.Collections; -// using System.Collections.Generic; -// using UnityEngine; -// -// //Done -// //This is basically just the settings, this can maybe be a ScriptableObject -// public class SC_GameVariables : MonoBehaviour -// { -// public GameObject bgTilePrefabs; -// public SC_Gem bomb; -// public SC_Gem[] gems; -// public float bonusAmount = 0.5f; -// public float bombChance = 2f; -// public int dropHeight = 0; -// public float gemSpeed; -// public float scoreSpeed = 5; -// -// [HideInInspector] -// public int rowsSize = 7; -// [HideInInspector] -// public int colsSize = 7; -// -// #region Singleton -// -// static SC_GameVariables instance; -// public static SC_GameVariables Instance -// { -// get -// { -// if (instance == null) -// instance = GameObject.Find("SC_GameVariables").GetComponent(); -// -// return instance; -// } -// } -// -// #endregion -// } diff --git a/Assets/Scripts/SC_GameVariables.cs.meta b/Assets/Scripts/SC_GameVariables.cs.meta deleted file mode 100644 index 60c61bc..0000000 --- a/Assets/Scripts/SC_GameVariables.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3b3b891bcdf21df4ab82ea34c3afc99e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/SC_Gem.cs b/Assets/Scripts/SC_Gem.cs deleted file mode 100644 index 8bf17f2..0000000 --- a/Assets/Scripts/SC_Gem.cs +++ /dev/null @@ -1,136 +0,0 @@ -// using System.Collections; -// using System.Collections.Generic; -// using Enums; -// using UnityEngine; -// -// //This is a SINGULAR gem -// public class SC_Gem : MonoBehaviour -// { -// [HideInInspector] -// public Vector2Int posIndex; -// -// private Vector2 firstTouchPosition; -// private Vector2 finalTouchPosition; -// private bool mousePressed; -// private float swipeAngle = 0; -// private SC_Gem otherGem; -// -// public GemType type; -// public bool isMatch = false; -// private Vector2Int previousPos; -// public GameObject destroyEffect; -// public int scoreValue = 10; -// -// public int blastSize = 1; -// private SC_GameLogic scGameLogic; -// -// void Update() -// { -// //if the current position doesnt match the index in GameBoard, animate it to move to the correct position -// //else, we update the GameBoard with the current possition, do we need this every frame???? -// if (Vector2.Distance(this.transform.position, this.posIndex) > 0.01f) -// this.transform.position = Vector2.Lerp(this.transform.position, this.posIndex, SC_GameVariables.Instance.gemSpeed * Time.deltaTime); -// else -// { -// this.transform.position = new Vector3(this.posIndex.x, this.posIndex.y, 0); -// this.scGameLogic.SetGem(this.posIndex.x, this.posIndex.y, this); -// } -// -// if (this.mousePressed && Input.GetMouseButtonUp(0)) -// { -// this.mousePressed = false; -// if (this.scGameLogic.CurrentState == GameState.Move) -// { -// this.finalTouchPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); -// CalculateAngle(); -// } -// } -// } -// -// public void SetupGem(SC_GameLogic _ScGameLogic,Vector2Int _Position) -// { -// this.posIndex = _Position; -// this.scGameLogic = _ScGameLogic; -// } -// -// private void OnMouseDown() -// { -// if (this.scGameLogic.CurrentState == GameState.Move) -// { -// this.firstTouchPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); -// this.mousePressed = true; -// } -// } -// -// private void CalculateAngle() -// { -// this.swipeAngle = Mathf.Atan2(this.finalTouchPosition.y - this.firstTouchPosition.y, this.finalTouchPosition.x - this.firstTouchPosition.x); -// this.swipeAngle = this.swipeAngle * 180 / Mathf.PI; -// -// if (Vector3.Distance(this.firstTouchPosition, this.finalTouchPosition) > .5f) -// MovePieces(); -// } -// -// private void MovePieces() -// { -// this.previousPos = this.posIndex; -// -// if (this.swipeAngle < 45 && this.swipeAngle > -45 && this.posIndex.x < SC_GameVariables.Instance.rowsSize - 1) -// { -// this.otherGem = this.scGameLogic.GetGem(this.posIndex.x + 1, this.posIndex.y); -// this.otherGem.posIndex.x--; -// this.posIndex.x++; -// -// } -// else if (this.swipeAngle > 45 && this.swipeAngle <= 135 && this.posIndex.y < SC_GameVariables.Instance.colsSize - 1) -// { -// this.otherGem = this.scGameLogic.GetGem(this.posIndex.x, this.posIndex.y + 1); -// this.otherGem.posIndex.y--; -// this.posIndex.y++; -// } -// else if (this.swipeAngle < -45 && this.swipeAngle >= -135 && this.posIndex.y > 0) -// { -// this.otherGem = this.scGameLogic.GetGem(this.posIndex.x, this.posIndex.y - 1); -// this.otherGem.posIndex.y++; -// this.posIndex.y--; -// } -// else if (this.swipeAngle > 135 || this.swipeAngle < -135 && this.posIndex.x > 0) -// { -// this.otherGem = this.scGameLogic.GetGem(this.posIndex.x - 1, this.posIndex.y); -// this.otherGem.posIndex.x++; -// this.posIndex.x--; -// } -// -// this.scGameLogic.SetGem(this.posIndex.x, this.posIndex.y, this); -// this.scGameLogic.SetGem(this.otherGem.posIndex.x, this.otherGem.posIndex.y, this.otherGem); -// -// StartCoroutine(CheckMoveCo()); -// } -// -// public IEnumerator CheckMoveCo() -// { -// this.scGameLogic.SetState(GameState.Wait); -// -// yield return new WaitForSeconds(.5f); -// this.scGameLogic.FindAllMatches(); -// -// if (this.otherGem != null) -// { -// if (this.isMatch == false && this.otherGem.isMatch == false) -// { -// this.otherGem.posIndex = this.posIndex; -// this.posIndex = this.previousPos; -// -// this.scGameLogic.SetGem(this.posIndex.x, this.posIndex.y, this); -// this.scGameLogic.SetGem(this.otherGem.posIndex.x, this.otherGem.posIndex.y, this.otherGem); -// -// yield return new WaitForSeconds(.5f); -// this.scGameLogic.SetState(GameState.Move); -// } -// else -// { -// this.scGameLogic.DestroyMatches(); -// } -// } -// } -// } diff --git a/Assets/Scripts/SC_Gem.cs.meta b/Assets/Scripts/SC_Gem.cs.meta deleted file mode 100644 index bc8671f..0000000 --- a/Assets/Scripts/SC_Gem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c5d1e13e319aa044e9776fbd351e9d03 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Scopes/LevelLifetimeScope.cs b/Assets/Scripts/Scopes/LevelLifetimeScope.cs index 7432a41..55a10f0 100644 --- a/Assets/Scripts/Scopes/LevelLifetimeScope.cs +++ b/Assets/Scripts/Scopes/LevelLifetimeScope.cs @@ -21,6 +21,8 @@ namespace Scopes builder.RegisterInstance(this.gameVariables); builder.RegisterInstance(this.gemsHolder); + builder.RegisterComponentInHierarchy(); + builder.Register(c => new GameBoard(this.gameVariables.width, this.gameVariables.height), Lifetime.Scoped); @@ -32,8 +34,9 @@ namespace Scopes new ObjectPoolService(this.gameVariables.gemsPrefabs, this.gemsHolder), Lifetime.Scoped); + builder.Register(Lifetime.Scoped); builder.Register(Lifetime.Scoped).AsImplementedInterfaces(); - + builder.RegisterEntryPoint(); } } diff --git a/Assets/Scripts/ScriptableObjects/GameVariables.cs b/Assets/Scripts/ScriptableObjects/GameVariables.cs index 10d3073..b0bf637 100644 --- a/Assets/Scripts/ScriptableObjects/GameVariables.cs +++ b/Assets/Scripts/ScriptableObjects/GameVariables.cs @@ -1,5 +1,7 @@ +using System; using System.Collections.Generic; using Enums; +using Structs; using UnityEngine; using Views; @@ -8,9 +10,7 @@ namespace ScriptableObjects { [CreateAssetMenu(fileName = "GameVariables", menuName = "Game Variables")] public class GameVariables : ScriptableObject { public GameObject bgTilePrefabs; - public GemView bombPrefab; - public GemView[] gemsPrefabs; - public GameObject[] destroyEffectPrefabs; + public GemTypeValues[] gemsPrefabs; public float bonusAmount = 0.5f; public float bombChance = 2f; public int dropHeight = 1; diff --git a/Assets/Scripts/Services/GameBoardService.cs b/Assets/Scripts/Services/GameBoardService.cs index bdfc4a1..e3e34e8 100644 --- a/Assets/Scripts/Services/GameBoardService.cs +++ b/Assets/Scripts/Services/GameBoardService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Cysharp.Threading.Tasks; @@ -14,31 +15,38 @@ using Object = UnityEngine.Object; using Random = UnityEngine.Random; namespace Services { - public class GameBoardService : IGameBoardService, ITickable { + public class GameBoardService : IGameBoardService, ITickable, IDisposable { + #region Inject private readonly IGameBoard gameBoard; private readonly GameVariables gameVariables; private readonly IMatchService matchService; private readonly IScoreService scoreService; private readonly IObjectPool objectPool; private readonly Transform gemsHolder; + #endregion + #region Variables private readonly List gemPresenters = new List(); + private readonly ScorePresenter scorePresenter; + private GameState currentState = GameState.Move; + #endregion - public GameBoardService(IGameBoard gameBoard, GameVariables gameVariables, IMatchService matchService, IScoreService scoreSerivce, IObjectPool objectPool, Transform gemsHolder) { + public GameBoardService(IGameBoard gameBoard, GameVariables gameVariables, IMatchService matchService, IScoreService scoreSerivce, IObjectPool objectPool, Transform gemsHolder, ScorePresenter scorePresenter) { this.gameBoard = gameBoard; this.gameVariables = gameVariables; this.matchService = matchService; this.scoreService = scoreSerivce; this.objectPool = objectPool; this.gemsHolder = gemsHolder; + this.scorePresenter = scorePresenter; } public void Tick() { - int i = 0; foreach (GemPresenter gemPresenter in gemPresenters) { gemPresenter.Tick(); - i++; } + + this.scorePresenter.Tick(); } //Instantiates background tiles and calls SpawnGems @@ -61,18 +69,20 @@ namespace Services { iterations++; } - SpawnGem(new Vector2Int(x, y), this.gameVariables.gemsPrefabs[gemToUse], (GemType)gemToUse); + SpawnGem(new Vector2Int(x, y), (GemType)gemToUse); } + + this.currentState = GameState.Move; } //Uses the ObjectPool to spawn a gem at the given position - public void SpawnGem(Vector2Int position, GemView gemPrefab, GemType gemType) { + private void SpawnGem(Vector2Int position, GemType gemType) { if (Random.Range(0, 100f) < this.gameVariables.bombChance) - gemPrefab = this.gameVariables.bombPrefab; + gemType = GemType.Bomb; GemView gemView = this.objectPool.Get(gemType, position, this.gameVariables.dropHeight); - gemView.name = "Gem - " + position.x + ", " + position.y; - Gem gem = new Gem(gemType, position); + gemView.name = "Gem - " + position.x + ", " + position.y + ' ' + gemType; + Gem gem = new Gem(gemType, position, 50); gemView.Bind(gem); this.gemPresenters.Add(new GemPresenter(gem, gemView)); @@ -80,17 +90,65 @@ namespace Services { } //Sets the gem on the GameBoard - public void SetGem(Vector2Int position, Gem gem) { + private void SetGem(Vector2Int position, Gem gem) { this.gameBoard.SetGemAt(new Vector2Int(position.x, position.y), gem); } //Gets the gem from the GameBoard - public Gem GetGem(Vector2Int position) { + private Gem GetGem(Vector2Int position) { return this.gameBoard.GetGemAt(position); } + + //Listens to InputService OnSwapRequest + public async UniTask TrySwap(Vector2Int from, Vector2Int to) { + if (this.currentState != GameState.Move) + return false; + + if (!InBounds(from) || !InBounds(to)) + return false; + + if (!AreAdjacentCardinal(from, to)) + return false; + + Gem fromGem = GetGem(from); + Gem toGem = GetGem(to); + + if(fromGem == null || toGem == null) + return false; + + this.currentState = GameState.Wait; + + ApplySwap(from, to, fromGem, toGem); + + await UniTask.Delay(600); + + this.matchService.FindAllMatches(); + bool hasMatch = this.matchService.CurrentMatches.Count > 0; + + if (!hasMatch) { + ApplySwap(to, from, fromGem, toGem); + await UniTask.Delay(600); + this.currentState = GameState.Move; + return false; + } + + DestroyMatches(); + this.currentState = GameState.Move; + return true; + } + + private void ApplySwap(Vector2Int posA, Vector2Int posB, Gem gemA, Gem gemB) { + // swap their stored positions + gemA.SetPosition(posB); + gemB.SetPosition(posA); + + // update grid + SetGem(posA, gemB); + SetGem(posB, gemA); + } //If there are matches, destroys them and moves the gems down - public void DestroyMatches() { + private void DestroyMatches() { for (int i = 0; i < this.matchService.CurrentMatches.Count; i++) if (this.matchService.CurrentMatches[i] != null) { @@ -101,8 +159,8 @@ namespace Services { MoveGemsDown(); } - public async UniTask MoveGemsDown() { - await UniTask.Delay(2); + private async UniTask MoveGemsDown() { + await UniTask.Delay(50); int nullCounter = 0; for (int x = 0; x < this.gameBoard.Width; x++) @@ -127,7 +185,7 @@ namespace Services { await FillBoard(); } - public async UniTask FillBoard() { + private async UniTask FillBoard() { await UniTask.Delay(5); RefillBoard(); await UniTask.Delay(5); @@ -140,11 +198,11 @@ namespace Services { else { await UniTask.Delay(5); - // currentState = GameState.Move; + this.currentState = GameState.Move; } } - public void RefillBoard() { + private void RefillBoard() { for (int x = 0; x < this.gameBoard.Width; x++) { for (int y = 0; y < this.gameBoard.Height; y++) @@ -152,39 +210,13 @@ namespace Services { Gem currentGem = this.gameBoard.GetGemAt(new Vector2Int(x,y)); if (currentGem == null) { int gemToUse = RandomUtils.RandomGemTypeAsInt(); - SpawnGem(new Vector2Int(x, y), this.gameVariables.gemsPrefabs[gemToUse], (GemType)gemToUse); + SpawnGem(new Vector2Int(x, y), (GemType)gemToUse); } } } - CheckMisplacedGems(); } - //Checks if there are gems that are not in the board - public void CheckMisplacedGems() { - List gemsViews = GemsViews(); - - for (int x = 0; x < this.gameBoard.Width; x++) - { - for (int y = 0; y < this.gameBoard.Height; y++) - { - Gem currentGem = this.gameBoard.GetGemAt(new Vector2Int(x,y)); - GemView gemView = gemsViews.FirstOrDefault(gv => gv.Gem == currentGem); - - if (gemView != null) { - gemsViews.Remove(gemView); - } - } - } - - foreach (GemView g in gemsViews) { - RemovePresenterFor(g); - - g.Unbind(); - this.objectPool.Release(g); - } - } - - public void DestroyMatchedGems(Vector2Int position) { + private void DestroyMatchedGems(Vector2Int position) { List gemsViews = GemsViews(); Gem currentGem = this.gameBoard.GetGemAt(position); if (currentGem != null) @@ -194,30 +226,38 @@ namespace Services { return; } - //ToDo: Destroy effect - if(this.gameVariables.destroyEffectPrefabs.Length > 0) - Object.Instantiate(this.gameVariables.destroyEffectPrefabs[(int)currentGem.Type], new Vector2(position.x, position.y), Quaternion.identity); - - RemovePresenterFor(gemView); - gemView.Unbind(); this.objectPool.Release(gemView); + RemovePresenterFor(gemView); SetGem(position, null); } } + #region Utils private void RemovePresenterFor(GemView gemView) { if (gemView is null) { return; } - - List presentersToRemove = this.gemPresenters.Where(p => p.GemView == gemView).ToList(); - foreach (GemPresenter presenter in presentersToRemove) { - this.gemPresenters.Remove(presenter); - } + + GemPresenter presenter = this.gemPresenters.FirstOrDefault(p => p.GemView == gemView); + this.gemPresenters.Remove(presenter); } private List GemsViews() { return this.gemsHolder.GetComponentsInChildren().ToList(); } + + private bool InBounds(Vector2Int p) { + return p.x >= 0 && p.x < this.gameBoard.Width && p.y >= 0 && p.y < this.gameBoard.Height; + } + + private static bool AreAdjacentCardinal(Vector2Int a, Vector2Int b) { + Vector2Int d = b - a; + return (Mathf.Abs(d.x) == 1 && d.y == 0) || (Mathf.Abs(d.y) == 1 && d.x == 0); + } + #endregion + + public void Dispose() { + this.objectPool.Clear(); + } } } \ No newline at end of file diff --git a/Assets/Scripts/Services/InputService.cs b/Assets/Scripts/Services/InputService.cs index 83c3766..9d802de 100644 --- a/Assets/Scripts/Services/InputService.cs +++ b/Assets/Scripts/Services/InputService.cs @@ -5,31 +5,105 @@ using UnityEngine; namespace Services { public class InputService : MonoBehaviour, IInputService { - public event Action OnPointerDown; - public event Action OnPointerUp; + public event Action OnSwapRequested; - private bool wasDown; + private Camera inputCamera; + private Vector2 pointerDownScreenPos; + private bool isPointerDown; + + private readonly Vector2 boardOrigin = Vector2.zero; + private readonly float minDrag = 0.35f; + private readonly float cellSize = 1f; + + private void Awake() { + this.inputCamera = Camera.main; + } private void Update() { - // Mouse - var isDown = Input.GetMouseButton(0); - if (!wasDown && isDown) - OnPointerDown?.Invoke(Input.mousePosition); + if (TryGetPrimaryPointerState(out var isDown, out var screenPos)) + { + if (!this.isPointerDown && isDown) { + pointerDownScreenPos = screenPos; + } - if (wasDown && !isDown) - OnPointerUp?.Invoke(Input.mousePosition); + if (this.isPointerDown && !isDown) { + TryEmitSwap(pointerDownScreenPos, screenPos); + } - wasDown = isDown; + this.isPointerDown = isDown; + } + else + { + // No pointer available this frame (rare). Ensure we don't get stuck. + this.isPointerDown = false; + } + } - // Optional: Touch (if you want both, you can merge logic more carefully) - if (Input.touchCount <= 0) return; + private void TryEmitSwap(Vector2 downScreen, Vector2 upScreen) + { + if (this.inputCamera == null) return; - var t = Input.GetTouch(0); - if (t.phase == TouchPhase.Began) - OnPointerDown?.Invoke(t.position); - else if (t.phase == TouchPhase.Ended || t.phase == TouchPhase.Canceled) - OnPointerUp?.Invoke(t.position); + Vector2 downWorld = this.inputCamera.ScreenToWorldPoint(downScreen); + Vector2 upWorld = this.inputCamera.ScreenToWorldPoint(upScreen); + Vector2 dragWorld = upWorld - downWorld; + + if (dragWorld.magnitude < this.minDrag) + return; + + Vector2Int fromCell = WorldToCell(downWorld); + Vector2Int dir = DragToCardinalDirection(dragWorld); + Vector2Int toCell = fromCell + dir; + + // Adjacency is guaranteed by construction (to = from + dir). + // Debug.Log($"Swap {fromCell} -> {toCell}"); + OnSwapRequested?.Invoke(fromCell, toCell); + } + + private Vector2Int WorldToCell(Vector2 worldPos) + { + // Convert to board-local coords first + Vector2 local = (worldPos - this.boardOrigin) / this.cellSize; + + // Assumes cell centers lie on integer coordinates in local space + int x = Mathf.FloorToInt(local.x + 0.5f); + int y = Mathf.FloorToInt(local.y + 0.5f); + + return new Vector2Int(x, y); + } + + private static Vector2Int DragToCardinalDirection(Vector2 dragWorld) + { + if (Mathf.Abs(dragWorld.x) >= Mathf.Abs(dragWorld.y)) + return dragWorld.x >= 0 ? Vector2Int.right : Vector2Int.left; + + return dragWorld.y >= 0 ? Vector2Int.up : Vector2Int.down; + } + + private static bool TryGetPrimaryPointerState(out bool isDown, out Vector2 screenPosition) + { + // Prefer touch when present (mobile) + if (Input.touchCount > 0) + { + Touch touch = Input.GetTouch(0); + screenPosition = touch.position; + + // Treat "Moved/Stationary" as still down, and "Began" as down. + isDown = touch.phase == TouchPhase.Began + || touch.phase == TouchPhase.Moved + || touch.phase == TouchPhase.Stationary; + + // Ended/Canceled => not down (we still report position) + if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled) + isDown = false; + + return true; + } + + // Fallback to mouse (editor/desktop) + screenPosition = Input.mousePosition; + isDown = Input.GetMouseButton(0); + return true; } } } \ No newline at end of file diff --git a/Assets/Scripts/Services/Interfaces/IGameBoardService.cs b/Assets/Scripts/Services/Interfaces/IGameBoardService.cs index 0d19414..1b8f724 100644 --- a/Assets/Scripts/Services/Interfaces/IGameBoardService.cs +++ b/Assets/Scripts/Services/Interfaces/IGameBoardService.cs @@ -6,14 +6,7 @@ using Views; namespace Services.Interfaces { public interface IGameBoardService { void Setup(); - void SpawnGem(Vector2Int position, GemView gemPrefab, GemType gemType); - void SetGem(Vector2Int position, Gem gem); - Gem GetGem(Vector2Int position); - void DestroyMatches(); - UniTask MoveGemsDown(); - UniTask FillBoard(); - void RefillBoard(); - void CheckMisplacedGems(); - void DestroyMatchedGems(Vector2Int position); + + UniTask TrySwap(Vector2Int from, Vector2Int to); } } \ No newline at end of file diff --git a/Assets/Scripts/Services/Interfaces/IInputService.cs b/Assets/Scripts/Services/Interfaces/IInputService.cs index 9f1c6ce..07cf9cb 100644 --- a/Assets/Scripts/Services/Interfaces/IInputService.cs +++ b/Assets/Scripts/Services/Interfaces/IInputService.cs @@ -3,7 +3,6 @@ using UnityEngine; namespace Services.Interfaces { public interface IInputService { - event Action OnPointerDown; - event Action OnPointerUp; + event Action OnSwapRequested; } } \ No newline at end of file diff --git a/Assets/Scripts/Services/Interfaces/IMatchService.cs b/Assets/Scripts/Services/Interfaces/IMatchService.cs index 717105f..cb59130 100644 --- a/Assets/Scripts/Services/Interfaces/IMatchService.cs +++ b/Assets/Scripts/Services/Interfaces/IMatchService.cs @@ -7,7 +7,6 @@ namespace Services.Interfaces { List CurrentMatches { get; } bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck); void FindAllMatches(); - void CheckForBombs(); void MarkBombArea(Vector2Int bombPosition, int blastSize); } } \ No newline at end of file diff --git a/Assets/Scripts/Services/Interfaces/IScoreService.cs b/Assets/Scripts/Services/Interfaces/IScoreService.cs index 89aa549..401de0d 100644 --- a/Assets/Scripts/Services/Interfaces/IScoreService.cs +++ b/Assets/Scripts/Services/Interfaces/IScoreService.cs @@ -1,5 +1,9 @@ +using System; + namespace Services.Interfaces { public interface IScoreService { + event Action OnScoreChanged; + int Score { get; } void ScoreCheck(int value); } } \ No newline at end of file diff --git a/Assets/Scripts/Services/LevelEntryPoint.cs b/Assets/Scripts/Services/LevelEntryPoint.cs index fb19533..4e56199 100644 --- a/Assets/Scripts/Services/LevelEntryPoint.cs +++ b/Assets/Scripts/Services/LevelEntryPoint.cs @@ -9,16 +9,23 @@ namespace Services { private readonly IObjectPool gemViewPool; private readonly IGameBoardService gameBoardService; - - public LevelEntryPoint(IObjectPool gemViewPool, IGameBoardService gameBoardService) + private readonly IInputService inputService; + + public LevelEntryPoint(IObjectPool gemViewPool, IGameBoardService gameBoardService, IInputService inputService) { this.gemViewPool = gemViewPool; this.gameBoardService = gameBoardService; + this.inputService = inputService; } public void Start() { this.gameBoardService.Setup(); + this.inputService.OnSwapRequested += HandleSwapRequest; + } + + private void HandleSwapRequest(Vector2Int from, Vector2Int to) { + this.gameBoardService.TrySwap(from, to); } } } \ No newline at end of file diff --git a/Assets/Scripts/Services/MatchService.cs b/Assets/Scripts/Services/MatchService.cs index 951efbe..24f1290 100644 --- a/Assets/Scripts/Services/MatchService.cs +++ b/Assets/Scripts/Services/MatchService.cs @@ -89,12 +89,61 @@ namespace Services { CheckForBombs(); } - public void CheckForBombs() { - throw new System.NotImplementedException(); + private void CheckForBombs() { + Gem[,] gems = this.gameBoard.GemsGrid; + int width = this.gameBoard.Width; + int height = this.gameBoard.Height; + + for (int i = 0; i < this.currentMatches.Count; i++) + { + Gem gem = this.currentMatches[i]; + int x = gem.Position.x; + int y = gem.Position.y; + + Vector2Int[] directions = + { + Vector2Int.left, + Vector2Int.right, + Vector2Int.down, + Vector2Int.up + }; + + foreach (Vector2Int direction in directions) + { + int newX = x + direction.x; + int newY = y + direction.y; + + if (newX < 0 || newX >= width || newY < 0 || newY >= height) + continue; + + Gem neighbor = gems[newX, newY]; + if (neighbor?.Type == GemType.Bomb) + MarkBombArea(new Vector2Int(newX, newY), 1); + } + } } public void MarkBombArea(Vector2Int bombPosition, int blastSize) { - throw new System.NotImplementedException(); + Gem[,] gems = this.gameBoard.GemsGrid; + int width = this.gameBoard.Width; + int height = this.gameBoard.Height; + + for (int x = bombPosition.x - blastSize; x <= bombPosition.x + blastSize; x++) + { + for (int y = bombPosition.y - blastSize; y <= bombPosition.y + blastSize; y++) + { + if (x >= 0 && x < width && y >= 0 && y < height) + { + if (gems[x, y] != null) + { + gems[x, y].isMatch = true; + this.currentMatches.Add(gems[x, y]); + } + } + } + } + + this.currentMatches = this.currentMatches.Distinct().ToList(); } } } \ No newline at end of file diff --git a/Assets/Scripts/Services/ObjectPoolService.cs b/Assets/Scripts/Services/ObjectPoolService.cs index 481ec79..ccf7393 100644 --- a/Assets/Scripts/Services/ObjectPoolService.cs +++ b/Assets/Scripts/Services/ObjectPoolService.cs @@ -1,50 +1,65 @@ using System.Collections.Generic; using Enums; using Services.Interfaces; +using Structs; using UnityEngine; +using Utils; using Views; using Object = UnityEngine.Object; namespace Services { public class ObjectPoolService:IObjectPool { - private readonly GemView[] prefabs; + private readonly GemTypeValues[] gemValues; private readonly Transform parent; - private readonly Stack pool = new Stack(); + private readonly Dictionary> gemTypeToPools = new Dictionary>(); - public ObjectPoolService(GemView[] prefabs, Transform parent) { - this.prefabs = prefabs; + public ObjectPoolService(GemTypeValues[] gemValues, Transform parent) { + this.gemValues = gemValues; this.parent = parent; } public GemView Get(GemType type, Vector2Int position, float dropHeight) { - int typeAsInt = (int) type; + if (!this.gemTypeToPools.ContainsKey(type)) { + this.gemTypeToPools.Add(type, new Stack()); + } GemView gemView; float randomOffset = Random.Range(1f, 2.5f); Vector2 vector2Position = new Vector2(position.x, position.y + dropHeight * randomOffset); - if (this.pool.Count > 0) { - gemView = this.pool.Pop(); + if (this.gemTypeToPools[type].Count > 0) { + gemView = this.gemTypeToPools[type].Pop(); gemView.transform.localPosition = vector2Position; return gemView; } - gemView = Object.Instantiate(this.prefabs[typeAsInt], vector2Position, Quaternion.identity, this.parent); + gemView = Object.Instantiate(GemUtils.GetGemValues(type, this.gemValues).gemPrefab, vector2Position, Quaternion.identity, this.parent); return gemView; } public void Release(GemView gemView) { - if (gemView == null) + if (gemView is null) return; + + Object.Instantiate(GemUtils.GetGemValues(gemView.Gem.Type, this.gemValues).explosionPrefab, gemView.transform.position, Quaternion.identity, this.parent); + + if (!this.gemTypeToPools.ContainsKey(gemView.Gem.Type)) { + this.gemTypeToPools.Add(gemView.Gem.Type, new Stack()); + } gemView.gameObject.SetActive(false); - this.pool.Push(gemView); + this.gemTypeToPools[gemView.Gem.Type].Push(gemView); + gemView.Unbind(); } public void Clear() { - this.pool.Clear(); + foreach (Stack pool in this.gemTypeToPools.Values) { + pool.Clear(); + } + + this.gemTypeToPools.Clear(); } } } \ No newline at end of file diff --git a/Assets/Scripts/Services/ScoreService.cs b/Assets/Scripts/Services/ScoreService.cs index 09af96a..35a20c7 100644 --- a/Assets/Scripts/Services/ScoreService.cs +++ b/Assets/Scripts/Services/ScoreService.cs @@ -1,11 +1,16 @@ using System; using Services.Interfaces; +using UnityEngine; namespace Services { public class ScoreService : IScoreService { private int score = 0; + public int Score => this.score; + public event Action OnScoreChanged; public void ScoreCheck(int value) { this.score += value; + + OnScoreChanged?.Invoke(this.score); } } } \ No newline at end of file diff --git a/Assets/Scripts/Structs.meta b/Assets/Scripts/Structs.meta new file mode 100644 index 0000000..a0f2a95 --- /dev/null +++ b/Assets/Scripts/Structs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f21342694e084644b1e157c9c4292b4f +timeCreated: 1765732834 \ No newline at end of file diff --git a/Assets/Scripts/Structs/GemTypeValues.cs b/Assets/Scripts/Structs/GemTypeValues.cs new file mode 100644 index 0000000..9b54b04 --- /dev/null +++ b/Assets/Scripts/Structs/GemTypeValues.cs @@ -0,0 +1,14 @@ +using System; +using Enums; +using UnityEngine; +using Views; + +namespace Structs { + [Serializable] + public struct GemTypeValues { + public GemType type; + public GemView gemPrefab; + public GameObject explosionPrefab; + public int scoreValue; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Structs/GemTypeValues.cs.meta b/Assets/Scripts/Structs/GemTypeValues.cs.meta new file mode 100644 index 0000000..b8c637c --- /dev/null +++ b/Assets/Scripts/Structs/GemTypeValues.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 91d203259e7b4e62843f564a556aca72 +timeCreated: 1765732844 \ No newline at end of file diff --git a/Assets/Scripts/Utils/GemUtils.cs b/Assets/Scripts/Utils/GemUtils.cs new file mode 100644 index 0000000..e5ad539 --- /dev/null +++ b/Assets/Scripts/Utils/GemUtils.cs @@ -0,0 +1,14 @@ +using Enums; +using Structs; + +namespace Utils { + public static class GemUtils { + public static GemTypeValues GetGemValues(GemType type, GemTypeValues[] gemValues) { + foreach (GemTypeValues gemValue in gemValues) { + if(gemValue.type == type) return gemValue; + } + + return default; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Utils/GemUtils.cs.meta b/Assets/Scripts/Utils/GemUtils.cs.meta new file mode 100644 index 0000000..2481863 --- /dev/null +++ b/Assets/Scripts/Utils/GemUtils.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 23b7012d4cd64b99be4170c1f0ea9fdb +timeCreated: 1765733100 \ No newline at end of file diff --git a/Assets/Scripts/Views/GemView.cs b/Assets/Scripts/Views/GemView.cs index b479354..d9a1c4c 100644 --- a/Assets/Scripts/Views/GemView.cs +++ b/Assets/Scripts/Views/GemView.cs @@ -37,7 +37,7 @@ namespace Views { return; if (Vector2.Distance(this.transform.position, positionBasedOnIndex.ToVector2()) > 0.01f) { - this.transform.position = Vector2.Lerp(this.transform.position, positionBasedOnIndex.ToVector2(), 0.01f); + this.transform.position = Vector2.Lerp(this.transform.position, positionBasedOnIndex.ToVector2(), 0.05f); } } } diff --git a/Assets/Scripts/Views/ScoreView.cs b/Assets/Scripts/Views/ScoreView.cs new file mode 100644 index 0000000..f33dbf4 --- /dev/null +++ b/Assets/Scripts/Views/ScoreView.cs @@ -0,0 +1,24 @@ +using System; +using TMPro; +using UnityEngine; + +namespace Views { + public class ScoreView : MonoBehaviour { + private TextMeshProUGUI scoreText; + private float displayScore = 0; + private int actualScore = 0; + + private void Awake() { + this.scoreText = GetComponentInChildren(); + } + + public void UpdateScore() { + this.displayScore = Mathf.Lerp(this.displayScore, this.actualScore, 5 * Time.deltaTime); + this.scoreText.text = this.displayScore.ToString("0"); + } + + public void SetScore(int score) { + this.actualScore = score; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Views/ScoreView.cs.meta b/Assets/Scripts/Views/ScoreView.cs.meta new file mode 100644 index 0000000..7f73981 --- /dev/null +++ b/Assets/Scripts/Views/ScoreView.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 86d965b2c3d5474082911bdd360847ce +timeCreated: 1765733889 \ No newline at end of file