Chessistics/Scripts/Main.cs

516 lines
14 KiB
C#
Raw Normal View History

using Godot;
using System.Collections.Generic;
using System.Linq;
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!;
private PanelContainer _sidePanel = null!;
private PanelContainer _controlBarWrapper = null!;
private Camera2D _camera = null!;
// Simulation timer
private Godot.Timer _simTimer = null!;
private float _simInterval = 1.0f;
private bool _running;
private bool _panning;
private static readonly string[] LevelFiles = ["level_01.json", "level_02.json", "level_03.json"];
private const float SidePanelWidth = 280f;
private const float ControlBarHeight = 48f;
private static readonly Color BackgroundColor = new("#2D2D2D");
public override void _Ready()
{
RenderingServer.SetDefaultClearColor(BackgroundColor);
BuildSceneTree();
ConnectSignals();
ShowLevelSelect();
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event is InputEventMouseButton mb)
{
if (mb.ButtonIndex == MouseButton.Middle)
_panning = mb.Pressed;
}
else if (@event is InputEventMouseMotion motion && _panning)
{
_camera.Position -= motion.Relative / _camera.Zoom;
}
}
private void BuildSceneTree()
{
// Camera
_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);
// Root control anchored to viewport (required for child anchoring)
var uiRoot = new Control();
uiRoot.SetAnchorsPreset(Control.LayoutPreset.FullRect);
uiRoot.MouseFilter = Control.MouseFilterEnum.Ignore;
_uiLayer.AddChild(uiRoot);
// Level title (top-left)
_levelTitle = new Label { Text = "CHESSISTICS" };
_levelTitle.SetAnchorsPreset(Control.LayoutPreset.TopLeft);
_levelTitle.OffsetLeft = 16;
_levelTitle.OffsetTop = 12;
_levelTitle.AddThemeFontSizeOverride("font_size", 20);
_levelTitle.MouseFilter = Control.MouseFilterEnum.Ignore;
uiRoot.AddChild(_levelTitle);
// --- Side Panel (anchored to right edge) ---
_sidePanel = new PanelContainer();
_sidePanel.AnchorLeft = 1.0f;
_sidePanel.AnchorRight = 1.0f;
_sidePanel.AnchorTop = 0.0f;
_sidePanel.AnchorBottom = 1.0f;
_sidePanel.OffsetLeft = -SidePanelWidth;
_sidePanel.OffsetRight = 0;
_sidePanel.OffsetTop = 0;
_sidePanel.OffsetBottom = -ControlBarHeight;
var sidePanelStyle = new StyleBoxFlat
{
BgColor = new Color(0.14f, 0.14f, 0.16f, 0.97f),
BorderColor = new Color(0.25f, 0.25f, 0.28f),
BorderWidthLeft = 1,
ContentMarginLeft = 16,
ContentMarginRight = 16,
ContentMarginTop = 16,
ContentMarginBottom = 16
};
_sidePanel.AddThemeStyleboxOverride("panel", sidePanelStyle);
var sideScroll = new ScrollContainer
{
HorizontalScrollMode = ScrollContainer.ScrollMode.Disabled,
SizeFlagsVertical = Control.SizeFlags.ExpandFill
};
var sideVBox = new VBoxContainer();
sideVBox.AddThemeConstantOverride("separation", 12);
_objectivePanel = new ObjectivePanel();
sideVBox.AddChild(_objectivePanel);
sideVBox.AddChild(new HSeparator());
_pieceStockPanel = new PieceStockPanel();
sideVBox.AddChild(_pieceStockPanel);
_detailPanel = new DetailPanel();
sideVBox.AddChild(_detailPanel);
sideScroll.AddChild(sideVBox);
_sidePanel.AddChild(sideScroll);
uiRoot.AddChild(_sidePanel);
// --- Control Bar (anchored to bottom, left of side panel) ---
_controlBarWrapper = new PanelContainer();
_controlBarWrapper.AnchorLeft = 0.0f;
_controlBarWrapper.AnchorRight = 1.0f;
_controlBarWrapper.AnchorTop = 1.0f;
_controlBarWrapper.AnchorBottom = 1.0f;
_controlBarWrapper.OffsetTop = -ControlBarHeight;
_controlBarWrapper.OffsetRight = -SidePanelWidth;
var controlBarStyle = new StyleBoxFlat
{
BgColor = new Color(0.11f, 0.11f, 0.13f, 0.97f),
BorderColor = new Color(0.25f, 0.25f, 0.28f),
BorderWidthTop = 1,
ContentMarginLeft = 12,
ContentMarginRight = 12,
ContentMarginTop = 4,
ContentMarginBottom = 4
};
_controlBarWrapper.AddThemeStyleboxOverride("panel", controlBarStyle);
_controlBar = new ControlBar();
_controlBarWrapper.AddChild(_controlBar);
uiRoot.AddChild(_controlBarWrapper);
// --- Metrics Overlay (centered in board area) ---
var metricsCenter = new CenterContainer();
metricsCenter.SetAnchorsPreset(Control.LayoutPreset.FullRect);
metricsCenter.OffsetRight = -SidePanelWidth;
metricsCenter.OffsetBottom = -ControlBarHeight;
metricsCenter.MouseFilter = Control.MouseFilterEnum.Ignore;
_metricsOverlay = new MetricsOverlay();
_metricsOverlay.CustomMinimumSize = new Vector2(340, 260);
metricsCenter.AddChild(_metricsOverlay);
uiRoot.AddChild(metricsCenter);
// --- Level Select Screen (full viewport) ---
_levelSelectScreen = new LevelSelectScreen();
_levelSelectScreen.SetAnchorsPreset(Control.LayoutPreset.FullRect);
uiRoot.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;
_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;
_sidePanel.Visible = false;
_controlBarWrapper.Visible = false;
_levelTitle.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;
_sidePanel.Visible = true;
_controlBarWrapper.Visible = true;
_levelTitle.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
// Board X spans 0..W*Cell, Y spans -(H-1)*Cell .. Cell
_camera.Position = new Vector2(
_currentLevel.Width * BoardView.CellSize / 2f,
-_currentLevel.Height * BoardView.CellSize / 2f + BoardView.CellSize
);
_camera.Offset = new Vector2(-SidePanelWidth / 2f, -ControlBarHeight / 2f);
var snapshot = _sim.GetSnapshot();
GD.Print($"[Main] LoadLevel — snapshot null? {snapshot == null}, width={snapshot?.Width}, height={snapshot?.Height}");
_inputMapper.SetSnapshot(snapshot);
}
// --- 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<IWorldEvent> 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)
{
_running = false;
_simTimer.Stop();
}
}
private void OnVictory()
{
_running = false;
_simTimer.Stop();
}
// --- Navigation ---
private void OnRetry()
{
LoadLevel(_currentLevelIndex);
}
private void OnNextLevel()
{
if (_currentLevelIndex + 1 < LevelFiles.Length)
LoadLevel(_currentLevelIndex + 1);
else
ShowLevelSelect();
}
}