using Godot; using System.Collections.Generic; using Chessistics.Engine.Commands; using Chessistics.Engine.Events; using Chessistics.Engine.Loading; using Chessistics.Engine.Model; using Chessistics.Engine.Simulation; using Chessistics.Scripts.Board; using Chessistics.Scripts.Input; using Chessistics.Scripts.Pieces; using Chessistics.Scripts.Presentation; using Chessistics.Scripts.UI; namespace Chessistics.Scripts; public partial class Main : Node2D { private GameSim? _sim; private LevelDef? _currentLevel; private int _currentLevelIndex; // Views private BoardView _boardView = null!; private InputMapper _inputMapper = null!; private EventAnimator _eventAnimator = null!; // UI private CanvasLayer _uiLayer = null!; private ObjectivePanel _objectivePanel = null!; private PieceStockPanel _pieceStockPanel = null!; private DetailPanel _detailPanel = null!; private ControlBar _controlBar = null!; private MetricsOverlay _metricsOverlay = null!; private LevelSelectScreen _levelSelectScreen = null!; private Label _levelTitle = null!; // Simulation timer private Godot.Timer _simTimer = null!; private float _simInterval = 1.0f; private bool _running; private static readonly string[] LevelFiles = ["level_01.json", "level_02.json", "level_03.json"]; private static readonly Color BackgroundColor = new("#2D2D2D"); public override void _Ready() { RenderingServer.SetDefaultClearColor(BackgroundColor); BuildSceneTree(); ConnectSignals(); ShowLevelSelect(); } private void BuildSceneTree() { // Camera var camera = new Camera2D { Enabled = true }; AddChild(camera); // Board _boardView = new BoardView(); AddChild(_boardView); // Input _inputMapper = new InputMapper(); _inputMapper.Initialize(_boardView); AddChild(_inputMapper); // Animator _eventAnimator = new EventAnimator(); AddChild(_eventAnimator); // Sim timer _simTimer = new Godot.Timer { OneShot = false, WaitTime = _simInterval }; _simTimer.Timeout += OnSimTimerTick; AddChild(_simTimer); // UI Layer _uiLayer = new CanvasLayer(); AddChild(_uiLayer); // Level title _levelTitle = new Label { Position = new Vector2(10, 10), Text = "CHESSISTICS" }; _levelTitle.AddThemeFontSizeOverride("font_size", 20); _uiLayer.AddChild(_levelTitle); // Side panel (right) var sidePanel = new VBoxContainer { Position = new Vector2(700, 50), CustomMinimumSize = new Vector2(200, 500) }; _objectivePanel = new ObjectivePanel(); sidePanel.AddChild(_objectivePanel); sidePanel.AddChild(new HSeparator()); _pieceStockPanel = new PieceStockPanel(); sidePanel.AddChild(_pieceStockPanel); _detailPanel = new DetailPanel(); sidePanel.AddChild(_detailPanel); _uiLayer.AddChild(sidePanel); // Control bar (bottom) _controlBar = new ControlBar { Position = new Vector2(10, 600) }; _uiLayer.AddChild(_controlBar); // Metrics overlay (center) _metricsOverlay = new MetricsOverlay { Position = new Vector2(200, 150), CustomMinimumSize = new Vector2(300, 250) }; _uiLayer.AddChild(_metricsOverlay); // Level select screen _levelSelectScreen = new LevelSelectScreen(); _levelSelectScreen.SetAnchorsPreset(Control.LayoutPreset.FullRect); _uiLayer.AddChild(_levelSelectScreen); // Initialize animator _eventAnimator.Initialize(_boardView, _objectivePanel, _controlBar, _metricsOverlay); } private void ConnectSignals() { _levelSelectScreen.LevelSelected += OnLevelSelected; _pieceStockPanel.PieceSelected += OnPieceKindSelected; _inputMapper.PlacementRequested += OnPlacementRequested; _inputMapper.Cancelled += OnPlacementCancelled; _controlBar.PlayPressed += OnPlay; _controlBar.PausePressed += OnPause; _controlBar.StepPressed += OnStep; _controlBar.StopPressed += OnStop; _controlBar.SpeedChanged += OnSpeedChanged; _eventAnimator.TurnAnimationCompleted += OnTurnAnimationCompleted; _eventAnimator.VictoryReached += OnVictory; _eventAnimator.CollisionOccurred += OnCollision; _metricsOverlay.RetryPressed += OnRetry; _metricsOverlay.NextLevelPressed += OnNextLevel; _detailPanel.RemoveRequested += OnRemoveRequested; _inputMapper.CellClicked += OnCellClicked; } private void OnCellClicked(int col, int row) { if (_sim == null) return; var snap = _sim.GetSnapshot(); if (snap.Phase != SimPhase.Edit) return; var coords = new Coords(col, row); var piece = snap.Pieces.FirstOrDefault(p => p.StartCell == coords || p.EndCell == coords); if (piece != null) _detailPanel.ShowPiece(piece); else _detailPanel.Hide(); } // --- Level Management --- private void ShowLevelSelect() { _levelSelectScreen.Visible = true; _boardView.Visible = false; } private void OnLevelSelected(int levelIndex) { _currentLevelIndex = levelIndex; LoadLevel(levelIndex); } private void LoadLevel(int index) { if (index < 0 || index >= LevelFiles.Length) return; var path = $"res://Data/levels/{LevelFiles[index]}"; var file = Godot.FileAccess.Open(path, Godot.FileAccess.ModeFlags.Read); if (file == null) { GD.PrintErr($"Cannot open level file: {path}"); return; } var json = file.GetAsText(); file.Close(); _currentLevel = LevelLoader.Load(json); _sim = new GameSim(_currentLevel); _levelSelectScreen.Visible = false; _boardView.Visible = true; _boardView.BuildBoard(_currentLevel); _objectivePanel.Setup(_currentLevel.Demands); _pieceStockPanel.Setup(_currentLevel.Stock); _controlBar.UpdateForPhase(SimPhase.Edit); _controlBar.ResetTurn(); _metricsOverlay.Hide(); _detailPanel.Hide(); _eventAnimator.ClearAll(); _levelTitle.Text = $"CHESSISTICS — {_currentLevel.Name}"; // Center camera on board var cam = GetNode("Camera2D"); cam.Position = new Vector2( _currentLevel.Width * BoardView.CellSize / 2f, -_currentLevel.Height * BoardView.CellSize / 2f ); _inputMapper.SetSnapshot(_sim.GetSnapshot()); } // --- Edit Phase --- private void OnPieceKindSelected(int kindIndex) { _inputMapper.SelectPieceKind((PieceKind)kindIndex); } private void OnPlacementRequested(int kindIndex, int startCol, int startRow, int endCol, int endRow) { if (_sim == null) return; var kind = (PieceKind)kindIndex; var start = new Coords(startCol, startRow); var end = new Coords(endCol, endRow); var events = _sim.ProcessCommand(new PlacePieceCommand(kind, start, end)); HandleEditEvents(events); _inputMapper.SetSnapshot(_sim.GetSnapshot()); } private void OnPlacementCancelled() { _pieceStockPanel.ClearSelection(); } private void OnRemoveRequested(int pieceId) { if (_sim == null) return; var events = _sim.ProcessCommand(new RemovePieceCommand(pieceId)); HandleEditEvents(events); _inputMapper.SetSnapshot(_sim.GetSnapshot()); } private void HandleEditEvents(IReadOnlyList events) { foreach (var evt in events) { switch (evt) { case PiecePlacedEvent placed: CreatePieceVisual(placed); UpdateStockFromSnapshot(); break; case PieceRemovedEvent removed: _eventAnimator.UnregisterPiece(removed.PieceId); UpdateStockFromSnapshot(); _detailPanel.Hide(); break; case PlacementRejectedEvent rejected: GD.Print($"Placement rejected: {rejected.Reason}"); break; case CommandRejectedEvent rejected: GD.Print($"Command rejected: {rejected.Reason}"); break; } } } private void CreatePieceVisual(PiecePlacedEvent placed) { var pieceView = new PieceView(); pieceView.Setup(placed.PieceId, placed.Kind, placed.Start, placed.End, _boardView); _boardView.AddChild(pieceView); var color = placed.Kind switch { PieceKind.Rook => new Color("#4A7AB5"), PieceKind.Bishop => new Color("#B54A8E"), PieceKind.Knight => new Color("#B5824A"), _ => Colors.White }; var trajectView = new TrajectView(); trajectView.Setup(placed.PieceId, _boardView.CoordsToPixel(placed.Start), _boardView.CoordsToPixel(placed.End), color); _boardView.AddChild(trajectView); _eventAnimator.RegisterPiece(placed.PieceId, pieceView, trajectView); } private void UpdateStockFromSnapshot() { if (_sim == null) return; var snap = _sim.GetSnapshot(); foreach (var (kind, remaining) in snap.RemainingStock) _pieceStockPanel.UpdateCount(kind, remaining); } // --- Exec Phase --- private void OnPlay() { if (_sim == null) return; var snap = _sim.GetSnapshot(); if (snap.Phase == SimPhase.Edit) { var events = _sim.ProcessCommand(new StartSimulationCommand()); foreach (var evt in events) { if (evt is CommandRejectedEvent r) { GD.Print($"Cannot start: {r.Reason}"); return; } } } else if (snap.Phase == SimPhase.Paused) { _sim.ProcessCommand(new ResumeSimulationCommand()); } _running = true; _controlBar.UpdateForPhase(SimPhase.Running); _simTimer.WaitTime = _simInterval; _simTimer.Start(); } private void OnPause() { if (_sim == null) return; _sim.ProcessCommand(new PauseSimulationCommand()); _running = false; _simTimer.Stop(); _controlBar.UpdateForPhase(SimPhase.Paused); } private void OnStep() { if (_sim == null || _eventAnimator.IsAnimating) return; var events = _sim.ProcessCommand(new StepSimulationCommand()); _eventAnimator.ProcessEvents(events); _controlBar.UpdateForPhase(_sim.GetSnapshot().Phase); } private void OnStop() { if (_sim == null) return; _running = false; _simTimer.Stop(); _sim.ProcessCommand(new StopSimulationCommand()); _eventAnimator.ResetPiecePositions(_sim.GetSnapshot()); _controlBar.UpdateForPhase(SimPhase.Edit); _controlBar.ResetTurn(); _metricsOverlay.Hide(); _inputMapper.SetSnapshot(_sim.GetSnapshot()); // Reset objective panel if (_currentLevel != null) _objectivePanel.Setup(_currentLevel.Demands); } private void OnSpeedChanged(float interval) { _simInterval = interval; if (_simTimer.TimeLeft > 0) _simTimer.WaitTime = interval; } private void OnSimTimerTick() { if (_sim == null || _eventAnimator.IsAnimating) return; var events = _sim.ProcessCommand(new StepSimulationCommand()); _eventAnimator.ProcessEvents(events); } private void OnTurnAnimationCompleted() { if (_sim == null) return; var phase = _sim.GetSnapshot().Phase; _controlBar.UpdateForPhase(phase); if (phase == SimPhase.Victory || phase == SimPhase.Defeat || phase == SimPhase.Collision) { _running = false; _simTimer.Stop(); } } private void OnVictory() { _running = false; _simTimer.Stop(); } private void OnCollision() { _running = false; _simTimer.Stop(); _controlBar.UpdateForPhase(SimPhase.Collision); } // --- Navigation --- private void OnRetry() { LoadLevel(_currentLevelIndex); } private void OnNextLevel() { if (_currentLevelIndex + 1 < LevelFiles.Length) LoadLevel(_currentLevelIndex + 1); else ShowLevelSelect(); } }