using Godot; using System; using Chessistics.Engine.Model; using Chessistics.Engine.Rules; using Chessistics.Scripts.Board; namespace Chessistics.Scripts.Input; public partial class InputMapper : Node { [Signal] public delegate void PlacementRequestedEventHandler(int kindIndex, int startCol, int startRow, int endCol, int endRow); [Signal] public delegate void RemovalRequestedEventHandler(int pieceId); [Signal] public delegate void CellClickedEventHandler(int col, int row); [Signal] public delegate void CancelledEventHandler(); [Signal] public delegate void RelocateRequestedEventHandler(int pieceId, int newStartCol, int newStartRow, int newEndCol, int newEndRow); public enum PlacementPhase { None, SelectingStart, SelectingEnd } private const float DragThreshold = 8f; private BoardView _boardView = null!; private PieceKind? _selectedKind; private Coords? _selectedStart; private PlacementPhase _phase = PlacementPhase.None; private BoardSnapshot? _snapshot; private Coords? _hoverCoords; // Drag & drop of a placed piece private int? _dragPieceId; private Vector2 _dragMouseStart; private bool _dragging; public PlacementPhase CurrentPhase => _phase; public void Initialize(BoardView boardView) { _boardView = boardView; } public void SetSnapshot(BoardSnapshot snapshot) { GD.Print($"[InputMapper] SetSnapshot called — null? {snapshot == null}"); _snapshot = snapshot; } public void SelectPieceKind(PieceKind kind) { GD.Print($"[InputMapper] SelectPieceKind: {kind}, phase → SelectingStart"); _selectedKind = kind; _selectedStart = null; _phase = PlacementPhase.SelectingStart; } public void Cancel() { _selectedKind = null; _selectedStart = null; _phase = PlacementPhase.None; _boardView.ClearHighlights(); EmitSignal(SignalName.Cancelled); } public override void _Process(double delta) { if (_boardView == null || !_boardView.Visible) return; var localPos = _boardView.GetLocalMousePosition(); var coords = _boardView.PixelToCoords(localPos); if (coords != _hoverCoords) { _hoverCoords = coords; _boardView.SetHoverCell(coords); } } public override void _UnhandledInput(InputEvent @event) { if (@event is InputEventMouseButton mouseEvent && mouseEvent.ButtonIndex == MouseButton.Left) { var localPos = _boardView.GetLocalMousePosition(); if (mouseEvent.Pressed) HandleLeftPress(localPos); else HandleLeftRelease(localPos); } if (@event is InputEventMouseMotion && _dragPieceId != null) { var localPos = _boardView.GetLocalMousePosition(); UpdateDrag(localPos); } if (@event is InputEventKey keyEvent && keyEvent.Pressed && keyEvent.Keycode == Key.Escape) { CancelDrag(); Cancel(); } } private void HandleLeftPress(Vector2 localPos) { // In placement mode, ignore drag behavior — click advances placement if (_phase != PlacementPhase.None) return; var coords = _boardView.PixelToCoords(localPos); if (coords == null || _snapshot == null) return; var piece = _snapshot.Pieces.FirstOrDefault( p => p.StartCell == coords.Value || p.EndCell == coords.Value); if (piece != null) { _dragPieceId = piece.Id; _dragMouseStart = localPos; _dragging = false; } } private void UpdateDrag(Vector2 localPos) { if (_dragPieceId == null) return; if (!_dragging && (localPos - _dragMouseStart).Length() > DragThreshold) { _dragging = true; HighlightLegalDropsFor(_dragPieceId.Value); } } private void HandleLeftRelease(Vector2 localPos) { if (_dragging && _dragPieceId != null) { var dropCoords = _boardView.PixelToCoords(localPos); TryRelocate(_dragPieceId.Value, dropCoords); CancelDrag(); return; } // Normal click flow CancelDrag(); HandleLeftClick(); } private void CancelDrag() { _dragPieceId = null; _dragging = false; _boardView.ClearHighlights(); } private void HighlightLegalDropsFor(int pieceId) { var legal = ComputeLegalDrops(pieceId); _boardView.ClearHighlights(); _boardView.HighlightCells(legal, new Color("#44FF88AA")); } private List ComputeLegalDrops(int pieceId) { var result = new List(); if (_snapshot == null) return result; var piece = _snapshot.Pieces.FirstOrDefault(p => p.Id == pieceId); if (piece == null) return result; var dc = piece.EndCell.Col - piece.StartCell.Col; var dr = piece.EndCell.Row - piece.StartCell.Row; var boardState = GetBoardStateForValidation(); if (boardState == null) return result; for (int c = 0; c < _snapshot.Width; c++) { for (int r = 0; r < _snapshot.Height; r++) { var newStart = new Coords(c, r); var newEnd = new Coords(c + dc, r + dr); if (!newEnd.IsOnBoard(_snapshot.Width, _snapshot.Height)) continue; if (_snapshot.Grid[newStart.Col, newStart.Row] == CellType.Wall) continue; if (_snapshot.Grid[newEnd.Col, newEnd.Row] == CellType.Wall) continue; if (!MoveValidator.IsLegalPlacement(piece.Kind, newStart, newEnd, boardState)) continue; result.Add(newStart); } } return result; } private void TryRelocate(int pieceId, Coords? dropCoords) { if (_snapshot == null || dropCoords == null) return; var piece = _snapshot.Pieces.FirstOrDefault(p => p.Id == pieceId); if (piece == null) return; var dc = piece.EndCell.Col - piece.StartCell.Col; var dr = piece.EndCell.Row - piece.StartCell.Row; var newStart = dropCoords.Value; var newEnd = new Coords(newStart.Col + dc, newStart.Row + dr); if (!newEnd.IsOnBoard(_snapshot.Width, _snapshot.Height)) return; if (_snapshot.Grid[newStart.Col, newStart.Row] == CellType.Wall) return; if (_snapshot.Grid[newEnd.Col, newEnd.Row] == CellType.Wall) return; var boardState = GetBoardStateForValidation(); if (boardState == null) return; if (!MoveValidator.IsLegalPlacement(piece.Kind, newStart, newEnd, boardState)) return; EmitSignal(SignalName.RelocateRequested, pieceId, newStart.Col, newStart.Row, newEnd.Col, newEnd.Row); } private void HandleLeftClick() { var localPos = _boardView.GetLocalMousePosition(); var coords = _boardView.PixelToCoords(localPos); GD.Print($"[InputMapper] HandleLeftClick — localPos={localPos}, coords={coords}"); if (coords == null) { GD.Print("[InputMapper] coords is null — click outside board"); return; } HandleClickAt(coords.Value); } private void HandleClickAt(Coords coords) { switch (_phase) { case PlacementPhase.SelectingStart: OnStartSelected(coords); break; case PlacementPhase.SelectingEnd: OnEndSelected(coords); break; default: EmitSignal(SignalName.CellClicked, coords.Col, coords.Row); break; } } /// /// Same effect as a left/right click on a board cell, for automation. /// Runs the exact branch HandleLeftClick runs (no InputEvent synthesis). /// public void SimulateClick(Coords coords, MouseButton button) { if (button == MouseButton.Right) { Cancel(); return; } HandleClickAt(coords); } private void OnStartSelected(Coords start) { if (_selectedKind == null || _snapshot == null) { GD.Print($"[InputMapper] OnStartSelected ABORT — kind={_selectedKind}, snapshot={(_snapshot != null ? "ok" : "null")}"); return; } var boardState = GetBoardStateForValidation(); if (boardState == null) { GD.Print("[InputMapper] OnStartSelected ABORT — boardState is null"); return; } var legalEnds = MoveValidator.GetLegalEndCells(_selectedKind.Value, start, boardState); GD.Print($"[InputMapper] OnStartSelected({start}) — {legalEnds.Count} legal end cells"); if (legalEnds.Count == 0) return; _selectedStart = start; _phase = PlacementPhase.SelectingEnd; _boardView.ClearHighlights(); _boardView.HighlightCells(legalEnds, new Color("#4488FF88")); var startCell = _boardView.GetCellView(start); startCell?.SetHighlightColor(new Color("#44FFFF44")); } private void OnEndSelected(Coords end) { if (_selectedKind == null || _selectedStart == null) return; var start = _selectedStart.Value; var kind = _selectedKind.Value; GD.Print($"[InputMapper] OnEndSelected — placing {kind} from {start} to {end}"); EmitSignal(SignalName.PlacementRequested, (int)kind, start.Col, start.Row, end.Col, end.Row); // Reset placement state _phase = PlacementPhase.None; _selectedKind = null; _selectedStart = null; _boardView.ClearHighlights(); } private BoardState? GetBoardStateForValidation() { if (_snapshot == null) return null; var level = new LevelDef { Width = _snapshot.Width, Height = _snapshot.Height, Productions = [], Demands = [], Walls = [], Stock = [] }; var state = BoardState.FromLevel(level); for (int c = 0; c < _snapshot.Width; c++) for (int r = 0; r < _snapshot.Height; r++) state.Grid[c, r] = _snapshot.Grid[c, r]; return state; } }