using System; using Services.Interfaces; using UnityEngine; namespace Services { public class InputService : MonoBehaviour, IInputService { public event Action OnSwapRequested; 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() { if (TryGetPrimaryPointerState(out var isDown, out var screenPos)) { if (!this.isPointerDown && isDown) { pointerDownScreenPos = screenPos; } if (this.isPointerDown && !isDown) { TryEmitSwap(pointerDownScreenPos, screenPos); } this.isPointerDown = isDown; } else { // No pointer available this frame (rare). Ensure we don't get stuck. this.isPointerDown = false; } } private void TryEmitSwap(Vector2 downScreen, Vector2 upScreen) { if (this.inputCamera is null) return; 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; } } }