Compare commits
No commits in common. "358ab48d596406b8bc540717e0cb6b15a327f558" and "e1218b3eaa43deb9228c37eb863d04164cd9b37d" have entirely different histories.
358ab48d59
...
e1218b3eaa
22 changed files with 233 additions and 1085 deletions
|
|
@ -24,9 +24,3 @@ Input → Command → GameSim (state + rules) → Events → Presentation
|
|||
Tout `Control` (ColorRect, Label…) a `MouseFilter = Stop` par defaut. Quand un Control est enfant d'un Node2D (ex: les ColorRect dans CellView, les Labels dans PieceView), **il participe quand meme au systeme GUI et consomme les clics**, empechant `_UnhandledInput` de recevoir l'evenement.
|
||||
|
||||
**Regle** : toujours mettre `MouseFilter = Control.MouseFilterEnum.Ignore` sur les Controls purement visuels enfants de Node2D.
|
||||
|
||||
## Conventions Claude
|
||||
|
||||
### Plans
|
||||
|
||||
Les fichiers de plan doivent etre rediges a la racine du workspace (ex: `/workspace/PLAN_juice.md`), **pas** dans `.claude/plans/` car ce dossier a une taille limitee.
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"id": 7,
|
||||
"name": "La Dame Blanche",
|
||||
"description": "La Dame entre en jeu. Sa portee sur 8 directions en fait une piece logistique supreme.",
|
||||
"width": 10,
|
||||
"height": 10,
|
||||
"productions": [
|
||||
{ "col": 0, "row": 0, "name": "Scierie", "cargo": "wood" },
|
||||
{ "col": 9, "row": 0, "name": "Carriere", "cargo": "stone" }
|
||||
],
|
||||
"demands": [
|
||||
{ "col": 9, "row": 9, "name": "Chateau", "cargo": "wood", "amount": 4, "deadline": 50 },
|
||||
{ "col": 0, "row": 9, "name": "Forge Royale", "cargo": "stone", "amount": 4, "deadline": 50 }
|
||||
],
|
||||
"walls": [
|
||||
{ "col": 3, "row": 3 }, { "col": 3, "row": 4 }, { "col": 3, "row": 5 }, { "col": 3, "row": 6 },
|
||||
{ "col": 6, "row": 3 }, { "col": 6, "row": 4 }, { "col": 6, "row": 5 }, { "col": 6, "row": 6 },
|
||||
{ "col": 4, "row": 6 }, { "col": 5, "row": 6 },
|
||||
{ "col": 4, "row": 3 }, { "col": 5, "row": 3 }
|
||||
],
|
||||
"stock": [
|
||||
{ "kind": "pawn", "count": 10 },
|
||||
{ "kind": "rook", "count": 4 },
|
||||
{ "kind": "bishop", "count": 2 },
|
||||
{ "kind": "knight", "count": 2 },
|
||||
{ "kind": "queen", "count": 1 }
|
||||
]
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
{
|
||||
"id": 8,
|
||||
"name": "Le Grand Reseau",
|
||||
"description": "Quatre productions, quatre demandes. Construisez un reseau logistique complet a travers un terrain hostile.",
|
||||
"width": 12,
|
||||
"height": 10,
|
||||
"productions": [
|
||||
{ "col": 0, "row": 0, "name": "Scierie Ouest", "cargo": "wood" },
|
||||
{ "col": 11, "row": 0, "name": "Carriere Sud", "cargo": "stone" },
|
||||
{ "col": 0, "row": 9, "name": "Scierie Nord", "cargo": "wood" },
|
||||
{ "col": 11, "row": 9, "name": "Carriere Nord", "cargo": "stone" }
|
||||
],
|
||||
"demands": [
|
||||
{ "col": 5, "row": 0, "name": "Depot Sud", "cargo": "wood", "amount": 3, "deadline": 60 },
|
||||
{ "col": 6, "row": 9, "name": "Depot Nord", "cargo": "stone", "amount": 3, "deadline": 60 },
|
||||
{ "col": 5, "row": 5, "name": "Forge Centrale", "cargo": "stone", "amount": 4, "deadline": 60 },
|
||||
{ "col": 6, "row": 4, "name": "Chantier Central", "cargo": "wood", "amount": 4, "deadline": 60 }
|
||||
],
|
||||
"walls": [
|
||||
{ "col": 3, "row": 2 }, { "col": 3, "row": 3 }, { "col": 3, "row": 4 },
|
||||
{ "col": 3, "row": 5 }, { "col": 3, "row": 6 }, { "col": 3, "row": 7 },
|
||||
{ "col": 8, "row": 2 }, { "col": 8, "row": 3 }, { "col": 8, "row": 4 },
|
||||
{ "col": 8, "row": 5 }, { "col": 8, "row": 6 }, { "col": 8, "row": 7 },
|
||||
{ "col": 4, "row": 4 }, { "col": 7, "row": 5 }
|
||||
],
|
||||
"stock": [
|
||||
{ "kind": "pawn", "count": 16 },
|
||||
{ "kind": "rook", "count": 6 },
|
||||
{ "kind": "bishop", "count": 3 },
|
||||
{ "kind": "knight", "count": 4 },
|
||||
{ "kind": "queen", "count": 2 }
|
||||
]
|
||||
}
|
||||
34
PLAN.md
34
PLAN.md
|
|
@ -28,28 +28,22 @@
|
|||
- Production interval removed: all productions fire every turn
|
||||
- GDD updated with Pion, 6 levels
|
||||
|
||||
## Phase 5: Dame, network levels, juice pass (DONE)
|
||||
## Phase 5: Network levels and Dame (Queen)
|
||||
|
||||
- Dame (Queen): 8 directions, range 2, status 7 (highest)
|
||||
- Levels 7-8: La Dame Blanche (10x10), Le Grand Reseau (12x10)
|
||||
- Procedural SFX, particles, polished animations, fade transitions
|
||||
- UI bugfixes: stop reset, piece selection visuals, back-to-menu button
|
||||
- Trajectory preview on piece click
|
||||
**Goal**: Open-ended logistics puzzles with interconnected supply networks.
|
||||
|
||||
## Phase 6: Godot integration (DONE)
|
||||
- Multiple productions feeding multiple demands through shared infrastructure.
|
||||
- Dame piece: combines Rook + Bishop movement (range 2, all 8 directions).
|
||||
- Powerful but expensive — forces cost/benefit tradeoffs.
|
||||
- Larger boards (12x12+) with complex wall configurations.
|
||||
- Potential for player-designed levels (level editor data format).
|
||||
|
||||
- Board renderer, piece placement, step/play/pause controls
|
||||
- Event visualization with simultaneous animations per phase
|
||||
- Victory/defeat screens with animated metrics
|
||||
- Production flash, cargo slide trails, destruction particles, confetti
|
||||
## Phase 6: Godot integration
|
||||
|
||||
## Phase 7: Zoom, scroll wheel, and camera polish
|
||||
**Goal**: Playable visual prototype.
|
||||
|
||||
- Mouse scroll wheel to zoom in/out on board
|
||||
- Zoom limits (min/max) to prevent getting lost
|
||||
- Double-click to center on a piece
|
||||
|
||||
## Phase 8: Level editor (future)
|
||||
|
||||
- Player-designed levels via JSON export
|
||||
- In-game editing of board size, walls, productions, demands, stock
|
||||
- Board renderer: grid, walls, buildings, pieces.
|
||||
- Drag-and-drop piece placement during Edit phase.
|
||||
- Step/play/pause simulation controls.
|
||||
- Event visualization: cargo movement, transfers, delivery animations.
|
||||
- Victory/defeat screens with Metrics display.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ public partial class CellView : Node2D
|
|||
{
|
||||
private ColorRect _background = null!;
|
||||
private ColorRect _highlight = null!;
|
||||
private ColorRect _innerShadow = null!;
|
||||
private Label _label = null!;
|
||||
|
||||
// Hover outline (4 thin rects forming a border)
|
||||
|
|
@ -18,14 +17,13 @@ public partial class CellView : Node2D
|
|||
|
||||
public Coords Coords { get; private set; }
|
||||
|
||||
// Warmer, more grounded palette
|
||||
private static readonly Color LightColor = new("#E8D5A8"); // warm parchment
|
||||
private static readonly Color DarkColor = new("#A07850"); // warm walnut
|
||||
private static readonly Color WallColor = new("#3A3A3A"); // charcoal
|
||||
private static readonly Color ProductionColor = new("#4A6E3A"); // deep forest
|
||||
private static readonly Color DemandColor = new("#B8942A"); // aged gold
|
||||
private static readonly Color LightColor = new("#F0D9B5");
|
||||
private static readonly Color DarkColor = new("#B58863");
|
||||
private static readonly Color WallColor = new("#555555");
|
||||
private static readonly Color ProductionColor = new("#6B8E5A");
|
||||
private static readonly Color DemandColor = new("#C9A833");
|
||||
private static readonly Color HighlightColor = new("#44FF4444");
|
||||
private static readonly Color HoverOutlineColor = new("#FFFFFF88");
|
||||
private static readonly Color HoverOutlineColor = new("#FFFFFFAA");
|
||||
|
||||
private const int OutlineWidth = 2;
|
||||
|
||||
|
|
@ -51,17 +49,6 @@ public partial class CellView : Node2D
|
|||
};
|
||||
AddChild(_background);
|
||||
|
||||
// Subtle inner shadow for depth (top-left lit, bottom-right dark)
|
||||
_innerShadow = new ColorRect
|
||||
{
|
||||
Size = new Vector2(cellSize, cellSize),
|
||||
Position = Vector2.Zero,
|
||||
Color = new Color(0, 0, 0, 0.06f),
|
||||
Visible = cellType == CellType.Empty,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_innerShadow);
|
||||
|
||||
_highlight = new ColorRect
|
||||
{
|
||||
Size = new Vector2(cellSize, cellSize),
|
||||
|
|
@ -73,34 +60,54 @@ public partial class CellView : Node2D
|
|||
AddChild(_highlight);
|
||||
|
||||
// Hover outline (4 border rects)
|
||||
_hoverTop = CreateBorderRect(new Vector2(cellSize, OutlineWidth), Vector2.Zero);
|
||||
_hoverBottom = CreateBorderRect(new Vector2(cellSize, OutlineWidth), new Vector2(0, cellSize - OutlineWidth));
|
||||
_hoverLeft = CreateBorderRect(new Vector2(OutlineWidth, cellSize), Vector2.Zero);
|
||||
_hoverRight = CreateBorderRect(new Vector2(OutlineWidth, cellSize), new Vector2(cellSize - OutlineWidth, 0));
|
||||
|
||||
_label = new Label
|
||||
_hoverTop = new ColorRect
|
||||
{
|
||||
Position = new Vector2(4, 3),
|
||||
Text = "",
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
_label.AddThemeFontSizeOverride("font_size", 9);
|
||||
_label.AddThemeColorOverride("font_color", new Color(1, 1, 1, 0.7f));
|
||||
AddChild(_label);
|
||||
}
|
||||
|
||||
private ColorRect CreateBorderRect(Vector2 size, Vector2 pos)
|
||||
{
|
||||
var rect = new ColorRect
|
||||
{
|
||||
Size = size,
|
||||
Position = pos,
|
||||
Size = new Vector2(cellSize, OutlineWidth),
|
||||
Position = Vector2.Zero,
|
||||
Color = HoverOutlineColor,
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(rect);
|
||||
return rect;
|
||||
AddChild(_hoverTop);
|
||||
|
||||
_hoverBottom = new ColorRect
|
||||
{
|
||||
Size = new Vector2(cellSize, OutlineWidth),
|
||||
Position = new Vector2(0, cellSize - OutlineWidth),
|
||||
Color = HoverOutlineColor,
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_hoverBottom);
|
||||
|
||||
_hoverLeft = new ColorRect
|
||||
{
|
||||
Size = new Vector2(OutlineWidth, cellSize),
|
||||
Position = Vector2.Zero,
|
||||
Color = HoverOutlineColor,
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_hoverLeft);
|
||||
|
||||
_hoverRight = new ColorRect
|
||||
{
|
||||
Size = new Vector2(OutlineWidth, cellSize),
|
||||
Position = new Vector2(cellSize - OutlineWidth, 0),
|
||||
Color = HoverOutlineColor,
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_hoverRight);
|
||||
|
||||
_label = new Label
|
||||
{
|
||||
Position = new Vector2(2, 2),
|
||||
Text = "",
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
_label.AddThemeFontSizeOverride("font_size", 10);
|
||||
AddChild(_label);
|
||||
}
|
||||
|
||||
public void SetLabel(string text) => _label.Text = text;
|
||||
|
|
@ -121,15 +128,14 @@ public partial class CellView : Node2D
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Production pulse: warm glow that radiates outward.
|
||||
/// Brief white flash on the cell to signal production.
|
||||
/// </summary>
|
||||
public void FlashProduce(float duration = 0.3f)
|
||||
{
|
||||
_highlight.Color = new Color(0.9f, 0.85f, 0.5f, 0.55f); // warm golden
|
||||
_highlight.Color = new Color(1, 1, 1, 0.5f);
|
||||
_highlight.Visible = true;
|
||||
var tween = CreateTween();
|
||||
tween.TweenProperty(_highlight, "color", new Color(0.9f, 0.85f, 0.5f, 0f), duration)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||
tween.TweenProperty(_highlight, "color", new Color(1, 1, 1, 0f), duration);
|
||||
tween.TweenCallback(Callable.From(() => _highlight.Visible = false));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
146
Scripts/Main.cs
146
Scripts/Main.cs
|
|
@ -1,5 +1,4 @@
|
|||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Chessistics.Engine.Commands;
|
||||
|
|
@ -38,7 +37,6 @@ public partial class Main : Node2D
|
|||
private PanelContainer _sidePanel = null!;
|
||||
private PanelContainer _controlBarWrapper = null!;
|
||||
private Camera2D _camera = null!;
|
||||
private ColorRect _fadeOverlay = null!;
|
||||
|
||||
// Simulation timer
|
||||
private Godot.Timer _simTimer = null!;
|
||||
|
|
@ -46,7 +44,7 @@ public partial class Main : Node2D
|
|||
private bool _running;
|
||||
private bool _panning;
|
||||
|
||||
private static readonly string[] LevelFiles = ["level_01.json", "level_02.json", "level_03.json", "level_04.json", "level_05.json", "level_06.json", "level_07.json", "level_08.json"];
|
||||
private static readonly string[] LevelFiles = ["level_01.json", "level_02.json", "level_03.json", "level_04.json", "level_05.json", "level_06.json"];
|
||||
|
||||
private const float SidePanelWidth = 280f;
|
||||
private const float ControlBarHeight = 48f;
|
||||
|
|
@ -60,9 +58,6 @@ public partial class Main : Node2D
|
|||
BuildSceneTree();
|
||||
ConnectSignals();
|
||||
ShowLevelSelect();
|
||||
|
||||
// Fade in from black on startup
|
||||
FadeIn(0.5f);
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
|
|
@ -71,10 +66,6 @@ public partial class Main : Node2D
|
|||
{
|
||||
if (mb.ButtonIndex == MouseButton.Middle)
|
||||
_panning = mb.Pressed;
|
||||
else if (mb.Pressed && mb.ButtonIndex == MouseButton.WheelUp)
|
||||
ZoomCamera(1.1f);
|
||||
else if (mb.Pressed && mb.ButtonIndex == MouseButton.WheelDown)
|
||||
ZoomCamera(0.9f);
|
||||
}
|
||||
else if (@event is InputEventMouseMotion motion && _panning)
|
||||
{
|
||||
|
|
@ -82,40 +73,12 @@ public partial class Main : Node2D
|
|||
}
|
||||
}
|
||||
|
||||
private void ZoomCamera(float factor)
|
||||
{
|
||||
var newZoom = _camera.Zoom * factor;
|
||||
newZoom = newZoom.Clamp(new Vector2(0.3f, 0.3f), new Vector2(3f, 3f));
|
||||
_camera.Zoom = newZoom;
|
||||
}
|
||||
|
||||
private void FadeIn(float duration)
|
||||
{
|
||||
_fadeOverlay.Color = new Color(0, 0, 0, 1);
|
||||
var tween = CreateTween();
|
||||
tween.TweenProperty(_fadeOverlay, "color:a", 0f, duration)
|
||||
.SetEase(Tween.EaseType.Out);
|
||||
}
|
||||
|
||||
private void FadeOut(float duration, Action onComplete)
|
||||
{
|
||||
_fadeOverlay.Color = new Color(0, 0, 0, 0);
|
||||
var tween = CreateTween();
|
||||
tween.TweenProperty(_fadeOverlay, "color:a", 1f, duration)
|
||||
.SetEase(Tween.EaseType.In);
|
||||
tween.TweenCallback(Callable.From(onComplete));
|
||||
}
|
||||
|
||||
private void BuildSceneTree()
|
||||
{
|
||||
// Camera
|
||||
_camera = new Camera2D { Enabled = true };
|
||||
AddChild(_camera);
|
||||
|
||||
// SFX
|
||||
var sfx = new SfxManager();
|
||||
AddChild(sfx);
|
||||
|
||||
// Board
|
||||
_boardView = new BoardView();
|
||||
AddChild(_boardView);
|
||||
|
|
@ -144,35 +107,14 @@ public partial class Main : Node2D
|
|||
uiRoot.MouseFilter = Control.MouseFilterEnum.Ignore;
|
||||
_uiLayer.AddChild(uiRoot);
|
||||
|
||||
// Level title bar (top-left)
|
||||
var titleBar = new HBoxContainer();
|
||||
titleBar.SetAnchorsPreset(Control.LayoutPreset.TopLeft);
|
||||
titleBar.OffsetLeft = 12;
|
||||
titleBar.OffsetTop = 8;
|
||||
titleBar.AddThemeConstantOverride("separation", 12);
|
||||
|
||||
var backButton = new Button { Text = "← Menu", CustomMinimumSize = new Vector2(70, 28) };
|
||||
backButton.AddThemeFontSizeOverride("font_size", 11);
|
||||
var backStyle = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("#2A2A2E"),
|
||||
BorderColor = new Color("#444448"),
|
||||
BorderWidthBottom = 1, BorderWidthTop = 1, BorderWidthLeft = 1, BorderWidthRight = 1,
|
||||
CornerRadiusTopLeft = 4, CornerRadiusTopRight = 4,
|
||||
CornerRadiusBottomLeft = 4, CornerRadiusBottomRight = 4,
|
||||
ContentMarginLeft = 8, ContentMarginRight = 8,
|
||||
ContentMarginTop = 2, ContentMarginBottom = 2
|
||||
};
|
||||
backButton.AddThemeStyleboxOverride("normal", backStyle);
|
||||
backButton.Pressed += OnBackToMenu;
|
||||
titleBar.AddChild(backButton);
|
||||
|
||||
// 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;
|
||||
titleBar.AddChild(_levelTitle);
|
||||
|
||||
uiRoot.AddChild(titleBar);
|
||||
uiRoot.AddChild(_levelTitle);
|
||||
|
||||
// --- Side Panel (anchored to right edge) ---
|
||||
_sidePanel = new PanelContainer();
|
||||
|
|
@ -262,15 +204,6 @@ public partial class Main : Node2D
|
|||
_levelSelectScreen.SetAnchorsPreset(Control.LayoutPreset.FullRect);
|
||||
uiRoot.AddChild(_levelSelectScreen);
|
||||
|
||||
// --- Fade overlay (on top of everything) ---
|
||||
_fadeOverlay = new ColorRect
|
||||
{
|
||||
Color = new Color(0, 0, 0, 1),
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
_fadeOverlay.SetAnchorsPreset(Control.LayoutPreset.FullRect);
|
||||
uiRoot.AddChild(_fadeOverlay);
|
||||
|
||||
// Initialize animator
|
||||
_eventAnimator.Initialize(_boardView, _objectivePanel, _controlBar, _metricsOverlay);
|
||||
}
|
||||
|
|
@ -300,24 +233,13 @@ public partial class Main : Node2D
|
|||
var snap = _sim.GetSnapshot();
|
||||
if (snap.Phase != SimPhase.Edit) return;
|
||||
|
||||
_boardView.ClearHighlights();
|
||||
|
||||
var coords = new Coords(col, row);
|
||||
var piece = snap.Pieces.FirstOrDefault(p => p.StartCell == coords || p.EndCell == coords);
|
||||
if (piece != null)
|
||||
{
|
||||
_detailPanel.ShowPiece(piece);
|
||||
|
||||
// Highlight start and end cells to show trajectory
|
||||
var pieceColor = PieceView.GetPieceColor(piece.Kind);
|
||||
var highlightColor = new Color(pieceColor, 0.3f);
|
||||
_boardView.HighlightCells([piece.StartCell, piece.EndCell], highlightColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
_detailPanel.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Level Management ---
|
||||
|
||||
|
|
@ -332,15 +254,8 @@ public partial class Main : Node2D
|
|||
|
||||
private void OnLevelSelected(int levelIndex)
|
||||
{
|
||||
SfxManager.Instance?.PlayClick();
|
||||
_currentLevelIndex = levelIndex;
|
||||
|
||||
// Fade out, load, fade in
|
||||
FadeOut(0.25f, () =>
|
||||
{
|
||||
LoadLevel(levelIndex);
|
||||
FadeIn(0.3f);
|
||||
});
|
||||
}
|
||||
|
||||
private void LoadLevel(int index)
|
||||
|
|
@ -455,13 +370,17 @@ public partial class Main : Node2D
|
|||
|
||||
private void CreatePieceVisual(PiecePlacedEvent placed)
|
||||
{
|
||||
SfxManager.Instance?.PlayPlace();
|
||||
|
||||
var pieceView = new PieceView();
|
||||
pieceView.Setup(placed.PieceId, placed.Kind, placed.Start, placed.End, _boardView);
|
||||
_boardView.AddChild(pieceView);
|
||||
|
||||
var color = PieceView.GetPieceColor(placed.Kind);
|
||||
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,
|
||||
|
|
@ -534,32 +453,13 @@ public partial class Main : Node2D
|
|||
_running = false;
|
||||
_simTimer.Stop();
|
||||
_sim.ProcessCommand(new StopSimulationCommand());
|
||||
|
||||
// Full visual rebuild: clear everything and recreate from snapshot
|
||||
_eventAnimator.ClearAll();
|
||||
var snap = _sim.GetSnapshot();
|
||||
foreach (var ps in snap.Pieces)
|
||||
{
|
||||
var pieceView = new PieceView();
|
||||
pieceView.Setup(ps.Id, ps.Kind, ps.StartCell, ps.EndCell, _boardView);
|
||||
_boardView.AddChild(pieceView);
|
||||
|
||||
var color = PieceView.GetPieceColor(ps.Kind);
|
||||
var trajectView = new TrajectView();
|
||||
trajectView.Setup(ps.Id,
|
||||
_boardView.CoordsToPixel(ps.StartCell),
|
||||
_boardView.CoordsToPixel(ps.EndCell),
|
||||
color);
|
||||
_boardView.AddChild(trajectView);
|
||||
|
||||
_eventAnimator.RegisterPiece(ps.Id, pieceView, trajectView);
|
||||
}
|
||||
|
||||
_eventAnimator.ResetPiecePositions(_sim.GetSnapshot());
|
||||
_controlBar.UpdateForPhase(SimPhase.Edit);
|
||||
_controlBar.ResetTurn();
|
||||
_metricsOverlay.Hide();
|
||||
_inputMapper.SetSnapshot(snap);
|
||||
_inputMapper.SetSnapshot(_sim.GetSnapshot());
|
||||
|
||||
// Reset objective panel
|
||||
if (_currentLevel != null)
|
||||
_objectivePanel.Setup(_currentLevel.Demands);
|
||||
}
|
||||
|
|
@ -612,18 +512,4 @@ public partial class Main : Node2D
|
|||
else
|
||||
ShowLevelSelect();
|
||||
}
|
||||
|
||||
private void OnBackToMenu()
|
||||
{
|
||||
SfxManager.Instance?.PlayClick();
|
||||
_running = false;
|
||||
_simTimer.Stop();
|
||||
_eventAnimator.ClearAll();
|
||||
|
||||
FadeOut(0.2f, () =>
|
||||
{
|
||||
ShowLevelSelect();
|
||||
FadeIn(0.3f);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,26 +6,21 @@ namespace Chessistics.Scripts.Pieces;
|
|||
|
||||
public partial class PieceView : Node2D
|
||||
{
|
||||
private Sprite2D _shadow = null!;
|
||||
private Sprite2D _sprite = null!;
|
||||
private ColorRect _cargoIndicator = null!;
|
||||
private Label _label = null!;
|
||||
private Tween? _cargoPulseTween;
|
||||
|
||||
public int PieceId { get; private set; }
|
||||
public PieceKind Kind { get; private set; }
|
||||
public Coords StartCell { get; private set; }
|
||||
public Coords EndCell { get; private set; }
|
||||
|
||||
// Muted, earthy palette — teal, sienna, gold tones
|
||||
private static readonly Color PawnColor = new("#5A8C6B"); // sage green
|
||||
private static readonly Color RookColor = new("#3D6B8E"); // deep teal
|
||||
private static readonly Color BishopColor = new("#8E5A6B"); // dusty rose
|
||||
private static readonly Color KnightColor = new("#8E7A3D"); // burnt sienna
|
||||
private static readonly Color QueenColor = new("#8E3D5A"); // deep burgundy
|
||||
private static readonly Color WoodCargoColor = new("#A67C32");
|
||||
private static readonly Color StoneCargoColor = new("#7A7A7A");
|
||||
private static readonly Color ShadowColor = new Color(0, 0, 0, 0.18f);
|
||||
private static readonly Color PawnColor = new("#7AB54A");
|
||||
private static readonly Color RookColor = new("#4A7AB5");
|
||||
private static readonly Color BishopColor = new("#B54A8E");
|
||||
private static readonly Color KnightColor = new("#B5824A");
|
||||
private static readonly Color WoodCargoColor = new("#8B6914");
|
||||
private static readonly Color StoneCargoColor = new("#808080");
|
||||
|
||||
public void Setup(int pieceId, PieceKind kind, Coords startCell, Coords endCell, BoardView boardView)
|
||||
{
|
||||
|
|
@ -36,36 +31,26 @@ public partial class PieceView : Node2D
|
|||
|
||||
Position = boardView.CoordsToPixel(startCell);
|
||||
|
||||
var color = GetPieceColor(kind);
|
||||
|
||||
// Shadow (slightly offset, rendered first)
|
||||
_shadow = new Sprite2D();
|
||||
var shadowTex = new GradientTexture2D
|
||||
var color = kind switch
|
||||
{
|
||||
Width = 44, Height = 44,
|
||||
Fill = GradientTexture2D.FillEnum.Radial,
|
||||
Gradient = new Gradient()
|
||||
PieceKind.Pawn => PawnColor,
|
||||
PieceKind.Rook => RookColor,
|
||||
PieceKind.Bishop => BishopColor,
|
||||
PieceKind.Knight => KnightColor,
|
||||
_ => Colors.White
|
||||
};
|
||||
shadowTex.Gradient.SetColor(0, ShadowColor);
|
||||
shadowTex.Gradient.SetColor(1, new Color(0, 0, 0, 0));
|
||||
_shadow.Texture = shadowTex;
|
||||
_shadow.Position = new Vector2(3, 5); // subtle offset down-right
|
||||
AddChild(_shadow);
|
||||
|
||||
// Piece body (circle with inner glow)
|
||||
// Piece body (circle)
|
||||
_sprite = new Sprite2D();
|
||||
var texture = new GradientTexture2D
|
||||
{
|
||||
Width = 48, Height = 48,
|
||||
Width = 48,
|
||||
Height = 48,
|
||||
Fill = GradientTexture2D.FillEnum.Radial,
|
||||
Gradient = new Gradient()
|
||||
};
|
||||
texture.Gradient.Colors = [
|
||||
color.Lightened(0.15f), // bright center
|
||||
color, // main color
|
||||
color.Darkened(0.25f) // dark rim
|
||||
];
|
||||
texture.Gradient.Offsets = [0f, 0.5f, 1f];
|
||||
texture.Gradient.SetColor(0, color);
|
||||
texture.Gradient.SetColor(1, color.Darkened(0.3f));
|
||||
_sprite.Texture = texture;
|
||||
AddChild(_sprite);
|
||||
|
||||
|
|
@ -78,7 +63,6 @@ public partial class PieceView : Node2D
|
|||
PieceKind.Rook => "T",
|
||||
PieceKind.Bishop => "F",
|
||||
PieceKind.Knight => "C",
|
||||
PieceKind.Queen => "D",
|
||||
_ => "?"
|
||||
},
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
|
|
@ -87,47 +71,25 @@ public partial class PieceView : Node2D
|
|||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
_label.AddThemeFontSizeOverride("font_size", 16);
|
||||
_label.AddThemeColorOverride("font_color", new Color(1, 1, 1, 0.9f));
|
||||
_label.AddThemeColorOverride("font_color", Colors.White);
|
||||
AddChild(_label);
|
||||
|
||||
// Cargo indicator (hidden by default)
|
||||
_cargoIndicator = new ColorRect
|
||||
{
|
||||
Size = new Vector2(12, 12),
|
||||
Position = new Vector2(-6, -28),
|
||||
Size = new Vector2(14, 14),
|
||||
Position = new Vector2(-7, -30),
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_cargoIndicator);
|
||||
|
||||
// Entrance animation: scale bounce
|
||||
Scale = Vector2.Zero;
|
||||
var entranceTween = CreateTween();
|
||||
entranceTween.TweenProperty(this, "scale", new Vector2(1.15f, 1.15f), 0.12f)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Back);
|
||||
entranceTween.TweenProperty(this, "scale", Vector2.One, 0.08f)
|
||||
.SetEase(Tween.EaseType.InOut);
|
||||
}
|
||||
|
||||
public static Color GetPieceColor(PieceKind kind) => kind switch
|
||||
{
|
||||
PieceKind.Pawn => PawnColor,
|
||||
PieceKind.Rook => RookColor,
|
||||
PieceKind.Bishop => BishopColor,
|
||||
PieceKind.Knight => KnightColor,
|
||||
PieceKind.Queen => QueenColor,
|
||||
_ => Colors.White
|
||||
};
|
||||
|
||||
public void SetCargo(CargoType? cargo)
|
||||
{
|
||||
_cargoPulseTween?.Kill();
|
||||
_cargoPulseTween = null;
|
||||
|
||||
if (cargo == null)
|
||||
{
|
||||
_cargoIndicator.Visible = false;
|
||||
_cargoIndicator.Scale = Vector2.One;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -138,16 +100,6 @@ public partial class PieceView : Node2D
|
|||
CargoType.Stone => StoneCargoColor,
|
||||
_ => Colors.White
|
||||
};
|
||||
|
||||
// Cargo pulse: gentle breathing
|
||||
_cargoPulseTween = CreateTween();
|
||||
_cargoPulseTween.SetLoops();
|
||||
_cargoPulseTween.TweenProperty(_cargoIndicator, "scale",
|
||||
new Vector2(1.25f, 1.25f), 0.5f)
|
||||
.SetEase(Tween.EaseType.InOut).SetTrans(Tween.TransitionType.Sine);
|
||||
_cargoPulseTween.TweenProperty(_cargoIndicator, "scale",
|
||||
Vector2.One, 0.5f)
|
||||
.SetEase(Tween.EaseType.InOut).SetTrans(Tween.TransitionType.Sine);
|
||||
}
|
||||
|
||||
public void AnimateMoveTo(Vector2 target, float duration = 0.3f)
|
||||
|
|
@ -155,6 +107,7 @@ public partial class PieceView : Node2D
|
|||
var tween = CreateTween();
|
||||
if (Kind == PieceKind.Knight)
|
||||
{
|
||||
// Arc animation for knight
|
||||
var mid = (Position + target) / 2 + new Vector2(0, -30);
|
||||
tween.TweenMethod(Callable.From<float>(t =>
|
||||
{
|
||||
|
|
@ -166,8 +119,7 @@ public partial class PieceView : Node2D
|
|||
}
|
||||
else
|
||||
{
|
||||
tween.TweenProperty(this, "position", target, duration)
|
||||
.SetEase(Tween.EaseType.InOut).SetTrans(Tween.TransitionType.Sine);
|
||||
tween.TweenProperty(this, "position", target, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,34 +5,15 @@ namespace Chessistics.Scripts.Pieces;
|
|||
public partial class TrajectView : Line2D
|
||||
{
|
||||
public int PieceId { get; private set; }
|
||||
private Polygon2D? _arrow;
|
||||
|
||||
public void Setup(int pieceId, Vector2 from, Vector2 to, Color color)
|
||||
{
|
||||
PieceId = pieceId;
|
||||
Width = 2.5f;
|
||||
DefaultColor = new Color(color, 0.35f);
|
||||
Antialiased = true;
|
||||
Width = 3f;
|
||||
DefaultColor = new Color(color, 0.5f);
|
||||
ClearPoints();
|
||||
AddPoint(from);
|
||||
AddPoint(to);
|
||||
ZIndex = -1;
|
||||
|
||||
// Arrowhead at the end point
|
||||
var dir = (to - from).Normalized();
|
||||
var perp = new Vector2(-dir.Y, dir.X);
|
||||
float arrowSize = 8f;
|
||||
var tip = to - dir * 4f; // slightly inset from end
|
||||
var baseL = tip - dir * arrowSize + perp * arrowSize * 0.5f;
|
||||
var baseR = tip - dir * arrowSize - perp * arrowSize * 0.5f;
|
||||
|
||||
_arrow = new Polygon2D
|
||||
{
|
||||
Polygon = [tip - Position, baseL - Position, baseR - Position],
|
||||
Color = new Color(color, 0.4f),
|
||||
Position = Vector2.Zero
|
||||
};
|
||||
// Position relative to parent, not this Line2D
|
||||
AddChild(_arrow);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ public partial class EventAnimator : Node
|
|||
private bool _animating;
|
||||
public bool IsAnimating => _animating;
|
||||
|
||||
private static readonly Color WoodCargoColor = new("#A67C32");
|
||||
private static readonly Color StoneCargoColor = new("#7A7A7A");
|
||||
private static readonly Color WoodCargoColor = new("#8B6914");
|
||||
private static readonly Color StoneCargoColor = new("#808080");
|
||||
|
||||
private const float ProduceDuration = 0.35f;
|
||||
private const float TransferDuration = 0.28f;
|
||||
private const float MoveDuration = 0.32f;
|
||||
private const float KnightMoveDuration = 0.42f;
|
||||
private const float DestroyDuration = 0.45f;
|
||||
private const float ProduceDuration = 0.3f;
|
||||
private const float TransferDuration = 0.25f;
|
||||
private const float MoveDuration = 0.3f;
|
||||
private const float KnightMoveDuration = 0.4f;
|
||||
private const float DestroyDuration = 0.3f;
|
||||
|
||||
[Signal]
|
||||
public delegate void TurnAnimationCompletedEventHandler();
|
||||
|
|
@ -107,8 +107,6 @@ public partial class EventAnimator : Node
|
|||
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
SfxManager.Instance?.PlayVictory();
|
||||
SpawnConfetti();
|
||||
_metricsOverlay.ShowMetrics(victory.Metrics);
|
||||
EmitSignal(SignalName.VictoryReached);
|
||||
}));
|
||||
|
|
@ -139,50 +137,45 @@ public partial class EventAnimator : Node
|
|||
List<PieceMovedEvent> moveEvents,
|
||||
List<PieceDestroyedEvent> collisionEvents)
|
||||
{
|
||||
// Phase 1: Produce — warm golden flash + particle burst
|
||||
// Phase 1: Produce — flash production cells
|
||||
if (produceEvents.Count > 0)
|
||||
{
|
||||
var captured = produceEvents.ToList();
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
SfxManager.Instance?.PlayProduce();
|
||||
foreach (var evt in captured)
|
||||
foreach (var evt in produceEvents.ToList())
|
||||
{
|
||||
var cell = _boardView.GetCellView(evt.ProductionCell);
|
||||
cell?.FlashProduce(ProduceDuration);
|
||||
SpawnProduceParticles(evt.ProductionCell, evt.Type);
|
||||
}
|
||||
}));
|
||||
tween.TweenInterval(ProduceDuration);
|
||||
produceEvents.Clear();
|
||||
}
|
||||
|
||||
// Phase 2: Transfers — cargo slides with trail particles
|
||||
// Phase 2: Transfers — animate cargo sliding from giver to receiver
|
||||
if (transferEvents.Count > 0)
|
||||
{
|
||||
// Capture the events list before clearing
|
||||
var eventsToAnimate = transferEvents.ToList();
|
||||
|
||||
// Step 1: remove cargo from givers + spawn sliding cargo sprites
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
bool hasDelivery = false;
|
||||
foreach (var evt in eventsToAnimate)
|
||||
{
|
||||
if (evt is CargoTransferredEvent transfer)
|
||||
{
|
||||
// Remove cargo indicator from giver
|
||||
if (transfer.GivingPieceId != null && _pieceViews.ContainsKey(transfer.GivingPieceId.Value))
|
||||
_pieceViews[transfer.GivingPieceId.Value].SetCargo(null);
|
||||
|
||||
// Create sliding cargo sprite
|
||||
SpawnCargoSlide(transfer);
|
||||
|
||||
if (transfer.ReceivingPieceId == null) hasDelivery = true;
|
||||
}
|
||||
}
|
||||
if (hasDelivery)
|
||||
SfxManager.Instance?.PlayDeliver();
|
||||
else
|
||||
SfxManager.Instance?.PlayTransfer();
|
||||
}));
|
||||
|
||||
// Step 2: wait for slide, then show cargo on receivers + update demand progress
|
||||
tween.TweenInterval(TransferDuration);
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
|
|
@ -203,10 +196,9 @@ public partial class EventAnimator : Node
|
|||
transferEvents.Clear();
|
||||
}
|
||||
|
||||
// Phase 3: Movement — simultaneous, with sfx
|
||||
// Phase 3: Movement — all pieces move simultaneously
|
||||
if (moveEvents.Count > 0)
|
||||
{
|
||||
tween.TweenCallback(Callable.From(() => SfxManager.Instance?.PlayMove()));
|
||||
tween.SetParallel(true);
|
||||
foreach (var moved in moveEvents)
|
||||
{
|
||||
|
|
@ -214,218 +206,74 @@ public partial class EventAnimator : Node
|
|||
{
|
||||
var target = _boardView.CoordsToPixel(moved.To);
|
||||
float duration = pv.Kind == PieceKind.Knight ? KnightMoveDuration : MoveDuration;
|
||||
tween.TweenProperty(pv, "position", target, duration)
|
||||
.SetEase(Tween.EaseType.InOut).SetTrans(Tween.TransitionType.Sine);
|
||||
tween.TweenProperty(pv, "position", target, duration);
|
||||
}
|
||||
}
|
||||
tween.SetParallel(false);
|
||||
moveEvents.Clear();
|
||||
}
|
||||
|
||||
// Phase 4: Collision/Destruction — shrink + spin + particles
|
||||
// Phase 4: Collision/Destruction
|
||||
if (collisionEvents.Count > 0)
|
||||
{
|
||||
var captured = collisionEvents.ToList();
|
||||
tween.SetParallel(true);
|
||||
foreach (var destroyed in collisionEvents)
|
||||
{
|
||||
var pieceId = destroyed.PieceId;
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
SfxManager.Instance?.PlayDestroy();
|
||||
foreach (var destroyed in captured)
|
||||
{
|
||||
if (_pieceViews.TryGetValue(destroyed.PieceId, out var pv))
|
||||
{
|
||||
SpawnDestroyParticles(pv.Position);
|
||||
|
||||
var dt = pv.CreateTween();
|
||||
dt.SetParallel(true);
|
||||
dt.TweenProperty(pv, "scale", Vector2.Zero, DestroyDuration)
|
||||
.SetEase(Tween.EaseType.In).SetTrans(Tween.TransitionType.Back);
|
||||
dt.TweenProperty(pv, "rotation", Mathf.Pi * 2f, DestroyDuration);
|
||||
dt.TweenProperty(pv, "modulate:a", 0f, DestroyDuration);
|
||||
}
|
||||
}
|
||||
FlashPiece(pieceId);
|
||||
UnregisterPiece(pieceId);
|
||||
}));
|
||||
}
|
||||
tween.SetParallel(false);
|
||||
tween.TweenInterval(DestroyDuration);
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
foreach (var destroyed in captured)
|
||||
UnregisterPiece(destroyed.PieceId);
|
||||
}));
|
||||
collisionEvents.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Visual Effects ---
|
||||
|
||||
/// <summary>
|
||||
/// Creates a temporary colored square that slides from the giver to the receiver.
|
||||
/// </summary>
|
||||
private void SpawnCargoSlide(CargoTransferredEvent transfer)
|
||||
{
|
||||
var from = _boardView.CoordsToPixel(transfer.From);
|
||||
var to = _boardView.CoordsToPixel(transfer.To);
|
||||
var color = GetCargoColor(transfer.Type);
|
||||
|
||||
var container = new Node2D { Position = from };
|
||||
_boardView.AddChild(container);
|
||||
|
||||
// Main cargo square
|
||||
var main = new ColorRect
|
||||
{
|
||||
Size = new Vector2(12, 12),
|
||||
Position = new Vector2(-6, -6),
|
||||
Color = color,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
container.AddChild(main);
|
||||
|
||||
// Trail particles (2 smaller squares that follow with delay)
|
||||
for (int i = 1; i <= 2; i++)
|
||||
{
|
||||
float trailDelay = i * 0.04f;
|
||||
float trailSize = 12f - i * 3f;
|
||||
float trailAlpha = 0.6f - i * 0.2f;
|
||||
|
||||
var trail = new ColorRect
|
||||
{
|
||||
Size = new Vector2(trailSize, trailSize),
|
||||
Position = new Vector2(-trailSize / 2f, -trailSize / 2f),
|
||||
Color = new Color(color, trailAlpha),
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
var trailContainer = new Node2D { Position = from };
|
||||
trailContainer.AddChild(trail);
|
||||
_boardView.AddChild(trailContainer);
|
||||
|
||||
var trailTween = trailContainer.CreateTween();
|
||||
trailTween.TweenInterval(trailDelay);
|
||||
trailTween.TweenProperty(trailContainer, "position", to, TransferDuration - trailDelay)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Back);
|
||||
trailTween.TweenCallback(Callable.From(() => trailContainer.QueueFree()));
|
||||
}
|
||||
|
||||
// Main slide with whip easing
|
||||
var slideTween = container.CreateTween();
|
||||
slideTween.TweenProperty(container, "position", to, TransferDuration)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Back);
|
||||
slideTween.TweenCallback(Callable.From(() => container.QueueFree()));
|
||||
}
|
||||
|
||||
private void SpawnProduceParticles(Coords cell, CargoType type)
|
||||
{
|
||||
var center = _boardView.CoordsToPixel(cell);
|
||||
var color = GetCargoColor(type);
|
||||
var rng = new Random();
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
float angle = i * Mathf.Pi * 2f / 6f + (float)rng.NextDouble() * 0.5f;
|
||||
float dist = 25f + (float)rng.NextDouble() * 15f;
|
||||
float size = 4f + (float)rng.NextDouble() * 4f;
|
||||
|
||||
var particle = new ColorRect
|
||||
{
|
||||
Size = new Vector2(size, size),
|
||||
Position = new Vector2(-size / 2f, -size / 2f),
|
||||
Color = new Color(color, 0.8f),
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
var pNode = new Node2D { Position = center };
|
||||
pNode.AddChild(particle);
|
||||
_boardView.AddChild(pNode);
|
||||
|
||||
var target = center + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * dist;
|
||||
var pt = pNode.CreateTween();
|
||||
pt.SetParallel(true);
|
||||
pt.TweenProperty(pNode, "position", target, ProduceDuration * 0.8f)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||
pt.TweenProperty(pNode, "modulate:a", 0f, ProduceDuration);
|
||||
pt.SetParallel(false);
|
||||
pt.TweenCallback(Callable.From(() => pNode.QueueFree()));
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnDestroyParticles(Vector2 position)
|
||||
{
|
||||
var rng = new Random();
|
||||
var red = new Color(0.9f, 0.25f, 0.2f);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
float angle = (float)rng.NextDouble() * Mathf.Pi * 2f;
|
||||
float dist = 20f + (float)rng.NextDouble() * 30f;
|
||||
float size = 3f + (float)rng.NextDouble() * 5f;
|
||||
|
||||
var particle = new ColorRect
|
||||
{
|
||||
Size = new Vector2(size, size),
|
||||
Position = new Vector2(-size / 2f, -size / 2f),
|
||||
Color = new Color(red, 0.9f),
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
var pNode = new Node2D { Position = position };
|
||||
pNode.AddChild(particle);
|
||||
_boardView.AddChild(pNode);
|
||||
|
||||
var target = position + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * dist;
|
||||
var pt = pNode.CreateTween();
|
||||
pt.SetParallel(true);
|
||||
pt.TweenProperty(pNode, "position", target, DestroyDuration * 0.7f)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||
pt.TweenProperty(pNode, "modulate:a", 0f, DestroyDuration);
|
||||
pt.TweenProperty(pNode, "scale", Vector2.Zero, DestroyDuration)
|
||||
.SetEase(Tween.EaseType.In);
|
||||
pt.SetParallel(false);
|
||||
pt.TweenCallback(Callable.From(() => pNode.QueueFree()));
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnConfetti()
|
||||
{
|
||||
var rng = new Random();
|
||||
var viewport = GetViewport().GetVisibleRect().Size;
|
||||
Color[] confettiColors = [
|
||||
new("#FFD700"), new("#FF6B35"), new("#3D8E5A"),
|
||||
new("#4A7AB5"), new("#B54A8E"), new("#E8D5A8")
|
||||
];
|
||||
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
float x = (float)rng.NextDouble() * viewport.X;
|
||||
float startY = -20f;
|
||||
float endY = viewport.Y + 50f;
|
||||
float size = 4f + (float)rng.NextDouble() * 6f;
|
||||
float duration = 1.5f + (float)rng.NextDouble() * 1.5f;
|
||||
float delay = (float)rng.NextDouble() * 0.5f;
|
||||
float drift = ((float)rng.NextDouble() - 0.5f) * 80f;
|
||||
|
||||
var confetti = new ColorRect
|
||||
{
|
||||
Size = new Vector2(size, size * 0.5f),
|
||||
Color = confettiColors[rng.Next(confettiColors.Length)],
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
|
||||
// Confetti needs to be in screen space (CanvasLayer)
|
||||
var parent = GetTree().Root;
|
||||
var cNode = new Node2D { Position = new Vector2(x, startY) };
|
||||
cNode.AddChild(confetti);
|
||||
parent.AddChild(cNode);
|
||||
|
||||
var ct = cNode.CreateTween();
|
||||
ct.TweenInterval(delay);
|
||||
ct.SetParallel(true);
|
||||
ct.TweenProperty(cNode, "position", new Vector2(x + drift, endY), duration)
|
||||
.SetEase(Tween.EaseType.In).SetTrans(Tween.TransitionType.Sine);
|
||||
ct.TweenProperty(cNode, "rotation", (float)rng.NextDouble() * Mathf.Pi * 4f, duration);
|
||||
ct.SetParallel(false);
|
||||
ct.TweenCallback(Callable.From(() => cNode.QueueFree()));
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetCargoColor(CargoType type) => type switch
|
||||
var color = transfer.Type switch
|
||||
{
|
||||
CargoType.Wood => WoodCargoColor,
|
||||
CargoType.Stone => StoneCargoColor,
|
||||
_ => Colors.White
|
||||
};
|
||||
|
||||
var sprite = new ColorRect
|
||||
{
|
||||
Size = new Vector2(14, 14),
|
||||
Position = new Vector2(-7, -7),
|
||||
Color = color,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
|
||||
var container = new Node2D { Position = from };
|
||||
container.AddChild(sprite);
|
||||
_boardView.AddChild(container);
|
||||
|
||||
var slideTween = container.CreateTween();
|
||||
slideTween.TweenProperty(container, "position", to, TransferDuration)
|
||||
.SetEase(Tween.EaseType.InOut)
|
||||
.SetTrans(Tween.TransitionType.Cubic);
|
||||
slideTween.TweenCallback(Callable.From(() => container.QueueFree()));
|
||||
}
|
||||
|
||||
private void FlashPiece(int pieceId)
|
||||
{
|
||||
if (!_pieceViews.TryGetValue(pieceId, out var pv)) return;
|
||||
var tween = pv.CreateTween();
|
||||
tween.TweenProperty(pv, "modulate", new Color(1, 0.2f, 0.2f), 0.1f);
|
||||
tween.TweenProperty(pv, "modulate", Colors.White, 0.1f);
|
||||
tween.SetLoops(3);
|
||||
}
|
||||
|
||||
public void ResetPiecePositions(BoardSnapshot snapshot)
|
||||
{
|
||||
foreach (var ps in snapshot.Pieces)
|
||||
|
|
|
|||
|
|
@ -1,160 +0,0 @@
|
|||
using Godot;
|
||||
using System;
|
||||
|
||||
namespace Chessistics.Scripts.Presentation;
|
||||
|
||||
/// <summary>
|
||||
/// Procedural sound effects via synthesized waveforms.
|
||||
/// No external audio files needed — everything is generated in code.
|
||||
/// </summary>
|
||||
public partial class SfxManager : Node
|
||||
{
|
||||
private const int SampleRate = 22050;
|
||||
private const float MasterVolume = 0.15f;
|
||||
|
||||
public static SfxManager? Instance { get; private set; }
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
// --- Public API ---
|
||||
|
||||
public void PlayPlace() => PlayTone(523.25f, 0.08f, vol: 0.5f); // C5 short blip
|
||||
public void PlayProduce() => PlayTone(130.81f, 0.12f, vol: 0.3f, wave: Wave.Triangle); // C3 warm
|
||||
public void PlayTransfer() => PlayNoise(0.12f, vol: 0.15f); // filtered swoosh
|
||||
public void PlayDeliver() => PlayChord([523.25f, 659.25f], 0.18f, vol: 0.4f); // C5+E5 ding
|
||||
public void PlayMove() => PlayNoise(0.04f, vol: 0.08f); // tiny whoosh
|
||||
public void PlayDestroy() => PlaySweep(262f, 65f, 0.18f, vol: 0.4f); // descending crunch
|
||||
public void PlayVictory() => PlayArpeggio([262f, 330f, 392f, 523f], 0.12f, vol: 0.35f); // C-E-G-C arp
|
||||
public void PlayClick() => PlayTone(880f, 0.02f, vol: 0.2f); // tiny tick
|
||||
|
||||
// --- Synthesis ---
|
||||
|
||||
private enum Wave { Sine, Triangle, Square }
|
||||
|
||||
private void PlayTone(float freq, float duration, float vol = 0.3f, Wave wave = Wave.Sine)
|
||||
{
|
||||
var samples = GenerateTone(freq, duration, vol, wave);
|
||||
PlaySamples(samples);
|
||||
}
|
||||
|
||||
private void PlayNoise(float duration, float vol = 0.2f)
|
||||
{
|
||||
var count = (int)(SampleRate * duration);
|
||||
var samples = new float[count];
|
||||
var rng = new Random();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float t = (float)i / count;
|
||||
float envelope = Envelope(t);
|
||||
samples[i] = (float)(rng.NextDouble() * 2 - 1) * vol * envelope * MasterVolume;
|
||||
}
|
||||
// Simple low-pass: average with previous sample
|
||||
for (int i = count - 1; i > 0; i--)
|
||||
samples[i] = (samples[i] + samples[i - 1]) * 0.5f;
|
||||
PlaySamples(samples);
|
||||
}
|
||||
|
||||
private void PlayChord(float[] freqs, float duration, float vol = 0.3f)
|
||||
{
|
||||
var count = (int)(SampleRate * duration);
|
||||
var samples = new float[count];
|
||||
foreach (var freq in freqs)
|
||||
{
|
||||
var tone = GenerateTone(freq, duration, vol / freqs.Length, Wave.Sine);
|
||||
for (int i = 0; i < count; i++)
|
||||
samples[i] += tone[i];
|
||||
}
|
||||
PlaySamples(samples);
|
||||
}
|
||||
|
||||
private void PlaySweep(float startFreq, float endFreq, float duration, float vol = 0.3f)
|
||||
{
|
||||
var count = (int)(SampleRate * duration);
|
||||
var samples = new float[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float t = (float)i / count;
|
||||
float freq = Mathf.Lerp(startFreq, endFreq, t);
|
||||
float envelope = Envelope(t);
|
||||
float phase = 2f * Mathf.Pi * freq * i / SampleRate;
|
||||
samples[i] = Mathf.Sin(phase) * vol * envelope * MasterVolume;
|
||||
}
|
||||
PlaySamples(samples);
|
||||
}
|
||||
|
||||
private void PlayArpeggio(float[] notes, float noteLength, float vol = 0.3f)
|
||||
{
|
||||
float totalDuration = noteLength * notes.Length;
|
||||
var totalCount = (int)(SampleRate * totalDuration);
|
||||
var samples = new float[totalCount];
|
||||
var noteCount = (int)(SampleRate * noteLength);
|
||||
|
||||
for (int n = 0; n < notes.Length; n++)
|
||||
{
|
||||
int offset = n * noteCount;
|
||||
var tone = GenerateTone(notes[n], noteLength, vol, Wave.Sine);
|
||||
for (int i = 0; i < tone.Length && offset + i < totalCount; i++)
|
||||
samples[offset + i] += tone[i];
|
||||
}
|
||||
PlaySamples(samples);
|
||||
}
|
||||
|
||||
private float[] GenerateTone(float freq, float duration, float vol, Wave wave)
|
||||
{
|
||||
var count = (int)(SampleRate * duration);
|
||||
var samples = new float[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float t = (float)i / count;
|
||||
float phase = 2f * Mathf.Pi * freq * i / SampleRate;
|
||||
float value = wave switch
|
||||
{
|
||||
Wave.Triangle => 2f * Mathf.Abs(2f * ((freq * i / SampleRate) % 1f) - 1f) - 1f,
|
||||
Wave.Square => Mathf.Sin(phase) >= 0 ? 1f : -1f,
|
||||
_ => Mathf.Sin(phase)
|
||||
};
|
||||
float envelope = Envelope(t);
|
||||
samples[i] = value * vol * envelope * MasterVolume;
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
/// <summary>Simple ADSR-ish envelope: quick attack, sustain, smooth release.</summary>
|
||||
private static float Envelope(float t)
|
||||
{
|
||||
if (t < 0.05f) return t / 0.05f; // attack
|
||||
if (t < 0.3f) return 1f; // sustain
|
||||
return 1f - (t - 0.3f) / 0.7f; // release
|
||||
}
|
||||
|
||||
private void PlaySamples(float[] samples)
|
||||
{
|
||||
var stream = new AudioStreamWav
|
||||
{
|
||||
Format = AudioStreamWav.FormatEnum.Format16Bits,
|
||||
MixRate = SampleRate,
|
||||
Stereo = false,
|
||||
Data = FloatsToWav16(samples)
|
||||
};
|
||||
|
||||
var player = new AudioStreamPlayer { Stream = stream, VolumeDb = -6f };
|
||||
AddChild(player);
|
||||
player.Finished += () => player.QueueFree();
|
||||
player.Play();
|
||||
}
|
||||
|
||||
private static byte[] FloatsToWav16(float[] samples)
|
||||
{
|
||||
var bytes = new byte[samples.Length * 2];
|
||||
for (int i = 0; i < samples.Length; i++)
|
||||
{
|
||||
short val = (short)(Mathf.Clamp(samples[i], -1f, 1f) * 32767);
|
||||
bytes[i * 2] = (byte)(val & 0xFF);
|
||||
bytes[i * 2 + 1] = (byte)((val >> 8) & 0xFF);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://budg72rej2hx3
|
||||
|
|
@ -24,92 +24,38 @@ public partial class ControlBar : HBoxContainer
|
|||
private OptionButton _speedSelect = null!;
|
||||
private Label _turnLabel = null!;
|
||||
|
||||
private static readonly Color BtnBg = new("#2A2A2E");
|
||||
private static readonly Color BtnHover = new("#3A3A40");
|
||||
private static readonly Color BtnPressed = new("#1A1A1E");
|
||||
private static readonly Color BtnDisabled = new("#1E1E20");
|
||||
private static readonly Color BtnBorder = new("#444448");
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
AddThemeConstantOverride("separation", 8);
|
||||
|
||||
_playButton = CreateStyledButton("PLAY");
|
||||
_playButton = new Button { Text = "▶ PLAY" };
|
||||
_playButton.Pressed += () => EmitSignal(SignalName.PlayPressed);
|
||||
AddChild(_playButton);
|
||||
|
||||
_pauseButton = CreateStyledButton("PAUSE");
|
||||
_pauseButton = new Button { Text = "⏸ PAUSE" };
|
||||
_pauseButton.Pressed += () => EmitSignal(SignalName.PausePressed);
|
||||
AddChild(_pauseButton);
|
||||
|
||||
_stepButton = CreateStyledButton("STEP");
|
||||
_stepButton = new Button { Text = "⏭ STEP" };
|
||||
_stepButton.Pressed += () => EmitSignal(SignalName.StepPressed);
|
||||
AddChild(_stepButton);
|
||||
|
||||
_stopButton = CreateStyledButton("STOP");
|
||||
_stopButton = new Button { Text = "⏹ STOP" };
|
||||
_stopButton.Pressed += () => EmitSignal(SignalName.StopPressed);
|
||||
AddChild(_stopButton);
|
||||
|
||||
// Spacer
|
||||
AddChild(new Control { CustomMinimumSize = new Vector2(12, 0) });
|
||||
|
||||
_speedSelect = new OptionButton { CustomMinimumSize = new Vector2(60, 30) };
|
||||
_speedSelect = new OptionButton();
|
||||
_speedSelect.AddItem("x1", 0);
|
||||
_speedSelect.AddItem("x2", 1);
|
||||
_speedSelect.AddItem("x4", 2);
|
||||
_speedSelect.ItemSelected += OnSpeedSelected;
|
||||
AddChild(_speedSelect);
|
||||
|
||||
// Spacer
|
||||
AddChild(new Control { SizeFlagsHorizontal = SizeFlags.ExpandFill });
|
||||
|
||||
_turnLabel = new Label { Text = "Coup: --" };
|
||||
_turnLabel.AddThemeFontSizeOverride("font_size", 13);
|
||||
_turnLabel.AddThemeColorOverride("font_color", new Color("#999999"));
|
||||
_turnLabel.AddThemeFontSizeOverride("font_size", 14);
|
||||
AddChild(_turnLabel);
|
||||
|
||||
UpdateForPhase(SimPhase.Edit);
|
||||
}
|
||||
|
||||
private static Button CreateStyledButton(string text)
|
||||
{
|
||||
var btn = new Button
|
||||
{
|
||||
Text = text,
|
||||
CustomMinimumSize = new Vector2(70, 30)
|
||||
};
|
||||
btn.AddThemeFontSizeOverride("font_size", 11);
|
||||
|
||||
var normal = MakeStyle(BtnBg);
|
||||
var hover = MakeStyle(BtnHover);
|
||||
var pressed = MakeStyle(BtnPressed);
|
||||
var disabled = MakeStyle(BtnDisabled);
|
||||
disabled.BorderColor = new Color("#2A2A2E");
|
||||
|
||||
btn.AddThemeStyleboxOverride("normal", normal);
|
||||
btn.AddThemeStyleboxOverride("hover", hover);
|
||||
btn.AddThemeStyleboxOverride("pressed", pressed);
|
||||
btn.AddThemeStyleboxOverride("disabled", disabled);
|
||||
btn.AddThemeColorOverride("font_disabled_color", new Color("#555555"));
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
private static StyleBoxFlat MakeStyle(Color bg)
|
||||
{
|
||||
return new StyleBoxFlat
|
||||
{
|
||||
BgColor = bg,
|
||||
BorderColor = BtnBorder,
|
||||
BorderWidthBottom = 1, BorderWidthTop = 1,
|
||||
BorderWidthLeft = 1, BorderWidthRight = 1,
|
||||
CornerRadiusTopLeft = 4, CornerRadiusTopRight = 4,
|
||||
CornerRadiusBottomLeft = 4, CornerRadiusBottomRight = 4,
|
||||
ContentMarginLeft = 10, ContentMarginRight = 10,
|
||||
ContentMarginTop = 4, ContentMarginBottom = 4
|
||||
};
|
||||
}
|
||||
|
||||
private void OnSpeedSelected(long index)
|
||||
{
|
||||
float speed = index switch
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Godot;
|
||||
using Chessistics.Engine.Model;
|
||||
using Chessistics.Scripts.Pieces;
|
||||
|
||||
namespace Chessistics.Scripts.UI;
|
||||
|
||||
|
|
@ -15,41 +14,17 @@ public partial class DetailPanel : PanelContainer
|
|||
|
||||
public override void _Ready()
|
||||
{
|
||||
var style = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color(0.14f, 0.14f, 0.16f, 0.95f),
|
||||
BorderColor = new Color("#444448"),
|
||||
BorderWidthTop = 1,
|
||||
CornerRadiusTopLeft = 4, CornerRadiusTopRight = 4,
|
||||
ContentMarginLeft = 12, ContentMarginRight = 12,
|
||||
ContentMarginTop = 8, ContentMarginBottom = 8
|
||||
};
|
||||
AddThemeStyleboxOverride("panel", style);
|
||||
|
||||
var vbox = new VBoxContainer();
|
||||
vbox.AddThemeConstantOverride("separation", 4);
|
||||
|
||||
var title = new Label { Text = "DETAIL" };
|
||||
title.AddThemeFontSizeOverride("font_size", 13);
|
||||
title.AddThemeColorOverride("font_color", new Color("#B8942A"));
|
||||
title.AddThemeFontSizeOverride("font_size", 14);
|
||||
vbox.AddChild(title);
|
||||
|
||||
_infoLabel = new Label { Text = "" };
|
||||
_infoLabel.AddThemeFontSizeOverride("font_size", 11);
|
||||
_infoLabel.AddThemeColorOverride("font_color", new Color("#CCCCCC"));
|
||||
vbox.AddChild(_infoLabel);
|
||||
|
||||
_removeButton = new Button { Text = "Retirer", CustomMinimumSize = new Vector2(80, 26) };
|
||||
_removeButton.AddThemeFontSizeOverride("font_size", 11);
|
||||
var btnStyle = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("#5A2A2A"),
|
||||
CornerRadiusTopLeft = 4, CornerRadiusTopRight = 4,
|
||||
CornerRadiusBottomLeft = 4, CornerRadiusBottomRight = 4,
|
||||
ContentMarginLeft = 8, ContentMarginRight = 8,
|
||||
ContentMarginTop = 2, ContentMarginBottom = 2
|
||||
};
|
||||
_removeButton.AddThemeStyleboxOverride("normal", btnStyle);
|
||||
_removeButton = new Button { Text = "Retirer" };
|
||||
_removeButton.Pressed += () => EmitSignal(SignalName.RemoveRequested, _currentPieceId);
|
||||
vbox.AddChild(_removeButton);
|
||||
|
||||
|
|
@ -62,11 +37,9 @@ public partial class DetailPanel : PanelContainer
|
|||
_currentPieceId = piece.Id;
|
||||
var kindName = piece.Kind switch
|
||||
{
|
||||
PieceKind.Pawn => "Pion",
|
||||
PieceKind.Rook => "Tour II",
|
||||
PieceKind.Bishop => "Fou II",
|
||||
PieceKind.Knight => "Cavalier",
|
||||
PieceKind.Queen => "Dame",
|
||||
_ => piece.Kind.ToString()
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ public partial class LevelSelectScreen : Control
|
|||
("Le Col", "Franchissez le mur et gerez deux types de cargaison."),
|
||||
("Le Carrefour", "Deux productions, deux demandes, et un carrefour au centre."),
|
||||
("Le Labyrinthe", "Un couloir etroit serpente a travers les murs."),
|
||||
("Trois Royaumes", "Trois productions, trois demandes. Gerez un reseau complet."),
|
||||
("La Dame Blanche", "La Dame entre en jeu. Portee supreme sur 8 directions."),
|
||||
("Le Grand Reseau", "Quatre productions, quatre demandes. Reseau complet.")
|
||||
("Trois Royaumes", "Trois productions, trois demandes. Gerez un reseau complet.")
|
||||
];
|
||||
|
||||
public override void _Ready()
|
||||
|
|
|
|||
|
|
@ -10,62 +10,46 @@ public partial class MetricsOverlay : PanelContainer
|
|||
[Signal]
|
||||
public delegate void RetryPressedEventHandler();
|
||||
|
||||
private Label _titleLabel = null!;
|
||||
private Label _piecesLabel = null!;
|
||||
private Label _turnsLabel = null!;
|
||||
private Label _cellsLabel = null!;
|
||||
private HBoxContainer _buttons = null!;
|
||||
private Label _metricsLabel = null!;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
var style = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color(0.1f, 0.1f, 0.12f, 0.95f),
|
||||
BorderColor = new Color("#B8942A"),
|
||||
BorderWidthBottom = 2, BorderWidthTop = 2,
|
||||
BorderWidthLeft = 2, BorderWidthRight = 2,
|
||||
CornerRadiusTopLeft = 12, CornerRadiusTopRight = 12,
|
||||
CornerRadiusBottomLeft = 12, CornerRadiusBottomRight = 12,
|
||||
ContentMarginLeft = 32, ContentMarginRight = 32,
|
||||
ContentMarginTop = 28, ContentMarginBottom = 28
|
||||
};
|
||||
AddThemeStyleboxOverride("panel", style);
|
||||
|
||||
var vbox = new VBoxContainer();
|
||||
vbox.AddThemeConstantOverride("separation", 8);
|
||||
vbox.SetAnchorsPreset(LayoutPreset.Center);
|
||||
|
||||
_titleLabel = new Label
|
||||
var title = new Label
|
||||
{
|
||||
Text = "VICTOIRE !",
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
_titleLabel.AddThemeFontSizeOverride("font_size", 26);
|
||||
_titleLabel.AddThemeColorOverride("font_color", new Color("#FFD700"));
|
||||
vbox.AddChild(_titleLabel);
|
||||
title.AddThemeFontSizeOverride("font_size", 24);
|
||||
title.AddThemeColorOverride("font_color", new Color("#FFD700"));
|
||||
vbox.AddChild(title);
|
||||
|
||||
vbox.AddChild(new HSeparator());
|
||||
|
||||
_piecesLabel = CreateMetricLabel();
|
||||
vbox.AddChild(_piecesLabel);
|
||||
_turnsLabel = CreateMetricLabel();
|
||||
vbox.AddChild(_turnsLabel);
|
||||
_cellsLabel = CreateMetricLabel();
|
||||
vbox.AddChild(_cellsLabel);
|
||||
_metricsLabel = new Label
|
||||
{
|
||||
Text = "",
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
_metricsLabel.AddThemeFontSizeOverride("font_size", 14);
|
||||
vbox.AddChild(_metricsLabel);
|
||||
|
||||
vbox.AddChild(new HSeparator());
|
||||
|
||||
_buttons = new HBoxContainer { Alignment = BoxContainer.AlignmentMode.Center };
|
||||
_buttons.AddThemeConstantOverride("separation", 16);
|
||||
var buttons = new HBoxContainer();
|
||||
buttons.Alignment = BoxContainer.AlignmentMode.Center;
|
||||
|
||||
var retryBtn = CreateStyledButton("Rejouer");
|
||||
var retryBtn = new Button { Text = "Rejouer" };
|
||||
retryBtn.Pressed += () => EmitSignal(SignalName.RetryPressed);
|
||||
_buttons.AddChild(retryBtn);
|
||||
buttons.AddChild(retryBtn);
|
||||
|
||||
var nextBtn = CreateStyledButton("Niveau suivant");
|
||||
var nextBtn = new Button { Text = "Niveau suivant" };
|
||||
nextBtn.Pressed += () => EmitSignal(SignalName.NextLevelPressed);
|
||||
_buttons.AddChild(nextBtn);
|
||||
buttons.AddChild(nextBtn);
|
||||
|
||||
vbox.AddChild(_buttons);
|
||||
vbox.AddChild(buttons);
|
||||
AddChild(vbox);
|
||||
|
||||
Visible = false;
|
||||
|
|
@ -73,87 +57,11 @@ public partial class MetricsOverlay : PanelContainer
|
|||
|
||||
public void ShowMetrics(Metrics metrics)
|
||||
{
|
||||
_piecesLabel.Text = $"Pieces utilisees: {metrics.PiecesUsed}";
|
||||
_turnsLabel.Text = $"Coups: {metrics.TurnsTaken}";
|
||||
_cellsLabel.Text = $"Cases occupees: {metrics.CellsOccupied}";
|
||||
|
||||
// Start invisible, fade + scale in
|
||||
Modulate = new Color(1, 1, 1, 0);
|
||||
Scale = new Vector2(0.85f, 0.85f);
|
||||
PivotOffset = Size / 2f;
|
||||
_metricsLabel.Text = $"Pieces utilisees: {metrics.PiecesUsed}\n" +
|
||||
$"Coups: {metrics.TurnsTaken}\n" +
|
||||
$"Cases occupees: {metrics.CellsOccupied}";
|
||||
Visible = true;
|
||||
|
||||
// Hide metrics initially, reveal sequentially
|
||||
_piecesLabel.Modulate = new Color(1, 1, 1, 0);
|
||||
_turnsLabel.Modulate = new Color(1, 1, 1, 0);
|
||||
_cellsLabel.Modulate = new Color(1, 1, 1, 0);
|
||||
_buttons.Modulate = new Color(1, 1, 1, 0);
|
||||
|
||||
var tween = CreateTween();
|
||||
// Panel fade in
|
||||
tween.SetParallel(true);
|
||||
tween.TweenProperty(this, "modulate:a", 1f, 0.3f)
|
||||
.SetEase(Tween.EaseType.Out);
|
||||
tween.TweenProperty(this, "scale", Vector2.One, 0.35f)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Back);
|
||||
tween.SetParallel(false);
|
||||
|
||||
// Sequential metric reveals
|
||||
tween.TweenInterval(0.15f);
|
||||
tween.TweenProperty(_piecesLabel, "modulate:a", 1f, 0.2f);
|
||||
tween.TweenInterval(0.1f);
|
||||
tween.TweenProperty(_turnsLabel, "modulate:a", 1f, 0.2f);
|
||||
tween.TweenInterval(0.1f);
|
||||
tween.TweenProperty(_cellsLabel, "modulate:a", 1f, 0.2f);
|
||||
tween.TweenInterval(0.15f);
|
||||
tween.TweenProperty(_buttons, "modulate:a", 1f, 0.2f);
|
||||
}
|
||||
|
||||
public new void Hide()
|
||||
{
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
private static Label CreateMetricLabel()
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
Text = "",
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
label.AddThemeFontSizeOverride("font_size", 14);
|
||||
label.AddThemeColorOverride("font_color", new Color("#CCCCCC"));
|
||||
return label;
|
||||
}
|
||||
|
||||
private static Button CreateStyledButton(string text)
|
||||
{
|
||||
var btn = new Button
|
||||
{
|
||||
Text = text,
|
||||
CustomMinimumSize = new Vector2(130, 36)
|
||||
};
|
||||
|
||||
var normal = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("#3D6B8E"),
|
||||
CornerRadiusTopLeft = 6, CornerRadiusTopRight = 6,
|
||||
CornerRadiusBottomLeft = 6, CornerRadiusBottomRight = 6,
|
||||
ContentMarginLeft = 16, ContentMarginRight = 16,
|
||||
ContentMarginTop = 6, ContentMarginBottom = 6
|
||||
};
|
||||
var hover = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("#4A8EBF"),
|
||||
CornerRadiusTopLeft = 6, CornerRadiusTopRight = 6,
|
||||
CornerRadiusBottomLeft = 6, CornerRadiusBottomRight = 6,
|
||||
ContentMarginLeft = 16, ContentMarginRight = 16,
|
||||
ContentMarginTop = 6, ContentMarginBottom = 6
|
||||
};
|
||||
btn.AddThemeStyleboxOverride("normal", normal);
|
||||
btn.AddThemeStyleboxOverride("hover", hover);
|
||||
btn.AddThemeFontSizeOverride("font_size", 13);
|
||||
|
||||
return btn;
|
||||
}
|
||||
public new void Hide() => Visible = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace Chessistics.Scripts.UI;
|
|||
|
||||
public partial class ObjectivePanel : VBoxContainer
|
||||
{
|
||||
private readonly Dictionary<Coords, (Label label, ProgressBar bar, Label deadline)> _entries = new();
|
||||
private readonly Dictionary<Coords, (Label label, ProgressBar bar)> _entries = new();
|
||||
|
||||
public void Setup(IReadOnlyList<DemandDef> demands)
|
||||
{
|
||||
|
|
@ -16,7 +16,7 @@ public partial class ObjectivePanel : VBoxContainer
|
|||
|
||||
var title = new Label { Text = "OBJECTIFS" };
|
||||
title.AddThemeFontSizeOverride("font_size", 16);
|
||||
title.AddThemeColorOverride("font_color", new Color("#B8942A")); // aged gold
|
||||
title.AddThemeColorOverride("font_color", new Color("#FFD700"));
|
||||
AddChild(title);
|
||||
|
||||
AddChild(new HSeparator());
|
||||
|
|
@ -24,11 +24,9 @@ public partial class ObjectivePanel : VBoxContainer
|
|||
foreach (var demand in demands)
|
||||
{
|
||||
var vbox = new VBoxContainer();
|
||||
vbox.AddThemeConstantOverride("separation", 2);
|
||||
|
||||
var label = new Label { Text = $"{demand.Name}: 0/{demand.Amount} {demand.Cargo}" };
|
||||
label.AddThemeFontSizeOverride("font_size", 12);
|
||||
label.AddThemeColorOverride("font_color", new Color("#CCCCCC"));
|
||||
vbox.AddChild(label);
|
||||
|
||||
var bar = new ProgressBar
|
||||
|
|
@ -36,34 +34,18 @@ public partial class ObjectivePanel : VBoxContainer
|
|||
MinValue = 0,
|
||||
MaxValue = demand.Amount,
|
||||
Value = 0,
|
||||
CustomMinimumSize = new Vector2(180, 14),
|
||||
CustomMinimumSize = new Vector2(180, 16),
|
||||
ShowPercentage = false
|
||||
};
|
||||
|
||||
// Style the progress bar
|
||||
var bgStyle = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color(0.2f, 0.2f, 0.22f),
|
||||
CornerRadiusTopLeft = 3, CornerRadiusTopRight = 3,
|
||||
CornerRadiusBottomLeft = 3, CornerRadiusBottomRight = 3
|
||||
};
|
||||
var fillStyle = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("#3D6B8E"), // teal fill
|
||||
CornerRadiusTopLeft = 3, CornerRadiusTopRight = 3,
|
||||
CornerRadiusBottomLeft = 3, CornerRadiusBottomRight = 3
|
||||
};
|
||||
bar.AddThemeStyleboxOverride("background", bgStyle);
|
||||
bar.AddThemeStyleboxOverride("fill", fillStyle);
|
||||
vbox.AddChild(bar);
|
||||
|
||||
var deadline = new Label { Text = $"Deadline: {demand.Deadline} coups" };
|
||||
deadline.AddThemeFontSizeOverride("font_size", 10);
|
||||
deadline.AddThemeColorOverride("font_color", new Color("#777777"));
|
||||
deadline.AddThemeColorOverride("font_color", new Color("#AAAAAA"));
|
||||
vbox.AddChild(deadline);
|
||||
|
||||
AddChild(vbox);
|
||||
_entries[demand.Position] = (label, bar, deadline);
|
||||
_entries[demand.Position] = (label, bar);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,24 +54,9 @@ public partial class ObjectivePanel : VBoxContainer
|
|||
if (!_entries.TryGetValue(demandCell, out var entry)) return;
|
||||
|
||||
entry.label.Text = $"{name}: {current}/{required}";
|
||||
|
||||
// Animate the progress bar value
|
||||
var tween = entry.bar.CreateTween();
|
||||
tween.TweenProperty(entry.bar, "value", (double)current, 0.2f)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||
entry.bar.Value = current;
|
||||
|
||||
if (current >= required)
|
||||
{
|
||||
entry.label.AddThemeColorOverride("font_color", new Color("#5AAC5A")); // warm green
|
||||
|
||||
// Flash the progress bar green
|
||||
var fillStyle = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("#5AAC5A"),
|
||||
CornerRadiusTopLeft = 3, CornerRadiusTopRight = 3,
|
||||
CornerRadiusBottomLeft = 3, CornerRadiusBottomRight = 3
|
||||
};
|
||||
entry.bar.AddThemeStyleboxOverride("fill", fillStyle);
|
||||
}
|
||||
entry.label.AddThemeColorOverride("font_color", new Color("#44CC44"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using Godot;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Chessistics.Engine.Model;
|
||||
using Chessistics.Scripts.Pieces;
|
||||
|
||||
namespace Chessistics.Scripts.UI;
|
||||
|
||||
|
|
@ -16,13 +15,6 @@ public partial class PieceStockPanel : VBoxContainer
|
|||
|
||||
public PieceKind? SelectedKind => _selectedKind;
|
||||
|
||||
private static readonly Color NormalBg = new("#2A2A2E");
|
||||
private static readonly Color SelectedBg = new("#3D6B8E");
|
||||
private static readonly Color HoverBg = new("#353538");
|
||||
private static readonly Color DisabledBg = new("#1E1E20");
|
||||
private static readonly Color BorderNormal = new("#444448");
|
||||
private static readonly Color BorderSelected = new("#5A9ECC");
|
||||
|
||||
public void Setup(IReadOnlyList<PieceStock> stock)
|
||||
{
|
||||
foreach (var child in GetChildren())
|
||||
|
|
@ -32,7 +24,7 @@ public partial class PieceStockPanel : VBoxContainer
|
|||
|
||||
var title = new Label { Text = "PIECES" };
|
||||
title.AddThemeFontSizeOverride("font_size", 16);
|
||||
title.AddThemeColorOverride("font_color", new Color("#B8942A"));
|
||||
title.AddThemeColorOverride("font_color", new Color("#FFD700"));
|
||||
AddChild(title);
|
||||
|
||||
AddChild(new HSeparator());
|
||||
|
|
@ -40,31 +32,16 @@ public partial class PieceStockPanel : VBoxContainer
|
|||
foreach (var entry in stock)
|
||||
{
|
||||
var hbox = new HBoxContainer();
|
||||
hbox.AddThemeConstantOverride("separation", 8);
|
||||
|
||||
// Color dot matching piece color
|
||||
var dot = new ColorRect
|
||||
{
|
||||
CustomMinimumSize = new Vector2(10, 10),
|
||||
Size = new Vector2(10, 10),
|
||||
Color = PieceView.GetPieceColor(entry.Kind),
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
var dotCenter = new CenterContainer { CustomMinimumSize = new Vector2(14, 32) };
|
||||
dotCenter.AddChild(dot);
|
||||
hbox.AddChild(dotCenter);
|
||||
|
||||
var button = new Button
|
||||
{
|
||||
Text = GetPieceName(entry.Kind),
|
||||
CustomMinimumSize = new Vector2(120, 32),
|
||||
ToggleMode = false // We manage selection state ourselves
|
||||
ToggleMode = true
|
||||
};
|
||||
ApplyButtonStyle(button, false);
|
||||
|
||||
var countLabel = new Label { Text = $"x{entry.Count}" };
|
||||
countLabel.AddThemeFontSizeOverride("font_size", 13);
|
||||
countLabel.AddThemeColorOverride("font_color", new Color("#999999"));
|
||||
countLabel.AddThemeFontSizeOverride("font_size", 14);
|
||||
|
||||
var kind = entry.Kind;
|
||||
button.Pressed += () => OnPieceButtonPressed(kind);
|
||||
|
|
@ -95,45 +72,11 @@ public partial class PieceStockPanel : VBoxContainer
|
|||
{
|
||||
foreach (var (k, (button, _, remaining)) in _entries)
|
||||
{
|
||||
bool selected = k == _selectedKind;
|
||||
button.ButtonPressed = k == _selectedKind;
|
||||
button.Disabled = remaining <= 0;
|
||||
ApplyButtonStyle(button, selected);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyButtonStyle(Button button, bool selected)
|
||||
{
|
||||
var bg = selected ? SelectedBg : NormalBg;
|
||||
var border = selected ? BorderSelected : BorderNormal;
|
||||
|
||||
var normal = MakeStyle(bg, border);
|
||||
var hover = MakeStyle(selected ? SelectedBg.Lightened(0.1f) : HoverBg, border);
|
||||
var disabled = MakeStyle(DisabledBg, new Color("#2A2A2E"));
|
||||
|
||||
button.AddThemeStyleboxOverride("normal", normal);
|
||||
button.AddThemeStyleboxOverride("hover", hover);
|
||||
button.AddThemeStyleboxOverride("pressed", MakeStyle(bg.Darkened(0.15f), border));
|
||||
button.AddThemeStyleboxOverride("disabled", disabled);
|
||||
button.AddThemeFontSizeOverride("font_size", 12);
|
||||
button.AddThemeColorOverride("font_color", selected ? Colors.White : new Color("#CCCCCC"));
|
||||
button.AddThemeColorOverride("font_disabled_color", new Color("#555555"));
|
||||
}
|
||||
|
||||
private static StyleBoxFlat MakeStyle(Color bg, Color border)
|
||||
{
|
||||
return new StyleBoxFlat
|
||||
{
|
||||
BgColor = bg,
|
||||
BorderColor = border,
|
||||
BorderWidthBottom = 1, BorderWidthTop = 1,
|
||||
BorderWidthLeft = 1, BorderWidthRight = 1,
|
||||
CornerRadiusTopLeft = 4, CornerRadiusTopRight = 4,
|
||||
CornerRadiusBottomLeft = 4, CornerRadiusBottomRight = 4,
|
||||
ContentMarginLeft = 8, ContentMarginRight = 8,
|
||||
ContentMarginTop = 4, ContentMarginBottom = 4
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateCount(PieceKind kind, int remaining)
|
||||
{
|
||||
if (!_entries.TryGetValue(kind, out var entry)) return;
|
||||
|
|
@ -154,7 +97,6 @@ public partial class PieceStockPanel : VBoxContainer
|
|||
PieceKind.Rook => "Tour II",
|
||||
PieceKind.Bishop => "Fou II",
|
||||
PieceKind.Knight => "Cavalier",
|
||||
PieceKind.Queen => "Dame",
|
||||
_ => kind.ToString()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ public static class LevelLoader
|
|||
"rook" => PieceKind.Rook,
|
||||
"bishop" => PieceKind.Bishop,
|
||||
"knight" => PieceKind.Knight,
|
||||
"queen" => PieceKind.Queen,
|
||||
_ => throw new JsonException($"Unknown piece kind: '{kind}'")
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,5 @@ public enum PieceKind
|
|||
Pawn,
|
||||
Rook,
|
||||
Bishop,
|
||||
Knight,
|
||||
Queen
|
||||
Knight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ public static class PieceRules
|
|||
PieceKind.Rook => 5,
|
||||
PieceKind.Bishop => 3,
|
||||
PieceKind.Knight => 3,
|
||||
PieceKind.Queen => 7,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind))
|
||||
};
|
||||
|
||||
|
|
@ -18,7 +17,6 @@ public static class PieceRules
|
|||
PieceKind.Rook => 2,
|
||||
PieceKind.Bishop => 2,
|
||||
PieceKind.Knight => 0, // Knight uses L-shape, not range
|
||||
PieceKind.Queen => 2,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind))
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ public static class MoveValidator
|
|||
{
|
||||
private static readonly (int dc, int dr)[] OrthogonalDirs = [(0, 1), (0, -1), (1, 0), (-1, 0)];
|
||||
private static readonly (int dc, int dr)[] DiagonalDirs = [(1, 1), (1, -1), (-1, 1), (-1, -1)];
|
||||
private static readonly (int dc, int dr)[] AllDirs = [(0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1)];
|
||||
private static readonly (int dc, int dr)[] KnightOffsets =
|
||||
[
|
||||
(1, 2), (2, 1), (2, -1), (1, -2),
|
||||
|
|
@ -24,7 +23,6 @@ public static class MoveValidator
|
|||
PieceKind.Rook => GetSlidingMoves(start, OrthogonalDirs, 2, board),
|
||||
PieceKind.Bishop => GetSlidingMoves(start, DiagonalDirs, 2, board),
|
||||
PieceKind.Knight => GetKnightMoves(start, board),
|
||||
PieceKind.Queen => GetSlidingMoves(start, AllDirs, 2, board),
|
||||
_ => []
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,24 +145,6 @@ C'est tout. Pas de programmation, pas de route multi-etapes. La piece fait l'all
|
|||
- **Saute par-dessus** les murs et les autres pieces
|
||||
- Statut social : **3**
|
||||
|
||||
#### Dame
|
||||
|
||||
```
|
||||
X X X
|
||||
X X X
|
||||
X X X
|
||||
X X X [Dame] X X X
|
||||
X X X
|
||||
X X X
|
||||
X X X
|
||||
```
|
||||
|
||||
- Se deplace de **1 ou 2 cases** dans les **8 directions** (horizontal, vertical et diagonal)
|
||||
- Combine les mouvements de la Tour et du Fou
|
||||
- Ne peut pas traverser les murs ni les autres pieces
|
||||
- Statut social : **7** (le plus eleve — prioritaire sur toutes les autres pieces)
|
||||
- Piece la plus puissante mais rare — force des choix strategiques
|
||||
|
||||
> A statut egal, la piece de **niveau le plus eleve** est consideree superieure (ex: Tour II > Tour I, Fou III > Cavalier II). En cas d'egalite parfaite, le departage se fait par **direction en sens horaire** depuis la piece qui donne (voir 4.3).
|
||||
|
||||
### 3.3 Occupation et collision
|
||||
|
|
@ -206,7 +188,6 @@ Quand plusieurs transferts sont possibles au meme point, la priorite determine l
|
|||
|
||||
```
|
||||
Hierarchie de statut social (proto) :
|
||||
Dame 7
|
||||
Tour 5
|
||||
Fou 3
|
||||
Cavalier 3
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue