Static Palette class that defines game-wide Color constants for environment, playfield, snake, apple, lighting, and a Segment method that linearly interpolates body-to-tail colors by index and length.
namespace Coilgarden;
/// <summary>
/// Every colour in the game world, in one place.
/// <para>
/// <b>The direction: a cozy garden tray on a table.</b> A shallow terracotta planter holding
/// pale sand, lit warmly from one side, with a deep green snake and one vivid red apple. The
/// palette is warm earth tones plus exactly two saturated accents, which is what lets it be
/// colourful and restrained at the same time.
/// </para>
/// <para>
/// The floor is <em>light</em> and the snake is <em>dark</em>. That inversion is deliberate:
/// it is the cozier and less generic of the two options, it gives the largest possible value
/// gap between the snake and the surface it moves on, and it lets the apple's red read
/// against a neutral rather than competing with a dark background.
/// </para>
/// <para>
/// Two hard rules, both learned the hard way:
/// </para>
/// <list type="number">
/// <item>Every channel stays within 0..1. Above 1 the tint path wraps rather than clamping
/// and the colour comes out completely wrong - a green of 1.30 rendered as purple.</item>
/// <item>Contrast comes from <em>value</em>, not saturation. Saturated colours next to each
/// other read as noise; the same hues separated by lightness read as clean.</item>
/// </list>
/// </summary>
public static class Palette
{
// ---------------------------------------------------------------- environment
/// <summary>Behind and around the tray. Warm and deep, so the tray reads as lit.</summary>
public static readonly Color Backdrop = new( 0.082f, 0.070f, 0.086f );
/// <summary>The table the tray sits on - a shade up from the backdrop, so the tray has a surface.</summary>
public static readonly Color Table = new( 0.184f, 0.133f, 0.106f );
/// <summary>The planter's outer wall.</summary>
public static readonly Color TrayRim = new( 0.784f, 0.404f, 0.227f );
/// <summary>The rim's inner face, which is turned away from the key light.</summary>
public static readonly Color TrayRimInner = new( 0.478f, 0.259f, 0.176f );
/// <summary>A thin lighter line along the top of the rim, so the edge catches the light.</summary>
public static readonly Color TrayRimTop = new( 0.906f, 0.588f, 0.361f );
// ---------------------------------------------------------------- the playfield
/// <summary>Pale sand. The two soil tones differ by about 4% so the grid is legible but silent.</summary>
public static readonly Color SoilLight = new( 1.000f, 0.965f, 0.878f );
public static readonly Color SoilDark = new( 0.941f, 0.898f, 0.804f );
// ---------------------------------------------------------------- the snake
/// <summary>The head. Warmer and lighter than the body, so it is the first thing found.</summary>
public static readonly Color SnakeHead = new( 0.325f, 0.686f, 0.310f );
public static readonly Color SnakeBody = new( 0.169f, 0.494f, 0.251f );
/// <summary>The tail end. Darker, so the body reads as travelling head-first in a still frame.</summary>
public static readonly Color SnakeTail = new( 0.098f, 0.325f, 0.176f );
/// <summary>
/// Eyes. Nearly black rather than white-with-a-pupil: at this size a dark dot reads
/// cleanly and a two-tone eye turns to mush.
/// </summary>
public static readonly Color SnakeEye = new( 0.086f, 0.106f, 0.098f );
// ---------------------------------------------------------------- the apple
/// <summary>
/// The one high-chroma red in the game, used nowhere else, so the eye always finds the
/// apple first without any effect being needed.
/// </summary>
public static readonly Color AppleFlesh = new( 0.949f, 0.216f, 0.157f );
public static readonly Color AppleStem = new( 0.298f, 0.196f, 0.129f );
public static readonly Color AppleLeaf = new( 0.510f, 0.729f, 0.251f );
/// <summary>
/// Sparkles thrown when an apple is eaten: the apple's own red, a shade deeper, so the burst
/// reads as <em>that apple</em> coming apart rather than as a generic effect.
/// <para>
/// They were originally a pale salmon - the apple lightened towards the sand - and at that
/// value they were completely invisible however correctly they simulated. On a light floor a
/// burst has to be <em>darker</em> than what it lands on, not brighter.
/// </para>
/// </summary>
public static readonly Color Sparkle = new( 0.878f, 0.243f, 0.122f );
/// <summary>
/// The head at the instant of death. Near-white so the moment of impact is unmissable, and
/// it is the only near-white in the game.
/// </summary>
public static readonly Color DeathFlash = new( 1.000f, 0.949f, 0.906f );
/// <summary>
/// What the snake settles to after dying: a dark, dry olive, like a plant that has gone
/// over. It wilts rather than vanishing, so the shape of the run the player just made is
/// still on the tray behind the game-over card.
/// <para>
/// Kept clearly darker than the sand. A first attempt landed close to the sand's own value
/// and the dead snake half-dissolved into the floor - which loses the whole point of leaving
/// it there.
/// </para>
/// </summary>
public static readonly Color SnakeWilt = new( 0.216f, 0.192f, 0.129f );
// ---------------------------------------------------------------- lighting
/// <summary>Warm key light, like late afternoon through a window.</summary>
public static readonly Color KeyLight = new( 1.000f, 0.929f, 0.808f );
/// <summary>Cool fill, so shadows are blue-grey rather than muddy brown.</summary>
public static readonly Color FillLight = new( 0.255f, 0.310f, 0.408f );
/// <summary>Ambient wash. Keeps shadowed sides readable instead of black.</summary>
public static readonly Color Ambient = new( 0.478f, 0.435f, 0.400f );
/// <summary>Vignette tint - the backdrop hue, so the edges recede rather than darken.</summary>
public static readonly Color Vignette = new( 0.055f, 0.043f, 0.067f );
/// <summary>
/// The colour a segment is drawn in, by distance from the head.
/// <para>
/// The head is its own colour; the rest fade body-to-tail. The gradient is over the body
/// only, so a two-segment snake still has a distinct head rather than being half-faded.
/// </para>
/// </summary>
public static Color Segment( int index, int length )
{
if ( index <= 0 ) return SnakeHead;
if ( length <= 2 ) return SnakeBody;
var t = (index - 1) / (float)(length - 2);
return Color.Lerp( SnakeBody, SnakeTail, t );
}
}